Skip to content

Commit 6cd76e9

Browse files
committed
add combineMode
1 parent a5215c1 commit 6cd76e9

File tree

7 files changed

+157
-43
lines changed

7 files changed

+157
-43
lines changed

packages/core/src/features/filters/types.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,24 @@ export const isFilterSet = (input: any): input is FilterSet => {
122122
return true;
123123
};
124124

125+
export function isUnion(value: unknown): value is Union {
126+
return (
127+
typeof value === 'object' &&
128+
value !== null &&
129+
(value as Union).operator === 'or' &&
130+
Array.isArray((value as Union).operands)
131+
);
132+
}
133+
134+
export function isIntersection(value: unknown): value is Intersection {
135+
return (
136+
typeof value === 'object' &&
137+
value !== null &&
138+
(value as Intersection).operator === 'and' &&
139+
Array.isArray((value as Intersection).operands)
140+
);
141+
}
142+
125143
export interface OperationHandler<T> {
126144
handleEquals: (op: Equals) => T;
127145
handleNotEquals: (op: NotEquals) => T;

packages/frontend/src/components/charts/Charts.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,11 @@ const Charts = ({
176176
</React.Fragment>
177177
);
178178
};
179-
const dataKeys = data[field].length > 0 ? Object.keys(data[field][0]) : [];
179+
console.log(field, data);
180+
const dataKeys =
181+
field in data && data?.[field].length > 0
182+
? Object.keys(data[field][0])
183+
: [];
180184

181185
const chartTitle = charts[field].title ?? fieldNameToTitle(field);
182186

packages/frontend/src/components/facets/FacetEnumList.tsx

Lines changed: 54 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import {
55
LoadingOverlay,
66
SegmentedControl,
77
TextInput,
8-
Tooltip, useMantineTheme,
8+
Tooltip,
9+
useMantineTheme,
910
} from '@mantine/core';
1011
import { MdClose as CloseIcon } from 'react-icons/md';
1112
import FacetSortPanel from './FacetSortPanel';
@@ -15,7 +16,7 @@ import FacetExpander from './FacetExpander';
1516
import { EnumFacetChart } from '../charts';
1617
import React, { useEffect, useRef, useState } from 'react';
1718
import { EnumFacetHooks } from './EnumFacet';
18-
import { SortType } from './types';
19+
import { SortType, CombineMode } from './types';
1920
import { updateFacetEnum } from './utils';
2021
import { useDeepCompareCallback, useDeepCompareEffect } from 'use-deep-compare';
2122
import { Icon } from '@iconify/react';
@@ -80,8 +81,10 @@ const FacetEnumList: React.FC<FacetEnumListProps> = ({
8081
const searchInputRef = useRef<HTMLInputElement>(null);
8182
const settingRef = useRef<HTMLDivElement>(null);
8283
const [searchTerm, setSearchTerm] = useState('');
83-
const [combineMode, setCombineMode] = useState<'or' | 'and'>('and');
84-
const { data, enumFilters, isSuccess, error } = hooks.useGetFacetData(field);
84+
// const [combineMode, setCombineMode] = useState<'or' | 'and'>('or');
85+
86+
const { data, enumFilters, combineMode, isSuccess, error } =
87+
hooks.useGetFacetData(field);
8588
const [selectedEnums, setSelectedEnums] = useState(enumFilters ?? []);
8689
const totalCount = hooks?.useTotalCounts ? hooks.useTotalCounts() : 1;
8790
const clearFilters = hooks.useClearFilter();
@@ -126,13 +129,36 @@ const FacetEnumList: React.FC<FacetEnumListProps> = ({
126129
});
127130
if (checked) {
128131
const updated = selectedEnums ? [...selectedEnums, value] : [value];
129-
updateFacetEnum(field, updated, updateFacetFilters, clearFilters);
132+
updateFacetEnum(
133+
field,
134+
updated,
135+
updateFacetFilters,
136+
clearFilters,
137+
combineMode,
138+
);
130139
} else {
131140
const updated = selectedEnums?.filter((x) => x != value);
132-
updateFacetEnum(field, updated ?? [], updateFacetFilters, clearFilters);
141+
updateFacetEnum(
142+
field,
143+
updated ?? [],
144+
updateFacetFilters,
145+
clearFilters,
146+
combineMode,
147+
);
133148
}
134149
};
135150

151+
const handleCombineModeChange = (mode: CombineMode) => {
152+
updateFacetEnum(
153+
field,
154+
selectedEnums ?? [],
155+
updateFacetFilters,
156+
clearFilters,
157+
mode,
158+
);
159+
console.log('set mode', mode);
160+
};
161+
136162
const [facetChartData, setFacetChartData] = useState<{
137163
filteredData: [string | number, number][];
138164
filteredDataObj: Record<string | number, number>;
@@ -309,29 +335,29 @@ const FacetEnumList: React.FC<FacetEnumListProps> = ({
309335
{isSettings && (
310336
<div className="flex w-full justify-center">
311337
<div className="flex items-center space-x-1 mt-1">
312-
<SegmentedControl
313-
classNames = {{
314-
root: 'border-1 border-accent rounded-l-md rounded-r-md',
315-
control: 'p-0 m-0',
316-
indicator: 'bg-accent text-accent-contrast'
317-
}}
318-
ref={settingRef}
319-
value={combineMode}
320-
onChange={(value: string) =>
321-
setCombineMode(value as 'and' | 'or')
322-
}
323-
data={[
324-
{ label: 'AND', value: 'and' },
325-
{ label: 'OR', value: 'or' },
326-
]}
327-
/>
328-
<Tooltip label="Combine filters with AND or OR">
329-
<Icon
330-
icon="gen3:info"
331-
height={12}
332-
width={12}
333-
color={theme.colors.accent[4]}
338+
<SegmentedControl
339+
classNames={{
340+
root: 'border-1 border-accent rounded-l-md rounded-r-md',
341+
control: 'p-0 m-0',
342+
indicator: 'bg-accent text-accent-contrast',
343+
}}
344+
ref={settingRef}
345+
value={combineMode}
346+
onChange={(value: string) =>
347+
handleCombineModeChange(value as 'and' | 'or')
348+
}
349+
data={[
350+
{ label: 'AND', value: 'and' },
351+
{ label: 'OR', value: 'or' },
352+
]}
334353
/>
354+
<Tooltip label="Combine filters with AND or OR">
355+
<Icon
356+
icon="gen3:info"
357+
height={12}
358+
width={12}
359+
color={theme.colors.accent[4]}
360+
/>
335361
</Tooltip>
336362
</div>
337363
</div>

packages/frontend/src/components/facets/hooks.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,25 +22,25 @@ export const useClearFilters = (index: string) => {
2222

2323
/**
2424
* Selector for the facet values (if any) from the current cohort
25+
* @param index - index of filter
2526
* @param field - field name to find filter for
2627
* @return Value of Filters or undefined
2728
*/
2829
export const useExtractEnumFilterValues = (
2930
index: string,
3031
field: string,
3132
): EnumFilterValue => {
32-
const filter = useCoreSelector((state : CoreState) =>
33+
const filter = useCoreSelector((state: CoreState) =>
3334
selectIndexedFilterByName(state, index, field),
3435
);
35-
return filter ? extractEnumFilterValue(filter) : [];
36+
return filter ? extractEnumFilterValue(filter) : [];
3637
};
3738

38-
3939
export const useExtractRangeFilterValues = (
4040
index: string,
4141
field: string,
4242
): FromToRange<string | number> | undefined => {
43-
const filter = useCoreSelector((state : CoreState) =>
43+
const filter = useCoreSelector((state: CoreState) =>
4444
selectIndexedFilterByName(state, index, field),
4545
);
4646
return filter ? extractRangeValues(filter) : undefined;

packages/frontend/src/components/facets/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ export type FacetType =
3737
| 'datetime'
3838
| 'toggle';
3939

40+
export type CombineMode = 'and' | 'or';
41+
4042
// required functions
4143
export type ClearFacetFunction = (field: string) => void;
4244
export type ClearIndexedFacetFunction = (index: string, field: string) => void;
@@ -83,6 +85,7 @@ export interface FacetResponse {
8385

8486
export interface EnumFacetResponse extends FacetResponse {
8587
readonly enumFilters?: EnumFilterValue;
88+
readonly combineMode?: CombineMode;
8689
}
8790

8891
export function isEnumFacetResponse(

packages/frontend/src/components/facets/utils.ts

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
HistogramDataArray,
66
Includes,
77
Operation,
8+
isUnion,
89
selectIndexedFilterByName,
910
isOperationWithField,
1011
updateCohortFilter,
@@ -19,6 +20,7 @@ import {
1920
FromToRange,
2021
UpdateFacetFilterFunction,
2122
FieldToName,
23+
CombineMode,
2224
} from './types';
2325
import { isArray } from 'lodash';
2426
import { TabConfig } from '../../features/CohortBuilder/types';
@@ -31,6 +33,7 @@ export const getAllFieldsFromFilterConfigs = (
3133
interface ExplorerResultsData {
3234
[key: string]: Record<string, any>;
3335
}
36+
3437
export const processBucketData = (
3538
data?: HistogramDataArray,
3639
): Record<string, number> => {
@@ -74,15 +77,30 @@ export const updateFacetEnum = (
7477
values: EnumFilterValue,
7578
updateFacetFilters: UpdateFacetFilterFunction,
7679
clearFilters: ClearFacetFunction,
80+
combineMode: CombineMode = 'or',
7781
): void => {
7882
if (values === undefined) return;
7983
if (values.length > 0) {
8084
// TODO: Assuming Includes by default but this might change to Include|Excludes
81-
updateFacetFilters(fieldName, {
82-
operator: 'in',
83-
field: fieldName,
84-
operands: values,
85-
} as Includes);
85+
updateFacetFilters(
86+
fieldName,
87+
combineMode === 'and'
88+
? {
89+
operator: 'and',
90+
operands: [
91+
{
92+
operator: 'in',
93+
field: fieldName,
94+
operands: values,
95+
} as Includes,
96+
],
97+
}
98+
: ({
99+
operator: 'in',
100+
field: fieldName,
101+
operands: values,
102+
} as Includes),
103+
);
86104
}
87105
// no values remove the filter
88106
else {
@@ -173,6 +191,7 @@ export const useUpdateFilters = (index: string) => {
173191
// update the filter for this facet
174192

175193
return (field: string, filter: Operation) => {
194+
console.log('useUpdateFilters', index, filter);
176195
dispatch(
177196
updateCohortFilter({
178197
index: index,
@@ -230,3 +249,33 @@ export const extractRangeValues = <T extends string | number>(
230249
export const convertToStringArray = (
231250
inputArray: (string | number)[],
232251
): string[] => inputArray.map(String);
252+
253+
/**
254+
* This function creates a new operation by combining the provided filter
255+
* with an 'and' logical operator. The resulting operation contains the filter
256+
* as its sole operand initially.
257+
*
258+
* @param {Operation} filter - The operation to be added as the first operand.
259+
* @returns {Operation} A new operation object with an 'and' operator and the given filter as its operand.
260+
*/
261+
export const addUnion = (filter: Operation): Operation => {
262+
return {
263+
operator: 'and',
264+
operands: [filter],
265+
};
266+
};
267+
268+
/**
269+
* Removes a union operation and returns the sole operand if the union
270+
* operation contains only one operand. If the union operation has multiple
271+
* operands or if the input is not a union, returns undefined.
272+
*
273+
* @param {Operation} filter - The operation to evaluate and possibly modify, expected to be a union.
274+
* @returns {Operation | undefined} The sole operand of the union if it contains only one, or undefined otherwise.
275+
*/
276+
export const removeUnion = (filter: Operation): Operation | undefined => {
277+
if (isUnion(filter) && filter.operands.length === 1) {
278+
return filter.operands[0];
279+
}
280+
return undefined;
281+
};

packages/frontend/src/features/CohortBuilder/CohortPanel.tsx

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
extractEnumFilterValue,
77
type FacetDefinition,
88
FacetType,
9+
isUnion,
910
selectIndexFilters,
1011
useCoreSelector,
1112
useGetAggsQuery,
@@ -22,11 +23,12 @@ import {
2223
getAllFieldsFromFilterConfigs,
2324
processBucketData,
2425
processRangeData,
26+
removeUnion,
2527
useGetFacetFilters,
2628
useUpdateFilters,
2729
} from '../../components/facets/utils';
2830
import { useClearFilters } from '../../components/facets/hooks';
29-
import { FacetDataHooks } from '../../components/facets/types';
31+
import { CombineMode, FacetDataHooks } from '../../components/facets/types';
3032
import CohortManager from './CohortManager';
3133
import { Charts } from '../../components/charts';
3234
import ExplorerTable from './ExplorerTable/ExplorerTable';
@@ -102,12 +104,24 @@ export const CohortPanel = ({
102104

103105
const getEnumFacetData = useDeepCompareCallback(
104106
(field: string) => {
107+
let filters = undefined;
108+
let combineMode: CombineMode = 'or';
109+
if (field in cohortFilters.root) {
110+
if (isUnion(cohortFilters.root[field])) {
111+
const unionFilters = removeUnion(cohortFilters.root[field]);
112+
if (unionFilters) {
113+
filters = extractEnumFilterValue(unionFilters);
114+
combineMode = 'and';
115+
}
116+
} else {
117+
filters = extractEnumFilterValue(cohortFilters.root[field]);
118+
}
119+
}
120+
105121
return {
106122
data: processBucketData(data?.[field]),
107-
enumFilters:
108-
field in cohortFilters.root
109-
? extractEnumFilterValue(cohortFilters.root[field])
110-
: undefined,
123+
enumFilters: filters,
124+
combineMode: combineMode,
111125
isSuccess: isSuccess,
112126
};
113127
},

0 commit comments

Comments
 (0)