Scenes: Upgrade to latest URL sync system (#88836)

* Urlsync updates

* Update

* Fixing tests

* Update to latest canary

* fix

* Update

* Update

* Update

* Fix data trails issue

* Data trails fixes

* Update

* correctly sync scene object graph with url state

* Update
pull/89202/head
Torkel Ödegaard 1 year ago committed by GitHub
parent dd3c3b5857
commit e3da5ed35d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 6
      package.json
  2. 5
      public/app/features/dashboard-scene/pages/DashboardScenePage.tsx
  3. 37
      public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.test.ts
  4. 4
      public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts
  5. 11
      public/app/features/dashboard-scene/pages/PublicDashboardScenePage.tsx
  6. 3
      public/app/features/dashboard-scene/saving/useSaveDashboard.ts
  7. 19
      public/app/features/dashboard-scene/scene/DashboardScene.tsx
  8. 12
      public/app/features/dashboard-scene/scene/NavToolbarActions.test.tsx
  9. 11
      public/app/features/dashboard-scene/utils/dashboardSessionState.test.ts
  10. 8
      public/app/features/dashboard-scene/utils/dashboardSessionState.ts
  11. 20
      public/app/features/trails/DataTrail.test.tsx
  12. 36
      public/app/features/trails/DataTrail.tsx
  13. 75
      public/app/features/trails/DataTrailsApp.tsx
  14. 2
      public/app/features/trails/TrailStore/TrailStore.test.ts
  15. 22
      public/app/features/trails/TrailStore/TrailStore.ts
  16. 608
      yarn.lock

@ -94,6 +94,7 @@
"@testing-library/jest-dom": "6.4.2",
"@testing-library/react": "15.0.2",
"@testing-library/user-event": "14.5.2",
"@types/add": "^2",
"@types/angular": "1.8.9",
"@types/angular-route": "1.7.6",
"@types/babel__core": "^7",
@ -258,7 +259,7 @@
"@grafana/prometheus": "workspace:*",
"@grafana/runtime": "workspace:*",
"@grafana/saga-icons": "workspace:*",
"@grafana/scenes": "4.29.0",
"@grafana/scenes": "^5.0.2",
"@grafana/schema": "workspace:*",
"@grafana/sql": "workspace:*",
"@grafana/ui": "workspace:*",
@ -398,7 +399,8 @@
"uuid": "9.0.1",
"visjs-network": "4.25.0",
"whatwg-fetch": "3.6.20",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz"
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz",
"yarn": "^1.22.22"
},
"resolutions": {
"underscore": "1.13.6",

@ -2,6 +2,7 @@
import React, { useEffect, useMemo } from 'react';
import { PageLayoutType } from '@grafana/data';
import { UrlSyncContextProvider } from '@grafana/scenes';
import { Page } from 'app/core/components/Page/Page';
import PageLoader from 'app/core/components/PageLoader/PageLoader';
import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
@ -78,10 +79,10 @@ export function DashboardScenePage({ match, route, queryParams, history }: Props
}
return (
<>
<UrlSyncContextProvider scene={dashboard}>
<dashboard.Component model={dashboard} key={dashboard.state.key} />
<DashboardPrompt dashboard={dashboard} />
</>
</UrlSyncContextProvider>
);
}

