From dddf21761e2bcee9f6e961acf8bc9f4a3504c895 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armin=20Ahmetovi=C4=87?= Date: Wed, 10 Jul 2024 14:22:39 +0200 Subject: [PATCH] Allow to filtering time of DataStream data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add from/to time input fields to filter Interface DataStream data Signed-off-by: Armin Ahmetović --- src/DeviceInterfaceValues.tsx | 190 ++++++++++++++++++++++++++++++---- 1 file changed, 171 insertions(+), 19 deletions(-) diff --git a/src/DeviceInterfaceValues.tsx b/src/DeviceInterfaceValues.tsx index aaa583f6..1b9786cd 100644 --- a/src/DeviceInterfaceValues.tsx +++ b/src/DeviceInterfaceValues.tsx @@ -19,6 +19,7 @@ import React, { ChangeEvent, useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; import { Button, Card, Col, Container, Form, Modal, Row, Spinner, Table } from 'react-bootstrap'; +import DatePicker from 'react-datepicker'; import { AstarteDataTuple, AstarteDataTreeNode, @@ -35,12 +36,10 @@ import _ from 'lodash'; import BackButton from './ui/BackButton'; import Empty from './components/Empty'; import WaitForData from './components/WaitForData'; -import useFetch from './hooks/useFetch'; import { useAstarte } from './AstarteManager'; import { AlertsBanner, useAlerts } from 'AlertManager'; import * as yup from 'yup'; import { getValidationSchema } from 'astarte-client/models/InterfaceValue'; -import DatePicker from 'react-datepicker'; import 'react-datepicker/dist/react-datepicker.css'; const MAX_SHOWN_VALUES = 20; @@ -120,14 +119,16 @@ const ObjectDatastreamTable = ({ if (treeData.length === 0) { return

No data sent by the device.

; } - const objectProperties = Object.keys(treeData[0].value); const orderedData = _.orderBy(treeData, 'timestamp', 'desc'); - return ( <> -
Path
-

{dataTreeNode.endpoint}

+ + +
Path
+

{dataTreeNode.endpoint}

+ +
@@ -517,31 +518,143 @@ const SendInterfaceDataModal = ({ ); }; +interface TimeRangeModalProps { + showTimeModal: boolean; + handleShowTimeModal: () => void; + requestingData: boolean; + fetchInterfaceTimeData: (since?: string, to?: string) => void; +} + +const TimeRangeModal = ({ + showTimeModal, + requestingData, + handleShowTimeModal, + fetchInterfaceTimeData, +}: TimeRangeModalProps) => { + const [fromTime, setFromTime] = useState(null); + const [toTime, setToTime] = useState(null); + const [loading, setLoading] = useState(false); + + const handleFetchData = async () => { + setLoading(true); + try { + const from = fromTime ? fromTime.toISOString() : new Date(0).toISOString(); + const to = toTime ? toTime.toISOString() : new Date().toISOString(); + fetchInterfaceTimeData(from, to); + handleShowTimeModal(); + } catch (error) { + console.error('Error fetching data:', error); + } finally { + setLoading(false); + } + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + handleFetchData(); + }; + + return ( + +
+ + Select Time Range + + + + +
+ From + setFromTime(date)} + showTimeSelect + dateFormat="Pp" + className="form-control" + placeholderText="Select from time" + /> + + + To + setToTime(date)} + showTimeSelect + dateFormat="Pp" + className="form-control" + placeholderText="Select to time" + /> + + + + + + + + + + + ); +}; + export default (): React.ReactElement => { const { deviceId = '', interfaceName = '' } = useParams(); const astarte = useAstarte(); - const deviceDataFetcher = useFetch(() => - astarte.client.getDeviceDataTree({ - deviceId, - interfaceName, - }), - ); const [showModal, setShowModal] = useState(false); + const [showTimeModal, setShowTimeModal] = useState(false); const [sendingData, setSendingData] = useState(false); + const [requestingData, setRequestingData] = useState(false); + const [filteredData, setFilteredData] = + useState>(); const [formAlerts, formAlertsController] = useAlerts(); - const iface = deviceDataFetcher.value?.interface as AstarteInterface; + const [data, setData] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const iface = data?.interface as AstarteInterface; const handleShowModal = () => { setShowModal(!showModal); }; + const handleShowTimeModal = () => { + setShowTimeModal(!showTimeModal); + }; + + const fetchInterfaceData = async (since?: string, to?: string) => { + setRequestingData(true); + astarte.client + .getDeviceDataTree({ + deviceId, + interfaceName, + since, + to, + }) + .then((data) => { + setFilteredData(data as AstarteDataTreeNode); + handleShowTimeModal(); + }) + .catch((err) => { + formAlertsController.showError( + `Could not fetch data to interface: ${err.response.data.errors.detail}`, + ); + handleShowModal(); + }) + .finally(() => { + setRequestingData(false); + }); + }; + const sendInterfaceData = (data: { endpoint: string; value: AstarteDataValue }) => { setSendingData(true); astarte.client .sendDataToInterface({ deviceId, interfaceName, path: data.endpoint, data: data.value }) .then(() => { handleShowModal(); - deviceDataFetcher.refresh(); }) .catch((err) => { formAlertsController.showError( @@ -554,6 +667,23 @@ export default (): React.ReactElement => { }); }; + useEffect(() => { + const fetchDeviceData = async () => { + try { + setLoading(true); + setError(null); + const result = await astarte.client.getDeviceDataTree({ deviceId, interfaceName }); + setData(result); + } catch (err) { + setError(err as Error); + } finally { + setLoading(false); + } + }; + + fetchDeviceData(); + }, [deviceId, interfaceName]); + return (
@@ -566,11 +696,16 @@ export default (): React.ReactElement => { 'POST', `devices/${deviceId}/interfaces/${interfaceName}`, ) && - iface?.ownership === 'server' && ( + data?.interface.ownership === 'server' && ( )} + {data?.interface.type !== 'properties' && ( + + )}
@@ -579,18 +714,27 @@ export default (): React.ReactElement => {
} errorFallback={ - + fetchInterfaceData(deviceId, interfaceName)} + /> } > - {(data) => } + {(data) => + filteredData ? ( + + ) : ( + + ) + } @@ -603,6 +747,14 @@ export default (): React.ReactElement => { sendInterfaceData={sendInterfaceData} /> )} + {showTimeModal && ( + + )} ); };