From eabbe22e9d9f445e039900bff2b8eeb29fb18a37 Mon Sep 17 00:00:00 2001 From: Petr Benes Date: Mon, 7 Oct 2024 14:13:49 +0200 Subject: [PATCH] feat: defer catalog loading until dashboard is fully executed - replace full catalog loads on dash viewmode with targeted calls to only the displayforms needed - defer drilling and edit mode switching until full catalog is loaded - mark app with css class when catalog finishes loading (for e2e) --- .../src/backend/workspace/attributes/index.ts | 38 +++++++++++++++---- .../api/sdk-ui-dashboard.api.md | 3 ++ .../initializeDashboardHandler/index.ts | 29 ++------------ .../commandHandlers/render/renderingWorker.ts | 17 +++++++++ .../model/queryServices/queryWidgetFilters.ts | 25 ++++++------ .../model/store/catalog/catalogReducers.ts | 12 +++--- .../model/store/catalog/catalogSelectors.ts | 7 ++++ .../sdk-ui-dashboard/src/model/store/index.ts | 1 + .../widgetDrills/widgetDrillSelectors.ts | 25 ++++++++---- .../src/model/utils/displayFormResolver.ts | 2 +- .../dashboard/components/DashboardInner.tsx | 3 ++ .../button/editButton/DefaultEditButton.tsx | 4 +- .../Insight/useDashboardInsightDrills.ts | 7 +++- 13 files changed, 111 insertions(+), 62 deletions(-) diff --git a/libs/sdk-backend-tiger/src/backend/workspace/attributes/index.ts b/libs/sdk-backend-tiger/src/backend/workspace/attributes/index.ts index 9bafd8a87cd..4476c15eedc 100644 --- a/libs/sdk-backend-tiger/src/backend/workspace/attributes/index.ts +++ b/libs/sdk-backend-tiger/src/backend/workspace/attributes/index.ts @@ -1,4 +1,4 @@ -// (C) 2019-2022 GoodData Corporation +// (C) 2019-2024 GoodData Corporation import { IElementsQueryFactory, IWorkspaceAttributesService, @@ -28,7 +28,6 @@ import { AfmValidObjectsQuery, AttributeItem, } from "@gooddata/api-client-tiger"; -import flatMap from "lodash/flatMap.js"; import { invariant } from "ts-invariant"; import { @@ -61,12 +60,37 @@ export class TigerWorkspaceAttributes implements IWorkspaceAttributesService { }; public getAttributeDisplayForms(refs: ObjRef[]): Promise { - return this.authCall(async (client) => { - const allAttributes = await loadAttributes(client, this.workspace); + if (refs.length === 0) { + return Promise.resolve([]); + } - return flatMap(allAttributes, (attr) => attr.displayForms).filter((df) => - refs.find((ref) => areObjRefsEqual(ref, df.ref)), - ); + return this.authCall(async (client) => { + const filter = refs.map((ref: any) => `id==${ref.identifier}`).join(","); + const allDisplayForms = await client.entities.getAllEntitiesLabels({ + include: ["attribute"], + workspaceId: this.workspace, + origin: "ALL", + filter, + }); + const result = allDisplayForms?.data?.data; + + return result?.map((item) => ({ + attribute: { + identifier: item.relationships?.attribute?.data?.id || "", + type: item.relationships?.attribute?.data?.type || "attribute", + }, + ref: { identifier: item.id, type: "displayForm" }, + title: item?.attributes?.title || "", + description: item?.attributes?.description || "", + tags: item?.attributes?.tags, + primary: item?.attributes?.primary, + deprecated: false, + uri: item.id, + type: "displayForm", // item.type is "label" + production: true, + unlisted: false, + id: item.id, + })); }); } diff --git a/libs/sdk-ui-dashboard/api/sdk-ui-dashboard.api.md b/libs/sdk-ui-dashboard/api/sdk-ui-dashboard.api.md index d9d2f9e34da..dc4e1a657d9 100644 --- a/libs/sdk-ui-dashboard/api/sdk-ui-dashboard.api.md +++ b/libs/sdk-ui-dashboard/api/sdk-ui-dashboard.api.md @@ -7185,6 +7185,9 @@ export const selectCatalogDateDatasets: DashboardSelector // @public (undocumented) export const selectCatalogFacts: DashboardSelector; +// @alpha (undocumented) +export const selectCatalogIsLoaded: DashboardSelector; + // @public (undocumented) export const selectCatalogMeasures: DashboardSelector; diff --git a/libs/sdk-ui-dashboard/src/model/commandHandlers/dashboard/initializeDashboardHandler/index.ts b/libs/sdk-ui-dashboard/src/model/commandHandlers/dashboard/initializeDashboardHandler/index.ts index 032acdf7e09..3a971a04079 100644 --- a/libs/sdk-ui-dashboard/src/model/commandHandlers/dashboard/initializeDashboardHandler/index.ts +++ b/libs/sdk-ui-dashboard/src/model/commandHandlers/dashboard/initializeDashboardHandler/index.ts @@ -14,7 +14,6 @@ import { dateFilterConfigActions } from "../../../store/dateFilterConfig/index.j import { DateFilterMergeResult, mergeDateFilterConfigWithOverrides } from "./mergeDateFilterConfigs.js"; import { resolvePermissions } from "./resolvePermissions.js"; import { permissionsActions } from "../../../store/permissions/index.js"; -import { loadCatalog } from "./loadCatalog.js"; import { loadDashboardAlerts } from "./loadDashboardAlerts.js"; import { catalogActions } from "../../../store/catalog/index.js"; import { alertsActions } from "../../../store/alerts/index.js"; @@ -41,7 +40,7 @@ import { actionsToInitializeNewDashboard, } from "../common/stateInitializers.js"; import { executionResultsActions } from "../../../store/executionResults/index.js"; -import { createDisplayFormMapFromCatalog } from "../../../../_staging/catalog/displayFormMap.js"; +import { createDisplayFormMap } from "../../../../_staging/catalog/displayFormMap.js"; import { getPrivateContext } from "../../../store/_infra/contexts.js"; import { accessibleDashboardsActions } from "../../../store/accessibleDashboards/index.js"; import { loadAccessibleDashboardList } from "./loadAccessibleDashboardList.js"; @@ -163,7 +162,6 @@ function* loadExistingDashboard( call(resolveDashboardConfig, ctx, cmd), call(resolvePermissions, ctx, cmd), call(resolveEntitlements, ctx), - call(loadCatalog, ctx, cmd), call(loadDashboardAlerts, ctx), call(loadUser, ctx), call(loadDashboardList, ctx), @@ -182,7 +180,6 @@ function* loadExistingDashboard( config, permissions, entitlements, - catalog, alerts, user, listedDashboards, @@ -196,7 +193,6 @@ function* loadExistingDashboard( SagaReturnType, SagaReturnType, PromiseFnReturnType, - PromiseFnReturnType, PromiseFnReturnType, PromiseFnReturnType, PromiseFnReturnType, @@ -229,8 +225,8 @@ function* loadExistingDashboard( insights, config.settings, effectiveDateFilterConfig.config, - catalog.dateDatasets(), - createDisplayFormMapFromCatalog(catalog), + [], + createDisplayFormMap([], []), cmd.payload.persistedDashboard, ); @@ -253,11 +249,6 @@ function* loadExistingDashboard( userActions.setUser(user), permissionsActions.setPermissions(permissions), catalogActions.setCatalogItems({ - attributes: catalog.attributes(), - dateDatasets: catalog.dateDatasets(), - facts: catalog.facts(), - measures: catalog.measures(), - attributeHierarchies: catalog.attributeHierarchies(), dateHierarchyTemplates: dateHierarchyTemplates, }), ...initActions, @@ -306,32 +297,26 @@ function* initializeNewDashboard( config, permissions, entitlements, - catalog, user, listedDashboards, accessibleDashboards, legacyDashboards, - dateHierarchyTemplates, ]: [ SagaReturnType, SagaReturnType, PromiseFnReturnType, - PromiseFnReturnType, PromiseFnReturnType, PromiseFnReturnType, PromiseFnReturnType, PromiseFnReturnType, - PromiseFnReturnType, ] = yield all([ call(resolveDashboardConfig, ctx, cmd), call(resolvePermissions, ctx, cmd), call(resolveEntitlements, ctx), - call(loadCatalog, ctx, cmd), call(loadUser, ctx), call(loadDashboardList, ctx), call(loadAccessibleDashboardList, ctx), call(loadLegacyDashboards, ctx), - call(loadDateHierarchyTemplates, ctx), call(loadFilterViews, ctx), ]); @@ -353,14 +338,6 @@ function* initializeNewDashboard( entitlementsActions.setEntitlements(entitlements), userActions.setUser(user), permissionsActions.setPermissions(permissions), - catalogActions.setCatalogItems({ - attributes: catalog.attributes(), - dateDatasets: catalog.dateDatasets(), - facts: catalog.facts(), - measures: catalog.measures(), - attributeHierarchies: catalog.attributeHierarchies(), - dateHierarchyTemplates: dateHierarchyTemplates, - }), listedDashboardsActions.setListedDashboards(listedDashboards), accessibleDashboardsActions.setAccessibleDashboards(accessibleDashboards), legacyDashboardsActions.setLegacyDashboards(legacyDashboards), diff --git a/libs/sdk-ui-dashboard/src/model/commandHandlers/render/renderingWorker.ts b/libs/sdk-ui-dashboard/src/model/commandHandlers/render/renderingWorker.ts index f1a072b4751..36559af63df 100644 --- a/libs/sdk-ui-dashboard/src/model/commandHandlers/render/renderingWorker.ts +++ b/libs/sdk-ui-dashboard/src/model/commandHandlers/render/renderingWorker.ts @@ -6,6 +6,9 @@ import { DashboardContext } from "../../types/commonTypes.js"; import { newDashboardEventPredicate } from "../../events/index.js"; import { renderRequested, renderResolved } from "../../events/render.js"; import { executedActions } from "../../store/executed/index.js"; +import { loadCatalog } from "../dashboard/initializeDashboardHandler/loadCatalog.js"; +import { loadDateHierarchyTemplates } from "../dashboard/initializeDashboardHandler/loadDateHierarchyTemplates.js"; +import { catalogActions } from "../../store/catalog/index.js"; function* wait(ms: number): SagaIterator { yield delay(ms); @@ -103,6 +106,20 @@ export function newRenderingWorker(renderingWorkerConfig: Partial { - const dateDataset = fromCatalog.get(filterObjRef(filter)); + // const dateDataset = fromCatalog.get(filterObjRef(filter)); return { - dateDataset, + dateDataset: filterObjRef(filter), filter, }; }); @@ -218,9 +218,8 @@ function resolveWidgetDateFilterIgnore( const nonIgnoredCommonDateFilterDateDatasetPairs = commonDateFilterDateDatasetPairs.filter( ({ dateDataset }) => { return ( - !!widget.dateDataSet && - dateDataset && - refMatchesMdObject(widget.dateDataSet, dateDataset.dataSet, "dataSet") + !!widget.dateDataSet && dateDataset && areObjRefsEqual(widget.dateDataSet, dateDataset) + // refMatchesMdObject(widget.dateDataSet, dateDataset.dataSet, "dataSet") ); }, ); @@ -230,7 +229,8 @@ function resolveWidgetDateFilterIgnore( dateDataset && widget.ignoreDashboardFilters ?.filter(isDashboardDateFilterReference) - .some((ignored) => refMatchesMdObject(ignored.dataSet, dateDataset.dataSet, "dataSet")); + //.some((ignored) => refMatchesMdObject(ignored.dataSet, dateDataset.dataSet, "dataSet")); + .some((ignored) => areObjRefsEqual(ignored.dataSet, dateDataset)); return !matches; }, @@ -268,7 +268,8 @@ function resolveDateFilters( .filter((item) => !!item.dateDataset) .reduceRight((acc: IDateFilter[], curr) => { const alreadyPresent = acc.some((item) => - refMatchesMdObject(filterObjRef(item), curr.dateDataset!.dataSet, "dataSet"), + //refMatchesMdObject(filterObjRef(item), curr.dateDataset!.dataSet, "dataSet"), + areObjRefsEqual(filterObjRef(item), curr.dateDataset), ); if (!alreadyPresent) { diff --git a/libs/sdk-ui-dashboard/src/model/store/catalog/catalogReducers.ts b/libs/sdk-ui-dashboard/src/model/store/catalog/catalogReducers.ts index e8d95a4c9e6..0d850177b7e 100644 --- a/libs/sdk-ui-dashboard/src/model/store/catalog/catalogReducers.ts +++ b/libs/sdk-ui-dashboard/src/model/store/catalog/catalogReducers.ts @@ -14,12 +14,12 @@ import { CatalogState } from "./catalogState.js"; type CatalogReducer = CaseReducer; export interface SetCatalogItemsPayload { - attributes: ICatalogAttribute[]; - measures: ICatalogMeasure[]; - facts: ICatalogFact[]; - dateDatasets: ICatalogDateDataset[]; - attributeHierarchies: ICatalogAttributeHierarchy[]; - dateHierarchyTemplates: IDateHierarchyTemplate[]; + attributes?: ICatalogAttribute[]; + measures?: ICatalogMeasure[]; + facts?: ICatalogFact[]; + dateDatasets?: ICatalogDateDataset[]; + attributeHierarchies?: ICatalogAttributeHierarchy[]; + dateHierarchyTemplates?: IDateHierarchyTemplate[]; } const setCatalogItems: CatalogReducer> = (state, action) => { diff --git a/libs/sdk-ui-dashboard/src/model/store/catalog/catalogSelectors.ts b/libs/sdk-ui-dashboard/src/model/store/catalog/catalogSelectors.ts index dc515cf07af..e73a9420a5d 100644 --- a/libs/sdk-ui-dashboard/src/model/store/catalog/catalogSelectors.ts +++ b/libs/sdk-ui-dashboard/src/model/store/catalog/catalogSelectors.ts @@ -136,6 +136,13 @@ export const selectHasCatalogDateDatasets: DashboardSelector = createSe negate(isEmpty), ); +/** + * @alpha + */ +export const selectCatalogIsLoaded: DashboardSelector = createSelector(selectSelf, (state) => { + return state.attributes !== undefined; +}); + /** * @public */ diff --git a/libs/sdk-ui-dashboard/src/model/store/index.ts b/libs/sdk-ui-dashboard/src/model/store/index.ts index c4dd2c52d31..9f0ceafea7e 100644 --- a/libs/sdk-ui-dashboard/src/model/store/index.ts +++ b/libs/sdk-ui-dashboard/src/model/store/index.ts @@ -234,6 +234,7 @@ export { } from "./insights/insightsSelectors.js"; export type { CatalogState } from "./catalog/catalogState.js"; export { + selectCatalogIsLoaded, selectAttributesWithDrillDown, selectCatalogAttributes, selectCatalogAttributeDisplayForms, diff --git a/libs/sdk-ui-dashboard/src/model/store/widgetDrills/widgetDrillSelectors.ts b/libs/sdk-ui-dashboard/src/model/store/widgetDrills/widgetDrillSelectors.ts index c32fcd06798..80781db7820 100644 --- a/libs/sdk-ui-dashboard/src/model/store/widgetDrills/widgetDrillSelectors.ts +++ b/libs/sdk-ui-dashboard/src/model/store/widgetDrills/widgetDrillSelectors.ts @@ -72,6 +72,7 @@ import { } from "../backendCapabilities/backendCapabilitiesSelectors.js"; import { existBlacklistHierarchyPredicate } from "../../utils/attributeHierarchyUtils.js"; import { selectDisableDashboardCrossFiltering } from "../meta/metaSelectors.js"; +import { selectCatalogIsLoaded } from "../catalog/catalogSelectors.js"; /** * @internal @@ -628,17 +629,27 @@ export const selectConfiguredAndImplicitDrillsByWidgetRef: ( ref: ObjRef, ) => DashboardSelector = createMemoizedSelector((ref: ObjRef) => createSelector( + selectCatalogIsLoaded, selectValidConfiguredDrillsByWidgetRef(ref), selectImplicitDrillsDownByWidgetRef(ref), selectImplicitDrillsToUrlByWidgetRef(ref), selectCrossFilteringByWidgetRef(ref), - (configuredDrills, implicitDrillDownDrills, implicitDrillToUrlDrills, crossFiltering) => { - return compact([ - ...configuredDrills, - ...implicitDrillDownDrills, - ...implicitDrillToUrlDrills, - crossFiltering, - ]); + ( + catalogIsLoaded, + configuredDrills, + implicitDrillDownDrills, + implicitDrillToUrlDrills, + crossFiltering, + ) => { + // disable drilling until catalog is fully loaded + return catalogIsLoaded + ? compact([ + ...configuredDrills, + ...implicitDrillDownDrills, + ...implicitDrillToUrlDrills, + crossFiltering, + ]) + : []; }, ), ); diff --git a/libs/sdk-ui-dashboard/src/model/utils/displayFormResolver.ts b/libs/sdk-ui-dashboard/src/model/utils/displayFormResolver.ts index 41598975424..a31d36e2253 100644 --- a/libs/sdk-ui-dashboard/src/model/utils/displayFormResolver.ts +++ b/libs/sdk-ui-dashboard/src/model/utils/displayFormResolver.ts @@ -1,4 +1,4 @@ -// (C) 2021-2022 GoodData Corporation +// (C) 2021-2024 GoodData Corporation import { ObjRef, IAttributeDisplayFormMetadataObject } from "@gooddata/sdk-model"; import { SagaIterator } from "redux-saga"; import { selectAllCatalogDisplayFormsMap } from "../store/catalog/catalogSelectors.js"; diff --git a/libs/sdk-ui-dashboard/src/presentation/dashboard/components/DashboardInner.tsx b/libs/sdk-ui-dashboard/src/presentation/dashboard/components/DashboardInner.tsx index 7a72dc19e93..28fb64d7cb4 100644 --- a/libs/sdk-ui-dashboard/src/presentation/dashboard/components/DashboardInner.tsx +++ b/libs/sdk-ui-dashboard/src/presentation/dashboard/components/DashboardInner.tsx @@ -6,6 +6,7 @@ import { useDashboardSelector, selectLocale, selectIsInEditMode, + selectCatalogIsLoaded, useDashboardAutomations, } from "../../../model/index.js"; import { DashboardHeader } from "../DashboardHeader/DashboardHeader.js"; @@ -29,6 +30,7 @@ const overlayController = OverlayController.getInstance(DASHBOARD_HEADER_OVERLAY export const DashboardInner: React.FC = (props) => { const locale = useDashboardSelector(selectLocale); const isEditMode = useDashboardSelector(selectIsInEditMode); + const isCatalogLoaded = useDashboardSelector(selectCatalogIsLoaded); const headerRef = useRef(null); const layoutRef = useRef(null); @@ -47,6 +49,7 @@ export const DashboardInner: React.FC = (props) => {
diff --git a/libs/sdk-ui-dashboard/src/presentation/topBar/buttonBar/button/editButton/DefaultEditButton.tsx b/libs/sdk-ui-dashboard/src/presentation/topBar/buttonBar/button/editButton/DefaultEditButton.tsx index 78dd7ebe392..45dd00a38e6 100644 --- a/libs/sdk-ui-dashboard/src/presentation/topBar/buttonBar/button/editButton/DefaultEditButton.tsx +++ b/libs/sdk-ui-dashboard/src/presentation/topBar/buttonBar/button/editButton/DefaultEditButton.tsx @@ -6,6 +6,7 @@ import { Bubble, BubbleHoverTrigger, Button, useMediaQuery } from "@gooddata/sdk import { selectIsDashboardLoading, selectIsInEditMode, + selectCatalogIsLoaded, switchToEditRenderMode, useDashboardDispatch, useDashboardSelector, @@ -23,6 +24,7 @@ export function useEditButtonProps(): IEditButtonProps { const canEnterEdit = useDashboardSelector(selectCanEnterEditMode); const isDashboardLoading = useDashboardSelector(selectIsDashboardLoading); + const isCatalogLoaded = useDashboardSelector(selectCatalogIsLoaded); const isEditing = useDashboardSelector(selectIsInEditMode); const dispatch = useDashboardDispatch(); @@ -30,7 +32,7 @@ export function useEditButtonProps(): IEditButtonProps { return { isVisible: minWidthForEditing && !isEditing && canEnterEdit, - isEnabled: !isDashboardLoading, + isEnabled: !isDashboardLoading && isCatalogLoaded, onEditClick, }; } diff --git a/libs/sdk-ui-dashboard/src/presentation/widget/insight/ViewModeDashboardInsight/Insight/useDashboardInsightDrills.ts b/libs/sdk-ui-dashboard/src/presentation/widget/insight/ViewModeDashboardInsight/Insight/useDashboardInsightDrills.ts index 3d4c8021b64..ff7530d8af8 100644 --- a/libs/sdk-ui-dashboard/src/presentation/widget/insight/ViewModeDashboardInsight/Insight/useDashboardInsightDrills.ts +++ b/libs/sdk-ui-dashboard/src/presentation/widget/insight/ViewModeDashboardInsight/Insight/useDashboardInsightDrills.ts @@ -1,4 +1,4 @@ -// (C) 2020-2022 GoodData Corporation +// (C) 2020-2024 GoodData Corporation import { useCallback, useMemo } from "react"; import isEqual from "lodash/isEqual.js"; import { @@ -10,6 +10,7 @@ import { selectConfiguredAndImplicitDrillsByWidgetRef, selectIsInEditMode, selectEnableKPIDashboardDrillFromAttribute, + selectCatalogIsLoaded, } from "../../../../../model/index.js"; import { OnWidgetDrill } from "../../../../drill/types.js"; import { @@ -42,6 +43,7 @@ export const useDashboardInsightDrills = ({ const drillTargets = useDashboardSelector(selectDrillTargetsByWidgetRef(widget.ref)); const isDrillFromAttributeEnabled = useDashboardSelector(selectEnableKPIDashboardDrillFromAttribute); const disableDrillDownOnWidget = insight.insight.properties.controls?.disableDrillDown; + const isCatalogLoaded = useDashboardSelector(selectCatalogIsLoaded); const onPushData = useCallback( (data: IPushData): void => { @@ -97,8 +99,9 @@ export const useDashboardInsightDrills = ({ } : undefined; + // disable all drills until catalog is loaded return { - drillableItems, + drillableItems: isCatalogLoaded ? drillableItems : [], onPushData, onDrill, };