xmCloud Graphql queries Tips and manage Complexity

Tips for managing the complexity of GraphQL queries in xmCloud

xmCloud Graphql queries Tips and manage Complexity

Here I want to share a few Tips about working with integrated graphql queries in xmcloud:

  • How to use it
  • Optimize queries for managing complexity
  • Naming Conventions for renderings and templates

Graphql Queries for Edge

As you may know, the Json Rendering in xmcloud comes with an integrated graphql query. Using that, you can handle many scenarios without writing a single line of code.

To test and create your query, you can use graphql playground ide, which is accessible under this URL:

https://[your instance]/sitecore/api/graph/edge/ide

For the examples I am using the following templates:

Contact Card:

  • Name: Single-line text
  • Email: Single-line text
  • Phone: Single-line text
  • Country: Droplink | query:$site/[@@name='Data']/[@@templatename='CountryFolder']/*

Country:

  • Code: Single-line text
  • Name: Single-line text

NJj2ouafAe
Sr19lfnOqJ

Value vs jsonValue

when you use the value as return type, the component field will not be editable in sitecore pages. To make the fields editable, you need to return the jsonValue. Doing that the chrome-data will be added to the fields

Editable:

    name: field(name: "ContactName") {
      jsonValue
    }

None-Editable:

    name: field(name: "ContactName") {
      value
    }

Item Query

On the first try I want to get all of the contacts from the contacts folder:

query ContactsQuery($datasource: String!, $language: String!) {
  datasource: item(path: $datasource, language: $language) {
    children {
      results {
        name: field(name: "ContactName") {
          jsonValue
        }
        email: field(name: "ContactEmail") {
          jsonValue
        }
        phone: field(name: "ContactPhone") {
          jsonValue
        }
        country: field(name: "ContactCountry") {
          jsonValue
          ... on LookupField {
            targetItem {
              name: field(name: "CountryName") {
                jsonValue
              }
              code: field(name: "CountryCode") {
                jsonValue
              }
            }
          }
        }
      }
    }
  }
}

it kinda works and it returns the first 10 children.
To get the children we need to pass in the first parameter like this:

query ContactsQuery($datasource: String!, $language: String!) {
  datasource: item(path: $datasource, language: $language) {
    children (first: 20) {
      results {
        name: field(name: "ContactName") {
          jsonValue
        }
        email: field(name: "ContactEmail") {
          jsonValue
        }
        phone: field(name: "ContactPhone") {
          jsonValue
        }
        country: field(name: "ContactCountry") {
          jsonValue
          ... on LookupField {
            targetItem {
              name: field(name: "CountryName") {
                jsonValue
              }
              code: field(name: "CountryCode") {
                jsonValue
              }
            }
          }
        }
      }
    }
  }
}

now we get the error:

{
  "errors": [
    {
      "message": "Query is too complex to execute. The field with the highest complexity is: Field{name='field', alias='name', arguments=GraphQL.Language.AST.Arguments, directives=GraphQL.Language.AST.Directives, selectionSet=SelectionSet{selections=Field{name='jsonValue', alias='', arguments=GraphQL.Language.AST.Arguments, directives=GraphQL.Language.AST.Directives, selectionSet=SelectionSet{selections=}}}}",
      "extensions": {
        "code": "INVALID_OPERATION"
      }
    }
  ]
}

Optimize the query using Template references

Let's try to rewrite the same query using the template instead of item query:
You can use ... on [Template Name] and access the field name with camel case format:

query ContactsQuery($datasource: String!, $language: String!) {
  datasource: item(path: $datasource, language: $language) {
    children (first: 200) {
      results {
        ... on ContactCard{          
            contactName {
              jsonValue
            }
            contactEmail {
              jsonValue
            }
            contactPhone {
              jsonValue
            }
            contactCountry {
              targetItem{
                ... on Country{
                  countryCode{
                    jsonValue
                  }
                  countryName{
                    jsonValue
                  }
                }
              }
            }
          
        }
      }
    }
  }
}

Now you will get the result with much less complexity and you can go up for example to get the first 200 results.

Another way is to write the Search Query.

Note: At the time of this writing, the search queries will not be run in sitecore pages edit mode, but they are available in preview mode. That means you will lose editing capabilities.

query ContactsQuerySearch($datasource: String!, $language: String!) {
  container: search(
    where: {
      AND: [
        {
          name: "_templates"
          value: "{68253D4D-20BB-4424-9A18-60AA0D0FD58A}"
          operator: CONTAINS
        }
        { name: "_language", value: $language }
        { name: "_path", value: $datasource, operator: CONTAINS }
      ]
    }
    first: 50
    #orderBy: { name: "_path", direction: ASC }
  ) {
    total
    pageInfo {
      endCursor
      hasNext
    }
    results {
      ... on ContactCard {
        name:contactName {
          jsonValue
        }
        email:contactEmail {
          jsonValue
        }
        phone:contactPhone {
          jsonValue
        }
        country:contactCountry {
          targetItem {
              code:field(name:"CountryCode") {
                jsonValue
              }
              name:field(name:"countryName") {
                jsonValue
              }        
          }
        }
      }
    }
  }
}

In the result you will get the endCursor parameter which allows you to access the next page of the results:

{
  "data": {
    "container": {
      "total": 76,
      "pageInfo": {
        "endCursor": "NzA=",
        "hasNext": true
      },
      "results": [
        {
          "name": {
            "jsonValue": {
              "value": "Mike"
            }
          },
          "email": {
            "jsonValue": {
              "value": "Mike@gmail.com"
            }
          },
          "phone": {
            "jsonValue": {
              "value": "+491111111111111111"
            }
          },
          ...

Keep in mind the cons of using search queries:

  • In sitecore pages editing will not be possible and you will get empty result when you are in edit mode
  • In Preview mode all of the item versions will be returned. On Experience edge only the latest (approved version if you have workflow) will be published, so the preview will not match the vercel.

Other Queries

using the layout query you will get the rendered results of the page:

query LayoutQuery {
  layout(site: "acme", routePath: "/", language: "en") {
    item {
      rendered
    }
  }
}

You can find the built-in queries by checking the sitecore jss source code on github.

SXA Error Pages Query:

query ErrorPagesQuery($siteName: String!, $language: String!) {
  site {
    siteInfo(site: $siteName) {
      errorHandling(language: $language) {
        notFoundPage {
          rendered
        }
        notFoundPagePath
        serverErrorPage {
          rendered
        }
        serverErrorPagePath
      }
    }
  }
}

SXA Redirects Query:

query RedirectsQuery($siteName: String!) {
  site {
    siteInfo(site: $siteName) {
      redirects {
        pattern
        target
        redirectType
        isQueryStringPreserved
        locale
      }
    }
  }
}

SXA Sitemap:

  query SitemapQuery($siteName: String!) {
    site {
      siteInfo(site: $siteName) {
        sitemap
      }
    }
  }

Dictionary Service:

query DictionarySearch(
  $rootItemId: String!
  $language: String!
  $templates: String!
  $pageSize: Int = 10
  $after: String
) {
  search(
    where: {
      AND: [
        { name: "_path", value: $rootItemId, operator: CONTAINS }
        { name: "_language", value: $language }
        { name: "_templates", value: $templates, operator: CONTAINS }
      ]
    }
    first: $pageSize
    after: $after
  ) {
    total
    pageInfo {
      endCursor
      hasNext
    }
    results {
      key: field(name: "Key") {
        value
      }
      phrase: field(name: "Phrase") {
        value
      }
    }
  }
}

Parameters:
{"rootItemId": "Your site item id","language": "en","templates": "{6D1CD897-1936-4A3A-A511-289A94C2A7B1}"}

Naming Conventions

As you saw for optimizing the queries you can use ...on [Template name] to have less complexity.

To be able to use that, you need to pay attention to the naming, for example, if the template and rendering have the same name, then you will end up with something like this:

query ContactsQuery($datasource: String!, $language: String!) {
  datasource: item(path: $datasource, language: $language) {
    children (first: 200) {
      results {
        ... on ContactCard_68253D4D20BB44249A1860AA0D0FD58A{
            contactName {
              jsonValue
            }

to have a cleaner query, at the beginning of the project set some naming conventions.

For example:

  • Templates should not have any spacing in their item name (use display name for readable naming for the customer)
  • Template fields should have perfixed: use ContactName instead of Name
  • Use different naming for rendering:
    • Template name: Contact
    • Rendering Parameter Template: ContactParammeter
    • Rendering: ContactComponent