From 1ad12c430a9f9057cac900ead48e610f3b0d359b Mon Sep 17 00:00:00 2001 From: Jover Lee Date: Fri, 6 Sep 2024 14:45:45 -0700 Subject: [PATCH] measurements: Add URL query param `m_groupBy` Use query param `m_groupBy` to specify which group by field to use in the measurements panel. If it is not one of the collection's groupings, then the query param will be ignored and removed. --- src/actions/measurements.js | 91 ++++++++++++------- .../controls/measurementsOptions.js | 16 ++-- src/middleware/changeURL.js | 1 + src/reducers/controls.ts | 3 +- 4 files changed, 66 insertions(+), 45 deletions(-) diff --git a/src/actions/measurements.js b/src/actions/measurements.js index 58b8f3967..a37c2d418 100644 --- a/src/actions/measurements.js +++ b/src/actions/measurements.js @@ -5,6 +5,7 @@ import { APPLY_MEASUREMENTS_FILTER, CHANGE_MEASUREMENTS_COLLECTION, CHANGE_MEASUREMENTS_DISPLAY, + CHANGE_MEASUREMENTS_GROUP_BY, LOAD_MEASUREMENTS, TOGGLE_MEASUREMENTS_OVERALL_MEAN, TOGGLE_MEASUREMENTS_THRESHOLD, @@ -48,30 +49,32 @@ function getCollectionDefaultControl(controlKey, collection) { } const collectionDefaults = collection["display_defaults"] || {}; const displayDefaultKey = collectionControlToDisplayDefaults[controlKey]; - const defaultControl = collectionDefaults[displayDefaultKey]; + let defaultControl = collectionDefaults[displayDefaultKey]; // Check default is a valid value for the control key - if (defaultControl !== undefined) { - switch (controlKey) { - case 'measurementsGroupBy': - if (!collection.fields.has(defaultControl)) { - console.error(`Ignoring invalid ${displayDefaultKey} value ${defaultControl}, must be one of collection's fields`) - defaultControl = undefined; - } - break; - case 'measurementsDisplay': - const expectedValues = ["mean", "raw"]; - if (!expectedValues.includes(defaultControl)) { - console.error(`Ignoring invalid ${displayDefaultKey} value ${defaultControl}, must be one of ${expectedValues}`) - defaultControl = undefined; + switch (controlKey) { + case 'measurementsGroupBy': + if (defaultControl === undefined || !collection.groupings.has(defaultControl)) { + if (defaultControl !== undefined) { + console.error(`Ignoring invalid ${displayDefaultKey} value ${defaultControl}, must be one of collection's groupings. Using first grouping as default`) } - break; - case 'measurementsShowOverallMean': - if (typeof defaultControl !== "boolean") { - console.error(`Ignoring invalid ${displayDefaultKey} value ${defaultControl}, must be a boolean`) - defaultControl = undefined; - } - break; - case 'measurementsShowThreshold': + defaultControl = collection.groupings.keys().next().value; + } + break; + case 'measurementsDisplay': + const expectedValues = ["mean", "raw"]; + if (defaultControl !== undefined && !expectedValues.includes(defaultControl)) { + console.error(`Ignoring invalid ${displayDefaultKey} value ${defaultControl}, must be one of ${expectedValues}`) + defaultControl = undefined; + } + break; + case 'measurementsShowOverallMean': + if (defaultControl !== undefined && typeof defaultControl !== "boolean") { + console.error(`Ignoring invalid ${displayDefaultKey} value ${defaultControl}, must be a boolean`) + defaultControl = undefined; + } + break; + case 'measurementsShowThreshold': + if (defaultControl !== undefined) { if (!Array.isArray(collection.thresholds) || !collection.thresholds.some((threshold) => typeof threshold === "number")) { console.error(`Ignoring ${displayDefaultKey} value because collection does not have valid thresholds`) @@ -80,13 +83,13 @@ function getCollectionDefaultControl(controlKey, collection) { console.error(`Ignoring invalid ${displayDefaultKey} value ${defaultControl}, must be a boolean`) defaultControl = undefined; } - break; - case 'measurementsFilters': - console.debug(`Skipping control key ${controlKey} because it does not have default controls`); - break; - default: - console.error(`Skipping unknown control key ${controlKey}`); - } + } + break; + case 'measurementsFilters': + console.debug(`Skipping control key ${controlKey} because it does not have default controls`); + break; + default: + console.error(`Skipping unknown control key ${controlKey}`); } return defaultControl; } @@ -104,10 +107,10 @@ function getCollectionDefaultControl(controlKey, collection) { const getCollectionDisplayControls = (controls, collection) => { // Copy current control options for measurements const newControls = pick(controls, Object.keys(defaultMeasurementsControlState)); - // Checks the current group by is available as a field in collection - if (!collection.fields.has(newControls.measurementsGroupBy)) { - // If current group by is not available as a field, then default to the first grouping option. - [newControls.measurementsGroupBy] = collection.groupings.keys(); + // Checks the current group by is available as a grouping in collection + // If it doesn't exist, set to undefined so it will get filled in with collection's default + if (!collection.groupings.has(newControls.measurementsGroupBy)) { + newControls.measurementsGroupBy = undefined } // Verify that current filters are valid for the new collection @@ -364,8 +367,21 @@ export const changeMeasurementsDisplay = (newDisplay) => (dispatch, getState) => }); } +export const changeMeasurementsGroupBy = (newGroupBy) => (dispatch, getState) => { + const { controls, measurements } = getState(); + const controlKey = "measurementsGroupBy"; + const newControls = { [controlKey]: newGroupBy }; + + dispatch({ + type: CHANGE_MEASUREMENTS_GROUP_BY, + controls: newControls, + queryParams: createMeasurementsQueryFromControls(newControls, measurements.collectionToDisplay) + }); +} + const controlToQueryParamMap = { measurementsDisplay: "m_display", + measurementsGroupBy: "m_groupBy", measurementsShowOverallMean: "m_overallMean", measurementsShowThreshold: "m_threshold", }; @@ -381,7 +397,8 @@ function createMeasurementsQueryFromControls(measurementControls, collection) { newQuery[queryKey] = ""; } else { switch(controlKey) { - case "measurementsDisplay": + case "measurementsDisplay": // fallthrough + case "measurementsGroupBy": newQuery[queryKey] = controlValue; break; case "measurementsShowOverallMean": @@ -414,6 +431,12 @@ export function createMeasurementsControlsFromQuery(query){ expectedValues = ["mean", "raw"]; conversionFn = () => queryValue; break; + case "m_groupBy": + // Accept any value here because we cannot validate the query before + // the measurements JSON is loaded + expectedValues = [queryValue]; + conversionFn = () => queryValue; + break; case "m_overallMean": // fallthrough case "m_threshold": expectedValues = ["show", "hide"]; diff --git a/src/components/controls/measurementsOptions.js b/src/components/controls/measurementsOptions.js index 484655f3d..78ace0c37 100644 --- a/src/components/controls/measurementsOptions.js +++ b/src/components/controls/measurementsOptions.js @@ -2,10 +2,13 @@ import React from "react"; import { useSelector } from "react-redux"; import { useAppDispatch } from "../../hooks"; import { isEqual } from "lodash"; -import { changeMeasurementsCollection,changeMeasurementsDisplay, toggleOverallMean, toggleThreshold } from "../../actions/measurements"; import { - CHANGE_MEASUREMENTS_GROUP_BY -} from "../../actions/types"; + changeMeasurementsCollection, + changeMeasurementsDisplay, + changeMeasurementsGroupBy, + toggleOverallMean, + toggleThreshold +} from "../../actions/measurements"; import { controlsWidth } from "../../util/globals"; import { SidebarSubtitle, SidebarButton } from "./styles"; import Toggle from "./toggle"; @@ -78,12 +81,7 @@ const MeasurementsOptions = () => { isClearable={false} isSearchable={false} isMulti={false} - onChange={(opt) => { - dispatch({ - type: CHANGE_MEASUREMENTS_GROUP_BY, - data: opt.value - }); - }} + onChange={(opt) => {dispatch(changeMeasurementsGroupBy(opt.value));}} /> diff --git a/src/middleware/changeURL.js b/src/middleware/changeURL.js index 32f382cbc..7558b27b1 100644 --- a/src/middleware/changeURL.js +++ b/src/middleware/changeURL.js @@ -225,6 +225,7 @@ export const changeURLMiddleware = (store) => (next) => (action) => { case types.LOAD_MEASUREMENTS: // fallthrough case types.CHANGE_MEASUREMENTS_COLLECTION: // fallthrough case types.CHANGE_MEASUREMENTS_DISPLAY: // fallthrough + case types.CHANGE_MEASUREMENTS_GROUP_BY: // fallthrough case types.TOGGLE_MEASUREMENTS_OVERALL_MEAN: // fallthrough case types.TOGGLE_MEASUREMENTS_THRESHOLD: query = {...query, ...action.queryParams}; diff --git a/src/reducers/controls.ts b/src/reducers/controls.ts index b0d4303b1..3b0c7f664 100644 --- a/src/reducers/controls.ts +++ b/src/reducers/controls.ts @@ -361,11 +361,10 @@ const Controls = (state: ControlsState = getDefaultControlsState(), action): Con case types.LOAD_MEASUREMENTS: // fallthrough case types.CHANGE_MEASUREMENTS_COLLECTION: // fallthrough case types.CHANGE_MEASUREMENTS_DISPLAY: // fallthrough + case types.CHANGE_MEASUREMENTS_GROUP_BY: // fallthrough case types.TOGGLE_MEASUREMENTS_OVERALL_MEAN: // fallthrough case types.TOGGLE_MEASUREMENTS_THRESHOLD: return {...state, ...action.controls}; - case types.CHANGE_MEASUREMENTS_GROUP_BY: - return {...state, measurementsGroupBy: action.data}; case types.APPLY_MEASUREMENTS_FILTER: return {...state, measurementsFilters: action.data}; /**