diff --git a/src/App.scss b/src/App.scss index ade1014c..b9420145 100644 --- a/src/App.scss +++ b/src/App.scss @@ -3,3 +3,7 @@ margin-right: 0.25rem; } } + +.MuiSvgIcon-root { + margin-top: -4px; +} \ No newline at end of file diff --git a/src/Cat/Cat.tsx b/src/Cat/Cat.tsx index 78c91df1..08ad1318 100644 --- a/src/Cat/Cat.tsx +++ b/src/Cat/Cat.tsx @@ -313,6 +313,7 @@ const ShowCat = ({ )} +

Battery diff --git a/src/HistoricalData/HistoricalDataChart.tsx b/src/HistoricalData/HistoricalDataChart.tsx index 67851b69..56c77d0e 100644 --- a/src/HistoricalData/HistoricalDataChart.tsx +++ b/src/HistoricalData/HistoricalDataChart.tsx @@ -1,9 +1,12 @@ import * as am4core from '@amcharts/amcharts4/core' import * as am4charts from '@amcharts/amcharts4/charts' -import React, { useEffect, useRef } from 'react' +import React, { useEffect, useRef, useState } from 'react' import { v4 } from 'uuid' import Athena from 'aws-sdk/clients/athena' import { exponential } from 'backoff' +import { parseAthenaResult } from '../parseAthenaResult' +import { Loading } from '../Loading/Loading' +import { Error as ShowError } from '../Error/Error' import './HistoricalDataChart.scss' @@ -20,11 +23,32 @@ export const HistoricalDataChart = ({ }) => { const chartRef = useRef() const uuid = useRef(v4()) + const [loading, setLoading] = useState(true) + const [error, setError] = useState() useEffect(() => { + const chart = am4core.create(uuid.current, am4charts.XYChart) + chartRef.current = chart + + const dateAxis = chart.xAxes.push( + new am4charts.DateAxis(), + ) + dateAxis.fontSize = 10 + dateAxis.renderer.minGridDistance = 60 + + const valueAxes = chart.yAxes.push( + new am4charts.ValueAxis(), + ) + valueAxes.fontSize = 10 + + const series = chart.series.push(new am4charts.LineSeries()) + series.dataFields.valueY = 'value' + series.dataFields.dateX = 'date' + series.tooltipText = '{value}' + athena .startQueryExecution({ WorkGroup: athenaWorkGroup, - QueryString: `SELECT * FROM ${athenaDataBase}.${athenaRawDataTable} WHERE deviceId='${deviceId}' AND reported.bat IS NOT NULL`, + QueryString: `SELECT reported.bat.ts as date, reported.bat.v as value FROM ${athenaDataBase}.${athenaRawDataTable} WHERE deviceId='${deviceId}' AND reported.bat IS NOT NULL ORDER BY reported.bat.v DESC LIMIT 100`, }) .promise() .then(({ QueryExecutionId }) => { @@ -76,46 +100,30 @@ export const HistoricalDataChart = ({ reject(new Error(`Timed out waiting for query ${QueryExecutionId}`)) }) b.backoff() - }) - .then(() => athena.getQueryResults({ QueryExecutionId }).promise()) + }).then(() => athena.getQueryResults({ QueryExecutionId }).promise()) }) - .then(console.log) - .catch(console.error) - - const chart = am4core.create(uuid.current, am4charts.XYChart) - chartRef.current = chart - - const data = [] - let value = 50 - for (let i = 0; i < 300; i++) { - const date = new Date() - date.setHours(0, 0, 0, 0) - date.setDate(i) - value -= Math.round((Math.random() < 0.5 ? 1 : -1) * Math.random() * 10) - data.push({ date: date, value: value }) - } - - chart.data = data - - const dateAxis = chart.xAxes.push( - new am4charts.DateAxis(), - ) - dateAxis.fontSize = 10 - dateAxis.renderer.minGridDistance = 60 - - const valueAxes = chart.yAxes.push( - new am4charts.ValueAxis(), - ) - valueAxes.fontSize = 10 + .then(({ ResultSet }) => { + if (!ResultSet || !ResultSet.Rows) { + throw new Error(`No resultset returned.`) + } + return parseAthenaResult(ResultSet) + }) + .then(data => { + setLoading(false) + chart.data = data + }) + .catch(setError) - const series = chart.series.push(new am4charts.LineSeries()) - series.dataFields.valueY = 'value' - series.dataFields.dateX = 'date' - series.tooltipText = '{value}' return () => { chartRef.current && chartRef.current.dispose() } - }, [athena, deviceId]) + }, [athena, deviceId, setLoading, setError]) - return

+ return ( + <> + {loading && } + {error && } +
+ + ) } diff --git a/src/parseAthenaResult.spec.ts b/src/parseAthenaResult.spec.ts new file mode 100644 index 00000000..4509a974 --- /dev/null +++ b/src/parseAthenaResult.spec.ts @@ -0,0 +1,62 @@ +import { parseAthenaResult } from './parseAthenaResult' + +describe('parseAthenaResult', () => { + it('parse an Athena result into an array of values', () => { + expect( + parseAthenaResult({ + Rows: [ + { Data: [{ VarCharValue: 'date' }, { VarCharValue: 'value' }] }, + { + Data: [ + { VarCharValue: '2019-08-01T10:29:54.406Z' }, + { VarCharValue: '2607' }, + ], + }, + { + Data: [ + { VarCharValue: '2019-07-31T08:34:20.765Z' }, + { VarCharValue: '2046' }, + ], + }, + ], + ResultSetMetadata: { + ColumnInfo: [ + { + CatalogName: 'hive', + SchemaName: '', + TableName: '', + Name: 'date', + Label: 'date', + Type: 'varchar', + Precision: 2147483647, + Scale: 0, + Nullable: 'UNKNOWN', + CaseSensitive: true, + }, + { + CatalogName: 'hive', + SchemaName: '', + TableName: '', + Name: 'value', + Label: 'value', + Type: 'integer', + Precision: 10, + Scale: 0, + Nullable: 'UNKNOWN', + CaseSensitive: false, + }, + ], + }, + }), + ).toEqual([ + { + date: '2019-08-01T10:29:54.406Z', + value: 2607, + }, + { + date: '2019-07-31T08:34:20.765Z', + value: 2046, + }, + ]) + }) +}) diff --git a/src/parseAthenaResult.ts b/src/parseAthenaResult.ts new file mode 100644 index 00000000..5eb1e242 --- /dev/null +++ b/src/parseAthenaResult.ts @@ -0,0 +1,40 @@ +import Athena from 'aws-sdk/clients/athena' + +const parseValue = ({ value, type }: { value?: string; type: string }) => { + if (value === undefined) { + return value + } + switch (type) { + case 'integer': + return parseInt(value, 10) + default: + return value + } +} + +export type ParsedResult = { [key: string]: string | number }[] + +export const parseAthenaResult = ({ + Rows, + ResultSetMetadata, +}: Athena.ResultSet): ParsedResult => { + if (!Rows || !ResultSetMetadata || !ResultSetMetadata.ColumnInfo) { + return [] + } + const { ColumnInfo } = ResultSetMetadata + return Rows.slice(1).map(({ Data }) => { + if (!Data) { + return {} + } + return ColumnInfo.reduce( + (result, { Name, Type }, key) => ({ + ...result, + [Name]: parseValue({ + value: Data[key].VarCharValue, + type: Type, + }), + }), + {}, + ) + }) +}