diff --git a/.betterer.results b/.betterer.results index 42b0a38b41d..16d6a4be916 100644 --- a/.betterer.results +++ b/.betterer.results @@ -2511,10 +2511,6 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"] ], - "public/app/features/dashboard-scene/saving/SaveDashboardForm.tsx:5381": [ - [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"], - [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "1"] - ], "public/app/features/dashboard-scene/saving/getDashboardChanges.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"] @@ -2523,9 +2519,6 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"] ], - "public/app/features/dashboard-scene/saving/shared.tsx:5381": [ - [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"] - ], "public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], diff --git a/package.json b/package.json index 4e197870fd6..4734b6e8c82 100644 --- a/package.json +++ b/package.json @@ -255,7 +255,7 @@ "@grafana/prometheus": "workspace:*", "@grafana/runtime": "workspace:*", "@grafana/saga-icons": "workspace:*", - "@grafana/scenes": "^4.2.1", + "@grafana/scenes": "^4.5.3", "@grafana/schema": "workspace:*", "@grafana/sql": "workspace:*", "@grafana/ui": "workspace:*", diff --git a/packages/grafana-e2e-selectors/src/selectors/pages.ts b/packages/grafana-e2e-selectors/src/selectors/pages.ts index f40a8709f8e..abda6b077c4 100644 --- a/packages/grafana-e2e-selectors/src/selectors/pages.ts +++ b/packages/grafana-e2e-selectors/src/selectors/pages.ts @@ -226,6 +226,7 @@ export const Pages = { save: 'Dashboard settings Save Dashboard Modal Save button', saveVariables: 'Dashboard settings Save Dashboard Modal Save variables checkbox', saveTimerange: 'Dashboard settings Save Dashboard Modal Save timerange checkbox', + saveRefresh: 'Dashboard settings Save Dashboard Modal Save refresh checkbox', }, SharePanelModal: { linkToRenderedImage: 'Link to rendered image', diff --git a/public/app/features/dashboard-scene/saving/DashboardSceneChangeTracker.ts b/public/app/features/dashboard-scene/saving/DashboardSceneChangeTracker.ts index 2e04e4d0d25..26991da1f77 100644 --- a/public/app/features/dashboard-scene/saving/DashboardSceneChangeTracker.ts +++ b/public/app/features/dashboard-scene/saving/DashboardSceneChangeTracker.ts @@ -31,7 +31,10 @@ export class DashboardSceneChangeTracker { private onStateChanged({ payload }: SceneObjectStateChangedEvent) { if (payload.changedObject instanceof SceneRefreshPicker) { - if (Object.prototype.hasOwnProperty.call(payload.partialUpdate, 'intervals')) { + if ( + Object.prototype.hasOwnProperty.call(payload.partialUpdate, 'intervals') || + Object.prototype.hasOwnProperty.call(payload.partialUpdate, 'refresh') + ) { this.detectChanges(); } } diff --git a/public/app/features/dashboard-scene/saving/DetectChangesWorker.ts b/public/app/features/dashboard-scene/saving/DetectChangesWorker.ts index 4c29daef47f..3dc6e7ec8b0 100644 --- a/public/app/features/dashboard-scene/saving/DetectChangesWorker.ts +++ b/public/app/features/dashboard-scene/saving/DetectChangesWorker.ts @@ -5,6 +5,6 @@ import debounce from 'lodash/debounce'; import { getDashboardChanges } from './getDashboardChanges'; self.onmessage = debounce((e: MessageEvent<{ initial: any; changed: any }>) => { - const result = getDashboardChanges(e.data.initial, e.data.changed, false, false); + const result = getDashboardChanges(e.data.initial, e.data.changed, false, false, false); self.postMessage(result); }, 500); diff --git a/public/app/features/dashboard-scene/saving/SaveDashboardDrawer.test.tsx b/public/app/features/dashboard-scene/saving/SaveDashboardDrawer.test.tsx index a015b2a8964..8aa5e9f9cc4 100644 --- a/public/app/features/dashboard-scene/saving/SaveDashboardDrawer.test.tsx +++ b/public/app/features/dashboard-scene/saving/SaveDashboardDrawer.test.tsx @@ -4,7 +4,7 @@ import React from 'react'; import { TestProvider } from 'test/helpers/TestProvider'; import { selectors } from '@grafana/e2e-selectors'; -import { sceneGraph } from '@grafana/scenes'; +import { sceneGraph, SceneRefreshPicker } from '@grafana/scenes'; import { SaveDashboardResponseDTO } from 'app/types'; import { transformSaveModelToScene } from '../serialization/transformSaveModelToScene'; @@ -31,7 +31,7 @@ describe('SaveDashboardDrawer', () => { setup().openAndRender(); expect(await screen.findByText('Save dashboard')).toBeInTheDocument(); - expect(screen.queryByLabelText(selectors.pages.SaveDashboardModal.saveTimerange)).not.toBeInTheDocument(); + expect(screen.queryByTestId(selectors.pages.SaveDashboardModal.saveTimerange)).not.toBeInTheDocument(); expect(screen.getByText('No changes to save')).toBeInTheDocument(); expect(screen.queryByLabelText('Tab Changes')).not.toBeInTheDocument(); }); @@ -49,7 +49,7 @@ describe('SaveDashboardDrawer', () => { openAndRender(); expect(await screen.findByText('Save dashboard')).toBeInTheDocument(); - expect(screen.queryByLabelText(selectors.pages.SaveDashboardModal.saveTimerange)).toBeInTheDocument(); + expect(screen.queryByTestId(selectors.pages.SaveDashboardModal.saveTimerange)).toBeInTheDocument(); }); it('Should update diff when including time range is', async () => { @@ -60,10 +60,43 @@ describe('SaveDashboardDrawer', () => { openAndRender(); expect(await screen.findByText('Save dashboard')).toBeInTheDocument(); - expect(screen.queryByLabelText(selectors.pages.SaveDashboardModal.saveTimerange)).toBeInTheDocument(); + expect(screen.queryByTestId(selectors.pages.SaveDashboardModal.saveTimerange)).toBeInTheDocument(); expect(screen.queryByLabelText('Tab Changes')).not.toBeInTheDocument(); - await userEvent.click(screen.getByLabelText(selectors.pages.SaveDashboardModal.saveTimerange)); + await userEvent.click(screen.getByTestId(selectors.pages.SaveDashboardModal.saveTimerange)); + + expect(await screen.findByLabelText('Tab Changes')).toBeInTheDocument(); + }); + + it('When refresh changed show save refresh option', async () => { + const { dashboard, openAndRender } = setup(); + + const refreshPicker = sceneGraph.findObject(dashboard, (obj) => obj instanceof SceneRefreshPicker); + if (refreshPicker instanceof SceneRefreshPicker) { + refreshPicker.setState({ refresh: '5s' }); + } + + openAndRender(); + + expect(await screen.findByText('Save dashboard')).toBeInTheDocument(); + expect(screen.queryByTestId(selectors.pages.SaveDashboardModal.saveRefresh)).toBeInTheDocument(); + }); + + it('Should update diff when including time range is', async () => { + const { dashboard, openAndRender } = setup(); + + const refreshPicker = sceneGraph.findObject(dashboard, (obj) => obj instanceof SceneRefreshPicker); + if (refreshPicker instanceof SceneRefreshPicker) { + refreshPicker.setState({ refresh: '5s' }); + } + + openAndRender(); + + expect(await screen.findByText('Save dashboard')).toBeInTheDocument(); + expect(screen.getByTestId(selectors.pages.SaveDashboardModal.saveRefresh)).toBeInTheDocument(); + expect(screen.queryByLabelText('Tab Changes')).not.toBeInTheDocument(); + + await userEvent.click(screen.getByTestId(selectors.pages.SaveDashboardModal.saveRefresh)); expect(await screen.findByLabelText('Tab Changes')).toBeInTheDocument(); }); @@ -89,7 +122,7 @@ describe('SaveDashboardDrawer', () => { mockSaveDashboard(); - await userEvent.click(await screen.findByLabelText(selectors.pages.SaveDashboardModal.save)); + await userEvent.click(await screen.findByTestId(selectors.pages.SaveDashboardModal.save)); const dataSent = saveDashboardMutationMock.mock.calls[0][0]; expect(dataSent.dashboard.title).toEqual('New title'); @@ -107,13 +140,13 @@ describe('SaveDashboardDrawer', () => { mockSaveDashboard({ saveError: 'version-mismatch' }); - await userEvent.click(await screen.findByLabelText(selectors.pages.SaveDashboardModal.save)); + await userEvent.click(await screen.findByTestId(selectors.pages.SaveDashboardModal.save)); expect(await screen.findByText('Someone else has updated this dashboard')).toBeInTheDocument(); expect(await screen.findByText('Save and overwrite')).toBeInTheDocument(); // Now save and overwrite - await userEvent.click(await screen.findByLabelText(selectors.pages.SaveDashboardModal.save)); + await userEvent.click(await screen.findByTestId(selectors.pages.SaveDashboardModal.save)); const dataSent = saveDashboardMutationMock.mock.calls[1][0]; expect(dataSent.overwrite).toEqual(true); @@ -129,7 +162,7 @@ describe('SaveDashboardDrawer', () => { mockSaveDashboard(); - await userEvent.click(await screen.findByLabelText(selectors.pages.SaveDashboardModal.save)); + await userEvent.click(await screen.findByTestId(selectors.pages.SaveDashboardModal.save)); const dataSent = saveDashboardMutationMock.mock.calls[0][0]; expect(dataSent.dashboard.uid).toEqual(''); diff --git a/public/app/features/dashboard-scene/saving/SaveDashboardDrawer.tsx b/public/app/features/dashboard-scene/saving/SaveDashboardDrawer.tsx index e52aa49698e..26c08217605 100644 --- a/public/app/features/dashboard-scene/saving/SaveDashboardDrawer.tsx +++ b/public/app/features/dashboard-scene/saving/SaveDashboardDrawer.tsx @@ -16,6 +16,7 @@ interface SaveDashboardDrawerState extends SceneObjectState { showDiff?: boolean; saveTimeRange?: boolean; saveVariables?: boolean; + saveRefresh?: boolean; saveAsCopy?: boolean; onSaveSuccess?: () => void; } @@ -33,9 +34,18 @@ export class SaveDashboardDrawer extends SceneObjectBase { + this.setState({ saveRefresh: !this.state.saveRefresh }); + }; + static Component = ({ model }: SceneComponentProps) => { - const { showDiff, saveAsCopy, saveTimeRange, saveVariables } = model.useState(); - const changeInfo = getDashboardChangesFromScene(model.state.dashboardRef.resolve(), saveTimeRange, saveVariables); + const { showDiff, saveAsCopy, saveTimeRange, saveVariables, saveRefresh } = model.useState(); + const changeInfo = getDashboardChangesFromScene( + model.state.dashboardRef.resolve(), + saveTimeRange, + saveVariables, + saveRefresh + ); const { changedSaveModel, initialSaveModel, diffs, diffCount } = changeInfo; const dashboard = model.state.dashboardRef.resolve(); const isProvisioned = dashboard.state.meta.provisioned; diff --git a/public/app/features/dashboard-scene/saving/SaveDashboardForm.tsx b/public/app/features/dashboard-scene/saving/SaveDashboardForm.tsx index 128d7f07a35..3de8731f875 100644 --- a/public/app/features/dashboard-scene/saving/SaveDashboardForm.tsx +++ b/public/app/features/dashboard-scene/saving/SaveDashboardForm.tsx @@ -130,8 +130,8 @@ export interface SaveDashboardFormCommonOptionsProps { } export function SaveDashboardFormCommonOptions({ drawer, changeInfo }: SaveDashboardFormCommonOptionsProps) { - const { saveVariables = false, saveTimeRange = false } = drawer.useState(); - const { hasTimeChanges, hasVariableValueChanges } = changeInfo; + const { saveVariables = false, saveTimeRange = false, saveRefresh = false } = drawer.useState(); + const { hasTimeChanges, hasVariableValueChanges, hasRefreshChange } = changeInfo; return ( <> @@ -141,7 +141,17 @@ export function SaveDashboardFormCommonOptions({ drawer, changeInfo }: SaveDashb id="save-timerange" checked={saveTimeRange} onChange={drawer.onToggleSaveTimeRange} - aria-label={selectors.pages.SaveDashboardModal.saveTimerange} + data-testid={selectors.pages.SaveDashboardModal.saveTimerange} + /> + + )} + {hasRefreshChange && ( + + )} @@ -151,7 +161,7 @@ export function SaveDashboardFormCommonOptions({ drawer, changeInfo }: SaveDashb id="save-variables" checked={saveVariables} onChange={drawer.onToggleSaveVariables} - aria-label={selectors.pages.SaveDashboardModal.saveVariables} + data-testid={selectors.pages.SaveDashboardModal.saveVariables} /> )} diff --git a/public/app/features/dashboard-scene/saving/getDashboardChanges.ts b/public/app/features/dashboard-scene/saving/getDashboardChanges.ts index bd5bd4d7a30..9231ee43f5e 100644 --- a/public/app/features/dashboard-scene/saving/getDashboardChanges.ts +++ b/public/app/features/dashboard-scene/saving/getDashboardChanges.ts @@ -10,17 +10,23 @@ export function getDashboardChanges( initial: Dashboard, changed: Dashboard, saveTimeRange?: boolean, - saveVariables?: boolean + saveVariables?: boolean, + saveRefresh?: boolean ) { const initialSaveModel = initial; const changedSaveModel = changed; const hasTimeChanged = getHasTimeChanged(changedSaveModel, initialSaveModel); const hasVariableValueChanges = applyVariableChanges(changedSaveModel, initialSaveModel, saveVariables); + const hasRefreshChanged = changedSaveModel.refresh !== initialSaveModel.refresh; if (!saveTimeRange) { changedSaveModel.time = initialSaveModel.time; } + if (!saveRefresh) { + changedSaveModel.refresh = initialSaveModel.refresh; + } + const diff = jsonDiff(initialSaveModel, changedSaveModel); let diffCount = 0; @@ -36,6 +42,7 @@ export function getDashboardChanges( hasTimeChanges: hasTimeChanged, isNew: changedSaveModel.version === 0, hasVariableValueChanges, + hasRefreshChange: hasRefreshChanged, }; } diff --git a/public/app/features/dashboard-scene/saving/getDashboardChangesFromScene.test.ts b/public/app/features/dashboard-scene/saving/getDashboardChangesFromScene.test.ts index 59d74c83b15..7ddab4930a7 100644 --- a/public/app/features/dashboard-scene/saving/getDashboardChangesFromScene.test.ts +++ b/public/app/features/dashboard-scene/saving/getDashboardChangesFromScene.test.ts @@ -1,5 +1,11 @@ import { config } from '@grafana/runtime'; -import { AdHocFiltersVariable, GroupByVariable, MultiValueVariable, sceneGraph } from '@grafana/scenes'; +import { + AdHocFiltersVariable, + GroupByVariable, + MultiValueVariable, + sceneGraph, + SceneRefreshPicker, +} from '@grafana/scenes'; import { VariableModel } from '@grafana/schema'; import { buildPanelEditScene } from '../panel-edit/PanelEditor'; @@ -38,6 +44,33 @@ describe('getDashboardChangesFromScene', () => { expect(result.diffCount).toBe(1); }); + it('Can detect refresh changed', () => { + const dashboard = setup(); + + const refreshPicker = sceneGraph.findObject(dashboard, (obj) => obj instanceof SceneRefreshPicker); + if (refreshPicker instanceof SceneRefreshPicker) { + refreshPicker.setState({ refresh: '5s' }); + } + + const result = getDashboardChangesFromScene(dashboard, false, false, false); + expect(result.hasChanges).toBe(false); + expect(result.diffCount).toBe(0); + expect(result.hasRefreshChange).toBe(true); + }); + + it('Can save refresh change', () => { + const dashboard = setup(); + + const refreshPicker = sceneGraph.findObject(dashboard, (obj) => obj instanceof SceneRefreshPicker); + if (refreshPicker instanceof SceneRefreshPicker) { + refreshPicker.setState({ refresh: '5s' }); + } + + const result = getDashboardChangesFromScene(dashboard, false, false, true); + expect(result.hasChanges).toBe(true); + expect(result.diffCount).toBe(1); + }); + describe('variable changes', () => { it('Can detect variable change', () => { const dashboard = setup(); diff --git a/public/app/features/dashboard-scene/saving/getDashboardChangesFromScene.ts b/public/app/features/dashboard-scene/saving/getDashboardChangesFromScene.ts index 4859fe77f4b..4d01fdce1bb 100644 --- a/public/app/features/dashboard-scene/saving/getDashboardChangesFromScene.ts +++ b/public/app/features/dashboard-scene/saving/getDashboardChangesFromScene.ts @@ -3,12 +3,18 @@ import { transformSceneToSaveModel } from '../serialization/transformSceneToSave import { getDashboardChanges } from './getDashboardChanges'; -export function getDashboardChangesFromScene(scene: DashboardScene, saveTimeRange?: boolean, saveVariables?: boolean) { +export function getDashboardChangesFromScene( + scene: DashboardScene, + saveTimeRange?: boolean, + saveVariables?: boolean, + saveRefresh?: boolean +) { const changeInfo = getDashboardChanges( scene.getInitialSaveModel()!, transformSceneToSaveModel(scene), saveTimeRange, - saveVariables + saveVariables, + saveRefresh ); return changeInfo; } diff --git a/public/app/features/dashboard-scene/saving/getSaveDashboardChange.ts b/public/app/features/dashboard-scene/saving/getSaveDashboardChange.ts index f9f9ab943cb..f802919b6e0 100644 --- a/public/app/features/dashboard-scene/saving/getSaveDashboardChange.ts +++ b/public/app/features/dashboard-scene/saving/getSaveDashboardChange.ts @@ -12,7 +12,8 @@ import { DashboardChangeInfo } from './shared'; export function getSaveDashboardChange( dashboard: DashboardScene, saveTimeRange?: boolean, - saveVariables?: boolean + saveVariables?: boolean, + saveRefresh?: boolean ): DashboardChangeInfo { const initialSaveModel = dashboard.getInitialSaveModel()!; @@ -24,11 +25,16 @@ export function getSaveDashboardChange( const hasTimeChanged = getHasTimeChanged(changedSaveModel, initialSaveModel); const hasVariableValueChanges = applyVariableChanges(changedSaveModel, initialSaveModel, saveVariables); + const hasRefreshChanged = changedSaveModel.refresh !== initialSaveModel.refresh; if (!saveTimeRange) { changedSaveModel.time = initialSaveModel.time; } + if (!saveRefresh) { + changedSaveModel.refresh = initialSaveModel.refresh; + } + const diff = jsonDiff(initialSaveModel, changedSaveModel); let diffCount = 0; @@ -45,6 +51,7 @@ export function getSaveDashboardChange( hasTimeChanges: hasTimeChanged, isNew: changedSaveModel.version === 0, hasVariableValueChanges, + hasRefreshChange: hasRefreshChanged, }; } diff --git a/public/app/features/dashboard-scene/saving/shared.tsx b/public/app/features/dashboard-scene/saving/shared.tsx index ad52947cb44..39010d3f86b 100644 --- a/public/app/features/dashboard-scene/saving/shared.tsx +++ b/public/app/features/dashboard-scene/saving/shared.tsx @@ -15,6 +15,7 @@ export interface DashboardChangeInfo { hasChanges: boolean; hasTimeChanges: boolean; hasVariableValueChanges: boolean; + hasRefreshChange: boolean; isNew?: boolean; } @@ -63,7 +64,7 @@ export function SaveButton({ overwrite, isLoading, isValid, onSave }: SaveButton