diff --git a/src/actions/loadData.js b/src/actions/loadData.js index c16e43781..4b81391ca 100644 --- a/src/actions/loadData.js +++ b/src/actions/loadData.js @@ -4,7 +4,6 @@ import { getServerAddress } from "../util/globals"; import { goTo404 } from "./navigation"; import { createStateFromQueryOrJSONs, createTreeTooState, getNarrativePageFromQuery } from "./recomputeReduxState"; import { loadFrequencies } from "./frequencies"; -import { parseMeasurementsJSON, loadMeasurements } from "./measurements"; import { fetchJSON, fetchWithErrorHandling } from "../util/serverInteraction"; import { warningNotification, errorNotification } from "./notifications"; import { parseMarkdownNarrativeFile } from "../util/parseNarrative"; @@ -12,7 +11,6 @@ import { NoContentError, FetchError} from "../util/exceptions"; import { parseMarkdown } from "../util/parseMarkdown"; import { updateColorByWithRootSequenceData } from "../actions/colors"; import { explodeTree } from "./tree"; -import { togglePanelDisplay } from "./panelDisplay"; export function getDatasetNamesFromUrl(url) { let secondTreeUrl; @@ -121,6 +119,7 @@ function narrativeFetchingErrorNotification(err) { */ async function dispatchCleanStart(dispatch, main, second, query, narrativeBlocks) { const json = await main.main; + const measurementsData = main.measurements ? (await main.measurements) : undefined; const secondTreeDataset = second ? (await second.main) : undefined; const pathnameShouldBe = second ? `${main.pathname}:${second.pathname}` : main.pathname; dispatch({ @@ -128,6 +127,7 @@ async function dispatchCleanStart(dispatch, main, second, query, narrativeBlocks pathnameShouldBe: narrativeBlocks ? undefined : pathnameShouldBe, ...createStateFromQueryOrJSONs({ json, + measurementsData, secondTreeDataset, query, narrativeBlocks, @@ -280,7 +280,19 @@ Dataset.prototype.fetchMain = function fetchMain() { } return res; }) - .then((res) => res.json()); + .then((res) => res.json()) + .then((json) => { + if (json.meta.panels && json.meta.panels.includes("measurements") && !this.measurements) { + /** + * Fetch measurements and store the resulting promise. + * Avoid the browser's default unhandled promise rejection logging and + * just resolve to an Error object that will be handled appropriately in loadMeasurements. + */ + this.measurements = fetchJSON(this.apiCalls.measurements) + .catch((reason) => Promise.resolve(reason)); + } + return json; + }); }; Dataset.prototype.fetchSidecars = async function fetchSidecars() { /** @@ -304,12 +316,6 @@ Dataset.prototype.fetchSidecars = async function fetchSidecars() { this.rootSequence = fetchJSON(this.apiCalls.rootSequence) .catch((reason) => Promise.resolve(reason)) } - - if (mainJson.meta.panels && mainJson.meta.panels.includes("measurements") && !this.measurements) { - this.measurements = fetchJSON(this.apiCalls.measurements) - .then((json) => parseMeasurementsJSON(json)) - .catch((reason) => Promise.resolve(reason)) - } }; Dataset.prototype.loadSidecars = function loadSidecars(dispatch) { /* Helper function to load (dispatch) the visualisation of sidecar files. @@ -346,23 +352,6 @@ Dataset.prototype.loadSidecars = function loadSidecars(dispatch) { dispatch(warningNotification({message: "Failed to parse root sequence JSON"})); }) } - if (this.measurements) { - this.measurements - .then((data) => { - if (data instanceof Error) throw data; - return data - }) - .then((data) => dispatch(loadMeasurements(data))) - .catch((err) => { - const errorMessage = `Failed to ${err instanceof FetchError ? 'fetch' : 'parse'} measurements collections`; - console.error(errorMessage, err.message); - dispatch(warningNotification({message: errorMessage})); - // Hide measurements panel - dispatch(togglePanelDisplay("measurements")); - // Save error message to display if user toggles panel again - dispatch({ type: types.UPDATE_MEASUREMENTS_ERROR, data: errorMessage }); - }); - } }; Dataset.prototype.fetchAvailable = async function fetchAvailable() { this.available = fetchJSON(this.apiCalls.getAvailable); diff --git a/src/actions/measurements.js b/src/actions/measurements.js index b4b7c365f..7e43a8800 100644 --- a/src/actions/measurements.js +++ b/src/actions/measurements.js @@ -1,12 +1,13 @@ import { cloneDeep, pick } from "lodash"; import { measurementIdSymbol } from "../util/globals"; import { defaultMeasurementsControlState } from "../reducers/controls"; +import { getDefaultMeasurementsState } from "../reducers/measurements"; +import { warningNotification } from "./notifications"; import { APPLY_MEASUREMENTS_FILTER, CHANGE_MEASUREMENTS_COLLECTION, CHANGE_MEASUREMENTS_DISPLAY, CHANGE_MEASUREMENTS_GROUP_BY, - LOAD_MEASUREMENTS, TOGGLE_MEASUREMENTS_OVERALL_MEAN, TOGGLE_MEASUREMENTS_THRESHOLD, } from "./types"; @@ -24,7 +25,7 @@ import { * @param {string} defaultKey * @returns {Object} */ -export const getCollectionToDisplay = (collections, collectionKey, defaultKey) => { +const getCollectionToDisplay = (collections, collectionKey, defaultKey) => { const defaultCollection = collections.filter((collection) => collection.key === defaultKey)[0]; if (!collectionKey) return defaultCollection; const potentialCollections = collections.filter((collection) => collection.key === collectionKey); @@ -91,7 +92,6 @@ function getCollectionDefaultControl(controlKey, collection) { } break; } - case 'measurementsCollectionKey': // fallthrough case 'measurementsFilters': { // eslint-disable-next-line no-console console.debug(`Skipping control key ${controlKey} because it does not have default controls`); @@ -109,10 +109,9 @@ function getCollectionDefaultControl(controlKey, collection) { * @param {Object} collection * @returns {MeasurementsControlState} */ -export function getCollectionDefaultControls(collection) { +function getCollectionDefaultControls(collection) { const defaultControls = {...defaultMeasurementsControlState}; if (Object.keys(collection).length) { - defaultControls.measurementsCollectionKey = collection.key; for (const [key, value] of Object.entries(defaultControls)) { const collectionDefault = getCollectionDefaultControl(key, collection); defaultControls[key] = collectionDefault !== undefined ? collectionDefault : value; @@ -134,7 +133,6 @@ export function getCollectionDefaultControls(collection) { const getCollectionDisplayControls = (controls, collection) => { // Copy current control options for measurements const newControls = cloneDeep(pick(controls, Object.keys(defaultMeasurementsControlState))); - newControls.measurementsCollectionKey = collection.key; // 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)) { @@ -171,8 +169,9 @@ const getCollectionDisplayControls = (controls, collection) => { return newControls; }; -export const parseMeasurementsJSON = (json) => { - const collections = json["collections"]; +const parseMeasurementsJSON = (json) => { + // Avoid editing the original json values since they are cached for narratives + const collections = cloneDeep(json["collections"]); if (!collections || collections.length === 0) { throw new Error("Measurements JSON does not have collections"); } @@ -263,34 +262,47 @@ export const parseMeasurementsJSON = (json) => { ); }); - return {collections, defaultCollection: json["default_collection"]}; + const collectionKeys = collections.map((collection) => collection.key); + let defaultCollectionKey = json["default_collection"]; + if (!collectionKeys.includes(defaultCollectionKey)) { + defaultCollectionKey = collectionKeys[0]; + } + const collectionToDisplay = collections.filter((collection) => collection.key === defaultCollectionKey)[0]; + return { + loaded: true, + error: undefined, + defaultCollectionKey, + collections, + collectionToDisplay + } }; -export const loadMeasurements = ({collections, defaultCollection}) => (dispatch, getState) => { - const { tree, controls } = getState(); - if (!tree.loaded) { - throw new Error("tree not loaded"); +export const loadMeasurements = (measurementsData, dispatch) => { + let measurementState = getDefaultMeasurementsState(); + /* Just return default state there are no measurements data to load */ + if (!measurementsData) { + return measurementState } - const collectionKeys = collections.map((collection) => collection.key); - let defaultCollectionKey = defaultCollection; - if (!collectionKeys.includes(defaultCollectionKey)) { - defaultCollectionKey = collectionKeys[0]; + let warningMessage = ""; + if (measurementsData instanceof Error) { + console.error(measurementsData); + warningMessage = "Failed to fetch measurements collections"; + } else { + try { + measurementState = { ...measurementState, ...parseMeasurementsJSON(measurementsData) }; + } catch (error) { + console.error(error); + warningMessage = "Failed to parse measurements collections"; + } } - // Get the collection to display to set up default controls - const collectionToDisplay = getCollectionToDisplay(collections, controls.measurementsCollectionKey, defaultCollectionKey); - const newControls = getCollectionDisplayControls(controls, collectionToDisplay); - const queryParams = createMeasurementsQueryFromControls(newControls, collectionToDisplay, defaultCollectionKey); + if (warningMessage) { + measurementState.error = warningMessage; + dispatch(warningNotification({ message: warningMessage })); + } - dispatch({ - type: LOAD_MEASUREMENTS, - defaultCollectionKey, - collections, - collectionToDisplay, - controls: newControls, - queryParams - }); + return measurementState; }; export const changeMeasurementsCollection = (newCollectionKey) => (dispatch, getState) => { @@ -353,7 +365,7 @@ export const removeAllFieldFilters = (field) => (dispatch, getState) => { dispatch({ type: APPLY_MEASUREMENTS_FILTER, controls: { measurementsFilters }, - queryParams: createMeasurementsQueryFromControls({measurementsFilters}, measurements.collectionToDisplay) + queryParams: createMeasurementsQueryFromControls({measurementsFilters}, measurements.collectionToDisplay, measurements.defaultCollectionKey) }); }; @@ -379,7 +391,7 @@ export const toggleOverallMean = () => (dispatch, getState) => { dispatch({ type: TOGGLE_MEASUREMENTS_OVERALL_MEAN, controls: newControls, - queryParams: createMeasurementsQueryFromControls(newControls, measurements.collectionToDisplay) + queryParams: createMeasurementsQueryFromControls(newControls, measurements.collectionToDisplay, measurements.defaultCollectionKey) }); } @@ -403,7 +415,7 @@ export const changeMeasurementsDisplay = (newDisplay) => (dispatch, getState) => dispatch({ type: CHANGE_MEASUREMENTS_DISPLAY, controls: newControls, - queryParams: createMeasurementsQueryFromControls(newControls, measurements.collectionToDisplay) + queryParams: createMeasurementsQueryFromControls(newControls, measurements.collectionToDisplay, measurements.defaultCollectionKey) }); } @@ -420,7 +432,6 @@ export const changeMeasurementsGroupBy = (newGroupBy) => (dispatch, getState) => } const controlToQueryParamMap = { - measurementsCollectionKey: "m_collection", measurementsDisplay: "m_display", measurementsGroupBy: "m_groupBy", measurementsShowOverallMean: "m_overallMean", @@ -439,7 +450,9 @@ export function removeInvalidMeasurementsFilterQuery(query, newQueryParams) { } function createMeasurementsQueryFromControls(measurementControls, collection, defaultCollectionKey) { - const newQuery = {}; + const newQuery = { + m_collection: collection.key === defaultCollectionKey ? "" : collection.key + }; for (const [controlKey, controlValue] of Object.entries(measurementControls)) { let queryKey = controlToQueryParamMap[controlKey]; const collectionDefault = getCollectionDefaultControl(controlKey, collection); @@ -449,13 +462,6 @@ function createMeasurementsQueryFromControls(measurementControls, collection, de newQuery[queryKey] = ""; } else { switch(controlKey) { - case "measurementsCollectionKey": - if (controlValue !== defaultCollectionKey) { - newQuery[queryKey] = controlValue; - } else { - newQuery[queryKey] = ""; - } - break; case "measurementsDisplay": // fallthrough case "measurementsGroupBy": newQuery[queryKey] = controlValue; @@ -493,49 +499,100 @@ function createMeasurementsQueryFromControls(measurementControls, collection, de return newQuery; } -export function createMeasurementsControlsFromQuery(query){ - const newState = {}; +/** + * Parses the current collection's controls from measurements and updates them + * with valid query parameters. + * + * In cases where the query param is invalid, the query param is removed from the + * returned query object. + * @param {Object} measurements + * @param {Object} query + * @returns {Object} + */ +export const combineMeasurementsControlsAndQuery = (measurements, query) => { + const updatedQuery = cloneDeep(query); + const collectionKeys = measurements.collections.map((collection) => collection.key); + // Remove m_collection query if it's invalid or the default collection key + if (!collectionKeys.includes(updatedQuery.m_collection) || + updatedQuery.m_collection === measurements.defaultCollectionKey) { + delete updatedQuery.m_collection; + } + // Parse collection's default controls + const collectionKey = updatedQuery.m_collection || measurements.defaultCollectionKey; + const collectionToDisplay = getCollectionToDisplay(measurements.collections, collectionKey, measurements.defaultCollectionKey) + const collectionControls = getCollectionDefaultControls(collectionToDisplay); + const collectionGroupings = Array.from(collectionToDisplay.groupings.keys()); + // Modify controls via query for (const [controlKey, queryKey] of Object.entries(controlToQueryParamMap)) { - const queryValue = query[queryKey]; + const queryValue = updatedQuery[queryKey]; if (queryValue === undefined) continue; - let expectedValues = []; - let conversionFn = () => null; + let newControlState = undefined; switch(queryKey) { case "m_display": - expectedValues = ["mean", "raw"]; - conversionFn = () => queryValue; + if (queryValue === "mean" || queryValue === "raw") { + newControlState = queryValue; + } break; - case "m_collection": // fallthrough case "m_groupBy": - // Accept any value here because we cannot validate the query before - // the measurements JSON is loaded - expectedValues = [queryValue]; - conversionFn = () => queryValue; + // Verify value is a valid grouping of collection + if (collectionGroupings.includes(queryValue)) { + newControlState = queryValue; + } + break; + case "m_overallMean": + if (queryValue === "show" || queryValue === "hide") { + newControlState = queryValue === "show"; + } break; - case "m_overallMean": // fallthrough case "m_threshold": - expectedValues = ["show", "hide"]; - conversionFn = () => queryValue === "show"; + if (collectionToDisplay.thresholds && + (queryValue === "show" || queryValue === "hide")) { + newControlState = queryValue === "show"; + } break; + default: + console.error(`Ignoring unsupported query ${queryKey}`); } - if(expectedValues.includes(queryValue)) { - newState[controlKey] = conversionFn(); - } else { - console.error(`Ignoring invalid query param ${queryKey}=${queryValue}, value should be one of ${expectedValues}`); + // Remove query if it's invalid or the same as the collection's default controls + if (newControlState === undefined || newControlState === collectionControls[controlKey]) { + delete updatedQuery[queryKey]; + continue; } + collectionControls[controlKey] = newControlState } - // Accept any value here because we cannot validate the query before the measurements JSON is loaded - for (const filterKey of Object.keys(query).filter((c) => c.startsWith(filterQueryPrefix))) { + // Special handling of the filter query since these can be arbitrary query keys `mf_*` + for (const filterKey of Object.keys(updatedQuery).filter((c) => c.startsWith(filterQueryPrefix))) { + // Remove and ignore query for invalid fields const field = filterKey.replace(filterQueryPrefix, ''); - const filterValues = Array.isArray(query[filterKey]) ? query[filterKey] : [query[filterKey]]; - const measurementsFilters = {...newState.measurementsFilters}; + if (!collectionToDisplay.filters.has(field)) { + delete updatedQuery[filterKey]; + continue; + } + + // Remove and ignore query for invalid field values + const collectionFieldValues = collectionToDisplay.filters.get(field).values; + const filterValues = Array.isArray(updatedQuery[filterKey]) ? updatedQuery[filterKey] : [updatedQuery[filterKey]]; + const validFilterValues = filterValues.filter((value) => collectionFieldValues.has(value)); + if (!validFilterValues.length) { + delete updatedQuery[filterKey]; + continue; + } + + // Set field filter controls and query to the valid filter values + updatedQuery[filterKey] = validFilterValues; + const measurementsFilters = {...collectionControls.measurementsFilters}; measurementsFilters[field] = new Map(measurementsFilters[field]); - for (const value of filterValues) { + for (const value of validFilterValues) { measurementsFilters[field].set(value, {active: true}); } - newState.measurementsFilters = measurementsFilters; + collectionControls.measurementsFilters = measurementsFilters; + + } + return { + collectionToDisplay, + collectionControls, + updatedQuery } - return newState; } diff --git a/src/actions/navigation.js b/src/actions/navigation.js index 04a43bd71..06aae3dc1 100644 --- a/src/actions/navigation.js +++ b/src/actions/navigation.js @@ -40,11 +40,13 @@ const updateNarrativeDataset = async (dispatch, datasets, narrativeBlocks, path, const mainDataset = datasets[mainTreeName]; const secondDataset = datasets[secondTreeName]; const mainJson = await mainDataset.main; + const measurementsData = mainDataset.measurements ? (await mainDataset.measurements) : undefined; const secondJson = secondDataset ? (await secondDataset.main) : false; dispatch({ type: URL_QUERY_CHANGE_WITH_COMPUTED_STATE, ...createStateFromQueryOrJSONs({ json: mainJson, + measurementsData, secondTreeDataset: secondJson, mainTreeName, secondTreeName, @@ -90,8 +92,8 @@ export const changePage = ({ const oldState = getState(); /* set some defaults */ - if (!path) path = window.location.pathname; - if (!query) query = queryString.parse(window.location.search); + if (!path) path = window.location.pathname; + if (!query) query = queryString.parse(window.location.search); /* some booleans */ const pathHasChanged = oldState.general.pathname !== path; diff --git a/src/actions/recomputeReduxState.js b/src/actions/recomputeReduxState.js index 588e5dc62..41917ed23 100644 --- a/src/actions/recomputeReduxState.js +++ b/src/actions/recomputeReduxState.js @@ -7,7 +7,6 @@ import { getIdxMatchingLabel, calculateVisiblityAndBranchThickness } from "../ut import { constructVisibleTipLookupBetweenTrees } from "../util/treeTangleHelpers"; import { getDefaultControlsState, shouldDisplayTemporalConfidence } from "../reducers/controls"; import { getDefaultFrequenciesState } from "../reducers/frequencies"; -import { getDefaultMeasurementsState } from "../reducers/measurements"; import { countTraitsAcrossTree, calcTotalTipsInTree, gatherTraitNames } from "../util/treeCountingHelpers"; import { calcEntropyInView } from "../util/entropy"; import { treeJsonToState } from "../util/treeJsonProcessing"; @@ -23,7 +22,7 @@ import { getTraitFromNode, getDivFromNode, collectGenotypeStates } from "../util import { collectAvailableTipLabelOptions } from "../components/controls/choose-tip-label"; import { hasMultipleGridPanels } from "./panelDisplay"; import { strainSymbolUrlString } from "../middleware/changeURL"; -import { createMeasurementsControlsFromQuery, getCollectionDefaultControls, getCollectionToDisplay } from "./measurements"; +import { combineMeasurementsControlsAndQuery, loadMeasurements } from "./measurements"; export const doesColorByHaveConfidence = (controlsState, colorBy) => controlsState.coloringsPresentOnTreeWithConfidence.has(colorBy); @@ -208,9 +207,6 @@ const modifyStateViaURLQuery = (state, query) => { if (query.scatterX) state.scatterVariables.x = query.scatterX; if (query.scatterY) state.scatterVariables.y = query.scatterY; - /* Process query params for measurements panel. These all start with `m_` or `mf_` prefix to avoid conflicts */ - state = {...state, ...createMeasurementsControlsFromQuery(query)} - return state; function _validDate(dateNum, absoluteDateMinNumeric, absoluteDateMaxNumeric) { return !(dateNum===undefined || dateNum > absoluteDateMaxNumeric || dateNum < absoluteDateMinNumeric); @@ -858,6 +854,7 @@ export const getNarrativePageFromQuery = (query, narrative) => { export const createStateFromQueryOrJSONs = ({ json = false, /* raw json data - completely nuke existing redux state */ + measurementsData = false, /* raw measurements json data or error, only used when main json is provided */ secondTreeDataset = false, oldState = false, /* existing redux state (instead of jsons) */ narrativeBlocks = false, /* if in a narrative this argument is set */ @@ -875,8 +872,8 @@ export const createStateFromQueryOrJSONs = ({ entropy = entropyCreateState(json.meta.genome_annotations); /* ensure default frequencies state */ frequencies = getDefaultFrequenciesState(); - /* ensure default measurements state */ - measurements = getDefaultMeasurementsState(); + /* Load measurements if available, otherwise ensure default measurements state */ + measurements = loadMeasurements(measurementsData, dispatch); /* new tree state(s) */ tree = treeJsonToState(json.tree); castIncorrectTypes(metadata, tree); @@ -903,12 +900,6 @@ export const createStateFromQueryOrJSONs = ({ measurements = {...oldState.measurements}; controls = restoreQueryableStateToDefaults(controls); controls = modifyStateViaMetadata(controls, metadata, entropy.genomeMap); - /* If available, reset to the default collection and the collection's default controls - so that narrative queries are respected between slides */ - if (measurements.loaded) { - measurements.collectionToDisplay = getCollectionToDisplay(measurements.collections, "", measurements.defaultCollectionKey) - controls = {...controls, ...getCollectionDefaultControls(measurements.collectionToDisplay)}; - } } /* For the creation of state, we want to parse out URL query parameters @@ -922,22 +913,28 @@ export const createStateFromQueryOrJSONs = ({ narrativeSlideIdx = getNarrativePageFromQuery(query, narrative); /* replace the query with the information which can guide the view */ query = queryString.parse(narrative[narrativeSlideIdx].query); - /** - * Special case where narrative includes query param for new measurements collection `m_collection` - * We need to reset the measurements and controls to the new collection's defaults before - * processing the remaining query params - */ - if (query.m_collection && measurements.loaded) { - const newCollectionToDisplay = getCollectionToDisplay(measurements.collections, query.m_collection, measurements.defaultCollectionKey); - measurements.collectionToDisplay = newCollectionToDisplay; - controls = {...controls, ...getCollectionDefaultControls(measurements.collectionToDisplay)}; - // Delete `m_collection` so there's no chance of things getting mixed up when processing remaining query params - delete query.m_collection; - } } controls = modifyStateViaURLQuery(controls, query); + /* Special handling of measurements controls and query params */ + if (measurements.loaded) { + const { collectionToDisplay, collectionControls, updatedQuery} = combineMeasurementsControlsAndQuery(measurements, query); + measurements.collectionToDisplay = collectionToDisplay; + controls = {...controls, ...collectionControls}; + query = updatedQuery; + } else { + // Hide measurements panel if loading failed + controls.panelsToDisplay = controls.panelsToDisplay.filter((panel) => panel !== "measurements"); + controls.canTogglePanelLayout = hasMultipleGridPanels(controls.panelsToDisplay); + controls.panelLayout = controls.canTogglePanelLayout ? controls.panelLayout : "full"; + // Remove all measurements query params which start with `m_` or `mf_` + query = Object.fromEntries( + Object.entries(query) + .filter(([key, _]) => !(key.startsWith("m_") || key.startsWith("mf_"))) + ); + } + /* certain narrative slides prescribe the main panel to simply render narrative-provided markdown content */ if (narrativeBlocks && narrative[narrativeSlideIdx].mainDisplayMarkdown) { controls.panelsToDisplay = ["MainDisplayMarkdown"]; diff --git a/src/actions/types.js b/src/actions/types.js index fb59b92fe..719bd2275 100644 --- a/src/actions/types.js +++ b/src/actions/types.js @@ -51,16 +51,13 @@ export const CACHE_JSONS = "CACHE_JSONS"; export const SET_ROOT_SEQUENCE = "SET_ROOT_SEQUENCE"; export const CHANGE_TIP_LABEL_KEY = "CHANGE_TIP_LABEL_KEY"; export const CHANGE_EXPLODE_ATTR = "CHANGE_EXPLODE_ATTR"; -export const LOAD_MEASUREMENTS = "LOAD_MEASUREMENTS"; export const CHANGE_MEASUREMENTS_COLLECTION = "CHANGE_MEASUREMENTS_COLLECTION"; export const CHANGE_MEASUREMENTS_GROUP_BY = "CHANGE_MEASUREMENTS_GROUP_BY"; export const TOGGLE_MEASUREMENTS_THRESHOLD = "TOGGLE_MEASUREMENTS_THRESHOLD"; export const TOGGLE_MEASUREMENTS_OVERALL_MEAN = "TOGGLE_MEASUREMENTS_OVERALL_MEAN"; export const CHANGE_MEASUREMENTS_DISPLAY = "CHANGE_MEASUREMENTS_DISPLAY"; export const APPLY_MEASUREMENTS_FILTER = "APPLY_MEASUREMENTS_FILTER"; -export const UPDATE_MEASUREMENTS_ERROR = "UPDATE_MEASUREMENTS_ERROR"; export const TOGGLE_SHOW_ALL_BRANCH_LABELS = "TOGGLE_SHOW_ALL_BRANCH_LABELS"; export const TOGGLE_MOBILE_DISPLAY = "TOGGLE_MOBILE_DISPLAY"; export const SELECT_NODE = "SELECT_NODE"; export const DESELECT_NODE = "DESELECT_NODE"; - diff --git a/src/components/measurements/index.js b/src/components/measurements/index.js index 39f5256b5..9d6fe6dee 100644 --- a/src/components/measurements/index.js +++ b/src/components/measurements/index.js @@ -343,20 +343,19 @@ const Measurements = ({height, width, showLegend}) => { return ( - {measurementsLoaded && - (measurementsError ? - -

- {measurementsError} -

-
: - - ) + {measurementsLoaded ? + : + +

+ {measurementsError || + "Failed to fetch/load measurements due to unknown error"} +

+
}
); diff --git a/src/components/narrativeEditor/examineNarrative.js b/src/components/narrativeEditor/examineNarrative.js index 8ee0b7bc8..9a20ab9fe 100644 --- a/src/components/narrativeEditor/examineNarrative.js +++ b/src/components/narrativeEditor/examineNarrative.js @@ -202,6 +202,7 @@ const ExamineNarrative = ({narrative, datasetResponses, setDisplayNarrative}) => preserveCache: true, // specific for the narrative debugger ...createStateFromQueryOrJSONs({ json: await narrative.datasets[datasetNames[0]].main, + measurementsData: narrative.datasets[datasetNames[0]].measurements ? (await narrative.datasets[datasetNames[0]].measurements) : undefined, secondTreeDataset: datasetNames[1] ? await narrative.datasets[datasetNames[1]].main : undefined, query: n ? {n} : {}, // query is things like n=3 to load a specific page narrativeBlocks: narrative.blocks, diff --git a/src/components/narrativeEditor/useDatasetFetch.js b/src/components/narrativeEditor/useDatasetFetch.js index c85de38c4..f6577265c 100644 --- a/src/components/narrativeEditor/useDatasetFetch.js +++ b/src/components/narrativeEditor/useDatasetFetch.js @@ -118,4 +118,3 @@ async function fetchDatasetAndSidecars(name, dataset, dispatchDatasetResponses) console.error("Programming error within fetchDatasetAndSidecars", err); } } - diff --git a/src/middleware/changeURL.js b/src/middleware/changeURL.js index a57511c7f..da22f195f 100644 --- a/src/middleware/changeURL.js +++ b/src/middleware/changeURL.js @@ -223,7 +223,6 @@ export const changeURLMiddleware = (store) => (next) => (action) => { } break; } - case types.LOAD_MEASUREMENTS: // fallthrough case types.CHANGE_MEASUREMENTS_COLLECTION: // fallthrough case types.APPLY_MEASUREMENTS_FILTER: query = removeInvalidMeasurementsFilterQuery(query, action.queryParams) diff --git a/src/reducers/controls.ts b/src/reducers/controls.ts index 5fc96bb77..1f0b2c979 100644 --- a/src/reducers/controls.ts +++ b/src/reducers/controls.ts @@ -44,7 +44,6 @@ export interface BasicControlsState { } export interface MeasurementsControlState { - measurementsCollectionKey: string | undefined, measurementsGroupBy: string | undefined, measurementsDisplay: string | undefined, measurementsShowOverallMean: boolean | undefined, @@ -134,7 +133,6 @@ export const getDefaultControlsState = () => { showOnlyPanels: false, showTransmissionLines: true, normalizeFrequencies: true, - measurementsCollectionKey: undefined, measurementsGroupBy: undefined, measurementsDisplay: undefined, measurementsShowOverallMean: undefined, @@ -153,7 +151,6 @@ export const getDefaultControlsState = () => { * differentiate the clean slate vs the added URL params. */ export const defaultMeasurementsControlState: MeasurementsControlState = { - measurementsCollectionKey: undefined, measurementsGroupBy: undefined, measurementsDisplay: "mean", measurementsShowOverallMean: true, @@ -386,7 +383,6 @@ const Controls = (state: ControlsState = getDefaultControlsState(), action): Con } return state; } - case types.LOAD_MEASUREMENTS: // fallthrough case types.CHANGE_MEASUREMENTS_COLLECTION: // fallthrough case types.CHANGE_MEASUREMENTS_DISPLAY: // fallthrough case types.CHANGE_MEASUREMENTS_GROUP_BY: // fallthrough diff --git a/src/reducers/measurements.js b/src/reducers/measurements.js index b0e2af395..f7805e951 100644 --- a/src/reducers/measurements.js +++ b/src/reducers/measurements.js @@ -1,7 +1,6 @@ import { CHANGE_MEASUREMENTS_COLLECTION, - LOAD_MEASUREMENTS, - UPDATE_MEASUREMENTS_ERROR, + CLEAN_START, URL_QUERY_CHANGE_WITH_COMPUTED_STATE } from "../actions/types"; @@ -15,28 +14,15 @@ export const getDefaultMeasurementsState = () => ({ const measurements = (state = getDefaultMeasurementsState(), action) => { switch (action.type) { + case CLEAN_START: // fallthrough case URL_QUERY_CHANGE_WITH_COMPUTED_STATE: return { ...action.measurements }; - case LOAD_MEASUREMENTS: - return { - ...state, - loaded: true, - defaultCollectionKey: action.defaultCollectionKey, - collections: action.collections, - collectionToDisplay: action.collectionToDisplay - }; case CHANGE_MEASUREMENTS_COLLECTION: return { ...state, loaded: true, collectionToDisplay: action.collectionToDisplay }; - case UPDATE_MEASUREMENTS_ERROR: - return { - ...state, - loaded: true, - error: action.data - }; default: return state; }