Skip to content

Commit 0e88908

Browse files
committed
fix: save ad-hoc migrated attribute filters via Save as dashboard
Ad-hoc updated attribute filters were saved only when dashboard changes were saved from edit mode. When dashboard copy was created via Save as function, the changes to filter context were lost. Attribute filter configs were kept. The fix allows the migrated filters to be saved even from view mode. JIRA: LX-845 risk: low
1 parent 0778041 commit 0e88908

File tree

4 files changed

+97
-38
lines changed

4 files changed

+97
-38
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// (C) 2025 GoodData Corporation
2+
3+
import {
4+
IDashboardAttributeFilter,
5+
areObjRefsEqual,
6+
IFilterContextDefinition,
7+
isDashboardAttributeFilter,
8+
} from "@gooddata/sdk-model";
9+
10+
/**
11+
* Returns attribute filters that were ad-hoc migrated (non-primary label information was moved from
12+
* filter's displayForm to dashboard's attribute filter config).
13+
*
14+
* @param persistedAttributeFilters - attribute filters that are persisted in metadata
15+
* @param currentAttributeFilters - attribute filters that are in the current state
16+
*/
17+
export function getMigratedAttributeFilters(
18+
persistedAttributeFilters: IDashboardAttributeFilter[] = [],
19+
currentAttributeFilters: IDashboardAttributeFilter[] = [],
20+
): IDashboardAttributeFilter[] {
21+
return currentAttributeFilters.filter((currentFilter) => {
22+
const persistedFilter = persistedAttributeFilters.find(
23+
(persistedFilter) =>
24+
persistedFilter.attributeFilter.localIdentifier ===
25+
currentFilter.attributeFilter.localIdentifier,
26+
);
27+
return !areObjRefsEqual(
28+
persistedFilter?.attributeFilter.displayForm,
29+
currentFilter.attributeFilter.displayForm,
30+
);
31+
});
32+
}
33+
34+
/**
35+
* Returns provided filter context with provided filters merged into (the filters are matched by
36+
* local identifier).
37+
* @param filterContext - the filter context that will get the provided filters merged into
38+
* @param migratedAttributeFilters - filters that should be merged into the provided filter context
39+
*/
40+
export const mergedMigratedAttributeFilters = (
41+
filterContext: IFilterContextDefinition,
42+
migratedAttributeFilters: IDashboardAttributeFilter[],
43+
): IFilterContextDefinition => ({
44+
...filterContext,
45+
filters: filterContext.filters.map((filter) => {
46+
if (isDashboardAttributeFilter(filter)) {
47+
const migratedFilter = migratedAttributeFilters.find(
48+
(migratedFilter) =>
49+
migratedFilter.attributeFilter.localIdentifier === filter.attributeFilter.localIdentifier,
50+
);
51+
return migratedFilter ?? filter;
52+
}
53+
return filter;
54+
}),
55+
});

libs/sdk-ui-dashboard/src/model/commandHandlers/dashboard/common/stateInitializers.ts

+2-14
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import { dateFilterConfigsActions } from "../../../store/dateFilterConfigs/index
5454
import { drillActions } from "../../../store/drill/index.js";
5555

5656
import { dashboardInitialize, EmptyDashboardLayout } from "./dashboardInitialize.js";
57+
import { mergedMigratedAttributeFilters } from "./migratedAttributeFilters.js";
5758

