xmCloud Graphql queries Tips and manage Complexity
Tips for managing the complexity of GraphQL queries in xmCloud
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
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.
Rewrite the query using Search
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