@ -1,7 +1,6 @@
import { advanceBy } from 'jest-date-mock';
import { BackendSrv, locationService, setBackendSrv } from '@grafana/runtime';
import { getUrlSyncManager } from '@grafana/scenes';
import { BackendSrv, setBackendSrv } from '@grafana/runtime';
import store from 'app/core/store';
import { DASHBOARD_FROM_LS_KEY } from 'app/features/dashboard/state/initDashboard';
import { DashboardRoutes } from 'app/types';
@ -95,40 +94,6 @@ describe('DashboardScenePageStateManager', () => {
expect(loader.state.isLoading).toBe(false);
});
it('should initialize url sync', async () => {
setupLoadDashboardMock({ dashboard: { uid: 'fake-dash' }, meta: {} });
locationService.partial({ from: 'now-5m', to: 'now' });
const loader = new DashboardScenePageStateManager({});
await loader.loadDashboard({ uid: 'fake-dash', route: DashboardRoutes.Normal });
const dash = loader.state.dashboard;
expect(dash!.state.$timeRange?.state.from).toEqual('now-5m');
getUrlSyncManager().cleanUp(dash!);
// try loading again (and hitting cache)
locationService.partial({ from: 'now-10m', to: 'now' });
await loader.loadDashboard({ uid: 'fake-dash', route: DashboardRoutes.Normal });
const dash2 = loader.state.dashboard;
expect(dash2!.state.$timeRange?.state.from).toEqual('now-10m');
});
it('should not initialize url sync for embedded dashboards', async () => {
setupLoadDashboardMock({ dashboard: { uid: 'fake-dash' }, meta: {} });
locationService.partial({ from: 'now-5m', to: 'now' });
const loader = new DashboardScenePageStateManager({});
await loader.loadDashboard({ uid: 'fake-dash', route: DashboardRoutes.Embedded });
const dash = loader.state.dashboard;
expect(dash!.state.$timeRange?.state.from).toEqual('now-6h');
});
describe('New dashboards', () => {
it('Should have new empty model with meta.isNew and should not be cached', async () => {
const loader = new DashboardScenePageStateManager({});

@ -182,10 +182,6 @@ export class DashboardScenePageStateManager extends StateManagerBase<DashboardSc
restoreDashboardStateFromLocalStorage(dashboard);
}
if (!(config.publicDashboardAccessToken && dashboard.state.controls?.state.hideTimeControls)) {
dashboard.startUrlSync();
}
this.setState({ dashboard: dashboard, isLoading: false });
const measure = stopMeasure(LOAD_SCENE_MEASUREMENT);
trackDashboardSceneLoaded(dashboard, measure?.duration);

@ -3,7 +3,7 @@ import React, { useEffect, useState } from 'react';
import { GrafanaTheme2, PageLayoutType } from '@grafana/data';
import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
import { SceneComponentProps } from '@grafana/scenes';
import { SceneComponentProps, UrlSyncContextProvider } from '@grafana/scenes';
import { Icon, Stack, useStyles2 } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
import PageLoader from 'app/core/components/PageLoader/PageLoader';
@ -55,9 +55,18 @@ export function PublicDashboardScenePage({ match, route }: Props) {
return <PublicDashboardNotAvailable />;
}
// if no time picker render without url sync
if (dashboard.state.controls?.state.hideTimeControls) {
return <PublicDashboardSceneRenderer model={dashboard} />;
}
return (
<UrlSyncContextProvider scene={dashboard}>
<PublicDashboardSceneRenderer model={dashboard} />
</UrlSyncContextProvider>
);
}
function PublicDashboardSceneRenderer({ model }: SceneComponentProps<DashboardScene>) {
const [isActive, setIsActive] = useState(false);
const { controls, title } = model.useState();

@ -61,10 +61,7 @@ export function useSaveDashboard(isCopy = false) {
if (newUrl !== currentLocation.pathname) {
setTimeout(() => {
// Because the path changes we need to stop and restart url sync
scene.stopUrlSync();
locationService.push({ pathname: newUrl, search: currentLocation.search });
scene.startUrlSync();
});
}

@ -12,7 +12,6 @@ import {
} from '@grafana/data';
import { config, locationService } from '@grafana/runtime';
import {
getUrlSyncManager,
SceneFlexLayout,
sceneGraph,
SceneGridLayout,
@ -212,27 +211,15 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
window.__grafanaSceneContext = prevSceneContext;
clearKeyBindings();
this._changeTracker.terminate();
this.stopUrlSync();
oldDashboardWrapper.destroy();
dashboardWatcher.leave();
};
}
public startUrlSync() {
if (!this.state.meta.isEmbedded) {
getUrlSyncManager().initSync(this);
}
}
public stopUrlSync() {
getUrlSyncManager().cleanUp(this);
}
public onEnterEditMode = (fromExplore = false) => {
this._fromExplore = fromExplore;
// Save this state
this._initialState = sceneUtils.cloneSceneObjectState(this.state);
this._initialUrlState = locationService.getLocation();
// Switch to edit mode
@ -303,10 +290,7 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
private exitEditModeConfirmed(restoreInitialState = true) {
// No need to listen to changes anymore
this._changeTracker.stopTrackingChanges();
// Stop url sync before updating url
this.stopUrlSync();
// Now we can update urls
// We are updating url and removing editview and editPanel.
// The initial url may be including edit view, edit panel or inspect query params if the user pasted the url,
// hence we need to cleanup those query params to get back to the dashboard view. Otherwise url sync can trigger overlays.
@ -330,8 +314,7 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
// Do not restore
this.setState({ isEditing: false });
}
// and start url sync again
this.startUrlSync();
// Disable grid dragging
this.propagateEditModeChange();
}

@ -5,8 +5,8 @@ import { TestProvider } from 'test/helpers/TestProvider';
import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock';
import { selectors } from '@grafana/e2e-selectors';
import { config } from '@grafana/runtime';
import { SceneGridLayout, SceneQueryRunner, SceneTimeRange, VizPanel } from '@grafana/scenes';
import { config, locationService } from '@grafana/runtime';
import { SceneGridLayout, SceneQueryRunner, SceneTimeRange, UrlSyncContextProvider, VizPanel } from '@grafana/scenes';
import { playlistSrv } from 'app/features/playlist/PlaylistSrv';
import { buildPanelEditScene } from '../panel-edit/PanelEditor';
@ -103,9 +103,8 @@ describe('NavToolbarActions', () => {
});
it('Should show correct buttons when in settings menu', async () => {
const { dashboard } = setup();
setup();
dashboard.startUrlSync();
await userEvent.click(await screen.findByText('Edit'));
await userEvent.click(await screen.findByText('Settings'));
@ -118,6 +117,7 @@ describe('NavToolbarActions', () => {
it('Should show correct buttons when editing a new panel', async () => {
const { dashboard } = setup();
await act(() => {
dashboard.onEnterEditMode();
const editingPanel = ((dashboard.state.body as SceneGridLayout).state.children[0] as DashboardGridItem).state
@ -205,9 +205,13 @@ function setup() {
const context = getGrafanaContextMock();
locationService.push('/');
render(
<TestProvider grafanaContext={context}>
<UrlSyncContextProvider scene={dashboard}>
<ToolbarActions dashboard={dashboard} />
</UrlSyncContextProvider>
</TestProvider>
);

@ -1,5 +1,5 @@
import { config, locationService } from '@grafana/runtime';
import { CustomVariable } from '@grafana/scenes';
import { CustomVariable, getUrlSyncManager } from '@grafana/scenes';
import { DashboardDataDTO } from 'app/types';
import { transformSaveModelToScene } from '../serialization/transformSaveModelToScene';
@ -50,7 +50,8 @@ describe('dashboardSessionState', () => {
restoreDashboardStateFromLocalStorage(scene);
const variable = scene.state.$variables!.getByName('customVar') as CustomVariable;
const timeRange = scene.state.$timeRange;
scene.startUrlSync();
getUrlSyncManager().initSync(scene);
expect(variable!.state!.value).toEqual(['b']);
expect(variable!.state!.text).toEqual(['b']);
@ -63,11 +64,12 @@ describe('dashboardSessionState', () => {
PRESERVED_SCENE_STATE_KEY,
'?var-customVar=b&var-nonApplicableVar=b&from=now-5m&to=now&timezone=browser'
);
const scene = buildTestScene();
restoreDashboardStateFromLocalStorage(scene);
expect(locationService.getSearch().toString()).toBe('var-customVar=b&from=now-5m&to=now&timezone=browser');
expect(locationService.getLocation().search).toBe('?var-customVar=b&from=now-5m&to=now&timezone=browser');
});
// handles case when user navigates back to a dashboard with the same state, i.e. using back button
@ -79,7 +81,7 @@ describe('dashboardSessionState', () => {
restoreDashboardStateFromLocalStorage(scene);
expect(locationService.getSearch().toString()).toBe('var-customVar=b&from=now-6h&to=now&timezone=browser');
expect(locationService.getLocation().search).toBe('?var-customVar=b&from=now-6h&to=now&timezone=browser');
});
});
});
@ -116,6 +118,7 @@ function buildTestScene() {
version: 24,
weekStart: '',
};
const scene = transformSaveModelToScene({ dashboard: testDashboard, meta: {} });
// Removing data layers to avoid mocking built-in Grafana data source

@ -17,10 +17,6 @@ export function restoreDashboardStateFromLocalStorage(dashboard: DashboardScene)
preservedQueryParams.forEach((value, key) => {
if (!currentQueryParams.has(key)) {
currentQueryParams.append(key, value);
} else {
if (!currentQueryParams.getAll(key).includes(value)) {
currentQueryParams.append(key, value);
}
}
});
@ -38,9 +34,7 @@ export function restoreDashboardStateFromLocalStorage(dashboard: DashboardScene)
const finalParams = currentQueryParams.toString();
if (finalParams) {
locationService.replace({
search: finalParams,
});
locationService.replace({ search: finalParams });
}
}
}

@ -71,7 +71,7 @@ describe('DataTrail', () => {
});
it('should sync state with url', () => {
expect(locationService.getSearchObject().metric).toBe('metric_bucket');
expect(trail.getUrlState().metric).toBe('metric_bucket');
});
it('should add history step', () => {
@ -104,10 +104,6 @@ describe('DataTrail', () => {
trail.state.$timeRange?.setState({ from: 'now-1h' });
});
it('should sync state with url', () => {
expect(locationService.getSearchObject().from).toBe('now-1h');
});
it('should add history step', () => {
expect(trail.state.history.state.steps[2].type).toBe('time');
});
@ -154,10 +150,6 @@ describe('DataTrail', () => {
trail.state.$timeRange?.setState({ from: 'now-15m' });
});
it('should sync state with url', () => {
expect(locationService.getSearchObject().from).toBe('now-15m');
});
it('should add history step', () => {
expect(trail.state.history.state.steps[3].type).toBe('time');
});
@ -224,10 +216,6 @@ describe('DataTrail', () => {
getFilterVar().setState({ filters: [{ key: 'zone', operator: '=', value: 'a' }] });
});
it('should sync state with url', () => {
expect(decodeURIComponent(locationService.getSearchObject()['var-filters']?.toString()!)).toBe('zone|=|a');
});
it('should add history step', () => {
expect(trail.state.history.state.steps[2].type).toBe('filters');
});
@ -276,12 +264,6 @@ describe('DataTrail', () => {
getFilterVar().setState({ filters: [{ key: 'zone', operator: '=', value: 'b' }] });
});
it('should sync state with url', () => {
expect(decodeURIComponent(locationService.getSearchObject()['var-filters']?.toString()!)).toBe(
'zone|=|b'
);
});
it('should add history step', () => {
expect(trail.state.history.state.steps[3].type).toBe('filters');
});

@ -1,7 +1,7 @@
import { css } from '@emotion/css';
import React from 'react';
import { AdHocVariableFilter, GrafanaTheme2, VariableHide, urlUtil } from '@grafana/data';
import { AdHocVariableFilter, GrafanaTheme2, PageLayoutType, VariableHide, urlUtil } from '@grafana/data';
import { locationService } from '@grafana/runtime';
import {
AdHocFiltersVariable,
@ -25,6 +25,7 @@ import {
VariableValueSelectors,
} from '@grafana/scenes';
import { useStyles2 } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
import { DataTrailSettings } from './DataTrailSettings';
import { DataTrailHistory } from './DataTrailsHistory';
@ -35,6 +36,7 @@ import { getTrailStore } from './TrailStore/TrailStore';
import { MetricDatasourceHelper } from './helpers/MetricDatasourceHelper';
import { reportChangeInLabelFilters } from './interactions';
import { MetricSelectedEvent, trailDS, VAR_DATASOURCE, VAR_FILTERS } from './shared';
import { getMetricName } from './utils';
export interface DataTrailState extends SceneObjectState {
topScene?: SceneObject;
@ -93,21 +95,11 @@ export class DataTrail extends SceneObjectBase<DataTrailState> {
);
}
// Disconnects the current step history state from the current state, to prevent changes affecting history state
const currentState = this.state.history.state.steps[this.state.history.state.currentStep]?.trailState;
if (currentState) {
this.restoreFromHistoryStep(currentState);
}
this.enableUrlSync();
// Save the current trail as a recent if the browser closes or reloads
const saveRecentTrail = () => getTrailStore().setRecentTrail(this);
window.addEventListener('unload', saveRecentTrail);
return () => {
this.disableUrlSync();
if (!this.state.embedded) {
saveRecentTrail();
}
@ -115,18 +107,6 @@ export class DataTrail extends SceneObjectBase<DataTrailState> {
};
}
private enableUrlSync() {
if (!this.state.embedded) {
getUrlSyncManager().initSync(this);
}
}
private disableUrlSync() {
if (!this.state.embedded) {
getUrlSyncManager().cleanUp(this);
}
}
protected _variableDependency = new VariableDependencyConfig(this, {
variableNames: [VAR_DATASOURCE],
onReferencedVariableValueChanged: (variable: SceneVariable) => {
@ -167,8 +147,6 @@ export class DataTrail extends SceneObjectBase<DataTrailState> {
}
public restoreFromHistoryStep(state: DataTrailState) {
this.disableUrlSync();
if (!state.topScene && !state.metric) {
// If the top scene for an is missing, correct it.
state.topScene = new MetricSelectScene({});
@ -184,8 +162,6 @@ export class DataTrail extends SceneObjectBase<DataTrailState> {
const urlState = getUrlSyncManager().getUrlState(this);
const fullUrl = urlUtil.renderUrl(locationService.getLocation().pathname, urlState);
locationService.replace(fullUrl);
this.enableUrlSync();
}
private _handleMetricSelectedEvent(evt: MetricSelectedEvent) {
@ -227,11 +203,12 @@ export class DataTrail extends SceneObjectBase<DataTrailState> {
}
static Component = ({ model }: SceneComponentProps<DataTrail>) => {
const { controls, topScene, history, settings } = model.useState();
const { controls, topScene, history, settings, metric } = model.useState();
const styles = useStyles2(getStyles);
const showHeaderForFirstTimeUsers = getTrailStore().recent.length < 2;
return (
<Page navId="explore/metrics" pageNav={{ text: getMetricName(metric) }} layout={PageLayoutType.Custom}>
<div className={styles.container}>
{showHeaderForFirstTimeUsers && <MetricsHeader />}
<history.Component model={history} />
@ -245,6 +222,7 @@ export class DataTrail extends SceneObjectBase<DataTrailState> {
)}
<div className={styles.body}>{topScene && <topScene.Component model={topScene} />}</div>
</div>
</Page>
);
};
}
@ -288,6 +266,8 @@ function getStyles(theme: GrafanaTheme2) {
gap: theme.spacing(1),
minHeight: '100%',
flexDirection: 'column',
background: theme.isLight ? theme.colors.background.primary : theme.colors.background.canvas,
padding: theme.spacing(2, 3, 2, 3),
}),
body: css({
flexGrow: 1,

@ -1,11 +1,9 @@
import { css } from '@emotion/css';
import React, { useEffect } from 'react';
import { Route, Switch } from 'react-router-dom';
import { GrafanaTheme2, PageLayoutType } from '@grafana/data';
import { PageLayoutType } from '@grafana/data';
import { locationService } from '@grafana/runtime';
import { SceneComponentProps, SceneObjectBase, SceneObjectState, getUrlSyncManager } from '@grafana/scenes';
import { useStyles2 } from '@grafana/ui';
import { SceneComponentProps, SceneObjectBase, SceneObjectState, UrlSyncContextProvider } from '@grafana/scenes';
import { Page } from 'app/core/components/Page/Page';
import { DataTrail } from './DataTrail';
@ -13,7 +11,7 @@ import { DataTrailsHome } from './DataTrailsHome';
import { MetricsHeader } from './MetricsHeader';
import { getTrailStore } from './TrailStore/TrailStore';
import { HOME_ROUTE, TRAILS_ROUTE } from './shared';
import { getMetricName, getUrlForTrail, newMetricsTrail } from './utils';
import { getUrlForTrail, newMetricsTrail } from './utils';
export interface DataTrailsAppState extends SceneObjectState {
trail: DataTrail;
@ -26,13 +24,12 @@ export class DataTrailsApp extends SceneObjectBase<DataTrailsAppState> {
}
goToUrlForTrail(trail: DataTrail) {
this.setState({ trail });
locationService.push(getUrlForTrail(trail));
this.setState({ trail });
}
static Component = ({ model }: SceneComponentProps<DataTrailsApp>) => {
const { trail, home } = model.useState();
const styles = useStyles2(getStyles);
return (
<Switch>
@ -50,21 +47,7 @@ export class DataTrailsApp extends SceneObjectBase<DataTrailsAppState> {
</Page>
)}
/>
<Route
exact={true}
path={TRAILS_ROUTE}
render={() => (
<Page
navId="explore/metrics"
pageNav={{ text: getMetricName(trail.state.metric) }}
layout={PageLayoutType.Custom}
>
<div className={styles.customPage}>
<DataTrailView trail={trail} />
</div>
</Page>
)}
/>
<Route exact={true} path={TRAILS_ROUTE} render={() => <DataTrailView trail={trail} />} />
</Switch>
);
};
@ -84,7 +67,11 @@ function DataTrailView({ trail }: { trail: DataTrail }) {
return null;
}
return <trail.Component model={trail} />;
return (
<UrlSyncContextProvider scene={trail}>
<trail.Component model={trail} />
</UrlSyncContextProvider>
);
}
let dataTrailsApp: DataTrailsApp;
@ -92,50 +79,10 @@ let dataTrailsApp: DataTrailsApp;
export function getDataTrailsApp() {
if (!dataTrailsApp) {
dataTrailsApp = new DataTrailsApp({
trail: getInitialTrail(),
trail: newMetricsTrail(),
home: new DataTrailsHome({}),
});
}
return dataTrailsApp;
}
/**
* Get the initial trail for the app to work with based on the current URL
*
* It will either be a new trail that will be started based on the state represented
* in the URL parameters, or it will be the most recently used trail (according to the trail store)
* which has its current history step matching the URL parameters.
*
* The reason for trying to reinitialize from the recent trail is to resolve an issue
* where refreshing the browser would wipe the step history. This allows you to preserve
* it between browser refreshes, or when reaccessing the same URL.
*/
function getInitialTrail() {
const newTrail = newMetricsTrail();
// Set the initial state of the newTrail based on the URL,
// In case we are initializing from an externally created URL or a page reload
getUrlSyncManager().initSync(newTrail);
// Remove the URL sync for now. It will be restored on the trail if it is activated.
getUrlSyncManager().cleanUp(newTrail);
// If one of the recent trails is a match to the newTrail derived from the current URL,
// let's restore that trail so that a page refresh doesn't create a new trail.
const recentMatchingTrail = getTrailStore().findMatchingRecentTrail(newTrail)?.resolve();
// If there is a matching trail, initialize with that. Otherwise, use the new trail.
return recentMatchingTrail || newTrail;
}
function getStyles(theme: GrafanaTheme2) {
return {
customPage: css({
padding: theme.spacing(2, 3, 2, 3),
background: theme.isLight ? theme.colors.background.primary : theme.colors.background.canvas,
flexGrow: 1,
display: 'flex',
flexDirection: 'column',
}),
};
}

@ -349,6 +349,7 @@ describe('TrailStore', () => {
describe('And time range is changed to now-15m to now', () => {
let trail: DataTrail;
beforeEach(() => {
localStorage.clear();
localStorage.setItem(RECENT_TRAILS_KEY, JSON.stringify([{ history, currentStep: 1 }]));
@ -357,6 +358,7 @@ describe('TrailStore', () => {
trail = store.recent[0].resolve();
const urlState = getUrlSyncManager().getUrlState(trail);
locationService.partial(urlState);
trail.activate();
trail.state.history.activate();
trail.state.$timeRange?.setState({ from: 'now-15m' });

@ -1,5 +1,6 @@
import { debounce, isEqual } from 'lodash';
import { urlUtil } from '@grafana/data';
import { getUrlSyncManager, SceneObject, SceneObjectRef, SceneObjectUrlValues, sceneUtils } from '@grafana/scenes';
import { dispatch } from 'app/store/store';
@ -77,9 +78,14 @@ export class TrailStore {
});
const currentStep = t.currentStep ?? trail.state.history.state.steps.length - 1;
trail.state.history.setState({ currentStep });
// The state change listeners aren't activated yet, so maually change to the current step state
trail.setState(trail.state.history.state.steps[currentStep].trailState);
trail.setState(
sceneUtils.cloneSceneObjectState(trail.state.history.state.steps[currentStep].trailState, {
history: trail.state.history,
})
);
return trail;
}
@ -102,8 +108,8 @@ export class TrailStore {
}
private _loadFromUrl(node: SceneObject, urlValues: SceneObjectUrlValues) {
node.urlSync?.updateFromUrl(urlValues);
node.forEachChild((child) => this._loadFromUrl(child, urlValues));
const urlState = urlUtil.renderUrl('', urlValues);
sceneUtils.syncStateFromSearchParams(node, new URLSearchParams(urlState));
}
// Recent Trails
@ -140,14 +146,6 @@ export class TrailStore {
this._save();
}
findMatchingRecentTrail(trail: DataTrail) {
const matchUrlState = getUrlStateForComparison(trail);
return this._recent.find((t) => {
const urlState = getUrlStateForComparison(t.resolve());
return isEqual(matchUrlState, urlState);
});
}
// Bookmarked Trails
get bookmarks() {
return this._bookmarks;

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save