Add auto to refresh picker (#85584)

This commit is contained in:
Bogdan Matei 2024-04-05 18:31:40 +03:00 committed by GitHub
parent 040966220e
commit 9d44c8e8cf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 148 additions and 37 deletions

View File

@ -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"],

View File

@ -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:*",

View File

@ -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',

View File

@ -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();
}
}

View File

@ -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);

View File

@ -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('');

View File

@ -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<SaveDashboardDrawerStat
this.setState({ saveVariables: !this.state.saveVariables });
};
public onToggleSaveRefresh = () => {
this.setState({ saveRefresh: !this.state.saveRefresh });
};
static Component = ({ model }: SceneComponentProps<SaveDashboardDrawer>) => {
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;

View File

@ -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}
/>
</Field>
)}
{hasRefreshChange && (
<Field label="Update default refresh value" description="Will make the current refresh the new default">
<Checkbox
id="save-refresh"
checked={saveRefresh}
onChange={drawer.onToggleSaveRefresh}
data-testid={selectors.pages.SaveDashboardModal.saveRefresh}
/>
</Field>
)}
@ -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}
/>
</Field>
)}

View File

@ -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,
};
}

View File

@ -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();

View File

@ -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;
}

View File

@ -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,
};
}

View File

@ -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
<Button
disabled={!isValid || isLoading}
icon={isLoading ? 'spinner' : undefined}
aria-label={selectors.pages.SaveDashboardModal.save}
data-testid={selectors.pages.SaveDashboardModal.save}
onClick={() => onSave(overwrite)}
variant={overwrite ? 'destructive' : 'primary'}
>

View File

@ -183,6 +183,7 @@ exports[`transformSceneToSaveModel Given a scene with rows Should transform back
"type": "row",
},
],
"refresh": "",
"schemaVersion": 39,
"tags": [
"templating",
@ -449,6 +450,7 @@ exports[`transformSceneToSaveModel Given a simple scene with custom settings Sho
"type": "text",
},
],
"refresh": "5m",
"schemaVersion": 39,
"tags": [
"tag1",
@ -805,6 +807,7 @@ exports[`transformSceneToSaveModel Given a simple scene with variables Should tr
"type": "text",
},
],
"refresh": "",
"schemaVersion": 39,
"tags": [
"gdev",

View File

@ -178,6 +178,7 @@ describe('transformSceneToSaveModel', () => {
weekStart: 'monday',
graphTooltip: 1,
editable: false,
refresh: '5m',
timepicker: {
...dashboard_to_load1.timepicker,
refresh_intervals: ['5m', '15m', '30m', '1h'],

View File

@ -102,9 +102,11 @@ export function transformSceneToSaveModel(scene: DashboardScene, isSnapshot = fa
const controlsState = state.controls?.state;
const refreshPicker = controlsState?.refreshPicker;
const timePickerWithoutDefaults = removeDefaults<TimePickerConfig>(
{
refresh_intervals: controlsState?.refreshPicker.state.intervals,
refresh_intervals: refreshPicker?.state.intervals,
hidden: controlsState?.hideTimeControls,
nowDelay: timeRange.UNSAFE_nowDelay,
},
@ -146,6 +148,7 @@ export function transformSceneToSaveModel(scene: DashboardScene, isSnapshot = fa
graphTooltip,
liveNow,
schemaVersion: DASHBOARD_SCHEMA_VERSION,
refresh: refreshPicker?.state.refresh,
};
return sortedDeepCloneWithoutNulls(dashboard);

View File

@ -4170,9 +4170,9 @@ __metadata:
languageName: unknown
linkType: soft
"@grafana/scenes@npm:^4.2.1":
version: 4.3.0
resolution: "@grafana/scenes@npm:4.3.0"
"@grafana/scenes@npm:^4.5.3":
version: 4.5.3
resolution: "@grafana/scenes@npm:4.5.3"
dependencies:
"@grafana/e2e-selectors": "npm:10.3.3"
react-grid-layout: "npm:1.3.4"
@ -4186,7 +4186,7 @@ __metadata:
"@grafana/ui": ^10.0.3
react: ^18.0.0
react-dom: ^18.0.0
checksum: 10/c8af6135ec96307c45cb2ed60fc17580abb5bd304c8f2948e1d901604394f48c3e7378d1274548a88eb9ae76fd6093fcb2194b746e105b08110a0e14423c6731
checksum: 10/f1bdfe216e630833f9641e07b2ce295e20ee2f1cebeb0f2115cd81f4aa2204bb525034268b0b067ef7ff6d5070b817aabb27c7649d228a0dbf37affa9220f490
languageName: node
linkType: hard
@ -18651,7 +18651,7 @@ __metadata:
"@grafana/prometheus": "workspace:*"
"@grafana/runtime": "workspace:*"
"@grafana/saga-icons": "workspace:*"
"@grafana/scenes": "npm:^4.2.1"
"@grafana/scenes": "npm:^4.5.3"
"@grafana/schema": "workspace:*"
"@grafana/sql": "workspace:*"
"@grafana/tsconfig": "npm:^1.3.0-rc1"