-
Notifications
You must be signed in to change notification settings - Fork 2k
DataViews: Add user input filter #103276
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
DataViews: Add user input filter #103276
Conversation
This update introduces a new optional property, userInput, to both the FilterByConfig and NormalizedFilter interfaces, allowing filters to accept user input. Additionally, a type property is added to NormalizedFilter for better field type specification.
This commit introduces the UserInputWidget component, which allows users to input values for filters in the dataviews. It includes a SingleInputSummary for managing single selection operators and utilizes a debounced input for better performance. The component integrates with existing filter logic to update the view's filters based on user input.
This update modifies the FilterSummary component to conditionally render the UserInputWidget based on the userInput property of filters. It also adjusts the logic for determining active elements to accommodate user input, ensuring a seamless integration with existing filter functionalities. Additionally, the useFilters hook is updated to handle the new userInput property, and styles are added for the UserInputWidget.
This update enhances the sanitizeOperators function to specifically filter operators for integer fields. Additionally, the logic in the column header menu is adjusted to allow adding filters based on user input or existing elements, improving the overall filtering capabilities in the dataviews.
This update introduces a filterBy property for integer fields in the fixtures, allowing the use of specific operators and enabling user input for filtering. This enhancement aligns with previous updates to improve filtering capabilities in the dataviews.
This update refines the UserInputWidget component by renaming the currentFilter to activeFilter for clarity and consistency. The applyFilterChange function is simplified to directly handle the new value logic, enhancing readability. Additionally, the SingleInputSummary component is updated to ensure it only renders when an active filter is present and valid, improving the overall user experience in managing filters.
Calypso Live (direct link)
|
|
Here is how your PR affects size of JS and CSS bundles shipped to the user's browser: Sections (~278 bytes added 📈 [gzipped]) Sections contain code specific for a given set of routes. Is downloaded and parsed only when a particular route is navigated to. Legend What is parsed and gzip size?Parsed Size: Uncompressed size of the JS and CSS files. This much code needs to be parsed and stored in memory. Generated by performance advisor bot at iscalypsofastyet.com. |
youknowriad
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for working on this, the behavior seems correct but the abstraction/implementation not yet. Check the other comments and let me know what you think.
packages/dataviews/src/types.ts
Outdated
| /** | ||
| * Whether the filter allows user input instead of selecting from the list of options. | ||
| */ | ||
| userInput?: boolean; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this needed? Can we just fallback to the type's Edit function (which is already a user input) when the "type" is defined.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We introduced userInput prop because we want to differentiate the user input from the preset options. Previously, the filter can only be added if field has element. But for integer filer, users should be able to enable filter without adding any elements.
Enabling the filter based on the field type is an option, but it might not provide enough flexibility. For the integer type, I think these scenarios should be supported:
- No filter needed for the field
- Filter with preset options (set elements)
- Filter with user input (userInput: true)
- Filter with preset options and user input (not sure if this is needed?)
Does this make sense? Or we should always enable the filter for integer type?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For me if there's no elements, it should just fallback automatically to userInput, so the prop might not be needed at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've left some comment below on the API that is worth checking, but iterating on it and addressing the states we want to support:
- No filter needed for the field.
{
id: 'id',
type: 'integer'
}
- Filter with preset options and user input.
This is the default experience for typed fields that provide elements. It's the same for date.
{
id: 'id',
type: 'integer',
elements: [ ... ]
}
- Filter with only user input.
{
id: 'id',
type: 'integer',
filterBy: {
input: true
}
}
- Filter with only presets
{
id: 'id',
type: 'integer',
elements: [ ... ],
filterBy: {
input: false
}
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the feedback! @youknowriad @oandregal
For this PR, I'll go with the approach that @oandregal suggested.
- if field.type is integer, display the Edit control for the filter (input control for integers, by default, but the consumer can provide its own).
- if field.elements is provided, display presets as well.
For Woo Analytics, the design requires support for hiding the input control. However, I'll double check with the designer. If the designer still prefers hiding the input control, I'll work on it in a separate PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure I like the "input" config personally, I'd rather start opinionated: by default "input", if there are elements "presets", no mix of both, and potentially have a way to disable a field from being a filter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather start opinionated: by default "input", if there are elements "presets", no mix of both, and potentially have a way to disable a field from being a filter.
This sounds good to me. We don't have use cases for both "input" and "presets" at the same time.
Quick summary:
| Field Configuration | filterBy.enabled |
elements Present |
Rendered Filter UI |
|---|---|---|---|
{ type: 'integer' } |
not set (default) | ❌ | Input |
{ type: 'integer', elements: [...] } |
not set (default) | ✅ | Presets |
{ type: 'integer', filterBy: { enabled: false }} |
false | ❌ or ✅ | No filter |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't have use cases for both "input" and "presets" at the same time.
Yes, we do :) The date filter is one use case, for example. This is the default experience in my view.
{ type: 'integer' }
I don't think this field configuration should render any filter. Filter have been "opt-in" so far, and I think it makes more sense for them to stay that way. To opt-in, either you provide elements or you enable something so it displays the Edit/input. Additionally, consumers should be able to disable the input/Edit or the presets.
Based on the use cases you mentioned before, I'll elaborate a bit more on how I see this working:
- By default, a field doesn't provide any filter:
{ type: 'integer' } - If it has elements, it provides a filter with input/Edit and presets (this is the default experience):
{ type: 'integer', elements: [ ... ] }- It should be possible to disable the input or the presets:
{ type: 'integer', elements: [ ... ], filterBy: { widget: [ 'presets' ] } } // only enable presets
{ type: 'integer', elements: [ ... ], filterBy: { widget: [ 'edit' ] } } // only enable edit- It should be possible to enable the filter even if the field doesn't have elements:
{ type: 'integer', filterBy: { widget: [ 'edit' ] } } I care about supporting this experiences, but I don't mind much about using filterBy.input, filterBy.widget or any other naming. I'll be AKF next week, so I won't be able to provide feedback, but wanted to share how I expect this to work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, we do :) The date filter is one use case, for example. This is the default experience in my view.
Ah, you're right! The date filter does require both input and presets. 🤔
I care about supporting this experiences, but I don't mind much about using filterBy.input, filterBy.widget or any other naming.
I don't mind the naming either. I think the key point is whether the filter should be enabled or disabled by default.
Since we want to support input and presets, I'd agree to disable the filter by default to avoid clutter and confusion; no filter is rendered unless the consumer explicitly opts in.
| <VStack spacing={ 0 } justify="flex-start"> | ||
| <OperatorSelector { ...commonProps } /> | ||
| <SearchWidget { ...commonProps } /> | ||
| { filter.userInput ? ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this checking for a new prop userInput? Can we use the field type instead? For context, that's what I've explored in the date filter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We use userInput prop because if users only provide preset options, we should render SearchWidget. We can't use the field type to determine if we should render UserInputWidget or SearchWidget.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We use userInput prop because if users only provide preset options, we should render SearchWidget
This is how I'd expect it to work:
- if
field.typeisinteger, display theEditcontrol for the filter (input control for integers, by default, but the consumer can provide its own). - if
field.elementsis provided, display presets as well.
Do you need support for the case of hiding the input control? Elements without input is how the current filters work (untyped fields). If you want to add that in this PR, I'd suggest a generic approach that works for any field type later. For example, adding a new flag under filterBy to only render presets (the default experience is using everything):
{
id: '...',
type: 'integer',
elements: [ ... ]
filterBy: {
presetsOnly: true
}
}Additionally, I think the SearchWidget should just have different widgets per field type:
if ( props.filter.type === 'datetime' ) { ... }
if ( props.filter.type === 'integer' ) { ... }
// Default widgets that just render elements.As we iterate on this, using this approach would be clearer and has the following advantage: when/if we allow 3rd parties to define their own field types, they'll just work if we're able to make the filter part of the field definition. It's good to have it in mind, but it's not a requirement to implement it like that right now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Continued the conversation above #103276 (comment)
packages/dataviews/src/components/dataviews/stories/fixtures.tsx
Outdated
Show resolved
Hide resolved
|
This PR modifies the release build for the following Calypso Apps: For info about this notification, see here: PCYsg-OT6-p2
To test WordPress.com changes, run |
This change modifies the SearchWidget component to ensure that the InputWidget is only rendered for integer filters when no elements are present. This adjustment enhances the filtering logic and improves user experience by preventing unnecessary input fields from appearing.
packages/dataviews/src/components/dataviews-filters/filter-summary.tsx
Outdated
Show resolved
Hide resolved
|
|
||
| export default function SearchWidget( props: SearchWidgetProps ) { | ||
| if ( props.filter.type === 'integer' && ! props.filter.elements ) { | ||
| return <InputWidget { ...props } />; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I understand what's happening here and why we're creating this new "InputWidget" component, I believe we should just rely on the "edit" defined by the field type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The InputWidget component is created for managing the onChange event and the filter value. It does rely on the Edit component within it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Additionally, the InputWidget component will accommodate operators such as between and not between (we
plan to add these in another PR) for integer types, which require the rendering of two Edit components.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense, but we shouldn't be hard coding the "type", we should check the presence of "edit" and the config of the "type" instead. It's important to understand that when we add a new "type" to dataviews, we shouldn't have to touch any other file in the library, registration of types should be decoupled from the framework itself.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, thanks for the clarification. 🙏
I thought we wanted to add support for the integer field first, and gradually add support for other field types. The type logic is temporary and will be removed in the future. 🤔
Makes sense, but we shouldn't be hard coding the "type", we should check the presence of "edit" and the config of the "type" instead.
Then, this PR will affect all field types. Any fields with an Edit Prop will enable filter support. cc @oandregal
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The NormalizeField appears to guarantee the presence of the Edit prop. In this case, we should verify that the Edit returns non-null elements, right?
wp-calypso/packages/dataviews/src/types.ts
Line 188 in a68a7ad
| Edit: ComponentType< DataFormControlProps< Item > >; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The NormalizeField appears to guarantee the presence of the Edit prop. In this case, we should verify that the Edit returns non-null elements, right?
This is interesting, you're right, it does seem like it's always defined. Personally, I think we should change the normalized field to have a null Edit when there's no edit defined (basically when the field has no "type" or the field has no explicit "Edit" function).
I thought we wanted to add support for the integer field first, and gradually add support for other field types.
You're right, making this change will enable the direct input for all types, I think it's a good thing though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense, but we shouldn't be hard coding the "type", we should check the presence of "edit" and the config of the "type" instead. It's important to understand that when we add a new "type" to dataviews, we shouldn't have to touch any other file in the library, registration of types should be decoupled from the framework itself.
As I mentioned, I agree with this direction, but I am fine if we start limiting by type for now (making it work based on the Edit requires a few big changes, so starting with the types helps contain scope). I'm also fine if we want to make it all work for all types at once in a single PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I've updated the PR as suggested.
- Changed
EditProp type to acceptnull - Added user input filter support based on the
Editproperty - Added default filterBy operators for each field type
- Updated PR description and changelog to reflect the changes
Screen.Recording.2025-05-16.at.16.37.22.mov
This PR doesn't add a ability to disable the filter yet. I'll add that in a separate PR. Please review when you have time and let me know if anything else needs to be changed. 🙏
…e Edit Update changelog to reflect this change.
| return ( | ||
| <div className="dataviews-filters__user-input-widget"> | ||
| <field.Edit | ||
| data={ { [ field.id ]: currentValue } } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This Edit function doesn't have access to the whole object, just one value. This is not a restriction we have in DataForm, where the Edit is used. What if the Edit access values other than the field.id (e.g., "price" + "discount" to generate a "consolidated price")?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've changed how InputWidget passes data to Edit, providing all filter values instead in 5920eed.
packages/dataviews/src/types.ts
Outdated
| * The list of options to pick from when using the field as a filter. | ||
| */ | ||
| elements: Option[]; | ||
| elements?: Option[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea of NormalizedFilter (or NormalizedField) is that all props should exist, so code that deals with this type doesn't need to do checks for existance (we're normalized them before). In this case, why does it need to change to undefined? I haven't checked all the code, but I suppose we're using this to check something. Can't we check for size of the array?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed it to optional because the useFilters doesn't normalize the elements prop, it's pass through as is so it could be undefined or an empty array. If that's the case, then it's a bug. I'll revert the type and fix the bug to ensure the elements are always defined. We can check the size.
| elements: field.elements, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed filter.elements type and checked array size instead. 58f3f5e
| getValue: ( args: { item: Item } ) => any; | ||
| render: ComponentType< DataViewRenderFieldProps< Item > >; | ||
| Edit: ComponentType< DataFormControlProps< Item > >; | ||
| Edit: ComponentType< DataFormControlProps< Item > > | null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same thought here about "NormalizedField".
Additionally, if the filter is opt-in, as suggested, do we need to change Edit? We'd check for the presence of elements of a filterBy config instead of Edit, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Modifying the Edit function could prevent situations where the filter is opt-in, yet the Edit function displays empty content. Certain type definitions within the Edit function can lead to this issue. I'll check the code and think deeper about this.
packages/dataviews/src/types.ts
Outdated
| /** | ||
| * The filter config for the field. | ||
| */ | ||
| filterBy?: FilterByConfig | undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I consider FieldDefinition as "good defaults" for each type. Instead of allowing it to be undefined, can we making the filterBy.operators mandatory for every field type (and also for the untyped fallback)? It's okay if the fieldtype definition says "no operator" (empty set).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I'll make the operators mandatory.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated filterBy property to be required and set with default operators in field types in 09072f3.
|
Considering this comment as how we want it to work, this PR still needs to allow rendering both Edit and presets. We'd need to add a "custom" preset in that scenario that's selected when the user interacts with the Edit function, as in this example: Screen.Recording.2025-05-16.at.12.10.29.mov |
| ); | ||
| } | ||
|
|
||
| export default function FilterSummary( { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel the name of this component is a bit weird, this seems to be the actual filter (not a summary)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, it can be confusing. This is the code where the component is used. Renaming it to 'Filter' would be clearer. I'll rename it.
wp-calypso/packages/dataviews/src/components/dataviews-filters/index.tsx
Lines 195 to 208 in e29a70a
| const filterComponents = [ | |
| ...visibleFilters.map( ( filter ) => { | |
| return ( | |
| <FilterSummary | |
| key={ filter.field } | |
| filter={ filter } | |
| view={ view } | |
| fields={ fields } | |
| onChangeView={ onChangeView } | |
| addFilterRef={ addFilterRef } | |
| openedFilter={ openedFilter } | |
| /> | |
| ); | |
| } ), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed FilterSummary component to Filter in fe6fada.
youknowriad
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't test everything, but this is looking good to me.
|
When it comes to "presets" and "direct input". It doesn't seem like it's "configuration based". If you take a look at the "date" filter example, the presets are not shown in a select box and then we show the "edit" of the date type. It's a completely advanced UI. In other words, the For the other types it's more "normalized", it's either "elements" in a select/listbox or a simple input. So I'm actually wondering the solution is to remove the automatic handling of "elements" from the "framework" and move it to the "Edit" function/controls instead. Maybe it's fine to not address this immediately in this PR and think it properly with the Date "elements" support separately. |
…perators in field types
| .components-input-control__prefix { | ||
| padding-left: $grid-unit-10; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needs an empty line
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 53f987c.
youknowriad
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's ship this and iterate.
| * Internal dependencies | ||
| */ | ||
| import SearchWidget from './search-widget'; | ||
| import InputWidget from './input-widget'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@chihsuan @youknowriad I want to make sure everyone is aware that every 2 week we sync changes from Gutenberg upstream into the packages/dataviews folder in calypso as per PCYsg-173c-p2.
Renaming a file is the kind of thing we shouldn't do. Most of the time it'll go fine, but it can create unnecessary work in these syncs — for example, if anyone creates a new file filter-summary.tsx, git won't be able to detect the original summary was renamed. My recommendation is that we revert this file rename.
I'm also not super stoked about other renames in this PR (FilterSummary to Filter, etc.) for the same reasons, but their surface for errors is minor — it's okay to keep them if you feel strongly about them. My general recommendation would be to make the minimal change that works and avoid needless refactors (renames, reorganizations, etc.).
This may sound like a little thing, but if we're not diligent with the kind of changes we make, we'll run into issues with the sync process.
|
@chihsuan @youknowriad this PR has made filters visible for any field that declares a type without any way to opt-out. This is problematic, as I mentioned (here and here). For example, this field declaration generates a filter and there's no way to opt-out: {
id: 'date',
label: 'Date',
type: 'datetime',
}I've started a draft to look into this at #103949 |
|
Reviewing this work, I also see there's a follow-up I mentioned: the ability to have a filter that displays presets and the Edit function. I've been AFK and focused on other projects, so I may have missed some conversations: is anyone looking at this? @chihsuan |
Thanks for looking into this! 🙏 @oandregal
I will look at it, but I'm pausing for now because #103660 introduces "operators" to the Edit function. Before moving forward with the "date", I want to confirm that that is the correct approach. As @youknowriad commented, for all types except "date", it should display either the "elements" in a select or listbox, or use a basic input field. For the "date" type, we can modify the Edit function to accept elements and render both the preset options and a calendar picker. Additionally, we will need to render different controls for the "date" type based on the operator. |
Thanks for the context. I think I'm almost caught up. I want to address #103949 first, and then will look at the two other PRs for operators you've prepared. |



Part of WOOA7S-123
Proposed Changes
Add support for user input in filters
Why are these changes being made?
To support dynamic filtering for integer fields and other types that benefit from user input, aligning with ongoing improvements to DataViews filtering capabilities.
Some use cases require filters that accept user-provided input — like "Price greater than 100", or "Created before 2023-01-01". Right now, the package doesn’t allow this, making it hard to support numeric or textual search criteria.
Testing Instructions
yarn dataviews:storybook:starthttp://localhost:57863/?path=/story/dataviews-dataviews--defaultPre-merge Checklist