hasura / ra-data-hasura

react-admin data provider for Hasura GraphQL Engine
MIT License
336 stars 70 forks source link

OR filter can't handle multiple values #153

Open idesignpixels opened 1 year ago

idesignpixels commented 1 year ago

What's happening?

I understand that if i pass a comma separated key it will create an OR filter argument however the same value is applied to each condition for example:

<ListBase
  resource="meeting_invites"
  filter={{
    'rsvp_status#name@_eq,rsvp_status_id@_is_null': [
      'declined',
      true,
    ],
    'user_id@_neq': identity?.id,
    'meeting#start@_gte': 'now()',
  }}
>

both rsvp_status#name@_eq & rsvp_status_id@_is_null both receive ["declined", true]

What's expected?

There should be a way of passing multiple arguments to an OR filter, after looking into the code I see the same value will always be applied.

I think you should be able to pass an array of arguments to match the filter statements.

Boncom99 commented 5 months ago

Are there any news on this?

idesignpixels commented 5 months ago

Are there any news on this?

Nope

Boncom99 commented 5 months ago

I found a workaround. Following the logic of this filterReducer that ra-data-hasura uses to iterate over the filters and rewrite them, we can bypass this rewriting by passing this attribute format:'hasura-raw-query' to our filter. Then ra-data-hasura won't modify this concrete filter's value.

In your particular case:

 filter={{
    'rsvp_status#_or':{
       format:'hasura-raw-query',
       value:[{name:{_eq:'declined'}},{id:{_is_null:true}}]
     }
    // other regular filters
  }}

should solve your problem.

In my particular case, I needed a number input to search by a distance or Null. I created this component:

import { TextField } from '@mui/material';
import { ChangeEvent, useState } from 'react';
import { useListContext } from 'react-admin';

//This filter allows the user to filter the table shop by the distance.
// if the distance is _gte OR if the distance is null
export const DistanceOrNullFilter = ({
  label,
}: {
  alwaysOn: boolean;
  label?: string;
  source: string;
}) => {
  const { filterValues, setFilters } = useListContext();
  const [numberValue, setNumberValue] = useState<number | null>(
    filterValues?.['shop#_or']?.value[0]?.distance?._gte ?? null,
  );

  const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
    const newValue = event.target.value;
    const newFilters = { ...filterValues };
    if (!newValue) {
      setNumberValue(null);
      //remove  from filterValues if newValue is falsy
      delete newFilters['shop#_or'];
      setFilters?.(newFilters, {});
    } else {
      setNumberValue(parseFloat(newValue));
      newFilters['shop#_or'] = {
        //This format:'hasura-raw-query' is used to bypass the ra-data-hasura transforming the filter object.
        format: 'hasura-raw-query',
        value: [{ distance: { _gte: newValue } }, { distance: { _is_null: true } }],
      };
      setFilters?.(newFilters, {});
    }
  };

  return (
    <TextField type="number" value={numberValue ?? ''} onChange={handleChange} label={label} />
  );
};

and then use it in the <List> component

<List
      filter={{
       //fixed filters
      }}
      filters={[  
        <NumberInput label="y from" source="y@_gte" alwaysOn style={{ width: '5rem' }} />,
        <NumberInput label="y to" source="y@_lte" alwaysOn style={{ width: '5rem' }} />,
         <DistanceFilter alwaysOn label="Isolation distance" source="RandomString" />,
       ]}
 >