diff --git a/app/configurator/components/chart-configurator.tsx b/app/configurator/components/chart-configurator.tsx index 227eb6de9..ab5c28dae 100644 --- a/app/configurator/components/chart-configurator.tsx +++ b/app/configurator/components/chart-configurator.tsx @@ -18,7 +18,6 @@ import { import { makeStyles } from "@mui/styles"; import isEmpty from "lodash/isEmpty"; import isEqual from "lodash/isEqual"; -import omitBy from "lodash/omitBy"; import sortBy from "lodash/sortBy"; import { useEffect, useMemo, useRef, useState } from "react"; import { @@ -41,6 +40,7 @@ import { DataSource, Filters, getChartConfig, + getChartConfigFilters, isMapConfig, useChartConfigFilters, } from "@/configurator"; @@ -167,63 +167,74 @@ const useEnsurePossibleFilters = ({ const chartConfig = getChartConfig(state); const [fetching, setFetching] = useState(false); const [error, setError] = useState(); - const lastFilters = useRef(); + const lastFilters = useRef>({}); const client = useClient(); useEffect(() => { const run = async () => { - const { mappedFilters, unmappedFilters } = - getFiltersByMappingStatus(chartConfig); - if ( - lastFilters.current && - orderedIsEqual(lastFilters.current, unmappedFilters) - ) { - return; - } - lastFilters.current = unmappedFilters; - - setFetching(true); - const { data, error } = await client - .query( - PossibleFiltersDocument, - { - iri: chartConfig.dataSet, - sourceType: state.dataSource.type, - sourceUrl: state.dataSource.url, - filters: unmappedFilters, - - // @ts-ignore This is to make urql requery - filterKey: Object.keys(unmappedFilters).join(", "), - } - ) - .toPromise(); - if (error || !data) { - setError(error); + chartConfig.cubes.forEach(async (cube) => { + const { mappedFilters, unmappedFilters } = getFiltersByMappingStatus( + chartConfig, + cube.iri + ); + + if ( + lastFilters.current[cube.iri] && + orderedIsEqual(lastFilters.current[cube.iri], unmappedFilters) + ) { + return; + } + + lastFilters.current[cube.iri] = unmappedFilters; + setFetching(true); + + const { data, error } = await client + .query( + PossibleFiltersDocument, + { + iri: cube.iri, + sourceType: state.dataSource.type, + sourceUrl: state.dataSource.url, + filters: unmappedFilters, + // @ts-ignore This is to make urql requery + filterKey: Object.keys(unmappedFilters).join(", "), + } + ) + .toPromise(); + + if (error || !data) { + setError(error); + setFetching(false); + console.error("Could not fetch possible filters", error); + + return; + } + + setError(undefined); setFetching(false); - console.error("Could not fetch possible filters", error); - return; - } - setError(undefined); - setFetching(false); - - const filters = Object.assign( - Object.fromEntries( - data.possibleFilters.map((x) => [ - x.iri, - { type: x.type, value: x.value }, - ]) - ) as Filters, - mappedFilters - ); - if (!isEqual(filters, chartConfig.filters) && !isEmpty(filters)) { - dispatch({ - type: "CHART_CONFIG_FILTERS_UPDATE", - value: { - filters, - }, - }); - } + const filters = Object.assign( + Object.fromEntries( + data.possibleFilters.map((x) => [ + x.iri, + { type: x.type, value: x.value }, + ]) + ) as Filters, + mappedFilters + ); + + const oldFilters = getChartConfigFilters(chartConfig.cubes, cube.iri); + + if (!isEqual(filters, oldFilters) && !isEmpty(filters)) { + dispatch({ + type: "CHART_CONFIG_FILTERS_UPDATE", + value: { + cubeIri: cube.iri, + filters, + }, + }); + } + }); }; run(); @@ -231,7 +242,7 @@ const useEnsurePossibleFilters = ({ client, dispatch, chartConfig, - chartConfig.dataSet, + chartConfig.cubes, state.dataSource.type, state.dataSource.url, ]); @@ -247,37 +258,41 @@ const useFilterReorder = ({ const [state, dispatch] = useConfiguratorState(isConfiguring); const chartConfig = getChartConfig(state); const locale = useLocale(); - - const { filters } = chartConfig; - const { unmappedFilters, mappedFiltersIris } = useMemo(() => { + const filters = getChartConfigFilters(chartConfig.cubes); + const { mappedFiltersIris } = useMemo(() => { return getFiltersByMappingStatus(chartConfig); }, [chartConfig]); const variables = useMemo(() => { - const hasUnmappedFilters = Object.keys(unmappedFilters).length > 0; - const vars = { - iri: chartConfig.dataSet, - sourceType: state.dataSource.type, - sourceUrl: state.dataSource.url, - locale, - filters: hasUnmappedFilters ? unmappedFilters : undefined, - // This is important for urql not to think that filters - // are the same while the order of the keys has changed. - // If this is not present, we'll have outdated dimension - // values after we change the filter order - filterKeys: hasUnmappedFilters - ? Object.keys(unmappedFilters).join(", ") - : undefined, - }; + const filters = chartConfig.cubes.map((cube) => { + const { unmappedFilters } = getFiltersByMappingStatus( + chartConfig, + cube.iri + ); + return Object.keys(unmappedFilters).length > 0 + ? { + iri: cube.iri, + filters: unmappedFilters, + } + : { + iri: cube.iri, + filters: undefined, + }; + }); - return omitBy(vars, (x) => x === undefined) as typeof vars; - }, [ - chartConfig.dataSet, - state.dataSource.type, - state.dataSource.url, - locale, - unmappedFilters, - ]); + // This is important for urql not to think that filters + // are the same while the order of the keys has changed. + // If this is not present, we'll have outdated dimension + // values after we change the filter order + const requeryKey = filters.reduce((acc, d) => { + return `${acc}${d.iri}${JSON.stringify(d.filters)}`; + }, ""); + + return { + filters, + requeryKey: requeryKey ? requeryKey : undefined, + }; + }, [chartConfig]); const [ { data: componentsData, fetching: componentsFetching }, @@ -287,9 +302,7 @@ const useFilterReorder = ({ sourceType: state.dataSource.type, sourceUrl: state.dataSource.url, locale, - filters: [{ iri: chartConfig.dataSet, filters: variables.filters }], - // @ts-ignore This is to make urql requery - filterKeys: variables.filterKeys, + ...variables, }, }); @@ -299,9 +312,7 @@ const useFilterReorder = ({ sourceType: state.dataSource.type, sourceUrl: state.dataSource.url, locale, - filters: [{ iri: chartConfig.dataSet, filters: variables.filters }], - // @ts-ignore This is to make urql requery - filterKeys: variables.filterKeys, + ...variables, }, }); }, [ @@ -310,7 +321,6 @@ const useFilterReorder = ({ state.dataSource.type, state.dataSource.url, locale, - chartConfig.dataSet, ]); const dimensions = componentsData?.dataCubesComponents?.dimensions; diff --git a/app/configurator/config-form.tsx b/app/configurator/config-form.tsx index 14b30a921..8ae85d9ab 100644 --- a/app/configurator/config-form.tsx +++ b/app/configurator/config-form.tsx @@ -435,15 +435,17 @@ export const useSingleFilterSelect = ({ [cubeIri, dimensionIri, dispatch] ); - let value: string | undefined; + let value = FIELD_VALUE_NONE; + if (state.state === "CONFIGURING_CHART") { const chartConfig = getChartConfig(state); - value = get( - chartConfig, - ["filters", dimensionIri, "value"], - FIELD_VALUE_NONE - ); + const cube = chartConfig.cubes.find((cube) => cube.iri === cubeIri); + + if (cube) { + value = get(cube, ["filters", dimensionIri, "value"], FIELD_VALUE_NONE); + } } + return { value, onChange, diff --git a/app/configurator/configurator-state.spec.tsx b/app/configurator/configurator-state.spec.tsx index 1fee759c5..efffef195 100644 --- a/app/configurator/configurator-state.spec.tsx +++ b/app/configurator/configurator-state.spec.tsx @@ -784,7 +784,7 @@ describe("getFiltersByMappingStatus", () => { }, } as any as MapConfig; - const { mappedFiltersIris } = getFiltersByMappingStatus(config); + const { mappedFiltersIris } = getFiltersByMappingStatus(config, ""); expect([...mappedFiltersIris]).toEqual( expect.arrayContaining(["areaColorIri", "symbolColorIri"]) diff --git a/app/configurator/configurator-state.tsx b/app/configurator/configurator-state.tsx index db975e5c4..006650dd5 100644 --- a/app/configurator/configurator-state.tsx +++ b/app/configurator/configurator-state.tsx @@ -425,7 +425,7 @@ export const moveFilterField = produce( export const deriveFiltersFromFields = produce( (chartConfig: ChartConfig, components: Component[]) => { - const { chartType, fields, filters } = chartConfig; + const { chartType, fields, cubes } = chartConfig; if (chartType === "table") { // As dimensions in tables behave differently than in other chart types, @@ -435,37 +435,49 @@ export const deriveFiltersFromFields = produce( const isHidden = (iri: string) => hiddenFieldIris.has(iri); const isGrouped = (iri: string) => groupedDimensionIris.has(iri); - components.forEach((component) => { - if (isMeasure(component)) { - return; - } + cubes.forEach((cube) => { + const cubeComponents = components.filter( + (component) => component.cubeIri === cube.iri + ); + + cubeComponents.forEach((component) => { + if (isMeasure(component)) { + return; + } - applyTableDimensionToFilters({ - filters, - dimension: component, - isHidden: isHidden(component.iri), - isGrouped: isGrouped(component.iri), + applyTableDimensionToFilters({ + filters: cube.filters, + dimension: component, + isHidden: isHidden(component.iri), + isGrouped: isGrouped(component.iri), + }); }); }); } else { const fieldDimensionIris = getFieldComponentIris(fields); const isField = (iri: string) => fieldDimensionIris.has(iri); - // Apply hierarchical dimensions first - const sortedComponents = sortBy( - components, - (d) => (isGeoDimension(d) ? -1 : 1), - (d) => (isMeasure(d) ? 1 : d.hierarchy ? -1 : 1) - ); - sortedComponents.forEach((component) => { - if (isMeasure(component)) { - return; - } + cubes.forEach((cube) => { + const cubeComponents = components.filter( + (component) => component.cubeIri === cube.iri + ); + // Apply hierarchical dimensions first + const sortedCubeComponents = sortBy( + cubeComponents, + (d) => (isGeoDimension(d) ? -1 : 1), + (d) => (isMeasure(d) ? 1 : d.hierarchy ? -1 : 1) + ); + + sortedCubeComponents.forEach((component) => { + if (isMeasure(component)) { + return; + } - applyNonTableDimensionToFilters({ - filters, - dimension: component, - isField: isField(component.iri), + applyNonTableDimensionToFilters({ + filters: cube.filters, + dimension: component, + isField: isField(component.iri), + }); }); }); } @@ -712,17 +724,18 @@ export const getNonGenericFieldValues = ( * is structured at the moment (colorField) is a subfield of areaLayer and * symbolLayer fields. */ -export const getFiltersByMappingStatus = (chartConfig: ChartConfig) => { +export const getFiltersByMappingStatus = ( + chartConfig: ChartConfig, + cubeIri?: string +) => { const genericFieldValues = Object.values(chartConfig.fields).map( (d) => d.componentIri ); const nonGenericFieldValues = getNonGenericFieldValues(chartConfig); const iris = new Set([...genericFieldValues, ...nonGenericFieldValues]); - const mappedFilters = pickBy(chartConfig.filters, (_, iri) => iris.has(iri)); - const unmappedFilters = pickBy( - chartConfig.filters, - (_, iri) => !iris.has(iri) - ); + const filters = getChartConfigFilters(chartConfig.cubes, cubeIri); + const mappedFilters = pickBy(filters, (_, iri) => iris.has(iri)); + const unmappedFilters = pickBy(filters, (_, iri) => !iris.has(iri)); return { mappedFilters, mappedFiltersIris: iris, unmappedFilters }; }; @@ -1277,13 +1290,10 @@ const ConfiguratorStateContext = createContext< [ConfiguratorState, Dispatch] | undefined >(undefined); -type ChartId = string; -type DatasetIri = string; - export const initChartStateFromChartCopy = async ( - from: ChartId + fromChartId: string ): Promise => { - const config = await fetchChartConfig(from); + const config = await fetchChartConfig(fromChartId); if (config?.data) { return migrateConfiguratorState({ @@ -1294,9 +1304,9 @@ export const initChartStateFromChartCopy = async ( }; export const initChartStateFromChartEdit = async ( - from: ChartId + fromChartId: string ): Promise => { - const config = await fetchChartConfig(from); + const config = await fetchChartConfig(fromChartId); if (config?.data) { return migrateConfiguratorState({