From 8f4e50f439a9286de969277331f8a19e2c5d0cac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Wed, 11 Nov 2020 06:19:09 +0100 Subject: [PATCH] Variables: Fixes loading with a custom all value in url (#28958) --- .../shared/testing/multiVariableBuilder.ts | 4 + .../features/variables/state/actions.test.ts | 42 ----- .../app/features/variables/state/actions.ts | 14 +- .../variables/state/setOptionFromUrl.test.ts | 154 ++++++++++++++++++ 4 files changed, 171 insertions(+), 43 deletions(-) create mode 100644 public/app/features/variables/state/setOptionFromUrl.test.ts diff --git a/public/app/features/variables/shared/testing/multiVariableBuilder.ts b/public/app/features/variables/shared/testing/multiVariableBuilder.ts index 3e1b1d5861f..f4da7108051 100644 --- a/public/app/features/variables/shared/testing/multiVariableBuilder.ts +++ b/public/app/features/variables/shared/testing/multiVariableBuilder.ts @@ -10,4 +10,8 @@ export class MultiVariableBuilder extends Op this.variable.includeAll = includeAll; return this; } + withAllValue(allValue: string) { + this.variable.allValue = allValue; + return this; + } } diff --git a/public/app/features/variables/state/actions.test.ts b/public/app/features/variables/state/actions.test.ts index 50885bf7a5c..a4c7ddc91e2 100644 --- a/public/app/features/variables/state/actions.test.ts +++ b/public/app/features/variables/state/actions.test.ts @@ -16,7 +16,6 @@ import { initDashboardTemplating, initVariablesTransaction, processVariables, - setOptionFromUrl, validateVariableSelectionState, } from './actions'; import { @@ -203,7 +202,6 @@ describe('shared actions', () => { const query = { orgId: '1', 'var-stats': 'response', 'var-substats': ALL_VARIABLE_TEXT }; const tester = await reduxTester<{ templating: TemplatingState; location: { query: UrlQueryMap } }>({ preloadedState: { templating: ({} as unknown) as TemplatingState, location: { query } }, - debug: true, }) .givenRootReducer(getTemplatingAndLocationRootReducer()) .whenActionIsDispatched(variablesInitTransaction({ uid: '' })) @@ -241,46 +239,6 @@ describe('shared actions', () => { }); }); - describe('when setOptionFromUrl is dispatched with a custom variable (no refresh property)', () => { - it.each` - urlValue | isMulti | expected - ${'B'} | ${false} | ${'B'} - ${['B']} | ${false} | ${'B'} - ${'X'} | ${false} | ${'X'} - ${''} | ${false} | ${''} - ${null} | ${false} | ${null} - ${undefined} | ${false} | ${undefined} - ${'B'} | ${true} | ${['B']} - ${['B']} | ${true} | ${['B']} - ${'X'} | ${true} | ${['X']} - ${''} | ${true} | ${['']} - ${['A', 'B']} | ${true} | ${['A', 'B']} - ${null} | ${true} | ${[null]} - ${undefined} | ${true} | ${[undefined]} - `('and urlValue is $urlValue then correct actions are dispatched', async ({ urlValue, expected, isMulti }) => { - const custom = customBuilder() - .withId('0') - .withMulti(isMulti) - .withOptions('A', 'B', 'C') - .withCurrent('A') - .build(); - - const tester = await reduxTester<{ templating: TemplatingState }>() - .givenRootReducer(getTemplatingRootReducer()) - .whenActionIsDispatched(addVariable(toVariablePayload(custom, { global: false, index: 0, model: custom }))) - .whenAsyncActionIsDispatched(setOptionFromUrl(toVariableIdentifier(custom), urlValue), true); - - await tester.thenDispatchedActionsShouldEqual( - setCurrentVariableValue( - toVariablePayload( - { type: 'custom', id: '0' }, - { option: { text: expected, value: expected, selected: false } } - ) - ) - ); - }); - }); - describe('when validateVariableSelectionState is dispatched with a custom variable (no dependencies)', () => { describe('and not multivalue', () => { it.each` diff --git a/public/app/features/variables/state/actions.ts b/public/app/features/variables/state/actions.ts index caf890cb826..89fcdbdf025 100644 --- a/public/app/features/variables/state/actions.ts +++ b/public/app/features/variables/state/actions.ts @@ -29,7 +29,13 @@ import { variableStateFetching, variableStateNotStarted, } from './sharedReducer'; -import { toVariableIdentifier, toVariablePayload, VariableIdentifier } from './types'; +import { + ALL_VARIABLE_TEXT, + ALL_VARIABLE_VALUE, + toVariableIdentifier, + toVariablePayload, + VariableIdentifier, +} from './types'; import { contextSrv } from 'app/core/services/context_srv'; import { getTemplateSrv, TemplateSrv } from '../../templating/template_srv'; import { alignCurrentWithMulti } from '../shared/multiOptions'; @@ -287,6 +293,12 @@ export const setOptionFromUrl = ( return op.text === urlValue || op.value === urlValue; }); + if (!option && isMulti(variableFromState)) { + if (variableFromState.allValue && urlValue === variableFromState.allValue) { + option = { text: ALL_VARIABLE_TEXT, value: ALL_VARIABLE_VALUE, selected: false }; + } + } + if (!option) { let defaultText = urlValue as string | string[]; const defaultValue = urlValue as string | string[]; diff --git a/public/app/features/variables/state/setOptionFromUrl.test.ts b/public/app/features/variables/state/setOptionFromUrl.test.ts new file mode 100644 index 00000000000..afcce5fdd11 --- /dev/null +++ b/public/app/features/variables/state/setOptionFromUrl.test.ts @@ -0,0 +1,154 @@ +import { variableAdapters } from '../adapters'; +import { createCustomVariableAdapter } from '../custom/adapter'; +import { customBuilder } from '../shared/testing/builders'; +import { reduxTester } from '../../../../test/core/redux/reduxTester'; +import { TemplatingState } from './reducers'; +import { getTemplatingRootReducer } from './helpers'; +import { addVariable, setCurrentVariableValue } from './sharedReducer'; +import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE, toVariableIdentifier, toVariablePayload } from './types'; +import { setOptionFromUrl } from './actions'; + +variableAdapters.setInit(() => [createCustomVariableAdapter()]); + +describe('when setOptionFromUrl is dispatched with a custom variable (no refresh property)', () => { + it.each` + urlValue | isMulti | expected + ${'B'} | ${false} | ${'B'} + ${['B']} | ${false} | ${'B'} + ${'X'} | ${false} | ${'X'} + ${''} | ${false} | ${''} + ${null} | ${false} | ${null} + ${undefined} | ${false} | ${undefined} + ${'B'} | ${true} | ${['B']} + ${['B']} | ${true} | ${['B']} + ${'X'} | ${true} | ${['X']} + ${''} | ${true} | ${['']} + ${['A', 'B']} | ${true} | ${['A', 'B']} + ${null} | ${true} | ${[null]} + ${undefined} | ${true} | ${[undefined]} + `('and urlValue is $urlValue then correct actions are dispatched', async ({ urlValue, expected, isMulti }) => { + const custom = customBuilder() + .withId('0') + .withMulti(isMulti) + .withOptions('A', 'B', 'C') + .withCurrent('A') + .build(); + + const tester = await reduxTester<{ templating: TemplatingState }>() + .givenRootReducer(getTemplatingRootReducer()) + .whenActionIsDispatched(addVariable(toVariablePayload(custom, { global: false, index: 0, model: custom }))) + .whenAsyncActionIsDispatched(setOptionFromUrl(toVariableIdentifier(custom), urlValue), true); + + await tester.thenDispatchedActionsShouldEqual( + setCurrentVariableValue( + toVariablePayload({ type: 'custom', id: '0' }, { option: { text: expected, value: expected, selected: false } }) + ) + ); + }); +}); + +describe('when setOptionFromUrl is dispatched for a variable with a custom all value', () => { + it('and urlValue contains same all value then correct actions are dispatched', async () => { + const allValue = '.*'; + const urlValue = allValue; + const custom = customBuilder() + .withId('0') + .withMulti(false) + .withIncludeAll() + .withAllValue(allValue) + .withOptions('A', 'B', 'C') + .withCurrent('A') + .build(); + + const tester = await reduxTester<{ templating: TemplatingState }>() + .givenRootReducer(getTemplatingRootReducer()) + .whenActionIsDispatched(addVariable(toVariablePayload(custom, { global: false, index: 0, model: custom }))) + .whenAsyncActionIsDispatched(setOptionFromUrl(toVariableIdentifier(custom), urlValue), true); + + await tester.thenDispatchedActionsShouldEqual( + setCurrentVariableValue( + toVariablePayload( + { type: 'custom', id: '0' }, + { option: { text: ALL_VARIABLE_TEXT, value: ALL_VARIABLE_VALUE, selected: false } } + ) + ) + ); + }); + + it('and urlValue differs from all value then correct actions are dispatched', async () => { + const allValue = '.*'; + const urlValue = 'X'; + const custom = customBuilder() + .withId('0') + .withMulti(false) + .withIncludeAll() + .withAllValue(allValue) + .withOptions('A', 'B', 'C') + .withCurrent('A') + .build(); + + const tester = await reduxTester<{ templating: TemplatingState }>() + .givenRootReducer(getTemplatingRootReducer()) + .whenActionIsDispatched(addVariable(toVariablePayload(custom, { global: false, index: 0, model: custom }))) + .whenAsyncActionIsDispatched(setOptionFromUrl(toVariableIdentifier(custom), urlValue), true); + + await tester.thenDispatchedActionsShouldEqual( + setCurrentVariableValue( + toVariablePayload({ type: 'custom', id: '0' }, { option: { text: 'X', value: 'X', selected: false } }) + ) + ); + }); + + it('and urlValue differs but matches an option then correct actions are dispatched', async () => { + const allValue = '.*'; + const urlValue = 'B'; + const custom = customBuilder() + .withId('0') + .withMulti(false) + .withIncludeAll() + .withAllValue(allValue) + .withOptions('A', 'B', 'C') + .withCurrent('A') + .build(); + + const tester = await reduxTester<{ templating: TemplatingState }>() + .givenRootReducer(getTemplatingRootReducer()) + .whenActionIsDispatched(addVariable(toVariablePayload(custom, { global: false, index: 0, model: custom }))) + .whenAsyncActionIsDispatched(setOptionFromUrl(toVariableIdentifier(custom), urlValue), true); + + await tester.thenDispatchedActionsShouldEqual( + setCurrentVariableValue( + toVariablePayload({ type: 'custom', id: '0' }, { option: { text: 'B', value: 'B', selected: false } }) + ) + ); + }); + + it('and custom all value matches an option', async () => { + const allValue = '.*'; + const urlValue = allValue; + const custom = customBuilder() + .withId('0') + .withMulti(false) + .withIncludeAll() + .withAllValue(allValue) + .withOptions('A', 'B', '.*') + .withCurrent('A') + .build(); + + custom.options[2].value = 'special value for .*'; + + const tester = await reduxTester<{ templating: TemplatingState }>() + .givenRootReducer(getTemplatingRootReducer()) + .whenActionIsDispatched(addVariable(toVariablePayload(custom, { global: false, index: 0, model: custom }))) + .whenAsyncActionIsDispatched(setOptionFromUrl(toVariableIdentifier(custom), urlValue), true); + + await tester.thenDispatchedActionsShouldEqual( + setCurrentVariableValue( + toVariablePayload( + { type: 'custom', id: '0' }, + { option: { text: '.*', value: 'special value for .*', selected: false } } + ) + ) + ); + }); +});