contentful.js: GraphQL. DRAFT references cause errors on nullable fields

Expected Behavior

DRAFT as Reference should be ignored by graphQL on nullable fields.

Actual Behavior

Adding a DRAFT as Reference causes an error and prevents loading data.

Using: useQuery via @apollo/client or graphiql via https://graphql.contentful.com/content/v1/spaces/{SPACE}

"errors": [
    {
      "message": "Query execution error. Link to entry '{ENTRY_ID}' on field '{FIELD_NAME}' within type '{CONTENT_TYPE}' cannot be resolved",
      "extensions": {
        "contentful": {
          "code": "UNRESOLVABLE_LINK",
          "requestId": "{REQUEST_ID}",
          "details": {
            "type": "{CONTENT_TYPE}",
            "field": "{FIELD_NAME}",
            "linkType": "Entry",
            "linkId": "{ENTRY_ID}"
          }
        }
      },
      "locations": [
        {
          "line": 156,
          "column": 9
        }
      ],
      "path": [
        "{COLLECTION_NAME}",
        "items",
        0,
        "{FIELD_NAME}"
      ]
    }
  ]

Partial data exists on graphiql via https://graphql.contentful.com/content/v1/spaces/{SPACE} but it won’t be returned by @apollo/client.

contentful_error_graphql

Possible Solution

Removing the DRAFT works but is (probably) not the intended behavior

Steps to Reproduce

  1. Add a DRAFT as Reference
  2. Query the contentType via https://graphql.contentful.com/content/v1/spaces/{SPACE}
  3. See error

Context

When changing a published entry to DRAFT the page cannot be loaded. This can cause unexpected behavior and should be considered a showstopper.

Partial data exists on graphiql via https://graphql.contentful.com/content/v1/spaces/{SPACE} but it won’t be returned by @apollo/client.

If this is considered an @apollo/client issue please mention it in a comment so I can create an issue and link it. Maybe @apollo/client does not return data when an error is present. The error is unnecessary and IMHO a bug.

Environment

  • Language Version: v12.15.0
  • Package Manager Version: 6.13.4
  • Operating System: 19.5.0 Darwin Kernel Version 19.5.0: Tue May 26 20:41:44 PDT 2020; root:xnu-6153.121.2~2/RELEASE_X86_64 x86_64
  • Package Version: n.a.
  • Which API are you using?: GraphQL

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 7
  • Comments: 16

Most upvoted comments

Hey, this issue is causing us some troubles, using GraphQL Api. I know we can filter or ignore the errors, but what about the null entries? For example when we have an entry with linked entries, and some of then are still in draft mode, we get something like this:

[
  { sys: { id: <id> } },
  { sys: { id: <id> } },
  null,
  { sys: { id: <id> } }
]

Which is very annoying and error-prone when we use indexing of the items to track order, for example. And as far as I understood, the option removeUnresolved is not available in the GraphQL Api. Any other way of fixing this?

By default Apollo client will handle any error as a network error. You can change that behaviour so you can work with the results you get back. You can set the error policy to ignore or all more info: https://www.apollographql.com/docs/react/data/error-handling/#error-policies.

I am having the same issue - it’s strange - why the graphql api returns NULL fields in draft items instead of just not returning them? Since we have preview tokens for that purposes it would not make sense to always hide draft items? @pixelass

As @hugomn mentioned a lot of my queries are also returning this data structure:

[
  { sys: { id: <id> } },
  { sys: { id: <id> } },
  null,
  { sys: { id: <id> } }
]

So I am having to do a lot of data.filter(x=>x) as a workaround for this issue.

@hugomn did you find any solution for that?

Thanks.

@harleyharl The nice thing is that you receive a quite extensive GraphQL error from Contentful where it specifies all the JSON paths where there is unresolved content. You can loop through those errors one-by-one and remove them from the data JSON. After you removed them you can also remove the error from the GraphQL response (you sort of ‘fixed’ the error). Yes, I know this is not good practise at all, but this was causing a lot of issues for us when different clients were consuming Contentful data. Downside of such a third-party GraphQL API without all the options in the original SDK.

