From 22abc040002c66e0871c56e481361a3b2977fd74 Mon Sep 17 00:00:00 2001 From: Aditya Hegde Date: Thu, 4 Jun 2026 19:14:28 +0530 Subject: [PATCH 1/3] feat: add flag to disable persistent dashboard state --- runtime/feature_flags.go | 2 + runtime/feature_flags_test.go | 148 +++++++++--------- .../explore/[dashboard]/+page.svelte | 5 + .../loaders/DashboardStateDataLoader.ts | 6 +- .../loaders/DashboardStateManager.svelte | 6 + web-common/src/features/feature-flags.ts | 1 + .../routes/(viz)/explore/[name]/+page.svelte | 9 +- 7 files changed, 103 insertions(+), 74 deletions(-) diff --git a/runtime/feature_flags.go b/runtime/feature_flags.go index ae9f148f9567..15089e5452db 100644 --- a/runtime/feature_flags.go +++ b/runtime/feature_flags.go @@ -60,6 +60,8 @@ var defaultFeatureFlags = map[string]string{ "developer_agent": "true", // Controls if the dashboard state is persisted when navigating to a different dashboard. "sticky_dashboard_state": "false", + // Controls if the dashboard state is persisted when user revisits the same dashboard. + "disable_persistent_dashboard_state": "false", // Controls visibility of the cloud editing feature (Edit button and edit routes) "cloud_editing": "true", // Controls visibility of the custom chart option in canvas dashboards diff --git a/runtime/feature_flags_test.go b/runtime/feature_flags_test.go index 50a99b92a4c2..469e8a571a80 100644 --- a/runtime/feature_flags_test.go +++ b/runtime/feature_flags_test.go @@ -27,24 +27,25 @@ func Test_ResolveFeatureFlags(t *testing.T) { "domain": "rilldata.com", }, featureFlags: map[string]bool{ - "exports": true, - "cloudDataViewer": false, - "dimensionSearch": true, - "twoTieredNavigation": false, - "rillTime": true, - "hidePublicUrl": false, - "exportHeader": false, - "alerts": true, - "reports": true, - "chat": true, - "dashboardChat": false, - "developerChat": true, - "chatCharts": true, - "deploy": true, - "developerAgent": true, - "stickyDashboardState": false, - "cloudEditing": true, - "customCharts": false, + "exports": true, + "cloudDataViewer": false, + "dimensionSearch": true, + "twoTieredNavigation": false, + "rillTime": true, + "hidePublicUrl": false, + "exportHeader": false, + "alerts": true, + "reports": true, + "chat": true, + "dashboardChat": false, + "developerChat": true, + "chatCharts": true, + "deploy": true, + "developerAgent": true, + "stickyDashboardState": false, + "cloudEditing": true, + "customCharts": false, + "disablePersistentDashboardState": false, }, }, { @@ -53,24 +54,25 @@ func Test_ResolveFeatureFlags(t *testing.T) { "domain": "gmail.com", }, featureFlags: map[string]bool{ - "exports": true, - "cloudDataViewer": false, - "dimensionSearch": false, - "twoTieredNavigation": false, - "rillTime": true, - "hidePublicUrl": false, - "exportHeader": false, - "alerts": false, - "reports": true, - "chat": true, - "dashboardChat": false, - "developerChat": true, - "chatCharts": true, - "deploy": true, - "developerAgent": true, - "stickyDashboardState": false, - "cloudEditing": true, - "customCharts": false, + "exports": true, + "cloudDataViewer": false, + "dimensionSearch": false, + "twoTieredNavigation": false, + "rillTime": true, + "hidePublicUrl": false, + "exportHeader": false, + "alerts": false, + "reports": true, + "chat": true, + "dashboardChat": false, + "developerChat": true, + "chatCharts": true, + "deploy": true, + "developerAgent": true, + "stickyDashboardState": false, + "cloudEditing": true, + "customCharts": false, + "disablePersistentDashboardState": false, }, }, { @@ -79,24 +81,25 @@ func Test_ResolveFeatureFlags(t *testing.T) { "domain": "yahoo.com", }, featureFlags: map[string]bool{ - "exports": true, - "cloudDataViewer": false, - "dimensionSearch": false, - "twoTieredNavigation": false, - "rillTime": true, - "hidePublicUrl": false, - "exportHeader": false, - "alerts": false, - "reports": false, - "chat": true, - "dashboardChat": false, - "developerChat": true, - "chatCharts": true, - "deploy": true, - "developerAgent": true, - "stickyDashboardState": false, - "cloudEditing": true, - "customCharts": false, + "exports": true, + "cloudDataViewer": false, + "dimensionSearch": false, + "twoTieredNavigation": false, + "rillTime": true, + "hidePublicUrl": false, + "exportHeader": false, + "alerts": false, + "reports": false, + "chat": true, + "dashboardChat": false, + "developerChat": true, + "chatCharts": true, + "deploy": true, + "developerAgent": true, + "stickyDashboardState": false, + "cloudEditing": true, + "customCharts": false, + "disablePersistentDashboardState": false, }, }, { @@ -105,24 +108,25 @@ func Test_ResolveFeatureFlags(t *testing.T) { "embed": true, }, featureFlags: map[string]bool{ - "exports": true, - "cloudDataViewer": false, - "dimensionSearch": false, - "twoTieredNavigation": false, - "rillTime": true, - "hidePublicUrl": true, - "exportHeader": false, - "alerts": false, - "reports": false, - "chat": false, - "dashboardChat": false, // forced false because chat is false - "developerChat": true, - "chatCharts": true, - "deploy": true, - "developerAgent": true, - "stickyDashboardState": false, - "cloudEditing": true, - "customCharts": false, + "exports": true, + "cloudDataViewer": false, + "dimensionSearch": false, + "twoTieredNavigation": false, + "rillTime": true, + "hidePublicUrl": true, + "exportHeader": false, + "alerts": false, + "reports": false, + "chat": false, + "dashboardChat": false, // forced false because chat is false + "developerChat": true, + "chatCharts": true, + "deploy": true, + "developerAgent": true, + "stickyDashboardState": false, + "cloudEditing": true, + "customCharts": false, + "disablePersistentDashboardState": false, }, }, } diff --git a/web-admin/src/routes/[organization]/[project]/explore/[dashboard]/+page.svelte b/web-admin/src/routes/[organization]/[project]/explore/[dashboard]/+page.svelte index 4780623cbdc3..b3c562f81ebc 100644 --- a/web-admin/src/routes/[organization]/[project]/explore/[dashboard]/+page.svelte +++ b/web-admin/src/routes/[organization]/[project]/explore/[dashboard]/+page.svelte @@ -21,10 +21,13 @@ import { isNotFoundError } from "@rilldata/web-common/lib/errors"; import { useRuntimeClient } from "@rilldata/web-common/runtime-client/v2"; import type { PageData } from "./$types"; + import { featureFlags } from "@rilldata/web-common/features/feature-flags.ts"; export let data: PageData; $: ({ project } = data); + const { disablePersistentDashboardState } = featureFlags; + const runtimeClient = useRuntimeClient(); $: ({ organization: orgName, @@ -115,6 +118,8 @@ {exploreName} storageNamespacePrefix={`${orgName}__${projectName}__`} bookmarkOrTokenExploreState={bookmarkExploreStateQuery} + disableMostRecentDashboardState={$disablePersistentDashboardState} + disableSessionDashboardState={$disablePersistentDashboardState} > diff --git a/web-common/src/features/dashboards/state-managers/loaders/DashboardStateDataLoader.ts b/web-common/src/features/dashboards/state-managers/loaders/DashboardStateDataLoader.ts index 6c49f4e63929..4e89976a2ee8 100644 --- a/web-common/src/features/dashboards/state-managers/loaders/DashboardStateDataLoader.ts +++ b/web-common/src/features/dashboards/state-managers/loaders/DashboardStateDataLoader.ts @@ -66,6 +66,7 @@ export class DashboardStateDataLoader { | CompoundQueryResult | null> | undefined, public readonly disableMostRecentDashboardState: boolean, + public readonly disableSessionDashboardState: boolean, ) { this.validSpecQuery = useExploreValidSpec(client, exploreName); this.fullTimeRangeQuery = this.useFullTimeRangeQuery( @@ -153,6 +154,7 @@ export class DashboardStateDataLoader { exploreStateFromYAMLConfig, rillDefaultExploreState, backButtonUsed: false, + skipSessionStorage: this.disableSessionDashboardState, }); }, ); @@ -297,6 +299,7 @@ export class DashboardStateDataLoader { exploreStateFromYAMLConfig, rillDefaultExploreState, backButtonUsed, + skipSessionStorage = false, }: { metricsViewSpec: V1MetricsViewSpec; exploreSpec: V1ExploreSpec; @@ -305,10 +308,11 @@ export class DashboardStateDataLoader { exploreStateFromYAMLConfig: Partial; rillDefaultExploreState: ExploreState; backButtonUsed: boolean; + skipSessionStorage: boolean; }) { urlSearchParams = cleanEmbedUrlParams(urlSearchParams); - const skipSessionStorage = backButtonUsed; + skipSessionStorage ||= backButtonUsed; const exploreStateFromSessionStorage = skipSessionStorage ? null : getPartialExploreStateFromSessionStorage( diff --git a/web-common/src/features/dashboards/state-managers/loaders/DashboardStateManager.svelte b/web-common/src/features/dashboards/state-managers/loaders/DashboardStateManager.svelte index 59a77ef064ea..a56035dca4ad 100644 --- a/web-common/src/features/dashboards/state-managers/loaders/DashboardStateManager.svelte +++ b/web-common/src/features/dashboards/state-managers/loaders/DashboardStateManager.svelte @@ -21,6 +21,7 @@ } from "@rilldata/web-common/lib/errors"; import { useRuntimeClient } from "@rilldata/web-common/runtime-client/v2"; import { onDestroy } from "svelte"; + import { clearExploreSessionStore } from "@rilldata/web-common/features/dashboards/state-managers/loaders/explore-web-view-store.ts"; export let exploreName: string; export let storageNamespacePrefix: string | undefined = undefined; @@ -28,6 +29,7 @@ | CompoundQueryResult | null> | undefined = undefined; export let disableMostRecentDashboardState: boolean = false; + export let disableSessionDashboardState: boolean = false; const client = useRuntimeClient(); @@ -42,6 +44,7 @@ storageNamespacePrefix, bookmarkOrTokenExploreState, disableMostRecentDashboardState, + disableSessionDashboardState, ); let stateSync: DashboardStateSync | undefined; @@ -99,6 +102,9 @@ // Note: we still have this on top of the above reactive statement to handle cases where navigation is to a non-dashboard route. if (changedDashboard) { eventBus.emit("remove-banner", ExploreUrlLimitWarningBannerID); + if (disableSessionDashboardState) { + clearExploreSessionStore(exploreName, storageNamespacePrefix); + } } }); diff --git a/web-common/src/features/feature-flags.ts b/web-common/src/features/feature-flags.ts index 02e4dcc2a83a..e62e5ffb058c 100644 --- a/web-common/src/features/feature-flags.ts +++ b/web-common/src/features/feature-flags.ts @@ -66,6 +66,7 @@ class FeatureFlags { stickyDashboardState = new FeatureFlag("user", false); cloudEditing = new FeatureFlag("user", false); customCharts = new FeatureFlag("user", false); + disablePersistentDashboardState = new FeatureFlag("user", false); private flagsUnsub?: () => void; diff --git a/web-local/src/routes/(viz)/explore/[name]/+page.svelte b/web-local/src/routes/(viz)/explore/[name]/+page.svelte index f58035a7fdc6..c06b345b25f4 100644 --- a/web-local/src/routes/(viz)/explore/[name]/+page.svelte +++ b/web-local/src/routes/(viz)/explore/[name]/+page.svelte @@ -26,12 +26,15 @@ import { previewModeStore } from "@rilldata/web-common/layout/preview-mode-store"; import { useRuntimeClient } from "@rilldata/web-common/runtime-client/v2"; import type { PageData } from "./$types"; + import { featureFlags } from "@rilldata/web-common/features/feature-flags.ts"; const runtimeClient = useRuntimeClient(); export let data: PageData; $: ({ exploreName } = data); + const { disablePersistentDashboardState } = featureFlags; + resetSelectedMockUserAfterNavigate(queryClient, runtimeClient); $: exploreResource = useExploreWithPolling(runtimeClient, exploreName); @@ -131,7 +134,11 @@
{#key exploreName} - + From 9873a46521a7560d7c23963dc727fb74de1a786e Mon Sep 17 00:00:00 2001 From: Aditya Hegde Date: Fri, 5 Jun 2026 10:54:55 +0530 Subject: [PATCH 2/3] Add tests --- .../loaders/DashboardStateManager.spec.ts | 31 +++++++++++++++++++ .../test/DashboardStateManagerTest.svelte | 9 +++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/web-common/src/features/dashboards/state-managers/loaders/DashboardStateManager.spec.ts b/web-common/src/features/dashboards/state-managers/loaders/DashboardStateManager.spec.ts index 244f7df0be9e..d4459a7946fc 100644 --- a/web-common/src/features/dashboards/state-managers/loaders/DashboardStateManager.spec.ts +++ b/web-common/src/features/dashboards/state-managers/loaders/DashboardStateManager.spec.ts @@ -296,6 +296,33 @@ describe("DashboardStateManager", () => { // only 2 urls should in history expect(pageMock.urlSearchHistory).toEqual([initUrlSearch, ""]); }); + + it("Should not load local storage or session storage when disabled", async () => { + setMostRecentExploreStateInLocalStorage(AD_BIDS_EXPLORE_NAME, undefined, { + visibleMeasures: [AD_BIDS_BID_PRICE_MEASURE], + allMeasuresVisible: false, + visibleDimensions: [AD_BIDS_DOMAIN_DIMENSION], + allDimensionsVisible: false, + + leaderboardSortByMeasureName: AD_BIDS_BID_PRICE_MEASURE, + leaderboardMeasureNames: [AD_BIDS_BID_PRICE_MEASURE], + sortDirection: DashboardState_LeaderboardSortDirection.ASCENDING, + dashboardSortType: DashboardState_LeaderboardSortType.VALUE, + }); + setExploreStateForWebView( + AD_BIDS_EXPLORE_NAME, + undefined, + ExploreUrlWebView.Explore, + "view=explore&tr=P14D&compare_tr=rill-PW&grain=day&measures=bid_price&dims=domain&sort_by=bid_price&sort_type=delta_abs&sort_dir=DESC&leaderboard_measures=bid_price", + ); + renderDashboardStateManager(undefined, true, true); + await waitFor(() => expect(screen.getByText("Dashboard loaded!"))); + + assertExploreStateSubset({ + ...ExploreStateSubsetForRillDefaultState, + ...ExploreStateSubsetForYAMLState, + }); + }); }); describe("Dashboards without timeseries", () => { @@ -481,11 +508,15 @@ function renderDashboardStateManager( bookmarkOrTokenExploreState: | CompoundQueryResult | undefined> | undefined = undefined, + disableMostRecentDashboardState: boolean = false, + disableSessionDashboardState: boolean = false, ) { const renderResults = render(DashboardStateManagerTest, { props: { exploreName: AD_BIDS_EXPLORE_NAME, bookmarkOrTokenExploreState, + disableMostRecentDashboardState, + disableSessionDashboardState, }, // TODO: we need to make sure every single query uses an explicit queryClient instead of the global one // only then we can use a fresh client here. diff --git a/web-common/src/features/dashboards/state-managers/loaders/test/DashboardStateManagerTest.svelte b/web-common/src/features/dashboards/state-managers/loaders/test/DashboardStateManagerTest.svelte index 724324bae715..f3a218e5e5e6 100644 --- a/web-common/src/features/dashboards/state-managers/loaders/test/DashboardStateManagerTest.svelte +++ b/web-common/src/features/dashboards/state-managers/loaders/test/DashboardStateManagerTest.svelte @@ -10,8 +10,15 @@ export let bookmarkOrTokenExploreState: | CompoundQueryResult | null> | undefined = undefined; + export let disableMostRecentDashboardState: boolean = false; + export let disableSessionDashboardState: boolean = false; - +
Dashboard loaded!
From 7b3aa722f2d6efb4cd907b1e3908d6e16a00aba6 Mon Sep 17 00:00:00 2001 From: Aditya Hegde Date: Fri, 5 Jun 2026 11:02:10 +0530 Subject: [PATCH 3/3] Self review --- .../[project]/explore/[dashboard]/+page.svelte | 2 +- .../loaders/DashboardStateDataLoader.ts | 9 ++++++--- .../loaders/DashboardStateManager.spec.ts | 4 ++-- .../loaders/DashboardStateManager.svelte | 11 +++++++---- .../loaders/test/DashboardStateManagerTest.svelte | 4 ++-- .../src/routes/(viz)/explore/[name]/+page.svelte | 2 +- 6 files changed, 19 insertions(+), 13 deletions(-) diff --git a/web-admin/src/routes/[organization]/[project]/explore/[dashboard]/+page.svelte b/web-admin/src/routes/[organization]/[project]/explore/[dashboard]/+page.svelte index b3c562f81ebc..39335b3a9d79 100644 --- a/web-admin/src/routes/[organization]/[project]/explore/[dashboard]/+page.svelte +++ b/web-admin/src/routes/[organization]/[project]/explore/[dashboard]/+page.svelte @@ -119,7 +119,7 @@ storageNamespacePrefix={`${orgName}__${projectName}__`} bookmarkOrTokenExploreState={bookmarkExploreStateQuery} disableMostRecentDashboardState={$disablePersistentDashboardState} - disableSessionDashboardState={$disablePersistentDashboardState} + disableInitSessionDashboardState={$disablePersistentDashboardState} >
diff --git a/web-common/src/features/dashboards/state-managers/loaders/DashboardStateDataLoader.ts b/web-common/src/features/dashboards/state-managers/loaders/DashboardStateDataLoader.ts index 4e89976a2ee8..4f48473d10d4 100644 --- a/web-common/src/features/dashboards/state-managers/loaders/DashboardStateDataLoader.ts +++ b/web-common/src/features/dashboards/state-managers/loaders/DashboardStateDataLoader.ts @@ -66,7 +66,7 @@ export class DashboardStateDataLoader { | CompoundQueryResult | null> | undefined, public readonly disableMostRecentDashboardState: boolean, - public readonly disableSessionDashboardState: boolean, + public readonly disableInitSessionDashboardState: boolean, ) { this.validSpecQuery = useExploreValidSpec(client, exploreName); this.fullTimeRangeQuery = this.useFullTimeRangeQuery( @@ -154,7 +154,7 @@ export class DashboardStateDataLoader { exploreStateFromYAMLConfig, rillDefaultExploreState, backButtonUsed: false, - skipSessionStorage: this.disableSessionDashboardState, + skipSessionStorage: this.disableInitSessionDashboardState, }); }, ); @@ -193,6 +193,9 @@ export class DashboardStateDataLoader { exploreStateFromYAMLConfig, rillDefaultExploreState, backButtonUsed, + // This should not be disabled when disableInitSessionDashboardState is set. + // While going between views, we still need session storage to save the state. + skipSessionStorage: false, }); } @@ -299,7 +302,7 @@ export class DashboardStateDataLoader { exploreStateFromYAMLConfig, rillDefaultExploreState, backButtonUsed, - skipSessionStorage = false, + skipSessionStorage, }: { metricsViewSpec: V1MetricsViewSpec; exploreSpec: V1ExploreSpec; diff --git a/web-common/src/features/dashboards/state-managers/loaders/DashboardStateManager.spec.ts b/web-common/src/features/dashboards/state-managers/loaders/DashboardStateManager.spec.ts index d4459a7946fc..435684076dae 100644 --- a/web-common/src/features/dashboards/state-managers/loaders/DashboardStateManager.spec.ts +++ b/web-common/src/features/dashboards/state-managers/loaders/DashboardStateManager.spec.ts @@ -509,14 +509,14 @@ function renderDashboardStateManager( | CompoundQueryResult | undefined> | undefined = undefined, disableMostRecentDashboardState: boolean = false, - disableSessionDashboardState: boolean = false, + disableInitSessionDashboardState: boolean = false, ) { const renderResults = render(DashboardStateManagerTest, { props: { exploreName: AD_BIDS_EXPLORE_NAME, bookmarkOrTokenExploreState, disableMostRecentDashboardState, - disableSessionDashboardState, + disableInitSessionDashboardState, }, // TODO: we need to make sure every single query uses an explicit queryClient instead of the global one // only then we can use a fresh client here. diff --git a/web-common/src/features/dashboards/state-managers/loaders/DashboardStateManager.svelte b/web-common/src/features/dashboards/state-managers/loaders/DashboardStateManager.svelte index a56035dca4ad..14c17a68a992 100644 --- a/web-common/src/features/dashboards/state-managers/loaders/DashboardStateManager.svelte +++ b/web-common/src/features/dashboards/state-managers/loaders/DashboardStateManager.svelte @@ -29,7 +29,7 @@ | CompoundQueryResult | null> | undefined = undefined; export let disableMostRecentDashboardState: boolean = false; - export let disableSessionDashboardState: boolean = false; + export let disableInitSessionDashboardState: boolean = false; const client = useRuntimeClient(); @@ -44,7 +44,7 @@ storageNamespacePrefix, bookmarkOrTokenExploreState, disableMostRecentDashboardState, - disableSessionDashboardState, + disableInitSessionDashboardState, ); let stateSync: DashboardStateSync | undefined; @@ -97,12 +97,15 @@ onNavigate(({ from, to }) => { const changedDashboard = - !from || !to || from.params?.dashboard !== to.params?.dashboard; + !from || + !to || + from.params?.dashboard !== to.params?.dashboard || + from.params?.name !== to.params?.name; // Clear out any dashboard banners // Note: we still have this on top of the above reactive statement to handle cases where navigation is to a non-dashboard route. if (changedDashboard) { eventBus.emit("remove-banner", ExploreUrlLimitWarningBannerID); - if (disableSessionDashboardState) { + if (disableInitSessionDashboardState) { clearExploreSessionStore(exploreName, storageNamespacePrefix); } } diff --git a/web-common/src/features/dashboards/state-managers/loaders/test/DashboardStateManagerTest.svelte b/web-common/src/features/dashboards/state-managers/loaders/test/DashboardStateManagerTest.svelte index f3a218e5e5e6..ddf52fedd4ec 100644 --- a/web-common/src/features/dashboards/state-managers/loaders/test/DashboardStateManagerTest.svelte +++ b/web-common/src/features/dashboards/state-managers/loaders/test/DashboardStateManagerTest.svelte @@ -11,14 +11,14 @@ | CompoundQueryResult | null> | undefined = undefined; export let disableMostRecentDashboardState: boolean = false; - export let disableSessionDashboardState: boolean = false; + export let disableInitSessionDashboardState: boolean = false;
Dashboard loaded!
diff --git a/web-local/src/routes/(viz)/explore/[name]/+page.svelte b/web-local/src/routes/(viz)/explore/[name]/+page.svelte index c06b345b25f4..808a6e2e232e 100644 --- a/web-local/src/routes/(viz)/explore/[name]/+page.svelte +++ b/web-local/src/routes/(viz)/explore/[name]/+page.svelte @@ -137,7 +137,7 @@