Apollo-link-rest: [Help wanted] A query with a response where dynamic object keys are

Created on 3 Jul 2020  ·  4Comments  ·  Source: apollographql/apollo-link-rest

I'm requesting a Salesforce schema data. It has a format

res = {
  Id: { name, type, label },
  IsDeleted: { type, name, label },
  MasterRecordId: { type, name, label },
 ...and so on
}

So, fields are dynamic.

In queries.js I'm trying to describe it

export const GET_SALESFORCE_FIELDS = gql`
  query SalesforceFields {
    salesforceFields @rest(endpoint: "schemaservice", type: "SalesforceFields", path: "/fields") {
       // What should be here??
   }
  }
`;
`

How can I describe the dynamic part? I don't have any schema files or resolvers. Only queries.js (for further requests with useQuery) and client.js (where the new ApolloClient is defined)

"@apollo/client": "^3.0.0-rc.10",
"apollo-link-rest": "^0.8.0-beta.0",

question❔

All 4 comments

@ssuvorov -- Have you verified that the network call is being made successfully?

If it is, then based only on your example response: I would do something like:

export const GET_SALESFORCE_FIELDS = gql`
  query SalesforceFields {
    salesforceFields @rest(endpoint: "schemaservice", type: "SalesforceFields", path: "/fields") {
       Id {
         type, name, label
       }
       IsDeleted {
         type, name, label
       }
       # …remaining fields
   }
  }
`;

-- Now, this might not work because GraphQL expects every type to have a name, so instead you might need to change this:

export const GET_SALESFORCE_FIELDS = gql`
  query SalesforceFields {
    salesforceFields @rest(endpoint: "schemaservice", type: "SalesforceFields", path: "/fields") {
-      Id {
+      Id @type(name: "FieldDescriptor") {
         type, name, label
       }
-      IsDeleted {
+      IsDeleted @type(name: "FieldDescriptor") {
         type, name, label
       }
       # …remaining fields
   }
  }
`;

(An alternative way to do this is with a TypePatcher -- documented in the docs)

If every entry is the same object schema, you can even use a Fragment to help you:

export const GET_SALESFORCE_FIELDS = gql`
  query SalesforceFields {
    salesforceFields @rest(endpoint: "schemaservice", type: "SalesforceFields", path: "/fields") {
       Id @type(name: "FieldDescriptor") {
-          type, name, label
+          ...FieldFrag
       }
       IsDeleted @type(name: "FieldDescriptor") {
-          type, name, label
+          ...FieldFrag
       }
       # …remaining fields
   }
+ fragment FieldFrag on FieldDescriptor {
+    type
+    name
+    label
+ }
 }`;

If you're using Salesforce in a generic way, and you don't know how many columns you're going to have, then it gets a bit more complicated, and you need to use the type-patcher to change the shape of the API.

I don't use Salesforce -- but if it were me, I'd consider asking my Salesforce Representative if they have an official GraphQL Offering, or if they have a blessed 3rd party offering to get it natively.

A quick google search showed this exists: https://appexchange.salesforce.com/appxListingDetail?listingId=a0N3A00000G0l6nUAB -- but it only has one review, so I have no idea if it's any good.

@fbartho Thank you so much for your reply. Unfortunately, it's more than 500 fields. Well, I'd try to clarify with the SF team.

If you need to process a response to make that a little easier, you can use a typenamePatcher, that will give you a hook to completely reshape the response.

A better/more generic schema might virtually look like this:

type MyResponse (
  columns: [Column!]!
}
type Column {
  name: String!
  type: String!
  label: String!
}

@fbartho Thanks for your help. This is how I solved it.

// queries.js
export const GET_SALESFORCE_FIELDS = gql`
  query SalesforceFields {
    salesforceFields @rest(endpoint: "schemaservice", type: "SalesforceFieldsPayload", path: "/fields") {
      items @type(name: "Salesforce")
    }
  }
`;

// client.js
const restLink = new RestLink({
  ...,
  typePatcher: {
    SalesforceFieldsPayload: (
      data,
      outerType,
      patchDeeper
    ) => {
      if (data != null) {
        data.items = Object.keys(data).map(field => ({ __typename: "Salesforce", ...data[field] }));
      }
      return data;
    }
  }
});
Was this page helpful?
0 / 5 - 0 ratings