BowlingX/ra-postgraphile

Please document how to override filter operators

friday opened this issue ยท 4 comments

In #10 and #17 there are some posts of suggested syntax to use such as source="name@eq" or source="firstname||starts" or <TextInput source="name" {...includesInsensitive} />, but I don't think you actually implemented anything like that?

In case it's not possible I made this wrapper that implements it with the @ character as in #10. It would be easy to add in the actual ra-postgraphile code also, but it's weird that you would call #17 a fix for #10 if it can't be used by users as suggested?

import { ApolloClient } from '@apollo/client';
import pgDataProvider, { ProviderOptions } from 'ra-postgraphile';

async function wrapper(client: ApolloClient<any>, options?: ProviderOptions) {
    const provider = await pgDataProvider(client, options);

    return {
        ...provider,
        getList(resource: string, params: any) {
            // Override ra-postgraphile default filter operator mapping
            // Ex: <SearchInput source="name@includesInsensitive" />)
            Object.entries(params.filter || {}).forEach(([fullKey, value]) => {
                const [key, operator = "", ...rest] = fullKey.split("@");
                if (operator && rest.length === 0) {
                    delete params.filter[fullKey];
                    params.filter[key] = { operator, value }
                }
            });
            return provider.getList(resource, params);
        },
    };
}

export default wrapper;

Hi, thank you for reporting this;
The documentation was indeed missing; I adjusted the documentation here: https://github.com/BowlingX/ra-postgraphile#custom-filters.

Let me know if this is sufficient for your case.

๐ŸŽ‰ This issue has been resolved in version 6.1.0 ๐ŸŽ‰

The release is available on:

Your semantic-release bot ๐Ÿ“ฆ๐Ÿš€

Thank you @BowlingX ๐Ÿ‘ That was very fast, and a thorough job documenting :)

The code needed to override a filter is a bit on the verbose side for me personally, but probably justifiably so if you want stricter type checking or nested filters.

I tried it and it worked for me with my first filter case:

const setFilterOp = (operator: string, fallback: string|object) => ({
  parse: (value: string|object) => ({ operator, value }) as FilterSpec,
  format: (filter: FilterSpec) => filter?.value || fallback,
});
<SearchInput source="name" {...setFilterOp('includesInsensitive', '')} />

However, this sets the filter to {filterName: FilterSpec} instead of {filterName: value} for react-admins internal use also, including the url encoded parameter in the page link.

I have it set up so that for example each row in the list page of "Categories" have links to the list page of "Products" with that category applied to the filter (like this). So with this solution I would have to know the operator and pass the full FilterSpec syntax from that page also. And if I change it in one place, I have to remember to change it elsewhere. And if users use the new Saved Queries feature that means all of a sudden the operator would propagate to the user history also and you would have to prune them or tell users to (although this is kind of an inherent issue with saved queries and making changes to filters). And with nested filters example this would be even more complex.

So I wouldn't want to use this way personally. I did keep testing it though, and found more issues:

It also breaks for me with ReferenceInput and ReferenceArrayInput with SelectInput and SelectArrayInput inside. Those are likely new bugs as I just upgraded to RA4 and they seemingly haven't worked out all the kinks yet. I'm new to these methods and their scope and I'm not confident I understand this enough to report the issue to them.

If you are curious about the details on how the Reference:

For the first case it correctly generates the filter and fetches the query, but then it can't render itself after that (no actual errors thrown).

<ReferenceInput
    source="categoryId"
    reference="categories"
    sort={{ field: 'id', order: 'ASC' }}
>
    <SelectInput label="Exclude category" {...setFilterOp('notIn', [])} />
</ReferenceInput>

Screenshot from 2022-05-17 03-04-06

ReferenceArrayInput doesn't even get that far, because react-admin assumes the value is an array (see here and here) and this breaks before it even generates the call to the provider.

<ReferenceArrayInput
    source="storeIds"
    reference="stores"
>
    <SelectArrayInput
        label="Stores"
        {...setFilterOp('contains', [])} // This is an `integer[]` in postgres so "in" operator doesn't exist.
    />
</ReferenceArrayInput>

The latter case is indeed a ReferenceArrayInput, meaning my product table has store_ids integer[]. This is why the default operator (in) doesn't exist for that case. It's a shame RA don't share this type of data to the data provider.

Thank you for testing :). I did not test the library with version 4 yet, so maybe there are some adjustments required. I will have a look into that.