5859
/**
5960
* Returns a list of actions which when processed will initialize the essential parts of the dashboard
@@ -417,20 +418,7 @@ export function* actionsToInitializeExistingDashboard(
417418
const filterContextIdentity = dashboardFilterContextIdentity(customizedDashboard);
418419

419420
const migratedFilterContext: IFilterContextDefinition = isImmediateAttributeFilterMigrationEnabled
420-
? {
421-
...filterContextDefinition,
422-
filters: filterContextDefinition.filters.map((filter) => {
423-
if (isDashboardAttributeFilter(filter)) {
424-
const migratedFilter = migratedAttributeFilters.find(
425-
(migratedFilter) =>
426-
migratedFilter.attributeFilter.localIdentifier ===
427-
filter.attributeFilter.localIdentifier,
428-
);
429-
return migratedFilter ?? filter;
430-
}
431-
return filter;
432-
}),
433-
}
421+
? mergedMigratedAttributeFilters(filterContextDefinition, migratedAttributeFilters)
434422
: filterContextDefinition;
435423

436424
const effectiveAttributeFilterConfigs = isImmediateAttributeFilterMigrationEnabled

libs/sdk-ui-dashboard/src/model/commandHandlers/dashboard/resetDashboardHandler.ts

+1-18
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import uniqWith from "lodash/uniqWith.js";
2222
import {
2323
areObjRefsEqual,
2424
IDashboardAttributeFilterConfig,
25-
IDashboardAttributeFilter,
2625
isDashboardAttributeFilter,
2726
} from "@gooddata/sdk-model";
2827
import { resolveInsights } from "../../utils/insightResolver.js";
@@ -35,6 +34,7 @@ import { applyDefaultFilterView } from "./common/filterViews.js";
3534
import { selectFilterViews } from "../../store/filterViews/filterViewsReducersSelectors.js";
3635
import { selectFilterContextAttributeFilters } from "../../store/filterContext/filterContextSelectors.js";
3736
import { selectAttributeFilterConfigsOverrides } from "../../store/attributeFilterConfigs/attributeFilterConfigsSelectors.js";
37+
import { getMigratedAttributeFilters } from "./common/migratedAttributeFilters.js";
3838

3939
export function* resetDashboardHandler(
4040
ctx: DashboardContext,
@@ -185,23 +185,6 @@ function* resetDashboardFromPersisted(ctx: DashboardContext) {
185185
};
186186
}
187187

188-
function getMigratedAttributeFilters(
189-
persistedAttributeFilters: IDashboardAttributeFilter[] = [],
190-
currentAttributeFilters: IDashboardAttributeFilter[] = [],
191-
): IDashboardAttributeFilter[] {
192-
return currentAttributeFilters.filter((currentFilter) => {
193-
const persistedFilter = persistedAttributeFilters.find(
194-
(persistedFilter) =>
195-
persistedFilter.attributeFilter.localIdentifier ===
196-
currentFilter.attributeFilter.localIdentifier,
197-
);
198-
return !areObjRefsEqual(
199-
persistedFilter?.attributeFilter.displayForm,
200-
currentFilter.attributeFilter.displayForm,
201-
);
202-
});
203-
}
204-
205188
const mergeDashboardAttributeFilterConfigs = (
206189
originalConfigs: IDashboardAttributeFilterConfig[] = [],
207190
overridingConfigs: IDashboardAttributeFilterConfig[] = [],

libs/sdk-ui-dashboard/src/model/commandHandlers/dashboard/saveAsDashboardHandler.ts

+39-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
// (C) 2021-2025 GoodData Corporation
22

3-
import { IDashboard, IDashboardDefinition, IAccessControlAware } from "@gooddata/sdk-model";
3+
import {
4+
IDashboard,
5+
IDashboardDefinition,
6+
IAccessControlAware,
7+
isDashboardAttributeFilter,
8+
} from "@gooddata/sdk-model";
49
import { BatchAction, batchActions } from "redux-batched-actions";
510
import { SagaIterator } from "redux-saga";
611
import { call, put, SagaReturnType, select } from "redux-saga/effects";
@@ -12,7 +17,10 @@ import {
1217
import { SaveDashboardAs } from "../../commands/dashboard.js";
1318
import { DashboardCopySaved, dashboardCopySaved } from "../../events/dashboard.js";
1419
import { filterContextActions } from "../../store/filterContext/index.js";
15-
import { selectFilterContextDefinition } from "../../store/filterContext/filterContextSelectors.js";
20+
import {
21+
selectFilterContextDefinition,
22+
selectFilterContextAttributeFilters,
23+
} from "../../store/filterContext/filterContextSelectors.js";
1624
import { layoutActions } from "../../store/layout/index.js";
1725
import { selectBasicLayout } from "../../store/layout/layoutSelectors.js";
1826
import { metaActions } from "../../store/meta/index.js";
@@ -25,7 +33,10 @@ import { DashboardContext } from "../../types/commonTypes.js";
2533
import { PromiseFnReturnType } from "../../types/sagas.js";
2634
import { selectDateFilterConfigOverrides } from "../../store/dateFilterConfig/dateFilterConfigSelectors.js";
2735
import { savingActions } from "../../store/saving/index.js";
28-
import { selectSettings } from "../../store/config/configSelectors.js";
36+
import {
37+
selectSettings,
38+
selectEnableImmediateAttributeFilterDisplayAsLabelMigration,
39+
} from "../../store/config/configSelectors.js";
2940
import { selectBackendCapabilities } from "../../store/backendCapabilities/backendCapabilitiesSelectors.js";
3041
import { listedDashboardsActions } from "../../store/listedDashboards/index.js";
3142
import { createListedDashboard } from "../../../_staging/listedDashboard/listedDashboardUtils.js";
@@ -36,6 +47,10 @@ import { changeRenderMode } from "../../commands/index.js";
3647
import { selectIsInViewMode } from "../../store/renderMode/renderModeSelectors.js";
3748
import { selectAttributeFilterConfigsOverrides } from "../../store/attributeFilterConfigs/attributeFilterConfigsSelectors.js";
3849
import { selectDateFilterConfigsOverrides } from "../../store/dateFilterConfigs/dateFilterConfigsSelectors.js";
50+
import {
51+
getMigratedAttributeFilters,
52+
mergedMigratedAttributeFilters,
53+
} from "./common/migratedAttributeFilters.js";
3954

4055
type DashboardSaveAsContext = {
4156
cmd: SaveDashboardAs;
@@ -83,15 +98,33 @@ function* createDashboardSaveAsContext(cmd: SaveDashboardAs): SagaIterator<Dashb
8398
selectDashboardDescriptor,
8499
);
85100

86-
const originalDashboardDescription: ReturnType<typeof selectPersistedDashboard> = yield select(
101+
const originalPersistedDashboard: ReturnType<typeof selectPersistedDashboard> = yield select(
87102
selectPersistedDashboard,
88103
);
89104

105+
const isImmediateAttributeFilterMigrationEnabled: ReturnType<
106+
typeof selectEnableImmediateAttributeFilterDisplayAsLabelMigration
107+
> = yield select(selectEnableImmediateAttributeFilterDisplayAsLabelMigration);
108+
const currentFilters: ReturnType<typeof selectFilterContextAttributeFilters> = yield select(
109+
selectFilterContextAttributeFilters,
110+
);
111+
const migratedAttributeFilters = isImmediateAttributeFilterMigrationEnabled
112+
? getMigratedAttributeFilters(
113+
originalPersistedDashboard?.filterContext?.filters.filter(isDashboardAttributeFilter),
114+
currentFilters,
115+
)
116+
: [];
90117
const filterContextDefinition: ReturnType<typeof selectFilterContextDefinition> = yield select(
91-
!useOriginalFilterContext || !originalDashboardDescription
118+
!useOriginalFilterContext || !originalPersistedDashboard
92119
? selectFilterContextDefinition
93120
: selectPersistedDashboardFilterContextAsFilterContextDefinition,
94121
);
122+
// merge migrated filters only in view mode (useOriginalFilterContext), edit mode filter context is
123+
// selected from state where it is already migrated
124+
const migratedFilterContext =
125+
isImmediateAttributeFilterMigrationEnabled && useOriginalFilterContext
126+
? mergedMigratedAttributeFilters(filterContextDefinition, migratedAttributeFilters)
127+
: filterContextDefinition;
95128

96129
const layout: ReturnType<typeof selectBasicLayout> = yield select(selectBasicLayout);
97130
const dateFilterConfig: ReturnType<typeof selectDateFilterConfigOverrides> = yield select(
@@ -115,7 +148,7 @@ function* createDashboardSaveAsContext(cmd: SaveDashboardAs): SagaIterator<Dashb
115148
type: "IDashboard",
116149
...dashboardDescriptorRest,
117150
filterContext: {
118-
...filterContextDefinition,
151+
...migratedFilterContext,
119152
},
120153
layout,
121154
dateFilterConfig,

0 commit comments

Comments
 (0)