diff --git a/packages/grafana-ui/src/components/PanelChrome/PanelContext.ts b/packages/grafana-ui/src/components/PanelChrome/PanelContext.ts index 309f3cc1b81..336ca47e105 100644 --- a/packages/grafana-ui/src/components/PanelChrome/PanelContext.ts +++ b/packages/grafana-ui/src/components/PanelChrome/PanelContext.ts @@ -8,6 +8,7 @@ import { ThresholdsConfig, SplitOpen, CoreApp, + DataFrame, } from '@grafana/data'; import { AdHocFilterItem } from '../Table/types'; @@ -82,6 +83,12 @@ export interface PanelContext { * Called when a panel is changing the sort order of the legends. */ onToggleLegendSort?: (sortBy: string) => void; + + /** + * Optional, only some contexts support this. This action can be cancelled by user which will result + * in a the Promise resolving to a false value. + */ + onUpdateData?: (frames: DataFrame[]) => Promise; } export const PanelContextRoot = React.createContext({ diff --git a/public/app/features/dashboard/dashgrid/PanelStateWrapper.tsx b/public/app/features/dashboard/dashgrid/PanelStateWrapper.tsx index 08a09b9c45b..9d5f2f13b2d 100644 --- a/public/app/features/dashboard/dashgrid/PanelStateWrapper.tsx +++ b/public/app/features/dashboard/dashgrid/PanelStateWrapper.tsx @@ -8,6 +8,7 @@ import { AnnotationEventUIModel, CoreApp, DashboardCursorSync, + DataFrame, EventFilterOptions, FieldConfigSource, getDataSourceRef, @@ -42,6 +43,7 @@ import { InspectTab } from 'app/features/inspector/types'; import { getPanelLinksSupplier } from 'app/features/panel/panellinks/linkSuppliers'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { applyFilterFromTable } from 'app/features/variables/adhoc/actions'; +import { onUpdatePanelSnapshotData } from 'app/plugins/datasource/grafana/utils'; import { changeSeriesColorConfigFactory } from 'app/plugins/panel/timeseries/overrides/colorSeriesConfigFactory'; import { dispatch } from 'app/store/store'; import { RenderEvent } from 'app/types/events'; @@ -116,6 +118,7 @@ export class PanelStateWrapper extends PureComponent { canEditAnnotations: props.dashboard.canEditAnnotations.bind(props.dashboard), canDeleteAnnotations: props.dashboard.canDeleteAnnotations.bind(props.dashboard), onAddAdHocFilter: this.onAddAdHocFilter, + onUpdateData: this.onUpdateData, }, data: this.getInitialPanelDataState(), }; @@ -146,6 +149,10 @@ export class PanelStateWrapper extends PureComponent { return CoreApp.Dashboard; } + onUpdateData = (frames: DataFrame[]): Promise => { + return onUpdatePanelSnapshotData(this.props.panel, frames); + }; + onSeriesColorChange = (label: string, color: string) => { this.onFieldConfigChange(changeSeriesColorConfigFactory(label, color, this.props.panel.fieldConfig)); }; diff --git a/public/app/features/scenes/dashboard/DashboardsLoader.test.ts b/public/app/features/scenes/dashboard/DashboardsLoader.test.ts index 8d758d979c2..f99c01732c4 100644 --- a/public/app/features/scenes/dashboard/DashboardsLoader.test.ts +++ b/public/app/features/scenes/dashboard/DashboardsLoader.test.ts @@ -99,8 +99,6 @@ describe('DashboardLoader', () => { const loader = new DashboardLoader({}); await loader.load('fake-dash'); expect(loader.state.dashboard).toBeInstanceOf(DashboardScene); - // @ts-expect-error - private - expect(loader.state.dashboard?.urlSyncManager).toBeDefined(); expect(loader.state.isLoading).toBe(false); }); }); diff --git a/public/app/plugins/datasource/grafana/utils.ts b/public/app/plugins/datasource/grafana/utils.ts new file mode 100644 index 00000000000..ec9b6ff321c --- /dev/null +++ b/public/app/plugins/datasource/grafana/utils.ts @@ -0,0 +1,56 @@ +import { DataFrame, DataFrameJSON, dataFrameToJSON } from '@grafana/data'; +import appEvents from 'app/core/app_events'; +import { GRAFANA_DATASOURCE_NAME } from 'app/features/alerting/unified/utils/datasource'; +import { PanelModel } from 'app/features/dashboard/state'; +import { ShowConfirmModalEvent } from 'app/types/events'; + +import { GrafanaQuery, GrafanaQueryType } from './types'; + +/** + * Will show a confirm modal if the current panel does not have a snapshot query. + * If the confirm modal is shown, and the user aborts the promise will resolve with a false value, + * otherwise it will resolve with a true value. + */ +export function onUpdatePanelSnapshotData(panel: PanelModel, frames: DataFrame[]): Promise { + return new Promise((resolve) => { + if (panel.datasource?.uid === GRAFANA_DATASOURCE_NAME) { + updateSnapshotData(frames, panel); + resolve(true); + return; + } + + appEvents.publish( + new ShowConfirmModalEvent({ + title: 'Change to panel embedded data', + text: 'If you want to change the data shown in this panel Grafana will need to remove the panels current query and replace it with a snapshot of the current data. This enabled you to edit the data', + yesText: 'Continue', + icon: 'pen', + onConfirm: () => { + updateSnapshotData(frames, panel); + resolve(true); + }, + onDismiss: () => { + resolve(false); + }, + }) + ); + }); +} + +function updateSnapshotData(frames: DataFrame[], panel: PanelModel) { + const snapshot: DataFrameJSON[] = frames.map((f) => dataFrameToJSON(f)); + + const query: GrafanaQuery = { + refId: 'A', + queryType: GrafanaQueryType.Snapshot, + snapshot, + datasource: { uid: GRAFANA_DATASOURCE_NAME }, + }; + + panel.updateQueries({ + dataSource: { uid: GRAFANA_DATASOURCE_NAME }, + queries: [query], + }); + + panel.refresh(); +}