Skip to content

Commit 313b598

Browse files
authored
Merge pull request #60 from UMSATS/feat/backend-integration
Feat: Integrate Backend for Experiment Tab
2 parents e74c003 + 4c9c1cf commit 313b598

11 files changed

Lines changed: 6969 additions & 4879 deletions

File tree

client/package-lock.json

Lines changed: 6822 additions & 4829 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"@fontsource/poppins": "^5.0.14",
99
"@mui/icons-material": "^5.15.16",
1010
"@mui/material": "^5.15.18",
11+
"@tanstack/react-query": "^5.68.0",
1112
"@testing-library/jest-dom": "^5.17.0",
1213
"@testing-library/react": "^13.4.0",
1314
"@testing-library/user-event": "^13.5.0",
@@ -43,5 +44,6 @@
4344
"last 1 firefox version",
4445
"last 1 safari version"
4546
]
46-
}
47+
},
48+
"proxy": "http://localhost:5000"
4749
}

client/src/api/api.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// A general asynchronous function to fetch data from an endpoint
2+
export const fetchData = async (endpoint) => {
3+
try {
4+
const response = await fetch(endpoint);
5+
6+
if (!response.ok) {
7+
throw new Error(`Error: ${response.status}`);
8+
}
9+
10+
return await response.json();
11+
} catch (error) {
12+
console.error("API Fetch Error:", error);
13+
throw error;
14+
}
15+
};

client/src/api/experiment.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// API functions for the Experiment tab
2+
3+
import { fetchData } from './api';
4+
import { dateToRFC } from './utils';
5+
6+
const NUMBER_OF_WELLS = 16;
7+
8+
// Queries all wells for either temperature or luminosity
9+
const queryAllWells = async (start, end, key) => {
10+
let endpoint = `/payload/wells`
11+
12+
if (key === 'temperature') endpoint += `/temp`
13+
else if (key === 'luminosity') endpoint += `/lumin`
14+
15+
const params = new URLSearchParams({start: dateToRFC(start), end: dateToRFC(end)})
16+
17+
const requests = Array.from({ length: NUMBER_OF_WELLS }, (_, i) =>
18+
fetchData(`${endpoint}/${i + 1}?${params.toString()}`)
19+
);
20+
21+
const responses = await Promise.all(requests);
22+
23+
return responses.map((wellData) =>
24+
wellData.map(item => [item.timestamp, item[key]])
25+
);
26+
};
27+
28+
// Returns both temperature and luminosity data
29+
export const fetchExperiment = async (start, end) => {
30+
const [temperature, luminosity] = await Promise.all([
31+
queryAllWells(start, end, 'temperature'),
32+
queryAllWells(start, end, 'luminosity')
33+
]);
34+
return { temperature, luminosity };
35+
};

client/src/api/testData.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Contains randomly generated datasets, useful
3+
* for testing data display components without
4+
* connecting to the database.
5+
*/
6+
7+
let hour = 1000 * 60 * 60;
8+
9+
/* Payload datasets */
10+
export const testTemperatureData = Array.from({ length: 4 }, () =>
11+
Array.from({ length: 720 }, (_, i) => [
12+
Date.now() - i * hour,
13+
(Math.random() * 6 - 3).toFixed(1)
14+
])
15+
);
16+
17+
export const testLuminosityData = Array.from({ length: 4 }, () =>
18+
Array.from({ length: 720 }, (_, i) => [
19+
Date.now() - i * hour,
20+
(Math.random() * 200 + 600).toFixed(0)
21+
])
22+
);
23+
24+
export const testWellActivity = [
25+
1, 1, 0, 1,
26+
1, 1, 0, 1,
27+
1, 1, 0, 1,
28+
1, 1, 0, 1
29+
]

client/src/api/utils.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// General API utility functions
2+
3+
export const dateToRFC = (d) => {
4+
// removes the fractional seconds from the ISO string
5+
return d.toISOString().replace(/\.\d{3}Z$/, 'Z');
6+
}

client/src/components/ExperimentData.jsx

Lines changed: 0 additions & 21 deletions
This file was deleted.

