diff --git a/superset-frontend/packages/superset-ui-core/src/query/types/Filter.ts b/superset-frontend/packages/superset-ui-core/src/query/types/Filter.ts index be420b41a207b..e113a843d468b 100644 --- a/superset-frontend/packages/superset-ui-core/src/query/types/Filter.ts +++ b/superset-frontend/packages/superset-ui-core/src/query/types/Filter.ts @@ -72,6 +72,12 @@ export function isSimpleAdhocFilter( return filter.expressionType === 'SIMPLE'; } +export function isFreeFormAdhocFilter( + filter: AdhocFilter, +): filter is FreeFormAdhocFilter { + return filter.expressionType === 'SQL'; +} + export function isUnaryAdhocFilter( filter: SimpleAdhocFilter, ): filter is UnaryAdhocFilter { diff --git a/superset-frontend/packages/superset-ui-core/test/query/types/Filter.test.ts b/superset-frontend/packages/superset-ui-core/test/query/types/Filter.test.ts index 3861bb0085b51..4aa4a474159b0 100644 --- a/superset-frontend/packages/superset-ui-core/test/query/types/Filter.test.ts +++ b/superset-frontend/packages/superset-ui-core/test/query/types/Filter.test.ts @@ -21,6 +21,7 @@ import { isUnaryAdhocFilter, isBinaryAdhocFilter, isSetAdhocFilter, + isFreeFormAdhocFilter, } from '@superset-ui/core'; describe('Filter type guards', () => { @@ -95,4 +96,26 @@ describe('Filter type guards', () => { ).toEqual(false); }); }); + describe('isFreeFormAdhocFilter', () => { + it('should return true when it is the correct type', () => { + expect( + isFreeFormAdhocFilter({ + expressionType: 'SQL', + clause: 'WHERE', + sqlExpression: 'gender = "boy"', + }), + ).toEqual(true); + }); + it('should return false otherwise', () => { + expect( + isFreeFormAdhocFilter({ + expressionType: 'SIMPLE', + clause: 'WHERE', + subject: 'tea', + operator: '==', + comparator: 'matcha', + }), + ).toEqual(false); + }); + }); }); diff --git a/superset-frontend/src/explore/controlUtils/getFormDataFromDashboardContext.test.ts b/superset-frontend/src/explore/controlUtils/getFormDataFromDashboardContext.test.ts index ab14c8cf0806d..c38c80813bcbc 100644 --- a/superset-frontend/src/explore/controlUtils/getFormDataFromDashboardContext.test.ts +++ b/superset-frontend/src/explore/controlUtils/getFormDataFromDashboardContext.test.ts @@ -30,6 +30,35 @@ const getExploreFormData = (overrides: JsonObject = {}) => ({ comparator: ['boys'], filterOptionName: '123', }, + { + clause: 'WHERE' as const, + expressionType: 'SQL' as const, + operator: null, + subject: null, + comparator: null, + sqlExpression: "name = 'John'", + filterOptionName: '456', + }, + { + clause: 'WHERE' as const, + expressionType: 'SQL' as const, + operator: null, + subject: null, + comparator: null, + sqlExpression: "city = 'Warsaw'", + filterOptionName: '567', + }, + ], + adhoc_filters_b: [ + { + clause: 'WHERE' as const, + expressionType: 'SQL' as const, + operator: null, + subject: null, + comparator: null, + sqlExpression: "country = 'Poland'", + filterOptionName: '789', + }, ], applied_time_extras: {}, color_scheme: 'supersetColors', @@ -111,6 +140,53 @@ const getExpectedResultFormData = (overrides: JsonObject = {}) => ({ comparator: ['boys'], filterOptionName: '123', }, + { + clause: 'WHERE' as const, + expressionType: 'SQL' as const, + operator: null, + subject: null, + comparator: null, + sqlExpression: "name = 'John'", + filterOptionName: '456', + }, + { + clause: 'WHERE' as const, + expressionType: 'SQL' as const, + operator: null, + subject: null, + comparator: null, + sqlExpression: "city = 'Warsaw'", + filterOptionName: '567', + }, + { + clause: 'WHERE', + expressionType: 'SIMPLE', + operator: 'IN', + subject: 'name', + comparator: ['Aaron'], + isExtra: true, + filterOptionName: expect.any(String), + }, + { + clause: 'WHERE', + expressionType: 'SIMPLE', + operator: '<=', + subject: 'num_boys', + comparator: 10000, + isExtra: true, + filterOptionName: expect.any(String), + }, + ], + adhoc_filters_b: [ + { + clause: 'WHERE' as const, + expressionType: 'SQL' as const, + operator: null, + subject: null, + comparator: null, + sqlExpression: "country = 'Poland'", + filterOptionName: expect.any(String), + }, { clause: 'WHERE', expressionType: 'SIMPLE', diff --git a/superset-frontend/src/explore/controlUtils/getFormDataWithDashboardContext.ts b/superset-frontend/src/explore/controlUtils/getFormDataWithDashboardContext.ts index f3b604ba88048..28bdd68a9053b 100644 --- a/superset-frontend/src/explore/controlUtils/getFormDataWithDashboardContext.ts +++ b/superset-frontend/src/explore/controlUtils/getFormDataWithDashboardContext.ts @@ -26,6 +26,9 @@ import { QueryObjectFilterClause, SimpleAdhocFilter, QueryFormData, + AdhocFilter, + isFreeFormAdhocFilter, + isSimpleAdhocFilter, } from '@superset-ui/core'; import { NO_TIME_RANGE } from '../constants'; @@ -51,20 +54,26 @@ const simpleFilterToAdhoc = ( return result; }; -const removeAdhocFilterDuplicates = (filters: SimpleAdhocFilter[]) => { +const removeAdhocFilterDuplicates = (filters: AdhocFilter[]) => { const isDuplicate = ( - adhocFilter: SimpleAdhocFilter, - existingFilters: SimpleAdhocFilter[], + adhocFilter: AdhocFilter, + existingFilters: AdhocFilter[], ) => existingFilters.some( - (existingFilter: SimpleAdhocFilter) => - existingFilter.operator === adhocFilter.operator && - existingFilter.subject === adhocFilter.subject && - ((!('comparator' in existingFilter) && - !('comparator' in adhocFilter)) || - ('comparator' in existingFilter && - 'comparator' in adhocFilter && - isEqual(existingFilter.comparator, adhocFilter.comparator))), + (existingFilter: AdhocFilter) => + (isFreeFormAdhocFilter(existingFilter) && + isFreeFormAdhocFilter(adhocFilter) && + existingFilter.clause === adhocFilter.clause && + existingFilter.sqlExpression === adhocFilter.sqlExpression) || + (isSimpleAdhocFilter(existingFilter) && + isSimpleAdhocFilter(adhocFilter) && + existingFilter.operator === adhocFilter.operator && + existingFilter.subject === adhocFilter.subject && + ((!('comparator' in existingFilter) && + !('comparator' in adhocFilter)) || + ('comparator' in existingFilter && + 'comparator' in adhocFilter && + isEqual(existingFilter.comparator, adhocFilter.comparator)))), ); return filters.reduce((acc, filter) => { @@ -72,7 +81,7 @@ const removeAdhocFilterDuplicates = (filters: SimpleAdhocFilter[]) => { acc.push(filter); } return acc; - }, [] as SimpleAdhocFilter[]); + }, [] as AdhocFilter[]); }; const mergeFilterBoxToFormData = ( @@ -176,16 +185,28 @@ export const getFormDataWithDashboardContext = ( exploreFormData, dashboardContextFormData, ); - const adhocFilters = removeAdhocFilterDuplicates([ - ...ensureIsArray(exploreFormData.adhoc_filters), - ...ensureIsArray(filterBoxData.adhoc_filters), - ...ensureIsArray(nativeFiltersData.adhoc_filters), - ]); + const adhocFilters = [ + ...Object.keys(exploreFormData), + ...Object.keys(filterBoxData), + ...Object.keys(nativeFiltersData), + ] + .filter(key => key.match(/adhoc_filter.*/)) + .reduce( + (acc, key) => ({ + ...acc, + [key]: removeAdhocFilterDuplicates([ + ...ensureIsArray(exploreFormData[key]), + ...ensureIsArray(filterBoxData[key]), + ...ensureIsArray(nativeFiltersData[key]), + ]), + }), + {}, + ); return { ...exploreFormData, ...dashboardContextFormData, ...filterBoxData, ...nativeFiltersData, - adhoc_filters: adhocFilters, + ...adhocFilters, }; };