diff --git a/packages/ra-data-graphql-simple/README.md b/packages/ra-data-graphql-simple/README.md index 0b40a0ae2d8..d4e44270b6b 100644 --- a/packages/ra-data-graphql-simple/README.md +++ b/packages/ra-data-graphql-simple/README.md @@ -209,6 +209,37 @@ Pass the introspection options to the `buildApolloProvider` function: buildApolloProvider({ introspection: introspectionOptions }); ``` +## Sparse Field Support for Queries and Mutations + +By default, for every API call this data provider returns all top level fields in your GraphQL schema as well as association objects containing the association's ID. If you would like to implement sparse field support for your requests, you can request the specific fields you want in a request by passing them to the dataProvider via the available [meta param](https://marmelab.com/react-admin/Actions.html#meta-parameter). For example, + +```js +dataProvider.getOne( + 'posts', + { + id, + meta: { + sparseFields: [ + 'id', + 'title', + { + comments: [ + 'description', + { + author : [ + 'name', + 'email' + ] + } + ] + } + ] + } + }, +); +``` +This can increase efficiency, optimize client performance, improve security and reduce over-fetching. Also, it allows for the request of nested association fields beyond just their ID. It is available for all dataprovider actions. + ## `DELETE_MANY` and `UPDATE_MANY` Optimizations Your GraphQL backend may not allow multiple deletions or updates in a single query. This provider simply makes multiple requests to handle those. This is obviously not ideal but can be alleviated by supplying your own `ApolloClient` which could use the [apollo-link-batch-http](https://www.apollographql.com/docs/link/links/batch-http.html) link if your GraphQL backend support query batching. diff --git a/packages/ra-data-graphql-simple/src/buildGqlQuery.test.ts b/packages/ra-data-graphql-simple/src/buildGqlQuery.test.ts index d37fdff5d14..fcfa74eb81a 100644 --- a/packages/ra-data-graphql-simple/src/buildGqlQuery.test.ts +++ b/packages/ra-data-graphql-simple/src/buildGqlQuery.test.ts @@ -1,4 +1,5 @@ import { TypeKind, print } from 'graphql'; +import { gql } from '@apollo/client'; import { GET_LIST, GET_ONE, @@ -130,10 +131,138 @@ describe('buildApolloArgs', () => { }); }); +function buildGQLParamsWithSparseFieldsFactory() { + const introspectionResults = { + resources: [ + { + type: { + name: 'resourceType', + fields: [ + { + name: 'id', + type: { kind: TypeKind.SCALAR, name: 'ID' }, + }, + { + name: 'name', + type: { kind: TypeKind.SCALAR, name: 'String' }, + }, + { + name: 'foo', + type: { kind: TypeKind.SCALAR, name: 'String' }, + }, + ], + }, + }, + ], + types: [ + { + name: 'linkedType', + fields: [ + { + name: 'id', + type: { kind: TypeKind.SCALAR, name: 'ID' }, + }, + { + name: 'title', + type: { kind: TypeKind.SCALAR, name: 'String' }, + }, + { + name: 'nestedLink', + type: { + kind: TypeKind.OBJECT, + name: 'nestedLinkedType', + }, + }, + ], + }, + { + name: 'nestedLinkedType', + fields: [ + { + name: 'id', + type: { kind: TypeKind.SCALAR, name: 'ID' }, + }, + { + name: 'bar', + type: { kind: TypeKind.SCALAR, name: 'String' }, + }, + ], + }, + ], + }; + + const resource = { + type: { + fields: [ + { type: { kind: TypeKind.SCALAR, name: 'ID' }, name: 'id' }, + { + type: { kind: TypeKind.SCALAR, name: 'String' }, + name: 'address', + }, + { + type: { kind: TypeKind.SCALAR, name: '_internalField' }, + name: 'foo1', + }, + { + type: { kind: TypeKind.OBJECT, name: 'linkedType' }, + name: 'linked', + }, + { + type: { kind: TypeKind.OBJECT, name: 'resourceType' }, + name: 'resource', + }, + ], + }, + }; + + const queryType = { + name: 'allCommand', + args: [ + { + name: 'foo', + type: { + kind: TypeKind.NON_NULL, + ofType: { kind: TypeKind.SCALAR, name: 'Int' }, + }, + }, + ], + }; + + const params = { + foo: 'foo_value', + meta: { + sparseFields: [ + 'address', + { linked: ['title'] }, + { resource: ['foo', 'name'] }, + ], + }, + }; + + return { + introspectionResults, + queryType, + params, + resource, + }; +} + describe('buildFields', () => { it('returns an object with the fields to retrieve', () => { const introspectionResults = { - resources: [{ type: { name: 'resourceType' } }], + resources: [ + { + type: { + name: 'resourceType', + fields: [ + { + name: 'id', + type: { kind: TypeKind.SCALAR, name: 'ID' }, + }, + ], + }, + }, + ], types: [ { name: 'linkedType', @@ -173,12 +302,86 @@ describe('buildFields', () => { }`, ]); }); + + describe('with sparse fields', () => { + it('returns an object with the fields to retrieve', () => { + const { + introspectionResults, + resource, + params, + } = buildGQLParamsWithSparseFieldsFactory(); + + // nested sparse params + params.meta.sparseFields[1].linked.push({ nestedLink: ['bar'] }); + + expect( + print( + buildFields(introspectionResults)( + resource.type.fields, + params.meta.sparseFields + ) + ) + ).toEqual([ + 'address', + `linked { + title + nestedLink { + bar + } +}`, + `resource { + foo + name +}`, + ]); + }); + + it('throws an error when sparse fields is requested but empty', () => { + const { + introspectionResults, + resource, + } = buildGQLParamsWithSparseFieldsFactory(); + + expect(() => + buildFields(introspectionResults)(resource.type.fields, []) + ).toThrowError( + "Empty sparse fields. Specify at least one field or remove the 'sparseFields' param" + ); + }); + + it('throws an error when requested sparse fields are not available', () => { + const { + introspectionResults, + resource, + } = buildGQLParamsWithSparseFieldsFactory(); + + expect(() => + buildFields(introspectionResults)(resource.type.fields, [ + 'unavailbleField', + ]) + ).toThrowError( + "Requested sparse fields not found. Ensure sparse fields are available in the resource's type" + ); + }); + }); }); describe('buildFieldsWithCircularDependency', () => { it('returns an object with the fields to retrieve', () => { const introspectionResults = { - resources: [{ type: { name: 'resourceType' } }], + resources: [ + { + type: { + name: 'resourceType', + fields: [ + { + name: 'id', + type: { kind: TypeKind.SCALAR, name: 'ID' }, + }, + ], + }, + }, + ], types: [ { name: 'linkedType', @@ -227,7 +430,19 @@ describe('buildFieldsWithCircularDependency', () => { describe('buildFieldsWithSameType', () => { it('returns an object with the fields to retrieve', () => { const introspectionResults = { - resources: [{ type: { name: 'resourceType' } }], + resources: [ + { + type: { + name: 'resourceType', + fields: [ + { + name: 'id', + type: { kind: TypeKind.SCALAR, name: 'ID' }, + }, + ], + }, + }, + ], types: [ { name: 'linkedType', @@ -278,7 +493,19 @@ describe('buildFieldsWithSameType', () => { describe('buildGqlQuery', () => { const introspectionResults = { - resources: [{ type: { name: 'resourceType' } }], + resources: [ + { + type: { + name: 'resourceType', + fields: [ + { + name: 'id', + type: { kind: TypeKind.SCALAR, name: 'ID' }, + }, + ], + }, + }, + ], types: [ { name: 'linkedType', @@ -332,188 +559,464 @@ describe('buildGqlQuery', () => { }; const params = { foo: 'foo_value' }; - it('returns the correct query for GET_LIST', () => { - expect( - print( - buildGqlQuery(introspectionResults)( - resource, - GET_LIST, - queryType, - params + describe('GET_LIST', () => { + it('returns the correct query', () => { + expect( + print( + buildGqlQuery(introspectionResults)( + resource, + GET_LIST, + queryType, + params + ) ) - ) - ).toEqual( - `query allCommand($foo: Int!) { - items: allCommand(foo: $foo) { - foo - linked { - foo - } - resource { - id - } - } - total: _allCommandMeta(foo: $foo) { - count - } -} -` - ); + ).toEqual( + print(gql` + query allCommand($foo: Int!) { + items: allCommand(foo: $foo) { + foo + linked { + foo + } + resource { + id + } + } + total: _allCommandMeta(foo: $foo) { + count + } + } + `) + ); + }); + + it('returns the correct query with sparse fields', () => { + const { + introspectionResults, + params, + queryType, + resource, + } = buildGQLParamsWithSparseFieldsFactory(); + + expect( + print( + buildGqlQuery(introspectionResults)( + resource, + GET_LIST, + queryType, + params + ) + ) + ).toEqual( + print(gql` + query allCommand($foo: Int!) { + items: allCommand(foo: $foo) { + address + linked { + title + } + resource { + foo + name + } + } + total: _allCommandMeta(foo: $foo) { + count + } + } + `) + ); + }); }); - it('returns the correct query for GET_MANY', () => { - expect( - print( - buildGqlQuery(introspectionResults)( - resource, - GET_MANY, - queryType, - params + describe('GET_MANY', () => { + it('returns the correct query', () => { + expect( + print( + buildGqlQuery(introspectionResults)( + resource, + GET_MANY, + queryType, + params + ) ) - ) - ).toEqual( - `query allCommand($foo: Int!) { - items: allCommand(foo: $foo) { - foo - linked { - foo - } - resource { - id - } - } - total: _allCommandMeta(foo: $foo) { - count - } -} -` - ); + ).toEqual( + print(gql` + query allCommand($foo: Int!) { + items: allCommand(foo: $foo) { + foo + linked { + foo + } + resource { + id + } + } + total: _allCommandMeta(foo: $foo) { + count + } + } + `) + ); + }); + + it('returns the correct query with sparse fields', () => { + const { + introspectionResults, + params, + queryType, + resource, + } = buildGQLParamsWithSparseFieldsFactory(); + + expect( + print( + buildGqlQuery(introspectionResults)( + resource, + GET_MANY, + queryType, + params + ) + ) + ).toEqual( + print(gql` + query allCommand($foo: Int!) { + items: allCommand(foo: $foo) { + address + linked { + title + } + resource { + foo + name + } + } + total: _allCommandMeta(foo: $foo) { + count + } + } + `) + ); + }); }); - it('returns the correct query for GET_MANY_REFERENCE', () => { - expect( - print( - buildGqlQuery(introspectionResults)( - resource, - GET_MANY_REFERENCE, - queryType, - params + + describe('GET_MANY_REFERENCE', () => { + it('returns the correct query', () => { + expect( + print( + buildGqlQuery(introspectionResults)( + resource, + GET_MANY_REFERENCE, + queryType, + params + ) ) - ) - ).toEqual( - `query allCommand($foo: Int!) { - items: allCommand(foo: $foo) { - foo - linked { - foo - } - resource { - id - } - } - total: _allCommandMeta(foo: $foo) { - count - } -} -` - ); + ).toEqual( + print(gql` + query allCommand($foo: Int!) { + items: allCommand(foo: $foo) { + foo + linked { + foo + } + resource { + id + } + } + total: _allCommandMeta(foo: $foo) { + count + } + } + `) + ); + }); + + it('returns the correct query with sparse fields', () => { + const { + introspectionResults, + params, + queryType, + resource, + } = buildGQLParamsWithSparseFieldsFactory(); + + expect( + print( + buildGqlQuery(introspectionResults)( + resource, + GET_MANY_REFERENCE, + queryType, + params + ) + ) + ).toEqual( + print(gql` + query allCommand($foo: Int!) { + items: allCommand(foo: $foo) { + address + linked { + title + } + resource { + foo + name + } + } + total: _allCommandMeta(foo: $foo) { + count + } + } + `) + ); + }); }); - it('returns the correct query for GET_ONE', () => { - expect( - print( - buildGqlQuery(introspectionResults)( - resource, - GET_ONE, - { ...queryType, name: 'getCommand' }, - params + describe('GET_ONE', () => { + it('returns the correct query', () => { + expect( + print( + buildGqlQuery(introspectionResults)( + resource, + GET_ONE, + { ...queryType, name: 'getCommand' }, + params + ) ) - ) - ).toEqual( - `query getCommand($foo: Int!) { - data: getCommand(foo: $foo) { - foo - linked { - foo - } - resource { - id - } - } -} -` - ); + ).toEqual( + print(gql` + query getCommand($foo: Int!) { + data: getCommand(foo: $foo) { + foo + linked { + foo + } + resource { + id + } + } + } + `) + ); + }); + + it('returns the correct query with sparse fields', () => { + const { + introspectionResults, + params, + queryType, + resource, + } = buildGQLParamsWithSparseFieldsFactory(); + + expect( + print( + buildGqlQuery(introspectionResults)( + resource, + GET_ONE, + { ...queryType, name: 'getCommand' }, + params + ) + ) + ).toEqual( + print(gql` + query getCommand($foo: Int!) { + data: getCommand(foo: $foo) { + address + linked { + title + } + resource { + foo + name + } + } + } + `) + ); + }); }); - it('returns the correct query for UPDATE', () => { - expect( - print( - buildGqlQuery(introspectionResults)( - resource, - UPDATE, - { ...queryType, name: 'updateCommand' }, - params + describe('UPDATE', () => { + it('returns the correct query', () => { + expect( + print( + buildGqlQuery(introspectionResults)( + resource, + UPDATE, + { ...queryType, name: 'updateCommand' }, + params + ) ) - ) - ).toEqual( - `mutation updateCommand($foo: Int!) { - data: updateCommand(foo: $foo) { - foo - linked { - foo - } - resource { - id - } - } -} -` - ); + ).toEqual( + print(gql` + mutation updateCommand($foo: Int!) { + data: updateCommand(foo: $foo) { + foo + linked { + foo + } + resource { + id + } + } + } + `) + ); + }); + + it('returns the correct query with sparse fields', () => { + const { + introspectionResults, + params, + queryType, + resource, + } = buildGQLParamsWithSparseFieldsFactory(); + + expect( + print( + buildGqlQuery(introspectionResults)( + resource, + UPDATE, + { ...queryType, name: 'updateCommand' }, + params + ) + ) + ).toEqual( + print(gql` + mutation updateCommand($foo: Int!) { + data: updateCommand(foo: $foo) { + address + linked { + title + } + resource { + foo + name + } + } + } + `) + ); + }); }); - it('returns the correct query for CREATE', () => { - expect( - print( - buildGqlQuery(introspectionResults)( - resource, - CREATE, - { ...queryType, name: 'createCommand' }, - params + describe('CREATE', () => { + it('returns the correct query', () => { + expect( + print( + buildGqlQuery(introspectionResults)( + resource, + CREATE, + { ...queryType, name: 'createCommand' }, + params + ) ) - ) - ).toEqual( - `mutation createCommand($foo: Int!) { - data: createCommand(foo: $foo) { - foo - linked { - foo - } - resource { - id - } - } -} -` - ); + ).toEqual( + print(gql` + mutation createCommand($foo: Int!) { + data: createCommand(foo: $foo) { + foo + linked { + foo + } + resource { + id + } + } + } + `) + ); + }); + + it('returns the correct query with sparse fields', () => { + const { + introspectionResults, + params, + queryType, + resource, + } = buildGQLParamsWithSparseFieldsFactory(); + + expect( + print( + buildGqlQuery(introspectionResults)( + resource, + CREATE, + { ...queryType, name: 'createCommand' }, + params + ) + ) + ).toEqual( + print(gql` + mutation createCommand($foo: Int!) { + data: createCommand(foo: $foo) { + address + linked { + title + } + resource { + foo + name + } + } + } + `) + ); + }); }); - it('returns the correct query for DELETE', () => { - expect( - print( - buildGqlQuery(introspectionResults)( - resource, - DELETE, - { ...queryType, name: 'deleteCommand' }, - params + describe('DELETE', () => { + it('returns the correct query', () => { + expect( + print( + buildGqlQuery(introspectionResults)( + resource, + DELETE, + { ...queryType, name: 'deleteCommand' }, + params + ) ) - ) - ).toEqual( - `mutation deleteCommand($foo: Int!) { - data: deleteCommand(foo: $foo) { - foo - linked { - foo - } - resource { - id - } - } -} -` - ); + ).toEqual( + print(gql` + mutation deleteCommand($foo: Int!) { + data: deleteCommand(foo: $foo) { + foo + linked { + foo + } + resource { + id + } + } + } + `) + ); + }); + + it('returns the correct query with sparse fields', () => { + const { + introspectionResults, + params, + queryType, + resource, + } = buildGQLParamsWithSparseFieldsFactory(); + + expect( + print( + buildGqlQuery(introspectionResults)( + resource, + DELETE, + { ...queryType, name: 'deleteCommand' }, + params + ) + ) + ).toEqual( + print(gql` + mutation deleteCommand($foo: Int!) { + data: deleteCommand(foo: $foo) { + address + linked { + title + } + resource { + foo + name + } + } + } + `) + ); + }); }); }); diff --git a/packages/ra-data-graphql-simple/src/buildGqlQuery.ts b/packages/ra-data-graphql-simple/src/buildGqlQuery.ts index 59190f175b1..d9b71a89c8e 100644 --- a/packages/ra-data-graphql-simple/src/buildGqlQuery.ts +++ b/packages/ra-data-graphql-simple/src/buildGqlQuery.ts @@ -21,17 +21,86 @@ import getFinalType from './getFinalType'; import isList from './isList'; import isRequired from './isRequired'; +type SparseField = string | { [k: string]: SparseField[] }; +type ExpandedSparseField = { linkedType?: string; fields: SparseField[] }; +type ProcessedFields = { + resourceFields: IntrospectionField[]; + linkedSparseFields: ExpandedSparseField[]; +}; + +function processSparseFields( + resourceFields: readonly IntrospectionField[], + sparseFields: SparseField[] +): ProcessedFields & { resourceFields: readonly IntrospectionField[] } { + if (!sparseFields || sparseFields.length === 0) + throw new Error( + "Empty sparse fields. Specify at least one field or remove the 'sparseFields' param" + ); + + const permittedSparseFields: ProcessedFields = sparseFields.reduce( + (permitted: ProcessedFields, sparseField: SparseField) => { + let expandedSparseField: ExpandedSparseField; + if (typeof sparseField == 'string') + expandedSparseField = { fields: [sparseField] }; + else { + const [linkedType, linkedSparseFields] = Object.entries( + sparseField + )[0]; + expandedSparseField = { + linkedType, + fields: linkedSparseFields, + }; + } + + const availableField = resourceFields.find( + resourceField => + resourceField.name === + (expandedSparseField.linkedType || + expandedSparseField.fields[0]) + ); + + if (availableField && expandedSparseField.linkedType) { + permitted.linkedSparseFields.push(expandedSparseField); + permitted.resourceFields.push(availableField); + } else if (availableField) + permitted.resourceFields.push(availableField); + + return permitted; + }, + { resourceFields: [], linkedSparseFields: [] } + ); // ensure the requested fields are available + + if ( + permittedSparseFields.resourceFields.length === 0 && + permittedSparseFields.linkedSparseFields.length === 0 + ) + throw new Error( + "Requested sparse fields not found. Ensure sparse fields are available in the resource's type" + ); + + return permittedSparseFields; +} + export default (introspectionResults: IntrospectionResult) => ( resource: IntrospectedResource, raFetchMethod: string, queryType: IntrospectionField, variables: any ) => { - const { sortField, sortOrder, ...metaVariables } = variables; + let { sortField, sortOrder, ...metaVariables } = variables; + const apolloArgs = buildApolloArgs(queryType, variables); const args = buildArgs(queryType, variables); + + const sparseFields = metaVariables.meta?.sparseFields; + if (sparseFields) delete metaVariables.meta.sparseFields; + const metaArgs = buildArgs(queryType, metaVariables); - const fields = buildFields(introspectionResults)(resource.type.fields); + + const fields = buildFields(introspectionResults)( + resource.type.fields, + sparseFields + ); if ( raFetchMethod === GET_LIST || @@ -105,8 +174,12 @@ export default (introspectionResults: IntrospectionResult) => ( export const buildFields = ( introspectionResults: IntrospectionResult, paths = [] -) => fields => - fields.reduce((acc, field) => { +) => (fields: readonly IntrospectionField[], sparseFields?: SparseField[]) => { + const { resourceFields, linkedSparseFields } = sparseFields + ? processSparseFields(fields, sparseFields) + : { resourceFields: fields, linkedSparseFields: [] }; + + return resourceFields.reduce((acc, field) => { const type = getFinalType(field.type); if (type.name.startsWith('_')) { @@ -122,6 +195,15 @@ export const buildFields = ( ); if (linkedResource) { + const linkedResourceSparseFields = linkedSparseFields.find( + lSP => lSP.linkedType === field.name + )?.fields || ['id']; // default to id if no sparse fields specified for linked resource + + const linkedResourceFields = buildFields(introspectionResults)( + linkedResource.type.fields, + linkedResourceSparseFields + ); + return [ ...acc, gqlTypes.field( @@ -129,7 +211,7 @@ export const buildFields = ( null, null, null, - gqlTypes.selectionSet([gqlTypes.field(gqlTypes.name('id'))]) + gqlTypes.selectionSet(linkedResourceFields) ), ]; } @@ -141,6 +223,7 @@ export const buildFields = ( if (linkedType && !paths.includes(linkedType.name)) { const possibleTypes = (linkedType as IntrospectionUnionType).possibleTypes || []; + return [ ...acc, gqlTypes.field( @@ -153,7 +236,12 @@ export const buildFields = ( ...buildFields(introspectionResults, [ ...paths, linkedType.name, - ])((linkedType as IntrospectionObjectType).fields), + ])( + (linkedType as IntrospectionObjectType).fields, + linkedSparseFields.find( + lSP => lSP.linkedType === field.name + )?.fields + ), ]) ), ]; @@ -163,6 +251,7 @@ export const buildFields = ( // ending with endless circular dependencies return acc; }, []); +}; export const buildFragments = (introspectionResults: IntrospectionResult) => ( possibleTypes: readonly IntrospectionNamedTypeRef[] diff --git a/packages/ra-data-graphql-simple/src/buildVariables.test.ts b/packages/ra-data-graphql-simple/src/buildVariables.test.ts index 57b9ce3e5ea..0c2f6c7d1be 100644 --- a/packages/ra-data-graphql-simple/src/buildVariables.test.ts +++ b/packages/ra-data-graphql-simple/src/buildVariables.test.ts @@ -50,6 +50,25 @@ describe('buildVariables', () => { sortOrder: 'DESC', }); }); + + it('should return correct meta', () => { + const params = { + filter: {}, + meta: { sparseFields: [] }, + }; + + expect( + buildVariables(introspectionResult)( + { type: { name: 'Post', fields: [] } }, + GET_LIST, + params, + {} + ) + ).toEqual({ + filter: {}, + meta: { sparseFields: [] }, + }); + }); }); describe('CREATE', () => { @@ -78,6 +97,27 @@ describe('buildVariables', () => { title: 'Foo', }); }); + it('should return correct meta', () => { + const params = { + data: { + meta: { sparseFields: [] }, + }, + }; + const queryType = { + args: [], + }; + + expect( + buildVariables(introspectionResult)( + { type: { name: 'Post' } }, + CREATE, + params, + queryType + ) + ).toEqual({ + meta: { sparseFields: [] }, + }); + }); }); describe('UPDATE', () => { @@ -108,6 +148,28 @@ describe('buildVariables', () => { title: 'Foo', }); }); + + it('should return correct meta', () => { + const params = { + data: { + meta: { sparseFields: [] }, + }, + }; + const queryType = { + args: [], + }; + + expect( + buildVariables(introspectionResult)( + { type: { name: 'Post' } }, + UPDATE, + params, + queryType + ) + ).toEqual({ + meta: { sparseFields: [] }, + }); + }); }); describe('GET_MANY', () => { @@ -127,6 +189,24 @@ describe('buildVariables', () => { filter: { ids: ['tag1', 'tag2'] }, }); }); + + it('should return correct meta', () => { + const params = { + meta: { sparseFields: [] }, + }; + + expect( + buildVariables(introspectionResult)( + { type: { name: 'Post' } }, + GET_MANY, + params, + {} + ) + ).toEqual({ + filter: {}, + meta: { sparseFields: [] }, + }); + }); }); describe('GET_MANY_REFERENCE', () => { @@ -153,6 +233,24 @@ describe('buildVariables', () => { sortOrder: 'ASC', }); }); + + it('should return correct meta', () => { + const params = { + meta: { sparseFields: [] }, + }; + + expect( + buildVariables(introspectionResult)( + { type: { name: 'Post' } }, + GET_MANY_REFERENCE, + params, + {} + ) + ).toEqual({ + filter: {}, + meta: { sparseFields: [] }, + }); + }); }); describe('DELETE', () => { @@ -171,5 +269,21 @@ describe('buildVariables', () => { id: 'post1', }); }); + + it('should return correct meta', () => { + const params = { + meta: { sparseFields: [] }, + }; + expect( + buildVariables(introspectionResult)( + { type: { name: 'Post', inputFields: [] } }, + DELETE, + params, + {} + ) + ).toEqual({ + meta: { sparseFields: [] }, + }); + }); }); }); diff --git a/packages/ra-data-graphql-simple/src/buildVariables.ts b/packages/ra-data-graphql-simple/src/buildVariables.ts index ef6abaa8ea7..653dd95a284 100644 --- a/packages/ra-data-graphql-simple/src/buildVariables.ts +++ b/packages/ra-data-graphql-simple/src/buildVariables.ts @@ -43,6 +43,7 @@ export default (introspectionResults: IntrospectionResult) => ( case GET_MANY: return { filter: { ids: preparedParams.ids }, + ...(preparedParams.meta ? { meta: preparedParams.meta } : {}), }; case GET_MANY_REFERENCE: { let variables = buildGetListVariables(introspectionResults)( @@ -62,6 +63,7 @@ export default (introspectionResults: IntrospectionResult) => ( case DELETE: return { id: preparedParams.id, + ...(preparedParams.meta ? { meta: preparedParams.meta } : {}), }; case CREATE: case UPDATE: { @@ -188,6 +190,7 @@ const buildGetListVariables = (introspectionResults: IntrospectionResult) => ( perPage: number; sortField: string; sortOrder: string; + meta?: object; }> = { filter: {} }; if (params.filter) { variables.filter = Object.keys(params.filter).reduce((acc, key) => { @@ -285,6 +288,8 @@ const buildGetListVariables = (introspectionResults: IntrospectionResult) => ( variables.sortOrder = params.sort.order; } + if (params.meta) variables = { ...variables, meta: params.meta }; + return variables; };