Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

filters refactor #614

Merged
merged 2 commits into from
Aug 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading