Skip to content

Commit

Permalink
filters refactor (#614)
Browse files Browse the repository at this point in the history
* filters refactor

* Clean up arrayWrap
  • Loading branch information
edlouth authored Aug 23, 2023
1 parent 277f770 commit e810f00
Show file tree
Hide file tree
Showing 5 changed files with 315 additions and 389 deletions.
27 changes: 7 additions & 20 deletions grai-frontend/src/components/filters/FilterRow.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React from "react"
import { Close } from "@mui/icons-material"
import { Grid, IconButton, TextField } from "@mui/material"
import { Grid, IconButton } from "@mui/material"
import FilterField from "./FilterField"
import FilterRowValue from "./FilterRowValue"
import {
Field,
Filter,
Expand Down Expand Up @@ -84,25 +85,11 @@ const FilterRow: React.FC<FilterRowProps> = ({
/>
</Grid>
<Grid item md={2}>
{operator?.valueComponent ? (
operator.valueComponent(
!operator,
filter.value,
(value: string | string[] | null) => onChange({ ...filter, value }),
)
) : (
<TextField
fullWidth
disabled={!operator}
value={filter.value ?? ""}
onChange={event =>
onChange({ ...filter, value: event.target.value })
}
inputProps={{
"data-testid": "value",
}}
/>
)}
<FilterRowValue
operator={operator}
filter={filter}
onChange={onChange}
/>
</Grid>
<Grid item md={1}>
<IconButton onClick={onRemove} data-testid="filter-row-remove">
Expand Down
105 changes: 105 additions & 0 deletions grai-frontend/src/components/filters/FilterRowValue.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import React from "react"
import { CheckBox, CheckBoxOutlineBlank } from "@mui/icons-material"
import {
Autocomplete,
AutocompleteChangeReason,
Checkbox,
TextField,
} from "@mui/material"
import arrayFirst from "helpers/arrayFirst"
import arrayWrap from "helpers/arrayWrap"
import notEmpty from "helpers/notEmpty"
import { Filter, OperationOption, Operator } from "./filters"

const icon = <CheckBoxOutlineBlank fontSize="small" />
const checkedIcon = <CheckBox fontSize="small" />

type FilterRowValueProps = {
operator: Operator | null
filter: Filter
onChange: (filter: Filter) => void
}

const FilterRowValue: React.FC<FilterRowValueProps> = ({
operator,
filter,
onChange,
}) => {
const handleValueChange = (
event: React.SyntheticEvent<Element, Event>,
newValue:
| null
| string
| OperationOption
| (null | string | OperationOption)[],
reason: AutocompleteChangeReason,
) =>
onChange({
...filter,
value: Array.isArray(newValue)
? newValue
.map(option =>
typeof option === "string" ? option : option?.value,
)
.filter(notEmpty)
: (typeof newValue === "string" ? newValue : newValue?.value) ?? null,
})

if (!operator?.options)
return (
<TextField
fullWidth
disabled={!operator}
value={filter.value ?? ""}
onChange={event => onChange({ ...filter, value: event.target.value })}
inputProps={{
"data-testid": "value",
}}
/>
)

if (operator.multiple)
return (
<Autocomplete<string | OperationOption, true>
multiple
openOnFocus
autoSelect
limitTags={1}
options={operator.options}
value={operator.options.filter(option =>
arrayWrap(filter.value).includes(
typeof option === "string" ? option : option?.value,
),
)}
onChange={handleValueChange}
renderInput={params => <TextField {...params} />}
renderOption={(props, option, { selected }) => (
<li {...props}>
<Checkbox
icon={icon}
checkedIcon={checkedIcon}
style={{ marginRight: 8 }}
checked={selected}
/>
{typeof option === "string" ? option : option?.label}
</li>
)}
data-testid="autocomplete-value"
/>
)

return (
<Autocomplete<string | OperationOption, false>
openOnFocus
autoSelect
disabled={!operator}
options={operator.options}
value={arrayFirst(filter.value)}
onChange={handleValueChange}
renderInput={params => <TextField {...params} />}
data-testid="autocomplete-value"
/>
)
}

export default FilterRowValue
203 changes: 203 additions & 0 deletions grai-frontend/src/components/filters/filters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
export type Filter = {
type: string | null
field: string | null
operator: string | null
value: string | string[] | null
}

export interface OperationOption {
value: string
label: string
}

export type Operator = {
value: string
label: string
shortLabel?: string
options?: (string | OperationOption)[]
multiple?: boolean
}

export type Field = {
value: string
label: string
disabled?: boolean
operators: Operator[]
}

export type Property = {
value: string
label: string
disabled?: boolean
fields: Field[]
}

export interface Source {
id: string
name: string
}

export const defaultFilter: Filter = {
type: "table",
field: null,
operator: null,
value: null,
}

const nameField: Field = {
value: "name",
label: "Name",
operators: [
{
value: "equals",
label: "Equals",
shortLabel: "=",
},
{
value: "contains",
label: "Contains",
shortLabel: "*a*",
},
{
value: "starts-with",
label: "Starts With",
shortLabel: "a*",
},
{
value: "ends-with",
label: "Ends With",
shortLabel: "*a",
},
{
value: "not-equals",
label: "Not Equals",
shortLabel: "!=",
},
{
value: "not-contains",
label: "Not Contains",
shortLabel: "!*a*",
},
],
}

const namespaceField = (namespaces: string[]): Field => ({
value: "namespace",
label: "Namespace",
operators: [
{
value: "equals",
label: "Equals",
options: namespaces,
},
{
value: "in",
label: "In",
options: namespaces,
multiple: true,
},
],
})

const sourceField = (sources: Source[]): Field => ({
value: "data-source",
label: "Data Source",
operators: [
{
value: "in",
label: "In",
options: sources.map(source => ({
value: source.id,
label: source.name,
})),
multiple: true,
},
{
value: "not-in",
label: "Not In",
options: sources.map(source => ({
value: source.id,
label: source.name,
})),
multiple: true,
},
],
})

const tagField = (tags: string[]): Field => ({
value: "tag",
label: "Tag",
operators: [
{
value: "contains",
label: "Contains",
options: tags,
},
{
value: "not-contains",
label: "Doesn't Contain",
options: tags,
},
],
})

export const getProperties = (
namespaces: string[],
tags: string[],
sources: Source[],
): Property[] => [
{
value: "table",
label: "Default",
fields: [
nameField,
namespaceField(namespaces),
sourceField(sources),
tagField(tags),
],
},
{
value: "parent",
label: "Has Parent",
disabled: true,
fields: [],
},
{
value: "no-parent",
label: "No Parent",
disabled: true,
fields: [],
},
{
value: "child",
label: "Has Child",
disabled: true,
fields: [],
},
{
value: "no-child",
label: "No Child",
disabled: true,
fields: [],
},
{
value: "ancestor",
label: "Has Ancestor",
fields: [tagField(tags)],
},
{
value: "no-ancestor",
label: "No Ancestor",
fields: [tagField(tags)],
},
{
value: "descendant",
label: "Has Descendant",
fields: [tagField(tags)],
},
{
value: "no-descendant",
label: "No Descendant",
fields: [tagField(tags)],
},
]
Loading

0 comments on commit e810f00

Please sign in to comment.