Co-authored-by: Jacob Zelek <jzelek@linkedin.com> Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com>
This commit is contained in:
co-authored by
Jacob Zelek
Dominik Prokop
parent
513761e36e
commit
70a62d5fcf
+1
-1
@@ -3420,7 +3420,7 @@ exports[`better eslint`] = {
|
|||||||
[0, 0, 0, "Styles should be written using objects.", "0"]
|
[0, 0, 0, "Styles should be written using objects.", "0"]
|
||||||
],
|
],
|
||||||
"public/app/features/dashboard/dashgrid/DashboardGrid.tsx:5381": [
|
"public/app/features/dashboard/dashgrid/DashboardGrid.tsx:5381": [
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
[0, 0, 0, "Do not use any type assertions.", "0"]
|
||||||
],
|
],
|
||||||
"public/app/features/dashboard/dashgrid/DashboardPanel.tsx:5381": [
|
"public/app/features/dashboard/dashgrid/DashboardPanel.tsx:5381": [
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||||
|
|||||||
@@ -145,4 +145,5 @@ export interface FeatureToggles {
|
|||||||
cachingOptimizeSerializationMemoryUsage?: boolean;
|
cachingOptimizeSerializationMemoryUsage?: boolean;
|
||||||
panelTitleSearchInV1?: boolean;
|
panelTitleSearchInV1?: boolean;
|
||||||
pluginsInstrumentationStatusSource?: boolean;
|
pluginsInstrumentationStatusSource?: boolean;
|
||||||
|
panelFilterVariable?: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -891,5 +891,13 @@ var (
|
|||||||
Stage: FeatureStageExperimental,
|
Stage: FeatureStageExperimental,
|
||||||
Owner: grafanaPluginsPlatformSquad,
|
Owner: grafanaPluginsPlatformSquad,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "panelFilterVariable",
|
||||||
|
Description: "Enables use of the `systemPanelFilterVar` variable to filter panels in a dashboard",
|
||||||
|
Stage: FeatureStageExperimental,
|
||||||
|
FrontendOnly: true,
|
||||||
|
Owner: grafanaDashboardsSquad,
|
||||||
|
HideFromDocs: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -126,3 +126,4 @@ awsDatasourcesNewFormStyling,experimental,@grafana/aws-datasources,false,false,f
|
|||||||
cachingOptimizeSerializationMemoryUsage,experimental,@grafana/grafana-operator-experience-squad,false,false,false,false
|
cachingOptimizeSerializationMemoryUsage,experimental,@grafana/grafana-operator-experience-squad,false,false,false,false
|
||||||
panelTitleSearchInV1,experimental,@grafana/backend-platform,true,false,false,false
|
panelTitleSearchInV1,experimental,@grafana/backend-platform,true,false,false,false
|
||||||
pluginsInstrumentationStatusSource,experimental,@grafana/plugins-platform-backend,false,false,false,false
|
pluginsInstrumentationStatusSource,experimental,@grafana/plugins-platform-backend,false,false,false,false
|
||||||
|
panelFilterVariable,experimental,@grafana/dashboards-squad,false,false,false,true
|
||||||
|
|||||||
|
@@ -514,4 +514,8 @@ const (
|
|||||||
// FlagPluginsInstrumentationStatusSource
|
// FlagPluginsInstrumentationStatusSource
|
||||||
// Include a status source label for plugin request metrics and logs
|
// Include a status source label for plugin request metrics and logs
|
||||||
FlagPluginsInstrumentationStatusSource = "pluginsInstrumentationStatusSource"
|
FlagPluginsInstrumentationStatusSource = "pluginsInstrumentationStatusSource"
|
||||||
|
|
||||||
|
// FlagPanelFilterVariable
|
||||||
|
// Enables use of the `systemPanelFilterVar` variable to filter panels in a dashboard
|
||||||
|
FlagPanelFilterVariable = "panelFilterVariable"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,23 +1,73 @@
|
|||||||
import { render } from '@testing-library/react';
|
import { act, render, screen } from '@testing-library/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import { Router } from 'react-router-dom';
|
||||||
|
import { useEffectOnce } from 'react-use';
|
||||||
|
import { AutoSizerProps } from 'react-virtualized-auto-sizer';
|
||||||
|
import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock';
|
||||||
|
|
||||||
|
import { TextBoxVariableModel } from '@grafana/data';
|
||||||
|
import { locationService } from '@grafana/runtime';
|
||||||
import { Dashboard } from '@grafana/schema';
|
import { Dashboard } from '@grafana/schema';
|
||||||
|
import appEvents from 'app/core/app_events';
|
||||||
|
import { GrafanaContext } from 'app/core/context/GrafanaContext';
|
||||||
|
import { GetVariables } from 'app/features/variables/state/selectors';
|
||||||
|
import { VariablesChanged } from 'app/features/variables/types';
|
||||||
|
import { configureStore } from 'app/store/configureStore';
|
||||||
import { DashboardMeta } from 'app/types';
|
import { DashboardMeta } from 'app/types';
|
||||||
|
|
||||||
import { DashboardModel } from '../state';
|
import { DashboardModel } from '../state';
|
||||||
import { createDashboardModelFixture } from '../state/__fixtures__/dashboardFixtures';
|
import { createDashboardModelFixture } from '../state/__fixtures__/dashboardFixtures';
|
||||||
|
|
||||||
import { DashboardGrid, Props } from './DashboardGrid';
|
import { DashboardGrid, PANEL_FILTER_VARIABLE, Props } from './DashboardGrid';
|
||||||
import { Props as LazyLoaderProps } from './LazyLoader';
|
import { Props as LazyLoaderProps } from './LazyLoader';
|
||||||
|
|
||||||
|
jest.mock('@grafana/runtime', () => ({
|
||||||
|
...jest.requireActual('@grafana/runtime'),
|
||||||
|
config: {
|
||||||
|
...jest.requireActual('@grafana/runtime').config,
|
||||||
|
featureToggles: {
|
||||||
|
panelFilterVariable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
jest.mock('app/features/dashboard/dashgrid/LazyLoader', () => {
|
jest.mock('app/features/dashboard/dashgrid/LazyLoader', () => {
|
||||||
const LazyLoader = ({ children }: LazyLoaderProps) => {
|
const LazyLoader = ({ children, onLoad }: Pick<LazyLoaderProps, 'children' | 'onLoad'>) => {
|
||||||
return <>{children}</>;
|
useEffectOnce(() => {
|
||||||
|
onLoad?.();
|
||||||
|
});
|
||||||
|
return <>{typeof children === 'function' ? children({ isInView: true }) : children}</>;
|
||||||
};
|
};
|
||||||
return { LazyLoader };
|
return { LazyLoader };
|
||||||
});
|
});
|
||||||
|
|
||||||
function getTestDashboard(overrides?: Partial<Dashboard>, metaOverrides?: Partial<DashboardMeta>): DashboardModel {
|
jest.mock('react-virtualized-auto-sizer', () => {
|
||||||
|
// The size of the children need to be small enough to be outside the view.
|
||||||
|
// So it does not trigger the query to be run by the PanelQueryRunner.
|
||||||
|
return ({ children }: AutoSizerProps) => children({ height: 1, width: 1 });
|
||||||
|
});
|
||||||
|
|
||||||
|
function setup(props: Props) {
|
||||||
|
const context = getGrafanaContextMock();
|
||||||
|
const store = configureStore({});
|
||||||
|
|
||||||
|
return render(
|
||||||
|
<GrafanaContext.Provider value={context}>
|
||||||
|
<Provider store={store}>
|
||||||
|
<Router history={locationService.getHistory()}>
|
||||||
|
<DashboardGrid {...props} />
|
||||||
|
</Router>
|
||||||
|
</Provider>
|
||||||
|
</GrafanaContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTestDashboard(
|
||||||
|
overrides?: Partial<Dashboard>,
|
||||||
|
metaOverrides?: Partial<DashboardMeta>,
|
||||||
|
getVariablesFromState?: GetVariables
|
||||||
|
): DashboardModel {
|
||||||
const data = Object.assign(
|
const data = Object.assign(
|
||||||
{
|
{
|
||||||
title: 'My dashboard',
|
title: 'My dashboard',
|
||||||
@@ -30,20 +80,20 @@ function getTestDashboard(overrides?: Partial<Dashboard>, metaOverrides?: Partia
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
type: 'graph2',
|
type: 'table',
|
||||||
title: 'My graph2',
|
title: 'My table',
|
||||||
gridPos: { x: 0, y: 10, w: 25, h: 10 },
|
gridPos: { x: 0, y: 10, w: 25, h: 10 },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 3,
|
id: 3,
|
||||||
type: 'graph3',
|
type: 'table',
|
||||||
title: 'My graph3',
|
title: 'My table 2',
|
||||||
gridPos: { x: 0, y: 20, w: 25, h: 100 },
|
gridPos: { x: 0, y: 20, w: 25, h: 100 },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 4,
|
id: 4,
|
||||||
type: 'graph4',
|
type: 'gauge',
|
||||||
title: 'My graph4',
|
title: 'My gauge',
|
||||||
gridPos: { x: 0, y: 120, w: 25, h: 10 },
|
gridPos: { x: 0, y: 120, w: 25, h: 10 },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -51,17 +101,88 @@ function getTestDashboard(overrides?: Partial<Dashboard>, metaOverrides?: Partia
|
|||||||
overrides
|
overrides
|
||||||
);
|
);
|
||||||
|
|
||||||
return createDashboardModelFixture(data, metaOverrides);
|
return createDashboardModelFixture(data, metaOverrides, getVariablesFromState);
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('DashboardGrid', () => {
|
describe('DashboardGrid', () => {
|
||||||
it('should render without error', () => {
|
it('Should render panels', async () => {
|
||||||
const props: Props = {
|
const props: Props = {
|
||||||
editPanel: null,
|
editPanel: null,
|
||||||
viewPanel: null,
|
viewPanel: null,
|
||||||
isEditable: true,
|
isEditable: true,
|
||||||
dashboard: getTestDashboard(),
|
dashboard: getTestDashboard(),
|
||||||
};
|
};
|
||||||
expect(() => render(<DashboardGrid {...props} />)).not.toThrow();
|
|
||||||
|
act(() => {
|
||||||
|
setup(props);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(await screen.findByText('My graph')).toBeInTheDocument();
|
||||||
|
expect(await screen.findByText('My table')).toBeInTheDocument();
|
||||||
|
expect(await screen.findByText('My table 2')).toBeInTheDocument();
|
||||||
|
expect(await screen.findByText('My gauge')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should allow filtering panels', async () => {
|
||||||
|
const props: Props = {
|
||||||
|
editPanel: null,
|
||||||
|
viewPanel: null,
|
||||||
|
isEditable: true,
|
||||||
|
dashboard: getTestDashboard(),
|
||||||
|
};
|
||||||
|
act(() => {
|
||||||
|
setup(props);
|
||||||
|
});
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
appEvents.publish(
|
||||||
|
new VariablesChanged({
|
||||||
|
panelIds: [],
|
||||||
|
refreshAll: false,
|
||||||
|
variable: {
|
||||||
|
type: 'textbox',
|
||||||
|
id: PANEL_FILTER_VARIABLE,
|
||||||
|
current: {
|
||||||
|
value: 'My graph',
|
||||||
|
},
|
||||||
|
} as TextBoxVariableModel,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
const table = screen.queryByText('My table');
|
||||||
|
const table2 = screen.queryByText('My table 2');
|
||||||
|
const gauge = screen.queryByText('My gauge');
|
||||||
|
|
||||||
|
expect(await screen.findByText('My graph')).toBeInTheDocument();
|
||||||
|
expect(table).toBeNull();
|
||||||
|
expect(table2).toBeNull();
|
||||||
|
expect(gauge).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should rendered filtered panels on init when filter variable is present', async () => {
|
||||||
|
const props: Props = {
|
||||||
|
editPanel: null,
|
||||||
|
viewPanel: null,
|
||||||
|
isEditable: true,
|
||||||
|
dashboard: getTestDashboard(undefined, undefined, () => [
|
||||||
|
{
|
||||||
|
id: PANEL_FILTER_VARIABLE,
|
||||||
|
type: 'textbox',
|
||||||
|
query: 'My tab',
|
||||||
|
} as TextBoxVariableModel,
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
setup(props);
|
||||||
|
});
|
||||||
|
|
||||||
|
const graph = screen.queryByText('My graph');
|
||||||
|
const gauge = screen.queryByText('My gauge');
|
||||||
|
|
||||||
|
expect(await screen.findByText('My table')).toBeInTheDocument();
|
||||||
|
expect(await screen.findByText('My table 2')).toBeInTheDocument();
|
||||||
|
expect(graph).toBeNull();
|
||||||
|
expect(gauge).toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ 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 appEvents from 'app/core/app_events';
|
||||||
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 { contextSrv } from 'app/core/services/context_srv';
|
import { contextSrv } from 'app/core/services/context_srv';
|
||||||
|
import { VariablesChanged } from 'app/features/variables/types';
|
||||||
import { DashboardPanelsChangedEvent } from 'app/types/events';
|
import { DashboardPanelsChangedEvent } from 'app/types/events';
|
||||||
|
|
||||||
import { AddLibraryPanelWidget } from '../components/AddLibraryPanelWidget';
|
import { AddLibraryPanelWidget } from '../components/AddLibraryPanelWidget';
|
||||||
@@ -18,6 +20,8 @@ import { GridPos } from '../state/PanelModel';
|
|||||||
import DashboardEmpty from './DashboardEmpty';
|
import DashboardEmpty from './DashboardEmpty';
|
||||||
import { DashboardPanel } from './DashboardPanel';
|
import { DashboardPanel } from './DashboardPanel';
|
||||||
|
|
||||||
|
export const PANEL_FILTER_VARIABLE = 'systemPanelFilterVar';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
dashboard: DashboardModel;
|
dashboard: DashboardModel;
|
||||||
isEditable: boolean;
|
isEditable: boolean;
|
||||||
@@ -25,7 +29,12 @@ export interface Props {
|
|||||||
viewPanel: PanelModel | null;
|
viewPanel: PanelModel | null;
|
||||||
hidePanelMenus?: boolean;
|
hidePanelMenus?: boolean;
|
||||||
}
|
}
|
||||||
export class DashboardGrid extends PureComponent<Props> {
|
|
||||||
|
interface State {
|
||||||
|
panelFilter?: RegExp;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DashboardGrid 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;
|
||||||
@@ -37,10 +46,43 @@ export class DashboardGrid extends PureComponent<Props> {
|
|||||||
|
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
panelFilter: undefined,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { dashboard } = this.props;
|
const { dashboard } = this.props;
|
||||||
|
|
||||||
|
if (config.featureToggles.panelFilterVariable) {
|
||||||
|
// If panel filter variable is set on load then
|
||||||
|
// update state to filter panels
|
||||||
|
for (const variable of dashboard.getVariables()) {
|
||||||
|
if (variable.id === PANEL_FILTER_VARIABLE) {
|
||||||
|
if ('query' in variable) {
|
||||||
|
this.setPanelFilter(variable.query);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.eventSubs.add(
|
||||||
|
appEvents.subscribe(VariablesChanged, (e) => {
|
||||||
|
if (e.payload.variable?.id === PANEL_FILTER_VARIABLE) {
|
||||||
|
if ('current' in e.payload.variable) {
|
||||||
|
let variable = e.payload.variable.current;
|
||||||
|
if ('value' in variable) {
|
||||||
|
let value = variable.value;
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
this.setPanelFilter(value as string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
this.eventSubs.add(dashboard.events.subscribe(DashboardPanelsChangedEvent, this.triggerForceUpdate));
|
this.eventSubs.add(dashboard.events.subscribe(DashboardPanelsChangedEvent, this.triggerForceUpdate));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,10 +90,25 @@ export class DashboardGrid extends PureComponent<Props> {
|
|||||||
this.eventSubs.unsubscribe();
|
this.eventSubs.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setPanelFilter(regex: string) {
|
||||||
|
// Only set the panels filter if the systemPanelFilterVar variable
|
||||||
|
// is a non-empty string
|
||||||
|
let panelFilter = undefined;
|
||||||
|
if (regex.length > 0) {
|
||||||
|
panelFilter = new RegExp(regex, 'i');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
panelFilter: panelFilter,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
buildLayout() {
|
buildLayout() {
|
||||||
const layout: ReactGridLayout.Layout[] = [];
|
const layout: ReactGridLayout.Layout[] = [];
|
||||||
this.panelMap = {};
|
this.panelMap = {};
|
||||||
|
const { panelFilter } = this.state;
|
||||||
|
|
||||||
|
let count = 0;
|
||||||
for (const panel of this.props.dashboard.panels) {
|
for (const panel of this.props.dashboard.panels) {
|
||||||
if (!panel.key) {
|
if (!panel.key) {
|
||||||
panel.key = `panel-${panel.id}-${Date.now()}`;
|
panel.key = `panel-${panel.id}-${Date.now()}`;
|
||||||
@@ -78,13 +135,27 @@ export class DashboardGrid extends PureComponent<Props> {
|
|||||||
panelPos.isDraggable = panel.collapsed;
|
panelPos.isDraggable = panel.collapsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
layout.push(panelPos);
|
if (!panelFilter) {
|
||||||
|
layout.push(panelPos);
|
||||||
|
} else {
|
||||||
|
if (panelFilter.test(panel.title)) {
|
||||||
|
panelPos.isResizable = false;
|
||||||
|
panelPos.isDraggable = false;
|
||||||
|
panelPos.x = (count % 2) * GRID_COLUMN_COUNT;
|
||||||
|
panelPos.y = Math.floor(count / 2);
|
||||||
|
layout.push(panelPos);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return layout;
|
return layout;
|
||||||
}
|
}
|
||||||
|
|
||||||
onLayoutChange = (newLayout: ReactGridLayout.Layout[]) => {
|
onLayoutChange = (newLayout: ReactGridLayout.Layout[]) => {
|
||||||
|
if (this.state.panelFilter) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
for (const newPos of newLayout) {
|
for (const newPos of newLayout) {
|
||||||
this.panelMap[newPos.i!].updateGridPos(newPos, this.isLayoutInitialized);
|
this.panelMap[newPos.i!].updateGridPos(newPos, this.isLayoutInitialized);
|
||||||
}
|
}
|
||||||
@@ -136,6 +207,7 @@ export class DashboardGrid extends PureComponent<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderPanels(gridWidth: number, isDashboardDraggable: boolean) {
|
renderPanels(gridWidth: number, isDashboardDraggable: boolean) {
|
||||||
|
const { panelFilter } = this.state;
|
||||||
const panelElements = [];
|
const panelElements = [];
|
||||||
|
|
||||||
// Reset last panel bottom
|
// Reset last panel bottom
|
||||||
@@ -156,7 +228,7 @@ export class DashboardGrid extends PureComponent<Props> {
|
|||||||
// requires parent create stacking context to prevent overlap with parent elements
|
// requires parent create stacking context to prevent overlap with parent elements
|
||||||
const descIndex = this.props.dashboard.panels.length - panelElements.length;
|
const descIndex = this.props.dashboard.panels.length - panelElements.length;
|
||||||
|
|
||||||
panelElements.push(
|
const p = (
|
||||||
<GrafanaGridItem
|
<GrafanaGridItem
|
||||||
key={panel.key}
|
key={panel.key}
|
||||||
className={panelClasses}
|
className={panelClasses}
|
||||||
@@ -173,6 +245,14 @@ export class DashboardGrid extends PureComponent<Props> {
|
|||||||
}}
|
}}
|
||||||
</GrafanaGridItem>
|
</GrafanaGridItem>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!panelFilter) {
|
||||||
|
panelElements.push(p);
|
||||||
|
} else {
|
||||||
|
if (panelFilter.test(panel.title)) {
|
||||||
|
panelElements.push(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return panelElements;
|
return panelElements;
|
||||||
@@ -295,7 +375,7 @@ interface GrafanaGridItemProps extends React.HTMLAttributes<HTMLDivElement> {
|
|||||||
isViewing: boolean;
|
isViewing: boolean;
|
||||||
windowHeight: number;
|
windowHeight: number;
|
||||||
windowWidth: number;
|
windowWidth: number;
|
||||||
children: any;
|
children: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -623,6 +623,7 @@ export const variableUpdated = (
|
|||||||
: {
|
: {
|
||||||
refreshAll: false,
|
refreshAll: false,
|
||||||
panelIds: Array.from(getAllAffectedPanelIdsForVariableChange([variableInState.id], g, panelVars)),
|
panelIds: Array.from(getAllAffectedPanelIdsForVariableChange([variableInState.id], g, panelVars)),
|
||||||
|
variable: getVariable(identifier, state),
|
||||||
};
|
};
|
||||||
|
|
||||||
const node = g.getNode(variableInState.name);
|
const node = g.getNode(variableInState.name);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
QueryEditorProps,
|
QueryEditorProps,
|
||||||
BaseVariableModel,
|
BaseVariableModel,
|
||||||
VariableHide,
|
VariableHide,
|
||||||
|
TypedVariableModel,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
export {
|
export {
|
||||||
/** @deprecated Import from @grafana/data instead */
|
/** @deprecated Import from @grafana/data instead */
|
||||||
@@ -95,6 +96,7 @@ export type VariableQueryEditorType<
|
|||||||
export interface VariablesChangedEvent {
|
export interface VariablesChangedEvent {
|
||||||
refreshAll: boolean;
|
refreshAll: boolean;
|
||||||
panelIds: number[];
|
panelIds: number[];
|
||||||
|
variable?: TypedVariableModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class VariablesChanged extends BusEventWithPayload<VariablesChangedEvent> {
|
export class VariablesChanged extends BusEventWithPayload<VariablesChangedEvent> {
|
||||||
|
|||||||
Reference in New Issue
Block a user