mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Panels: Fixes crashing issue when migrating angular panels (#58232)
This commit is contained in:
@@ -66,10 +66,9 @@ export function updateDuplicateLibraryPanels(
|
|||||||
panel.configRev++;
|
panel.configRev++;
|
||||||
|
|
||||||
if (pluginChanged) {
|
if (pluginChanged) {
|
||||||
const cleanUpKey = panel.key;
|
|
||||||
panel.generateNewKey();
|
panel.generateNewKey();
|
||||||
|
|
||||||
dispatch(panelModelAndPluginReady({ key: panel.key, plugin: panel.plugin!, cleanUpKey }));
|
dispatch(panelModelAndPluginReady({ key: panel.key, plugin: panel.plugin! }));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resend last query result on source panel query runner
|
// Resend last query result on source panel query runner
|
||||||
@@ -129,10 +128,9 @@ export function exitPanelEditor(): ThunkResult<void> {
|
|||||||
if (panelTypeChanged) {
|
if (panelTypeChanged) {
|
||||||
// Loaded plugin is not included in the persisted properties so is not handled by restoreModel
|
// Loaded plugin is not included in the persisted properties so is not handled by restoreModel
|
||||||
sourcePanel.plugin = panel.plugin;
|
sourcePanel.plugin = panel.plugin;
|
||||||
const cleanUpKey = sourcePanel.key;
|
|
||||||
sourcePanel.generateNewKey();
|
sourcePanel.generateNewKey();
|
||||||
|
|
||||||
await dispatch(panelModelAndPluginReady({ key: sourcePanel.key, plugin: panel.plugin!, cleanUpKey }));
|
await dispatch(panelModelAndPluginReady({ key: sourcePanel.key, plugin: panel.plugin! }));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resend last query result on source panel query runner
|
// Resend last query result on source panel query runner
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { DashboardMeta } from 'app/types';
|
|||||||
|
|
||||||
import { DashboardModel } from '../state';
|
import { DashboardModel } from '../state';
|
||||||
|
|
||||||
import { DashboardGridUnconnected as DashboardGrid, Props } from './DashboardGrid';
|
import { DashboardGrid, Props } from './DashboardGrid';
|
||||||
|
|
||||||
jest.mock('app/features/dashboard/dashgrid/LazyLoader', () => {
|
jest.mock('app/features/dashboard/dashgrid/LazyLoader', () => {
|
||||||
const LazyLoader: React.FC = ({ children }) => {
|
const LazyLoader: React.FC = ({ children }) => {
|
||||||
@@ -58,7 +58,6 @@ describe('DashboardGrid', () => {
|
|||||||
editPanel: null,
|
editPanel: null,
|
||||||
viewPanel: null,
|
viewPanel: null,
|
||||||
dashboard: getTestDashboard(),
|
dashboard: getTestDashboard(),
|
||||||
cleanAndRemoveMany: jest.fn,
|
|
||||||
};
|
};
|
||||||
expect(() => render(<DashboardGrid {...props} />)).not.toThrow();
|
expect(() => render(<DashboardGrid {...props} />)).not.toThrow();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React, { PureComponent, CSSProperties } from 'react';
|
import React, { PureComponent, CSSProperties } from 'react';
|
||||||
import ReactGridLayout, { ItemCallback } from 'react-grid-layout';
|
import ReactGridLayout, { ItemCallback } from 'react-grid-layout';
|
||||||
import { connect, ConnectedProps } from 'react-redux';
|
|
||||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
|
|
||||||
import { config } from '@grafana/runtime';
|
import { config } from '@grafana/runtime';
|
||||||
import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, GRID_COLUMN_COUNT } from 'app/core/constants';
|
import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, GRID_COLUMN_COUNT } from 'app/core/constants';
|
||||||
import { cleanAndRemoveMany } from 'app/features/panel/state/actions';
|
|
||||||
import { DashboardPanelsChangedEvent } from 'app/types/events';
|
import { DashboardPanelsChangedEvent } from 'app/types/events';
|
||||||
|
|
||||||
import { AddPanelWidget } from '../components/AddPanelWidget';
|
import { AddPanelWidget } from '../components/AddPanelWidget';
|
||||||
@@ -17,7 +15,7 @@ import { GridPos } from '../state/PanelModel';
|
|||||||
|
|
||||||
import { DashboardPanel } from './DashboardPanel';
|
import { DashboardPanel } from './DashboardPanel';
|
||||||
|
|
||||||
export interface OwnProps {
|
export interface Props {
|
||||||
dashboard: DashboardModel;
|
dashboard: DashboardModel;
|
||||||
editPanel: PanelModel | null;
|
editPanel: PanelModel | null;
|
||||||
viewPanel: PanelModel | null;
|
viewPanel: PanelModel | null;
|
||||||
@@ -27,15 +25,7 @@ export interface State {
|
|||||||
isLayoutInitialized: boolean;
|
isLayoutInitialized: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
export class DashboardGrid extends PureComponent<Props, State> {
|
||||||
cleanAndRemoveMany,
|
|
||||||
};
|
|
||||||
|
|
||||||
const connector = connect(null, mapDispatchToProps);
|
|
||||||
|
|
||||||
export type Props = OwnProps & ConnectedProps<typeof connector>;
|
|
||||||
|
|
||||||
export class DashboardGridUnconnected extends PureComponent<Props, State> {
|
|
||||||
private panelMap: { [key: string]: PanelModel } = {};
|
private panelMap: { [key: string]: PanelModel } = {};
|
||||||
private eventSubs = new Subscription();
|
private eventSubs = new Subscription();
|
||||||
private windowHeight = 1200;
|
private windowHeight = 1200;
|
||||||
@@ -59,7 +49,6 @@ export class DashboardGridUnconnected extends PureComponent<Props, State> {
|
|||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.eventSubs.unsubscribe();
|
this.eventSubs.unsubscribe();
|
||||||
this.props.cleanAndRemoveMany(Object.keys(this.panelMap));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildLayout() {
|
buildLayout() {
|
||||||
@@ -323,5 +312,3 @@ function translateGridHeightToScreenHeight(gridHeight: number): number {
|
|||||||
}
|
}
|
||||||
|
|
||||||
GrafanaGridItem.displayName = 'GridItemWithDimensions';
|
GrafanaGridItem.displayName = 'GridItemWithDimensions';
|
||||||
|
|
||||||
export const DashboardGrid = connector(DashboardGridUnconnected);
|
|
||||||
|
|||||||
@@ -102,6 +102,9 @@ export class PanelChromeAngularUnconnected extends PureComponent<Props, State> {
|
|||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.subs.unsubscribe();
|
this.subs.unsubscribe();
|
||||||
|
if (this.props.angularComponent) {
|
||||||
|
this.props.angularComponent?.destroy();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps: Props, prevState: State) {
|
componentDidUpdate(prevProps: Props, prevState: State) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { getBackendSrv } from '@grafana/runtime';
|
|||||||
import { notifyApp } from 'app/core/actions';
|
import { notifyApp } from 'app/core/actions';
|
||||||
import { createSuccessNotification } from 'app/core/copy/appNotification';
|
import { createSuccessNotification } from 'app/core/copy/appNotification';
|
||||||
import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher';
|
import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher';
|
||||||
|
import { removeAllPanels } from 'app/features/panel/state/reducers';
|
||||||
import { updateTimeZoneForSession, updateWeekStartForSession } from 'app/features/profile/state/reducers';
|
import { updateTimeZoneForSession, updateWeekStartForSession } from 'app/features/profile/state/reducers';
|
||||||
import { DashboardAcl, DashboardAclUpdateDTO, NewDashboardAclItem, PermissionLevel, ThunkResult } from 'app/types';
|
import { DashboardAcl, DashboardAclUpdateDTO, NewDashboardAclItem, PermissionLevel, ThunkResult } from 'app/types';
|
||||||
|
|
||||||
@@ -125,6 +126,7 @@ export const cleanUpDashboardAndVariables = (): ThunkResult<void> => (dispatch,
|
|||||||
getTimeSrv().stopAutoRefresh();
|
getTimeSrv().stopAutoRefresh();
|
||||||
|
|
||||||
dispatch(cleanUpDashboard());
|
dispatch(cleanUpDashboard());
|
||||||
|
dispatch(removeAllPanels());
|
||||||
dashboardWatcher.leave();
|
dashboardWatcher.leave();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,13 +8,7 @@ import { loadPanelPlugin } from 'app/features/plugins/admin/state/actions';
|
|||||||
import { ThunkResult } from 'app/types';
|
import { ThunkResult } from 'app/types';
|
||||||
import { PanelOptionsChangedEvent, PanelQueriesChangedEvent } from 'app/types/events';
|
import { PanelOptionsChangedEvent, PanelQueriesChangedEvent } from 'app/types/events';
|
||||||
|
|
||||||
import {
|
import { changePanelKey, panelModelAndPluginReady, removePanel } from './reducers';
|
||||||
changePanelKey,
|
|
||||||
cleanUpAngularComponent,
|
|
||||||
panelModelAndPluginReady,
|
|
||||||
removePanel,
|
|
||||||
removePanels,
|
|
||||||
} from './reducers';
|
|
||||||
|
|
||||||
export function initPanelState(panel: PanelModel): ThunkResult<void> {
|
export function initPanelState(panel: PanelModel): ThunkResult<void> {
|
||||||
return async (dispatch, getStore) => {
|
return async (dispatch, getStore) => {
|
||||||
@@ -45,23 +39,11 @@ export function initPanelState(panel: PanelModel): ThunkResult<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function cleanUpPanelState(panelKey: string): ThunkResult<void> {
|
export function cleanUpPanelState(panelKey: string): ThunkResult<void> {
|
||||||
return (dispatch, getStore) => {
|
return (dispatch) => {
|
||||||
const store = getStore().panels;
|
|
||||||
cleanUpAngularComponent(store[panelKey]);
|
|
||||||
dispatch(removePanel({ key: panelKey }));
|
dispatch(removePanel({ key: panelKey }));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function cleanAndRemoveMany(panelKeys: string[]): ThunkResult<void> {
|
|
||||||
return (dispatch, getStore) => {
|
|
||||||
const store = getStore().panels;
|
|
||||||
for (const key of panelKeys) {
|
|
||||||
cleanUpAngularComponent(store[key]);
|
|
||||||
}
|
|
||||||
dispatch(removePanels({ keys: panelKeys }));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ChangePanelPluginAndOptionsArgs {
|
export interface ChangePanelPluginAndOptionsArgs {
|
||||||
panel: PanelModel;
|
panel: PanelModel;
|
||||||
pluginId: string;
|
pluginId: string;
|
||||||
@@ -89,8 +71,6 @@ export function changePanelPlugin({
|
|||||||
plugin = await dispatch(loadPanelPlugin(pluginId));
|
plugin = await dispatch(loadPanelPlugin(pluginId));
|
||||||
}
|
}
|
||||||
|
|
||||||
let cleanUpKey = panel.key;
|
|
||||||
|
|
||||||
if (panel.type !== pluginId) {
|
if (panel.type !== pluginId) {
|
||||||
panel.changePlugin(plugin);
|
panel.changePlugin(plugin);
|
||||||
}
|
}
|
||||||
@@ -110,7 +90,7 @@ export function changePanelPlugin({
|
|||||||
|
|
||||||
panel.generateNewKey();
|
panel.generateNewKey();
|
||||||
|
|
||||||
dispatch(panelModelAndPluginReady({ key: panel.key, plugin, cleanUpKey }));
|
dispatch(panelModelAndPluginReady({ key: panel.key, plugin }));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,12 +119,10 @@ export function changeToLibraryPanel(panel: PanelModel, libraryPanel: LibraryEle
|
|||||||
plugin = await dispatch(loadPanelPlugin(newPluginId));
|
plugin = await dispatch(loadPanelPlugin(newPluginId));
|
||||||
}
|
}
|
||||||
|
|
||||||
const oldKey = panel.key;
|
|
||||||
|
|
||||||
panel.pluginLoaded(plugin);
|
panel.pluginLoaded(plugin);
|
||||||
panel.generateNewKey();
|
panel.generateNewKey();
|
||||||
|
|
||||||
await dispatch(panelModelAndPluginReady({ key: panel.key, plugin, cleanUpKey: oldKey }));
|
await dispatch(panelModelAndPluginReady({ key: panel.key, plugin }));
|
||||||
} else {
|
} else {
|
||||||
// Even if the plugin is the same, we want to change the key
|
// Even if the plugin is the same, we want to change the key
|
||||||
// to force a rerender
|
// to force a rerender
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createSlice, Draft, PayloadAction } from '@reduxjs/toolkit';
|
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
import { PanelPlugin } from '@grafana/data';
|
import { PanelPlugin } from '@grafana/data';
|
||||||
import { AngularComponent } from '@grafana/runtime';
|
import { AngularComponent } from '@grafana/runtime';
|
||||||
@@ -18,11 +18,6 @@ const panelsSlice = createSlice({
|
|||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
panelModelAndPluginReady: (state, action: PayloadAction<PanelModelAndPluginReadyPayload>) => {
|
panelModelAndPluginReady: (state, action: PayloadAction<PanelModelAndPluginReadyPayload>) => {
|
||||||
if (action.payload.cleanUpKey) {
|
|
||||||
cleanUpAngularComponent(state[action.payload.cleanUpKey]);
|
|
||||||
delete state[action.payload.cleanUpKey];
|
|
||||||
}
|
|
||||||
|
|
||||||
state[action.payload.key] = {
|
state[action.payload.key] = {
|
||||||
plugin: action.payload.plugin,
|
plugin: action.payload.plugin,
|
||||||
};
|
};
|
||||||
@@ -34,33 +29,22 @@ const panelsSlice = createSlice({
|
|||||||
removePanel: (state, action: PayloadAction<{ key: string }>) => {
|
removePanel: (state, action: PayloadAction<{ key: string }>) => {
|
||||||
delete state[action.payload.key];
|
delete state[action.payload.key];
|
||||||
},
|
},
|
||||||
removePanels: (state, action: PayloadAction<{ keys: string[] }>) => {
|
removeAllPanels: (state) => {
|
||||||
for (const key of action.payload.keys) {
|
Object.keys(state).forEach((key) => delete state[key]);
|
||||||
delete state[key];
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
setPanelInstanceState: (state, action: PayloadAction<SetPanelInstanceStatePayload>) => {
|
setPanelInstanceState: (state, action: PayloadAction<SetPanelInstanceStatePayload>) => {
|
||||||
state[action.payload.key].instanceState = action.payload.value;
|
state[action.payload.key].instanceState = action.payload.value;
|
||||||
},
|
},
|
||||||
setPanelAngularComponent: (state, action: PayloadAction<SetPanelAngularComponentPayload>) => {
|
setPanelAngularComponent: (state, action: PayloadAction<SetPanelAngularComponentPayload>) => {
|
||||||
const panelState = state[action.payload.key];
|
const panelState = state[action.payload.key];
|
||||||
cleanUpAngularComponent(panelState);
|
|
||||||
panelState.angularComponent = action.payload.angularComponent;
|
panelState.angularComponent = action.payload.angularComponent;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export function cleanUpAngularComponent(panelState?: Draft<PanelState>) {
|
|
||||||
if (panelState?.angularComponent) {
|
|
||||||
panelState.angularComponent.destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PanelModelAndPluginReadyPayload {
|
export interface PanelModelAndPluginReadyPayload {
|
||||||
key: string;
|
key: string;
|
||||||
plugin: PanelPlugin;
|
plugin: PanelPlugin;
|
||||||
/** Used to cleanup previous state when we change key (used when switching panel plugin) */
|
|
||||||
cleanUpKey?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SetPanelAngularComponentPayload {
|
export interface SetPanelAngularComponentPayload {
|
||||||
@@ -79,7 +63,7 @@ export const {
|
|||||||
setPanelInstanceState,
|
setPanelInstanceState,
|
||||||
changePanelKey,
|
changePanelKey,
|
||||||
removePanel,
|
removePanel,
|
||||||
removePanels,
|
removeAllPanels,
|
||||||
} = panelsSlice.actions;
|
} = panelsSlice.actions;
|
||||||
|
|
||||||
export const panelsReducer = panelsSlice.reducer;
|
export const panelsReducer = panelsSlice.reducer;
|
||||||
|
|||||||
Reference in New Issue
Block a user