mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
LibraryPanels: No save modal when user is on same dashboard (#31606)
This commit is contained in:
parent
a8bf1d68e3
commit
d306f417d3
@ -14,8 +14,9 @@ import { updateLocation } from 'app/core/actions';
|
||||
import { addPanel } from 'app/features/dashboard/state/reducers';
|
||||
import { DashboardModel, PanelModel } from '../../state';
|
||||
import { LibraryPanelsView } from '../../../library-panels/components/LibraryPanelsView/LibraryPanelsView';
|
||||
import { LibraryPanelDTO } from 'app/features/library-panels/state/api';
|
||||
import { LS_PANEL_COPY_KEY } from 'app/core/constants';
|
||||
import { LibraryPanelDTO } from '../../../library-panels/types';
|
||||
import { toPanelModelLibraryPanel } from '../../../library-panels/utils';
|
||||
|
||||
export type PanelPluginInfo = { id: any; defaults: { gridPos: { w: any; h: any }; title: any } };
|
||||
|
||||
@ -119,7 +120,7 @@ export const AddPanelWidgetUnconnected: React.FC<Props> = ({ panel, dashboard, u
|
||||
const newPanel: PanelModel = {
|
||||
...panelInfo.model,
|
||||
gridPos,
|
||||
libraryPanel: _.pick(panelInfo, 'name', 'uid', 'meta'),
|
||||
libraryPanel: toPanelModelLibraryPanel(panelInfo),
|
||||
};
|
||||
|
||||
dashboard.addPanel(newPanel);
|
||||
|
@ -29,10 +29,10 @@ import { DashboardPanel } from '../../dashgrid/DashboardPanel';
|
||||
|
||||
import {
|
||||
exitPanelEditor,
|
||||
updateSourcePanel,
|
||||
initPanelEditor,
|
||||
panelEditorCleanUp,
|
||||
updatePanelEditorUIState,
|
||||
updateSourcePanel,
|
||||
} from './state/actions';
|
||||
|
||||
import { updateTimeZoneForSession } from 'app/features/profile/state/reducers';
|
||||
@ -49,6 +49,14 @@ import { DashboardModel, PanelModel } from '../../state';
|
||||
import { PanelOptionsChangedEvent } from 'app/types/events';
|
||||
import { UnlinkModal } from '../../../library-panels/components/UnlinkModal/UnlinkModal';
|
||||
import { SaveLibraryPanelModal } from 'app/features/library-panels/components/SaveLibraryPanelModal/SaveLibraryPanelModal';
|
||||
import { isPanelModelLibraryPanel } from '../../../library-panels/guard';
|
||||
import { getLibraryPanelConnectedDashboards } from '../../../library-panels/state/api';
|
||||
import {
|
||||
createPanelLibraryErrorNotification,
|
||||
createPanelLibrarySuccessNotification,
|
||||
saveAndRefreshLibraryPanel,
|
||||
} from '../../../library-panels/utils';
|
||||
import { notifyApp } from '../../../../core/actions';
|
||||
|
||||
interface OwnProps {
|
||||
dashboard: DashboardModel;
|
||||
@ -79,6 +87,7 @@ const mapDispatchToProps = {
|
||||
setDiscardChanges,
|
||||
updatePanelEditorUIState,
|
||||
updateTimeZoneForSession,
|
||||
notifyApp,
|
||||
};
|
||||
|
||||
const connector = connect(mapStateToProps, mapDispatchToProps);
|
||||
@ -129,13 +138,28 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
|
||||
});
|
||||
};
|
||||
|
||||
onSavePanel = () => {
|
||||
const panelId = this.props.panel.libraryPanel?.uid;
|
||||
if (!panelId) {
|
||||
onSaveLibraryPanel = async () => {
|
||||
if (!isPanelModelLibraryPanel(this.props.panel)) {
|
||||
// New library panel, no need to display modal
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.props.panel.libraryPanel.meta.connectedDashboards === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const connectedDashboards = await getLibraryPanelConnectedDashboards(this.props.panel.libraryPanel.uid);
|
||||
if (connectedDashboards.length === 1 && connectedDashboards.indexOf(this.props.dashboard.id) !== -1) {
|
||||
try {
|
||||
await saveAndRefreshLibraryPanel(this.props.panel, this.props.dashboard.meta.folderId!);
|
||||
this.props.updateSourcePanel(this.props.panel);
|
||||
this.props.notifyApp(createPanelLibrarySuccessNotification('Library panel saved'));
|
||||
} catch (err) {
|
||||
this.props.notifyApp(createPanelLibraryErrorNotification(`Error saving library panel: "${err.statusText}"`));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
appEvents.emit(CoreEvents.showModalReact, {
|
||||
component: SaveLibraryPanelModal,
|
||||
props: {
|
||||
@ -289,7 +313,7 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
|
||||
</ToolbarButton>,
|
||||
this.props.panel.libraryPanel ? (
|
||||
<ToolbarButton
|
||||
onClick={this.onSavePanel}
|
||||
onClick={this.onSaveLibraryPanel}
|
||||
variant="primary"
|
||||
title="Apply changes and save library panel"
|
||||
key="save-panel"
|
||||
|
@ -6,17 +6,17 @@ import { getNextRefIdChar } from 'app/core/utils/query';
|
||||
// Types
|
||||
import {
|
||||
DataConfigSource,
|
||||
DataFrameDTO,
|
||||
DataLink,
|
||||
DataLinkBuiltInVars,
|
||||
DataQuery,
|
||||
DataTransformerConfig,
|
||||
EventBus,
|
||||
EventBusSrv,
|
||||
FieldConfigSource,
|
||||
PanelPlugin,
|
||||
ScopedVars,
|
||||
EventBus,
|
||||
EventBusSrv,
|
||||
DataFrameDTO,
|
||||
urlUtil,
|
||||
DataLinkBuiltInVars,
|
||||
} from '@grafana/data';
|
||||
import { EDIT_PANEL_ID } from 'app/core/constants';
|
||||
import config from 'app/core/config';
|
||||
@ -36,7 +36,7 @@ import {
|
||||
isStandardFieldProp,
|
||||
restoreCustomOverrideRules,
|
||||
} from './getPanelOptionsWithDefaults';
|
||||
import { LibraryPanelDTO } from 'app/features/library-panels/state/api';
|
||||
import { PanelModelLibraryPanel } from '../../library-panels/types';
|
||||
|
||||
export interface GridPos {
|
||||
x: number;
|
||||
@ -151,7 +151,7 @@ export class PanelModel implements DataConfigSource {
|
||||
links?: DataLink[];
|
||||
transparent: boolean;
|
||||
|
||||
libraryPanel?: { uid: undefined; name: string } | Pick<LibraryPanelDTO, 'uid' | 'name' | 'meta'>;
|
||||
libraryPanel?: { uid: undefined; name: string } | PanelModelLibraryPanel;
|
||||
|
||||
// non persisted
|
||||
isViewing: boolean;
|
||||
|
@ -2,7 +2,7 @@ import React, { useState } from 'react';
|
||||
import { Icon, IconButton, ConfirmModal, Tooltip, useStyles, Card } from '@grafana/ui';
|
||||
import { css } from 'emotion';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { LibraryPanelDTO } from '../../state/api';
|
||||
import { LibraryPanelDTO } from '../../types';
|
||||
|
||||
export interface LibraryPanelCardProps {
|
||||
libraryPanel: LibraryPanelDTO;
|
||||
|
@ -1,10 +1,12 @@
|
||||
import { Icon, Input, Button, stylesFactory, useStyles } from '@grafana/ui';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useDebounce } from 'react-use';
|
||||
import { cx, css } from 'emotion';
|
||||
import { LibraryPanelCard } from '../LibraryPanelCard/LibraryPanelCard';
|
||||
import { css, cx } from 'emotion';
|
||||
import { Button, Icon, Input, stylesFactory, useStyles } from '@grafana/ui';
|
||||
import { DateTimeInput, GrafanaTheme } from '@grafana/data';
|
||||
import { deleteLibraryPanel, getLibraryPanels, LibraryPanelDTO } from '../../state/api';
|
||||
|
||||
import { LibraryPanelCard } from '../LibraryPanelCard/LibraryPanelCard';
|
||||
import { deleteLibraryPanel, getLibraryPanels } from '../../state/api';
|
||||
import { LibraryPanelDTO } from '../../types';
|
||||
|
||||
interface LibraryPanelViewProps {
|
||||
className?: string;
|
||||
|
@ -1,14 +1,16 @@
|
||||
import React, { useState } from 'react';
|
||||
import { css } from 'emotion';
|
||||
import pick from 'lodash/pick';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { Button, stylesFactory, useStyles } from '@grafana/ui';
|
||||
|
||||
import { OptionsGroup } from 'app/features/dashboard/components/PanelEditor/OptionsGroup';
|
||||
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
||||
import { css } from 'emotion';
|
||||
import React, { useState } from 'react';
|
||||
import { AddLibraryPanelModal } from '../AddLibraryPanelModal/AddLibraryPanelModal';
|
||||
import { LibraryPanelsView } from '../LibraryPanelsView/LibraryPanelsView';
|
||||
import pick from 'lodash/pick';
|
||||
import { LibraryPanelDTO } from '../../state/api';
|
||||
import { PanelQueriesChangedEvent } from 'app/types/events';
|
||||
import { LibraryPanelDTO } from '../../types';
|
||||
import { toPanelModelLibraryPanel } from '../../utils';
|
||||
|
||||
interface Props {
|
||||
panel: PanelModel;
|
||||
@ -23,7 +25,7 @@ export const PanelLibraryOptionsGroup: React.FC<Props> = ({ panel, dashboard })
|
||||
panel.restoreModel({
|
||||
...panelInfo.model,
|
||||
...pick(panel, 'gridPos', 'id'),
|
||||
libraryPanel: pick(panelInfo, 'uid', 'name', 'meta'),
|
||||
libraryPanel: toPanelModelLibraryPanel(panelInfo),
|
||||
});
|
||||
|
||||
// dummy change for re-render
|
||||
|
@ -5,7 +5,8 @@ import { css } from 'emotion';
|
||||
import { useAsync, useDebounce } from 'react-use';
|
||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||
import { usePanelSave } from '../../utils/usePanelSave';
|
||||
import { getLibraryPanelConnectedDashboards, PanelModelWithLibraryPanel } from '../../state/api';
|
||||
import { getLibraryPanelConnectedDashboards } from '../../state/api';
|
||||
import { PanelModelWithLibraryPanel } from '../../types';
|
||||
|
||||
interface Props {
|
||||
panel: PanelModelWithLibraryPanel;
|
||||
|
6
public/app/features/library-panels/guard.ts
Normal file
6
public/app/features/library-panels/guard.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { PanelModel } from '../dashboard/state';
|
||||
import { PanelModelWithLibraryPanel } from './types';
|
||||
|
||||
export function isPanelModelLibraryPanel(panel: PanelModel): panel is PanelModelWithLibraryPanel {
|
||||
return Boolean(panel.libraryPanel?.uid);
|
||||
}
|
@ -1,36 +1,5 @@
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
|
||||
import { PanelModel } from '../../dashboard/state';
|
||||
|
||||
export interface LibraryPanelDTO {
|
||||
id: number;
|
||||
orgId: number;
|
||||
folderId: number;
|
||||
uid: string;
|
||||
name: string;
|
||||
model: any;
|
||||
version: number;
|
||||
meta: LibraryPanelDTOMeta;
|
||||
}
|
||||
|
||||
export interface LibraryPanelDTOMeta {
|
||||
canEdit: boolean;
|
||||
connectedDashboards: number;
|
||||
created: string;
|
||||
updated: string;
|
||||
createdBy: LibraryPanelDTOMetaUser;
|
||||
updatedBy: LibraryPanelDTOMetaUser;
|
||||
}
|
||||
|
||||
export interface LibraryPanelDTOMetaUser {
|
||||
id: number;
|
||||
name: string;
|
||||
avatarUrl: string;
|
||||
}
|
||||
|
||||
export interface PanelModelWithLibraryPanel extends PanelModel {
|
||||
libraryPanel: Pick<LibraryPanelDTO, 'uid' | 'name' | 'meta' | 'version'>;
|
||||
}
|
||||
import { LibraryPanelDTO, PanelModelWithLibraryPanel } from '../types';
|
||||
|
||||
export async function getLibraryPanels(): Promise<LibraryPanelDTO[]> {
|
||||
const { result } = await getBackendSrv().get(`/api/library-panels`);
|
||||
|
33
public/app/features/library-panels/types.ts
Normal file
33
public/app/features/library-panels/types.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { PanelModel } from '../dashboard/state';
|
||||
|
||||
export interface LibraryPanelDTO {
|
||||
id: number;
|
||||
orgId: number;
|
||||
folderId: number;
|
||||
uid: string;
|
||||
name: string;
|
||||
model: any;
|
||||
version: number;
|
||||
meta: LibraryPanelDTOMeta;
|
||||
}
|
||||
|
||||
export interface LibraryPanelDTOMeta {
|
||||
canEdit: boolean;
|
||||
connectedDashboards: number;
|
||||
created: string;
|
||||
updated: string;
|
||||
createdBy: LibraryPanelDTOMetaUser;
|
||||
updatedBy: LibraryPanelDTOMetaUser;
|
||||
}
|
||||
|
||||
export interface LibraryPanelDTOMetaUser {
|
||||
id: number;
|
||||
name: string;
|
||||
avatarUrl: string;
|
||||
}
|
||||
|
||||
export type PanelModelLibraryPanel = Pick<LibraryPanelDTO, 'uid' | 'name' | 'meta' | 'version'>;
|
||||
|
||||
export interface PanelModelWithLibraryPanel extends PanelModel {
|
||||
libraryPanel: PanelModelLibraryPanel;
|
||||
}
|
59
public/app/features/library-panels/utils.ts
Normal file
59
public/app/features/library-panels/utils.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { LibraryPanelDTO, PanelModelLibraryPanel } from './types';
|
||||
import { PanelModel } from '../dashboard/state';
|
||||
import { addLibraryPanel, updateLibraryPanel } from './state/api';
|
||||
import { createErrorNotification, createSuccessNotification } from '../../core/copy/appNotification';
|
||||
import { AppNotification } from '../../types';
|
||||
|
||||
export function createPanelLibraryErrorNotification(message: string): AppNotification {
|
||||
return createErrorNotification(message);
|
||||
}
|
||||
|
||||
export function createPanelLibrarySuccessNotification(message: string): AppNotification {
|
||||
return createSuccessNotification(message);
|
||||
}
|
||||
|
||||
export function toPanelModelLibraryPanel(libraryPanelDto: LibraryPanelDTO): PanelModelLibraryPanel {
|
||||
const { uid, name, meta, version } = libraryPanelDto;
|
||||
return { uid, name, meta, version };
|
||||
}
|
||||
|
||||
export async function saveAndRefreshLibraryPanel(panel: PanelModel, folderId: number): Promise<LibraryPanelDTO> {
|
||||
const panelSaveModel = toPanelSaveModel(panel);
|
||||
const savedPanel = await saveOrUpdateLibraryPanel(panelSaveModel, folderId);
|
||||
updatePanelModelWithUpdate(panel, savedPanel);
|
||||
return savedPanel;
|
||||
}
|
||||
|
||||
function toPanelSaveModel(panel: PanelModel): any {
|
||||
let panelSaveModel = panel.getSaveModel();
|
||||
panelSaveModel = {
|
||||
libraryPanel: {
|
||||
name: panel.title,
|
||||
uid: undefined,
|
||||
},
|
||||
...panelSaveModel,
|
||||
};
|
||||
|
||||
return panelSaveModel;
|
||||
}
|
||||
|
||||
function updatePanelModelWithUpdate(panel: PanelModel, updated: LibraryPanelDTO): void {
|
||||
panel.restoreModel({
|
||||
...updated.model,
|
||||
libraryPanel: toPanelModelLibraryPanel(updated),
|
||||
});
|
||||
panel.refresh();
|
||||
}
|
||||
|
||||
function saveOrUpdateLibraryPanel(panel: any, folderId: number): Promise<LibraryPanelDTO> {
|
||||
if (!panel.libraryPanel) {
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
if (panel.libraryPanel && panel.libraryPanel.uid === undefined) {
|
||||
panel.libraryPanel.name = panel.title;
|
||||
return addLibraryPanel(panel, folderId!);
|
||||
}
|
||||
|
||||
return updateLibraryPanel(panel, folderId!);
|
||||
}
|
@ -1,52 +1,27 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import useAsyncFn from 'react-use/lib/useAsyncFn';
|
||||
import { AppEvents } from '@grafana/data';
|
||||
import appEvents from 'app/core/app_events';
|
||||
|
||||
import { PanelModel } from 'app/features/dashboard/state';
|
||||
import { addLibraryPanel, updateLibraryPanel } from '../state/api';
|
||||
|
||||
const saveLibraryPanels = (panel: any, folderId: number) => {
|
||||
if (!panel.libraryPanel) {
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
if (panel.libraryPanel && panel.libraryPanel.uid === undefined) {
|
||||
panel.libraryPanel.name = panel.title;
|
||||
return addLibraryPanel(panel, folderId!);
|
||||
}
|
||||
|
||||
return updateLibraryPanel(panel, folderId!);
|
||||
};
|
||||
import {
|
||||
createPanelLibraryErrorNotification,
|
||||
createPanelLibrarySuccessNotification,
|
||||
saveAndRefreshLibraryPanel,
|
||||
} from '../utils';
|
||||
import { notifyApp } from 'app/core/actions';
|
||||
|
||||
export const usePanelSave = () => {
|
||||
const dispatch = useDispatch();
|
||||
const [state, saveLibraryPanel] = useAsyncFn(async (panel: PanelModel, folderId: number) => {
|
||||
let panelSaveModel = panel.getSaveModel();
|
||||
panelSaveModel = {
|
||||
libraryPanel: {
|
||||
name: panel.title,
|
||||
uid: undefined,
|
||||
},
|
||||
...panelSaveModel,
|
||||
};
|
||||
const savedPanel = await saveLibraryPanels(panelSaveModel, folderId);
|
||||
panel.restoreModel({
|
||||
...savedPanel.model,
|
||||
libraryPanel: {
|
||||
uid: savedPanel.uid,
|
||||
name: savedPanel.name,
|
||||
meta: savedPanel.meta,
|
||||
},
|
||||
});
|
||||
panel.refresh();
|
||||
return savedPanel;
|
||||
return await saveAndRefreshLibraryPanel(panel, folderId);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (state.error) {
|
||||
appEvents.emit(AppEvents.alertError, [`Error saving library panel: "${state.error.message}"`]);
|
||||
dispatch(notifyApp(createPanelLibraryErrorNotification(`Error saving library panel: "${state.error.message}"`)));
|
||||
}
|
||||
if (state.value) {
|
||||
appEvents.emit(AppEvents.alertSuccess, ['Library panel saved']);
|
||||
dispatch(notifyApp(createPanelLibrarySuccessNotification('Library panel saved')));
|
||||
}
|
||||
}, [state]);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user