grafana/public/app/features/trails/DataTrailsApp.tsx
Darren Janeczek 137061d1d5
datatrails: fix stability issues between conflict between browser history, URL sync, and trail history (#85134)
* fix: conflict between browser history and trail history

- ensure the back button or url changes don't generate trail steps
- ensure label breakdown TextLinks which appear on the summary tab work in embedded mode
2024-03-28 09:59:16 -04:00

122 lines
3.6 KiB
TypeScript

import { css } from '@emotion/css';
import React, { useEffect } from 'react';
import { Route, Switch } from 'react-router-dom';
import { GrafanaTheme2, PageLayoutType } from '@grafana/data';
import { locationService } from '@grafana/runtime';
import { SceneComponentProps, SceneObjectBase, SceneObjectState, getUrlSyncManager } from '@grafana/scenes';
import { useStyles2 } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
import { DataTrail } from './DataTrail';
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';
export interface DataTrailsAppState extends SceneObjectState {
trail: DataTrail;
home: DataTrailsHome;
}
export class DataTrailsApp extends SceneObjectBase<DataTrailsAppState> {
public constructor(state: DataTrailsAppState) {
super(state);
}
goToUrlForTrail(trail: DataTrail) {
this.setState({ trail });
locationService.push(getUrlForTrail(trail));
}
static Component = ({ model }: SceneComponentProps<DataTrailsApp>) => {
const { trail, home } = model.useState();
const styles = useStyles2(getStyles);
return (
<Switch>
<Route
exact={true}
path={HOME_ROUTE}
render={() => (
<Page
navId="explore/metrics"
layout={PageLayoutType.Standard}
renderTitle={() => <MetricsHeader />}
subTitle=""
>
<home.Component model={home} />
</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>
)}
/>
</Switch>
);
};
}
function DataTrailView({ trail }: { trail: DataTrail }) {
const [isInitialized, setIsInitialized] = React.useState(false);
useEffect(() => {
if (!isInitialized) {
// Set the initial state based on the URL.
getUrlSyncManager().initSync(trail);
// Any further changes to the state should occur directly to the state, not through the URL.
// We want to stop automatically syncing the URL state (and vice versa) to the trail after this point.
// Moving forward in the lifecycle of the trail, we will make explicit calls to trail.syncTrailToUrl()
// so we can ensure the URL is kept up to date at key points.
getUrlSyncManager().cleanUp(trail);
getTrailStore().setRecentTrail(trail);
setIsInitialized(true);
}
}, [trail, isInitialized]);
if (!isInitialized) {
return null;
}
return <trail.Component model={trail} />;
}
let dataTrailsApp: DataTrailsApp;
export function getDataTrailsApp() {
if (!dataTrailsApp) {
dataTrailsApp = new DataTrailsApp({
trail: newMetricsTrail(),
home: new DataTrailsHome({}),
});
}
return dataTrailsApp;
}
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',
}),
};
}