An example of a utility function that you can put in either your front-end or back-end middleware (BTW: if you are no fan of Lodash, then see this as a reference for you…):

import { filter, get, isArray, isEmpty, isNumber, map, pull, set, slice, unset, cloneDeep } from 'lodash';
import { GraphQLError } from 'graphql';

interface GraphQLResponse {
  errors: GraphQLError[];
  data: any; // type this if needed
}

// this is a piece of the error message to match on that you get back as a Contentful unresolved error.
// adapt this matcher to your own setup depending on how strict it should be (e.g. when you might have other API's with similar errors).
const CONTENT_UNRESOLVED_MATCHER = 'cannot be resolved';

/**
 * Based on GraphQL errors from contentful we can automatically filter out unresolved links
 * across the response and resolve the errors in our own middleware so the client is not dealing with it.
 */
export function filterUnresolved(response: GraphQLResponse) {
  const mutatableResponse = cloneDeep(response);
  const { errors, data } = mutatableResponse;
  const regex = new RegExp(CONTENT_UNRESOLVED_MATCHER);
  const unresolvedErrors = filter(errors, (error) => {
    const { message } = error;
    return regex.test(message);
  });
  const potentialUnresolvedPaths: (string|number)[][] = [];

  // guard: skip filtering when there are no unresolved errors
  if (isEmpty(unresolvedErrors)) {
    return mutatableResponse;
  }

  map(unresolvedErrors, (error) => {
    // cast to non-read only
    const path = (error.path) as (string|number)[];

    // guard: skip invalid paths
    if (!path) {
      return;
    }

    // by default we remove the unresolved content from the path completely
    unset(data, path);

    // add the path to be removed entirely later on
    potentialUnresolvedPaths.push(path);

    // register the parent path when it is an array to remove null entries later
    if (isNumber(path?.[path?.length - 1])) {
      potentialUnresolvedPaths.push(slice(path, 0, path.length - 1));
    }

    // if all is good we will delete the error as well
    pull(errors, error);
  });

  // strip out all the potential nulls that exist while filtering.
  // this is done afterwards, because some paths contain indexes and if you
  // mutate directly all the indexes will shift, causing the wrong data to be removed.
  map(potentialUnresolvedPaths, (path) => {
    const entry = get(data, path);

    if (isArray(entry)) {
      const filteredList = filter(entry, (entryPiece) => {

        // filter out nulls and undefined from a list
        // NOTE: if you expect nullable objects this will mess some things up!
        return entryPiece ?? false;
      });

      set(data, path, filteredList);
      return;
    }
  });

  return mutatableResponse;
}

I haven’t tested the code in complete isolation, as it was integrated our middleware, so let me know if something is still wrong. Hope this helps!

I’m also having this exact problem. I’m not using Apollo as my GraphQL client so I don’t have the option to removeUnresolved. I’m using graphql-request. Filtering out draft content in the front end doesn’t work!

I found this: https://www.apollographql.com/docs/react/api/core/ApolloClient/#example-constructor-call

So it seems it can be globally added.

Closing since this should resolve the issue.

No news on this? So, with graphql, we can’t reference a draft entry… That’s unfortunate

The solution for Apollo client can be to use errorPolicy: "all" and looks working for my case like: const { loading, error, data } = useQuery(GET_BY_ID, { variables: { id: params.id, }, errorPolicy: "all" });

It was actually mentioned here before 😀

I dont exactly understand:

Using the preview access token I should be able to query for drafts but when one of the entries of a referenced field is a draft Apollo throws an error. If I ignore the error (thanks @SergeiZag) the item is null.

  • Shouldnt drafts be resolved with the preview access token?
  • Also setting myCollection(preview: false) should show no drafts, but the error is thrown anyway