From 616ec9831bf39ed8a18fad2ae6a4f18927aa8caf Mon Sep 17 00:00:00 2001 From: Victor Marin <36818606+mdvictor@users.noreply.github.com> Date: Wed, 19 Mar 2025 17:24:21 +0200 Subject: [PATCH] Remove scopes to adhoc conversion code (#101699) --- .../scene/convertScopesToAdHocFilters.test.ts | 288 ------------------ .../scene/convertScopesToAdHocFilters.ts | 92 ------ 2 files changed, 380 deletions(-) delete mode 100644 public/app/features/dashboard-scene/scene/convertScopesToAdHocFilters.test.ts delete mode 100644 public/app/features/dashboard-scene/scene/convertScopesToAdHocFilters.ts diff --git a/public/app/features/dashboard-scene/scene/convertScopesToAdHocFilters.test.ts b/public/app/features/dashboard-scene/scene/convertScopesToAdHocFilters.test.ts deleted file mode 100644 index c6b84a4d39f..00000000000 --- a/public/app/features/dashboard-scene/scene/convertScopesToAdHocFilters.test.ts +++ /dev/null @@ -1,288 +0,0 @@ -import { Scope, ScopeSpecFilter } from '@grafana/data'; -import { FilterOrigin } from '@grafana/scenes'; - -import { convertScopesToAdHocFilters } from './convertScopesToAdHocFilters'; - -describe('convertScopesToAdHocFilters', () => { - it('should return empty filters when no scopes are provided', () => { - let scopes = generateScopes([]); - - expect(scopes).toEqual([]); - expect(convertScopesToAdHocFilters(scopes)).toEqual([]); - - scopes = generateScopes([[], []]); - - expect(convertScopesToAdHocFilters(scopes)).toEqual([]); - }); - - it('should return filters formatted for adHoc from a single scope', () => { - let scopes = generateScopes([ - [ - { key: 'key1', value: 'value1', operator: 'equals' }, - { key: 'key2', value: 'value2', operator: 'not-equals' }, - { key: 'key3', value: 'value3', operator: 'regex-not-match' }, - ], - ]); - - expect(convertScopesToAdHocFilters(scopes)).toEqual([ - { key: 'key1', value: 'value1', operator: '=', origin: FilterOrigin.Scopes, values: ['value1'] }, - { key: 'key2', value: 'value2', operator: '!=', origin: FilterOrigin.Scopes, values: ['value2'] }, - { key: 'key3', value: 'value3', operator: '!~', origin: FilterOrigin.Scopes, values: ['value3'] }, - ]); - - scopes = generateScopes([[{ key: 'key3', value: 'value3', operator: 'regex-match' }]]); - - expect(convertScopesToAdHocFilters(scopes)).toEqual([ - { key: 'key3', value: 'value3', operator: '=~', origin: FilterOrigin.Scopes, values: ['value3'] }, - ]); - }); - - it('should return filters formatted for adHoc from multiple scopes with single values', () => { - let scopes = generateScopes([ - [{ key: 'key1', value: 'value1', operator: 'equals' }], - [{ key: 'key2', value: 'value2', operator: 'regex-match' }], - ]); - - expect(convertScopesToAdHocFilters(scopes)).toEqual([ - { key: 'key1', value: 'value1', operator: '=', origin: FilterOrigin.Scopes, values: ['value1'] }, - { key: 'key2', value: 'value2', operator: '=~', origin: FilterOrigin.Scopes, values: ['value2'] }, - ]); - }); - - it('should return filters formatted for adHoc from multiple scopes with multiple values', () => { - let scopes = generateScopes([ - [ - { key: 'key1', value: 'value1', operator: 'equals' }, - { key: 'key2', value: 'value2', operator: 'not-equals' }, - ], - [ - { key: 'key3', value: 'value3', operator: 'regex-match' }, - { key: 'key4', value: 'value4', operator: 'regex-match' }, - ], - ]); - - expect(convertScopesToAdHocFilters(scopes)).toEqual([ - { key: 'key1', value: 'value1', operator: '=', origin: FilterOrigin.Scopes, values: ['value1'] }, - { key: 'key2', value: 'value2', operator: '!=', origin: FilterOrigin.Scopes, values: ['value2'] }, - { key: 'key3', value: 'value3', operator: '=~', origin: FilterOrigin.Scopes, values: ['value3'] }, - { key: 'key4', value: 'value4', operator: '=~', origin: FilterOrigin.Scopes, values: ['value4'] }, - ]); - }); - - it('should return formatted filters and concat values of the same key, coming from different scopes, if operator supports multi-value', () => { - let scopes = generateScopes([ - [ - { key: 'key1', value: 'value1', operator: 'equals' }, - { key: 'key2', value: 'value2', operator: 'not-equals' }, - ], - [ - { key: 'key1', value: 'value3', operator: 'equals' }, - { key: 'key2', value: 'value4', operator: 'not-equals' }, - ], - [{ key: 'key1', value: 'value5', operator: 'equals' }], - ]); - - expect(convertScopesToAdHocFilters(scopes)).toEqual([ - { - key: 'key1', - value: 'value1', - operator: '=|', - origin: FilterOrigin.Scopes, - values: ['value1', 'value3', 'value5'], - }, - { key: 'key2', value: 'value2', operator: '!=|', origin: FilterOrigin.Scopes, values: ['value2', 'value4'] }, - ]); - }); - - it('should ignore the rest of the duplicate filters, if they are a combination of equals and not-equals', () => { - let scopes = generateScopes([ - [{ key: 'key1', value: 'value1', operator: 'equals' }], - [{ key: 'key1', value: 'value2', operator: 'not-equals' }], - [{ key: 'key1', value: 'value3', operator: 'equals' }], - ]); - - expect(convertScopesToAdHocFilters(scopes)).toEqual([ - { - key: 'key1', - value: 'value1', - operator: '=|', - origin: FilterOrigin.Scopes, - values: ['value1', 'value3'], - }, - { - key: 'key1', - value: 'value2', - operator: '!=', - origin: FilterOrigin.Scopes, - values: ['value2'], - }, - ]); - }); - - it('should return formatted filters and keep only the first filter of the same key if operator is not multi-value', () => { - let scopes = generateScopes([ - [ - { key: 'key1', value: 'value1', operator: 'regex-match' }, - { key: 'key2', value: 'value2', operator: 'not-equals' }, - ], - [ - { key: 'key1', value: 'value3', operator: 'regex-match' }, - { key: 'key2', value: 'value4', operator: 'not-equals' }, - ], - [{ key: 'key1', value: 'value5', operator: 'equals' }], - ]); - - expect(convertScopesToAdHocFilters(scopes)).toEqual([ - { - key: 'key1', - value: 'value1', - operator: '=~', - origin: FilterOrigin.Scopes, - values: ['value1'], - }, - { key: 'key2', value: 'value2', operator: '!=|', origin: FilterOrigin.Scopes, values: ['value2', 'value4'] }, - { - key: 'key1', - value: 'value3', - operator: '=~', - origin: FilterOrigin.Scopes, - values: ['value3'], - }, - { - key: 'key1', - value: 'value5', - operator: '=', - origin: FilterOrigin.Scopes, - values: ['value5'], - }, - ]); - - scopes = generateScopes([ - [{ key: 'key1', value: 'value1', operator: 'regex-match' }], - [{ key: 'key1', value: 'value5', operator: 'equals' }], - [{ key: 'key1', value: 'value3', operator: 'regex-match' }], - ]); - - expect(convertScopesToAdHocFilters(scopes)).toEqual([ - { - key: 'key1', - value: 'value1', - operator: '=~', - origin: FilterOrigin.Scopes, - values: ['value1'], - }, - { - key: 'key1', - value: 'value5', - operator: '=', - origin: FilterOrigin.Scopes, - values: ['value5'], - }, - { - key: 'key1', - value: 'value3', - operator: '=~', - origin: FilterOrigin.Scopes, - values: ['value3'], - }, - ]); - }); - - it('should return formatted filters and concat values that are multi-value and drop duplicates with non multi-value operator', () => { - let scopes = generateScopes([ - [{ key: 'key1', value: 'value1', operator: 'equals' }], - [{ key: 'key1', value: 'value2', operator: 'regex-match' }], - [{ key: 'key1', value: 'value3', operator: 'equals' }], - ]); - - expect(convertScopesToAdHocFilters(scopes)).toEqual([ - { - key: 'key1', - value: 'value1', - operator: '=|', - origin: FilterOrigin.Scopes, - values: ['value1', 'value3'], - }, - { - key: 'key1', - value: 'value2', - operator: '=~', - origin: FilterOrigin.Scopes, - values: ['value2'], - }, - ]); - - scopes = generateScopes([ - [ - { key: 'key1', value: 'value1', operator: 'equals' }, - { key: 'key2', value: 'value2', operator: 'equals' }, - ], - [ - { key: 'key1', value: 'value3', operator: 'equals' }, - { key: 'key2', value: 'value4', operator: 'equals' }, - ], - [ - { key: 'key1', value: 'value5', operator: 'regex-match' }, - { key: 'key2', value: 'value6', operator: 'equals' }, - ], - [ - { key: 'key1', value: 'value7', operator: 'equals' }, - { key: 'key2', value: 'value8', operator: 'regex-match' }, - ], - [ - { key: 'key1', value: 'value9', operator: 'equals' }, - { key: 'key2', value: 'value10', operator: 'equals' }, - ], - ]); - - expect(convertScopesToAdHocFilters(scopes)).toEqual([ - { - key: 'key1', - value: 'value1', - operator: '=|', - origin: FilterOrigin.Scopes, - values: ['value1', 'value3', 'value7', 'value9'], - }, - { - key: 'key2', - value: 'value2', - operator: '=|', - origin: FilterOrigin.Scopes, - values: ['value2', 'value4', 'value6', 'value10'], - }, - { - key: 'key1', - value: 'value5', - operator: '=~', - origin: FilterOrigin.Scopes, - values: ['value5'], - }, - { - key: 'key2', - value: 'value8', - operator: '=~', - origin: FilterOrigin.Scopes, - values: ['value8'], - }, - ]); - }); -}); - -function generateScopes(filtersSpec: ScopeSpecFilter[][]) { - const scopes: Scope[] = []; - - for (let i = 0; i < filtersSpec.length; i++) { - scopes.push({ - metadata: { name: `name-${i}` }, - spec: { - title: `scope-${i}`, - type: '', - description: 'desc', - category: '', - filters: filtersSpec[i], - }, - }); - } - - return scopes; -} diff --git a/public/app/features/dashboard-scene/scene/convertScopesToAdHocFilters.ts b/public/app/features/dashboard-scene/scene/convertScopesToAdHocFilters.ts deleted file mode 100644 index 1066a8f7b15..00000000000 --- a/public/app/features/dashboard-scene/scene/convertScopesToAdHocFilters.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { - Scope, - ScopeSpecFilter, - isEqualityOrMultiOperator, - reverseScopeFilterOperatorMap, - scopeFilterOperatorMap, -} from '@grafana/data'; -import { AdHocFilterWithLabels, FilterOrigin } from '@grafana/scenes'; - -export function convertScopesToAdHocFilters(scopes: Scope[]): AdHocFilterWithLabels[] { - const formattedFilters: Map = new Map(); - // duplicated filters that could not be processed in any way are just appended to the list - const duplicatedFilters: AdHocFilterWithLabels[] = []; - const allFilters = scopes.flatMap((scope) => scope.spec.filters); - - for (const filter of allFilters) { - processFilter(formattedFilters, duplicatedFilters, filter); - } - - return [...formattedFilters.values(), ...duplicatedFilters]; -} - -function processFilter( - formattedFilters: Map, - duplicatedFilters: AdHocFilterWithLabels[], - filter: ScopeSpecFilter -) { - const existingFilter = formattedFilters.get(filter.key); - - if (existingFilter && canValueBeMerged(existingFilter.operator, filter.operator)) { - mergeFilterValues(existingFilter, filter); - } else if (!existingFilter) { - // Add filter to map either only if it is new. - // Otherwise it is an existing filter that cannot be converted to multi-value - // and thus will be moved to the duplicatedFilters list - formattedFilters.set(filter.key, { - key: filter.key, - operator: reverseScopeFilterOperatorMap[filter.operator], - value: filter.value, - values: filter.values ?? [filter.value], - origin: FilterOrigin.Scopes, - }); - } else { - duplicatedFilters.push({ - key: filter.key, - operator: reverseScopeFilterOperatorMap[filter.operator], - value: filter.value, - values: filter.values ?? [filter.value], - origin: FilterOrigin.Scopes, - }); - } -} - -function mergeFilterValues(adHocFilter: AdHocFilterWithLabels, filter: ScopeSpecFilter) { - const values = filter.values ?? [filter.value]; - - for (const value of values) { - if (!adHocFilter.values?.includes(value)) { - adHocFilter.values?.push(value); - } - } - - // If there's only one value, there's no need to update the - // operator to its multi-value equivalent - if (adHocFilter.values?.length === 1) { - return; - } - - // Otherwise update it to the equivalent multi-value operator - if (filter.operator === 'equals' && adHocFilter.operator === reverseScopeFilterOperatorMap['equals']) { - adHocFilter.operator = reverseScopeFilterOperatorMap['one-of']; - } else if (filter.operator === 'not-equals' && adHocFilter.operator === reverseScopeFilterOperatorMap['not-equals']) { - adHocFilter.operator = reverseScopeFilterOperatorMap['not-one-of']; - } -} - -function canValueBeMerged(adHocFilterOperator: string, filterOperator: string) { - const scopeConvertedOperator = scopeFilterOperatorMap[adHocFilterOperator]; - - if (!isEqualityOrMultiOperator(scopeConvertedOperator) || !isEqualityOrMultiOperator(filterOperator)) { - return false; - } - - if ( - (scopeConvertedOperator.includes('not') && !filterOperator.includes('not')) || - (!scopeConvertedOperator.includes('not') && filterOperator.includes('not')) - ) { - return false; - } - - return true; -}