mirror of https://github.com/grafana/grafana.git
Schema v2: Reason about new dashboard based on UID (#98879)
* Schema v2: Reason about new dashboard based on UID * Fix test * Alerting: respect isNew dashboard for legacy and new arch * Translate untranslated strings * Unify is new checks * PanelInspectDrawer update * typo fix * on close test for panel inspect drawer * Update public/app/features/alerting/unified/PanelAlertTabContent.tsx
This commit is contained in:
parent
0003efa285
commit
8e078315f0
|
@ -1597,9 +1597,7 @@ exports[`better eslint`] = {
|
||||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
|
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
|
||||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"],
|
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"],
|
||||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "2"],
|
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "2"],
|
||||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"],
|
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"]
|
||||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"],
|
|
||||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "5"]
|
|
||||||
],
|
],
|
||||||
"public/app/features/alerting/unified/RedirectToRuleViewer.tsx:5381": [
|
"public/app/features/alerting/unified/RedirectToRuleViewer.tsx:5381": [
|
||||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
|
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
|
||||||
|
@ -3301,7 +3299,7 @@ exports[`better eslint`] = {
|
||||||
"public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataAlertingTab.tsx:5381": [
|
"public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataAlertingTab.tsx:5381": [
|
||||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
|
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
|
||||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"],
|
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"],
|
||||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
|
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "2"],
|
||||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"]
|
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"]
|
||||||
],
|
],
|
||||||
"public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataPane.tsx:5381": [
|
"public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataPane.tsx:5381": [
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { css } from '@emotion/css';
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
import { Alert, LoadingPlaceholder, ScrollContainer, useStyles2 } from '@grafana/ui';
|
import { Alert, LoadingPlaceholder, ScrollContainer, useStyles2 } from '@grafana/ui';
|
||||||
|
import { Trans } from 'app/core/internationalization';
|
||||||
import { contextSrv } from 'app/core/services/context_srv';
|
import { contextSrv } from 'app/core/services/context_srv';
|
||||||
import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
|
import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
|
||||||
import { PanelModel } from 'app/features/dashboard/state/PanelModel';
|
import { PanelModel } from 'app/features/dashboard/state/PanelModel';
|
||||||
|
@ -59,18 +60,26 @@ export const PanelAlertTabContent = ({ dashboard, panel }: Props) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isNew = !Boolean(dashboard.uid);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-testid={selectors.components.PanelAlertTabContent.content} className={styles.noRulesWrapper}>
|
<div data-testid={selectors.components.PanelAlertTabContent.content} className={styles.noRulesWrapper}>
|
||||||
{alert}
|
{alert}
|
||||||
{!!dashboard.uid && (
|
{!isNew && (
|
||||||
<>
|
<>
|
||||||
<p>There are no alert rules linked to this panel.</p>
|
<p>
|
||||||
|
<Trans i18nKey="dashboard.panel-edit.alerting-tab.no-rules">
|
||||||
|
There are no alert rules linked to this panel.
|
||||||
|
</Trans>
|
||||||
|
</p>
|
||||||
{!!dashboard.meta.canSave && canCreateRules && <NewRuleFromPanelButton panel={panel} dashboard={dashboard} />}
|
{!!dashboard.meta.canSave && canCreateRules && <NewRuleFromPanelButton panel={panel} dashboard={dashboard} />}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{!dashboard.uid && !!dashboard.meta.canSave && (
|
{isNew && !!dashboard.meta.canSave && (
|
||||||
<Alert severity="info" title="Dashboard not saved">
|
<Alert severity="info" title="Dashboard not saved">
|
||||||
Dashboard must be saved before alerts can be added.
|
<Trans i18nKey="dashboard.panel-edit.alerting-tab.dashboard-not-saved">
|
||||||
|
Dashboard must be saved before alerts can be added.
|
||||||
|
</Trans>
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -494,6 +494,8 @@ export function useCombinedRules(
|
||||||
result?: CombinedRuleNamespace[];
|
result?: CombinedRuleNamespace[];
|
||||||
error?: unknown;
|
error?: unknown;
|
||||||
} {
|
} {
|
||||||
|
const isNewDashboard = !Boolean(dashboardUID);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
currentData: promRuleNs,
|
currentData: promRuleNs,
|
||||||
isLoading: isLoadingPromRules,
|
isLoading: isLoadingPromRules,
|
||||||
|
@ -505,8 +507,7 @@ export function useCombinedRules(
|
||||||
panelId,
|
panelId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// "null" means the dashboard isn't saved yet, as opposed to "undefined" which means we don't want to filter by dashboard UID
|
skip: isNewDashboard,
|
||||||
skip: dashboardUID === null,
|
|
||||||
pollingInterval: poll ? RULE_LIST_POLL_INTERVAL_MS : undefined,
|
pollingInterval: poll ? RULE_LIST_POLL_INTERVAL_MS : undefined,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -522,7 +523,7 @@ export function useCombinedRules(
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pollingInterval: poll ? RULE_LIST_POLL_INTERVAL_MS : undefined,
|
pollingInterval: poll ? RULE_LIST_POLL_INTERVAL_MS : undefined,
|
||||||
skip: dashboardUID === null,
|
skip: isNewDashboard,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -54,7 +54,6 @@ export const AnnoKeySavedFromUI = 'grafana.app/saved-from-ui';
|
||||||
export const AnnoKeyDashboardNotFound = 'grafana.app/dashboard-not-found';
|
export const AnnoKeyDashboardNotFound = 'grafana.app/dashboard-not-found';
|
||||||
export const AnnoKeyDashboardIsSnapshot = 'grafana.app/dashboard-is-snapshot';
|
export const AnnoKeyDashboardIsSnapshot = 'grafana.app/dashboard-is-snapshot';
|
||||||
export const AnnoKeyDashboardSnapshotOriginalUrl = 'grafana.app/dashboard-snapshot-original-url';
|
export const AnnoKeyDashboardSnapshotOriginalUrl = 'grafana.app/dashboard-snapshot-original-url';
|
||||||
export const AnnoKeyDashboardIsNew = 'grafana.app/dashboard-is-new';
|
|
||||||
export const AnnoKeyDashboardGnetId = 'grafana.app/dashboard-gnet-id';
|
export const AnnoKeyDashboardGnetId = 'grafana.app/dashboard-gnet-id';
|
||||||
|
|
||||||
// Annotations provided by the API
|
// Annotations provided by the API
|
||||||
|
@ -83,7 +82,6 @@ type GrafanaClientAnnotations = {
|
||||||
[AnnoKeyDashboardNotFound]?: boolean;
|
[AnnoKeyDashboardNotFound]?: boolean;
|
||||||
[AnnoKeyDashboardIsSnapshot]?: boolean;
|
[AnnoKeyDashboardIsSnapshot]?: boolean;
|
||||||
[AnnoKeyDashboardSnapshotOriginalUrl]?: string;
|
[AnnoKeyDashboardSnapshotOriginalUrl]?: string;
|
||||||
[AnnoKeyDashboardIsNew]?: boolean;
|
|
||||||
|
|
||||||
// TODO: This should be provided by the API
|
// TODO: This should be provided by the API
|
||||||
// This is the dashboard ID for the Gcom API. This set when a dashboard is created through importing a dashboard from Grafana.com.
|
// This is the dashboard ID for the Gcom API. This set when a dashboard is created through importing a dashboard from Grafana.com.
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
import { locationService } from '@grafana/runtime';
|
||||||
|
|
||||||
|
import { DashboardScene, DashboardSceneState } from '../scene/DashboardScene';
|
||||||
|
import { DefaultGridLayoutManager } from '../scene/layout-default/DefaultGridLayoutManager';
|
||||||
|
|
||||||
|
import { onPanelInspectClose } from './PanelInspectDrawer';
|
||||||
|
|
||||||
|
describe('onPanelInspectClose', () => {
|
||||||
|
test('when on default home dashboard page', async () => {
|
||||||
|
locationService.push('/');
|
||||||
|
|
||||||
|
const { scene } = await buildTestScene({
|
||||||
|
url: '',
|
||||||
|
slug: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
onPanelInspectClose(scene);
|
||||||
|
expect(locationService.getLocation().pathname).toBe('/');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('when on custom home dashboard page with uid defined', async () => {
|
||||||
|
locationService.push('/');
|
||||||
|
|
||||||
|
const { scene } = await buildTestScene(
|
||||||
|
{
|
||||||
|
url: '',
|
||||||
|
slug: '',
|
||||||
|
},
|
||||||
|
'home-dash '
|
||||||
|
);
|
||||||
|
|
||||||
|
onPanelInspectClose(scene);
|
||||||
|
expect(locationService.getLocation().pathname).toBe('/');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('when on new dashboard page', async () => {
|
||||||
|
locationService.push('/dashboard/new');
|
||||||
|
const { scene } = await buildTestScene(
|
||||||
|
{
|
||||||
|
url: '',
|
||||||
|
slug: '',
|
||||||
|
},
|
||||||
|
''
|
||||||
|
);
|
||||||
|
|
||||||
|
onPanelInspectClose(scene);
|
||||||
|
expect(locationService.getLocation().pathname).toBe('/dashboard/new');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('when on a dashboard page', async () => {
|
||||||
|
const { scene } = await buildTestScene(
|
||||||
|
{
|
||||||
|
slug: 'dash-slug',
|
||||||
|
url: '/d/dash-uid/dash-slug',
|
||||||
|
},
|
||||||
|
'dash-uid'
|
||||||
|
);
|
||||||
|
|
||||||
|
onPanelInspectClose(scene);
|
||||||
|
expect(locationService.getLocation().pathname).toBe('/d/dash-uid/dash-slug');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
async function buildTestScene(metaOverride?: DashboardSceneState['meta'], uid = 'dash-1') {
|
||||||
|
const scene = new DashboardScene({
|
||||||
|
title: 'hello',
|
||||||
|
uid,
|
||||||
|
meta: {
|
||||||
|
canEdit: true,
|
||||||
|
...metaOverride,
|
||||||
|
},
|
||||||
|
body: DefaultGridLayoutManager.fromVizPanels([]),
|
||||||
|
});
|
||||||
|
|
||||||
|
return { scene };
|
||||||
|
}
|
|
@ -15,6 +15,7 @@ import { getDataSourceWithInspector } from 'app/features/dashboard/components/In
|
||||||
import { supportsDataQuery } from 'app/features/dashboard/components/PanelEditor/utils';
|
import { supportsDataQuery } from 'app/features/dashboard/components/PanelEditor/utils';
|
||||||
import { InspectTab } from 'app/features/inspector/types';
|
import { InspectTab } from 'app/features/inspector/types';
|
||||||
|
|
||||||
|
import { DashboardScene } from '../scene/DashboardScene';
|
||||||
import { getDashboardUrl } from '../utils/getDashboardUrl';
|
import { getDashboardUrl } from '../utils/getDashboardUrl';
|
||||||
import { getDashboardSceneFor } from '../utils/utils';
|
import { getDashboardSceneFor } from '../utils/utils';
|
||||||
|
|
||||||
|
@ -91,21 +92,7 @@ export class PanelInspectDrawer extends SceneObjectBase<PanelInspectDrawerState>
|
||||||
}
|
}
|
||||||
|
|
||||||
onClose = () => {
|
onClose = () => {
|
||||||
const dashboard = getDashboardSceneFor(this);
|
onPanelInspectClose(getDashboardSceneFor(this));
|
||||||
const meta = dashboard.state.meta;
|
|
||||||
|
|
||||||
locationService.push(
|
|
||||||
getDashboardUrl({
|
|
||||||
uid: dashboard.state.uid,
|
|
||||||
slug: dashboard.state.meta.slug,
|
|
||||||
currentQueryParams: locationService.getLocation().search,
|
|
||||||
updateQuery: {
|
|
||||||
inspect: null,
|
|
||||||
inspectTab: null,
|
|
||||||
},
|
|
||||||
isHomeDashboard: !meta.url && !meta.slug && !meta.isNew,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,3 +143,23 @@ function PanelInspectRenderer({ model }: SceneComponentProps<PanelInspectDrawer>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function onPanelInspectClose(dashboard: DashboardScene) {
|
||||||
|
const meta = dashboard.state.meta;
|
||||||
|
// Checking for location here as well, otherwise down below isHomeDashboard will be set to true
|
||||||
|
// as it doesn't have uid neither slug nor url.
|
||||||
|
const isNew = !dashboard.state.uid && locationService.getLocation().pathname === '/dashboard/new';
|
||||||
|
|
||||||
|
locationService.push(
|
||||||
|
getDashboardUrl({
|
||||||
|
uid: dashboard.state.uid,
|
||||||
|
slug: dashboard.state.meta.slug,
|
||||||
|
currentQueryParams: locationService.getLocation().search,
|
||||||
|
updateQuery: {
|
||||||
|
inspect: null,
|
||||||
|
inspectTab: null,
|
||||||
|
},
|
||||||
|
isHomeDashboard: !meta.url && !meta.slug && !isNew,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -133,13 +133,12 @@ describe('DashboardScenePageStateManager v1', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('New dashboards', () => {
|
describe('New dashboards', () => {
|
||||||
it('Should have new empty model with meta.isNew and should not be cached', async () => {
|
it('Should have new empty model and should not be cached', async () => {
|
||||||
const loader = new DashboardScenePageStateManager({});
|
const loader = new DashboardScenePageStateManager({});
|
||||||
|
|
||||||
await loader.loadDashboard({ uid: '', route: DashboardRoutes.New });
|
await loader.loadDashboard({ uid: '', route: DashboardRoutes.New });
|
||||||
const dashboard = loader.state.dashboard!;
|
const dashboard = loader.state.dashboard!;
|
||||||
|
|
||||||
expect(dashboard.state.meta.isNew).toBe(true);
|
|
||||||
expect(dashboard.state.isEditing).toBe(undefined);
|
expect(dashboard.state.isEditing).toBe(undefined);
|
||||||
expect(dashboard.state.isDirty).toBe(false);
|
expect(dashboard.state.isDirty).toBe(false);
|
||||||
|
|
||||||
|
@ -431,13 +430,12 @@ describe('DashboardScenePageStateManager v2', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('New dashboards', () => {
|
describe('New dashboards', () => {
|
||||||
it('Should have new empty model with meta.isNew and should not be cached', async () => {
|
it('Should have new empty model and should not be cached', async () => {
|
||||||
const loader = new DashboardScenePageStateManagerV2({});
|
const loader = new DashboardScenePageStateManagerV2({});
|
||||||
|
|
||||||
await loader.loadDashboard({ uid: '', route: DashboardRoutes.New });
|
await loader.loadDashboard({ uid: '', route: DashboardRoutes.New });
|
||||||
const dashboard = loader.state.dashboard!;
|
const dashboard = loader.state.dashboard!;
|
||||||
|
|
||||||
expect(dashboard.state.meta.isNew).toBe(true);
|
|
||||||
expect(dashboard.state.isEditing).toBe(undefined);
|
expect(dashboard.state.isEditing).toBe(undefined);
|
||||||
expect(dashboard.state.isDirty).toBe(false);
|
expect(dashboard.state.isDirty).toBe(false);
|
||||||
|
|
||||||
|
|
|
@ -237,7 +237,7 @@ describe('PanelAlertTabContent', () => {
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
renderAlertTab(dashboard);
|
renderAlertTab(dashboard, dashboard);
|
||||||
|
|
||||||
const defaults = await clickNewButton();
|
const defaults = await clickNewButton();
|
||||||
|
|
||||||
|
@ -264,7 +264,7 @@ describe('PanelAlertTabContent', () => {
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
renderAlertTab(dashboard);
|
renderAlertTab(dashboard, dashboard);
|
||||||
const defaults = await clickNewButton();
|
const defaults = await clickNewButton();
|
||||||
|
|
||||||
expect(defaults.queries[0].model).toEqual({
|
expect(defaults.queries[0].model).toEqual({
|
||||||
|
@ -290,7 +290,7 @@ describe('PanelAlertTabContent', () => {
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
renderAlertTab(dashboard);
|
renderAlertTab(dashboard, dashboard);
|
||||||
const defaults = await clickNewButton();
|
const defaults = await clickNewButton();
|
||||||
|
|
||||||
expect(defaults.queries[0].model).toEqual({
|
expect(defaults.queries[0].model).toEqual({
|
||||||
|
@ -310,7 +310,7 @@ describe('PanelAlertTabContent', () => {
|
||||||
it('Will render alerts belonging to panel and a button to create alert from panel queries', async () => {
|
it('Will render alerts belonging to panel and a button to create alert from panel queries', async () => {
|
||||||
dashboard.panels = [panel];
|
dashboard.panels = [panel];
|
||||||
|
|
||||||
renderAlertTab(dashboard);
|
renderAlertTab(dashboard, dashboard);
|
||||||
|
|
||||||
const rows = await ui.row.findAll();
|
const rows = await ui.row.findAll();
|
||||||
expect(rows).toHaveLength(2);
|
expect(rows).toHaveLength(2);
|
||||||
|
@ -334,8 +334,8 @@ describe('PanelAlertTabContent', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function renderAlertTab(dashboard: DashboardModel) {
|
function renderAlertTab(dashboard: DashboardModel, dto: DashboardDataDTO) {
|
||||||
const model = createModel(dashboard);
|
const model = createModel(dashboard, dto);
|
||||||
renderAlertTabContent(model);
|
renderAlertTabContent(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -353,8 +353,8 @@ async function clickNewButton() {
|
||||||
return defaults;
|
return defaults;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createModel(dashboard: DashboardModel) {
|
function createModel(dashboard: DashboardModel, dto: DashboardDataDTO) {
|
||||||
const scene = createDashboardSceneFromDashboardModel(dashboard, {} as DashboardDataDTO);
|
const scene = createDashboardSceneFromDashboardModel(dashboard, dto);
|
||||||
const vizPanel = findVizPanelByKey(scene, getVizPanelKeyForPanelId(34))!;
|
const vizPanel = findVizPanelByKey(scene, getVizPanelKeyForPanelId(34))!;
|
||||||
const model = new PanelDataAlertingTab({ panelRef: vizPanel.getRef() });
|
const model = new PanelDataAlertingTab({ panelRef: vizPanel.getRef() });
|
||||||
jest.spyOn(utils, 'getDashboardSceneFor').mockReturnValue(scene);
|
jest.spyOn(utils, 'getDashboardSceneFor').mockReturnValue(scene);
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { SceneComponentProps, SceneObjectBase, SceneObjectRef, SceneObjectState, VizPanel } from '@grafana/scenes';
|
import { SceneComponentProps, SceneObjectBase, SceneObjectRef, SceneObjectState, VizPanel } from '@grafana/scenes';
|
||||||
import { Alert, LoadingPlaceholder, Tab, useStyles2 } from '@grafana/ui';
|
import { Alert, LoadingPlaceholder, Tab, useStyles2 } from '@grafana/ui';
|
||||||
import { contextSrv } from 'app/core/core';
|
import { contextSrv } from 'app/core/core';
|
||||||
|
import { Trans } from 'app/core/internationalization';
|
||||||
import { RulesTable } from 'app/features/alerting/unified/components/rules/RulesTable';
|
import { RulesTable } from 'app/features/alerting/unified/components/rules/RulesTable';
|
||||||
import { usePanelCombinedRules } from 'app/features/alerting/unified/hooks/usePanelCombinedRules';
|
import { usePanelCombinedRules } from 'app/features/alerting/unified/hooks/usePanelCombinedRules';
|
||||||
import { getRulesPermissions } from 'app/features/alerting/unified/utils/access-control';
|
import { getRulesPermissions } from 'app/features/alerting/unified/utils/access-control';
|
||||||
|
@ -86,10 +87,28 @@ export function PanelDataAlertingTabRendered({ model }: SceneComponentProps<Pane
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isNew = !Boolean(model.getDashboardUID());
|
||||||
|
const dashboard = model.getDashboard();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.noRulesWrapper}>
|
<div className={styles.noRulesWrapper}>
|
||||||
<p>There are no alert rules linked to this panel.</p>
|
{!isNew && (
|
||||||
{canCreateRules && <ScenesNewRuleFromPanelButton panel={panel}></ScenesNewRuleFromPanelButton>}
|
<>
|
||||||
|
<p>
|
||||||
|
<Trans i18nKey="dashboard.panel-edit.alerting-tab.no-rules">
|
||||||
|
There are no alert rules linked to this panel.
|
||||||
|
</Trans>
|
||||||
|
</p>
|
||||||
|
{canCreateRules && <ScenesNewRuleFromPanelButton panel={panel}></ScenesNewRuleFromPanelButton>}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{isNew && !!dashboard.state.meta.canSave && (
|
||||||
|
<Alert severity="info" title="Dashboard not saved">
|
||||||
|
<Trans i18nKey="dashboard.panel-edit.alerting-tab.dashboard-not-saved">
|
||||||
|
Dashboard must be saved before alerts can be added.
|
||||||
|
</Trans>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,27 +110,6 @@ describe('DashboardScene', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Given new dashboard in edit mode', () => {
|
|
||||||
it('when saving it should clear isNew state', () => {
|
|
||||||
const scene = buildTestScene({
|
|
||||||
meta: { isNew: true },
|
|
||||||
});
|
|
||||||
|
|
||||||
scene.activate();
|
|
||||||
scene.onEnterEditMode();
|
|
||||||
scene.saveCompleted({} as Dashboard, {
|
|
||||||
id: 1,
|
|
||||||
slug: 'slug',
|
|
||||||
uid: 'dash-1',
|
|
||||||
url: 'sss',
|
|
||||||
version: 2,
|
|
||||||
status: 'aaa',
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(scene.state.meta.isNew).toBeFalsy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Given scene in edit mode', () => {
|
describe('Given scene in edit mode', () => {
|
||||||
let scene: DashboardScene;
|
let scene: DashboardScene;
|
||||||
let deactivateScene: () => void;
|
let deactivateScene: () => void;
|
||||||
|
|
|
@ -114,7 +114,7 @@ export interface DashboardSceneState extends SceneObjectState {
|
||||||
/** True when user made a change */
|
/** True when user made a change */
|
||||||
isDirty?: boolean;
|
isDirty?: boolean;
|
||||||
/** meta flags */
|
/** meta flags */
|
||||||
meta: DashboardMeta;
|
meta: Omit<DashboardMeta, 'isNew'>;
|
||||||
/** Version of the dashboard */
|
/** Version of the dashboard */
|
||||||
version?: number;
|
version?: number;
|
||||||
/** Panel to inspect */
|
/** Panel to inspect */
|
||||||
|
@ -202,6 +202,7 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
||||||
|
|
||||||
private _activationHandler() {
|
private _activationHandler() {
|
||||||
let prevSceneContext = window.__grafanaSceneContext;
|
let prevSceneContext = window.__grafanaSceneContext;
|
||||||
|
const isNew = locationService.getLocation().pathname === '/dashboard/new';
|
||||||
|
|
||||||
window.__grafanaSceneContext = this;
|
window.__grafanaSceneContext = this;
|
||||||
|
|
||||||
|
@ -212,7 +213,7 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
||||||
this._changeTracker.startTrackingChanges();
|
this._changeTracker.startTrackingChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.meta.isNew) {
|
if (isNew) {
|
||||||
this.onEnterEditMode();
|
this.onEnterEditMode();
|
||||||
this.setState({ isDirty: true });
|
this.setState({ isDirty: true });
|
||||||
}
|
}
|
||||||
|
@ -286,7 +287,6 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
||||||
url: result.url,
|
url: result.url,
|
||||||
slug: result.slug,
|
slug: result.slug,
|
||||||
folderUid: folderUid,
|
folderUid: folderUid,
|
||||||
isNew: false,
|
|
||||||
version: result.version,
|
version: result.version,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -417,6 +417,7 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
||||||
|
|
||||||
public getPageNav(location: H.Location, navIndex: NavIndex) {
|
public getPageNav(location: H.Location, navIndex: NavIndex) {
|
||||||
const { meta, viewPanelScene, editPanel, title, uid } = this.state;
|
const { meta, viewPanelScene, editPanel, title, uid } = this.state;
|
||||||
|
const isNew = !Boolean(uid);
|
||||||
|
|
||||||
if (meta.dashboardNotFound) {
|
if (meta.dashboardNotFound) {
|
||||||
return { text: 'Not found' };
|
return { text: 'Not found' };
|
||||||
|
@ -429,7 +430,7 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
||||||
slug: meta.slug,
|
slug: meta.slug,
|
||||||
currentQueryParams: location.search,
|
currentQueryParams: location.search,
|
||||||
updateQuery: { viewPanel: null, inspect: null, editview: null, editPanel: null, tab: null, shareView: null },
|
updateQuery: { viewPanel: null, inspect: null, editview: null, editPanel: null, tab: null, shareView: null },
|
||||||
isHomeDashboard: !meta.url && !meta.slug && !meta.isNew && !meta.isSnapshot,
|
isHomeDashboard: !meta.url && !meta.slug && !isNew && !meta.isSnapshot,
|
||||||
isSnapshot: meta.isSnapshot,
|
isSnapshot: meta.isSnapshot,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
|
@ -71,6 +71,8 @@ export function ToolbarActions({ dashboard }: Props) {
|
||||||
const isEditedPanelDirty = usePanelEditDirty(editPanel);
|
const isEditedPanelDirty = usePanelEditDirty(editPanel);
|
||||||
const isEditingLibraryPanel = editPanel && isLibraryPanel(editPanel.state.panelRef.resolve());
|
const isEditingLibraryPanel = editPanel && isLibraryPanel(editPanel.state.panelRef.resolve());
|
||||||
const isNotFound = Boolean(meta.dashboardNotFound);
|
const isNotFound = Boolean(meta.dashboardNotFound);
|
||||||
|
const isNew = !Boolean(uid);
|
||||||
|
|
||||||
const hasCopiedPanel = store.exists(LS_PANEL_COPY_KEY);
|
const hasCopiedPanel = store.exists(LS_PANEL_COPY_KEY);
|
||||||
// Means we are not in settings view, fullscreen panel or edit panel
|
// Means we are not in settings view, fullscreen panel or edit panel
|
||||||
const isShowingDashboard = !editview && !isViewingPanel && !isEditingPanel;
|
const isShowingDashboard = !editview && !isViewingPanel && !isEditingPanel;
|
||||||
|
@ -489,7 +491,7 @@ export function ToolbarActions({ dashboard }: Props) {
|
||||||
|
|
||||||
toolbarActions.push({
|
toolbarActions.push({
|
||||||
group: 'main-buttons',
|
group: 'main-buttons',
|
||||||
condition: isEditing && !meta.isNew && isShowingDashboard,
|
condition: isEditing && !isNew && isShowingDashboard,
|
||||||
render: () => (
|
render: () => (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => dashboard.exitEditMode({ skipConfirm: false })}
|
onClick={() => dashboard.exitEditMode({ skipConfirm: false })}
|
||||||
|
@ -583,7 +585,7 @@ export function ToolbarActions({ dashboard }: Props) {
|
||||||
condition: isEditing && !isEditingLibraryPanel && (meta.canSave || canSaveAs),
|
condition: isEditing && !isEditingLibraryPanel && (meta.canSave || canSaveAs),
|
||||||
render: () => {
|
render: () => {
|
||||||
// if we only can save
|
// if we only can save
|
||||||
if (meta.isNew) {
|
if (isNew) {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
|
@ -155,7 +155,7 @@ export class V2DashboardSerializer
|
||||||
);
|
);
|
||||||
|
|
||||||
const hasFolderChanges = scene.getInitialState()?.meta.folderUid !== scene.state.meta.folderUid;
|
const hasFolderChanges = scene.getInitialState()?.meta.folderUid !== scene.state.meta.folderUid;
|
||||||
const isNew = scene.getInitialState()?.meta.isNew;
|
const isNew = !Boolean(scene.getInitialState()?.uid);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...changeInfo,
|
...changeInfo,
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
defaultTimeSettingsSpec,
|
defaultTimeSettingsSpec,
|
||||||
GroupByVariableKind,
|
GroupByVariableKind,
|
||||||
} from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0';
|
} from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0';
|
||||||
import { AnnoKeyDashboardIsNew, AnnoKeyFolder } from 'app/features/apiserver/types';
|
import { AnnoKeyFolder } from 'app/features/apiserver/types';
|
||||||
import { DashboardWithAccessInfo } from 'app/features/dashboard/api/types';
|
import { DashboardWithAccessInfo } from 'app/features/dashboard/api/types';
|
||||||
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||||
import { DashboardDTO } from 'app/types';
|
import { DashboardDTO } from 'app/types';
|
||||||
|
@ -129,7 +129,6 @@ export async function buildNewDashboardSaveModelV2(
|
||||||
creationTimestamp: '0',
|
creationTimestamp: '0',
|
||||||
annotations: {
|
annotations: {
|
||||||
[AnnoKeyFolder]: '',
|
[AnnoKeyFolder]: '',
|
||||||
[AnnoKeyDashboardIsNew]: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -26,7 +26,6 @@ import {
|
||||||
TextVariableKind,
|
TextVariableKind,
|
||||||
} from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0';
|
} from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0';
|
||||||
import { handyTestingSchema } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0/examples';
|
import { handyTestingSchema } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0/examples';
|
||||||
import { AnnoKeyDashboardIsNew } from 'app/features/apiserver/types';
|
|
||||||
import { DashboardWithAccessInfo } from 'app/features/dashboard/api/types';
|
import { DashboardWithAccessInfo } from 'app/features/dashboard/api/types';
|
||||||
import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource';
|
import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource';
|
||||||
|
|
||||||
|
@ -490,26 +489,5 @@ describe('transformSaveModelSchemaV2ToScene', () => {
|
||||||
expect(scene.state.meta.canDelete).toBe(true);
|
expect(scene.state.meta.canDelete).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('is new dashboard handling', () => {
|
|
||||||
it('handles undefined is new dashbaord annotation', () => {
|
|
||||||
const scene = transformSaveModelSchemaV2ToScene(defaultDashboard);
|
|
||||||
expect(scene.state.meta.isNew).toBe(false);
|
|
||||||
});
|
|
||||||
it('handles defined is new dashbaord annotation', () => {
|
|
||||||
const dashboard: DashboardWithAccessInfo<DashboardV2Spec> = {
|
|
||||||
...defaultDashboard,
|
|
||||||
metadata: {
|
|
||||||
...defaultDashboard.metadata,
|
|
||||||
annotations: {
|
|
||||||
...defaultDashboard.metadata.annotations,
|
|
||||||
[AnnoKeyDashboardIsNew]: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const scene = transformSaveModelSchemaV2ToScene(dashboard);
|
|
||||||
expect(scene.state.meta.isNew).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -59,7 +59,6 @@ import {
|
||||||
AnnoKeyFolder,
|
AnnoKeyFolder,
|
||||||
AnnoKeyUpdatedBy,
|
AnnoKeyUpdatedBy,
|
||||||
AnnoKeyUpdatedTimestamp,
|
AnnoKeyUpdatedTimestamp,
|
||||||
AnnoKeyDashboardIsNew,
|
|
||||||
AnnoKeyDashboardIsSnapshot,
|
AnnoKeyDashboardIsSnapshot,
|
||||||
} from 'app/features/apiserver/types';
|
} from 'app/features/apiserver/types';
|
||||||
import { DashboardWithAccessInfo } from 'app/features/dashboard/api/types';
|
import { DashboardWithAccessInfo } from 'app/features/dashboard/api/types';
|
||||||
|
@ -151,7 +150,6 @@ export function transformSaveModelSchemaV2ToScene(dto: DashboardWithAccessInfo<D
|
||||||
hasUnsavedFolderChange: false,
|
hasUnsavedFolderChange: false,
|
||||||
dashboardNotFound: Boolean(dto.metadata.annotations?.[AnnoKeyDashboardNotFound]),
|
dashboardNotFound: Boolean(dto.metadata.annotations?.[AnnoKeyDashboardNotFound]),
|
||||||
version: parseInt(metadata.resourceVersion, 10),
|
version: parseInt(metadata.resourceVersion, 10),
|
||||||
isNew: Boolean(dto.metadata.annotations?.[AnnoKeyDashboardIsNew]),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Ref: DashboardModel.initMeta
|
// Ref: DashboardModel.initMeta
|
||||||
|
|
|
@ -175,6 +175,7 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel,
|
||||||
let variables: SceneVariableSet | undefined;
|
let variables: SceneVariableSet | undefined;
|
||||||
let annotationLayers: SceneDataLayerProvider[] = [];
|
let annotationLayers: SceneDataLayerProvider[] = [];
|
||||||
let alertStatesLayer: AlertStatesDataLayer | undefined;
|
let alertStatesLayer: AlertStatesDataLayer | undefined;
|
||||||
|
const uid = dto.uid;
|
||||||
|
|
||||||
if (oldModel.templating?.list?.length) {
|
if (oldModel.templating?.list?.length) {
|
||||||
if (oldModel.meta.isSnapshot) {
|
if (oldModel.meta.isSnapshot) {
|
||||||
|
@ -228,15 +229,16 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel,
|
||||||
addPanelsOnLoadBehavior,
|
addPanelsOnLoadBehavior,
|
||||||
new DashboardScopesFacade({
|
new DashboardScopesFacade({
|
||||||
reloadOnParamsChange: config.featureToggles.reloadDashboardsOnParamsChange && oldModel.meta.reloadOnParamsChange,
|
reloadOnParamsChange: config.featureToggles.reloadDashboardsOnParamsChange && oldModel.meta.reloadOnParamsChange,
|
||||||
uid: oldModel.uid,
|
uid,
|
||||||
}),
|
}),
|
||||||
new DashboardReloadBehavior({
|
new DashboardReloadBehavior({
|
||||||
reloadOnParamsChange: config.featureToggles.reloadDashboardsOnParamsChange && oldModel.meta.reloadOnParamsChange,
|
reloadOnParamsChange: config.featureToggles.reloadDashboardsOnParamsChange && oldModel.meta.reloadOnParamsChange,
|
||||||
uid: oldModel.uid,
|
uid,
|
||||||
version: oldModel.version,
|
version: oldModel.version,
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
const dashboardScene = new DashboardScene({
|
const dashboardScene = new DashboardScene({
|
||||||
|
uid,
|
||||||
description: oldModel.description,
|
description: oldModel.description,
|
||||||
editable: oldModel.editable,
|
editable: oldModel.editable,
|
||||||
preload: dto.preload ?? false,
|
preload: dto.preload ?? false,
|
||||||
|
@ -246,7 +248,6 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel,
|
||||||
meta: oldModel.meta,
|
meta: oldModel.meta,
|
||||||
tags: oldModel.tags || [],
|
tags: oldModel.tags || [],
|
||||||
title: oldModel.title,
|
title: oldModel.title,
|
||||||
uid: oldModel.uid,
|
|
||||||
version: oldModel.version,
|
version: oldModel.version,
|
||||||
body: new DefaultGridLayoutManager({
|
body: new DefaultGridLayoutManager({
|
||||||
grid: new SceneGridLayout({
|
grid: new SceneGridLayout({
|
||||||
|
|
|
@ -944,6 +944,12 @@
|
||||||
"rows": "Total number rows",
|
"rows": "Total number rows",
|
||||||
"table-title": "Stats"
|
"table-title": "Stats"
|
||||||
},
|
},
|
||||||
|
"panel-edit": {
|
||||||
|
"alerting-tab": {
|
||||||
|
"dashboard-not-saved": "Dashboard must be saved before alerts can be added.",
|
||||||
|
"no-rules": "There are no alert rules linked to this panel."
|
||||||
|
}
|
||||||
|
},
|
||||||
"toolbar": {
|
"toolbar": {
|
||||||
"add": "Add",
|
"add": "Add",
|
||||||
"alert-rules": "Alert rules",
|
"alert-rules": "Alert rules",
|
||||||
|
|
|
@ -944,6 +944,12 @@
|
||||||
"rows": "Ŧőŧäľ ʼnūmþęř řőŵş",
|
"rows": "Ŧőŧäľ ʼnūmþęř řőŵş",
|
||||||
"table-title": "Ŝŧäŧş"
|
"table-title": "Ŝŧäŧş"
|
||||||
},
|
},
|
||||||
|
"panel-edit": {
|
||||||
|
"alerting-tab": {
|
||||||
|
"dashboard-not-saved": "Đäşĥþőäřđ mūşŧ þę şävęđ þęƒőřę äľęřŧş čäʼn þę äđđęđ.",
|
||||||
|
"no-rules": "Ŧĥęřę äřę ʼnő äľęřŧ řūľęş ľįʼnĸęđ ŧő ŧĥįş päʼnęľ."
|
||||||
|
}
|
||||||
|
},
|
||||||
"toolbar": {
|
"toolbar": {
|
||||||
"add": "Åđđ",
|
"add": "Åđđ",
|
||||||
"alert-rules": "Åľęřŧ řūľęş",
|
"alert-rules": "Åľęřŧ řūľęş",
|
||||||
|
|
Loading…
Reference in New Issue