From 877ba42f4865863565f8b2b08ae1bb40c368b3fd Mon Sep 17 00:00:00 2001 From: Elias Woldie Date: Mon, 24 Jul 2023 15:24:40 -0700 Subject: [PATCH 01/22] Added error bar to the line chart --- src/client/app/actions/lineReadings.ts | 23 +- .../app/components/ErrorBarComponent.tsx | 45 +++ .../app/components/TooltipHelpComponent.tsx | 1 + .../app/components/UIOptionsComponent.tsx | 10 +- .../app/containers/LineChartContainer.ts | 21 +- src/client/app/reducers/graph.ts | 3 +- src/client/app/reducers/lineReadings.ts | 6 +- src/client/app/translations/data.js | 6 +- src/client/app/types/readings.ts | 2 + src/client/app/types/redux/actions.ts | 1 + src/client/app/types/redux/graph.ts | 5 + src/client/app/types/redux/lineReadings.ts | 10 +- src/client/app/utils/api/ReadingsApi.ts | 10 +- src/server/models/Reading.js | 7 +- src/server/routes/unitReadings.js | 8 +- .../sql/reading/create_reading_views.sql | 295 ++++++++++-------- 16 files changed, 296 insertions(+), 157 deletions(-) create mode 100644 src/client/app/components/ErrorBarComponent.tsx diff --git a/src/client/app/actions/lineReadings.ts b/src/client/app/actions/lineReadings.ts index f2301489c..764a630f2 100644 --- a/src/client/app/actions/lineReadings.ts +++ b/src/client/app/actions/lineReadings.ts @@ -131,6 +131,27 @@ function fetchGroupLineReadings(groupIDs: number[], timeInterval: TimeInterval, }; } +/** + * Generates an action object to toggle the visibility of min/max lines. + * @returns {t.ToggleShowMinMaxAction} Action object + */ +function toggleShowMinMax(): t.ToggleShowMinMaxAction { + return { type: ActionType.ToggleShowMinMax }; +} + +/** + * Dispatches the toggleShowMinMax action in a Redux Thunk. + * @returns {Thunk} Thunk action that dispatches toggleShowMinMax when invoked + */ +export const dispatchToggleShowMinMax = (): Thunk => { + return (dispatch: Dispatch): Promise => { + return new Promise((resolve, reject) => { + dispatch(toggleShowMinMax()); + resolve(); + }); + }; +}; + /** * Fetches readings for the line chart of all selected meters and groups, if needed. * @param timeInterval the interval over which to check @@ -159,4 +180,4 @@ export function fetchNeededLineReadings(timeInterval: TimeInterval, unitID: numb return Promise.all(promises); }; -} +} \ No newline at end of file diff --git a/src/client/app/components/ErrorBarComponent.tsx b/src/client/app/components/ErrorBarComponent.tsx new file mode 100644 index 000000000..bd371ebd1 --- /dev/null +++ b/src/client/app/components/ErrorBarComponent.tsx @@ -0,0 +1,45 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import * as React from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +import { ActionType } from '../types/redux/actions'; +import { LineReadingsState } from 'types/redux/lineReadings'; +import translate from '../utils/translate'; +import TooltipMarkerComponent from './TooltipMarkerComponent'; + +/** + * React Component rendering an Error Bar checkbox for toggle operation. + * @returns Error Bar checkbox with tooltip and label + */ +const ErrorBarComponent = () => { + const dispatch = useDispatch(); + const showMinMax = useSelector((state: LineReadingsState) => state.showMinMax); + + /** + * Dispatches an action to toggle visibility of min/max lines on checkbox interaction + */ + const handleToggleShowMinMax = () => { + dispatch({ type: ActionType.ToggleShowMinMax }); + } + + return ( +
+ handleToggleShowMinMax()} + checked={showMinMax} + id='errorBar' + /> + + +
+ ); +}; + +export default ErrorBarComponent; + diff --git a/src/client/app/components/TooltipHelpComponent.tsx b/src/client/app/components/TooltipHelpComponent.tsx index d73457d43..36999dec4 100644 --- a/src/client/app/components/TooltipHelpComponent.tsx +++ b/src/client/app/components/TooltipHelpComponent.tsx @@ -62,6 +62,7 @@ export default class TooltipHelpComponent extends React.Component - - + + {/* Controls error bar, specifically for the line chart. */} + {this.props.chartToRender === ChartTypes.line && + + } {/* Controls specific to the bar chart. */} {this.props.chartToRender === ChartTypes.bar &&
@@ -333,4 +337,4 @@ class UIOptionsComponent extends React.Component ${timeReading.format('ddd, ll LTS')}
${label}: ${(reading.reading * rate).toPrecision(6)} ${unitLabel}`); }); } @@ -92,11 +97,18 @@ function mapStateToProps(state: State) { const timeReading = st.add(moment.utc(reading.endTimestamp).diff(st) / 2); xData.push(timeReading.format('YYYY-MM-DD HH:mm:ss')); let readingValue = reading.reading; + let minValue = reading.min; + let maxValue = reading.max; + if (state.graph.areaNormalization) { readingValue /= meterArea; + minValue /= meterArea; + maxValue /= meterArea; } yData.push(readingValue); - hoverText.push(` ${timeReading.format('ddd, ll LTS')}
${label}: ${readingValue.toPrecision(6)} ${unitLabel}`); + yMinData.push(minValue); + yMaxData.push(maxValue); + hoverText.push(` ${timeReading.format('ddd, ll LTS')}
${label}: ${readingValue.toPrecision(6)} ${unitLabel}
min: ${minValue.toPrecision(6)}
max: ${maxValue.toPrecision(6)}`); }); } @@ -119,6 +131,12 @@ function mapStateToProps(state: State) { name: label, x: xData, y: yData, + error_y: state.readings.line.showMinMax ? { + type: 'data', + symmetric: false, + array: yMaxData.map((maxValue, index) => (maxValue - yData[index])), + arrayminus: yData.map((value, index) => (value - yMinData[index])), + } : undefined, text: hoverText, hoverinfo: 'text', type: 'scatter', @@ -129,6 +147,7 @@ function mapStateToProps(state: State) { color: getGraphColor(colorID, DataType.Meter) } }); + } } } diff --git a/src/client/app/reducers/graph.ts b/src/client/app/reducers/graph.ts index a28c295a2..c5bf32f7c 100644 --- a/src/client/app/reducers/graph.ts +++ b/src/client/app/reducers/graph.ts @@ -26,7 +26,8 @@ const defaultState: GraphState = { hotlinked: false, optionsVisibility: true, lineGraphRate: {label: 'hour', rate: 1}, - renderOnce: false + renderOnce: false, + showMinMax: false, }; export default function graph(state = defaultState, action: GraphAction) { diff --git a/src/client/app/reducers/lineReadings.ts b/src/client/app/reducers/lineReadings.ts index 775645665..cc854c10c 100644 --- a/src/client/app/reducers/lineReadings.ts +++ b/src/client/app/reducers/lineReadings.ts @@ -10,7 +10,8 @@ const defaultState: LineReadingsState = { byGroupID: {}, isFetching: false, metersFetching: false, - groupsFetching: false + groupsFetching: false, + showMinMax: false, }; export default function readings(state = defaultState, action: LineReadingsAction) { @@ -117,6 +118,9 @@ export default function readings(state = defaultState, action: LineReadingsActio return newState; } + case ActionType.ToggleShowMinMax: { + return { ...state, showMinMax: !state.showMinMax }; + } default: return state; } diff --git a/src/client/app/translations/data.js b/src/client/app/translations/data.js index 3c004f868..8ff509bfa 100644 --- a/src/client/app/translations/data.js +++ b/src/client/app/translations/data.js @@ -143,6 +143,7 @@ const localeData = { "email": "Email", "empty.compare": "Select one or more items to compare", "enable": "Enable", + "error.bar": "Error Bar", "export.graph.data": "Export graph data", "export.raw.graph.data": "Export graph meter data", "failed.logging.in": "Failed logging in", @@ -215,6 +216,7 @@ const localeData = { "help.groups.groupview": "This page shows information on groups. Please visit {link} for further details and information.", "help.groups.area.calculate": "This will sum together the area of all meters in this group with a nonzero area and a set area unit. It will ignore any meters which have no area or area unit. If this group has no area unit, it will do nothing.", "help.home.area.normalize": "Toggles normalization by area. Meters without area will be hidden. Please visit {link} for further details and information.", + "help.home.error.bar": "Toggles error bar with min and max value.Please visit {link} for further details and information.", "help.home.bar.custom.slider.tip": "Allows user to select the desired number of days for each bar. Please see {link} for further details and information.", "help.home.bar.interval.tip": "Selects the time interval (Day, Week or 4 Weeks) for each bar. Please see {link} for further details and information.", "help.home.bar.stacking.tip": "Bars stack on top of each other. Please see {link} for further details and information.", @@ -236,7 +238,7 @@ const localeData = { "help.meters.meterview": "This page shows information on meters. Please visit {link} for further details and information.", "here": "here", "hide": "Hide", - "hide.options": "Hide options", + "hide.options": " options", "hide.options.in.link": "Hide menu in link", "home": "Home", "hour": "Hour", @@ -922,6 +924,7 @@ const localeData = { "area.error": "Por favor entra un numero por la área", "area.invalid": "(Need Spanish) Area must be a positive value. You gave ", "area.normalize": "(Need Spanish) Normalize by Area", + "error.bar": "(Need Spanish) Error Bar", "area.calculate.auto": "(Need Spanish) Calculate Group Area", "AreaUnitType.feet": "pies cuadrados", "AreaUnitType.meters": "metros cuadrados", @@ -1124,6 +1127,7 @@ const localeData = { "help.home.chart.select": "Se puede usar cualquier tipo de gráfico con cualquier combinación de grupos y medidores (excepto los mapas que sólo hacen medidor dentro del mapa). Los gráficos de líneas muestran el uso (por ejemplo, kW) contra el tiempo. Puede hacer zoom y desplazarse con los controles justo debajo del gráfico. La barra muestra el uso total (por ejemplo, kWh) para el período de tiempo de cada barra donde puede controlar el período de tiempo. Comparar le permite ver el uso actual contra el uso de el último período anterior durante un día, una semana y cuatro semanas. Gráficos del mapa muestra un imagen especial de cada meter donde el talla del círculo está relacionado de cuatro semanas de uso. Al hacer clic en uno de las opciones (Línea, Barra, Comparar, Mapa) va a representa ese gráfico. Visite {link} para obtener más detalles e información.", "help.home.compare.interval.tip": "Selecciona el intervalo de tiempo (Día, Semana o Cuatro semanas) para comparar el actual con el anterior. Por favor, vea {link} para más detalles e información.", "help.home.compare.sort.tip": "Permite al usuario seleccionar el orden de los gráficos de comparación múltiple para que sean alfabéticos (por nombre), ascendentes (de mayor a menor reducción de uso) y descendentes (de menor a mayor reducción de uso). Por favor, vea {link} para más detalles e información.", + "help.home.error.bar": "(Need Spanish) Toggles error bar with min and max value.Please visit {link} for further details and information.", "help.home.export.graph.data": "Con el botón \"Exportar data de gráfico\", uno puede exportar los datos del gráfico al ver una línea o comparar el gráfico. La función de zoom y desplazamiento en el gráfico de líneas le permite controlar el período de tiempo de los datos exportados. El \"Exportar data de gráfico\" botón da el puntos de dato para el gráfico y no los datos sin procesar de medidor. La \"Exportar el dato gráfhico de medidor\" proporciona los datos del medidor subyacente (solo gráficos de líneas). Visite {link} para obtener más detalles e información.", "help.home.header": "Los botones \"Grupos\", \"Medidores\", \"Hogar\", \"Administrador\" e \"Entrar\" le permiten acceder a otras páginas del OED como se describe {link0}. Ver la ayuda en cada pagina y/o visitar {link1} para medidores y {link2} para grupos de información y {link3} para mapas. Las funciones de inicio de sesión de los administradores se pueden encontrar {link4}.", "help.home.hide.or.show.options": "Con el botón \"Opciones de muelle\", los botones de opciones y los menús desplegables en la parte superior e izquierda de la ventana de OED están ocultos. Un nuevo botón \"Menú\" en la parte superior derecha de la página web está disponible para ver estas opciones, incluso un botón para \"mostrar opciones\" para revertir esta elección. Cuando la ventana de la página web se vuelve demasiado pequeña, las opciones se ocultarán automáticamente. Visite {link} para obtener más detalles e información.", diff --git a/src/client/app/types/readings.ts b/src/client/app/types/readings.ts index a81160e39..19b8d0202 100644 --- a/src/client/app/types/readings.ts +++ b/src/client/app/types/readings.ts @@ -25,6 +25,8 @@ export interface LineReading { reading: number; startTimestamp: number; endTimestamp: number; + min: number; + max: number; } export interface LineReadings { diff --git a/src/client/app/types/redux/actions.ts b/src/client/app/types/redux/actions.ts index 5546ea569..9dcaf4d83 100644 --- a/src/client/app/types/redux/actions.ts +++ b/src/client/app/types/redux/actions.ts @@ -46,6 +46,7 @@ export enum ActionType { ChangeChartToRender = 'CHANGE_CHART_TO_RENDER', ChangeBarStacking = 'CHANGE_BAR_STACKING', ToggleAreaNormalization = 'TOGGLE_AREA_NORMALIZATION', + ToggleShowMinMax = 'TOGGLE_SHOW_MIN_MAX', ChangeGraphZoom = 'CHANGE_GRAPH_ZOOM', ChangeSliderRange = 'CHANGE_SLIDER_RANGE', ResetRangeSliderStack = 'RESET_RANGE_SLIDER_STACK', diff --git a/src/client/app/types/redux/graph.ts b/src/client/app/types/redux/graph.ts index faa298eb7..bfb977b54 100644 --- a/src/client/app/types/redux/graph.ts +++ b/src/client/app/types/redux/graph.ts @@ -56,6 +56,9 @@ export interface ChangeChartToRenderAction { export interface ToggleAreaNormalizationAction { type: ActionType.ToggleAreaNormalization; } +export interface ToggleShowMinMaxAction { + type: ActionType.ToggleShowMinMax; +} export interface ChangeBarStackingAction { type: ActionType.ChangeBarStacking; @@ -111,6 +114,7 @@ export type GraphAction = | ResetRangeSliderStackAction | ChangeBarStackingAction | ToggleAreaNormalizationAction + | ToggleShowMinMaxAction | ChangeChartToRenderAction | UpdateBarDurationAction | UpdateSelectedGroupsAction @@ -147,4 +151,5 @@ export interface GraphState { optionsVisibility: boolean; lineGraphRate: LineGraphRate; renderOnce: boolean; + showMinMax: boolean; } diff --git a/src/client/app/types/redux/lineReadings.ts b/src/client/app/types/redux/lineReadings.ts index 17d2ef869..1bed27c15 100644 --- a/src/client/app/types/redux/lineReadings.ts +++ b/src/client/app/types/redux/lineReadings.ts @@ -4,7 +4,7 @@ import { TimeInterval } from '../../../../common/TimeInterval'; import { ActionType } from './actions'; -import {LineReading, LineReadings} from '../readings'; +import { LineReading, LineReadings } from '../readings'; export interface RequestMeterLineReadingsAction { type: ActionType.RequestMeterLineReadings; @@ -26,6 +26,7 @@ export interface ReceiveMeterLineReadingsAction { unitID: number; timeInterval: TimeInterval; readings: LineReadings; + } export interface ReceiveGroupLineReadingsAction { @@ -35,12 +36,16 @@ export interface ReceiveGroupLineReadingsAction { timeInterval: TimeInterval; readings: LineReadings; } +export interface ToggleShowMinMaxAction { + type: ActionType.ToggleShowMinMax; +} export type LineReadingsAction = ReceiveMeterLineReadingsAction | ReceiveGroupLineReadingsAction | RequestMeterLineReadingsAction | - RequestGroupLineReadingsAction; + RequestGroupLineReadingsAction | + ToggleShowMinMaxAction; export interface LineReadingsState { byMeterID: { @@ -66,4 +71,5 @@ export interface LineReadingsState { isFetching: boolean; metersFetching: boolean; groupsFetching: boolean; + showMinMax: boolean; } diff --git a/src/client/app/utils/api/ReadingsApi.ts b/src/client/app/utils/api/ReadingsApi.ts index b06be5bf5..69307c41f 100644 --- a/src/client/app/utils/api/ReadingsApi.ts +++ b/src/client/app/utils/api/ReadingsApi.ts @@ -6,8 +6,8 @@ import * as _ from 'lodash'; import ApiBackend from './ApiBackend'; -import {TimeInterval} from '../../../../common/TimeInterval'; -import {BarReadings, LineReading, LineReadings} from '../../types/readings'; +import { TimeInterval } from '../../../../common/TimeInterval'; +import { BarReadings, LineReading, LineReadings } from '../../types/readings'; export default class ReadingsApi { private readonly backend: ApiBackend; @@ -31,7 +31,7 @@ export default class ReadingsApi { ); // Ensure everything is sorted _.values(readings) - .forEach( (value: LineReading[]) => value.sort((a, b) => a.startTimestamp - b.startTimestamp)); + .forEach((value: LineReading[]) => value.sort((a, b) => a.startTimestamp - b.startTimestamp)); return readings; } @@ -50,7 +50,7 @@ export default class ReadingsApi { ); // Ensure everything is sorted _.values(readings) - .forEach( (value: LineReading[]) => value.sort((a, b) => a.startTimestamp - b.startTimestamp)); + .forEach((value: LineReading[]) => value.sort((a, b) => a.startTimestamp - b.startTimestamp)); return readings; } @@ -85,4 +85,4 @@ export default class ReadingsApi { { timeInterval: timeInterval.toString(), barWidthDays: barWidthDays.toString(), graphicUnitId: unitID.toString() } ); } -} +} \ No newline at end of file diff --git a/src/server/models/Reading.js b/src/server/models/Reading.js index 112eff71a..ef04404da 100644 --- a/src/server/models/Reading.js +++ b/src/server/models/Reading.js @@ -240,16 +240,15 @@ class Reading { static async getMeterLineReadings(meterIDs, graphicUnitId, fromTimestamp = null, toTimestamp = null, conn) { const [maxRawPoints, maxHourlyPoints] = determineMaxPoints(); /** - * @type {array<{meter_id: int, reading_rate: Number, start_timestamp: Moment, end_timestamp: Moment}>} + * @type {array<{meter_id: int, reading_rate: Number, max_rate: Number, min_rate: Number, start_timestamp: Moment, end_timestamp: Moment}>} */ const allMeterLineReadings = await conn.func('meter_line_readings_unit', [meterIDs, graphicUnitId, fromTimestamp || '-infinity', toTimestamp || 'infinity', 'auto', maxRawPoints, maxHourlyPoints] ); - const readingsByMeterID = mapToObject(meterIDs, () => []); for (const row of allMeterLineReadings) { readingsByMeterID[row.meter_id].push( - { reading_rate: row.reading_rate, start_timestamp: row.start_timestamp, end_timestamp: row.end_timestamp } + {reading_rate: row.reading_rate, min_rate: row.min_rate, max_rate: row.max_rate, start_timestamp: row.start_timestamp, end_timestamp: row.end_timestamp } ); } return readingsByMeterID; @@ -378,4 +377,4 @@ class Reading { } } -module.exports = Reading; +module.exports = Reading; \ No newline at end of file diff --git a/src/server/routes/unitReadings.js b/src/server/routes/unitReadings.js index dcda4d5a9..0a6dfea3e 100644 --- a/src/server/routes/unitReadings.js +++ b/src/server/routes/unitReadings.js @@ -50,6 +50,8 @@ function validateLineReadingsQueryParams(queryParams) { function formatReadingRow(readingRow) { return { reading: readingRow.reading_rate, + min: readingRow.min_rate, + max: readingRow.max_rate, // This returns a Unix timestamp in milliseconds. This should be smaller in size when sent to the client // compared to sending the formatted moment object. All values are sent as a string. // The consequence of doing this is that when the client recreates this as a moment it will do it in @@ -159,7 +161,7 @@ function formatBarReadingRow(readingRow) { async function meterBarReadings(meterIDs, graphicUnitId, barWidthDays, timeInterval) { const conn = getConnection(); const rawReadings = await Reading.getMeterBarReadings( - meterIDs, graphicUnitId, timeInterval.startTimestamp, timeInterval.endTimestamp, barWidthDays, conn); + meterIDs, graphicUnitId, timeInterval.startTimestamp, timeInterval.endTimestamp, barWidthDays, conn); return _.mapValues(rawReadings, readingsForMeter => readingsForMeter.map(formatBarReadingRow)); } @@ -187,7 +189,7 @@ function validateGroupBarReadingsParams(params) { * @param timeInterval The range of time to get readings for * @returns {Promise Date: Wed, 26 Jul 2023 19:52:31 -0700 Subject: [PATCH 02/22] Fixed bugs and added options for line graph export --- src/client/app/actions/graph.ts | 3 ++ .../app/components/ErrorBarComponent.tsx | 10 +++--- src/client/app/components/ExportComponent.tsx | 6 ++-- .../app/containers/LineChartContainer.ts | 12 +++++-- src/client/app/reducers/graph.ts | 6 ++++ src/client/app/types/redux/graph.ts | 7 ++-- src/client/app/utils/exportData.ts | 36 ++++++++++++++----- 7 files changed, 60 insertions(+), 20 deletions(-) diff --git a/src/client/app/actions/graph.ts b/src/client/app/actions/graph.ts index 865032736..6d2143f57 100644 --- a/src/client/app/actions/graph.ts +++ b/src/client/app/actions/graph.ts @@ -30,6 +30,9 @@ export function changeChartToRender(chartType: t.ChartTypes): t.ChangeChartToRen export function toggleAreaNormalization(): t.ToggleAreaNormalizationAction { return { type: ActionType.ToggleAreaNormalization }; } +export function toggleShowMinMax(): t.ToggleShowMinMaxAction { + return { type: ActionType.ToggleShowMinMax } +} export function changeBarStacking(): t.ChangeBarStackingAction { return { type: ActionType.ChangeBarStacking }; diff --git a/src/client/app/components/ErrorBarComponent.tsx b/src/client/app/components/ErrorBarComponent.tsx index bd371ebd1..4ab993d87 100644 --- a/src/client/app/components/ErrorBarComponent.tsx +++ b/src/client/app/components/ErrorBarComponent.tsx @@ -4,8 +4,8 @@ import * as React from 'react'; import { useSelector, useDispatch } from 'react-redux'; -import { ActionType } from '../types/redux/actions'; -import { LineReadingsState } from 'types/redux/lineReadings'; +import { State } from '../types/redux/state'; +import { toggleShowMinMax } from '../actions/graph'; import translate from '../utils/translate'; import TooltipMarkerComponent from './TooltipMarkerComponent'; @@ -15,13 +15,13 @@ import TooltipMarkerComponent from './TooltipMarkerComponent'; */ const ErrorBarComponent = () => { const dispatch = useDispatch(); - const showMinMax = useSelector((state: LineReadingsState) => state.showMinMax); + const graphState = useSelector((state: State) => state.graph); /** * Dispatches an action to toggle visibility of min/max lines on checkbox interaction */ const handleToggleShowMinMax = () => { - dispatch({ type: ActionType.ToggleShowMinMax }); + dispatch(toggleShowMinMax()); } return ( @@ -30,7 +30,7 @@ const ErrorBarComponent = () => { type='checkbox' style={{ marginRight: '10px' }} onChange={() => handleToggleShowMinMax()} - checked={showMinMax} + checked={graphState.showMinMax} id='errorBar' />
: ''} ); -} +} \ No newline at end of file diff --git a/src/client/app/containers/LineChartContainer.ts b/src/client/app/containers/LineChartContainer.ts index b5d258fe1..911a03f9d 100644 --- a/src/client/app/containers/LineChartContainer.ts +++ b/src/client/app/containers/LineChartContainer.ts @@ -83,8 +83,8 @@ function mapStateToProps(state: State) { xData.push(timeReading.format('YYYY-MM-DD HH:mm:ss')); yData.push(reading.reading * rate); // Min and Max values. - yMinData.push(reading.min); - yMaxData.push(reading.max); + yMinData.push(reading.min); + yMaxData.push(reading.max); hoverText.push(` ${timeReading.format('ddd, ll LTS')}
${label}: ${(reading.reading * rate).toPrecision(6)} ${unitLabel}`); }); } @@ -105,10 +105,16 @@ function mapStateToProps(state: State) { minValue /= meterArea; maxValue /= meterArea; } + yData.push(readingValue); yMinData.push(minValue); yMaxData.push(maxValue); - hoverText.push(` ${timeReading.format('ddd, ll LTS')}
${label}: ${readingValue.toPrecision(6)} ${unitLabel}
min: ${minValue.toPrecision(6)}
max: ${maxValue.toPrecision(6)}`); + + if (state.graph.showMinMax) { + hoverText.push(` ${timeReading.format('ddd, ll LTS')}
${label}: ${readingValue.toPrecision(6)} ${unitLabel}
min: ${minValue.toPrecision(6)}
max: ${maxValue.toPrecision(6)}`); + } else { + hoverText.push(` ${timeReading.format('ddd, ll LTS')}
${label}: ${readingValue.toPrecision(6)} ${unitLabel}`); + } }); } diff --git a/src/client/app/reducers/graph.ts b/src/client/app/reducers/graph.ts index c5bf32f7c..3b1a8f1c5 100644 --- a/src/client/app/reducers/graph.ts +++ b/src/client/app/reducers/graph.ts @@ -94,6 +94,12 @@ export default function graph(state = defaultState, action: GraphAction) { ...state, areaNormalization: !state.areaNormalization }; + case ActionType.ToggleShowMinMax: + return { + ...state, + showMinMax: !state.showMinMax + }; + case ActionType.ChangeBarStacking: return { ...state, diff --git a/src/client/app/types/redux/graph.ts b/src/client/app/types/redux/graph.ts index bfb977b54..239dab152 100644 --- a/src/client/app/types/redux/graph.ts +++ b/src/client/app/types/redux/graph.ts @@ -17,8 +17,8 @@ export enum ChartTypes { // Rates that can be graphed, only relevant to line graphs. export const LineGraphRates = { - 'second': (1/3600), - 'minute': (1/60), + 'second': (1 / 3600), + 'minute': (1 / 60), 'hour': 1, 'day': 24 } @@ -59,6 +59,9 @@ export interface ToggleAreaNormalizationAction { export interface ToggleShowMinMaxAction { type: ActionType.ToggleShowMinMax; } +export interface ToggleShowMinMaxAction { + type: ActionType.ToggleShowMinMax; +} export interface ChangeBarStackingAction { type: ActionType.ChangeBarStacking; diff --git a/src/client/app/utils/exportData.ts b/src/client/app/utils/exportData.ts index a591a7a58..174ad90b0 100644 --- a/src/client/app/utils/exportData.ts +++ b/src/client/app/utils/exportData.ts @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { LineReading, RawReadings } from '../types/readings'; +import { LineReading, BarReading, RawReadings } from '../types/readings'; import * as moment from 'moment'; import { ChartTypes } from '../types/redux/graph'; @@ -11,11 +11,23 @@ import { ChartTypes } from '../types/redux/graph'; * @param readings The meter readings. * @param meter the meter identifier for data being exported * @param unitLabel the full y-axis label on the graphic + * @param chartName the name of the chart/graphic being exported * @param scaling factor to scale readings by, normally the rate factor for line or 1 + * @param errorBarState This indicate if the error bars are on. Automatically false if no argument is given. * @returns A string containing the CSV formatted meter readings. */ -function convertToCSV(readings: LineReading[], meter: string, unitLabel: string, scaling: number) { - let csvOutput = `Readings,Start Timestamp, End Timestamp, Meter name, ${meter}, Unit, ${unitLabel}\n`; +function convertToCSV(readings: LineReading[] | BarReading[], meter: string, unitLabel: string, chartName: ChartTypes, + scaling: number, errorBarState: boolean = false) { + let csvOutput = 'Readings, Start Timestamp, End Timestamp'; + // Check if readings is of LineReading type and if error bars are turned on. + // If these two are true then add columns for min and max. + const showMinMax = chartName === ChartTypes.line && errorBarState; + if (showMinMax) { + csvOutput += ', Min, Max'; + } else { + csvOutput += ',,'; + } + csvOutput += `, Meter name, ${meter}, Unit, ${unitLabel}\n` readings.forEach(reading => { const value = reading.reading * scaling; // As usual, maintain UTC. @@ -24,7 +36,14 @@ function convertToCSV(readings: LineReading[], meter: string, unitLabel: string, // somewhat universal way of formatting. const startTimeStamp = moment.utc(reading.startTimestamp).format('YYYY-MM-DD HH:mm:ss'); const endTimeStamp = moment.utc(reading.endTimestamp).format('YYYY-MM-DD HH:mm:ss'); - csvOutput += `${value},${startTimeStamp},${endTimeStamp}\n`; + csvOutput += `${value},${startTimeStamp},${endTimeStamp}`; + // Populate the min and max columns only for LineReading types. + if (showMinMax) { + const min = reading.min * scaling; + const max = reading.max * scaling; + csvOutput += `,${min},${max}` + } + csvOutput += '\n'; }); return csvOutput; } @@ -55,12 +74,13 @@ function downloadCSV(inputCSV: string, fileName: string) { * @param unitIdentifier the unit identifier for data being exported * @param chartName the name of the chart/graphic being exported * @param scaling factor to scale readings by, normally the rate factor for line or 1 + * @param errorBarState This indicate if the error bars are on. Automatically false if no argument is given. */ -export default function graphExport(readings: LineReading[], meter: string, unitLabel: string, unitIdentifier: string, - chartName: ChartTypes, scaling: number) { +export default function graphExport(readings: LineReading[] | BarReading[], meter: string, unitLabel: string, unitIdentifier: string, + chartName: ChartTypes, scaling: number, errorBarState: boolean = false) { // It is possible that some meters have not readings so skip if do. This can happen if resize the range of dates (or no data). if (readings.length !== 0) { - const dataToExport = convertToCSV(readings, meter, unitLabel, scaling); + const dataToExport = convertToCSV(readings, meter, unitLabel, chartName, scaling, errorBarState); // Determine and format the first time in the dataset which is first one in array since just sorted and the start time. // As usual, maintain UTC. @@ -103,4 +123,4 @@ export function downloadRawCSV(readings: RawReadings[], meter: string, unit: str const filename = `oedRawExport_line_${startTime}_to_${endTime}_for_${meter}.csv`; downloadCSV(csvOutput, filename); } -} +} \ No newline at end of file From 2d4c2e03ed8f64d78760fc2b16a84b9aead85af5 Mon Sep 17 00:00:00 2001 From: Elias Woldie Date: Fri, 28 Jul 2023 12:29:54 -0700 Subject: [PATCH 03/22] added french translation for error bar --- src/client/app/translations/data.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/client/app/translations/data.js b/src/client/app/translations/data.js index 8ff509bfa..68567c037 100644 --- a/src/client/app/translations/data.js +++ b/src/client/app/translations/data.js @@ -595,6 +595,7 @@ const localeData = { "email": "E-mail", "empty.compare": "Selectionnez un ou plusieurs articles pour comparer l'utilisation au fil du temps", "enable": "Activer", + "error.bar": "(Need French) Error Bar", "export.graph.data": "Exporter les données du diagramme", "export.raw.graph.data": "(need French) Export graph meter data", "failed.logging.in": "Echec de la connexion", @@ -676,6 +677,7 @@ const localeData = { "help.home.chart.select": "(Need French) Any graph type can be used with any combination of groups and meters (except maps which only do meters within map). Line graphs show the usage (e.g., kW) vs. time. You can zoom and scroll with the controls right below the graph. Bar shows the total usage (e.g., kWh) for the time frame of each bar where you can control the time frame. Compare allows you to see the current usage vs. the usage in the last previous period for a day, week and four weeks. Map graphs show a spacial image of each meter where the circle size is related to four weeks of usage. Clicking on one of the choices (Line, Bar, Compare, Map) renders that graphic. Please visit {link} for further details and information.", "help.home.compare.interval.tip": "(Need French) Selects the time interval (Day, Week or 4 Weeks) to compare for current to previous. Please see {link} for further details and information.", "help.home.compare.sort.tip": "(Need French) Allows user to select the order of multiple comparison graphs to be Alphabetical (by name), Ascending (greatest to least reduction in usage) and Descending (least to greatest reduction in usage). Please see {link} for further details and information.", + "help.home.error.bar": "(Need French) Toggles error bar with min and max value.Please visit {link} for further details and information.", "help.home.export.graph.data": "(Need French) With the \"Export graph data\" button, one can export the data for the graph when viewing either a line or compare graphic. The zoom and scroll feature on the line graph allows you to control the time frame of the data exported. The \"Export graph data\" button gives the data points for the graph and not the original meter data. The \"Export graph meter data\" gives the underlying meter data (line graphs only). Please visit {link} for further details and information on when meter data export is allowed.", "help.home.header": "(Need French) The \"Groups\", \"Meters\", \"Home\", \"Admin\" and \"Log in\" buttons allow you to get to other OED pages as described {link0}. See the help on each page and/or visit {link1} for meters and {link2} for groups information and {link3} for maps. Admin login features can be found {link4}.", "help.home.hide.or.show.options": "(Need French) With the \"Hide options\" button the options buttons and dropdown menus on the left and top of the OED window are all hidden. A new \"Menu\" button at the top, right of the web page is available to see these options including a button to \"Show options\" to reverse this choice. When the web page window becomes too small the options will automatically hide. Please visit {link} for further details and information.", @@ -924,7 +926,6 @@ const localeData = { "area.error": "Por favor entra un numero por la área", "area.invalid": "(Need Spanish) Area must be a positive value. You gave ", "area.normalize": "(Need Spanish) Normalize by Area", - "error.bar": "(Need Spanish) Error Bar", "area.calculate.auto": "(Need Spanish) Calculate Group Area", "AreaUnitType.feet": "pies cuadrados", "AreaUnitType.meters": "metros cuadrados", @@ -1046,6 +1047,7 @@ const localeData = { "email": "Correo Electronico", "empty.compare": "Seleccione uno o más artículos para comparar", "enable": "Activar", + "error.bar": "(Need Spanish) Error Bar", "export.graph.data": "Exportar datos del gráfico", "export.raw.graph.data": "Exportar datos gráficos de medidor", "failed.logging.in": "Error al iniciar sesión", From 02d00e61e612e4c0c574d3ad9f5cda5a4a8f9beb Mon Sep 17 00:00:00 2001 From: Elias Woldie Date: Fri, 28 Jul 2023 13:06:56 -0700 Subject: [PATCH 04/22] removed unnecessary functions and definitions --- src/client/app/actions/lineReadings.ts | 22 ------------------- .../app/containers/LineChartContainer.ts | 2 +- src/client/app/reducers/lineReadings.ts | 4 ---- src/client/app/types/redux/lineReadings.ts | 7 +----- 4 files changed, 2 insertions(+), 33 deletions(-) diff --git a/src/client/app/actions/lineReadings.ts b/src/client/app/actions/lineReadings.ts index 764a630f2..4138805f0 100644 --- a/src/client/app/actions/lineReadings.ts +++ b/src/client/app/actions/lineReadings.ts @@ -130,28 +130,6 @@ function fetchGroupLineReadings(groupIDs: number[], timeInterval: TimeInterval, dispatch(receiveGroupLineReadings(groupIDs, timeInterval, unitID, groupLineReadings)); }; } - -/** - * Generates an action object to toggle the visibility of min/max lines. - * @returns {t.ToggleShowMinMaxAction} Action object - */ -function toggleShowMinMax(): t.ToggleShowMinMaxAction { - return { type: ActionType.ToggleShowMinMax }; -} - -/** - * Dispatches the toggleShowMinMax action in a Redux Thunk. - * @returns {Thunk} Thunk action that dispatches toggleShowMinMax when invoked - */ -export const dispatchToggleShowMinMax = (): Thunk => { - return (dispatch: Dispatch): Promise => { - return new Promise((resolve, reject) => { - dispatch(toggleShowMinMax()); - resolve(); - }); - }; -}; - /** * Fetches readings for the line chart of all selected meters and groups, if needed. * @param timeInterval the interval over which to check diff --git a/src/client/app/containers/LineChartContainer.ts b/src/client/app/containers/LineChartContainer.ts index 911a03f9d..e5c682c86 100644 --- a/src/client/app/containers/LineChartContainer.ts +++ b/src/client/app/containers/LineChartContainer.ts @@ -137,7 +137,7 @@ function mapStateToProps(state: State) { name: label, x: xData, y: yData, - error_y: state.readings.line.showMinMax ? { + error_y: state.graph.showMinMax ? { type: 'data', symmetric: false, array: yMaxData.map((maxValue, index) => (maxValue - yData[index])), diff --git a/src/client/app/reducers/lineReadings.ts b/src/client/app/reducers/lineReadings.ts index cc854c10c..e2c1d38c3 100644 --- a/src/client/app/reducers/lineReadings.ts +++ b/src/client/app/reducers/lineReadings.ts @@ -11,7 +11,6 @@ const defaultState: LineReadingsState = { isFetching: false, metersFetching: false, groupsFetching: false, - showMinMax: false, }; export default function readings(state = defaultState, action: LineReadingsAction) { @@ -118,9 +117,6 @@ export default function readings(state = defaultState, action: LineReadingsActio return newState; } - case ActionType.ToggleShowMinMax: { - return { ...state, showMinMax: !state.showMinMax }; - } default: return state; } diff --git a/src/client/app/types/redux/lineReadings.ts b/src/client/app/types/redux/lineReadings.ts index 1bed27c15..99e332bce 100644 --- a/src/client/app/types/redux/lineReadings.ts +++ b/src/client/app/types/redux/lineReadings.ts @@ -36,16 +36,12 @@ export interface ReceiveGroupLineReadingsAction { timeInterval: TimeInterval; readings: LineReadings; } -export interface ToggleShowMinMaxAction { - type: ActionType.ToggleShowMinMax; -} export type LineReadingsAction = ReceiveMeterLineReadingsAction | ReceiveGroupLineReadingsAction | RequestMeterLineReadingsAction | - RequestGroupLineReadingsAction | - ToggleShowMinMaxAction; + RequestGroupLineReadingsAction ; export interface LineReadingsState { byMeterID: { @@ -71,5 +67,4 @@ export interface LineReadingsState { isFetching: boolean; metersFetching: boolean; groupsFetching: boolean; - showMinMax: boolean; } From bb6cdb53feca01a735396b28089700fbf96a7ff6 Mon Sep 17 00:00:00 2001 From: Elias Woldie Date: Fri, 28 Jul 2023 13:24:26 -0700 Subject: [PATCH 05/22] added comments and fixed some missing parts --- src/client/app/reducers/graph.ts | 2 +- src/client/app/translations/data.js | 2 +- src/server/sql/reading/create_reading_views.sql | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/client/app/reducers/graph.ts b/src/client/app/reducers/graph.ts index 3b1a8f1c5..89dc9267b 100644 --- a/src/client/app/reducers/graph.ts +++ b/src/client/app/reducers/graph.ts @@ -25,7 +25,7 @@ const defaultState: GraphState = { areaNormalization: false, hotlinked: false, optionsVisibility: true, - lineGraphRate: {label: 'hour', rate: 1}, + lineGraphRate: { label: 'hour', rate: 1 }, renderOnce: false, showMinMax: false, }; diff --git a/src/client/app/translations/data.js b/src/client/app/translations/data.js index 68567c037..6b89dd68e 100644 --- a/src/client/app/translations/data.js +++ b/src/client/app/translations/data.js @@ -238,7 +238,7 @@ const localeData = { "help.meters.meterview": "This page shows information on meters. Please visit {link} for further details and information.", "here": "here", "hide": "Hide", - "hide.options": " options", + "hide.options": "hide options", "hide.options.in.link": "Hide menu in link", "home": "Home", "hour": "Hour", diff --git a/src/server/sql/reading/create_reading_views.sql b/src/server/sql/reading/create_reading_views.sql index 1e43e6c31..f22dd2fa1 100644 --- a/src/server/sql/reading/create_reading_views.sql +++ b/src/server/sql/reading/create_reading_views.sql @@ -130,7 +130,7 @@ daily_readings_unit )) END AS reading_rate, - -- The following code does the max/min and is commented out until the front-end supports this. + -- The following code does the min/max for daily readings CASE WHEN u.unit_represent = 'quantity'::unit_represent_type THEN (max(( --Extract the maximum rate over each day (r.reading * 3600 / (extract(EPOCH FROM (r.end_timestamp - r.start_timestamp)))) -- Reading rate in kw @@ -256,7 +256,7 @@ hourly_readings_unit )) END AS reading_rate, - -- The following code does the max/min and is commented out until the front-end supports this. + -- The following code does the min/max for hourly readings CASE WHEN u.unit_represent = 'quantity'::unit_represent_type THEN (max(( -- Extract the maximum rate over each day (r.reading * 3600 / (extract(EPOCH FROM (r.end_timestamp - r.start_timestamp)))) -- Reading rate in kw From 4808085745a5ea12a98a0833d91ee81ba6f20045 Mon Sep 17 00:00:00 2001 From: Elias Woldie Date: Sun, 30 Jul 2023 18:48:13 -0700 Subject: [PATCH 06/22] Fixed misspellings --- src/client/app/translations/data.js | 4 ++-- src/client/app/types/redux/lineReadings.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client/app/translations/data.js b/src/client/app/translations/data.js index 6b89dd68e..5cb344b93 100644 --- a/src/client/app/translations/data.js +++ b/src/client/app/translations/data.js @@ -216,7 +216,6 @@ const localeData = { "help.groups.groupview": "This page shows information on groups. Please visit {link} for further details and information.", "help.groups.area.calculate": "This will sum together the area of all meters in this group with a nonzero area and a set area unit. It will ignore any meters which have no area or area unit. If this group has no area unit, it will do nothing.", "help.home.area.normalize": "Toggles normalization by area. Meters without area will be hidden. Please visit {link} for further details and information.", - "help.home.error.bar": "Toggles error bar with min and max value.Please visit {link} for further details and information.", "help.home.bar.custom.slider.tip": "Allows user to select the desired number of days for each bar. Please see {link} for further details and information.", "help.home.bar.interval.tip": "Selects the time interval (Day, Week or 4 Weeks) for each bar. Please see {link} for further details and information.", "help.home.bar.stacking.tip": "Bars stack on top of each other. Please see {link} for further details and information.", @@ -226,6 +225,7 @@ const localeData = { "help.home.chart.select": "Any graph type can be used with any combination of groups and meters (except maps which only do meters within map). Line graphs show the usage (e.g., kW) vs. time. You can zoom and scroll with the controls right below the graph. Bar shows the total usage (e.g., kWh) for the time frame of each bar where you can control the time frame. Compare allows you to see the current usage vs. the usage in the last previous period for a day, week and four weeks. Map graphs show a spacial image of each meter where the circle size is related to four weeks of usage. Clicking on one of the choices (Line, Bar, Compare, Map) renders that graphic. Please visit {link} for further details and information.", "help.home.compare.interval.tip": "Selects the time interval (Day, Week or 4 Weeks) to compare for current to previous. Please see {link} for further details and information.", "help.home.compare.sort.tip": "Allows user to select the order of multiple comparison graphs to be Alphabetical (by name), Ascending (greatest to least reduction in usage) and Descending (least to greatest reduction in usage). Please see {link} for further details and information.", + "help.home.error.bar": "Toggles error bar with min and max value.Please visit {link} for further details and information.", "help.home.export.graph.data": "With the \"Export graph data\" button, one can export the data for the graph when viewing either a line or compare graphic. The zoom and scroll feature on the line graph allows you to control the time frame of the data exported. The \"Export graph data\" button gives the data points for the graph and not the original meter data. The \"Export graph meter data\" gives the underlying meter data (line graphs only). Please visit {link} for further details and information on when meter data export is allowed.", "help.home.header": "The \"Groups\", \"Meters\", \"Home\", \"Admin\" and \"Log in\" buttons allow you to get to other OED pages as described {link0}. See the help on each page and/or visit {link1} for meters and {link2} for groups information and {link3} for maps. Admin login features can be found {link4}.", "help.home.hide.or.show.options": "With the \"Hide options\" button the options buttons and dropdown menus on the left and top of the OED window are all hidden. A new \"Menu\" button at the top, right of the web page is available to see these options including a button to \"Show options\" to reverse this choice. When the web page window becomes too small the options will automatically hide. Please visit {link} for further details and information.", @@ -238,7 +238,7 @@ const localeData = { "help.meters.meterview": "This page shows information on meters. Please visit {link} for further details and information.", "here": "here", "hide": "Hide", - "hide.options": "hide options", + "hide.options": "Hide options", "hide.options.in.link": "Hide menu in link", "home": "Home", "hour": "Hour", diff --git a/src/client/app/types/redux/lineReadings.ts b/src/client/app/types/redux/lineReadings.ts index 99e332bce..3d94a102b 100644 --- a/src/client/app/types/redux/lineReadings.ts +++ b/src/client/app/types/redux/lineReadings.ts @@ -41,7 +41,7 @@ export type LineReadingsAction = ReceiveMeterLineReadingsAction | ReceiveGroupLineReadingsAction | RequestMeterLineReadingsAction | - RequestGroupLineReadingsAction ; + RequestGroupLineReadingsAction; export interface LineReadingsState { byMeterID: { From 97ba2e6b246e22af3acd7a48f7d22035084cf31b Mon Sep 17 00:00:00 2001 From: Elias Woldie Date: Sun, 30 Jul 2023 19:08:54 -0700 Subject: [PATCH 07/22] added some comments --- src/server/sql/reading/create_reading_views.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/src/server/sql/reading/create_reading_views.sql b/src/server/sql/reading/create_reading_views.sql index f22dd2fa1..78586515d 100644 --- a/src/server/sql/reading/create_reading_views.sql +++ b/src/server/sql/reading/create_reading_views.sql @@ -455,6 +455,7 @@ DECLARE -- If it is flow or raw readings then it is already a rate so just convert it but also need to normalize -- to per hour. ((r.reading * 3600 / u.sec_in_rate) * c.slope + c.intercept) + --The next two case expressions are copied from the case expression above. "reading_rate AS min/max_rate" does not work. END AS reading_rate, CASE WHEN u.unit_represent = 'quantity'::unit_represent_type THEN -- If it is quantity readings then need to convert to rate per hour by dividing by the time length where From ffff36c87319fed572eaddd0a033af4c49743fe0 Mon Sep 17 00:00:00 2001 From: Steven Huss-Lederman Date: Mon, 31 Jul 2023 10:58:04 -0500 Subject: [PATCH 08/22] restore stub even if test fails --- .../test/routes/unitReadingsRouteTests.js | 103 +++++++++++------- 1 file changed, 64 insertions(+), 39 deletions(-) diff --git a/src/server/test/routes/unitReadingsRouteTests.js b/src/server/test/routes/unitReadingsRouteTests.js index 6010df023..3e4fd694d 100644 --- a/src/server/test/routes/unitReadingsRouteTests.js +++ b/src/server/test/routes/unitReadingsRouteTests.js @@ -23,6 +23,7 @@ const { meterLineReadings, const { TimeInterval } = require('../../../common/TimeInterval'); +// TODO is this actually used anywhere? function mockResponse() { return { sendStatus: sinon.spy(), @@ -50,28 +51,41 @@ mocha.describe('unit readings routes', () => { // TODO Maybe check for invalid for each value in validateLineReadingsQueryParams (also in Bar below). }); - mocha.it('returns line readings correctly when called correctly', async () => { - // The moments in these tests all involve TimeInterval that converts to UTC - // and not the DB so okay to use local timezone. - const timeInterval = new TimeInterval(moment('2017-01-01'), moment('2017-01-02')); - - // getMeterLineReadings is called by meterLineReadings. This makes it appear the result is what is given here. - const readingsStub = sinon.stub(Reading, 'getMeterLineReadings'); - readingsStub.resolves({ - 1: [ - { reading_rate: 1, start_timestamp: timeInterval.startTimestamp, end_timestamp: timeInterval.endTimestamp } - ] + // TODO The mocha documentation (https://mochajs.org/#arrow-functions) discourages lambda functions. Thus, the following used function(). + // Should consider removing lambda functions from all tests. + + // This needs to run the after() for this test so separated into its own describe since after works at that level. + mocha.describe('correct call', function () { + // Used by the test and in after so needs to be at this scope. + let readingsStub; + + mocha.after('restore sinon stub', function () { + // If the original function isn't restored, It can break other tests in OED. + // Use after() so restores even if test fails. + readingsStub.restore(); + }); + + mocha.it('returns line readings correctly when called correctly', async function () { + // The moments in these tests all involve TimeInterval that converts to UTC + // and not the DB so okay to use local timezone. + const timeInterval = new TimeInterval(moment('2017-01-01'), moment('2017-01-02')); + + // getMeterLineReadings is called by meterLineReadings. This makes it appear the result is what is given here. + readingsStub = sinon.stub(Reading, 'getMeterLineReadings'); + readingsStub.resolves({ + 1: [ + { reading_rate: 1, start_timestamp: timeInterval.startTimestamp, end_timestamp: timeInterval.endTimestamp } + ] + }); + const response = await meterLineReadings([1], 99, timeInterval); + + const expectedResponse = { + 1: [ + { reading: 1, startTimestamp: timeInterval.startTimestamp.valueOf(), endTimestamp: timeInterval.endTimestamp.valueOf() } + ] + }; + expect(response).to.deep.equal(expectedResponse); }); - const response = await meterLineReadings([1], 99, timeInterval); - - const expectedResponse = { - 1: [ - { reading: 1, startTimestamp: timeInterval.startTimestamp.valueOf(), endTimestamp: timeInterval.endTimestamp.valueOf() } - ] - }; - expect(response).to.deep.equal(expectedResponse); - // If the original function isn't restored, It can break other tests in OED - readingsStub.restore(); }); }); mocha.describe('the bar readings route', () => { @@ -93,26 +107,37 @@ mocha.describe('unit readings routes', () => { }); }); - mocha.it('returns bar readings correctly when called correctly', async () => { - const timeInterval = new TimeInterval(moment('2017-01-01'), moment('2017-01-02')); - // getMeterBarReadings is called by meterBarReadings. This makes it appear the result is what is given here. - const readingsStub = sinon.stub(Reading, 'getMeterBarReadings'); - readingsStub.resolves({ - 1: [ - { reading: 1, start_timestamp: timeInterval.startTimestamp, end_timestamp: timeInterval.endTimestamp } - ] + // This needs to run the after() for this test so separated into its own describe since after works at that level. + mocha.describe('correct call', function () { + // Used by the test and in after so needs to be at this scope. + let readingsStub; + + mocha.after('restore sinon stub', function () { + // If the original function isn't restored, It can break other tests in OED. + // Use after() so restores even if test fails. + readingsStub.restore(); + }); + + mocha.it('returns bar readings correctly when called correctly', async () => { + const timeInterval = new TimeInterval(moment('2017-01-01'), moment('2017-01-02')); + + // getMeterBarReadings is called by meterBarReadings. This makes it appear the result is what is given here. + readingsStub = sinon.stub(Reading, 'getMeterBarReadings'); + readingsStub.resolves({ + 1: [ + { reading: 1, start_timestamp: timeInterval.startTimestamp, end_timestamp: timeInterval.endTimestamp } + ] + }); + const response = await meterBarReadings([1], 99, 1, timeInterval); + const expectedResponse = { + 1: [ + { reading: 1, startTimestamp: timeInterval.startTimestamp.valueOf(), endTimestamp: timeInterval.endTimestamp.valueOf() } + ] + }; + + expect(response).to.deep.equal(expectedResponse); }); - const response = await meterBarReadings([1], 99, 1, timeInterval); - const expectedResponse = { - 1: [ - { reading: 1, startTimestamp: timeInterval.startTimestamp.valueOf(), endTimestamp: timeInterval.endTimestamp.valueOf() } - ] - }; - - expect(response).to.deep.equal(expectedResponse); - // If the original function isn't restored, It can break other tests in OED - readingsStub.restore(); }); }); }); From c67102921c4e5d716f5a75609f38107ebec9f53c Mon Sep 17 00:00:00 2001 From: Steven Huss-Lederman Date: Mon, 31 Jul 2023 12:31:26 -0500 Subject: [PATCH 09/22] fix linting --- .../app/components/ErrorBarComponent.tsx | 46 +++++++++---------- .../app/containers/LineChartContainer.ts | 2 +- src/client/app/reducers/graph.ts | 2 +- src/client/app/reducers/lineReadings.ts | 2 +- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/client/app/components/ErrorBarComponent.tsx b/src/client/app/components/ErrorBarComponent.tsx index 4ab993d87..feaa74693 100644 --- a/src/client/app/components/ErrorBarComponent.tsx +++ b/src/client/app/components/ErrorBarComponent.tsx @@ -14,31 +14,31 @@ import TooltipMarkerComponent from './TooltipMarkerComponent'; * @returns Error Bar checkbox with tooltip and label */ const ErrorBarComponent = () => { - const dispatch = useDispatch(); - const graphState = useSelector((state: State) => state.graph); + const dispatch = useDispatch(); + const graphState = useSelector((state: State) => state.graph); - /** - * Dispatches an action to toggle visibility of min/max lines on checkbox interaction - */ - const handleToggleShowMinMax = () => { - dispatch(toggleShowMinMax()); - } + /** + * Dispatches an action to toggle visibility of min/max lines on checkbox interaction + */ + const handleToggleShowMinMax = () => { + dispatch(toggleShowMinMax()); + } - return ( -
- handleToggleShowMinMax()} - checked={graphState.showMinMax} - id='errorBar' - /> - - -
- ); + return ( +
+ handleToggleShowMinMax()} + checked={graphState.showMinMax} + id='errorBar' + /> + + +
+ ); }; export default ErrorBarComponent; diff --git a/src/client/app/containers/LineChartContainer.ts b/src/client/app/containers/LineChartContainer.ts index e9c68daff..8753accf4 100644 --- a/src/client/app/containers/LineChartContainer.ts +++ b/src/client/app/containers/LineChartContainer.ts @@ -117,7 +117,7 @@ function mapStateToProps(state: State) { type: 'data', symmetric: false, array: yMaxData.map((maxValue, index) => (maxValue - yData[index])), - arrayminus: yData.map((value, index) => (value - yMinData[index])), + arrayminus: yData.map((value, index) => (value - yMinData[index])) } : undefined, text: hoverText, hoverinfo: 'text', diff --git a/src/client/app/reducers/graph.ts b/src/client/app/reducers/graph.ts index b31b3c365..592c61033 100644 --- a/src/client/app/reducers/graph.ts +++ b/src/client/app/reducers/graph.ts @@ -27,7 +27,7 @@ const defaultState: GraphState = { optionsVisibility: true, lineGraphRate: { label: 'hour', rate: 1 }, renderOnce: false, - showMinMax: false, + showMinMax: false }; export default function graph(state = defaultState, action: GraphAction) { diff --git a/src/client/app/reducers/lineReadings.ts b/src/client/app/reducers/lineReadings.ts index e2c1d38c3..775645665 100644 --- a/src/client/app/reducers/lineReadings.ts +++ b/src/client/app/reducers/lineReadings.ts @@ -10,7 +10,7 @@ const defaultState: LineReadingsState = { byGroupID: {}, isFetching: false, metersFetching: false, - groupsFetching: false, + groupsFetching: false }; export default function readings(state = defaultState, action: LineReadingsAction) { From 2278984cccfcac4542cf3ba9c0c55c87a0d09764 Mon Sep 17 00:00:00 2001 From: Steven Huss-Lederman Date: Mon, 31 Jul 2023 13:45:49 -0500 Subject: [PATCH 10/22] fix stub test --- src/server/test/routes/unitReadingsRouteTests.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/test/routes/unitReadingsRouteTests.js b/src/server/test/routes/unitReadingsRouteTests.js index 3e4fd694d..d36d2049c 100644 --- a/src/server/test/routes/unitReadingsRouteTests.js +++ b/src/server/test/routes/unitReadingsRouteTests.js @@ -74,14 +74,14 @@ mocha.describe('unit readings routes', () => { readingsStub = sinon.stub(Reading, 'getMeterLineReadings'); readingsStub.resolves({ 1: [ - { reading_rate: 1, start_timestamp: timeInterval.startTimestamp, end_timestamp: timeInterval.endTimestamp } + { reading_rate: 1, min_rate: 1, max_rate: 1, start_timestamp: timeInterval.startTimestamp, end_timestamp: timeInterval.endTimestamp } ] }); const response = await meterLineReadings([1], 99, timeInterval); const expectedResponse = { 1: [ - { reading: 1, startTimestamp: timeInterval.startTimestamp.valueOf(), endTimestamp: timeInterval.endTimestamp.valueOf() } + { reading: 1, min: 1, max: 1, startTimestamp: timeInterval.startTimestamp.valueOf(), endTimestamp: timeInterval.endTimestamp.valueOf() } ] }; expect(response).to.deep.equal(expectedResponse); From 14fc38f3b21e94179dde0456856e78844b8bc0d8 Mon Sep 17 00:00:00 2001 From: Steven Huss-Lederman Date: Mon, 31 Jul 2023 15:03:30 -0500 Subject: [PATCH 11/22] add DB migration for min/max changes --- src/server/migrations/1.0.0-1.1.0/index.js | 14 + .../sql/readings/create_reading_views.sql | 455 ++++++++++++++++++ 2 files changed, 469 insertions(+) create mode 100644 src/server/migrations/1.0.0-1.1.0/index.js create mode 100644 src/server/migrations/1.0.0-1.1.0/sql/readings/create_reading_views.sql diff --git a/src/server/migrations/1.0.0-1.1.0/index.js b/src/server/migrations/1.0.0-1.1.0/index.js new file mode 100644 index 000000000..922df65f5 --- /dev/null +++ b/src/server/migrations/1.0.0-1.1.0/index.js @@ -0,0 +1,14 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const database = require('../../models/database'); +const sqlFile = database.sqlFile; + +module.exports = { + fromVersion: '1.0.0', + toVersion: '1.1.0', + up: async db => { + await db.none(sqlFile('../migrations/1.0.0-1.1.0/sql/readings/create_reading_views.sql')); + } +}; diff --git a/src/server/migrations/1.0.0-1.1.0/sql/readings/create_reading_views.sql b/src/server/migrations/1.0.0-1.1.0/sql/readings/create_reading_views.sql new file mode 100644 index 000000000..3fceecd30 --- /dev/null +++ b/src/server/migrations/1.0.0-1.1.0/sql/readings/create_reading_views.sql @@ -0,0 +1,455 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +-- Drop view since exists and only create +DROP VIEW IF EXISTS daily_readings_unit; + +CREATE MATERIALIZED VIEW IF NOT EXISTS +daily_readings_unit + AS SELECT + -- This gives the weighted average of the reading rates, defined as + -- sum(reading_rate * overlap_duration) / sum(overlap_duration) + r.meter_id AS meter_id, + CASE WHEN u.unit_represent = 'quantity'::unit_represent_type THEN + (sum( + (r.reading * 3600 / (extract(EPOCH FROM (r.end_timestamp - r.start_timestamp)))) -- Reading rate in kw + * + extract(EPOCH FROM -- The number of seconds that the reading shares with the interval + least(r.end_timestamp, gen.interval_start + '1 day'::INTERVAL) + - + greatest(r.start_timestamp, gen.interval_start) + ) + ) / sum( + extract(EPOCH FROM -- The number of seconds that the reading shares with the interval + least(r.end_timestamp, gen.interval_start + '1 day'::INTERVAL) + - + greatest(r.start_timestamp, gen.interval_start) + ) + )) + WHEN (u.unit_represent = 'flow'::unit_represent_type OR u.unit_represent = 'raw'::unit_represent_type) THEN + (sum( + (r.reading * 3600 / u.sec_in_rate) -- Reading rate in per hour + * + extract(EPOCH FROM -- The number of seconds that the reading shares with the interval + least(r.end_timestamp, gen.interval_start + '1 day'::INTERVAL) + - + greatest(r.start_timestamp, gen.interval_start) + ) + ) / sum( + extract(EPOCH FROM -- The number of seconds that the reading shares with the interval + least(r.end_timestamp, gen.interval_start + '1 day'::INTERVAL) + - + greatest(r.start_timestamp, gen.interval_start) + ) + )) + END AS reading_rate, + + -- The following code does the min/max for daily readings + CASE WHEN u.unit_represent = 'quantity'::unit_represent_type THEN + (max(( --Extract the maximum rate over each day + (r.reading * 3600 / (extract(EPOCH FROM (r.end_timestamp - r.start_timestamp)))) -- Reading rate in kw + * + extract(EPOCH FROM -- The number of seconds that the reading shares with the interval + least(r.end_timestamp, gen.interval_start + '1 day'::INTERVAL) + - + greatest(r.start_timestamp, gen.interval_start) + ) + ) / ( + extract(EPOCH FROM -- The number of seconds that the reading shares with the interval + least(r.end_timestamp, gen.interval_start + '1 day'::INTERVAL) + - + greatest(r.start_timestamp, gen.interval_start) + ) + ))) + WHEN (u.unit_represent = 'flow'::unit_represent_type OR u.unit_represent = 'raw'::unit_represent_type) THEN + (max(( + (r.reading * 3600 / u.sec_in_rate) + * + extract(EPOCH FROM -- The number of seconds that the reading shares with the interval + least(r.end_timestamp, gen.interval_start + '1 day'::INTERVAL) + - + greatest(r.start_timestamp, gen.interval_start) + ) + ) / ( + extract(EPOCH FROM -- The number of seconds that the reading shares with the interval + least(r.end_timestamp, gen.interval_start + '1 day'::INTERVAL) + - + greatest(r.start_timestamp, gen.interval_start) + ) + ))) + END as max_rate, + + CASE WHEN u.unit_represent = 'quantity'::unit_represent_type THEN + (min(( --Extract the minimum rate over each day + (r.reading * 3600 / (extract(EPOCH FROM (r.end_timestamp - r.start_timestamp)))) -- Reading rate in kw + * + extract(EPOCH FROM -- The number of seconds that the reading shares with the interval + least(r.end_timestamp, gen.interval_start + '1 day'::INTERVAL) + - + greatest(r.start_timestamp, gen.interval_start) + ) + ) / ( + extract(EPOCH FROM -- The number of seconds that the reading shares with the interval + least(r.end_timestamp, gen.interval_start + '1 day'::INTERVAL) + - + greatest(r.start_timestamp, gen.interval_start) + ) + ))) + WHEN (u.unit_represent = 'flow'::unit_represent_type OR u.unit_represent = 'raw'::unit_represent_type) THEN + (min(( + (r.reading * 3600 / u.sec_in_rate) -- Reading rate in kw + * + extract(EPOCH FROM -- The number of seconds that the reading shares with the interval + least(r.end_timestamp, gen.interval_start + '1 day'::INTERVAL) + - + greatest(r.start_timestamp, gen.interval_start) + ) + ) / ( + extract(EPOCH FROM -- The number of seconds that the reading shares with the interval + least(r.end_timestamp, gen.interval_start + '1 day'::INTERVAL) + - + greatest(r.start_timestamp, gen.interval_start) + ) + ))) + END as min_rate, + + tsrange(gen.interval_start, gen.interval_start + '1 day'::INTERVAL, '()') AS time_interval + FROM ((readings r + -- This sequence of joins takes the meter id to its unit and in the final join + -- it then uses the unit_index for this unit. + INNER JOIN meters m ON r.meter_id = m.id) + INNER JOIN units u ON m.unit_id = u.id) + CROSS JOIN LATERAL generate_series( + date_trunc('day', r.start_timestamp), + -- Subtract 1 interval width because generate_series is end-inclusive + date_trunc_up('day', r.end_timestamp) - '1 day'::INTERVAL, + '1 day'::INTERVAL + ) gen(interval_start) + GROUP BY r.meter_id, gen.interval_start, u.unit_represent + -- The order by ensures that the materialized view will be clustered in this way. + ORDER BY gen.interval_start, r.meter_id; + + +-- Drop view since exists and only create +DROP VIEW IF EXISTS hourly_readings_unit; + +CREATE MATERIALIZED VIEW IF NOT EXISTS +hourly_readings_unit + AS SELECT + -- This gives the weighted average of the reading rates, defined as + -- sum(reading_rate * overlap_duration) / sum(overlap_duration) + r.meter_id AS meter_id, + CASE WHEN u.unit_represent = 'quantity'::unit_represent_type THEN + (sum( + (r.reading * 3600 / (extract(EPOCH FROM (r.end_timestamp - r.start_timestamp)))) -- Reading rate in kw + * + extract(EPOCH FROM -- The number of seconds that the reading shares with the interval + least(r.end_timestamp, gen.interval_start + '1 hour'::INTERVAL) + - + greatest(r.start_timestamp, gen.interval_start) + ) + ) / sum( + extract(EPOCH FROM -- The number of seconds that the reading shares with the interval + least(r.end_timestamp, gen.interval_start + '1 hour'::INTERVAL) + - + greatest(r.start_timestamp, gen.interval_start) + ) + )) + WHEN (u.unit_represent = 'flow'::unit_represent_type OR u.unit_represent = 'raw'::unit_represent_type) THEN + (sum( + (r.reading * 3600 / u.sec_in_rate) -- Reading rate in per hour + * + extract(EPOCH FROM -- The number of seconds that the reading shares with the interval + least(r.end_timestamp, gen.interval_start + '1 hour'::INTERVAL) + - + greatest(r.start_timestamp, gen.interval_start) + ) + ) / sum( + extract(EPOCH FROM -- The number of seconds that the reading shares with the interval + least(r.end_timestamp, gen.interval_start + '1 hour'::INTERVAL) + - + greatest(r.start_timestamp, gen.interval_start) + ) + )) + END AS reading_rate, + + -- The following code does the min/max for hourly readings + CASE WHEN u.unit_represent = 'quantity'::unit_represent_type THEN + (max(( -- Extract the maximum rate over each day + (r.reading * 3600 / (extract(EPOCH FROM (r.end_timestamp - r.start_timestamp)))) -- Reading rate in kw + * + extract(EPOCH FROM -- The number of seconds that the reading shares with the interval + least(r.end_timestamp, gen.interval_start + '1 hour'::INTERVAL) + - + greatest(r.start_timestamp, gen.interval_start) + ) + ) / ( + extract(EPOCH FROM -- The number of seconds that the reading shares with the interval + least(r.end_timestamp, gen.interval_start + '1 hour'::INTERVAL) + - + greatest(r.start_timestamp, gen.interval_start) + ) + ))) + WHEN (u.unit_represent = 'flow'::unit_represent_type OR u.unit_represent = 'raw'::unit_represent_type) THEN + (max(( -- For flow and raw data the max/min is per minute, so we multiply the max/min by 24 hrs * 60 min + (r.reading * 3600 / u.sec_in_rate) -- Reading rate in kw + * + extract(EPOCH FROM -- The number of seconds that the reading shares with the interval + least(r.end_timestamp, gen.interval_start + '1 hour'::INTERVAL) + - + greatest(r.start_timestamp, gen.interval_start) + ) + ) / ( + extract(EPOCH FROM -- The number of seconds that the reading shares with the interval + least(r.end_timestamp, gen.interval_start + '1 hour'::INTERVAL) + - + greatest(r.start_timestamp, gen.interval_start) + ) + ))) + END as max_rate, + + CASE WHEN u.unit_represent = 'quantity'::unit_represent_type THEN + (min(( --Extract the minimum rate over each day + (r.reading * 3600 / (extract(EPOCH FROM (r.end_timestamp - r.start_timestamp)))) -- Reading rate in kw + * + extract(EPOCH FROM -- The number of seconds that the reading shares with the interval + least(r.end_timestamp, gen.interval_start + '1 hour'::INTERVAL) + - + greatest(r.start_timestamp, gen.interval_start) + ) + ) / ( + extract(EPOCH FROM -- The number of seconds that the reading shares with the interval + least(r.end_timestamp, gen.interval_start + '1 hour'::INTERVAL) + - + greatest(r.start_timestamp, gen.interval_start) + ) + ))) + WHEN (u.unit_represent = 'flow'::unit_represent_type OR u.unit_represent = 'raw'::unit_represent_type) THEN + (min(( + (r.reading * 3600 / u.sec_in_rate) -- Reading rate in kw + * + extract(EPOCH FROM -- The number of seconds that the reading shares with the interval + least(r.end_timestamp, gen.interval_start + '1 hour'::INTERVAL) + - + greatest(r.start_timestamp, gen.interval_start) + ) + ) / ( + extract(EPOCH FROM -- The number of seconds that the reading shares with the interval + least(r.end_timestamp, gen.interval_start + '1 day'::INTERVAL) + - + greatest(r.start_timestamp, gen.interval_start) + ) + ))) + END as min_rate, + + tsrange(gen.interval_start, gen.interval_start + '1 hour'::INTERVAL, '()') AS time_interval + FROM ((readings r + -- This sequence of joins takes the meter id to its unit and in the final join + -- it then uses the unit_index for this unit. + INNER JOIN meters m ON r.meter_id = m.id) + INNER JOIN units u ON m.unit_id = u.id) + CROSS JOIN LATERAL generate_series( + date_trunc('hour', r.start_timestamp), + -- Subtract 1 interval width because generate_series is end-inclusive + date_trunc_up('hour', r.end_timestamp) - '1 hour'::INTERVAL, + '1 hour'::INTERVAL + ) gen(interval_start) + GROUP BY r.meter_id, gen.interval_start, u.unit_represent + -- The order by ensures that the materialized view will be clustered in this way. + ORDER BY gen.interval_start, r.meter_id; + + +/* +The following function determines the correct duration view to query from, and returns averaged or raw reading from it. +It is designed to return data for plotting line graphs. It works on meters. +It is the new version of compressed_readings_2 that works with units. It takes these parameters: +meter_ids: A array of meter ids to query. +graphic_unit_id: The unit id of the unit to use for the graphic. +start_timestamp: The start timestamp of the data to return. +end_timestamp: The end timestamp of the data to return. +point_accuracy: Tells how decisions should be made on which types of points to return. 'auto' if automatic. +max_raw_points: The maximum number of data points to return if using the raw points for a meter. Only used if 'auto' for point_accuracy. +max_hour_points: The maximum number of data points to return if using the hour view. Only used if 'auto' for point_accuracy. +Details on how this function works can be found in the devDocs in the resource generalization document. + */ +CREATE OR REPLACE FUNCTION meter_line_readings_unit ( + meter_ids INTEGER[], + graphic_unit_id INTEGER, + start_stamp TIMESTAMP, + end_stamp TIMESTAMP, + point_accuracy reading_line_accuracy, + max_raw_points INTEGER, + max_hour_points INTEGER +) + RETURNS TABLE(meter_id INTEGER, reading_rate FLOAT, min_rate FLOAT, max_rate FLOAT, start_timestamp TIMESTAMP, end_timestamp TIMESTAMP) +AS $$ +DECLARE + requested_range TSRANGE; + requested_interval INTERVAL; + requested_interval_seconds INTEGER; + unit_column INTEGER; + frequency INTERVAL; + frequency_seconds INTEGER; + -- Which index of the meter_id array you are currently working on. + current_meter_index INTEGER := 1; + -- The id of the meter index working on + current_meter_id INTEGER; + -- Holds accuracy for current meter. + current_point_accuracy reading_line_accuracy; + BEGIN + -- unit_column holds the column index into the cik table. This is the unit that was requested for graphing. + SELECT unit_index INTO unit_column FROM units WHERE id = graphic_unit_id; + -- For each frequency of points, verify that you will get the minimum graphing points to use for each meter. + -- Start with the raw, then hourly and then daily if others will not work. + -- Loop over all meters. + WHILE current_meter_index <= cardinality(meter_ids) LOOP + -- Reset the point accuracy for each meter so it does what is desired. + current_point_accuracy := point_accuracy; + current_meter_id := meter_ids[current_meter_index]; + -- Make sure the time range is within the reading values for this meter. + -- There may be a better way to create the array with one element as last argument. + requested_range := shrink_tsrange_to_real_readings(tsrange(start_stamp, end_stamp, '[]'), array_append(ARRAY[]::INTEGER[], current_meter_id)); + IF (current_point_accuracy = 'auto'::reading_line_accuracy) THEN + -- The request wants automatic calculation of the points returned. + + -- The request_range will still be infinity if there is no meter data. This causes the + -- auto calculation to fail because you cannot subtract them. + -- Just check the upper range since simpler. + IF (upper(requested_range) = 'infinity') THEN + -- We know there is no data but easier to just let a query happen since fast. + -- Do daily since that should be the fastest due to the least data in most cases. + current_point_accuracy := 'daily'::reading_line_accuracy; + ELSE + -- The interval of time for the requested_range. + requested_interval := upper(requested_range) - lower(requested_range); + -- Get the seconds in the interval. + -- Wanted to use the INTO syntax used above but could not get it to work so using the set syntax. + requested_interval_seconds := (SELECT * FROM EXTRACT(EPOCH FROM requested_interval)); + -- Get the frequency that this meter reads at. + SELECT reading_frequency INTO frequency FROM meters WHERE id = current_meter_id; + -- Get the seconds in the frequency. + frequency_seconds := (SELECT * FROM EXTRACT(EPOCH FROM frequency)); + + -- The first part is making sure that there are no more than maximum raw readings to graph if use raw readings. + -- Divide the time being graphed by the frequency of reading for this meter to get the number of raw readings. + -- The second part checks if the frequency of raw readings is more than a day and use raw if this is the case + -- because even daily would interpolate points. 1 day is 24 hours * 60 minute/hour * 60 seconds/minute = 86400 seconds. + -- This can lead to too many points but do this for now since that is unlikely as you would need around 4+ years of data. + -- Note this overrides the max raw points if it applies. + IF ((requested_interval_seconds / frequency_seconds <= max_raw_points) OR (frequency_seconds >= 86400)) THEN + -- Return raw meter data. + current_point_accuracy := 'raw'::reading_line_accuracy; + -- The first part is making sure that the number of hour points is no more than maximum hourly readings. + -- Thus, check if no more than interval in seconds / (60 seconds/minute * 60 minutes/hour) = # hours in interval. + -- The second part is making sure that the frequency of reading is an hour or less (3600 seconds) + -- so you don't interpolate points by using the hourly data. + ELSIF ((requested_interval_seconds / 3600 <= max_hour_points) AND (frequency_seconds <= 3600)) THEN + -- Return hourly reading data. + current_point_accuracy := 'hourly'::reading_line_accuracy; + ELSE + -- Return daily reading data. + current_point_accuracy := 'daily'::reading_line_accuracy; + END IF; + END IF; + END IF; + -- At this point current_point_accuracy should never be 'auto'. + + IF (current_point_accuracy = 'raw'::reading_line_accuracy) THEN + -- Gets raw meter data to graph. + RETURN QUERY + SELECT r.meter_id as meter_id, + CASE WHEN u.unit_represent = 'quantity'::unit_represent_type THEN + -- If it is quantity readings then need to convert to rate per hour by dividing by the time length where + -- the 3600 is needed since EPOCH is in seconds. + ((r.reading / (extract(EPOCH FROM (r.end_timestamp - r.start_timestamp)) / 3600)) * c.slope + c.intercept) + WHEN (u.unit_represent = 'flow'::unit_represent_type OR u.unit_represent = 'raw'::unit_represent_type) THEN + -- If it is flow or raw readings then it is already a rate so just convert it but also need to normalize + -- to per hour. + ((r.reading * 3600 / u.sec_in_rate) * c.slope + c.intercept) + --The next two case expressions are copied from the case expression above. "reading_rate AS min/max_rate" does not work. + END AS reading_rate, + CASE WHEN u.unit_represent = 'quantity'::unit_represent_type THEN + -- If it is quantity readings then need to convert to rate per hour by dividing by the time length where + -- the 3600 is needed since EPOCH is in seconds. + ((r.reading / (extract(EPOCH FROM (r.end_timestamp - r.start_timestamp)) / 3600)) * c.slope + c.intercept) + WHEN (u.unit_represent = 'flow'::unit_represent_type OR u.unit_represent = 'raw'::unit_represent_type) THEN + -- If it is flow or raw readings then it is already a rate so just convert it but also need to normalize + -- to per hour. + ((r.reading * 3600 / u.sec_in_rate) * c.slope + c.intercept) + END AS min_rate, + CASE WHEN u.unit_represent = 'quantity'::unit_represent_type THEN + -- If it is quantity readings then need to convert to rate per hour by dividing by the time length where + -- the 3600 is needed since EPOCH is in seconds. + ((r.reading / (extract(EPOCH FROM (r.end_timestamp - r.start_timestamp)) / 3600)) * c.slope + c.intercept) + WHEN (u.unit_represent = 'flow'::unit_represent_type OR u.unit_represent = 'raw'::unit_represent_type) THEN + -- If it is flow or raw readings then it is already a rate so just convert it but also need to normalize + -- to per hour. + ((r.reading * 3600 / u.sec_in_rate) * c.slope + c.intercept) + END AS max_rate, + -- TODO This does not seem to work + -- reading_rate AS min_rate, + -- reading_rate AS max_rate, + r.start_timestamp, + r.end_timestamp + FROM (((readings r + INNER JOIN meters m ON m.id = current_meter_id) + INNER JOIN units u ON m.unit_id = u.id) + INNER JOIN cik c on c.row_index = u.unit_index AND c.column_index = unit_column) + WHERE lower(requested_range) <= r.start_timestamp AND r.end_timestamp <= upper(requested_range) AND r.meter_id = current_meter_id + -- This ensures the data is sorted + ORDER BY r.start_timestamp ASC; + -- The first part is making sure that the number of hour points is 1440 or less. + -- Thus, check if no more than 1440 hours * 60 minutes/hour * 60 seconds/hour = 5184000 seconds. + -- The second part is making sure that the frequency of reading is an hour or less (3600 seconds) + -- so you don't interpolate points by using the hourly data. + ELSIF (current_point_accuracy = 'hourly'::reading_line_accuracy) THEN + -- Get hourly points to graph. See daily for more comments. + RETURN QUERY + SELECT hourly.meter_id AS meter_id, + -- Convert the reading based on the conversion found below. + -- Hourly readings are already averaged correctly into a rate. + hourly.reading_rate * c.slope + c.intercept as reading_rate, + hourly.min_rate * c.slope + c.intercept AS min_rate, + hourly.max_rate * c.slope + c.intercept AS max_rate, + lower(hourly.time_interval) AS start_timestamp, + upper(hourly.time_interval) AS end_timestamp + FROM (((hourly_readings_unit hourly + INNER JOIN meters m ON m.id = current_meter_id) + INNER JOIN units u ON m.unit_id = u.id) + INNER JOIN cik c on c.row_index = u.unit_index AND c.column_index = unit_column) + WHERE requested_range @> time_interval AND hourly.meter_id = current_meter_id + -- This ensures the data is sorted + ORDER BY start_timestamp ASC; + ELSE + -- Get daily points to graph. This should be an okay number but can be too many + -- if there are a lot of days of readings. + -- TODO Someday consider averaging days if too many. + RETURN QUERY + SELECT + daily.meter_id AS meter_id, + -- Convert the reading based on the conversion found below. + -- Daily readings are already averaged correctly into a rate. + daily.reading_rate * c.slope + c.intercept as reading_rate, + daily.min_rate * c.slope + c.intercept AS min_rate, + daily.max_rate * c.slope + c.intercept AS max_rate, + lower(daily.time_interval) AS start_timestamp, + upper(daily.time_interval) AS end_timestamp + FROM (((daily_readings_unit daily + -- Get all the meter_ids in the passed array of meters. + -- This sequence of joins takes the meter id to its unit and in the final join + -- it then uses the unit_index for this unit. + INNER JOIN meters m ON m.id = current_meter_id) + INNER JOIN units u ON m.unit_id = u.id) + -- This is getting the conversion for the meter (row_index) and unit to graph (column_index). + -- The slope and intercept are used above the transform the reading to the desired unit. + INNER JOIN cik c on c.row_index = u.unit_index AND c.column_index = unit_column) + WHERE requested_range @> time_interval AND daily.meter_id = current_meter_id + -- This ensures the data is sorted + ORDER BY start_timestamp ASC; + END IF; + current_meter_index := current_meter_index + 1; + END LOOP; +END; +$$ LANGUAGE 'plpgsql'; From 973d2368087c743f037ddb515d4ca1656f41d962 Mon Sep 17 00:00:00 2001 From: Elias Woldie Date: Thu, 3 Aug 2023 09:56:48 -0700 Subject: [PATCH 12/22] Resolved requested changes --- src/client/app/actions/lineReadings.ts | 2 +- src/client/app/components/ErrorBarComponent.tsx | 7 ++----- src/client/app/containers/LineChartContainer.ts | 14 +++++++------- src/client/app/translations/data.js | 4 ++-- src/client/app/types/redux/graph.ts | 4 +--- src/server/migrations/registerMigration.js | 3 ++- 6 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src/client/app/actions/lineReadings.ts b/src/client/app/actions/lineReadings.ts index 4138805f0..9161a9559 100644 --- a/src/client/app/actions/lineReadings.ts +++ b/src/client/app/actions/lineReadings.ts @@ -158,4 +158,4 @@ export function fetchNeededLineReadings(timeInterval: TimeInterval, unitID: numb return Promise.all(promises); }; -} \ No newline at end of file +} diff --git a/src/client/app/components/ErrorBarComponent.tsx b/src/client/app/components/ErrorBarComponent.tsx index feaa74693..faf4608d6 100644 --- a/src/client/app/components/ErrorBarComponent.tsx +++ b/src/client/app/components/ErrorBarComponent.tsx @@ -13,7 +13,7 @@ import TooltipMarkerComponent from './TooltipMarkerComponent'; * React Component rendering an Error Bar checkbox for toggle operation. * @returns Error Bar checkbox with tooltip and label */ -const ErrorBarComponent = () => { +export default function ErrorBarComponent() { const dispatch = useDispatch(); const graphState = useSelector((state: State) => state.graph); @@ -39,7 +39,4 @@ const ErrorBarComponent = () => { ); -}; - -export default ErrorBarComponent; - +} diff --git a/src/client/app/containers/LineChartContainer.ts b/src/client/app/containers/LineChartContainer.ts index 8753accf4..ded85b998 100644 --- a/src/client/app/containers/LineChartContainer.ts +++ b/src/client/app/containers/LineChartContainer.ts @@ -18,8 +18,6 @@ function mapStateToProps(state: State) { const timeInterval = state.graph.timeInterval; const unitID = state.graph.selectedUnit; const datasets: any[] = []; - const yMinData: number[] = []; - const yMaxData: number[] = []; // The unit label depends on the unit which is in selectUnit state. const graphingUnit = state.graph.selectedUnit; // The current selected rate @@ -70,6 +68,9 @@ function mapStateToProps(state: State) { // Create two arrays for the x and y values. Fill the array with the data from the line readings const xData: string[] = []; const yData: number[] = []; + // Create two arrays to store the min and max values of y-axis data points + const yMinData: number[] = []; + const yMaxData: number[] = []; const hoverText: string[] = []; const readings = _.values(readingsData.readings); // The scaling is the factor to change the reading by. It divides by the area while will be 1 if no scaling by area. @@ -82,11 +83,11 @@ function mapStateToProps(state: State) { xData.push(timeReading.format('YYYY-MM-DD HH:mm:ss')); const readingValue = reading.reading * scaling; yData.push(readingValue); - const minValue = reading.min * scaling; - yMinData.push(minValue); - const maxValue = reading.max * scaling; - yMaxData.push(maxValue); if (state.graph.showMinMax) { + const minValue = reading.min * scaling; + yMinData.push(minValue); + const maxValue = reading.max * scaling; + yMaxData.push(maxValue); hoverText.push(` ${timeReading.format('ddd, ll LTS')}
${label}: ${readingValue.toPrecision(6)}` + ` ${unitLabel}
min: ${minValue.toPrecision(6)}
max: ${maxValue.toPrecision(6)}`); } else { @@ -129,7 +130,6 @@ function mapStateToProps(state: State) { color: getGraphColor(colorID, DataType.Meter) } }); - } } } diff --git a/src/client/app/translations/data.js b/src/client/app/translations/data.js index b4f12a0dd..fd82717fc 100644 --- a/src/client/app/translations/data.js +++ b/src/client/app/translations/data.js @@ -149,7 +149,7 @@ const localeData = { "edit.unit": "Edit Unit", "email": "Email", "enable": "Enable", - "error.bar": "Error Bar", + "error.bar": "Show error bars", "export.graph.data": "Export graph data", "export.raw.graph.data": "Export graph meter data", "failed.logging.in": "Failed logging in", @@ -234,7 +234,7 @@ const localeData = { "help.home.chart.select": "Any graph type can be used with any combination of groups and meters (except maps which only do meters within map). Line graphs show the usage (e.g., kW) vs. time. You can zoom and scroll with the controls right below the graph. Bar shows the total usage (e.g., kWh) for the time frame of each bar where you can control the time frame. Compare allows you to see the current usage vs. the usage in the last previous period for a day, week and four weeks. Map graphs show a spacial image of each meter where the circle size is related to four weeks of usage. Clicking on one of the choices (Line, Bar, Compare, Map) renders that graphic. Please visit {link} for further details and information.", "help.home.compare.interval.tip": "Selects the time interval (Day, Week or 4 Weeks) to compare for current to previous. Please see {link} for further details and information.", "help.home.compare.sort.tip": "Allows user to select the order of multiple comparison graphs to be Alphabetical (by name), Ascending (greatest to least reduction in usage) and Descending (least to greatest reduction in usage). Please see {link} for further details and information.", - "help.home.error.bar": "Toggles error bar with min and max value.Please visit {link} for further details and information.", + "help.home.error.bar": "Toggle error bars with min and max value. Please visit {link} for further details and information.", "help.home.export.graph.data": "With the \"Export graph data\" button, one can export the data for the graph when viewing either a line or compare graphic. The zoom and scroll feature on the line graph allows you to control the time frame of the data exported. The \"Export graph data\" button gives the data points for the graph and not the original meter data. The \"Export graph meter data\" gives the underlying meter data (line graphs only). Please visit {link} for further details and information on when meter data export is allowed.", "help.home.hide.or.show.options": "With the \"Hide options\" button the options buttons and dropdown menus on the left and top of the OED window are all hidden. A new \"Menu\" button at the top, right of the web page is available to see these options including a button to \"Show options\" to reverse this choice. When the web page window becomes too small the options will automatically hide. Please visit {link} for further details and information.", "help.home.select.groups": "Groups aggregate (sum the usage) of any combination of groups and meters. You can choose which groups to view in your graphic from the \"Groups:\" dropdown menu. Note you can type in text to limit which groups are shown. The Groups button in the top, right side of the window allows you to see more details about each group and, if you are an admin, to edit the groups. Please visit {link} for further details and information.", diff --git a/src/client/app/types/redux/graph.ts b/src/client/app/types/redux/graph.ts index 24d650a58..af49c591d 100644 --- a/src/client/app/types/redux/graph.ts +++ b/src/client/app/types/redux/graph.ts @@ -56,9 +56,7 @@ export interface ChangeChartToRenderAction { export interface ToggleAreaNormalizationAction { type: ActionType.ToggleAreaNormalization; } -export interface ToggleShowMinMaxAction { - type: ActionType.ToggleShowMinMax; -} + export interface ToggleShowMinMaxAction { type: ActionType.ToggleShowMinMax; } diff --git a/src/server/migrations/registerMigration.js b/src/server/migrations/registerMigration.js index eced32929..2a1f0b74d 100644 --- a/src/server/migrations/registerMigration.js +++ b/src/server/migrations/registerMigration.js @@ -12,7 +12,8 @@ const migrations = [ require('./0.5.0-0.6.0'), require('./0.6.0-0.7.0'), require('./0.7.0-0.8.0'), - require('./0.8.0-1.0.0') + require('./0.8.0-1.0.0'), + require('./1.0.0-1.1.0') /* eslint-enable global-require */ ]; From eb1a3626847a5457223c98d7c4f43fb89714b14e Mon Sep 17 00:00:00 2001 From: Steven Huss-Lederman Date: Fri, 4 Aug 2023 14:14:19 -0500 Subject: [PATCH 13/22] add min/max to chart link Per discussion and agreement with the developers of this PR, I added the min/max error bars to the chart link ability. --- src/client/app/actions/graph.ts | 7 +++++-- src/client/app/components/RouteComponent.tsx | 6 ++++++ src/client/app/containers/ChartLinkContainer.ts | 1 + src/client/app/containers/RouteContainer.ts | 3 ++- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/client/app/actions/graph.ts b/src/client/app/actions/graph.ts index bb1742e79..10a511c17 100644 --- a/src/client/app/actions/graph.ts +++ b/src/client/app/actions/graph.ts @@ -216,6 +216,7 @@ export interface LinkOptions { sliderRange?: TimeInterval; toggleAreaNormalization?: boolean; areaUnit?: string; + toggleMinMax?: boolean; toggleBarStacking?: boolean; comparePeriod?: ComparePeriod; compareSortingOrder?: SortingOrder; @@ -234,7 +235,7 @@ export function changeOptionsFromLink(options: LinkOptions) { const dispatchSecond: Array = []; + t.UpdateSelectedAreaUnitAction | t.ToggleShowMinMaxAction> = []; /* eslint-enable @typescript-eslint/indent */ if (options.meterIDs) { @@ -269,7 +270,9 @@ export function changeOptionsFromLink(options: LinkOptions) { } if (options.areaUnit) { dispatchSecond.push(updateSelectedAreaUnit(options.areaUnit as AreaUnitType)); - + } + if (options.toggleMinMax) { + dispatchSecond.push(toggleShowMinMax()); } if (options.toggleBarStacking) { dispatchSecond.push(changeBarStacking()); diff --git a/src/client/app/components/RouteComponent.tsx b/src/client/app/components/RouteComponent.tsx index 54c754ea0..4414af4a1 100644 --- a/src/client/app/components/RouteComponent.tsx +++ b/src/client/app/components/RouteComponent.tsx @@ -42,6 +42,7 @@ interface RouteProps { role: UserRole; renderOnce: boolean; areaNormalization: boolean; + minMax: boolean; changeOptionsFromLink(options: LinkOptions): Promise; clearCurrentUser(): any; changeRenderOnce(): any; @@ -214,6 +215,11 @@ export default class RouteComponent extends React.Component { case 'areaUnit': options.areaUnit = info; break; + case 'minMax': + if (this.props.minMax.toString() !== info) { + options.toggleMinMax = true; + } + break; case 'comparePeriod': options.comparePeriod = validateComparePeriod(info); break; diff --git a/src/client/app/containers/ChartLinkContainer.ts b/src/client/app/containers/ChartLinkContainer.ts index 2fe3c85b7..689da3426 100644 --- a/src/client/app/containers/ChartLinkContainer.ts +++ b/src/client/app/containers/ChartLinkContainer.ts @@ -61,6 +61,7 @@ function mapStateToProps(state: State) { linkText += `&unitID=${unitID.toString()}`; linkText += `&rate=${state.graph.lineGraphRate.label.toString()},${state.graph.lineGraphRate.rate.toString()}`; linkText += `&areaUnit=${state.graph.selectedAreaUnit}&areaNormalization=${state.graph.areaNormalization}`; + linkText += `&minMax=${state.graph.showMinMax}`; return { linkText, chartType diff --git a/src/client/app/containers/RouteContainer.ts b/src/client/app/containers/RouteContainer.ts index d56c691bf..ad383881d 100644 --- a/src/client/app/containers/RouteContainer.ts +++ b/src/client/app/containers/RouteContainer.ts @@ -27,7 +27,8 @@ function mapStateToProps(state: State) { role, // true if the chartlink rendering has been done. renderOnce: state.graph.renderOnce, - areaNormalization: state.graph.areaNormalization + areaNormalization: state.graph.areaNormalization, + minMax: state.graph.showMinMax }; } From 592cd4592c673c7d928516b3819752cad1221c73 Mon Sep 17 00:00:00 2001 From: Steven Huss-Lederman Date: Fri, 4 Aug 2023 15:20:01 -0500 Subject: [PATCH 14/22] remove min/max on line if raw data The line graphic no longer shows min/max values on raw data. This is done by setting the values to null in Redux and NaN from the DB. The graphic shows each meter so if one is raw it will not show min/max but if another in the same graphic is not raw then the min/max is shown. --- .../app/containers/LineChartContainer.ts | 15 ++++++++-- .../sql/reading/create_reading_views.sql | 28 ++++--------------- 2 files changed, 17 insertions(+), 26 deletions(-) diff --git a/src/client/app/containers/LineChartContainer.ts b/src/client/app/containers/LineChartContainer.ts index ded85b998..721e73b54 100644 --- a/src/client/app/containers/LineChartContainer.ts +++ b/src/client/app/containers/LineChartContainer.ts @@ -83,15 +83,24 @@ function mapStateToProps(state: State) { xData.push(timeReading.format('YYYY-MM-DD HH:mm:ss')); const readingValue = reading.reading * scaling; yData.push(readingValue); + // All hover have the date, meter name and value. + const hoverStart = ` ${timeReading.format('ddd, ll LTS')}
${label}: ${readingValue.toPrecision(6)} ${unitLabel}`; if (state.graph.showMinMax) { + // We want to show min/max. Note if the data is raw for this meter then all the min/max values are null. + // In this case we still push the min/max but plotly will not show them. This is a little extra work + // but makes the code cleaner. const minValue = reading.min * scaling; yMinData.push(minValue); const maxValue = reading.max * scaling; yMaxData.push(maxValue); - hoverText.push(` ${timeReading.format('ddd, ll LTS')}
${label}: ${readingValue.toPrecision(6)}` + - ` ${unitLabel}
min: ${minValue.toPrecision(6)}
max: ${maxValue.toPrecision(6)}`); + if (reading.min != null) { + // There are min/max values so put in hover. + hoverText.push(`${hoverStart}
min: ${minValue.toPrecision(6)}
max: ${maxValue.toPrecision(6)}`); + } else { + hoverText.push(hoverStart); + } } else { - hoverText.push(` ${timeReading.format('ddd, ll LTS')}
${label}: ${readingValue.toPrecision(6)} ${unitLabel}`); + hoverText.push(hoverStart); } }); diff --git a/src/server/sql/reading/create_reading_views.sql b/src/server/sql/reading/create_reading_views.sql index 78586515d..664e9c373 100644 --- a/src/server/sql/reading/create_reading_views.sql +++ b/src/server/sql/reading/create_reading_views.sql @@ -455,29 +455,11 @@ DECLARE -- If it is flow or raw readings then it is already a rate so just convert it but also need to normalize -- to per hour. ((r.reading * 3600 / u.sec_in_rate) * c.slope + c.intercept) - --The next two case expressions are copied from the case expression above. "reading_rate AS min/max_rate" does not work. END AS reading_rate, - CASE WHEN u.unit_represent = 'quantity'::unit_represent_type THEN - -- If it is quantity readings then need to convert to rate per hour by dividing by the time length where - -- the 3600 is needed since EPOCH is in seconds. - ((r.reading / (extract(EPOCH FROM (r.end_timestamp - r.start_timestamp)) / 3600)) * c.slope + c.intercept) - WHEN (u.unit_represent = 'flow'::unit_represent_type OR u.unit_represent = 'raw'::unit_represent_type) THEN - -- If it is flow or raw readings then it is already a rate so just convert it but also need to normalize - -- to per hour. - ((r.reading * 3600 / u.sec_in_rate) * c.slope + c.intercept) - END AS min_rate, - CASE WHEN u.unit_represent = 'quantity'::unit_represent_type THEN - -- If it is quantity readings then need to convert to rate per hour by dividing by the time length where - -- the 3600 is needed since EPOCH is in seconds. - ((r.reading / (extract(EPOCH FROM (r.end_timestamp - r.start_timestamp)) / 3600)) * c.slope + c.intercept) - WHEN (u.unit_represent = 'flow'::unit_represent_type OR u.unit_represent = 'raw'::unit_represent_type) THEN - -- If it is flow or raw readings then it is already a rate so just convert it but also need to normalize - -- to per hour. - ((r.reading * 3600 / u.sec_in_rate) * c.slope + c.intercept) - END AS max_rate, - -- TODO This does not seem to work - -- reading_rate AS min_rate, - -- reading_rate AS max_rate, + -- There is no range of values on raw/meter data so return NaN to indicate that. + -- The route will return this as null when it shows up in Redux state. + cast('NaN' AS DOUBLE PRECISION) AS min_rate, + cast('NaN' AS DOUBLE PRECISION) as max_rate, r.start_timestamp, r.end_timestamp FROM (((readings r @@ -778,4 +760,4 @@ BEGIN INNER JOIN unnest(group_ids) gids(id) on gdm.group_id = gids.id GROUP BY gdm.group_id, readings.start_timestamp, readings.end_timestamp; END; -$$ LANGUAGE 'plpgsql'; \ No newline at end of file +$$ LANGUAGE 'plpgsql'; From 9ee68a830bdd6cfc5e4cd27bd6a42de44631e395 Mon Sep 17 00:00:00 2001 From: Steven Huss-Lederman Date: Fri, 4 Aug 2023 15:23:45 -0500 Subject: [PATCH 15/22] add min/max change to migration --- .../sql/readings/create_reading_views.sql | 26 +++---------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/src/server/migrations/1.0.0-1.1.0/sql/readings/create_reading_views.sql b/src/server/migrations/1.0.0-1.1.0/sql/readings/create_reading_views.sql index 3fceecd30..abac7e769 100644 --- a/src/server/migrations/1.0.0-1.1.0/sql/readings/create_reading_views.sql +++ b/src/server/migrations/1.0.0-1.1.0/sql/readings/create_reading_views.sql @@ -368,29 +368,11 @@ DECLARE -- If it is flow or raw readings then it is already a rate so just convert it but also need to normalize -- to per hour. ((r.reading * 3600 / u.sec_in_rate) * c.slope + c.intercept) - --The next two case expressions are copied from the case expression above. "reading_rate AS min/max_rate" does not work. END AS reading_rate, - CASE WHEN u.unit_represent = 'quantity'::unit_represent_type THEN - -- If it is quantity readings then need to convert to rate per hour by dividing by the time length where - -- the 3600 is needed since EPOCH is in seconds. - ((r.reading / (extract(EPOCH FROM (r.end_timestamp - r.start_timestamp)) / 3600)) * c.slope + c.intercept) - WHEN (u.unit_represent = 'flow'::unit_represent_type OR u.unit_represent = 'raw'::unit_represent_type) THEN - -- If it is flow or raw readings then it is already a rate so just convert it but also need to normalize - -- to per hour. - ((r.reading * 3600 / u.sec_in_rate) * c.slope + c.intercept) - END AS min_rate, - CASE WHEN u.unit_represent = 'quantity'::unit_represent_type THEN - -- If it is quantity readings then need to convert to rate per hour by dividing by the time length where - -- the 3600 is needed since EPOCH is in seconds. - ((r.reading / (extract(EPOCH FROM (r.end_timestamp - r.start_timestamp)) / 3600)) * c.slope + c.intercept) - WHEN (u.unit_represent = 'flow'::unit_represent_type OR u.unit_represent = 'raw'::unit_represent_type) THEN - -- If it is flow or raw readings then it is already a rate so just convert it but also need to normalize - -- to per hour. - ((r.reading * 3600 / u.sec_in_rate) * c.slope + c.intercept) - END AS max_rate, - -- TODO This does not seem to work - -- reading_rate AS min_rate, - -- reading_rate AS max_rate, + -- There is no range of values on raw/meter data so return NaN to indicate that. + -- The route will return this as null when it shows up in Redux state. + cast('NaN' AS DOUBLE PRECISION) AS min_rate, + cast('NaN' AS DOUBLE PRECISION) as max_rate, r.start_timestamp, r.end_timestamp FROM (((readings r From 7fbc7f256d974383b34d36933b859d67af89b51a Mon Sep 17 00:00:00 2001 From: spearec Date: Fri, 4 Aug 2023 20:42:21 +0000 Subject: [PATCH 16/22] remove error bars when no data --- src/client/app/containers/LineChartContainer.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/client/app/containers/LineChartContainer.ts b/src/client/app/containers/LineChartContainer.ts index 721e73b54..d0c214a21 100644 --- a/src/client/app/containers/LineChartContainer.ts +++ b/src/client/app/containers/LineChartContainer.ts @@ -85,7 +85,7 @@ function mapStateToProps(state: State) { yData.push(readingValue); // All hover have the date, meter name and value. const hoverStart = ` ${timeReading.format('ddd, ll LTS')}
${label}: ${readingValue.toPrecision(6)} ${unitLabel}`; - if (state.graph.showMinMax) { + if (state.graph.showMinMax && reading.max != null) { // We want to show min/max. Note if the data is raw for this meter then all the min/max values are null. // In this case we still push the min/max but plotly will not show them. This is a little extra work // but makes the code cleaner. @@ -93,12 +93,7 @@ function mapStateToProps(state: State) { yMinData.push(minValue); const maxValue = reading.max * scaling; yMaxData.push(maxValue); - if (reading.min != null) { - // There are min/max values so put in hover. - hoverText.push(`${hoverStart}
min: ${minValue.toPrecision(6)}
max: ${maxValue.toPrecision(6)}`); - } else { - hoverText.push(hoverStart); - } + hoverText.push(`${hoverStart}
min: ${minValue.toPrecision(6)}
max: ${maxValue.toPrecision(6)}`); } else { hoverText.push(hoverStart); } @@ -123,7 +118,8 @@ function mapStateToProps(state: State) { name: label, x: xData, y: yData, - error_y: state.graph.showMinMax ? { + // only show error bars if enabled and there is data + error_y: state.graph.showMinMax && yMaxData.length > 0 ? { type: 'data', symmetric: false, array: yMaxData.map((maxValue, index) => (maxValue - yData[index])), From 1b55eb16c70a401b48bbb18baa71f2fbdb77d418 Mon Sep 17 00:00:00 2001 From: Elias Woldie Date: Mon, 7 Aug 2023 11:58:59 -0700 Subject: [PATCH 17/22] Fixed formats --- src/client/app/actions/lineReadings.ts | 1 + src/client/app/components/UIOptionsComponent.tsx | 2 +- src/client/app/translations/data.js | 6 +++--- src/client/app/utils/exportData.ts | 2 +- src/server/models/Reading.js | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/client/app/actions/lineReadings.ts b/src/client/app/actions/lineReadings.ts index 9161a9559..f2301489c 100644 --- a/src/client/app/actions/lineReadings.ts +++ b/src/client/app/actions/lineReadings.ts @@ -130,6 +130,7 @@ function fetchGroupLineReadings(groupIDs: number[], timeInterval: TimeInterval, dispatch(receiveGroupLineReadings(groupIDs, timeInterval, unitID, groupLineReadings)); }; } + /** * Fetches readings for the line chart of all selected meters and groups, if needed. * @param timeInterval the interval over which to check diff --git a/src/client/app/components/UIOptionsComponent.tsx b/src/client/app/components/UIOptionsComponent.tsx index 0b5145787..948cd9c46 100644 --- a/src/client/app/components/UIOptionsComponent.tsx +++ b/src/client/app/components/UIOptionsComponent.tsx @@ -90,7 +90,7 @@ class UIOptionsComponent extends React.Component - {/* Controls error bar, specifically for the line chart. */} + {/* Controls error bar, specifically for the line chart. */} {this.props.chartToRender === ChartTypes.line && } diff --git a/src/client/app/translations/data.js b/src/client/app/translations/data.js index fd82717fc..eebf49f0f 100644 --- a/src/client/app/translations/data.js +++ b/src/client/app/translations/data.js @@ -604,7 +604,7 @@ const localeData = { "edit.unit": "(need French) Edit Unit", "email": "E-mail", "enable": "Activer", - "error.bar": "(Need French) Error Bar", + "error.bar": "(Need French) Show error bars", "export.graph.data": "Exporter les données du diagramme", "export.raw.graph.data": "(need French) Export graph meter data", "failed.logging.in": "Echec de la connexion", @@ -689,7 +689,7 @@ const localeData = { "help.home.chart.select": "(Need French) Any graph type can be used with any combination of groups and meters (except maps which only do meters within map). Line graphs show the usage (e.g., kW) vs. time. You can zoom and scroll with the controls right below the graph. Bar shows the total usage (e.g., kWh) for the time frame of each bar where you can control the time frame. Compare allows you to see the current usage vs. the usage in the last previous period for a day, week and four weeks. Map graphs show a spacial image of each meter where the circle size is related to four weeks of usage. Clicking on one of the choices (Line, Bar, Compare, Map) renders that graphic. Please visit {link} for further details and information.", "help.home.compare.interval.tip": "(Need French) Selects the time interval (Day, Week or 4 Weeks) to compare for current to previous. Please see {link} for further details and information.", "help.home.compare.sort.tip": "(Need French) Allows user to select the order of multiple comparison graphs to be Alphabetical (by name), Ascending (greatest to least reduction in usage) and Descending (least to greatest reduction in usage). Please see {link} for further details and information.", - "help.home.error.bar": "(Need French) Toggles error bar with min and max value.Please visit {link} for further details and information.", + "help.home.error.bar": "(Need French) Toggles error bar with min and max value. Please visit {link} for further details and information.", "help.home.export.graph.data": "(Need French) With the \"Export graph data\" button, one can export the data for the graph when viewing either a line or compare graphic. The zoom and scroll feature on the line graph allows you to control the time frame of the data exported. The \"Export graph data\" button gives the data points for the graph and not the original meter data. The \"Export graph meter data\" gives the underlying meter data (line graphs only). Please visit {link} for further details and information on when meter data export is allowed.", "help.home.hide.or.show.options": "(Need French) With the \"Hide options\" button the options buttons and dropdown menus on the left and top of the OED window are all hidden. A new \"Menu\" button at the top, right of the web page is available to see these options including a button to \"Show options\" to reverse this choice. When the web page window becomes too small the options will automatically hide. Please visit {link} for further details and information.", "help.home.select.groups": "(Need French) Groups aggregate (sum the usage) of any combination of groups and meters. You can choose which groups to view in your graphic from the \"Groups:\" dropdown menu. Note you can type in text to limit which groups are shown. The Groups button in the top, right side of the window allows you to see more details about each group and, if you are an admin, to edit the groups. Please visit {link} for further details and information.", @@ -1059,7 +1059,7 @@ const localeData = { "edit.unit": "(need Spanish) Edit Unit", "email": "Correo Electronico", "enable": "Activar", - "error.bar": "(Need Spanish) Error Bar", + "error.bar": "(Need Spanish) Show error bars", "export.graph.data": "Exportar datos del gráfico", "export.raw.graph.data": "Exportar datos gráficos de medidor", "failed.logging.in": "Error al iniciar sesión", diff --git a/src/client/app/utils/exportData.ts b/src/client/app/utils/exportData.ts index 174ad90b0..8b5a81a7e 100644 --- a/src/client/app/utils/exportData.ts +++ b/src/client/app/utils/exportData.ts @@ -37,7 +37,7 @@ function convertToCSV(readings: LineReading[] | BarReading[], meter: string, uni const startTimeStamp = moment.utc(reading.startTimestamp).format('YYYY-MM-DD HH:mm:ss'); const endTimeStamp = moment.utc(reading.endTimestamp).format('YYYY-MM-DD HH:mm:ss'); csvOutput += `${value},${startTimeStamp},${endTimeStamp}`; - // Populate the min and max columns only for LineReading types. + // Include min and max in export if appropraite." if (showMinMax) { const min = reading.min * scaling; const max = reading.max * scaling; diff --git a/src/server/models/Reading.js b/src/server/models/Reading.js index ef04404da..17864308b 100644 --- a/src/server/models/Reading.js +++ b/src/server/models/Reading.js @@ -248,7 +248,7 @@ class Reading { const readingsByMeterID = mapToObject(meterIDs, () => []); for (const row of allMeterLineReadings) { readingsByMeterID[row.meter_id].push( - {reading_rate: row.reading_rate, min_rate: row.min_rate, max_rate: row.max_rate, start_timestamp: row.start_timestamp, end_timestamp: row.end_timestamp } + { reading_rate: row.reading_rate, min_rate: row.min_rate, max_rate: row.max_rate, start_timestamp: row.start_timestamp, end_timestamp: row.end_timestamp } ); } return readingsByMeterID; From 27d5abd9527cc8b073920f370bb88d0f1d836659 Mon Sep 17 00:00:00 2001 From: Elias Woldie Date: Wed, 9 Aug 2023 08:33:34 -0700 Subject: [PATCH 18/22] Internationalized min & max --- src/client/app/containers/LineChartContainer.ts | 2 +- src/client/app/translations/data.js | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/client/app/containers/LineChartContainer.ts b/src/client/app/containers/LineChartContainer.ts index d0c214a21..39576e1b4 100644 --- a/src/client/app/containers/LineChartContainer.ts +++ b/src/client/app/containers/LineChartContainer.ts @@ -93,7 +93,7 @@ function mapStateToProps(state: State) { yMinData.push(minValue); const maxValue = reading.max * scaling; yMaxData.push(maxValue); - hoverText.push(`${hoverStart}
min: ${minValue.toPrecision(6)}
max: ${maxValue.toPrecision(6)}`); + hoverText.push(`${hoverStart}
${translate('min')}: ${minValue.toPrecision(6)}
${translate('max')}: ${maxValue.toPrecision(6)}`); } else { hoverText.push(hoverStart); } diff --git a/src/client/app/translations/data.js b/src/client/app/translations/data.js index eebf49f0f..0345f0e55 100644 --- a/src/client/app/translations/data.js +++ b/src/client/app/translations/data.js @@ -342,6 +342,8 @@ const localeData = { "meter.time.zone": "Time Zone:", "meter.type": "Type:", "minute": "Minute", + "min": "min", + "max": "max", "more.energy": "more energy", "name": "Name", "navigation": "Navigation", @@ -797,6 +799,8 @@ const localeData = { "meter.time.zone": "fuseau horaire du mètre", "meter.type": "Type de Mèters", "minute": "(need French) Minute", + "min": "(Need French) min", + "max": "(Need French) max", "more.energy": "plus d'énergie", "name": "Nom", "navigation": "Navigation", @@ -1252,6 +1256,8 @@ const localeData = { "meter.time.zone": "Zona horaria del medidor", "meter.type": "Tipo de medidor", "minute": "Minuto", + "min": "(Need Spanish) min", + "max": "(Need Spanish) max", "more.energy": "más energía", "name": "Nombre", "navigation": "Navegación", From e498afce3ad6aa853cb8e88b04e1f6a83ecb4036 Mon Sep 17 00:00:00 2001 From: Elias Woldie Date: Wed, 9 Aug 2023 08:43:49 -0700 Subject: [PATCH 19/22] Fixed conventions --- src/client/app/reducers/graph.ts | 1 - src/client/app/translations/data.js | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/client/app/reducers/graph.ts b/src/client/app/reducers/graph.ts index 592c61033..3fbc42be0 100644 --- a/src/client/app/reducers/graph.ts +++ b/src/client/app/reducers/graph.ts @@ -99,7 +99,6 @@ export default function graph(state = defaultState, action: GraphAction) { ...state, showMinMax: !state.showMinMax }; - case ActionType.ChangeBarStacking: return { ...state, diff --git a/src/client/app/translations/data.js b/src/client/app/translations/data.js index 0345f0e55..626a6cd66 100644 --- a/src/client/app/translations/data.js +++ b/src/client/app/translations/data.js @@ -691,7 +691,7 @@ const localeData = { "help.home.chart.select": "(Need French) Any graph type can be used with any combination of groups and meters (except maps which only do meters within map). Line graphs show the usage (e.g., kW) vs. time. You can zoom and scroll with the controls right below the graph. Bar shows the total usage (e.g., kWh) for the time frame of each bar where you can control the time frame. Compare allows you to see the current usage vs. the usage in the last previous period for a day, week and four weeks. Map graphs show a spacial image of each meter where the circle size is related to four weeks of usage. Clicking on one of the choices (Line, Bar, Compare, Map) renders that graphic. Please visit {link} for further details and information.", "help.home.compare.interval.tip": "(Need French) Selects the time interval (Day, Week or 4 Weeks) to compare for current to previous. Please see {link} for further details and information.", "help.home.compare.sort.tip": "(Need French) Allows user to select the order of multiple comparison graphs to be Alphabetical (by name), Ascending (greatest to least reduction in usage) and Descending (least to greatest reduction in usage). Please see {link} for further details and information.", - "help.home.error.bar": "(Need French) Toggles error bar with min and max value. Please visit {link} for further details and information.", + "help.home.error.bar": "(Need French) Toggle error bars with min and max value. Please visit {link} for further details and information.", "help.home.export.graph.data": "(Need French) With the \"Export graph data\" button, one can export the data for the graph when viewing either a line or compare graphic. The zoom and scroll feature on the line graph allows you to control the time frame of the data exported. The \"Export graph data\" button gives the data points for the graph and not the original meter data. The \"Export graph meter data\" gives the underlying meter data (line graphs only). Please visit {link} for further details and information on when meter data export is allowed.", "help.home.hide.or.show.options": "(Need French) With the \"Hide options\" button the options buttons and dropdown menus on the left and top of the OED window are all hidden. A new \"Menu\" button at the top, right of the web page is available to see these options including a button to \"Show options\" to reverse this choice. When the web page window becomes too small the options will automatically hide. Please visit {link} for further details and information.", "help.home.select.groups": "(Need French) Groups aggregate (sum the usage) of any combination of groups and meters. You can choose which groups to view in your graphic from the \"Groups:\" dropdown menu. Note you can type in text to limit which groups are shown. The Groups button in the top, right side of the window allows you to see more details about each group and, if you are an admin, to edit the groups. Please visit {link} for further details and information.", @@ -1148,7 +1148,7 @@ const localeData = { "help.home.chart.select": "Se puede usar cualquier tipo de gráfico con cualquier combinación de grupos y medidores (excepto los mapas que sólo hacen medidor dentro del mapa). Los gráficos de líneas muestran el uso (por ejemplo, kW) contra el tiempo. Puede hacer zoom y desplazarse con los controles justo debajo del gráfico. La barra muestra el uso total (por ejemplo, kWh) para el período de tiempo de cada barra donde puede controlar el período de tiempo. Comparar le permite ver el uso actual contra el uso de el último período anterior durante un día, una semana y cuatro semanas. Gráficos del mapa muestra un imagen especial de cada meter donde el talla del círculo está relacionado de cuatro semanas de uso. Al hacer clic en uno de las opciones (Línea, Barra, Comparar, Mapa) va a representa ese gráfico. Visite {link} para obtener más detalles e información.", "help.home.compare.interval.tip": "Selecciona el intervalo de tiempo (Día, Semana o Cuatro semanas) para comparar el actual con el anterior. Por favor, vea {link} para más detalles e información.", "help.home.compare.sort.tip": "Permite al usuario seleccionar el orden de los gráficos de comparación múltiple para que sean alfabéticos (por nombre), ascendentes (de mayor a menor reducción de uso) y descendentes (de menor a mayor reducción de uso). Por favor, vea {link} para más detalles e información.", - "help.home.error.bar": "(Need Spanish) Toggles error bar with min and max value.Please visit {link} for further details and information.", + "help.home.error.bar": "(Need Spanish) Toggle error bars with min and max value. Please visit {link} for further details and information.", "help.home.export.graph.data": "Con el botón \"Exportar data de gráfico\", uno puede exportar los datos del gráfico al ver una línea o comparar el gráfico. La función de zoom y desplazamiento en el gráfico de líneas le permite controlar el período de tiempo de los datos exportados. El \"Exportar data de gráfico\" botón da el puntos de dato para el gráfico y no los datos sin procesar de medidor. La \"Exportar el dato gráfhico de medidor\" proporciona los datos del medidor subyacente (solo gráficos de líneas). Visite {link} para obtener más detalles e información.", "help.home.hide.or.show.options": "Con el botón \"Opciones de muelle\", los botones de opciones y los menús desplegables en la parte superior e izquierda de la ventana de OED están ocultos. Un nuevo botón \"Menú\" en la parte superior derecha de la página web está disponible para ver estas opciones, incluso un botón para \"mostrar opciones\" para revertir esta elección. Cuando la ventana de la página web se vuelve demasiado pequeña, las opciones se ocultarán automáticamente. Visite {link} para obtener más detalles e información.", "help.home.select.groups": "Los grupos se agregan (suman el uso) de cualquier combinación de grupos y medidores. Puede elegir qué grupos ver en su gráfico desde el menú desplegable \"Groups:\" . Tenga en cuenta que puede escribir texto para limitar que grupos se muestran. El botón Grupos en la parte superior derecha de la ventana le permite ver más detalles sobre cada grupo y, si es un administrador, editar los grupos. Visite {link} para obtener más detalles e información.", From 5fe0d2c4c5f2cb38971e532fa5b7c85a33ef233f Mon Sep 17 00:00:00 2001 From: Elias Woldie Date: Wed, 9 Aug 2023 08:58:04 -0700 Subject: [PATCH 20/22] Formatted min & max string alphabetically --- src/client/app/translations/data.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client/app/translations/data.js b/src/client/app/translations/data.js index 626a6cd66..7eb1180ef 100644 --- a/src/client/app/translations/data.js +++ b/src/client/app/translations/data.js @@ -298,6 +298,7 @@ const localeData = { "map.new.upload": "Upload map image to begin.", "map.notify.calibration.needed": "Calibration needed before display", "map.upload.new.file": "Redo", + "max": "max", "menu": "Menu", "meter": "Meter", "meters": "Meters", @@ -343,7 +344,6 @@ const localeData = { "meter.type": "Type:", "minute": "Minute", "min": "min", - "max": "max", "more.energy": "more energy", "name": "Name", "navigation": "Navigation", @@ -755,6 +755,7 @@ const localeData = { "map.new.upload": "Téléchargez l'image de la carte pour commencer.", "map.notify.calibration.needed": "Étalonnage nécessaire pour l'affichage", "map.upload.new.file": "Refaire", + "max": "(Need French) max", "menu": "Menu", "meter": "Mèter", "meters": "Mèters", @@ -800,7 +801,6 @@ const localeData = { "meter.type": "Type de Mèters", "minute": "(need French) Minute", "min": "(Need French) min", - "max": "(Need French) max", "more.energy": "plus d'énergie", "name": "Nom", "navigation": "Navigation", @@ -1212,6 +1212,7 @@ const localeData = { "map.new.upload": "Subir la imagen del mapa para empezar.", "map.notify.calibration.needed": "Nececitar calibración antes de visualizar", "map.upload.new.file": "Rehacer", + "max": "(Need Spanish) max", "menu": "Menú", "meter": "Medidor", "meters": "Medidores", @@ -1257,7 +1258,6 @@ const localeData = { "meter.type": "Tipo de medidor", "minute": "Minuto", "min": "(Need Spanish) min", - "max": "(Need Spanish) max", "more.energy": "más energía", "name": "Nombre", "navigation": "Navegación", From 2d3edc9c50b4ab0914e019f1244279cbbaed2879 Mon Sep 17 00:00:00 2001 From: Steven Huss-Lederman Date: Thu, 17 Aug 2023 12:33:16 -0500 Subject: [PATCH 21/22] fix my spelling mistake --- src/client/app/utils/exportData.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/app/utils/exportData.ts b/src/client/app/utils/exportData.ts index 8b5a81a7e..fde020263 100644 --- a/src/client/app/utils/exportData.ts +++ b/src/client/app/utils/exportData.ts @@ -37,7 +37,7 @@ function convertToCSV(readings: LineReading[] | BarReading[], meter: string, uni const startTimeStamp = moment.utc(reading.startTimestamp).format('YYYY-MM-DD HH:mm:ss'); const endTimeStamp = moment.utc(reading.endTimestamp).format('YYYY-MM-DD HH:mm:ss'); csvOutput += `${value},${startTimeStamp},${endTimeStamp}`; - // Include min and max in export if appropraite." + // Include min and max in export if appropriate." if (showMinMax) { const min = reading.min * scaling; const max = reading.max * scaling; @@ -123,4 +123,4 @@ export function downloadRawCSV(readings: RawReadings[], meter: string, unit: str const filename = `oedRawExport_line_${startTime}_to_${endTime}_for_${meter}.csv`; downloadCSV(csvOutput, filename); } -} \ No newline at end of file +} From d8e9b0a3b439a7c4e16fb4c0b14f9f96ddee732a Mon Sep 17 00:00:00 2001 From: moll0928 <112609691+moll0928@users.noreply.github.com> Date: Mon, 21 Aug 2023 09:18:06 +0000 Subject: [PATCH 22/22] Update the usage of cookie-parser --- package-lock.json | 35 ----------------------------------- package.json | 1 - src/server/app.js | 2 -- 3 files changed, 38 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8de354e84..66b7e7c20 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,6 @@ "bcryptjs": "~2.4.3", "body-parser": "~1.20.2", "bootstrap": "~5.2.3", - "cookie-parser": "~1.4.6", "csv": "~5.3.2", "csv-parse": "~4.16.3", "csv-stringify": "~5.6.5", @@ -4338,26 +4337,6 @@ "safe-buffer": "~5.1.1" } }, - "node_modules/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-parser": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", - "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", - "dependencies": { - "cookie": "0.4.1", - "cookie-signature": "1.0.6" - }, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/cookie-signature": { "version": "1.0.6", "license": "MIT" @@ -14881,20 +14860,6 @@ "safe-buffer": "~5.1.1" } }, - "cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" - }, - "cookie-parser": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", - "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", - "requires": { - "cookie": "0.4.1", - "cookie-signature": "1.0.6" - } - }, "cookie-signature": { "version": "1.0.6" }, diff --git a/package.json b/package.json index cf46682ab..939eb829c 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,6 @@ "bcryptjs": "~2.4.3", "body-parser": "~1.20.2", "bootstrap": "~5.2.3", - "cookie-parser": "~1.4.6", "csv": "~5.3.2", "csv-parse": "~4.16.3", "csv-stringify": "~5.6.5", diff --git a/src/server/app.js b/src/server/app.js index e753cd05b..a29aac42e 100644 --- a/src/server/app.js +++ b/src/server/app.js @@ -8,7 +8,6 @@ const rateLimit = require('express-rate-limit'); const path = require('path'); const favicon = require('serve-favicon'); const logger = require('morgan'); -const cookieParser = require('cookie-parser'); const bodyParser = require('body-parser'); const config = require('./config'); @@ -62,7 +61,6 @@ if (log.level !== LogLevel.SILENT) { app.use(favicon(path.join(__dirname, '..', 'client', 'public', 'favicon.ico'))); app.use(bodyParser.json({ limit: '50mb' })); app.use(bodyParser.urlencoded({ extended: false, limit: '50mb' })); -app.use(cookieParser()); app.use('/api/users', users); app.use('/api/meters', meters);