add analytics page for tests conducted
- add robot.txt entry - fix graph render with new set of actions
This commit is contained in:
@@ -1,3 +1,3 @@
|
|||||||
# https://www.robotstxt.org/robotstxt.html
|
# https://www.robotstxt.org/robotstxt.html
|
||||||
User-agent: *
|
User-agent: *
|
||||||
Disallow:
|
Disallow: /
|
||||||
|
|||||||
@@ -50,9 +50,11 @@ def consolidate(curdir):
|
|||||||
# df_csv_append.to_csv("D8 Merged.csv", index=False)
|
# df_csv_append.to_csv("D8 Merged.csv", index=False)
|
||||||
|
|
||||||
|
|
||||||
for file in os.listdir(rootdir):
|
if __name__ == "__main__":
|
||||||
|
for file in os.listdir(rootdir):
|
||||||
d = os.path.join(rootdir, file)
|
d = os.path.join(rootdir, file)
|
||||||
if os.path.isdir(d):
|
if os.path.isdir(d):
|
||||||
# print(d)
|
# print(d)
|
||||||
# os.chdir(d)
|
# os.chdir(d)
|
||||||
consolidate(d)
|
consolidate(d)
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ class AbsorbanceGraph extends Component {
|
|||||||
this.state = { url: this.props.url, show: this.props.show };
|
this.state = { url: this.props.url, show: this.props.show };
|
||||||
}
|
}
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.drawChart(this.props.url);
|
this.drawChart(this.state.url);
|
||||||
}
|
}
|
||||||
|
|
||||||
drawChart(url) {
|
drawChart(url) {
|
||||||
@@ -51,7 +51,9 @@ class AbsorbanceGraph extends Component {
|
|||||||
.then(response => response.data.text())
|
.then(response => response.data.text())
|
||||||
.then((text) => {
|
.then((text) => {
|
||||||
const testData = d3.csvParse(text.replaceAll('"', '')).filter(x => {
|
const testData = d3.csvParse(text.replaceAll('"', '')).filter(x => {
|
||||||
if (x.NM > 350 && x.NM < 600 && x.CA !== "-Infinity" && x.CA !== "Infinity") return x;
|
if (x.NM > 350 && x.NM < 600
|
||||||
|
&& x.CA !== "-Infinity" && x.CA !== "Infinity"
|
||||||
|
&& !isNaN(x.CA)) return x;
|
||||||
});
|
});
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
.dashboard-title::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -72,11 +76,20 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.clear-button {
|
.clear-button {
|
||||||
background-color: aquamarine;
|
background-color: rgb(207, 235, 250);
|
||||||
width: 3vw;
|
width: 10vw;
|
||||||
height: 5vh;
|
height: 5vh;
|
||||||
margin: 5px;
|
margin: 5px;
|
||||||
border-radius: 50%;
|
border-radius: 10px;
|
||||||
|
font-size: medium;
|
||||||
|
font-weight: 600;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.graph-icon {
|
||||||
|
width: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
table,
|
table,
|
||||||
|
|||||||
@@ -69,14 +69,23 @@ const Devices = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const changeTest = ({ value }) => {
|
const changeTest = ({ value }) => {
|
||||||
setCurTest(null); setCurTestData(null); setShowGraph(false);
|
setCurTestData(null); setShowGraph(false);
|
||||||
if (curTest) alert('Click clear button first');
|
|
||||||
setCurTest(value);
|
setCurTest(value);
|
||||||
setCurTestData(allTests.find(x => x._id === value));
|
}
|
||||||
|
|
||||||
|
const showTestGraph = () => {
|
||||||
|
if (!curTest) alert('Select a test');
|
||||||
|
setCurTestData(allTests.find(x => x._id === curTest));
|
||||||
setShowGraph(true);
|
setShowGraph(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
const menuPortalTarget = document.getElementById('root');
|
const toggleShowGraph = () => {
|
||||||
|
if (!curTestData) {
|
||||||
|
showTestGraph();
|
||||||
|
} else {
|
||||||
|
setCurTest(null); setCurTestData(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!allTests) {
|
if (!allTests) {
|
||||||
@@ -103,7 +112,7 @@ const Devices = () => {
|
|||||||
}, [curTestData, curTest, device, testDate]);
|
}, [curTestData, curTest, device, testDate]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="devices-page">
|
||||||
<div className='title'>Insights</div>
|
<div className='title'>Insights</div>
|
||||||
<div className='dropdown-group'>
|
<div className='dropdown-group'>
|
||||||
<Dropdown className='device-dropdown' options={devices} onChange={changeDevice} value={device} placeholder="Select an device" />
|
<Dropdown className='device-dropdown' options={devices} onChange={changeDevice} value={device} placeholder="Select an device" />
|
||||||
@@ -114,11 +123,11 @@ const Devices = () => {
|
|||||||
<Select className='day-dropdown' options={days.map(x => { return { value: x, label: x }; })} menuPlacement='auto' maxMenuHeight={250} menuShouldScrollIntoView={true} onChange={changeTestDate} />
|
<Select className='day-dropdown' options={days.map(x => { return { value: x, label: x }; })} menuPlacement='auto' maxMenuHeight={250} menuShouldScrollIntoView={true} onChange={changeTestDate} />
|
||||||
<Select className='test-dropdown' options={tests.map(x => { return { value: x, label: x }; })} menuPlacement='auto' maxMenuHeight={220} menuShouldScrollIntoView={true} onChange={changeTestDate} /> */}
|
<Select className='test-dropdown' options={tests.map(x => { return { value: x, label: x }; })} menuPlacement='auto' maxMenuHeight={220} menuShouldScrollIntoView={true} onChange={changeTestDate} /> */}
|
||||||
|
|
||||||
<button className='clear-button' onClick={() => { setCurTest(null); setCurTestData(null); }}>Clear</button>
|
<button className='clear-button' onClick={toggleShowGraph}><img className="graph-icon" src={require('./graph_icon.png')}></img>{curTestData ? 'Clear' : 'Show'}</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='graph-person-conatiner'>
|
<div className='graph-person-conatiner'>
|
||||||
{!curTestData && <div className='refresh-graph-message'>Select test device, date and person to get the detailed graph. Then, refresh or click clear button to render another entry.</div>}
|
{!curTestData && <div className='refresh-graph-message'>Select test device, date and person to get the detailed graph. Then, click show button to render graph.</div>}
|
||||||
{curTestData?.csvPath && <DeviceGraph url={curTestData?.csvPath} show={showGraph} />}
|
{curTestData?.csvPath && <DeviceGraph url={curTestData?.csvPath} show={showGraph} />}
|
||||||
|
|
||||||
{curTestData && <div className='person-details'>
|
{curTestData && <div className='person-details'>
|
||||||
|
|||||||
150
src/components/TestAnalytics.js
Normal file
150
src/components/TestAnalytics.js
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { query, collection, onSnapshot, getDocs } from 'firebase/firestore';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
import Dropdown from 'react-dropdown';
|
||||||
|
import 'react-dropdown/style.css';
|
||||||
|
import moment from 'moment';
|
||||||
|
import Select from 'react-select';
|
||||||
|
|
||||||
|
import DeviceGraph from "./DeviceGraph";
|
||||||
|
import db from '../firebase';
|
||||||
|
import './Devices.css';
|
||||||
|
import TestAnalyticsGraph from './TestAnalyticsGraph';
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
menuList: (base) => ({
|
||||||
|
...base,
|
||||||
|
|
||||||
|
"::-webkit-scrollbar": {
|
||||||
|
width: "4px",
|
||||||
|
height: "0px",
|
||||||
|
},
|
||||||
|
"::-webkit-scrollbar-track": {
|
||||||
|
background: "#f1f1f1"
|
||||||
|
},
|
||||||
|
"::-webkit-scrollbar-thumb": {
|
||||||
|
background: "#888"
|
||||||
|
},
|
||||||
|
"::-webkit-scrollbar-thumb:hover": {
|
||||||
|
background: "#555"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
const TestAnalytics = () => {
|
||||||
|
const [device, setDevice] = useState("Select a device");
|
||||||
|
const [testDate, setTestDate] = useState("Select a test date");
|
||||||
|
const [curTest, setCurTest] = useState("Select a test");
|
||||||
|
const [curTestData, setCurTestData] = useState(null);
|
||||||
|
const [devices, setDevices] = useState([]);
|
||||||
|
const [days, setDays] = useState([]);
|
||||||
|
const [tests, setTests] = useState([]);
|
||||||
|
const [allTests, setAllTests] = useState(null);
|
||||||
|
const [showGraph, setShowGraph] = useState(false);
|
||||||
|
const [dailyResults, setDailyResults] = useState(null);
|
||||||
|
|
||||||
|
const changeDevice = ({ value }) => {
|
||||||
|
console.log(value);
|
||||||
|
// console.log(event);
|
||||||
|
|
||||||
|
setDevice(value);
|
||||||
|
setDays([...new Set(allTests
|
||||||
|
.filter((x) => {
|
||||||
|
if (x.deviceSerialNumber === value) return x;
|
||||||
|
})
|
||||||
|
.map(x => moment(x.testTime).format("YYYY-MM-DD")))]
|
||||||
|
.sort()
|
||||||
|
.reverse());
|
||||||
|
}
|
||||||
|
|
||||||
|
const changeTestDate = ({ value }) => {
|
||||||
|
setTestDate(value);
|
||||||
|
setCurTest(null);
|
||||||
|
setCurTestData(null);
|
||||||
|
setShowGraph(false);
|
||||||
|
setTests(allTests
|
||||||
|
.filter((x) => {
|
||||||
|
if (x.deviceSerialNumber === device && moment(x.testTime).isBetween(moment(value).startOf('day'), moment(value).endOf('day'))) return x;
|
||||||
|
})
|
||||||
|
.map(x => x._id)
|
||||||
|
.sort());
|
||||||
|
}
|
||||||
|
|
||||||
|
const changeTest = ({ value }) => {
|
||||||
|
setCurTestData(null); setShowGraph(false);
|
||||||
|
setCurTest(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const showTestGraph = () => {
|
||||||
|
if (!curTest) alert('Select a test');
|
||||||
|
setCurTestData(allTests.find(x => x._id === curTest));
|
||||||
|
setShowGraph(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleShowGraph = () => {
|
||||||
|
if (!curTestData) {
|
||||||
|
showTestGraph();
|
||||||
|
} else {
|
||||||
|
setCurTest(null); setCurTestData(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!allTests) {
|
||||||
|
const q = query(collection(db, "testData"));
|
||||||
|
|
||||||
|
const unsub = onSnapshot(q, (querySnapshot) => {
|
||||||
|
const testDevices = new Set();
|
||||||
|
const hposTests = [];
|
||||||
|
querySnapshot.forEach((document) => {
|
||||||
|
const hposTestData = document.data();
|
||||||
|
hposTests.push(hposTestData);
|
||||||
|
const { deviceSerialNumber } = hposTestData;
|
||||||
|
testDevices.add(deviceSerialNumber);
|
||||||
|
// console.log(document.data());
|
||||||
|
});
|
||||||
|
const groups = [...testDevices];
|
||||||
|
// console.log(groups);
|
||||||
|
setDevices(groups);
|
||||||
|
setAllTests(hposTests);
|
||||||
|
|
||||||
|
// let rsult;
|
||||||
|
const dailyCounts = hposTests.reduce(function (result, test) {
|
||||||
|
const day = moment(test.testTime).format("YYYY-MM-DD");
|
||||||
|
if (!result[day]) {
|
||||||
|
result[day] = 0;
|
||||||
|
}
|
||||||
|
result[day]++;
|
||||||
|
return result;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
setDailyResults(Object.entries(dailyCounts).sort().map(x => {
|
||||||
|
return { date: x[0], count: x[1] };
|
||||||
|
}));
|
||||||
|
const rss = _.groupBy(hposTests, function (test) {
|
||||||
|
return moment(test.testTime).startOf('day').format();
|
||||||
|
});
|
||||||
|
// setDailyResults(rss);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => unsub;
|
||||||
|
}
|
||||||
|
}, [curTestData, curTest, device, testDate]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="devices-page">
|
||||||
|
<div className='title'>Analytics</div>
|
||||||
|
{/* <div className='dropdown-group'>
|
||||||
|
<Dropdown className='device-dropdown' options={devices} onChange={changeDevice} value={device} placeholder="Select an device" />
|
||||||
|
<Dropdown className='day-dropdown' options={days} onChange={changeTestDate} value={testDate} placeholder="Select an test date" />
|
||||||
|
<Dropdown className='test-dropdown' options={tests} onChange={changeTest} value={curTest} placeholder="Select an test" />
|
||||||
|
|
||||||
|
<button className='clear-button' onClick={toggleShowGraph}><img className="graph-icon" src={require('./graph_icon.png')}></img>{curTestData ? 'Clear' : 'Show'}</button>
|
||||||
|
</div> */}
|
||||||
|
|
||||||
|
{dailyResults && <TestAnalyticsGraph data={dailyResults} />}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TestAnalytics;
|
||||||
97
src/components/TestAnalyticsGraph.js
Normal file
97
src/components/TestAnalyticsGraph.js
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import * as d3 from 'd3';
|
||||||
|
import { sgg } from 'ml-savitzky-golay-generalized';
|
||||||
|
import axios from 'axios';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
import './DeviceGraph.css';
|
||||||
|
|
||||||
|
class TestAnalyticsGraph extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = { data: this.props.data, show: this.props.show };
|
||||||
|
}
|
||||||
|
componentDidMount() {
|
||||||
|
this.drawChart(this.state.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
drawChart(data) {
|
||||||
|
if (!data) return;
|
||||||
|
var margin = { top: 20, right: 20, bottom: 30, left: 50 },
|
||||||
|
width = 960 - margin.left - margin.right,
|
||||||
|
height = 500 - margin.top - margin.bottom;
|
||||||
|
|
||||||
|
// set the ranges
|
||||||
|
var x = d3.scaleTime().range([0, width]);
|
||||||
|
var y = d3.scaleLinear().range([height, 0]);
|
||||||
|
|
||||||
|
// define the line
|
||||||
|
var valueline = d3.line()
|
||||||
|
.x(function (d) { return x(moment(d.date)); })
|
||||||
|
.y(function (d) { return y(d.count); });
|
||||||
|
|
||||||
|
var svg = d3.select("#result-graph").append("svg")
|
||||||
|
.attr("width", width + margin.left + margin.right)
|
||||||
|
.attr("height", height + margin.top + margin.bottom)
|
||||||
|
.append("g").attr("transform",
|
||||||
|
"translate(" + margin.left + "," + margin.top + ")");
|
||||||
|
var g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
|
||||||
|
|
||||||
|
// const options = {
|
||||||
|
// windowSize: 5,
|
||||||
|
// derivative: 0,
|
||||||
|
// polynomial: 3,
|
||||||
|
// };
|
||||||
|
// const sggResult = sgg(data.map(x => x.CA), (Math.PI * 2) / data.length, options);
|
||||||
|
|
||||||
|
// data.forEach(function (d, index) {
|
||||||
|
// d.NM = d.NM;
|
||||||
|
// d.CA = sggResult[index];
|
||||||
|
// });
|
||||||
|
// data.forEach(function (d) {
|
||||||
|
// d.date = d.date;
|
||||||
|
// d.count = +d.count;
|
||||||
|
// });
|
||||||
|
|
||||||
|
x.domain(d3.extent(data, function (d) { return moment(d.date); }));
|
||||||
|
y.domain([0, d3.max(data, function (d) { return d.count; })]);
|
||||||
|
|
||||||
|
svg.append("path")
|
||||||
|
.data([data])
|
||||||
|
.attr("class", "line")
|
||||||
|
.attr("d", valueline);
|
||||||
|
|
||||||
|
svg.append("g")
|
||||||
|
.attr("transform", "translate(0," + height + ")")
|
||||||
|
.call(d3.axisBottom(x))
|
||||||
|
.append("text")
|
||||||
|
// .attr("transform", "rotate(-90)")
|
||||||
|
.attr("x", 400)
|
||||||
|
.attr("y", 30)
|
||||||
|
.attr("dx", "0.71em")
|
||||||
|
.attr("fill", "#000")
|
||||||
|
.text("Test time");
|
||||||
|
|
||||||
|
svg.append("g")
|
||||||
|
.call(d3.axisLeft(y))
|
||||||
|
.append("text")
|
||||||
|
.attr("transform", "rotate(-90)")
|
||||||
|
.attr("y", 6)
|
||||||
|
.attr("dy", "0.71em")
|
||||||
|
.attr("fill", "#000")
|
||||||
|
.text("Tests Count");
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className='graph-card'>
|
||||||
|
<div id="result-graph" style={{ margin: '1em' }}>
|
||||||
|
</div>
|
||||||
|
<div className='graph-title'>count vs result</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export default TestAnalyticsGraph;
|
||||||
BIN
src/components/graph_icon.png
Normal file
BIN
src/components/graph_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
@@ -8,6 +8,7 @@ import './index.css';
|
|||||||
import App from './App';
|
import App from './App';
|
||||||
import reportWebVitals from './reportWebVitals';
|
import reportWebVitals from './reportWebVitals';
|
||||||
import Devices from './components/Devices';
|
import Devices from './components/Devices';
|
||||||
|
import TestAnalytics from './components/TestAnalytics';
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
@@ -21,6 +22,10 @@ const router = createBrowserRouter([
|
|||||||
{
|
{
|
||||||
path: "/insights",
|
path: "/insights",
|
||||||
element: <Devices />,
|
element: <Devices />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/analytics",
|
||||||
|
element: <TestAnalytics />,
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user