diff --git a/.betterer.results b/.betterer.results index a07a231954f..28b253c9993 100644 --- a/.betterer.results +++ b/.betterer.results @@ -1597,9 +1597,7 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "3"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "4"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "5"] + [0, 0, 0, "No untranslated strings. Wrap text with ", "3"] ], "public/app/features/alerting/unified/RedirectToRuleViewer.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], @@ -3301,7 +3299,7 @@ exports[`better eslint`] = { "public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataAlertingTab.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "2"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], [0, 0, 0, "No untranslated strings. Wrap text with ", "3"] ], "public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataPane.tsx:5381": [ diff --git a/public/app/features/alerting/unified/PanelAlertTabContent.tsx b/public/app/features/alerting/unified/PanelAlertTabContent.tsx index 0bd2a1e7def..f4d527640a6 100644 --- a/public/app/features/alerting/unified/PanelAlertTabContent.tsx +++ b/public/app/features/alerting/unified/PanelAlertTabContent.tsx @@ -3,6 +3,7 @@ import { css } from '@emotion/css'; import { GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { Alert, LoadingPlaceholder, ScrollContainer, useStyles2 } from '@grafana/ui'; +import { Trans } from 'app/core/internationalization'; import { contextSrv } from 'app/core/services/context_srv'; import { DashboardModel } from 'app/features/dashboard/state/DashboardModel'; import { PanelModel } from 'app/features/dashboard/state/PanelModel'; @@ -59,18 +60,26 @@ export const PanelAlertTabContent = ({ dashboard, panel }: Props) => { ); } + const isNew = !Boolean(dashboard.uid); + return (
{alert} - {!!dashboard.uid && ( + {!isNew && ( <> -

There are no alert rules linked to this panel.

+

+ + There are no alert rules linked to this panel. + +

{!!dashboard.meta.canSave && canCreateRules && } )} - {!dashboard.uid && !!dashboard.meta.canSave && ( + {isNew && !!dashboard.meta.canSave && ( - Dashboard must be saved before alerts can be added. + + Dashboard must be saved before alerts can be added. + )}
diff --git a/public/app/features/alerting/unified/hooks/useCombinedRuleNamespaces.ts b/public/app/features/alerting/unified/hooks/useCombinedRuleNamespaces.ts index 1b8043691d9..f28bc644ecc 100644 --- a/public/app/features/alerting/unified/hooks/useCombinedRuleNamespaces.ts +++ b/public/app/features/alerting/unified/hooks/useCombinedRuleNamespaces.ts @@ -494,6 +494,8 @@ export function useCombinedRules( result?: CombinedRuleNamespace[]; error?: unknown; } { + const isNewDashboard = !Boolean(dashboardUID); + const { currentData: promRuleNs, isLoading: isLoadingPromRules, @@ -505,8 +507,7 @@ export function useCombinedRules( 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: dashboardUID === null, + skip: isNewDashboard, pollingInterval: poll ? RULE_LIST_POLL_INTERVAL_MS : undefined, } ); @@ -522,7 +523,7 @@ export function useCombinedRules( }, { pollingInterval: poll ? RULE_LIST_POLL_INTERVAL_MS : undefined, - skip: dashboardUID === null, + skip: isNewDashboard, } ); diff --git a/public/app/features/apiserver/types.ts b/public/app/features/apiserver/types.ts index 1df68585beb..bbbdf2f1246 100644 --- a/public/app/features/apiserver/types.ts +++ b/public/app/features/apiserver/types.ts @@ -54,7 +54,6 @@ export const AnnoKeySavedFromUI = 'grafana.app/saved-from-ui'; export const AnnoKeyDashboardNotFound = 'grafana.app/dashboard-not-found'; export const AnnoKeyDashboardIsSnapshot = 'grafana.app/dashboard-is-snapshot'; 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'; // Annotations provided by the API @@ -83,7 +82,6 @@ type GrafanaClientAnnotations = { [AnnoKeyDashboardNotFound]?: boolean; [AnnoKeyDashboardIsSnapshot]?: boolean; [AnnoKeyDashboardSnapshotOriginalUrl]?: string; - [AnnoKeyDashboardIsNew]?: boolean; // 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. diff --git a/public/app/features/dashboard-scene/inspect/PanelInspectDrawer.test.tsx b/public/app/features/dashboard-scene/inspect/PanelInspectDrawer.test.tsx new file mode 100644 index 00000000000..49998fa30d9 --- /dev/null +++ b/public/app/features/dashboard-scene/inspect/PanelInspectDrawer.test.tsx @@ -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 }; +} diff --git a/public/app/features/dashboard-scene/inspect/PanelInspectDrawer.tsx b/public/app/features/dashboard-scene/inspect/PanelInspectDrawer.tsx index 2b75bec161f..1694d89070f 100644 --- a/public/app/features/dashboard-scene/inspect/PanelInspectDrawer.tsx +++ b/public/app/features/dashboard-scene/inspect/PanelInspectDrawer.tsx @@ -15,6 +15,7 @@ import { getDataSourceWithInspector } from 'app/features/dashboard/components/In import { supportsDataQuery } from 'app/features/dashboard/components/PanelEditor/utils'; import { InspectTab } from 'app/features/inspector/types'; +import { DashboardScene } from '../scene/DashboardScene'; import { getDashboardUrl } from '../utils/getDashboardUrl'; import { getDashboardSceneFor } from '../utils/utils'; @@ -91,21 +92,7 @@ export class PanelInspectDrawer extends SceneObjectBase } onClose = () => { - const dashboard = 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, - }) - ); + onPanelInspectClose(getDashboardSceneFor(this)); }; } @@ -156,3 +143,23 @@ function PanelInspectRenderer({ model }: SceneComponentProps ); } + +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, + }) + ); +} diff --git a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.test.ts b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.test.ts index 74c025ebb02..38bf7a0fcb5 100644 --- a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.test.ts +++ b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.test.ts @@ -133,13 +133,12 @@ describe('DashboardScenePageStateManager v1', () => { }); 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({}); await loader.loadDashboard({ uid: '', route: DashboardRoutes.New }); const dashboard = loader.state.dashboard!; - expect(dashboard.state.meta.isNew).toBe(true); expect(dashboard.state.isEditing).toBe(undefined); expect(dashboard.state.isDirty).toBe(false); @@ -431,13 +430,12 @@ describe('DashboardScenePageStateManager v2', () => { }); 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({}); await loader.loadDashboard({ uid: '', route: DashboardRoutes.New }); const dashboard = loader.state.dashboard!; - expect(dashboard.state.meta.isNew).toBe(true); expect(dashboard.state.isEditing).toBe(undefined); expect(dashboard.state.isDirty).toBe(false); diff --git a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataAlertingTab.test.tsx b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataAlertingTab.test.tsx index 40aa3cd0926..1d9e2764ce8 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataAlertingTab.test.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataAlertingTab.test.tsx @@ -237,7 +237,7 @@ describe('PanelAlertTabContent', () => { }), ]; - renderAlertTab(dashboard); + renderAlertTab(dashboard, dashboard); const defaults = await clickNewButton(); @@ -264,7 +264,7 @@ describe('PanelAlertTabContent', () => { }), ]; - renderAlertTab(dashboard); + renderAlertTab(dashboard, dashboard); const defaults = await clickNewButton(); expect(defaults.queries[0].model).toEqual({ @@ -290,7 +290,7 @@ describe('PanelAlertTabContent', () => { }), ]; - renderAlertTab(dashboard); + renderAlertTab(dashboard, dashboard); const defaults = await clickNewButton(); 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 () => { dashboard.panels = [panel]; - renderAlertTab(dashboard); + renderAlertTab(dashboard, dashboard); const rows = await ui.row.findAll(); expect(rows).toHaveLength(2); @@ -334,8 +334,8 @@ describe('PanelAlertTabContent', () => { }); }); -function renderAlertTab(dashboard: DashboardModel) { - const model = createModel(dashboard); +function renderAlertTab(dashboard: DashboardModel, dto: DashboardDataDTO) { + const model = createModel(dashboard, dto); renderAlertTabContent(model); } @@ -353,8 +353,8 @@ async function clickNewButton() { return defaults; } -function createModel(dashboard: DashboardModel) { - const scene = createDashboardSceneFromDashboardModel(dashboard, {} as DashboardDataDTO); +function createModel(dashboard: DashboardModel, dto: DashboardDataDTO) { + const scene = createDashboardSceneFromDashboardModel(dashboard, dto); const vizPanel = findVizPanelByKey(scene, getVizPanelKeyForPanelId(34))!; const model = new PanelDataAlertingTab({ panelRef: vizPanel.getRef() }); jest.spyOn(utils, 'getDashboardSceneFor').mockReturnValue(scene); diff --git a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataAlertingTab.tsx b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataAlertingTab.tsx index a4fbbc4471d..5ccbd2af8fe 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataAlertingTab.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataAlertingTab.tsx @@ -4,6 +4,7 @@ import { GrafanaTheme2 } from '@grafana/data'; import { SceneComponentProps, SceneObjectBase, SceneObjectRef, SceneObjectState, VizPanel } from '@grafana/scenes'; import { Alert, LoadingPlaceholder, Tab, useStyles2 } from '@grafana/ui'; import { contextSrv } from 'app/core/core'; +import { Trans } from 'app/core/internationalization'; import { RulesTable } from 'app/features/alerting/unified/components/rules/RulesTable'; import { usePanelCombinedRules } from 'app/features/alerting/unified/hooks/usePanelCombinedRules'; import { getRulesPermissions } from 'app/features/alerting/unified/utils/access-control'; @@ -86,10 +87,28 @@ export function PanelDataAlertingTabRendered({ model }: SceneComponentProps -

There are no alert rules linked to this panel.

- {canCreateRules && } + {!isNew && ( + <> +

+ + There are no alert rules linked to this panel. + +

+ {canCreateRules && } + + )} + {isNew && !!dashboard.state.meta.canSave && ( + + + Dashboard must be saved before alerts can be added. + + + )} ); } diff --git a/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx b/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx index 149cba2953a..80d4b19fa3a 100644 --- a/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx @@ -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', () => { let scene: DashboardScene; let deactivateScene: () => void; diff --git a/public/app/features/dashboard-scene/scene/DashboardScene.tsx b/public/app/features/dashboard-scene/scene/DashboardScene.tsx index a532a77cd15..1accc0eeff2 100644 --- a/public/app/features/dashboard-scene/scene/DashboardScene.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardScene.tsx @@ -114,7 +114,7 @@ export interface DashboardSceneState extends SceneObjectState { /** True when user made a change */ isDirty?: boolean; /** meta flags */ - meta: DashboardMeta; + meta: Omit; /** Version of the dashboard */ version?: number; /** Panel to inspect */ @@ -202,6 +202,7 @@ export class DashboardScene extends SceneObjectBase { private _activationHandler() { let prevSceneContext = window.__grafanaSceneContext; + const isNew = locationService.getLocation().pathname === '/dashboard/new'; window.__grafanaSceneContext = this; @@ -212,7 +213,7 @@ export class DashboardScene extends SceneObjectBase { this._changeTracker.startTrackingChanges(); } - if (this.state.meta.isNew) { + if (isNew) { this.onEnterEditMode(); this.setState({ isDirty: true }); } @@ -286,7 +287,6 @@ export class DashboardScene extends SceneObjectBase { url: result.url, slug: result.slug, folderUid: folderUid, - isNew: false, version: result.version, }, }); @@ -417,6 +417,7 @@ export class DashboardScene extends SceneObjectBase { public getPageNav(location: H.Location, navIndex: NavIndex) { const { meta, viewPanelScene, editPanel, title, uid } = this.state; + const isNew = !Boolean(uid); if (meta.dashboardNotFound) { return { text: 'Not found' }; @@ -429,7 +430,7 @@ export class DashboardScene extends SceneObjectBase { slug: meta.slug, currentQueryParams: location.search, 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, }), }; diff --git a/public/app/features/dashboard-scene/scene/NavToolbarActions.tsx b/public/app/features/dashboard-scene/scene/NavToolbarActions.tsx index 8d65d35238b..69666caa007 100644 --- a/public/app/features/dashboard-scene/scene/NavToolbarActions.tsx +++ b/public/app/features/dashboard-scene/scene/NavToolbarActions.tsx @@ -71,6 +71,8 @@ export function ToolbarActions({ dashboard }: Props) { const isEditedPanelDirty = usePanelEditDirty(editPanel); const isEditingLibraryPanel = editPanel && isLibraryPanel(editPanel.state.panelRef.resolve()); const isNotFound = Boolean(meta.dashboardNotFound); + const isNew = !Boolean(uid); + const hasCopiedPanel = store.exists(LS_PANEL_COPY_KEY); // Means we are not in settings view, fullscreen panel or edit panel const isShowingDashboard = !editview && !isViewingPanel && !isEditingPanel; @@ -489,7 +491,7 @@ export function ToolbarActions({ dashboard }: Props) { toolbarActions.push({ group: 'main-buttons', - condition: isEditing && !meta.isNew && isShowingDashboard, + condition: isEditing && !isNew && isShowingDashboard, render: () => (