From bc525aed3976bf9c902e630ab84781dcb0d3de59 Mon Sep 17 00:00:00 2001 From: "Frank.van.Zutphen" Date: Sat, 5 Oct 2024 13:17:59 +0200 Subject: [PATCH 01/13] initial commit --- packages/sitecore-jss/src/constants.ts | 3 + packages/sitecore-jss/src/debug.ts | 1 + .../src/i18n/content-token-service.ts | 72 +++++++ .../src/i18n/graphql-content-token-service.ts | 198 ++++++++++++++++++ 4 files changed, 274 insertions(+) create mode 100644 packages/sitecore-jss/src/i18n/content-token-service.ts create mode 100644 packages/sitecore-jss/src/i18n/graphql-content-token-service.ts diff --git a/packages/sitecore-jss/src/constants.ts b/packages/sitecore-jss/src/constants.ts index 295c116dcf..866efc73d5 100644 --- a/packages/sitecore-jss/src/constants.ts +++ b/packages/sitecore-jss/src/constants.ts @@ -4,6 +4,9 @@ export enum SitecoreTemplateId { // /sitecore/templates/System/Dictionary/Dictionary entry DictionaryEntry = '6d1cd89719364a3aa511289a94c2a7b1', + + // /sitecore/templates/Feature/Experience Accelerator/Content Tokens/Content Token + ContentTokenEntry = '7d659ee9d4874d408a9210c6d68844c8' } export const FETCH_WITH = { diff --git a/packages/sitecore-jss/src/debug.ts b/packages/sitecore-jss/src/debug.ts index 692c342be8..da87446909 100644 --- a/packages/sitecore-jss/src/debug.ts +++ b/packages/sitecore-jss/src/debug.ts @@ -40,4 +40,5 @@ export default { personalize: debug(`${rootNamespace}:personalize`), errorpages: debug(`${rootNamespace}:errorpages`), proxy: debug(`${rootNamespace}:proxy`), + contenttokens: debug(`${rootNamespace}:contenttokens`), }; diff --git a/packages/sitecore-jss/src/i18n/content-token-service.ts b/packages/sitecore-jss/src/i18n/content-token-service.ts new file mode 100644 index 0000000000..458091d37d --- /dev/null +++ b/packages/sitecore-jss/src/i18n/content-token-service.ts @@ -0,0 +1,72 @@ +import { CacheClient, CacheOptions, MemoryCacheClient } from '../cache-client'; + +/** + * Object model for Sitecore dictionary phrases + */ +export interface ContentTokenPhrases { + [k: string]: string; +} + +/** + * Service that fetches dictionary data using Sitecore's GraphQL API. + */ +export interface ContentTokenService { + /** + * Fetch dictionary data for a language. + * @param {string} language the language to be used to fetch the dictionary + */ + fetchContentTokens(language: string): Promise; +} + +/** + * Base implementation of @see ContentTokenService that handles caching dictionary values + */ +export abstract class ContentTokenServiceBase + implements ContentTokenService, CacheClient { + private cache: CacheClient; + + /** + * Initializes a new instance of @see ContentTokenService using the provided @see CacheOptions + * @param {CacheOptions} options Configuration options + */ + constructor(public options: CacheOptions) { + this.cache = this.getCacheClient(); + } + + /** + * Caches a @see ContentTokenPhrases value for the specified cache key. + * @param {string} key The cache key. + * @param {ContentTokenPhrases} value The value to cache. + * @returns The value added to the cache. + * @mixes CacheClient + */ + setCacheValue(key: string, value: ContentTokenPhrases): ContentTokenPhrases { + return this.cache.setCacheValue(key, value); + } + + /** + * Retrieves a @see ContentTokenPhrases value from the cache. + * @param {string} key The cache key. + * @returns The @see ContentTokenPhrases value, or null if the specified key is not found in the cache. + */ + getCacheValue(key: string): ContentTokenPhrases | null { + return this.cache.getCacheValue(key); + } + + /** + * Gets a cache client that can cache data. Uses memory-cache as the default + * library for caching (@see MemoryCacheClient). Override this method if you + * want to use something else. + * @returns {CacheClient} implementation + */ + protected getCacheClient(): CacheClient { + return new MemoryCacheClient(this.options); + } + + /** + * Fetch dictionary data for a language. + * @param {string} language the language to be used to fetch the dictionary + * @returns {Promise} + */ + abstract fetchContentTokens(language: string): Promise; +} diff --git a/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts b/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts new file mode 100644 index 0000000000..76e21d0970 --- /dev/null +++ b/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts @@ -0,0 +1,198 @@ +import { + GraphQLClient, + GraphQLRequestClientConfig, + GraphQLRequestClientFactory, +} from '../graphql-request-client'; +import { SitecoreTemplateId } from '../constants'; +import { ContentTokenPhrases, ContentTokenServiceBase } from './content-token-service'; +import { CacheOptions } from '../cache-client'; +import { getAppRootId, SearchQueryService, SearchQueryVariables } from '../graphql'; +import debug from '../debug'; + +/** @private */ +export const queryError = + 'Valid value for rootItemId not provided and failed to auto-resolve app root item.'; + +const query = /* GraphQL */ ` + 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 + } + value: field(name: "Value") { + value + } + } + } + } +`; + +/** + * Configuration options for @see GraphQLContentTokenService instances + */ +export interface GraphQLContentTokenServiceConfig + extends Omit, + CacheOptions, + Pick { + /** + * The name of the current Sitecore site. This is used to to determine the search query root + * in cases where one is not specified by the caller. + */ + siteName: string; + + /** + * A GraphQL Request Client Factory is a function that accepts configuration and returns an instance of a GraphQLRequestClient. + * This factory function is used to create and configure GraphQL clients for making GraphQL API requests. + */ + clientFactory: GraphQLRequestClientFactory; + + /** + * Optional. The template ID to use when searching for content token entries. + * @default '7d659ee9d4874d408a9210c6d68844c8' (/sitecore/templates/Feature/Experience Accelerator/Content Tokens/Content Token) + */ + contentTokenEntryTemplateId?: string; + + /** + * Optional. The template ID of a JSS App to use when searching for the appRootId. + * @default '061cba1554744b918a0617903b102b82' (/sitecore/templates/Foundation/JavaScript Services/App) + */ + jssAppTemplateId?: string; +} + +/** + * The schema of data returned in response to a dictionary query request. + */ +export type ContentTokenQueryResult = { + key: { value: string }; + value: { value: string }; +}; + +/** + * Service that fetch dictionary data using Sitecore's GraphQL API. + * @augments ContentTokenServiceBase + * @mixes SearchQueryService + */ +export class GraphQLContentTokenService extends ContentTokenServiceBase { + private graphQLClient: GraphQLClient; + private searchService: SearchQueryService; + + /** + * Creates an instance of graphQL dictionary service with the provided options + * @param {GraphQLContentTokenService} options instance + */ + constructor(public options: GraphQLContentTokenServiceConfig) { + super(options); + this.graphQLClient = this.getGraphQLClient(); + this.searchService = new SearchQueryService(this.graphQLClient); + } + + /** + * Fetches dictionary data for internalization. Uses search query by default + * @param {string} language the language to fetch + * @returns {Promise} dictionary phrases + * @throws {Error} if the app root was not found for the specified site and language. + */ + async fetchContentTokens(language: string): Promise { + const cacheKey = this.options.siteName + language; + const cachedValue = this.getCacheValue(cacheKey); + if (cachedValue) { + debug.contenttokens('using cached dictionary data for %s %s', language, this.options.siteName); + return cachedValue; + } + + const phrases = await this.fetchWithSearchQuery(language); + + this.setCacheValue(cacheKey, phrases); + return phrases; + } + + /** + * Fetches dictionary data with search query + * This is the default behavior for non-XMCloud deployments + * @param {string} language the language to fetch + * @default query (@see query) + * @returns {Promise} dictionary phrases + * @throws {Error} if the app root was not found for the specified site and language. + */ + async fetchWithSearchQuery(language: string): Promise { + debug.contenttokens('fetching site root for %s %s', language, this.options.siteName); + + // If the caller does not specify a root item ID, then we try to figure it out + const rootItemId = + this.options.rootItemId || + (await getAppRootId( + this.graphQLClient, + this.options.siteName, + language, + this.options.jssAppTemplateId + )); + + if (!rootItemId) { + throw new Error(queryError); + } + + debug.contenttokens('fetching dictionary data for %s %s', language, this.options.siteName); + const phrases: ContentTokenPhrases = {}; + //TODO: remove searchservice and implement gql client instead + // await this.getGraphQLClient.request(query, { + // rootItemId, + // language, + // templates: this.options.contentTokenEntryTemplateId || SitecoreTemplateId.ContentTokenEntry, + // pageSiqe: this.options.pageSize + // }) + // .then((results) => { + // results.forEach((item) => (phrases[item.key.value] = item.value.value)); + // }) + await this.searchService + .fetch(query, { + rootItemId, + language, + templates: this.options.contentTokenEntryTemplateId || SitecoreTemplateId.ContentTokenEntry, + pageSize: this.options.pageSize, + }) + .then((results) => { + results.forEach((item) => (phrases[item.key.value] = item.value.value)); + }); + + return phrases; + } + + /** + * Gets a GraphQL client that can make requests to the API. Uses graphql-request as the default + * library for fetching graphql data (@see GraphQLRequestClient). Override this method if you + * want to use something else. + * @returns {GraphQLClient} implementation + */ + protected getGraphQLClient(): GraphQLClient { + if (!this.options.clientFactory) { + throw new Error('clientFactory needs to be provided when initializing GraphQL client.'); + } + return this.options.clientFactory({ + debugger: debug.contenttokens, + retries: this.options.retries, + retryStrategy: this.options.retryStrategy, + }); + } +} \ No newline at end of file From f42a7e39681a05e77d96b2daadf243b18f0553a8 Mon Sep 17 00:00:00 2001 From: "Frank.van.Zutphen" Date: Sat, 5 Oct 2024 13:32:08 +0200 Subject: [PATCH 02/13] added export to Nextjs --- packages/sitecore-jss-nextjs/src/index.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/sitecore-jss-nextjs/src/index.ts b/packages/sitecore-jss-nextjs/src/index.ts index bff971f82c..15ca956c01 100644 --- a/packages/sitecore-jss-nextjs/src/index.ts +++ b/packages/sitecore-jss-nextjs/src/index.ts @@ -48,10 +48,16 @@ export { PageViewInstance, } from '@sitecore-jss/sitecore-jss/tracking'; export { + ContentTokenPhrases, + ContentTokenService, DictionaryPhrases, DictionaryService, + GraphQLContentTokenService, + GraphQLContentTokenServiceConfig, GraphQLDictionaryService, GraphQLDictionaryServiceConfig, + //TODO:RestContentTokenService, + //TODO:RestContentTokenServiceConfig, RestDictionaryService, RestDictionaryServiceConfig, } from '@sitecore-jss/sitecore-jss/i18n'; From 06b23cf82a37859cf16eb22004569c4a9271a749 Mon Sep 17 00:00:00 2001 From: "Frank.van.Zutphen" Date: Sat, 5 Oct 2024 13:46:45 +0200 Subject: [PATCH 03/13] setup for tests --- .../graphql-content-token-service.test.ts | 273 ++++++++++++++++++ .../src/i18n/graphql-content-token-service.ts | 2 +- packages/sitecore-jss/src/i18n/index.ts | 5 + .../mockContentTokenQueryResponse.json | 29 ++ 4 files changed, 308 insertions(+), 1 deletion(-) create mode 100644 packages/sitecore-jss/src/i18n/graphql-content-token-service.test.ts create mode 100644 packages/sitecore-jss/src/test-data/mockContentTokenQueryResponse.json diff --git a/packages/sitecore-jss/src/i18n/graphql-content-token-service.test.ts b/packages/sitecore-jss/src/i18n/graphql-content-token-service.test.ts new file mode 100644 index 0000000000..bb963a33bc --- /dev/null +++ b/packages/sitecore-jss/src/i18n/graphql-content-token-service.test.ts @@ -0,0 +1,273 @@ +/* eslint-disable no-unused-expressions */ +import { expect } from 'chai'; +import sinon, { SinonSpy } from 'sinon'; +import nock from 'nock'; +import { SitecoreTemplateId } from '../constants'; +import { GraphQLClient, GraphQLRequestClient } from '../graphql-request-client'; +import { queryError, GraphQLContentTokenServiceConfig } from './graphql-content-token-service'; +import { GraphQLContentTokenService } from '.'; +import contentTokenQueryResponse from '../test-data/mockContentTokenQueryResponse.json'; +import appRootQueryResponse from '../test-data/mockAppRootQueryResponse.json'; + +class TestService extends GraphQLContentTokenService { + public client: GraphQLClient; + constructor(options: GraphQLContentTokenServiceConfig) { + super(options); + this.client = this.getGraphQLClient(); + } +} + +describe('GraphQLContentTokenService', () => { + const endpoint = 'http://site'; + const siteName = 'site-name'; + const apiKey = 'api-key'; + const rootItemId = '{GUID}'; + const clientFactory = GraphQLRequestClient.createClientFactory({ + endpoint, + apiKey, + }); + + afterEach(() => { + nock.cleanAll(); + }); + + it('should fetch dictionary phrases using clientFactory', async () => { + nock(endpoint, { reqheaders: { sc_apikey: apiKey } }) + .post('/', /ContentTokenSearch/gi) + .reply(200, contentTokenQueryResponse); + + const service = new GraphQLContentTokenService({ + siteName, + rootItemId, + cacheEnabled: false, + clientFactory, + }); + const result = await service.fetchContentTokens('en'); + expect(result.foo).to.equal('foo'); + expect(result.bar).to.equal('bar'); + }); + + it('should attempt to fetch the rootItemId, if rootItemId not provided', async () => { + nock(endpoint) + .post('/', /AppRootQuery/) + .reply(200, appRootQueryResponse); + + nock(endpoint) + .post('/', (body) => body.variables.rootItemId === 'GUIDGUIDGUID') + .reply(200, contentTokenQueryResponse); + + const service = new GraphQLContentTokenService({ + clientFactory, + siteName, + cacheEnabled: false, + }); + const result = await service.fetchContentTokens('en'); + expect(result).to.have.all.keys('foo', 'bar'); + // eslint-disable-next-line no-unused-expressions + expect(nock.isDone()).to.be.true; + }); + + it('should use a custom rootItemId, if provided', async () => { + const customRootId = 'cats'; + + nock(endpoint) + .post('/', (body) => body.variables.rootItemId === customRootId) + .reply(200, contentTokenQueryResponse); + + const service = new GraphQLContentTokenService({ + clientFactory, + siteName, + cacheEnabled: false, + rootItemId: customRootId, + }); + const result = await service.fetchContentTokens('en'); + expect(result).to.have.all.keys('foo', 'bar'); + }); + + it('should use a jssTemplateId, if provided', async () => { + const jssAppTemplateId = '{71d608ca-ac9c-4f1c-8e0a-85a6946e30f8}'; + const randomId = '{412286b7-6d4f-4deb-80e9-108ee986c6e9}'; + + nock(endpoint) + .post('/', (body) => body.variables.jssAppTemplateId === jssAppTemplateId) + .reply(200, { + data: { + layout: { + homePage: { + rootItem: [ + { + id: randomId, + }, + ], + }, + }, + }, + }); + + nock(endpoint) + .post('/', (body) => body.variables.rootItemId === randomId) + .reply(200, contentTokenQueryResponse); + + const service = new GraphQLContentTokenService({ + clientFactory, + siteName, + cacheEnabled: false, + jssAppTemplateId, + }); + + const result = await service.fetchContentTokens('en'); + expect(result).to.have.all.keys('foo', 'bar'); + }); + + it('should throw error if could not resolve rootItemId', async () => { + nock(endpoint) + .post('/', /AppRootQuery/) + .reply(200, { + data: { + layout: { + homePage: null, + }, + }, + }); + + const service = new GraphQLContentTokenService({ + clientFactory, + siteName, + cacheEnabled: false, + }); + + await service.fetchContentTokens('en').catch((error) => { + expect(error).to.be.instanceOf(Error); + expect(error.message).to.equal(queryError); + }); + }); + + it('should use default pageSize, if pageSize not provided', async () => { + nock(endpoint) + .post( + '/', + (body) => + body.query.indexOf('$pageSize: Int = 10') > 0 && body.variables.pageSize === undefined + ) + .reply(200, contentTokenQueryResponse); + + const service = new GraphQLContentTokenService({ + clientFactory, + siteName, + rootItemId, + cacheEnabled: false, + pageSize: undefined, + }); + const result = await service.fetchContentTokens('en'); + expect(result).to.have.all.keys('foo', 'bar'); + }); + + it('should use a custom pageSize, if provided', async () => { + const customPageSize = 2; + + nock(endpoint) + .post('/', (body) => body.variables.pageSize === customPageSize) + .reply(200, contentTokenQueryResponse); + + const service = new GraphQLContentTokenService({ + clientFactory, + siteName, + rootItemId, + cacheEnabled: false, + pageSize: customPageSize, + }); + const result = await service.fetchContentTokens('en'); + expect(result).to.have.all.keys('foo', 'bar'); + }); + + it('should use custom dictionary entry template ID, if provided', async () => { + const customTemplateId = 'custom-template-id'; + + nock(endpoint) + .post('/', (body) => body.variables.templates === customTemplateId) + .reply(200, contentTokenQueryResponse); + + const service = new GraphQLContentTokenService({ + clientFactory, + siteName, + rootItemId, + cacheEnabled: false, + dictionaryEntryTemplateId: customTemplateId, + }); + const result = await service.fetchContentTokens('en'); + expect(result).to.have.all.keys('foo', 'bar'); + }); + + it('should use default dictionary entry template ID, if template ID not provided', async () => { + nock(endpoint) + .post('/', (body) => body.variables.templates === SitecoreTemplateId.DictionaryEntry) + .reply(200, contentTokenQueryResponse); + + const service = new GraphQLContentTokenService({ + clientFactory, + siteName, + rootItemId, + cacheEnabled: false, + }); + const result = await service.fetchContentTokens('en'); + expect(result).to.have.all.keys('foo', 'bar'); + }); + + it('should use cache', async () => { + nock(endpoint, { reqheaders: { sc_apikey: apiKey } }) + .post('/', /ContentTokenSearch/gi) + .reply(200, contentTokenQueryResponse); + + const service = new GraphQLContentTokenService({ + clientFactory, + siteName, + rootItemId, + cacheEnabled: true, + cacheTimeout: 2, + }); + + const result1 = await service.fetchContentTokens('en'); + expect(result1).to.have.all.keys('foo', 'bar'); + + const result2 = await service.fetchContentTokens('en'); + expect(result2).to.have.all.keys('foo', 'bar'); + }); + + it('should provide a default GraphQL client', () => { + const service = new TestService({ + clientFactory, + siteName, + rootItemId, + cacheEnabled: false, + }); + + const graphQLClient = service.client as GraphQLClient; + const graphQLRequestClient = service.client as GraphQLRequestClient; + // eslint-disable-next-line no-unused-expressions + expect(graphQLClient).to.exist; + // eslint-disable-next-line no-unused-expressions + expect(graphQLRequestClient).to.exist; + }); + + it('should call clientFactory with the correct arguments', () => { + const clientFactorySpy: SinonSpy = sinon.spy(); + const mockServiceConfig = { + siteName: 'supersite', + clientFactory: clientFactorySpy, + retries: 3, + retryStrategy: { + getDelay: () => 1000, + shouldRetry: () => true, + }, + }; + + new GraphQLContentTokenService(mockServiceConfig); + + expect(clientFactorySpy.calledOnce).to.be.true; + + const calledWithArgs = clientFactorySpy.firstCall.args[0]; + expect(calledWithArgs.debugger).to.exist; + expect(calledWithArgs.retries).to.equal(mockServiceConfig.retries); + expect(calledWithArgs.retryStrategy).to.deep.equal(mockServiceConfig.retryStrategy); + }); +}); diff --git a/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts b/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts index 76e21d0970..a096fb7cef 100644 --- a/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts +++ b/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts @@ -14,7 +14,7 @@ export const queryError = 'Valid value for rootItemId not provided and failed to auto-resolve app root item.'; const query = /* GraphQL */ ` - query DictionarySearch( + query ContentTokenSearch( $rootItemId: String! $language: String! $templates: String! diff --git a/packages/sitecore-jss/src/i18n/index.ts b/packages/sitecore-jss/src/i18n/index.ts index 2d6f9242cf..5cf56f777d 100644 --- a/packages/sitecore-jss/src/i18n/index.ts +++ b/packages/sitecore-jss/src/i18n/index.ts @@ -1,3 +1,8 @@ +export { ContentTokenPhrases, ContentTokenService, ContentTokenServiceBase } from './content-token-service'; +export { + GraphQLContentTokenServiceConfig, + GraphQLContentTokenService, +} from './graphql-content-token-service' export { DictionaryPhrases, DictionaryService, DictionaryServiceBase } from './dictionary-service'; export { GraphQLDictionaryServiceConfig, diff --git a/packages/sitecore-jss/src/test-data/mockContentTokenQueryResponse.json b/packages/sitecore-jss/src/test-data/mockContentTokenQueryResponse.json new file mode 100644 index 0000000000..7c1bbf9fef --- /dev/null +++ b/packages/sitecore-jss/src/test-data/mockContentTokenQueryResponse.json @@ -0,0 +1,29 @@ +{ + "data": { + "search": { + "total": 5, + "pageInfo": { + "endCursor": "NQ==", + "hasNext": false + }, + "results": [ + { + "key": { + "value": "foo" + }, + "value": { + "value": "foo" + } + }, + { + "key": { + "value": "bar" + }, + "value": { + "value": "bar" + } + } + ] + } + } +} From e8102a4c95574c940860c8096de3fcdb6112025a Mon Sep 17 00:00:00 2001 From: "Frank.van.Zutphen" Date: Sat, 5 Oct 2024 15:48:19 +0200 Subject: [PATCH 04/13] added tests --- .../src/i18n/graphql-content-token-service.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/sitecore-jss/src/i18n/graphql-content-token-service.test.ts b/packages/sitecore-jss/src/i18n/graphql-content-token-service.test.ts index bb963a33bc..68d1b18bea 100644 --- a/packages/sitecore-jss/src/i18n/graphql-content-token-service.test.ts +++ b/packages/sitecore-jss/src/i18n/graphql-content-token-service.test.ts @@ -192,7 +192,7 @@ describe('GraphQLContentTokenService', () => { siteName, rootItemId, cacheEnabled: false, - dictionaryEntryTemplateId: customTemplateId, + contentTokenEntryTemplateId: customTemplateId, }); const result = await service.fetchContentTokens('en'); expect(result).to.have.all.keys('foo', 'bar'); @@ -200,7 +200,7 @@ describe('GraphQLContentTokenService', () => { it('should use default dictionary entry template ID, if template ID not provided', async () => { nock(endpoint) - .post('/', (body) => body.variables.templates === SitecoreTemplateId.DictionaryEntry) + .post('/', (body) => body.variables.templates === SitecoreTemplateId.ContentTokenEntry) .reply(200, contentTokenQueryResponse); const service = new GraphQLContentTokenService({ From 7ac432fed79ec1abe004a0379324cbbec2a9402a Mon Sep 17 00:00:00 2001 From: "Frank.van.Zutphen" Date: Sat, 5 Oct 2024 16:20:24 +0200 Subject: [PATCH 05/13] implemented gql client rather than searchservice --- .../src/i18n/graphql-content-token-service.ts | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts b/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts index a096fb7cef..a65f5ef075 100644 --- a/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts +++ b/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts @@ -6,7 +6,7 @@ import { import { SitecoreTemplateId } from '../constants'; import { ContentTokenPhrases, ContentTokenServiceBase } from './content-token-service'; import { CacheOptions } from '../cache-client'; -import { getAppRootId, SearchQueryService, SearchQueryVariables } from '../graphql'; +import { getAppRootId, SearchQueryResult, SearchQueryVariables } from '../graphql'; import debug from '../debug'; /** @private */ @@ -96,7 +96,7 @@ export type ContentTokenQueryResult = { */ export class GraphQLContentTokenService extends ContentTokenServiceBase { private graphQLClient: GraphQLClient; - private searchService: SearchQueryService; + // private searchService: SearchQueryService; /** * Creates an instance of graphQL dictionary service with the provided options @@ -105,7 +105,7 @@ export class GraphQLContentTokenService extends ContentTokenServiceBase { constructor(public options: GraphQLContentTokenServiceConfig) { super(options); this.graphQLClient = this.getGraphQLClient(); - this.searchService = new SearchQueryService(this.graphQLClient); + // this.searchService = new SearchQueryService(this.graphQLClient); } /** @@ -155,26 +155,26 @@ export class GraphQLContentTokenService extends ContentTokenServiceBase { debug.contenttokens('fetching dictionary data for %s %s', language, this.options.siteName); const phrases: ContentTokenPhrases = {}; - //TODO: remove searchservice and implement gql client instead - // await this.getGraphQLClient.request(query, { - // rootItemId, - // language, - // templates: this.options.contentTokenEntryTemplateId || SitecoreTemplateId.ContentTokenEntry, - // pageSiqe: this.options.pageSize - // }) - // .then((results) => { - // results.forEach((item) => (phrases[item.key.value] = item.value.value)); - // }) - await this.searchService - .fetch(query, { - rootItemId, - language, - templates: this.options.contentTokenEntryTemplateId || SitecoreTemplateId.ContentTokenEntry, - pageSize: this.options.pageSize, - }) - .then((results) => { - results.forEach((item) => (phrases[item.key.value] = item.value.value)); - }); + let results: ContentTokenQueryResult[] = []; + let hasNext = true; + let after = ''; + + while (hasNext) { + const fetchResponse = + await this.graphQLClient.request>(query, { + rootItemId, + language, + templates: this.options.contentTokenEntryTemplateId || SitecoreTemplateId.ContentTokenEntry, + pageSize: this.options.pageSize, + after + }); + + results = results.concat(fetchResponse?.search?.results); + hasNext = fetchResponse.search.pageInfo.hasNext; + after = fetchResponse.search.pageInfo.endCursor; + } + + results.forEach((item) => (phrases[item.key.value] = item.value.value)); return phrases; } From b04f636cefb2fa913e9b875ae4346bcf5c75b4cd Mon Sep 17 00:00:00 2001 From: "Frank.van.Zutphen" Date: Sat, 5 Oct 2024 17:13:29 +0200 Subject: [PATCH 06/13] documentation + rewrote dictionary to content token on the service --- .../src/i18n/graphql-content-token-service.ts | 18 +- .../classes/i18n.ContentTokenServiceBase.md | 201 ++++++++++++++++++ 2 files changed, 210 insertions(+), 9 deletions(-) create mode 100644 ref-docs/sitecore-jss/classes/i18n.ContentTokenServiceBase.md diff --git a/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts b/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts index a65f5ef075..ad9e44ad44 100644 --- a/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts +++ b/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts @@ -82,7 +82,7 @@ export interface GraphQLContentTokenServiceConfig } /** - * The schema of data returned in response to a dictionary query request. + * The schema of data returned in response to a content token query request. */ export type ContentTokenQueryResult = { key: { value: string }; @@ -90,7 +90,7 @@ export type ContentTokenQueryResult = { }; /** - * Service that fetch dictionary data using Sitecore's GraphQL API. + * Service that fetch content token data using Sitecore's GraphQL API. * @augments ContentTokenServiceBase * @mixes SearchQueryService */ @@ -99,7 +99,7 @@ export class GraphQLContentTokenService extends ContentTokenServiceBase { // private searchService: SearchQueryService; /** - * Creates an instance of graphQL dictionary service with the provided options + * Creates an instance of graphQL content token service with the provided options * @param {GraphQLContentTokenService} options instance */ constructor(public options: GraphQLContentTokenServiceConfig) { @@ -109,16 +109,16 @@ export class GraphQLContentTokenService extends ContentTokenServiceBase { } /** - * Fetches dictionary data for internalization. Uses search query by default + * Fetches content token data for internalization. Uses search query by default * @param {string} language the language to fetch - * @returns {Promise} dictionary phrases + * @returns {Promise} content token phrases * @throws {Error} if the app root was not found for the specified site and language. */ async fetchContentTokens(language: string): Promise { const cacheKey = this.options.siteName + language; const cachedValue = this.getCacheValue(cacheKey); if (cachedValue) { - debug.contenttokens('using cached dictionary data for %s %s', language, this.options.siteName); + debug.contenttokens('using cached content token data for %s %s', language, this.options.siteName); return cachedValue; } @@ -129,11 +129,11 @@ export class GraphQLContentTokenService extends ContentTokenServiceBase { } /** - * Fetches dictionary data with search query + * Fetches content token data with search query * This is the default behavior for non-XMCloud deployments * @param {string} language the language to fetch * @default query (@see query) - * @returns {Promise} dictionary phrases + * @returns {Promise} content token phrases * @throws {Error} if the app root was not found for the specified site and language. */ async fetchWithSearchQuery(language: string): Promise { @@ -153,7 +153,7 @@ export class GraphQLContentTokenService extends ContentTokenServiceBase { throw new Error(queryError); } - debug.contenttokens('fetching dictionary data for %s %s', language, this.options.siteName); + debug.contenttokens('fetching content token data for %s %s', language, this.options.siteName); const phrases: ContentTokenPhrases = {}; let results: ContentTokenQueryResult[] = []; let hasNext = true; diff --git a/ref-docs/sitecore-jss/classes/i18n.ContentTokenServiceBase.md b/ref-docs/sitecore-jss/classes/i18n.ContentTokenServiceBase.md new file mode 100644 index 0000000000..9b6d95a9f3 --- /dev/null +++ b/ref-docs/sitecore-jss/classes/i18n.ContentTokenServiceBase.md @@ -0,0 +1,201 @@ +[@sitecore-jss/sitecore-jss](../README.md) / [i18n](../modules/i18n.md) / ContentTokenServiceBase + +# Class: ContentTokenServiceBase + +[i18n](../modules/i18n.md).ContentTokenServiceBase + +Base implementation of + +**`See`** + +ContentTokenService that handles caching content token values + +## Hierarchy + +- **`ContentTokenServiceBase`** + + ↳ [`GraphQLContentTokenService`](i18n.GraphQLContentTokenService.md) + +## Implements + +- [`ContentTokenService`](../interfaces/i18n.ContentTokenService.md) +- `CacheClient`\<[`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md)\> + +## Table of contents + +### Constructors + +- [constructor](i18n.ContentTokenServiceBase.md#constructor) + +### Properties + +- [cache](i18n.ContentTokenServiceBase.md#cache) +- [options](i18n.ContentTokenServiceBase.md#options) + +### Methods + +- [fetchContentTokens](i18n.ContentTokenServiceBase.md#fetchContentTokens) +- [getCacheClient](i18n.ContentTokenServiceBase.md#getcacheclient) +- [getCacheValue](i18n.ContentTokenServiceBase.md#getcachevalue) +- [setCacheValue](i18n.ContentTokenServiceBase.md#setcachevalue) + +## Constructors + +### constructor + +• **new ContentTokenServiceBase**(`options`) + +Initializes a new instance of + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `options` | `CacheOptions` | Configuration options | + +**`See`** + + - ContentTokenService using the provided + - CacheOptions + +#### Defined in + +[packages/sitecore-jss/src/i18n/content-token-service.ts:32](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/content-token-service.ts#L32) + +## Properties + +### cache + +• `Private` **cache**: `CacheClient`\<[`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md)\> + +#### Defined in + +[packages/sitecore-jss/src/i18n/content-token-service.ts:26](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/content-token-service.ts#L26) + +___ + +### options + +• **options**: `CacheOptions` + +Configuration options + +#### Defined in + +[packages/sitecore-jss/src/i18n/content-token-service.ts:32](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/content-token-service.ts#L32) + +## Methods + +### fetchContentTokens + +▸ `Abstract` **fetchContentTokens**(`language`): `Promise`\<[`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md)\> + +Fetch content tokens data for a language. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `language` | `string` | the language to be used to fetch the content tokens | + +#### Returns + +`Promise`\<[`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md)\> + +#### Implementation of + +[ContentTokenService](../interfaces/i18n.ContentTokenService.md).[fetchContentTokens](../interfaces/i18n.ContentTokenService.md#fetchContentTokens) + +#### Defined in + +[packages/sitecore-jss/src/i18n/content-token-service.ts:71](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/content-token-service.ts#L71) + +___ + +### getCacheClient + +▸ `Protected` **getCacheClient**(): `CacheClient`\<[`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md)\> + +Gets a cache client that can cache data. Uses memory-cache as the default +library for caching (@see MemoryCacheClient). Override this method if you +want to use something else. + +#### Returns + +`CacheClient`\<[`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md)\> + +implementation + +#### Defined in + +[packages/sitecore-jss/src/i18n/content-token-service.ts:62](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/content-token-service.ts#L62) + +___ + +### getCacheValue + +▸ **getCacheValue**(`key`): ``null`` \| [`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md) + +Retrieves a + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `key` | `string` | The cache key. | + +#### Returns + +``null`` \| [`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md) + +The + +**`See`** + + - ContentTokenPhrases value from the cache. + - ContentTokenPhrases value, or null if the specified key is not found in the cache. + +#### Implementation of + +CacheClient.getCacheValue + +#### Defined in + +[packages/sitecore-jss/src/i18n/content-token-service.ts:52](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/content-token-service.ts#L52) + +___ + +### setCacheValue + +▸ **setCacheValue**(`key`, `value`): [`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md) + +Caches a + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `key` | `string` | The cache key. | +| `value` | [`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md) | The value to cache. | + +#### Returns + +[`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md) + +The value added to the cache. + +**`See`** + +ContentTokenPhrases value for the specified cache key. + +**`Mixes`** + +CacheClient + +#### Implementation of + +CacheClient.setCacheValue + +#### Defined in + +[packages/sitecore-jss/src/i18n/content-token-service.ts:43](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/content-token-service.ts#L43) From 6dc221b0d02d6c40478482da552d33187d5358aa Mon Sep 17 00:00:00 2001 From: "Frank.van.Zutphen" Date: Sat, 5 Oct 2024 18:37:44 +0200 Subject: [PATCH 07/13] documentation update for gql content token service --- .../src/i18n/graphql-content-token-service.ts | 3 - .../i18n.GraphQLContentTokenService.md | 262 ++++++++++++++++++ 2 files changed, 262 insertions(+), 3 deletions(-) create mode 100644 ref-docs/sitecore-jss/classes/i18n.GraphQLContentTokenService.md diff --git a/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts b/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts index ad9e44ad44..8e28535fe4 100644 --- a/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts +++ b/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts @@ -92,11 +92,9 @@ export type ContentTokenQueryResult = { /** * Service that fetch content token data using Sitecore's GraphQL API. * @augments ContentTokenServiceBase - * @mixes SearchQueryService */ export class GraphQLContentTokenService extends ContentTokenServiceBase { private graphQLClient: GraphQLClient; - // private searchService: SearchQueryService; /** * Creates an instance of graphQL content token service with the provided options @@ -105,7 +103,6 @@ export class GraphQLContentTokenService extends ContentTokenServiceBase { constructor(public options: GraphQLContentTokenServiceConfig) { super(options); this.graphQLClient = this.getGraphQLClient(); - // this.searchService = new SearchQueryService(this.graphQLClient); } /** diff --git a/ref-docs/sitecore-jss/classes/i18n.GraphQLContentTokenService.md b/ref-docs/sitecore-jss/classes/i18n.GraphQLContentTokenService.md new file mode 100644 index 0000000000..f0a1d43e6e --- /dev/null +++ b/ref-docs/sitecore-jss/classes/i18n.GraphQLContentTokenService.md @@ -0,0 +1,262 @@ +[@sitecore-jss/sitecore-jss](../README.md) / [i18n](../modules/i18n.md) / GraphQLContentTokenService + +# Class: GraphQLContentTokenService + +[i18n](../modules/i18n.md).GraphQLContentTokenService + +Service that fetch content token data using Sitecore's GraphQL API. + +## Hierarchy + +- [`ContentTokenServiceBase`](i18n.ContentTokenServiceBase.md) + + ↳ **`GraphQLContentTokenService`** + +## Table of contents + +### Constructors + +- [constructor](i18n.GraphQLContentTokenService.md#constructor) + +### Properties + +- [graphQLClient](i18n.GraphQLContentTokenService.md#graphqlclient) +- [options](i18n.GraphQLContentTokenService.md#options) + +### Methods + +- [fetchContentTokens](i18n.GraphQLContentTokenService.md#fetchContentTokens) +- [fetchWithSearchQuery](i18n.GraphQLContentTokenService.md#fetchwithsearchquery) +- [getCacheClient](i18n.GraphQLContentTokenService.md#getcacheclient) +- [getCacheValue](i18n.GraphQLContentTokenService.md#getcachevalue) +- [getGraphQLClient](i18n.GraphQLContentTokenService.md#getgraphqlclient) +- [setCacheValue](i18n.GraphQLContentTokenService.md#setcachevalue) + +## Constructors + +### constructor + +• **new GraphQLContentTokenService**(`options`) + +Creates an instance of graphQL content token service with the provided options + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `options` | [`GraphQLContentTokenServiceConfig`](../interfaces/i18n.GraphQLContentTokenServiceConfig.md) | instance | + +#### Overrides + +[ContentTokenServiceBase](i18n.ContentTokenServiceBase.md).[constructor](i18n.ContentTokenServiceBase.md#constructor) + +#### Defined in + +[packages/sitecore-jss/src/i18n/graphql-content-token-service.ts:103](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts#L103) + +## Properties + +### graphQLClient + +• `Private` **graphQLClient**: [`GraphQLClient`](../interfaces/index.GraphQLClient.md) + +#### Defined in + +[packages/sitecore-jss/src/i18n/graphql-content-token-service.ts:97](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts#L97) + +___ + +### options + +• **options**: [`GraphQLContentTokenServiceConfig`](../interfaces/i18n.GraphQLContentTokenServiceConfig.md) + +instance + +#### Inherited from + +[ContentTokenServiceBase](i18n.ContentTokenServiceBase.md).[options](i18n.ContentTokenServiceBase.md#options) + +#### Defined in + +[packages/sitecore-jss/src/i18n/graphql-content-token-service.ts:103](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts#L103) + +## Methods + +### fetchContentTokens + +▸ **fetchContentTokens**(`language`): `Promise`\<[`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md)\> + +Fetches content token data for internalization. Uses search query by default + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `language` | `string` | the language to fetch | + +#### Returns + +`Promise`\<[`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md)\> + +content token phrases + +**`Throws`** + +if the app root was not found for the specified site and language. + +#### Overrides + +[ContentTokenServiceBase](i18n.ContentTokenServiceBase.md).[fetchContentTokens](i18n.ContentTokenServiceBase.md#fetchContentTokens) + +#### Defined in + +[packages/sitecore-jss/src/i18n/graphql-content-token-service.ts:114](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts#L114) + +___ + +### fetchWithSearchQuery + +▸ **fetchWithSearchQuery**(`language`): `Promise`\<[`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md)\> + +Fetches content token data with search query +This is the default behavior for non-XMCloud deployments + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `language` | `string` | the language to fetch | + +#### Returns + +`Promise`\<[`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md)\> + +content token phrases + +**`Default`** + +```ts +query (@see query) +``` + +**`Throws`** + +if the app root was not found for the specified site and language. + +#### Defined in + +[packages/sitecore-jss/src/i18n/graphql-content-token-service.ts:136](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts#L136) + +___ + +### getCacheClient + +▸ `Protected` **getCacheClient**(): `CacheClient`\<[`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md)\> + +Gets a cache client that can cache data. Uses memory-cache as the default +library for caching (@see MemoryCacheClient). Override this method if you +want to use something else. + +#### Returns + +`CacheClient`\<[`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md)\> + +implementation + +#### Inherited from + +[ContentTokenServiceBase](i18n.ContentTokenServiceBase.md).[getCacheClient](i18n.ContentTokenServiceBase.md#getcacheclient) + +#### Defined in + +[packages/sitecore-jss/src/i18n/content-token-service.ts:62](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/content-token-service.ts#L62) + +___ + +### getCacheValue + +▸ **getCacheValue**(`key`): ``null`` \| [`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md) + +Retrieves a + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `key` | `string` | The cache key. | + +#### Returns + +``null`` \| [`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md) + +The + +**`See`** + + - ContentTokenPhrases value from the cache. + - ContentTokenPhrases value, or null if the specified key is not found in the cache. + +#### Inherited from + +[ContentTokenServiceBase](i18n.ContentTokenServiceBase.md).[getCacheValue](i18n.ContentTokenServiceBase.md#getcachevalue) + +#### Defined in + +[packages/sitecore-jss/src/i18n/content-token-service.ts:52](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/content-token-service.ts#L52) + +___ + +### getGraphQLClient + +▸ `Protected` **getGraphQLClient**(): [`GraphQLClient`](../interfaces/index.GraphQLClient.md) + +Gets a GraphQL client that can make requests to the API. Uses graphql-request as the default +library for fetching graphql data (@see GraphQLRequestClient). Override this method if you +want to use something else. + +#### Returns + +[`GraphQLClient`](../interfaces/index.GraphQLClient.md) + +implementation + +#### Defined in + +[packages/sitecore-jss/src/i18n/graphql-content-token-service.ts:185](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts#L185) + +___ + +### setCacheValue + +▸ **setCacheValue**(`key`, `value`): [`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md) + +Caches a + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `key` | `string` | The cache key. | +| `value` | [`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md) | The value to cache. | + +#### Returns + +[`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md) + +The value added to the cache. + +**`See`** + +ContentTokenPhrases value for the specified cache key. + +**`Mixes`** + +CacheClient + +#### Inherited from + +[ContentTokenServiceBase](i18n.ContentTokenServiceBase.md).[setCacheValue](i18n.ContentTokenServiceBase.md#setcachevalue) + +#### Defined in + +[packages/sitecore-jss/src/i18n/content-token-service.ts:43](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/content-token-service.ts#L43) From 55d752ad8c796e4de53be000b74b1618dccc6a1c Mon Sep 17 00:00:00 2001 From: "Frank.van.Zutphen" Date: Sat, 5 Oct 2024 18:42:16 +0200 Subject: [PATCH 08/13] documentation for interfaces --- .../interfaces/i18n.ContentTokenPhrases.md | 11 ++++++ .../interfaces/i18n.ContentTokenService.md | 39 +++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 ref-docs/sitecore-jss/interfaces/i18n.ContentTokenPhrases.md create mode 100644 ref-docs/sitecore-jss/interfaces/i18n.ContentTokenService.md diff --git a/ref-docs/sitecore-jss/interfaces/i18n.ContentTokenPhrases.md b/ref-docs/sitecore-jss/interfaces/i18n.ContentTokenPhrases.md new file mode 100644 index 0000000000..7f9b87bf8d --- /dev/null +++ b/ref-docs/sitecore-jss/interfaces/i18n.ContentTokenPhrases.md @@ -0,0 +1,11 @@ +[@sitecore-jss/sitecore-jss](../README.md) / [i18n](../modules/i18n.md) / ContentTokenPhrases + +# Interface: ContentTokenPhrases + +[i18n](../modules/i18n.md).ContentTokenPhrases + +Object model for Sitecore content token phrases + +## Indexable + +▪ [k: `string`]: `string` \ No newline at end of file diff --git a/ref-docs/sitecore-jss/interfaces/i18n.ContentTokenService.md b/ref-docs/sitecore-jss/interfaces/i18n.ContentTokenService.md new file mode 100644 index 0000000000..01d24e91f6 --- /dev/null +++ b/ref-docs/sitecore-jss/interfaces/i18n.ContentTokenService.md @@ -0,0 +1,39 @@ +[@sitecore-jss/sitecore-jss](../README.md) / [i18n](../modules/i18n.md) / ContentTokenService + +# Interface: ContentTokenService + +[i18n](../modules/i18n.md).ContentTokenService + +Service that fetches content token data using Sitecore's GraphQL API. + +## Implemented by + +- [`ContentTokenServiceBase`](../classes/i18n.ContentTokenServiceBase.md) + +## Table of contents + +### Methods + +- [fetchContentTokens](i18n.ContentTokenService.md#fetchContentTokens) + +## Methods + +### fetchContentTokens + +▸ **fetchContentTokens**(`language`): `Promise`\<[`ContentTokenPhrases`](i18n.ContentTokenPhrases.md)\> + +Fetch content token data for a language. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `language` | `string` | the language to be used to fetch the content token | + +#### Returns + +`Promise`\<[`ContentTokenPhrases`](i18n.ContentTokenPhrases.md)\> + +#### Defined in + +[packages/sitecore-jss/src/i18n/content-token-service.ts:18](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/content-token-service.ts#L18) From 4acdc80eff913442c008cb271b81c0f830c156d9 Mon Sep 17 00:00:00 2001 From: "Frank.van.Zutphen" Date: Sat, 5 Oct 2024 18:56:54 +0200 Subject: [PATCH 09/13] rename template reference from contentTokenEntryTemplateId to contentTokenTemplateId --- packages/sitecore-jss/src/constants.ts | 2 +- .../sitecore-jss/src/i18n/graphql-content-token-service.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/sitecore-jss/src/constants.ts b/packages/sitecore-jss/src/constants.ts index 866efc73d5..322341ea9a 100644 --- a/packages/sitecore-jss/src/constants.ts +++ b/packages/sitecore-jss/src/constants.ts @@ -6,7 +6,7 @@ export enum SitecoreTemplateId { DictionaryEntry = '6d1cd89719364a3aa511289a94c2a7b1', // /sitecore/templates/Feature/Experience Accelerator/Content Tokens/Content Token - ContentTokenEntry = '7d659ee9d4874d408a9210c6d68844c8' + ContentToken = '7d659ee9d4874d408a9210c6d68844c8' } export const FETCH_WITH = { diff --git a/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts b/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts index 8e28535fe4..37d68e8bd1 100644 --- a/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts +++ b/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts @@ -72,7 +72,7 @@ export interface GraphQLContentTokenServiceConfig * Optional. The template ID to use when searching for content token entries. * @default '7d659ee9d4874d408a9210c6d68844c8' (/sitecore/templates/Feature/Experience Accelerator/Content Tokens/Content Token) */ - contentTokenEntryTemplateId?: string; + contentTokenTemplateId?: string; /** * Optional. The template ID of a JSS App to use when searching for the appRootId. @@ -161,7 +161,7 @@ export class GraphQLContentTokenService extends ContentTokenServiceBase { await this.graphQLClient.request>(query, { rootItemId, language, - templates: this.options.contentTokenEntryTemplateId || SitecoreTemplateId.ContentTokenEntry, + templates: this.options.contentTokenTemplateId || SitecoreTemplateId.ContentToken, pageSize: this.options.pageSize, after }); From 86d8b7e7a212acdaf4bccdb4f96f9b865e40fddc Mon Sep 17 00:00:00 2001 From: "Frank.van.Zutphen" Date: Sat, 5 Oct 2024 20:48:16 +0200 Subject: [PATCH 10/13] more documentation --- .../i18n.GraphQLContentTokenServiceConfig.md | 218 ++++++++++++++++++ ref-docs/sitecore-jss/modules/i18n.md | 5 + 2 files changed, 223 insertions(+) create mode 100644 ref-docs/sitecore-jss/interfaces/i18n.GraphQLContentTokenServiceConfig.md diff --git a/ref-docs/sitecore-jss/interfaces/i18n.GraphQLContentTokenServiceConfig.md b/ref-docs/sitecore-jss/interfaces/i18n.GraphQLContentTokenServiceConfig.md new file mode 100644 index 0000000000..2649d19340 --- /dev/null +++ b/ref-docs/sitecore-jss/interfaces/i18n.GraphQLContentTokenServiceConfig.md @@ -0,0 +1,218 @@ +[@sitecore-jss/sitecore-jss](../README.md) / [i18n](../modules/i18n.md) / GraphQLContentTokenServiceConfig + +# Interface: GraphQLContentTokenServiceConfig + +[i18n](../modules/i18n.md).GraphQLContentTokenServiceConfig + +Configuration options for + +**`See`** + +GraphQLContentTokenService instances + +## Hierarchy + +- `Omit`\<[`SearchQueryVariables`](graphql.SearchQueryVariables.md), ``"language"``\> + +- `CacheOptions` + +- `Pick`\<[`GraphQLRequestClientConfig`](../modules/index.md#graphqlrequestclientconfig), ``"retries"`` \| ``"retryStrategy"``\> + + ↳ **`GraphQLContentTokenServiceConfig`** + +## Table of contents + +### Properties + +- [cacheEnabled](i18n.GraphQLContentTokenServiceConfig.md#cacheenabled) +- [cacheTimeout](i18n.GraphQLContentTokenServiceConfig.md#cachetimeout) +- [clientFactory](i18n.GraphQLContentTokenServiceConfig.md#clientfactory) +- [contentTokenTemplateId](i18n.GraphQLContentTokenServiceConfig.md#contentTokenTemplateId) +- [jssAppTemplateId](i18n.GraphQLContentTokenServiceConfig.md#jssapptemplateid) +- [pageSize](i18n.GraphQLContentTokenServiceConfig.md#pagesize) +- [retries](i18n.GraphQLContentTokenServiceConfig.md#retries) +- [retryStrategy](i18n.GraphQLContentTokenServiceConfig.md#retrystrategy) +- [rootItemId](i18n.GraphQLContentTokenServiceConfig.md#rootitemid) +- [siteName](i18n.GraphQLContentTokenServiceConfig.md#sitename) +- [templates](i18n.GraphQLContentTokenServiceConfig.md#templates) + +## Properties + +### cacheEnabled + +• `Optional` **cacheEnabled**: `boolean` + +Enable/disable caching mechanism + +**`Default`** + +```ts +true +``` + +#### Inherited from + +CacheOptions.cacheEnabled + +#### Defined in + +[packages/sitecore-jss/src/cache-client.ts:40](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/cache-client.ts#L40) + +___ + +### cacheTimeout + +• `Optional` **cacheTimeout**: `number` + +Cache timeout (sec) + +**`Default`** + +```ts +60 +``` + +#### Inherited from + +CacheOptions.cacheTimeout + +#### Defined in + +[packages/sitecore-jss/src/cache-client.ts:45](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/cache-client.ts#L45) + +___ + +### clientFactory + +• **clientFactory**: [`GraphQLRequestClientFactory`](../modules/index.md#graphqlrequestclientfactory) + +A GraphQL Request Client Factory is a function that accepts configuration and returns an instance of a GraphQLRequestClient. +This factory function is used to create and configure GraphQL clients for making GraphQL API requests. + +#### Defined in + +[packages/sitecore-jss/src/i18n/graphql-content-token-service.ts:94](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts#L94) + +___ + +### contentTokenTemplateId + +• `Optional` **contentTokenTemplateId**: `string` + +Optional. The template ID to use when searching for dictionary entries. + +**`Default`** + +```ts +'7d659ee9d4874d408a9210c6d68844c8' (/sitecore/templates/Feature/Experience Accelerator/Content Tokens/Content Token) +``` + +#### Defined in + +[packages/sitecore-jss/src/i18n/graphql-content-token-service.ts:75](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts#L75) + +___ + +### jssAppTemplateId + +• `Optional` **jssAppTemplateId**: `string` + +Optional. The template ID of a JSS App to use when searching for the appRootId. + +**`Default`** + +```ts +'061cba1554744b918a0617903b102b82' (/sitecore/templates/Foundation/JavaScript Services/App) +``` + +#### Defined in + +[packages/sitecore-jss/src/i18n/graphql-content-token-service.ts:81](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts#L81) + +___ + +### pageSize + +• `Optional` **pageSize**: `number` + +common variable for all GraphQL queries +it will be used for every type of query to regulate result batch size +Optional. How many result items to fetch in each GraphQL call. This is needed for pagination. + +**`Default`** + +```ts +10 +``` + +#### Inherited from + +Omit.pageSize +___ + +### retries + +• `Optional` **retries**: `number` + +Number of retries for client. Will use the specified `retryStrategy`. + +#### Inherited from + +Pick.retries + +#### Defined in + +[packages/sitecore-jss/src/graphql-request-client.ts:83](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/graphql-request-client.ts#L83) + +___ + +### retryStrategy + +• `Optional` **retryStrategy**: [`RetryStrategy`](index.RetryStrategy.md) + +Retry strategy for the client. Uses `DefaultRetryStrategy` by default with exponential +back-off factor of 2 for codes 429, 502, 503, 504, 520, 521, 522, 523, 524. + +#### Inherited from + +Pick.retryStrategy + +#### Defined in + +[packages/sitecore-jss/src/graphql-request-client.ts:88](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/graphql-request-client.ts#L88) + +___ + +### rootItemId + +• `Optional` **rootItemId**: `string` + +Optional. The ID of the search root item. Fetch items that have this item as an ancestor. + +#### Inherited from + +Omit.rootItemId +___ + +### siteName + +• **siteName**: `string` + +The name of the current Sitecore site. This is used to to determine the search query root +in cases where one is not specified by the caller. + +#### Defined in + +[packages/sitecore-jss/src/i18n/graphql-content-token-service.ts:63](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts#L63) + +___ + +### templates + +• `Optional` **templates**: `string` + +Optional. Sitecore template ID(s). Fetch items that inherit from this template(s). + +#### Inherited from + +Omit.templates diff --git a/ref-docs/sitecore-jss/modules/i18n.md b/ref-docs/sitecore-jss/modules/i18n.md index 77d9f8dc9a..7d846c3ce0 100644 --- a/ref-docs/sitecore-jss/modules/i18n.md +++ b/ref-docs/sitecore-jss/modules/i18n.md @@ -6,14 +6,19 @@ ### Classes +- [ContentTokenServiceBase](../classes/i18n.ContentTokenServiceBase.md) - [DictionaryServiceBase](../classes/i18n.DictionaryServiceBase.md) +- [GraphQLContentTokenService](../classes/i18n.GraphQLContentTokenService.md) - [GraphQLDictionaryService](../classes/i18n.GraphQLDictionaryService.md) - [RestDictionaryService](../classes/i18n.RestDictionaryService.md) ### Interfaces +- [ContentTokenPhrases](../interfaces/i18n.ContentTokenPhrases.md) +- [ContentTokenService](../interfaces/i18n.ContentTokenService.md) - [DictionaryPhrases](../interfaces/i18n.DictionaryPhrases.md) - [DictionaryService](../interfaces/i18n.DictionaryService.md) +- [GraphQLContentTokenServiceConfig](../interfaces/i18n.GraphQLContentTokenServiceConfig.md) - [GraphQLDictionaryServiceConfig](../interfaces/i18n.GraphQLDictionaryServiceConfig.md) ### Type Aliases From 63f3886288f0c59aca26eb0e4cc8ed3031d4ee17 Mon Sep 17 00:00:00 2001 From: "Frank.van.Zutphen" Date: Sat, 5 Oct 2024 22:26:04 +0200 Subject: [PATCH 11/13] updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd6b8f3b3c..0625b5a832 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,7 @@ Our versioning strategy is as follows: * `[sitecore-jss]` GenericFieldValue model is updated to accept Date type ([#1916](https://github.com/Sitecore/jss/pull/1916)) * `[template/node-xmcloud-proxy]` `[sitecore-jss-proxy]` Introduced /api/healthz endpoint ([#1928](https://github.com/Sitecore/jss/pull/1928)) * `[sitecore-jss]` `[sitecore-jss-angular]` Render field metdata chromes in editMode metadata - in edit mode metadata in Pages, angular package field directives will render wrapping `code` elements with field metadata required for editing; ([#1926](https://github.com/Sitecore/jss/pull/1926)) +* `[sitecore-jss]` Added services for Content Tokens `/sitecore/templates/Feature/Experience Accelerator/Content Tokens/Content Token` so we're a step closer to enabling (custom) content replacement tokens in headless development ### 🛠 Breaking Change From bf7662d34a6a82d849afa001ceb3124148c9d1df Mon Sep 17 00:00:00 2001 From: "Frank.van.Zutphen" Date: Sun, 6 Oct 2024 14:03:38 +0200 Subject: [PATCH 12/13] lint fixes --- packages/sitecore-jss-nextjs/src/index.ts | 4 +-- packages/sitecore-jss/src/constants.ts | 2 +- .../src/i18n/graphql-content-token-service.ts | 27 +++++++++++-------- packages/sitecore-jss/src/i18n/index.ts | 8 ++++-- 4 files changed, 25 insertions(+), 16 deletions(-) diff --git a/packages/sitecore-jss-nextjs/src/index.ts b/packages/sitecore-jss-nextjs/src/index.ts index 15ca956c01..1ca2981304 100644 --- a/packages/sitecore-jss-nextjs/src/index.ts +++ b/packages/sitecore-jss-nextjs/src/index.ts @@ -56,8 +56,8 @@ export { GraphQLContentTokenServiceConfig, GraphQLDictionaryService, GraphQLDictionaryServiceConfig, - //TODO:RestContentTokenService, - //TODO:RestContentTokenServiceConfig, + // TODO:RestContentTokenService, + // TODO:RestContentTokenServiceConfig, RestDictionaryService, RestDictionaryServiceConfig, } from '@sitecore-jss/sitecore-jss/i18n'; diff --git a/packages/sitecore-jss/src/constants.ts b/packages/sitecore-jss/src/constants.ts index 322341ea9a..08f5746481 100644 --- a/packages/sitecore-jss/src/constants.ts +++ b/packages/sitecore-jss/src/constants.ts @@ -6,7 +6,7 @@ export enum SitecoreTemplateId { DictionaryEntry = '6d1cd89719364a3aa511289a94c2a7b1', // /sitecore/templates/Feature/Experience Accelerator/Content Tokens/Content Token - ContentToken = '7d659ee9d4874d408a9210c6d68844c8' + ContentToken = '7d659ee9d4874d408a9210c6d68844c8', } export const FETCH_WITH = { diff --git a/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts b/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts index 37d68e8bd1..bfd683a99a 100644 --- a/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts +++ b/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts @@ -115,7 +115,11 @@ export class GraphQLContentTokenService extends ContentTokenServiceBase { const cacheKey = this.options.siteName + language; const cachedValue = this.getCacheValue(cacheKey); if (cachedValue) { - debug.contenttokens('using cached content token data for %s %s', language, this.options.siteName); + debug.contenttokens( + 'using cached content token data for %s %s', + language, + this.options.siteName + ); return cachedValue; } @@ -157,16 +161,17 @@ export class GraphQLContentTokenService extends ContentTokenServiceBase { let after = ''; while (hasNext) { - const fetchResponse = - await this.graphQLClient.request>(query, { - rootItemId, - language, - templates: this.options.contentTokenTemplateId || SitecoreTemplateId.ContentToken, - pageSize: this.options.pageSize, - after - }); + const fetchResponse = await this.graphQLClient.request< + SearchQueryResult + >(query, { + rootItemId, + language, + templates: this.options.contentTokenTemplateId || SitecoreTemplateId.ContentToken, + pageSize: this.options.pageSize, + after, + }); - results = results.concat(fetchResponse?.search?.results); + results = results.concat(fetchResponse?.search?.results); hasNext = fetchResponse.search.pageInfo.hasNext; after = fetchResponse.search.pageInfo.endCursor; } @@ -192,4 +197,4 @@ export class GraphQLContentTokenService extends ContentTokenServiceBase { retryStrategy: this.options.retryStrategy, }); } -} \ No newline at end of file +} diff --git a/packages/sitecore-jss/src/i18n/index.ts b/packages/sitecore-jss/src/i18n/index.ts index 5cf56f777d..5534199182 100644 --- a/packages/sitecore-jss/src/i18n/index.ts +++ b/packages/sitecore-jss/src/i18n/index.ts @@ -1,8 +1,12 @@ -export { ContentTokenPhrases, ContentTokenService, ContentTokenServiceBase } from './content-token-service'; +export { + ContentTokenPhrases, + ContentTokenService, + ContentTokenServiceBase, +} from './content-token-service'; export { GraphQLContentTokenServiceConfig, GraphQLContentTokenService, -} from './graphql-content-token-service' +} from './graphql-content-token-service'; export { DictionaryPhrases, DictionaryService, DictionaryServiceBase } from './dictionary-service'; export { GraphQLDictionaryServiceConfig, From 9ff304fc57301d1d43becc08ef53d2e5e4e4850f Mon Sep 17 00:00:00 2001 From: "Frank.van.Zutphen" Date: Sun, 6 Oct 2024 22:16:01 +0200 Subject: [PATCH 13/13] fixed tests --- .../src/i18n/graphql-content-token-service.test.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/sitecore-jss/src/i18n/graphql-content-token-service.test.ts b/packages/sitecore-jss/src/i18n/graphql-content-token-service.test.ts index 68d1b18bea..33b9a32295 100644 --- a/packages/sitecore-jss/src/i18n/graphql-content-token-service.test.ts +++ b/packages/sitecore-jss/src/i18n/graphql-content-token-service.test.ts @@ -31,7 +31,7 @@ describe('GraphQLContentTokenService', () => { nock.cleanAll(); }); - it('should fetch dictionary phrases using clientFactory', async () => { + it('should fetch content token phrases using clientFactory', async () => { nock(endpoint, { reqheaders: { sc_apikey: apiKey } }) .post('/', /ContentTokenSearch/gi) .reply(200, contentTokenQueryResponse); @@ -180,7 +180,7 @@ describe('GraphQLContentTokenService', () => { expect(result).to.have.all.keys('foo', 'bar'); }); - it('should use custom dictionary entry template ID, if provided', async () => { + it('should use custom content token template ID, if provided', async () => { const customTemplateId = 'custom-template-id'; nock(endpoint) @@ -192,15 +192,15 @@ describe('GraphQLContentTokenService', () => { siteName, rootItemId, cacheEnabled: false, - contentTokenEntryTemplateId: customTemplateId, + contentTokenTemplateId: customTemplateId, }); const result = await service.fetchContentTokens('en'); expect(result).to.have.all.keys('foo', 'bar'); }); - it('should use default dictionary entry template ID, if template ID not provided', async () => { + it('should use default content token template ID, if template ID not provided', async () => { nock(endpoint) - .post('/', (body) => body.variables.templates === SitecoreTemplateId.ContentTokenEntry) + .post('/', (body) => body.variables.templates === SitecoreTemplateId.ContentToken) .reply(200, contentTokenQueryResponse); const service = new GraphQLContentTokenService({