Skip to content
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

Multiple groups of filters for search products query #726

Closed
Izayda opened this issue Feb 23, 2021 · 5 comments
Closed

Multiple groups of filters for search products query #726

Izayda opened this issue Feb 23, 2021 · 5 comments

Comments

@Izayda
Copy link
Contributor

Izayda commented Feb 23, 2021

Is your feature request related to a problem? Please describe.
Allow user to filter by multiple filters. For example, user want to filter some category by color (red (facet_id=1) or white (facet_id=2)) and by material (leather (facet_id=10)).

Now search query allow to input list of facets and logical operator for list https://www.vendure.io/docs/graphql-api/shop/input-types/#searchinput

Describe the solution you'd like
Allowing to pass groups of facets to search query.
For example,: { facets: [ { facets_ids: [1,2], logical_operator: "OR" }, { facets_ids:[10] } ], logical_operator: "AND" }

Describe alternatives you've considered
So, the only way to filter by multiple filters is querying shop-api two times.

  • first time with facet_ids = [1,2] and logical operator= Or
  • second time with facet_ids=[10]
  • merge results at client side

Additional context
Probably it can be implemented by ElasticSearch plugin?

@chladog
Copy link
Contributor

chladog commented Feb 24, 2021

We discussed this on Slack. Here is my piece of code that can be used to extend ListQueryBuilder object (Product or ProductVariant) that does similiar to what you ask for, though without customizable logical operator - it will always look between facetValues of same facet (OR between facetValues of x facet AND OR between facetValues of y facet AND so on...)

qb.andWhere("product"."id" IN ( WITH targets AS (SELECT * FROM facet_value WHERE id IN (:...argProductFacetValues)) SELECT "productId" FROM product_facet_values_facet_value AS map INNER JOIN targets AS tgt ON tgt.id = map."facetValueId" GROUP BY map."productId" HAVING COUNT(DISTINCT tgt."facetId") = (SELECT COUNT(DISTINCT "facetId") FROM targets) ), { argProductFacetValues: options.facetValueIds });

@dccarta
Copy link

dccarta commented Feb 24, 2021

+1 for this. It does seem to make sense to be able to use the OR operator within what I've been calling 'facet value groups', but the AND operator between said groups (although I would say it makes sense, because it's what I'm trying to do with my site 😜 ).

To try and give a real-world example, using place of origin of coffee beans (number in brackets shows products found by search):

FILTERS - NOWT SELECTED
Region

  • Africa (1)
  • South America (3)

Country

  • Ethiopia (1)
  • Colombia (1)
  • Brazil (2)

I'd expect that when a user selects Africa, the facet counts drop to 0 for Colombia and Brazil, thus reflecting the AND condition across groups, but not on South America, as these should still be selectable via the OR condition (also obviously only one product is visible):

FILTERS - AFRICA SELECTED
Region

  • Africa (1) [x]
  • South America (3)

Country

  • Ethiopia (1)
  • Colombia (0)
  • Brazil (0)

If the user then selects South America as well as Africa, I'd expect the facet counts to jump back up on Colombia and Brazil, and the products from South America reappear, due to the OR:

FILTERS - AFRICA AND SOUTH AMERICA SELECTED
Region

  • Africa (1) [x]
  • South America (3) [x]

Country

  • Ethiopia (1)
  • Colombia (1)
  • Brazil (2)

If the user THEN selects Colombia, I'd expect the facet count on Africa to go to 0 and South America to 1 (maybe slightly weird as they're already selected but this is the nature of facets I guess) along with the other countries, due to the AND across groups:

FILTERS - AFRICA AND SOUTH AMERICA SELECTED
Region

  • Africa (0) [x]
  • South America (1) [x]

Country

  • Ethiopia (0)

  • Colombia (1) [x]

  • Brazil (0)

    I hope that this both a) makes sense and b) is at all helpful or even relevant, and if not I shall return to my box.

@Izayda
Copy link
Contributor Author

Izayda commented Mar 29, 2021

Discussed this issue with Michael in Slack, so the plan is:

  1. Implement facetFilters input with logic similar to https://www.algolia.com/doc/api-reference/api-parameters/facetFilters and https://docs.meilisearch.com/reference/features/faceted_search.html#usage
  2. Allow developers to define their own free-form ES queries to be defined via the plugin init config, which can accept any kind of input (also as defined by the dev) and then executes that query against the ES indices

Michael will add later more detailed suggestion of point 2.

@michaelbromley
Copy link
Member

Here's a suggestion of how we can support this (and many other cases) using the Elasticsearch plugin: we allow the developer to define arbitrary ES queries in the plugin config, which can implement any kind of filtering (and other) logic they need. This would enable the developer to fully leverage the power of ES without us needing to explicitly build-in support for every use-case.

Rough plan of implementation:

  1. Add a customQueries option to the ElasticSearchOptions interface of the following type:

    export interface CustomQuery {
      name: string;
      inputType: DocumentNode;
      outputType: DocumentNode;
      buildQuery: (ctx: RequestContext, input: any) => any;
    }

    The input & output types would be GraphQL types defined using the gql` .... ` syntax. This allows the query to accept any arbitrary input via the API.

    example:

    const myCustomQuery: CustomQuery  = {
      name: 'groupedFilters',
      inputType: gql`input GroupedFilterInput { ... }`,
      outputType: gql`type GroupedFilterOutput { ... }`,
      buildQuery: (ctx, input) => {
        return {
          bool: {
            filter: [ ... ]
          },
        };
      },
    };
  2. Via run-time dynamic schema manipulation similar to how we handle custom mappings, we can generate custom queries like this:

    extend type Query {
      customSearchGroupedFilters(input: GroupedFilterInput): GroupedFilterOutput;
    }

This allows the developer to make use of the existing indexing functionality of the ES plugin, without being constrained by the default APIs exposed.

Alternative

The alternative is to implement the filter groups as a completely custom plugin. However, this means the developer need to the re-implement all the indexing logic which is not a trivial task.

@Izayda
Copy link
Contributor Author

Izayda commented Apr 8, 2021

Open the continuation of this issue #815

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants