diff --git a/public/app/features/variables/state/actions.ts b/public/app/features/variables/state/actions.ts index 5a31fc12e89..85ead05d594 100644 --- a/public/app/features/variables/state/actions.ts +++ b/public/app/features/variables/state/actions.ts @@ -678,7 +678,9 @@ export const templateVarsChangedInUrl = async (dispatch, getState) => { const update: Array> = []; const dashboard = getState().dashboard.getModel(); - for (const variable of getVariablesByKey(key, getState())) { + const panelIds = new Set(); + const variables = getVariablesByKey(key, getState()); + for (const variable of variables) { const key = `var-${variable.name}`; if (!vars.hasOwnProperty(key)) { // key not found quick exit @@ -704,13 +706,26 @@ export const templateVarsChangedInUrl = } } + // for adhoc variables we don't know which panels that will be impacted + if (!isAdHoc(variable)) { + getAllAffectedPanelIdsForVariableChange(variable.id, variables, dashboard?.panels ?? []).forEach((id) => + panelIds.add(id) + ); + } + const promise = variableAdapters.get(variable.type).setValueFromUrl(variable, value); update.push(promise); } if (update.length) { await Promise.all(update); - events.publish(new VariablesChangedInUrl({ panelIds: [], refreshAll: true })); + + events.publish( + new VariablesChangedInUrl({ + refreshAll: panelIds.size === 0, + panelIds: Array.from(panelIds), + }) + ); } }; diff --git a/public/app/features/variables/state/templateVarsChangedInUrl.test.ts b/public/app/features/variables/state/templateVarsChangedInUrl.test.ts index d50ad5d8dcd..a98eee8f0d8 100644 --- a/public/app/features/variables/state/templateVarsChangedInUrl.test.ts +++ b/public/app/features/variables/state/templateVarsChangedInUrl.test.ts @@ -1,5 +1,5 @@ import { DashboardState, StoreState } from '../../../types'; -import { DashboardModel } from '../../dashboard/state'; +import { DashboardModel, PanelModel } from '../../dashboard/state'; import { initialState } from '../../dashboard/state/reducers'; import { variableAdapters } from '../adapters'; import { createConstantVariableAdapter } from '../constant/adapter'; @@ -30,9 +30,62 @@ async function getTestContext(urlQueryMap: ExtendedUrlQueryMap = {}, variable: V .build(); } + const variableB = customBuilder() + .withId('variableB') + .withRootStateKey(key) + .withName('variableB') + .withCurrent(['B']) + .withOptions('A', 'B', 'C') + .build(); + const setValueFromUrlMock = jest.fn(); variableAdapters.get(variable.type).setValueFromUrl = setValueFromUrlMock; + const modelJson = { + id: 1, + type: 'table', + maxDataPoints: 100, + interval: '5m', + showColumns: true, + targets: [{ refId: 'A', queryType: '${variable}' }, { noRefId: true }], + options: null, + fieldConfig: { + defaults: { + unit: 'mpg', + thresholds: { + mode: 'absolute', + steps: [ + { color: 'green', value: null }, + { color: 'red', value: 80 }, + ], + }, + }, + overrides: [ + { + matcher: { + id: '1', + options: {}, + }, + properties: [ + { + id: 'thresholds', + value: { + mode: 'absolute', + steps: [ + { color: 'green', value: null }, + { color: 'red', value: 80 }, + ], + }, + }, + ], + }, + ], + }, + }; + + const panelModelA = new PanelModel(modelJson); + const panelModelB = new PanelModel({ ...modelJson, id: 2, targets: [{ refId: 'B', queryType: '${variableB}' }] }); + const templateVariableValueUpdatedMock = jest.fn(); const startRefreshMock = jest.fn(); const dashboard: DashboardState = { @@ -41,11 +94,12 @@ async function getTestContext(urlQueryMap: ExtendedUrlQueryMap = {}, variable: V dashboardModel.templateVariableValueUpdated = templateVariableValueUpdatedMock; dashboardModel.startRefresh = startRefreshMock; dashboardModel.templating = { list: [variable] }; + dashboardModel.panels = [panelModelA, panelModelB]; return dashboardModel; }, }; - const variables: VariablesState = { variable }; + const variables: VariablesState = { variable, variableB }; const state: Partial = { dashboard, ...getPreloadedState(key, { variables }), @@ -57,7 +111,7 @@ async function getTestContext(urlQueryMap: ExtendedUrlQueryMap = {}, variable: V await thunk(dispatch, getState, undefined); - return { setValueFromUrlMock, templateVariableValueUpdatedMock, startRefreshMock, variable }; + return { setValueFromUrlMock, templateVariableValueUpdatedMock, startRefreshMock, variable, variableB }; } describe('templateVarsChangedInUrl', () => { @@ -107,6 +161,20 @@ describe('templateVarsChangedInUrl', () => { expect(setValueFromUrlMock).toHaveBeenCalledWith(variable, 'B'); expect(templateVariableValueUpdatedMock).toHaveBeenCalledTimes(1); expect(startRefreshMock).toHaveBeenCalledTimes(1); + expect(startRefreshMock).toHaveBeenCalledWith({ refreshAll: false, panelIds: [1] }); + }); + + it('should update URL value and only refresh panels with variableB dependency', async () => { + const { setValueFromUrlMock, templateVariableValueUpdatedMock, startRefreshMock, variableB } = + await getTestContext({ + 'var-variableB': { value: 'A' }, + }); + + expect(setValueFromUrlMock).toHaveBeenCalledTimes(1); + expect(setValueFromUrlMock).toHaveBeenCalledWith(variableB, 'A'); + expect(templateVariableValueUpdatedMock).toHaveBeenCalledTimes(1); + expect(startRefreshMock).toHaveBeenCalledTimes(1); + expect(startRefreshMock).toHaveBeenCalledWith({ refreshAll: false, panelIds: [2] }); }); describe('but the values in url query map were removed', () => {