client/src/components/TimeRangePicker.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export default function TimeRangePicker() {
1616

1717
const rangeRef = useRef({
1818
// Min (and maybe max) date should be based on data timestamps
19-
min: new Date(timeRange.start.getTime() - 1000 * 3600 * 24 * 30),
19+
min: new Date(timeRange.start.getTime() - 1000 * 3600 * 24 * 365),
2020
max: new Date(timeRange.end.getTime() + 1000 * 3600 * 24)
2121
});
2222

client/src/index.js

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react';
22
import ReactDOM from 'react-dom/client';
33
import { BrowserRouter as Router, Route, Routes, Navigate } from 'react-router-dom';
4+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
45
import './index.css';
56
import '@fontsource/poppins/400.css';
67
import '@fontsource/poppins/500.css';
@@ -17,28 +18,32 @@ import OrientationTab from './pages/OrientationTab';
1718
import RegistrationPage from './pages/RegistrationPage';
1819
import LoginPage from "./pages/LoginPage";
1920

21+
const queryClient = new QueryClient();
22+
2023
const root = ReactDOM.createRoot(document.getElementById('root'));
2124
root.render(
2225
<React.StrictMode>
23-
<CustomProvider theme="dark">
24-
<TimeRangeProvider>
25-
<Router>
26-
<Routes>
27-
<Route element={<PageLayout />}>
28-
<Route path="/" element={<Navigate to="/dashboard" replace />} />
29-
<Route path="/dashboard" element={<Dashboard />} />
30-
<Route path="/experiment" element={<ExperimentTab />} />
31-
<Route path="/event-log" element={<EventLogTab />} />
32-
<Route path="/temperatures" element={<TemperaturesTab />} />
33-
<Route path="/battery" element={<BatteryTab />} />
34-
<Route path="/orientation" element={<OrientationTab />} />
35-
</Route>
36-
<Route path="/login" element={<LoginPage />} />
37-
<Route path="/signup" element={<RegistrationPage />} />
38-
</Routes>
39-
</Router>
40-
</TimeRangeProvider>
41-
</CustomProvider>
26+
<QueryClientProvider client={queryClient}>
27+
<CustomProvider theme="dark">
28+
<TimeRangeProvider>
29+
<Router>
30+
<Routes>
31+
<Route element={<PageLayout />}>
32+
<Route path="/" element={<Navigate to="/dashboard" replace />} />
33+
<Route path="/dashboard" element={<Dashboard />} />
34+
<Route path="/experiment" element={<ExperimentTab />} />
35+
<Route path="/event-log" element={<EventLogTab />} />
36+
<Route path="/temperatures" element={<TemperaturesTab />} />
37+
<Route path="/battery" element={<BatteryTab />} />
38+
<Route path="/orientation" element={<OrientationTab />} />
39+
</Route>
40+
<Route path="/login" element={<LoginPage />} />
41+
<Route path="/signup" element={<RegistrationPage />} />
42+
</Routes>
43+
</Router>
44+
</TimeRangeProvider>
45+
</CustomProvider>
46+
</QueryClientProvider>
4247
</React.StrictMode>
4348
);
4449

client/src/pages/ExperimentTab.js

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import React, { useState } from 'react';
2-
import {
3-
temperatureData, luminosityData, labels, wellActivity
4-
} from '../components/ExperimentData.jsx';
1+
import React, { useState, useEffect } from 'react';
2+
import { useQuery } from '@tanstack/react-query';
3+
import { fetchExperiment } from '../api/experiment.js';
54
import Box from '@mui/material/Box';
65
import FormControlLabel from '@mui/material/FormControlLabel';
76
import Switch from '@mui/material/Switch';
87
import TimeseriesChart from '../components/TimeseriesChart.jsx';
98
import { useTimeContext } from "../components/TimeRangeContext.jsx";
9+
import { testTemperatureData, testLuminosityData, testWellActivity } from '../api/testData.js';
1010

1111
const CHART_WIDTH = 800;
1212

@@ -15,14 +15,37 @@ const chartSize = {
1515
height: 400
1616
}
1717

18+
const chartLabels = Array.from({length: 16}, (_, i) => `Well ${i + 1}`)
19+
1820
export default function ExperimentTab() {
1921
const { timeRange } = useTimeContext();
2022

23+
const [temperatureData, setTemperatureData] = useState([]);
24+
const [luminosityData, setLuminosityData] = useState([]);
25+
2126
const [showInactiveWells, setShowInactiveWells] = useState(true);
2227

28+
const { data, isError, error } = useQuery({
29+
queryKey: ['experiment', timeRange.start, timeRange.end],
30+
queryFn: () => fetchExperiment(timeRange.start, timeRange.end)
31+
});
32+
33+
if (isError) console.log(error.message);
34+
35+
useEffect(() => {
36+
if (data) {
37+
if (data.temperature) {
38+
setTemperatureData(data.temperature);
39+
}
40+
if (data.luminosity) {
41+
setLuminosityData(data.luminosity);
42+
}
43+
}
44+
}, [data]);
45+
2346
const chartProps = {
24-
labels: labels,
25-
seriesActivity: !showInactiveWells ? wellActivity : undefined,
47+
labels: chartLabels,
48+
seriesActivity: !showInactiveWells ? testWellActivity : undefined,
2649
xmin: timeRange.start,
2750
xmax: timeRange.end,
2851
style: {...chartSize}
@@ -39,7 +62,7 @@ export default function ExperimentTab() {
3962
<TimeseriesChart
4063
title="Luminosity (lm)"
4164
dataset={luminosityData}
42-
ymin={550} ymax={850}
65+
ymin={100} ymax={900}
4366
{...chartProps}
4467
/>
4568
<FormControlLabel

0 commit comments

Comments
 (0)