From 0788102b1e4da8b9c47ec508985385a25b18135d Mon Sep 17 00:00:00 2001 From: Galen Kistler <109082771+gtk-grafana@users.noreply.github.com> Date: Fri, 20 Dec 2024 14:40:04 -0600 Subject: [PATCH] AdHocFiltersVariable: provide updateFilters method to allow updating filters without emitting SceneVariableValueChangedEvent (#1004) --- .../adhoc/AdHocFiltersVariable.test.tsx | 128 ++++++++++++++++++ .../variables/adhoc/AdHocFiltersVariable.tsx | 30 ++++ 2 files changed, 158 insertions(+) diff --git a/packages/scenes/src/variables/adhoc/AdHocFiltersVariable.test.tsx b/packages/scenes/src/variables/adhoc/AdHocFiltersVariable.test.tsx index 87964784a..b9f89f287 100644 --- a/packages/scenes/src/variables/adhoc/AdHocFiltersVariable.test.tsx +++ b/packages/scenes/src/variables/adhoc/AdHocFiltersVariable.test.tsx @@ -958,6 +958,134 @@ describe.each(['11.1.2', '11.1.1'])('AdHocFiltersVariable', (v) => { expect(evtHandler).not.toHaveBeenCalled(); }); + it('Should not overwrite filterExpression on setState', () => { + const variable = new AdHocFiltersVariable({ + datasource: { uid: 'hello' }, + applyMode: 'manual', + filters: [{ key: 'key1', operator: '=', value: 'val1' }], + filterExpression: '', + }); + + variable.activate(); + + const evtHandler = jest.fn(); + variable.subscribeToEvent(SceneVariableValueChangedEvent, evtHandler); + + variable.setState({ filters: variable.state.filters.slice(0), filterExpression: 'hello filter expression!' }); + + expect(evtHandler).not.toHaveBeenCalled(); + expect(variable.state.filterExpression).toEqual('hello filter expression!'); + }); + + it('Should overwrite filterExpression on updateFilters', () => { + const variable = new AdHocFiltersVariable({ + datasource: { uid: 'hello' }, + applyMode: 'manual', + filters: [{ key: 'key1', operator: '=', value: 'val1' }], + filterExpression: 'hello filter expression!', + }); + + variable.activate(); + + const evtHandler = jest.fn(); + variable.subscribeToEvent(SceneVariableValueChangedEvent, evtHandler); + + variable.updateFilters(variable.state.filters.slice(0)); + + expect(evtHandler).toHaveBeenCalled(); + expect(variable.state.filterExpression).toEqual('key1="val1"'); + }); + + it('updateFilters should not publish event when expr did not change', () => { + const variable = new AdHocFiltersVariable({ + datasource: { uid: 'hello' }, + applyMode: 'manual', + filters: [{ key: 'key1', operator: '=', value: 'val1' }], + }); + + variable.activate(); + + const evtHandler = jest.fn(); + variable.subscribeToEvent(SceneVariableValueChangedEvent, evtHandler); + + variable.updateFilters(variable.state.filters.slice(0)); + + expect(evtHandler).not.toHaveBeenCalled(); + }); + + it('updateFilters should publish event when expr did not change, but forcePublish is set', () => { + const variable = new AdHocFiltersVariable({ + datasource: { uid: 'hello' }, + applyMode: 'manual', + filters: [{ key: 'key1', operator: '=', value: 'val1' }], + }); + + variable.activate(); + + const evtHandler = jest.fn(); + variable.subscribeToEvent(SceneVariableValueChangedEvent, evtHandler); + + variable.updateFilters(variable.state.filters.slice(0), { forcePublish: true }); + + expect(evtHandler).toHaveBeenCalled(); + expect(variable.state.filterExpression).toEqual('key1="val1"'); + }); + + it('updateFilters should publish event on when expr did change', () => { + const variable = new AdHocFiltersVariable({ + datasource: { uid: 'hello' }, + applyMode: 'manual', + filters: [{ key: 'key1', operator: '=', value: 'val1' }], + }); + + variable.activate(); + + const evtHandler = jest.fn(); + variable.subscribeToEvent(SceneVariableValueChangedEvent, evtHandler); + + variable.updateFilters([{ key: 'key2', operator: '=', value: 'val1' }]); + + expect(evtHandler).toHaveBeenCalled(); + expect(variable.state.filterExpression).toEqual(`key2="val1"`); + }); + + it('updateFilters should not publish event when skip event is true', () => { + const variable = new AdHocFiltersVariable({ + datasource: { uid: 'hello' }, + applyMode: 'manual', + filters: [{ key: 'key1', operator: '=', value: 'val1' }], + filterExpression: 'hello filter expression', + }); + + variable.activate(); + + const evtHandler = jest.fn(); + variable.subscribeToEvent(SceneVariableValueChangedEvent, evtHandler); + + variable.updateFilters([{ key: 'key2', operator: '=', value: 'val1' }], { skipPublish: true }); + + expect(evtHandler).not.toHaveBeenCalled(); + expect(variable.state.filterExpression).toEqual(`key2="val1"`); + }); + + it('updateFilters should not publish event on when expr did change, if skipPublish is true', () => { + const variable = new AdHocFiltersVariable({ + datasource: { uid: 'hello' }, + applyMode: 'manual', + filters: [{ key: 'key1', operator: '=', value: 'val1' }], + }); + + variable.activate(); + + const evtHandler = jest.fn(); + variable.subscribeToEvent(SceneVariableValueChangedEvent, evtHandler); + + variable.updateFilters([{ key: 'key2', operator: '=', value: 'val1' }], { skipPublish: true }); + + expect(evtHandler).not.toHaveBeenCalled(); + expect(variable.state.filterExpression).toEqual(`key2="val1"`); + }); + it('Should create variable with applyMode as manual by default and it allows to override it', () => { const defaultVariable = new AdHocFiltersVariable({ datasource: { uid: 'hello' }, diff --git a/packages/scenes/src/variables/adhoc/AdHocFiltersVariable.tsx b/packages/scenes/src/variables/adhoc/AdHocFiltersVariable.tsx index a05cb82c5..2c4249c9d 100644 --- a/packages/scenes/src/variables/adhoc/AdHocFiltersVariable.tsx +++ b/packages/scenes/src/variables/adhoc/AdHocFiltersVariable.tsx @@ -204,6 +204,36 @@ export class AdHocFiltersVariable } } + /** + * Updates the variable's `filters` and `filterExpression` state. + * If `skipPublish` option is true, this will not emit the `SceneVariableValueChangedEvent`, + * allowing consumers to update the filters without triggering dependent data providers. + */ + public updateFilters( + filters: AdHocFilterWithLabels[], + options?: { + skipPublish?: boolean; + forcePublish?: boolean; + } + ): void { + let filterExpressionChanged = false; + let filterExpression = undefined; + + if (filters && filters !== this.state.filters) { + filterExpression = renderExpression(this.state.expressionBuilder, filters); + filterExpressionChanged = filterExpression !== this.state.filterExpression; + } + + super.setState({ + filters, + filterExpression, + }); + + if ((filterExpressionChanged && options?.skipPublish !== true) || options?.forcePublish) { + this.publishEvent(new SceneVariableValueChangedEvent(this), true); + } + } + public getValue(): VariableValue | undefined { return this.state.filterExpression; }