From b9dbf849150f87e619da23f9a4bef968a45e9a7c Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Wed, 29 May 2024 21:09:56 -0400 Subject: [PATCH] feat: add `filterQueryOverride` to GraphQL Service --- .../graphql/GraphQL-Filtering.md | 21 ++++++++++++++ .../demo/src/examples/slickgrid/example6.ts | 29 +++++++++++++++++-- packages/demo/tsconfig.json | 2 +- test/cypress/e2e/example06.cy.ts | 26 +++++++++++++++++ 4 files changed, 75 insertions(+), 3 deletions(-) diff --git a/docs/backend-services/graphql/GraphQL-Filtering.md b/docs/backend-services/graphql/GraphQL-Filtering.md index 07a6b4900..d358facce 100644 --- a/docs/backend-services/graphql/GraphQL-Filtering.md +++ b/docs/backend-services/graphql/GraphQL-Filtering.md @@ -2,6 +2,7 @@ - [filterBy](#filterby) - [Complex Objects](#complex-objects) - [Extra Query Arguments](#extra-query-arguments) +- [Override the filter query](#override-the-filter-query) ### Introduction The implementation of a GraphQL Service requires a certain structure to follow for `Slickgrid-Universal` to work correctly (it will fail if your GraphQL Schema is any different than what is shown below). @@ -134,3 +135,23 @@ this.gridOptions = { } } ``` + +### Override the filter query + +Column filters may have a `Custom` Operator, that acts as a placeholder for you to define your own logic. To do so, the easiest way is to provide the `filterQueryOverride` callback in the GraphQL Options. This method will be called with `BackendServiceFilterQueryOverrideArgs` to let you decide dynamically on how the filter should be assembled. + +E.g. you could listen for a specific column and the active `OperatorType.custom` in order to switch the filter to an SQL LIKE search in GraphQL: + +> **Note** technically speaking GraphQL isn't a database query language like SQL, it's an application query language. Depending on your configuration, your GraphQL Server might already support regex querying (e.g. Hasura [_regex](https://hasura.io/docs/latest/queries/postgres/filters/text-search-operators/#_regex)) or you could add your own implementation (e.g. see this SO: https://stackoverflow.com/a/37981802/1212166). Just make sure that whatever custom operator that you want to use, is already included in your GraphQL Schema. +```ts +backendServiceApi: { + options: { + filterQueryOverride: ({ fieldName, columnDef, columnFilterOperator, searchValue }) => { + if (columnFilterOperator === OperatorType.custom && columnDef?.id === 'name') { + // the `operator` is a string, make sure to implement this new operator in your GraphQL Schema + return { field: fieldName, operator: 'Like', value: searchValue }; + } + }, + } +} +``` diff --git a/packages/demo/src/examples/slickgrid/example6.ts b/packages/demo/src/examples/slickgrid/example6.ts index a1f58f3c7..43875b224 100644 --- a/packages/demo/src/examples/slickgrid/example6.ts +++ b/packages/demo/src/examples/slickgrid/example6.ts @@ -34,7 +34,10 @@ export class Example6 {
  • The (*) can be used as startsWith (ex.: "abc*" => startsWith "abc") / endsWith (ex.: "*xyz" => endsWith "xyz")
  • The other operators can be used on column type number for example: ">=100" (greater or equal than 100)
  • -
  • You can also preload a grid with certain "presets" like Filters / Sorters / Pagination Wiki - Grid Preset +
  • You can also preload a grid with certain "presets" like Filters / Sorters / Pagination Wiki - Grid Preset
  • +
  • Also note that the column Name has a filter with a custom %% operator that behaves like an SQL LIKE operator supporting % wildcards.
  • +
  • Depending on your configuration, your GraphQL Server might already support regex querying (e.g. Hasura _regex) + or you could add your own implementation (e.g. see this SO Question).
  • `; isWithCursor = false; @@ -77,7 +80,15 @@ export class Example6 { sortable: true, filterable: true, filter: { - model: Filters.compoundInput + model: Filters.compoundInput, + compoundOperatorList: [ + { operator: '', desc: 'Contains' }, + { operator: '<>', desc: 'Not Contains' }, + { operator: '=', desc: 'Equals' }, + { operator: '!=', desc: 'Not equal to' }, + { operator: 'a*', desc: 'Starts With' }, + { operator: 'Custom', desc: 'SQL Like' }, + ], } }, { @@ -137,6 +148,10 @@ export class Example6 { i18n: this.i18n, gridHeight: 200, gridWidth: 900, + compoundOperatorAltTexts: { + // where '=' is any of the `OperatorString` type shown above + text: { 'Custom': { operatorAlt: '%%', descAlt: 'SQL Like' } }, + }, gridMenu: { resizeOnShowHeaderRow: true, }, @@ -180,6 +195,16 @@ export class Example6 { field: 'userId', value: 123 }], + filterQueryOverride: ({ fieldName, columnDef, columnFilterOperator, searchValue }) => { + if (columnFilterOperator === OperatorType.custom && columnDef?.id === 'name') { + // technically speaking GraphQL isn't a database query language like SQL, it's an application query language. + // What that means is that GraphQL won't let you write arbitrary queries out of the box. + // It will only support the types of queries defined in your GraphQL schema. + // see this SO: https://stackoverflow.com/a/37981802/1212166 + return { field: fieldName, operator: 'Like', value: searchValue }; + } + return; + }, useCursor: this.isWithCursor, // sets pagination strategy, if true requires a call to setPageInfo() when graphql call returns // when dealing with complex objects, we want to keep our field name with double quotes // example with gender: query { users (orderBy:[{field:"gender",direction:ASC}]) {} diff --git a/packages/demo/tsconfig.json b/packages/demo/tsconfig.json index e3a8a8f70..cbd66d962 100644 --- a/packages/demo/tsconfig.json +++ b/packages/demo/tsconfig.json @@ -12,7 +12,7 @@ "noEmit": false, "noUnusedLocals": false, "noUnusedParameters": false, - "noImplicitReturns": true, + "noImplicitReturns": false, "resolveJsonModule": true, "skipLibCheck": true, "sourceMap": true, diff --git a/test/cypress/e2e/example06.cy.ts b/test/cypress/e2e/example06.cy.ts index 18585646c..9c447d711 100644 --- a/test/cypress/e2e/example06.cy.ts +++ b/test/cypress/e2e/example06.cy.ts @@ -4,6 +4,9 @@ import { removeWhitespaces } from '../plugins/utilities'; const presetLowestDay = format(addDay(new Date(), -2), 'YYYY-MM-DD'); const presetHighestDay = format(addDay(new Date(), 20), 'YYYY-MM-DD'); +function removeSpaces(textS) { + return `${textS}`.replace(/\s+/g, ''); +} describe('Example 6 - GraphQL Grid', () => { it('should display Example title', () => { @@ -295,6 +298,29 @@ describe('Example 6 - GraphQL Grid', () => { }); }); + it('should perform filterQueryOverride when operator "%%" is selected', () => { + cy.get('.search-filter.filter-name select').find('option').last().then((element) => { + cy.get('.search-filter.filter-name select').select(element.val()); + }); + + cy.get('.search-filter.filter-name') + .find('input') + .clear() + .type('Jo%yn%er'); + + // wait for the query to finish + cy.get('[data-test=status]').should('contain', 'finished'); + + cy.get('[data-test=graphql-query-result]') + .should(($span) => { + const text = removeSpaces($span.text()); // remove all white spaces + expect(text).to.eq(removeSpaces(`query { users (first:30,offset:0, + orderBy:[{field:"name",direction:ASC}], + filterBy:[{field:"name",operator:Like,value:"Jo%yn%er"}], + locale:"en",userId:123) { totalCount, nodes { id,name,gender,company,billing{address{street,zip}},finish } } }`)); + }); + }); + it('should click on Set Dynamic Filter and expect query and filters to be changed', () => { cy.get('[data-test=set-dynamic-filter]') .click();