Sitecore / jss

Software development kit for JavaScript developers building web applications with Sitecore Experience Platform
https://jss.sitecore.com
Apache License 2.0
261 stars 272 forks source link

When the layout query stop expanding references, traversefield method crashes #1867

Open minyiediazvelir opened 1 month ago

minyiediazvelir commented 1 month ago

Describe the Bug

When the standard layout query stops expanding references, it returns the guid value of the field which is expected. But the traverse method doesn't handle this case and it crashes. Example error Cannot use 'in' operator to search for 'editable' in {77F1D209-AC8C-4945-A5C8-9FCCC93AFE4F} at traverseField (/opt/build/repo/src/acr/node_modules/@sitecore-jss/sitecore-jss-nextjs/node_modules/@sitecore-jss/sitecore-jss/dist/cjs/layout/content-styles.js:42:20)

To Reproduce

Create a sitecore item that has a reference field to another item, then the other item have a field that reference another item and so on do it 4 times.

Expected Behavior

I would expect to ignore it.

Possible Fix

No response

Provide environment information

yavorsk commented 1 month ago

Thanks @minyiediazvelir for submitting this. I am adding this to our backlog for prioritization.

jflheureux commented 2 weeks ago

We got this issue as well. Sitecore support suggested us to copy the original SDK file and apply the fix to our copy. Then use our copy instead of the SDK version.

Here is our copy of the SDK file src\lib\page-props-factory\content-styles.ts

// CUSTOMIZATION (whole file) - articles with authors erroring in EE because GQL depth exceeded. Implementing Sitecore's workaround customization (Sitecore.Support.JSS-1757)
// Original file URL from the SDK: https://github.com/Sitecore/jss/blob/dev/packages/sitecore-jss/src/layout/content-styles.ts
// Primary difference that makes this work is that traverseField() on line 66 now does an early return if the field is type string.
// This works because the article authors were coming back as a string guid, instead of a full field object.
// Unknown when this issue will be properly fixed. Sitecore said they will consider it for future releases.
import {
  ComponentRendering,
  Field,
  HtmlElementRendering,
  Item,
  LayoutServiceData,
  RouteData,
} from '@sitecore-jss/sitecore-jss/layout';
import { HTMLLink } from '@sitecore-jss/sitecore-jss';

/**
 * Regular expression to check if the content styles are used in the field value
 */
const CLASS_REGEXP = /class=".*(\bck-content\b).*"/g;

type Config = { loadStyles: boolean };

/**
 * Get the content styles link to be loaded from the Sitecore Edge Platform
 * @param {LayoutServiceData} layoutData Layout service data
 * @param {string} sitecoreEdgeContextId Sitecore Edge Context ID
 * @param {string} [sitecoreEdgeUrl] Sitecore Edge Platform URL. Default is https://edge-platform.sitecorecloud.io
 * @returns {HTMLLink | null} content styles link, null if no styles are used in layout
 */
export const getContentStylesheetLink = (
  layoutData: LayoutServiceData,
  sitecoreEdgeContextId: string,
  sitecoreEdgeUrl = 'https://edge-platform.sitecorecloud.io'
): HTMLLink | null => {
  if (!layoutData.sitecore.route) return null;

  const config: Config = { loadStyles: false };

  traverseComponent(layoutData.sitecore.route, config);

  if (!config.loadStyles) return null;

  return {
    href: getContentStylesheetUrl(sitecoreEdgeContextId, sitecoreEdgeUrl),
    rel: 'stylesheet',
  };
};

export const getContentStylesheetUrl = (
  sitecoreEdgeContextId: string,
  sitecoreEdgeUrl = 'https://edge-platform.sitecorecloud.io'
): string =>
  `${sitecoreEdgeUrl}/v1/files/pages/styles/content-styles.css?sitecoreContextId=${sitecoreEdgeContextId}`;

export const traversePlaceholder = (
  components: Array<ComponentRendering | HtmlElementRendering>,
  config: Config
) => {
  if (config.loadStyles) return;

  components.forEach((component) => {
    traverseComponent(component, config);
  });
};

export const traverseField = (field: Field | Item | Item[] | undefined, config: Config) => {
  //Sitecore.Support.JSS-1757
  if (!field || typeof field === 'string' || config.loadStyles) return;

  if ('editable' in field && field.editable) {
    config.loadStyles = CLASS_REGEXP.test(field.editable);
  } else if ('value' in field && typeof field.value === 'string') {
    config.loadStyles = CLASS_REGEXP.test(field.value);
  } else if ('fields' in field) {
    Object.values(field.fields).forEach((field) => {
      traverseField(field, config);
    });
  } else if (Array.isArray(field)) {
    field.forEach((field) => {
      traverseField(field, config);
    });
  }
};

export const traverseComponent = (
  component: RouteData | ComponentRendering | HtmlElementRendering,
  config: Config
) => {
  if (config.loadStyles) return;

  if ('fields' in component && component.fields) {
    Object.values(component.fields).forEach((field) => {
      traverseField(field, config);
    });
  }

  const placeholders = (component as ComponentRendering).placeholders || {};

  Object.keys(placeholders).forEach((placeholder) => {
    traversePlaceholder(placeholders[placeholder], config);
  });
};

And our usage of it in src\lib\page-props-factory\plugins\content-styles.ts:

import { SitecorePageProps } from 'lib/page-props';

// BEGIN CUSTOMIZATION - articles with authors erroring in EE because GQL depth exceeded. Implementing Sitecore's workaround customization (Sitecore.Support.JSS-1757)
// import { getContentStylesheetLink } from '@sitecore-jss/sitecore-jss-nextjs';
import { getContentStylesheetLink } from '../content-styles';
import config from 'temp/config';
// END CUSTOMIZATION
import { Plugin } from '..';

class ContentStylesPlugin implements Plugin {
  order = 2;

  async exec(props: SitecorePageProps) {
    // Get content stylessheet link, empty if styles are not used on the page
    // BEGIN CUSTOMIZATION - articles with authors erroring in EE because GQL depth exceeded. Implementing Sitecore's workaround customization (Sitecore.Support.JSS-1757)
    const contentStyles = getContentStylesheetLink(
      props.layoutData,
      config.sitecoreEdgeContextId,
      config.sitecoreEdgeUrl
    );
    // END CUSTOMIZATION

    contentStyles && props.headLinks.push(contentStyles);

    return props;
  }
}

export const contentStylesPlugin = new ContentStylesPlugin();