Skip to content

Commit

Permalink
DataViews: simplify filters API (#55917)
Browse files Browse the repository at this point in the history
  • Loading branch information
oandregal authored Nov 8, 2023
1 parent 48c74db commit e4c8ef8
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 107 deletions.
70 changes: 23 additions & 47 deletions packages/edit-site/src/components/dataviews/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,21 +59,38 @@ Example:
- `sort.field`: field used for sorting the dataset.
- `sort.direction`: the direction to use for sorting, one of `asc` or `desc`.
- `search`: the text search applied to the dataset.
- `filters`: the filters applied to the dataset. See filters section.
- `filters`: the filters applied to the dataset. Each item describes:
- `field`: which field this filter is bound to.
- `operator`: which type of filter it is. Only `in` available at the moment.
- `vaule`: the actual value selected by the user.
- `visibleFilters`: the `id` of the filters that are visible in the UI.
- `hiddenFields`: the `id` of the fields that are hidden in the UI.
- `layout`: ...

Note that it's the consumer's responsibility to provide the data and make sure the dataset corresponds to the view's config (sort, pagination, filters, etc.).
### View <=> data

Example:
The view is a representation of the visible state of the dataset. Note, however, that it's the consumer's responsibility to work with the data provider to make sure the user options defined through the view's config (sort, pagination, filters, etc.) are respected.

The following example shows how a view object is used to query the WordPress REST API via the entities abstraction. The same can be done with any other data provider.

```js
function MyCustomPageList() {
const [ view, setView ] = useState( {
type: 'list',
perPage: 5,
page: 1,
"...": "..."
sort: {
field: 'date',
direction: 'desc',
},
search: '',
filters: [
{ field: 'author', operator: 'in', value: 2 },
{ field: 'status', operator: 'in', value: 'publish,draft' }
],
visibleFilters: [ 'author', 'status' ],
hiddenFields: [ 'date', 'featured-image' ],
layout: {},
} );

const queryArgs = useMemo( () => {
Expand Down Expand Up @@ -143,7 +160,7 @@ Example:
{ value: 1, label: 'Admin' }
{ value: 2, label: 'User' }
]
filters: [ 'enumeration' ],
filters: [ 'in' ],
}
]
```
Expand All @@ -153,45 +170,4 @@ Example:
- `getValue`: function that returns the value of the field.
- `render`: function that renders the field.
- `elements`: the set of valid values for the field's value.
- `filters`: what filters are available for the user to use. See filters section.

## Filters

Filters describe the conditions a record should match to be listed as part of the dataset. Filters are provided per field.

```js
const field = [
{
id: 'author',
filters: [ 'enumeration' ],
}
];

<DataViews
fields={ fields }
/>
```

A filter is an object that may contain the following properties:

- `type`: the type of filter. Only `enumeration` is supported at the moment.
- `elements`: for filters of type `enumeration`, the list of options to show. A one-dimensional array of object with value/label keys, as in `[ { value: 1, label: "Value name" } ]`.
- `value`: what's serialized into the view's filters.
- `label`: nice-looking name for users.

As a convenience, field's filter can provide abbreviated versions for the filter. All of following examples result in the same filter:

```js
const field = [
{
id: 'author',
header: __( 'Author' ),
elements: authors,
filters: [
'enumeration',
{ type: 'enumeration' },
{ type: 'enumeration', elements: authors },
],
}
];
```
- `filters`: what filter operators are available for the user to use over this field. Only `in` available at the moment.
78 changes: 33 additions & 45 deletions packages/edit-site/src/components/dataviews/filters.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,67 +6,55 @@ import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import InFilter from './in-filter';
import { default as InFilter, OPERATOR_IN } from './in-filter';
const VALID_OPERATORS = [ OPERATOR_IN ];

export default function Filters( { fields, view, onChangeView } ) {
const filterIndex = {};
const filtersRegistered = [];
fields.forEach( ( field ) => {
if ( ! field.filters ) {
return;
}

field.filters.forEach( ( filter ) => {
const id = field.id;
if ( 'string' === typeof filter ) {
filterIndex[ id ] = {
id,
if ( VALID_OPERATORS.some( ( operator ) => operator === filter ) ) {
filtersRegistered.push( {
field: field.id,
name: field.header,
type: filter,
};
}

if ( 'object' === typeof filter ) {
filterIndex[ id ] = {
id,
name: field.header,
type: filter.type,
};
}

if ( 'enumeration' === filterIndex[ id ]?.type ) {
const elements = [
{
value: '',
label: __( 'All' ),
},
...( field.elements || [] ),
];
filterIndex[ id ] = {
...filterIndex[ id ],
elements,
};
operator: filter,
elements: [
{
value: '',
label: __( 'All' ),
},
...( field.elements || [] ),
],
} );
}
} );
} );

return view.visibleFilters?.map( ( filterName ) => {
const filter = filterIndex[ filterName ];
return view.visibleFilters?.map( ( fieldName ) => {
const visibleFiltersForField = filtersRegistered.filter(
( f ) => f.field === fieldName
);

if ( ! filter ) {
if ( visibleFiltersForField.length === 0 ) {
return null;
}

if ( filter.type === 'enumeration' ) {
return (
<InFilter
key={ filterName }
filter={ filter }
view={ view }
onChangeView={ onChangeView }
/>
);
}

return null;
return visibleFiltersForField.map( ( filter ) => {
if ( OPERATOR_IN === filter.operator ) {
return (
<InFilter
key={ fieldName }
filter={ visibleFiltersForField[ 0 ] }
view={ view }
onChangeView={ onChangeView }
/>
);
}
return null;
} );
} );
}
9 changes: 5 additions & 4 deletions packages/edit-site/src/components/dataviews/in-filter.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import {
SelectControl,
} from '@wordpress/components';

const OPERATOR_IN = 'in';
export const OPERATOR_IN = 'in';

export default ( { filter, view, onChangeView } ) => {
const valueFound = view.filters.find(
( f ) => f.field === filter.id && f.operator === OPERATOR_IN
( f ) => f.field === filter.field && f.operator === OPERATOR_IN
);

const activeValue =
Expand All @@ -32,11 +32,12 @@ export default ( { filter, view, onChangeView } ) => {
options={ filter.elements }
onChange={ ( value ) => {
const filters = view.filters.filter(
( f ) => f.field !== filter.id || f.operator !== OPERATOR_IN
( f ) =>
f.field !== filter.field || f.operator !== OPERATOR_IN
);
if ( value !== '' ) {
filters.push( {
field: filter.id,
field: filter.field,
operator: OPERATOR_IN,
value,
} );
Expand Down
16 changes: 7 additions & 9 deletions packages/edit-site/src/components/dataviews/view-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,11 @@ function HeaderMenu( { dataView, header } ) {
if (
header.column.columnDef.filters?.length > 0 &&
header.column.columnDef.filters.some(
( f ) =>
( 'string' === typeof f && f === 'enumeration' ) ||
( 'object' === typeof f && f.type === 'enumeration' )
( f ) => 'string' === typeof f && f === 'in'
)
) {
filter = {
id: header.column.columnDef.id,
field: header.column.columnDef.id,
elements: [
{
value: '',
Expand Down Expand Up @@ -149,7 +147,7 @@ function HeaderMenu( { dataView, header } ) {
{ isFilterable && (
<DropdownMenuGroupV2>
<DropdownSubMenuV2
key={ filter.id }
key={ filter.field }
trigger={
<DropdownSubMenuTriggerV2
prefix={ <Icon icon={ funnel } /> }
Expand All @@ -169,7 +167,7 @@ function HeaderMenu( { dataView, header } ) {
( f ) =>
Object.keys( f )[ 0 ].split(
':'
)[ 0 ] === filter.id
)[ 0 ] === filter.field
);

// Set the empty item as active if the filter is not set.
Expand Down Expand Up @@ -204,7 +202,7 @@ function HeaderMenu( { dataView, header } ) {
)[ 0 ].split( ':' );
return (
field !==
filter.id ||
filter.field ||
operator !== 'in'
);
}
Expand All @@ -218,8 +216,8 @@ function HeaderMenu( { dataView, header } ) {
dataView.setColumnFilters( [
...otherFilters,
{
[ filter.id + ':in' ]:
element.value,
[ filter.field +
':in' ]: element.value,
},
] );
}
Expand Down
4 changes: 2 additions & 2 deletions packages/edit-site/src/components/page-pages/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ export default function PagePages() {
</a>
);
},
filters: [ 'enumeration' ],
filters: [ 'in' ],
elements:
authors?.map( ( { id, name } ) => ( {
value: id,
Expand All @@ -244,7 +244,7 @@ export default function PagePages() {
getValue: ( { item } ) =>
statuses?.find( ( { slug } ) => slug === item.status )
?.name ?? item.status,
filters: [ 'enumeration' ],
filters: [ 'in' ],
elements:
statuses?.map( ( { slug, name } ) => ( {
value: slug,
Expand Down

1 comment on commit e4c8ef8

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flaky tests detected in e4c8ef8.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/6795834435
📝 Reported issues:

Please sign in to comment.