Skip to content

Commit a34d8e1

Browse files
committed
Add visualisation filtering for boolean values
1 parent d5f129f commit a34d8e1

File tree

3 files changed

+120
-6
lines changed

3 files changed

+120
-6
lines changed

apps/api/src/python/visualizations.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,11 @@ def _briefer_create_visualization(
604604
df = df[df[column_name].isnull()]
605605
elif operator == 'isNotNull':
606606
df = df[df[column_name].notnull()]
607+
elif pd.api.types.is_bool_dtype(df[column_name]):
608+
if operator == 'isTrue':
609+
df = df[df[column_name]]
610+
elif operator == 'isFalse':
611+
df = df[~df[column_name]]
607612
elif pd.api.types.is_datetime64_any_dtype(df[column_name]):
608613
# Convert both DataFrame column and value to UTC safely
609614
df_column_utc, value_utc = _briefer_convert_to_utc_safe(df[column_name], pd.to_datetime(value))

apps/web/src/components/v2Editor/customBlocks/visualization/FilterSelector.tsx

Lines changed: 89 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,15 @@ import {
1111
VisualizationDateFilterOperator,
1212
VisualizationNumberFilterOperator,
1313
VisualizationStringFilterOperator,
14+
VisualizationBooleanFilterOperator,
1415
numberFilterOperators,
1516
stringFilterOperators,
1617
dateFilterOperators,
18+
booleanFilterOperators,
1719
VisualizationNumberFilter,
1820
VisualizationStringFilter,
1921
VisualizationDateFilter,
22+
VisualizationBooleanFilter,
2023
toDate,
2124
DataFrame,
2225
VisualizationFilter,
@@ -49,6 +52,7 @@ function isNumberOperator(
4952
| VisualizationNumberFilterOperator
5053
| VisualizationStringFilterOperator
5154
| VisualizationDateFilterOperator
55+
| VisualizationBooleanFilterOperator
5256
): operator is VisualizationNumberFilterOperator {
5357
return VisualizationNumberFilterOperator.safeParse(operator).success
5458
}
@@ -58,15 +62,27 @@ function isStringOperator(
5862
| VisualizationNumberFilterOperator
5963
| VisualizationStringFilterOperator
6064
| VisualizationDateFilterOperator
65+
| VisualizationBooleanFilterOperator
6166
): operator is VisualizationStringFilterOperator {
6267
return VisualizationStringFilterOperator.safeParse(operator).success
6368
}
6469

70+
function isBooleanOperator(
71+
operator:
72+
| VisualizationNumberFilterOperator
73+
| VisualizationStringFilterOperator
74+
| VisualizationDateFilterOperator
75+
| VisualizationBooleanFilterOperator
76+
): operator is VisualizationBooleanFilterOperator {
77+
return VisualizationBooleanFilterOperator.safeParse(operator).success
78+
}
79+
6580
function isDateOperator(
6681
operator:
6782
| VisualizationNumberFilterOperator
6883
| VisualizationStringFilterOperator
6984
| VisualizationDateFilterOperator
85+
| VisualizationBooleanFilterOperator
7086
): operator is VisualizationDateFilterOperator {
7187
return VisualizationDateFilterOperator.safeParse(operator).success
7288
}
@@ -169,6 +185,27 @@ function stringOperatorLabel(
169185
}
170186
}
171187

188+
function booleanOperatorSymbol(
189+
operator: VisualizationBooleanFilterOperator
190+
): string {
191+
switch (operator) {
192+
case 'isTrue':
193+
return 'is true'
194+
case 'isFalse':
195+
return 'is false'
196+
}
197+
}
198+
function booleanOperatorLabel(
199+
operator: VisualizationBooleanFilterOperator
200+
): string {
201+
switch (operator) {
202+
case 'isTrue':
203+
return 'Is True'
204+
case 'isFalse':
205+
return 'Is False'
206+
}
207+
}
208+
172209
function dateOperatorSymbol(operator: VisualizationDateFilterOperator): string {
173210
switch (operator) {
174211
case 'eq':
@@ -215,6 +252,7 @@ function getOperatorLabel(
215252
| VisualizationStringFilterOperator
216253
| VisualizationNumberFilterOperator
217254
| VisualizationDateFilterOperator
255+
| VisualizationBooleanFilterOperator
218256
): string {
219257
if (isNumberOperator(operator)) {
220258
return numberOperatorLabel(operator)
@@ -224,6 +262,10 @@ function getOperatorLabel(
224262
return stringOperatorLabel(operator)
225263
}
226264

265+
if (isBooleanOperator(operator)) {
266+
return booleanOperatorLabel(operator)
267+
}
268+
227269
return dateOperatorLabel(operator)
228270
}
229271

@@ -232,6 +274,7 @@ function searchOperator<
232274
| VisualizationNumberFilterOperator
233275
| VisualizationStringFilterOperator
234276
| VisualizationDateFilterOperator
277+
| VisualizationBooleanFilterOperator
235278
>(options: T[], query: string): T[] {
236279
return options.filter((c) => {
237280
if (isNumberOperator(c)) {
@@ -248,6 +291,13 @@ function searchOperator<
248291
)
249292
}
250293

294+
if (isBooleanOperator(c)) {
295+
return (
296+
booleanOperatorLabel(c).toLowerCase().includes(query.toLowerCase()) ||
297+
booleanOperatorSymbol(c).toLowerCase().includes(query.toLowerCase())
298+
)
299+
}
300+
251301
return (
252302
dateOperatorLabel(c).toLowerCase().includes(query.toLowerCase()) ||
253303
dateOperatorSymbol(c).toLowerCase().includes(query.toLowerCase())
@@ -268,9 +318,8 @@ function getOperatorOptions(columnType: DataFrameColumn['type']) {
268318
return dateFilterOperators
269319
}
270320

271-
// TODO: add filtering capabilities for boolean types
272321
if (NumpyBoolTypes.safeParse(columnType).success) {
273-
return []
322+
return booleanFilterOperators
274323
}
275324

276325
// TODO: this should never happen, we should be alerted
@@ -281,6 +330,7 @@ type Operator =
281330
| VisualizationStringFilterOperator
282331
| VisualizationNumberFilterOperator
283332
| VisualizationDateFilterOperator
333+
| VisualizationBooleanFilterOperator
284334

285335
interface Props {
286336
dataframe: Pick<DataFrame, 'name' | 'columns'>
@@ -364,6 +414,13 @@ function FilterSelector(props: Props) {
364414
return
365415
}
366416

417+
if (NumpyBoolTypes.safeParse(column.type).success) {
418+
if (!isBooleanOperator(operator)) {
419+
setOperator('isTrue')
420+
}
421+
return
422+
}
423+
367424
if (NumpyDateTypes.safeParse(column.type).success) {
368425
if (!isDateOperator(operator)) {
369426
setOperator('eq')
@@ -418,6 +475,23 @@ function FilterSelector(props: Props) {
418475
}
419476
}
420477

478+
if (
479+
NumpyBoolTypes.safeParse(column.type).success
480+
) {
481+
if (isBooleanOperator(operator)) {
482+
const filter = VisualizationBooleanFilter.safeParse({
483+
id: props.filter.id,
484+
column,
485+
operator,
486+
value,
487+
})
488+
if (filter.success) {
489+
props.onChange(filter.data)
490+
return
491+
}
492+
}
493+
}
494+
421495
if (NumpyDateTypes.safeParse(column.type).success) {
422496
if (isDateOperator(operator)) {
423497
const filter = VisualizationDateFilter.safeParse({
@@ -514,7 +588,7 @@ function FilterSelector(props: Props) {
514588
}
515589
}
516590

517-
if (column && (newOp === 'isNull' || newOp === 'isNotNull')) {
591+
if (column && (newOp === 'isNull' || newOp === 'isNotNull' || newOp === 'isTrue' || newOp === 'isFalse')) {
518592
if (
519593
NumpyNumberTypes.or(NumpyTimeDeltaTypes).safeParse(column.type)
520594
.success
@@ -528,6 +602,12 @@ function FilterSelector(props: Props) {
528602
setValue('filter')
529603
}
530604

605+
if (
606+
NumpyBoolTypes.safeParse(column.type).success
607+
) {
608+
setValue('filter') // FIXME: Improve value handling for boolean filtering
609+
}
610+
531611
if (NumpyDateTypes.safeParse(column.type).success) {
532612
setValue(new Date().toISOString())
533613
}
@@ -636,7 +716,7 @@ function FilterSelector(props: Props) {
636716
<span>{column?.name ?? 'New filter'}</span>
637717
<span
638718
className={clsx(
639-
operator === 'isNull' || operator === 'isNotNull'
719+
operator === 'isNull' || operator === 'isNotNull' || operator === 'isTrue' || operator === 'isFalse'
640720
? 'pl-0.5'
641721
: 'px-0.5',
642722
props.isInvalid ? 'text-red-400' : 'text-gray-400'
@@ -647,10 +727,12 @@ function FilterSelector(props: Props) {
647727
? numberOperatorSymbol(operator)
648728
: isStringOperator(operator)
649729
? stringOperatorSymbol(operator)
730+
: isBooleanOperator(operator)
731+
? booleanOperatorSymbol(operator)
650732
: dateOperatorSymbol(operator)
651733
: ''}
652734
</span>
653-
{operator !== 'isNull' && operator !== 'isNotNull' ? (
735+
{operator !== 'isNull' && operator !== 'isNotNull' && operator !== 'isTrue' && operator !== 'isFalse' ? (
654736
<>
655737
{renderedValue ? (
656738
<span className="px-1.5 py-0.5 bg-ceramic-100 text-ceramic-500 rounded-md">
@@ -719,6 +801,7 @@ function FilterSelector(props: Props) {
719801
| VisualizationNumberFilterOperator
720802
| VisualizationDateFilterOperator
721803
| VisualizationStringFilterOperator
804+
| VisualizationBooleanFilterOperator
722805
>
723806
icon={() => null}
724807
label="Operator"
@@ -730,7 +813,7 @@ function FilterSelector(props: Props) {
730813
placeholder="Operator"
731814
disabled={props.disabled}
732815
/>
733-
{operator !== 'isNull' && operator !== 'isNotNull' && (
816+
{operator !== 'isNull' && operator !== 'isNotNull' && operator !== 'isTrue' && operator !== 'isFalse' && (
734817
<div className="relative">
735818
{VisualizationStringFilterMultiValuesOperator.safeParse(
736819
operator

packages/types/src/index.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,8 @@ export type VisualizationStringFilterSingleValueOperator = z.infer<
397397
export const VisualizationOperatorWithoutValue = z.union([
398398
z.literal('isNull'),
399399
z.literal('isNotNull'),
400+
z.literal('isTrue'),
401+
z.literal('isFalse'),
400402
])
401403
export type VisualizationOperatorWithoutValue = z.infer<
402404
typeof VisualizationOperatorWithoutValue
@@ -523,10 +525,33 @@ export const VisualizationDateFilter = z.object({
523525
})
524526
export type VisualizationDateFilter = z.infer<typeof VisualizationDateFilter>
525527

528+
export const VisualizationBooleanFilterOperator = z.union([
529+
z.literal('isTrue'),
530+
z.literal('isFalse'),
531+
])
532+
export type VisualizationBooleanFilterOperator = z.infer<
533+
typeof VisualizationBooleanFilterOperator
534+
>
535+
export const booleanFilterOperators: VisualizationBooleanFilterOperator[] = [
536+
'isTrue',
537+
'isFalse',
538+
]
539+
540+
export const VisualizationBooleanFilter = z.object({
541+
id: uuidSchema,
542+
column: DataFrameBooleanColumn,
543+
operator: VisualizationBooleanFilterOperator,
544+
value: z.string().optional(),
545+
renderError: PythonErrorOutput.optional(),
546+
renderedValue: z.string().optional(),
547+
})
548+
export type VisualizationBooleanFilter = z.infer<typeof VisualizationBooleanFilter>
549+
526550
const VisualizationFilterOperator = z.union([
527551
VisualizationNumberFilterOperator,
528552
VisualizationStringFilterOperator,
529553
VisualizationDateFilterOperator,
554+
VisualizationBooleanFilterOperator,
530555
])
531556

532557
export const UnfinishedVisualizationFilter = z.object({
@@ -544,6 +569,7 @@ export const VisualizationFilter = z.union([
544569
VisualizationStringFilter,
545570
VisualizationNumberFilter,
546571
VisualizationDateFilter,
572+
VisualizationBooleanFilter,
547573
UnfinishedVisualizationFilter,
548574
])
549575
export type VisualizationFilter = z.infer<typeof VisualizationFilter>

0 commit comments

Comments
 (0)