diff --git a/public/app/features/dashboard-scene/scene/DashboardScene.tsx b/public/app/features/dashboard-scene/scene/DashboardScene.tsx index 939d8e7be95..c5cdc3f1db7 100644 --- a/public/app/features/dashboard-scene/scene/DashboardScene.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardScene.tsx @@ -304,6 +304,14 @@ export class DashboardScene extends SceneObjectBase { return this._initialState !== undefined; } + public pauseTrackingChanges() { + this._changeTracker.stopTrackingChanges(); + } + + public resumeTrackingChanges() { + this._changeTracker.startTrackingChanges(); + } + public onRestore = async (version: DecoratedRevisionModel): Promise => { const versionRsp = await historySrv.restoreDashboard(version.uid, version.version); diff --git a/public/app/features/dashboard-scene/settings/JsonModelEditView.tsx b/public/app/features/dashboard-scene/settings/JsonModelEditView.tsx index 819d5704ad5..03406b38e0b 100644 --- a/public/app/features/dashboard-scene/settings/JsonModelEditView.tsx +++ b/public/app/features/dashboard-scene/settings/JsonModelEditView.tsx @@ -1,16 +1,23 @@ import { css } from '@emotion/css'; -import React from 'react'; +import React, { useState } from 'react'; import { GrafanaTheme2, PageLayoutType } from '@grafana/data'; import { SceneComponentProps, SceneObjectBase, sceneUtils } from '@grafana/scenes'; import { Dashboard } from '@grafana/schema'; -import { Button, CodeEditor, useStyles2 } from '@grafana/ui'; +import { Alert, Box, Button, CodeEditor, Stack, useStyles2 } from '@grafana/ui'; import { Page } from 'app/core/components/Page/Page'; import { Trans } from 'app/core/internationalization'; import LegacyAlertsDeprecationNotice from 'app/features/alerting/unified/integration/LegacyAlertsDeprecationNotice'; import { getPrettyJSON } from 'app/features/inspector/utils/utils'; -import { DashboardDTO } from 'app/types'; +import { DashboardDTO, SaveDashboardResponseDTO } from 'app/types'; +import { + NameAlreadyExistsError, + isNameExistsError, + isPluginDashboardError, + isVersionMismatchError, +} from '../saving/shared'; +import { useSaveDashboard } from '../saving/useSaveDashboard'; import { DashboardScene } from '../scene/DashboardScene'; import { NavToolbarActions } from '../scene/NavToolbarActions'; import { transformSaveModelToScene } from '../serialization/transformSaveModelToScene'; @@ -24,7 +31,7 @@ export interface JsonModelEditViewState extends DashboardEditViewState { } export class JsonModelEditView extends SceneObjectBase implements DashboardEditView { - constructor(state: Omit) { + constructor(state: Omit) { super({ ...state, jsonText: '', @@ -54,27 +61,120 @@ export class JsonModelEditView extends SceneObjectBase i this.setState({ jsonText: value }); }; - public onApplyChange = () => { + public onSaveSuccess = (result: SaveDashboardResponseDTO) => { const jsonModel = JSON.parse(this.state.jsonText); const dashboard = this.getDashboard(); + jsonModel.version = result.version; + const rsp: DashboardDTO = { dashboard: jsonModel, meta: dashboard.state.meta, }; const newDashboardScene = transformSaveModelToScene(rsp); const newState = sceneUtils.cloneSceneObjectState(newDashboardScene.state); + + dashboard.pauseTrackingChanges(); + dashboard.setInitialSaveModel(rsp.dashboard); dashboard.setState(newState); + + this.setState({ jsonText: this.getJsonText() }); }; static Component = ({ model }: SceneComponentProps) => { + const { state, onSaveDashboard } = useSaveDashboard(false); + const [isSaving, setIsSaving] = useState(false); + const dashboard = model.getDashboard(); + const { navModel, pageNav } = useDashboardEditPageNav(dashboard, model.getUrlKey()); const canSave = dashboard.useState().meta.canSave; const { jsonText } = model.useState(); + const onSave = async (overwrite: boolean) => { + const result = await onSaveDashboard(dashboard, JSON.parse(model.state.jsonText), { + folderUid: dashboard.state.meta.folderUid, + overwrite, + }); + + setIsSaving(true); + if (result.status === 'success') { + model.onSaveSuccess(result); + setIsSaving(false); + } else { + setIsSaving(true); + } + }; + + const saveButton = (overwrite: boolean) => ( + + ); + + const cancelButton = ( + + ); const styles = useStyles2(getStyles); const saveModel = model.getSaveModel(); + function renderSaveButtonAndError(error?: Error) { + if (error && isSaving) { + if (isVersionMismatchError(error)) { + return ( + +

Would you still like to save this dashboard?

+ + + {cancelButton} + {saveButton(true)} + + +
+ ); + } + + if (isNameExistsError(error)) { + return ; + } + + if (isPluginDashboardError(error)) { + return ( + +

+ Your changes will be lost when you update the plugin. Use Save As to create custom + version. +

+ + {saveButton(true)} + +
+ ); + } + } + + return ( + <> + {error && isSaving && ( + +

{error.message}

+
+ )} + {saveButton(false)} + + ); + } return ( @@ -93,13 +193,7 @@ export class JsonModelEditView extends SceneObjectBase i containerStyles={styles.codeEditor} onBlur={model.onCodeEditorBlur} /> - {canSave && ( -
- -
- )} + {canSave && {renderSaveButtonAndError(state.error)}}
);