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

[Help wanted] A query with a response where dynamic object keys are #258

Closed
ssuvorov opened this issue Jul 3, 2020 · 4 comments
Closed

Comments

@ssuvorov
Copy link

ssuvorov commented Jul 3, 2020

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",

@fbartho
Copy link
Collaborator

fbartho commented Jul 3, 2020

@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.

@ssuvorov
Copy link
Author

ssuvorov commented Jul 3, 2020

@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.

@fbartho
Copy link
Collaborator

fbartho commented Jul 4, 2020

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!
}

@ssuvorov
Copy link
Author

ssuvorov commented Jul 6, 2020

@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;
    }
  }
});

@ssuvorov ssuvorov closed this as completed Jul 6, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants