Skip to content

Commit 1582816

Browse files
GraphQL site info service (#1227)
* GraphQL site info service * some tweaks * more tweaks: debug, query * query fix for xm cloud * move template id to contstants
1 parent b8b1602 commit 1582816

File tree

5 files changed

+268
-0
lines changed

5 files changed

+268
-0
lines changed

packages/sitecore-jss/src/constants.ts

+2
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,6 @@ export const JSS_MODE = {
1616
DISCONNECTED: 'disconnected',
1717
};
1818

19+
export const headlessSiteGroupingTemplate = 'E46F3AF2-39FA-4866-A157-7017C4B2A40C';
20+
1921
export const siteNameError = 'The siteName cannot be empty';

packages/sitecore-jss/src/debug.ts

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export default {
3333
dictionary: debug(`${rootNamespace}:dictionary`),
3434
editing: debug(`${rootNamespace}:editing`),
3535
sitemap: debug(`${rootNamespace}:sitemap`),
36+
multisite: debug(`${rootNamespace}:multisite`),
3637
robots: debug(`${rootNamespace}:robots`),
3738
redirects: debug(`${rootNamespace}:redirects`),
3839
personalize: debug(`${rootNamespace}:personalize`),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import { expect } from 'chai';
2+
import nock from 'nock';
3+
import { GraphQLSiteInfoService } from './graphql-siteinfo-service';
4+
5+
describe('GraphQLSiteInfoService', () => {
6+
const endpoint = 'http://site';
7+
const apiKey = 'some-api-key';
8+
const nonEmptyResponse = {
9+
data: {
10+
search: {
11+
results: [
12+
{
13+
name: 'site 51',
14+
hostName: {
15+
value: 'restricted.gov',
16+
},
17+
virtualFolder: {
18+
value: '/aliens',
19+
},
20+
language: {
21+
value: 'en',
22+
},
23+
},
24+
{
25+
name: 'public',
26+
hostName: {
27+
value: 'pr.showercurtains.org',
28+
},
29+
virtualFolder: {
30+
value: '/we-are-open',
31+
},
32+
language: {
33+
value: '',
34+
},
35+
},
36+
],
37+
},
38+
},
39+
};
40+
const emptyResponse = {
41+
data: {
42+
search: {
43+
results: [],
44+
},
45+
},
46+
};
47+
48+
afterEach(() => {
49+
nock.cleanAll();
50+
});
51+
52+
const mockSiteInfoRequest = () => {
53+
nock(endpoint)
54+
.post('/')
55+
.reply(200, nonEmptyResponse);
56+
};
57+
58+
it('should return correct result', async () => {
59+
mockSiteInfoRequest();
60+
const service = new GraphQLSiteInfoService({ apiKey: apiKey, endpoint: endpoint });
61+
const result = await service.fetchSiteInfo();
62+
expect(result).to.be.deep.equal([
63+
{
64+
name: 'site 51',
65+
hostName: 'restricted.gov',
66+
virtualFolder: '/aliens',
67+
language: 'en',
68+
},
69+
{
70+
name: 'public',
71+
hostName: 'pr.showercurtains.org',
72+
virtualFolder: '/we-are-open',
73+
language: '',
74+
},
75+
]);
76+
});
77+
78+
it('should return empty array when empty result received', async () => {
79+
nock(endpoint)
80+
.post('/')
81+
.reply(200, emptyResponse);
82+
const service = new GraphQLSiteInfoService({ apiKey: apiKey, endpoint: endpoint });
83+
const result = await service.fetchSiteInfo();
84+
expect(result).to.deep.equal([]);
85+
});
86+
87+
it('should use caching by default', async () => {
88+
mockSiteInfoRequest();
89+
const service = new GraphQLSiteInfoService({ apiKey: apiKey, endpoint: endpoint });
90+
const result = await service.fetchSiteInfo();
91+
nock.cleanAll();
92+
nock(endpoint)
93+
.post('/')
94+
.reply(200, emptyResponse);
95+
const resultCached = await service.fetchSiteInfo();
96+
expect(resultCached).to.deep.equal(result);
97+
});
98+
99+
it('should be possible to disable cache', async () => {
100+
mockSiteInfoRequest();
101+
const service = new GraphQLSiteInfoService({
102+
apiKey: apiKey,
103+
endpoint: endpoint,
104+
cacheEnabled: false,
105+
});
106+
const result = await service.fetchSiteInfo();
107+
expect(result).to.be.deep.equal([
108+
{
109+
name: 'site 51',
110+
hostName: 'restricted.gov',
111+
virtualFolder: '/aliens',
112+
language: 'en',
113+
},
114+
{
115+
name: 'public',
116+
hostName: 'pr.showercurtains.org',
117+
virtualFolder: '/we-are-open',
118+
language: '',
119+
},
120+
]);
121+
nock.cleanAll();
122+
nock(endpoint)
123+
.post('/')
124+
.reply(200, emptyResponse);
125+
const resultCached = await service.fetchSiteInfo();
126+
expect(resultCached).to.deep.equal([]);
127+
});
128+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import { GraphQLClient, GraphQLRequestClient } from '../graphql';
2+
import debug from '../debug';
3+
import { CacheClient, CacheOptions, MemoryCacheClient } from '../cache-client';
4+
import { headlessSiteGroupingTemplate } from '../constants';
5+
6+
const defaultQuery = /* GraphQL */ `
7+
{
8+
search(
9+
where: { name: "_templates", value: "${headlessSiteGroupingTemplate}", operator: CONTAINS }
10+
) {
11+
results {
12+
... on Item {
13+
name
14+
hostName: field(name: "Hostname") {
15+
value
16+
}
17+
virtualFolder: field(name: "VirtualFolder") {
18+
value
19+
}
20+
language: field(name: "Language") {
21+
value
22+
}
23+
}
24+
}
25+
}
26+
}
27+
`;
28+
29+
export type SiteInfo = {
30+
name: string;
31+
hostName: string;
32+
virtualFolder: string;
33+
language: string;
34+
};
35+
36+
export type GraphQLSiteInfoServiceConfig = CacheOptions & {
37+
/**
38+
* Your Graphql endpoint
39+
*/
40+
endpoint: string;
41+
/**
42+
* The API key to use for authentication
43+
*/
44+
apiKey: string;
45+
};
46+
47+
type GraphQLSiteInfoResponse = {
48+
search: {
49+
results: GraphQLSiteInfoResult[];
50+
};
51+
};
52+
53+
type GraphQLSiteInfoResult = {
54+
name: string;
55+
hostName: {
56+
value: string;
57+
};
58+
virtualFolder: {
59+
value: string;
60+
};
61+
language: {
62+
value: string;
63+
};
64+
};
65+
66+
export class GraphQLSiteInfoService {
67+
private graphQLClient: GraphQLClient;
68+
private cache: CacheClient<SiteInfo[]>;
69+
70+
protected get query(): string {
71+
return defaultQuery;
72+
}
73+
74+
/**
75+
* Creates an instance of graphQL service to retrieve site configuration list from Sitecore
76+
* @param {GraphQLSiteInfoServiceConfig} config instance
77+
*/
78+
constructor(private config: GraphQLSiteInfoServiceConfig) {
79+
this.graphQLClient = this.getGraphQLClient();
80+
this.cache = this.getCacheClient();
81+
}
82+
83+
async fetchSiteInfo(): Promise<SiteInfo[]> {
84+
const cachedResult = this.cache.getCacheValue(this.getCacheKey());
85+
if (cachedResult) {
86+
return cachedResult;
87+
}
88+
89+
const response = await this.graphQLClient.request<GraphQLSiteInfoResponse>(this.query);
90+
const result = response?.search?.results?.reduce<SiteInfo[]>((result, current) => {
91+
result.push({
92+
name: current.name,
93+
hostName: current.hostName.value,
94+
virtualFolder: current.virtualFolder.value,
95+
language: current.language.value,
96+
});
97+
return result;
98+
}, []);
99+
this.cache.setCacheValue(this.getCacheKey(), result);
100+
return result;
101+
}
102+
103+
/**
104+
* Gets cache client implementation
105+
* Override this method if custom cache needs to be used
106+
* @returns CacheClient instance
107+
*/
108+
protected getCacheClient(): CacheClient<SiteInfo[]> {
109+
return new MemoryCacheClient<SiteInfo[]>({
110+
cacheEnabled: this.config.cacheEnabled ?? true,
111+
cacheTimeout: this.config.cacheTimeout ?? 10,
112+
});
113+
}
114+
115+
/**
116+
* Gets a GraphQL client that can make requests to the API. Uses graphql-request as the default
117+
* library for fetching graphql data (@see GraphQLRequestClient). Override this method if you
118+
* want to use something else.
119+
* @returns {GraphQLClient} implementation
120+
*/
121+
protected getGraphQLClient(): GraphQLClient {
122+
return new GraphQLRequestClient(this.config.endpoint, {
123+
apiKey: this.config.apiKey,
124+
debugger: debug.multisite,
125+
});
126+
}
127+
128+
private getCacheKey(): string {
129+
return 'siteinfo-service-cache';
130+
}
131+
}

packages/sitecore-jss/src/site/index.ts

+6
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,9 @@ export {
2424
GraphQLErrorPagesService,
2525
GraphQLErrorPagesServiceConfig,
2626
} from './graphql-error-pages-service';
27+
28+
export {
29+
SiteInfo,
30+
GraphQLSiteInfoService,
31+
GraphQLSiteInfoServiceConfig,
32+
} from './graphql-siteinfo-service';

0 commit comments

Comments
 (0)