Skip to content

feat(filter): add support for more operators than contains #1750

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

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
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
15 changes: 15 additions & 0 deletions src/backend/utils/filter/filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,21 @@ import { ActionContext } from '../../actions/index.js'

export const PARAM_SEPARATOR = '~~'

export const OPERATOR_SEPARATOR = '~'

export const MATCHING_PATTERNS = {
EQ: 'equals',
NE: 'notEquals',
CO: 'contains',
EW: 'endsWith',
SW: 'startsWith',
}

export const OPERATORS = {
AND: 'and',
OR: 'or',
}

export type FilterElement = {
path: string;
property: BaseProperty;
Expand Down
153 changes: 150 additions & 3 deletions src/frontend/components/property-type/default-type/filter.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,113 @@
import React from 'react'
import { FormGroup, Input, Select } from '@adminjs/design-system'
import { Box, FormGroup, Input, Select } from '@adminjs/design-system'

import allowOverride from '../../../hoc/allow-override.js'
import { FilterPropertyProps } from '../base-property-props.js'
import PropertyLabel from '../utils/property-label/property-label.js'
import { useTranslation } from '../../../hooks/use-translation.js'
import * as BackendFilter from '../../../../backend/utils/filter/filter.js'

const { MATCHING_PATTERNS, OPERATOR_SEPARATOR, OPERATORS, PARAM_SEPARATOR } = BackendFilter

const getKey = (path:string, patternMatching: string, operator:string) => {
if (patternMatching === MATCHING_PATTERNS.CO) {
if (operator === OPERATORS.AND) {
return path
}

return `${path}${PARAM_SEPARATOR}${operator}`
}
if (operator === OPERATORS.AND) {
return `${path}${PARAM_SEPARATOR}${patternMatching}`
}
return `${path}${PARAM_SEPARATOR}${operator}${OPERATOR_SEPARATOR}${patternMatching}`
}

const generatePossibleKeys = (path:string) => {
const possibleKeys:string[] = []

for (const matchingPattern of Object.values(MATCHING_PATTERNS)) {
for (const operator of Object.values(OPERATORS)) {
possibleKeys.push(getKey(path, matchingPattern, operator))
}
}

return possibleKeys
}

const Filter: React.FC<FilterPropertyProps> = (props) => {
const { property, onChange, filter } = props

const possibleKeys = generatePossibleKeys(property.path)

const [currentKey, currentInput] = Object.entries(filter).find(
([key]) => possibleKeys.includes(key),
) || []
let currentPatternMatching = MATCHING_PATTERNS.CO
let currentOperator = OPERATORS.AND

if (currentKey) {
const filterProperties = currentKey.split(PARAM_SEPARATOR)
const param = (filterProperties[1] || '').split(OPERATOR_SEPARATOR)
const tentativeOperator = param[0] || ''
const tentativeMatchingPattern = (param.length > 1 ? param[1] : param[0]) || ''
currentOperator = Object.values(OPERATORS).includes(tentativeOperator)
? tentativeOperator : OPERATORS.AND
currentPatternMatching = Object.values(MATCHING_PATTERNS).includes(tentativeMatchingPattern)
? tentativeMatchingPattern : MATCHING_PATTERNS.CO
}

const { tl } = useTranslation()

const handleInputChange = (event) => {
onChange(property.path, event.target.value)
}

const handleInputInComboChange = (event) => {
const key = getKey(property.path, currentPatternMatching, currentOperator)
onChange(key, event.target.value)
}

const handleSelectChange = (selected) => {
const value = selected ? selected.value : ''
onChange(property.path, value)
}

const handleSelectPatternMatchingInComboChange = (selected) => {
const changedKey = getKey(
property.path,
selected?.value || MATCHING_PATTERNS.CO,
currentOperator,
)

possibleKeys.forEach((key) => {
if (key !== changedKey) {
delete filter[key]
}
})
onChange(changedKey, currentInput || '')
}

const handleSelectOperatorInComboChange = (selected) => {
const changedKey = getKey(
property.path,
currentPatternMatching,
selected?.value || OPERATORS.AND,
)

possibleKeys.forEach((key) => {
if (key !== changedKey) {
delete filter[key]
}
})
onChange(changedKey, currentInput || '')
}

const renderInput = () => {
const filterKey = `filter-${property.path}`
const value = filter[property.path] || ''
const valueKey = getKey(property.path, currentPatternMatching, currentOperator)

const filterKey = `filter-${valueKey}`
const value = filter[valueKey] || ''
if (property.availableValues) {
const availableValues = property.availableValues.map((v) => ({
...v,
Expand All @@ -40,6 +126,67 @@ const Filter: React.FC<FilterPropertyProps> = (props) => {
/>
)
}

if (property.type === 'string') {
const patternMatching = { label: currentPatternMatching, value: currentPatternMatching }
const operator = { label: currentOperator, value: currentOperator }
return (
<Box flex flexDirection="column">
<Input
name={filterKey}
onChange={handleInputInComboChange}
value={filter[currentKey || property.path] || ''}
/>
<Box flex flexDirection="row">
<Box flexGrow={1}>
<Select
value={patternMatching}
options={[
{
label: MATCHING_PATTERNS.CO,
value: MATCHING_PATTERNS.CO,
},
{
label: MATCHING_PATTERNS.EQ,
value: MATCHING_PATTERNS.EQ,
},
{
label: MATCHING_PATTERNS.NE,
value: MATCHING_PATTERNS.NE,
},
{
label: MATCHING_PATTERNS.SW,
value: MATCHING_PATTERNS.SW,
},
{
label: MATCHING_PATTERNS.EW,
value: MATCHING_PATTERNS.EW,
},
]}
onChange={handleSelectPatternMatchingInComboChange}
/>
</Box>
<Box flexGrow={1}>
<Select
value={operator}
options={[
{
label: OPERATORS.AND,
value: OPERATORS.AND,
},
{
label: OPERATORS.OR,
value: OPERATORS.OR,
},
]}
onChange={handleSelectOperatorInComboChange}
/>
</Box>
</Box>
</Box>
)
}

return (
<Input
name={filterKey}
Expand Down