Skip to content

Commit ab68ee8

Browse files
committed
Merge branch 'main' into release/all-0.1
2 parents 27ede3a + 45cdc58 commit ab68ee8

File tree

13 files changed

+444
-68
lines changed

13 files changed

+444
-68
lines changed

__test__/test-website/src/app/en/layout.tsx

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,37 @@ import {
2626
Row,
2727
RowA,
2828
} from '@/components/with-display-templates';
29+
import {
30+
ctArray,
31+
ctBoolean,
32+
ctContentReference,
33+
ctInteger,
34+
ctLink,
35+
ctRich,
36+
ctString,
37+
ctWithCollision,
38+
} from '@/components/with-repeated-properties';
2939
import {
3040
initContentTypeRegistry,
3141
initDisplayTemplateRegistry,
3242
} from '@optimizely/cms-sdk';
3343
import { initReactComponentRegistry } from '@optimizely/cms-sdk/react/server';
3444

35-
initContentTypeRegistry([ct1, ct2, ct3, ct6, ct7]);
45+
initContentTypeRegistry([
46+
ct1,
47+
ct2,
48+
ct3,
49+
ct6,
50+
ct7,
51+
ctString,
52+
ctBoolean,
53+
ctInteger,
54+
ctRich,
55+
ctContentReference,
56+
ctArray,
57+
ctWithCollision,
58+
ctLink,
59+
]);
3660
initDisplayTemplateRegistry([dt1, dt2, dt3, dt4, dt5, dt6]);
3761
initReactComponentRegistry({
3862
resolver: {
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import RootLayout from '@/app/en/layout';
2+
3+
export default RootLayout;
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { GraphClient, GraphErrors } from '@optimizely/cms-sdk';
2+
import React from 'react';
3+
4+
type Props = {
5+
params: Promise<{
6+
slug: string[];
7+
}>;
8+
};
9+
10+
function handleGraphErrors(err: unknown): never {
11+
if (err instanceof GraphErrors.GraphResponseError) {
12+
console.log('Error message:', err.message);
13+
console.log('Query:', err.request.query);
14+
console.log('Variables:', err.request.variables);
15+
}
16+
if (err instanceof GraphErrors.GraphContentResponseError) {
17+
console.log('Detailed errors: ', err.errors);
18+
}
19+
20+
throw err;
21+
}
22+
23+
export default async function Page({ params }: Props) {
24+
const { slug } = await params;
25+
const path = `/en/${slug.join('/')}/`;
26+
27+
const client = new GraphClient(process.env.OPTIMIZELY_GRAPH_SINGLE_KEY!, {
28+
graphUrl: process.env.OPTIMIZELY_GRAPH_GATEWAY,
29+
});
30+
31+
const items = await client.getContentByPath(path).catch(handleGraphErrors);
32+
33+
return (
34+
<>
35+
<h1>Query result</h1>
36+
<pre>{JSON.stringify(items[0], null, 2)}</pre>
37+
</>
38+
);
39+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { contentType } from '@optimizely/cms-sdk';
2+
3+
export const ctString = contentType({
4+
baseType: '_component',
5+
key: 'ct_string',
6+
properties: { p1: { type: 'string' } },
7+
});
8+
export const ctBoolean = contentType({
9+
baseType: '_component',
10+
key: 'ct_boolean',
11+
properties: { p1: { type: 'boolean' } },
12+
});
13+
export const ctInteger = contentType({
14+
baseType: '_component',
15+
key: 'ct_integer',
16+
properties: { p1: { type: 'integer' } },
17+
});
18+
export const ctRich = contentType({
19+
baseType: '_component',
20+
key: 'ct_rich',
21+
properties: { p1: { type: 'richText' } },
22+
});
23+
export const ctLink = contentType({
24+
baseType: '_component',
25+
key: 'ct_link',
26+
properties: { p1: { type: 'link' } },
27+
});
28+
export const ctContentReference = contentType({
29+
baseType: '_component',
30+
key: 'ct_contentreference',
31+
properties: { p1: { type: 'contentReference' } },
32+
});
33+
export const ctArray = contentType({
34+
baseType: '_component',
35+
key: 'ct_array',
36+
properties: { p1: { type: 'array', items: { type: 'string' } } },
37+
});
38+
export const ctWithCollision = contentType({
39+
baseType: '_page',
40+
key: 'ct_with_collision',
41+
properties: {
42+
collision: {
43+
type: 'content',
44+
allowedTypes: [
45+
ctString,
46+
ctBoolean,
47+
ctInteger,
48+
ctRich,
49+
ctLink,
50+
ctContentReference,
51+
ctArray,
52+
],
53+
},
54+
p2: {
55+
type: 'array',
56+
items: {
57+
type: 'content',
58+
allowedTypes: [
59+
ctString,
60+
ctBoolean,
61+
ctInteger,
62+
ctRich,
63+
ctLink,
64+
ctContentReference,
65+
ctArray,
66+
],
67+
},
68+
},
69+
},
70+
});
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { buildConfig } from '@optimizely/cms-sdk';
22

33
export default buildConfig({
4-
components: ['./src/components/with-display-templates.tsx'],
4+
components: [
5+
'./src/components/with-display-templates.tsx',
6+
'./src/components/with-repeated-properties.tsx',
7+
],
58
});

packages/optimizely-cms-sdk/src/graph/__test__/convertProperty.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ describe('createFragment > Fragment threshold warning', () => {
6868
expect(result).toBeInstanceOf(Array);
6969
expect(warnSpy).toHaveBeenCalledOnce();
7070
expect(warnSpy.mock.calls[0][0]).toMatch(
71-
String.raw`generated 105 inner fragments`
71+
String.raw`generated 107 inner fragments`
7272
);
7373
expect(warnSpy.mock.calls[0][0]).toMatch(
7474
/Excessive fragment depth may breach GraphQL limits or degrade performance./

packages/optimizely-cms-sdk/src/graph/__test__/createQuery.test.ts

Lines changed: 115 additions & 42 deletions
Large diffs are not rendered by default.

packages/optimizely-cms-sdk/src/graph/__test__/createQueryExperiences.test.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@ describe('createFragment()', () => {
1616
expect(result).toMatchInlineSnapshot(`
1717
[
1818
"fragment MediaMetadata on MediaMetadata { mimeType thumbnail content }",
19+
"fragment ItemMetadata on ItemMetadata { changeset displayOption }",
20+
"fragment InstanceMetadata on InstanceMetadata { changeset locales expired container owner routeSegment lastModifiedBy path createdBy }",
1921
"fragment ContentUrl on ContentUrl { type default hierarchical internal graph base }",
20-
"fragment IContentMetadata on IContentMetadata { key locale fallbackForLocale version displayName url {...ContentUrl} types published status created lastModified sortOrder variation ...MediaMetadata }",
22+
"fragment IContentMetadata on IContentMetadata { key locale fallbackForLocale version displayName url {...ContentUrl} types published status created lastModified sortOrder variation ...MediaMetadata ...ItemMetadata ...InstanceMetadata }",
2123
"fragment _IContent on _IContent { _id _metadata {...IContentMetadata} }",
2224
"fragment _IExperience on _IExperience { composition {...ICompositionNode }}",
2325
"fragment ICompositionNode on ICompositionNode { __typename key type nodeType displayName displayTemplateKey displaySettings {key value} ...on CompositionStructureNode { nodes @recursive } ...on CompositionComponentNode { nodeType component { ..._IComponent } } }",
24-
"fragment CallToAction on CallToAction { __typename label link ..._IContent }",
25-
"fragment ExpSection on ExpSection { __typename heading ..._IContent }",
26+
"fragment CallToAction on CallToAction { __typename CallToAction__label:label CallToAction__link:link ..._IContent }",
27+
"fragment ExpSection on ExpSection { __typename ExpSection__heading:heading ..._IContent }",
2628
"fragment _IComponent on _IComponent { __typename ...CallToAction ...ExpSection }",
2729
"fragment MyExperience on MyExperience { __typename ..._IContent ..._IExperience }",
2830
]
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { describe, expect, test } from 'vitest';
2+
import { removeTypePrefix } from '../index.js';
3+
4+
describe('removeTypePrefix()', () => {
5+
test('basic functionality', () => {
6+
const input = {
7+
__typename: 'T',
8+
T__p1: 'p1',
9+
T__p2: 42,
10+
T__p3: ['p3', 32],
11+
T__p4: { nested: 'p4' },
12+
T__p5: { nested: ['p5'] },
13+
ShouldBeKept__p6: 'p6',
14+
};
15+
const expected = {
16+
__typename: 'T',
17+
p1: 'p1',
18+
p2: 42,
19+
p3: ['p3', 32],
20+
p4: { nested: 'p4' },
21+
p5: { nested: ['p5'] },
22+
ShouldBeKept__p6: 'p6',
23+
};
24+
expect(removeTypePrefix(input)).toStrictEqual(expected);
25+
});
26+
27+
test('should remove prefixes only in the same level', () => {
28+
const input = {
29+
__typename: 'T',
30+
T__p1: { T_shouldBeKept: 'shouldBeKept' },
31+
};
32+
const expected = {
33+
__typename: 'T',
34+
p1: { T_shouldBeKept: 'shouldBeKept' },
35+
};
36+
expect(removeTypePrefix(input)).toStrictEqual(expected);
37+
});
38+
39+
test('should work for nested objects', () => {
40+
const input = {
41+
__typename: 'T',
42+
T__p1: {
43+
__typename: 'U',
44+
U__p1: 'p1',
45+
U__p2: {
46+
__typename: 'V',
47+
V__p1: 'p1',
48+
},
49+
},
50+
T__p2: [{ __typename: 'U', U__p1: 'p1' }],
51+
};
52+
const expected = {
53+
__typename: 'T',
54+
p1: {
55+
__typename: 'U',
56+
p1: 'p1',
57+
p2: {
58+
__typename: 'V',
59+
p1: 'p1',
60+
},
61+
},
62+
p2: [{ __typename: 'U', p1: 'p1' }],
63+
};
64+
expect(removeTypePrefix(input)).toStrictEqual(expected);
65+
});
66+
67+
test('should not do anything if __typename is not found', () => {
68+
const input = {
69+
T__p1: 'hello',
70+
T__p2: 42,
71+
T__p3: ['hello', 32],
72+
T__p4: { nested: 'nested' },
73+
T__p5: { nested: ['hello'] },
74+
};
75+
76+
expect(removeTypePrefix(input)).toStrictEqual(input);
77+
});
78+
});

packages/optimizely-cms-sdk/src/graph/createQuery.ts

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,16 @@ function convertProperty(
6969
name: string,
7070
property: AnyProperty,
7171
rootName: string,
72+
suffix: string,
7273
visited: Set<string>
7374
): { fields: string[]; extraFragments: string[] } {
74-
const result = convertPropertyField(name, property, rootName, visited);
75+
const result = convertPropertyField(
76+
name,
77+
property,
78+
rootName,
79+
suffix,
80+
visited
81+
);
7582

7683
// logs warnings if the fragment generation causes potential issues
7784
const warningMessage = checkTypeConstraintIssues(rootName, property, result);
@@ -95,17 +102,19 @@ function convertPropertyField(
95102
name: string,
96103
property: AnyProperty,
97104
rootName: string,
105+
suffix: string,
98106
visited: Set<string>
99107
): { fields: string[]; extraFragments: string[] } {
100108
const fields: string[] = [];
101109
const subfields: string[] = [];
102110
const extraFragments: string[] = [];
111+
const nameInFragment = `${rootName}${suffix}__${name}:${name}`;
103112

104113
if (property.type === 'component') {
105114
const key = property.contentType.key;
106115
const fragmentName = `${key}Property`;
107-
extraFragments.push(...createFragment(key, visited, 'Property'));
108-
fields.push(`${name} { ...${fragmentName} }`);
116+
extraFragments.push(...createFragment(key, visited, 'Property', false));
117+
fields.push(`${nameInFragment} { ...${fragmentName} }`);
109118
} else if (property.type === 'content') {
110119
const allowed = resolveAllowedTypes(
111120
property.allowedTypes,
@@ -132,24 +141,24 @@ function convertPropertyField(
132141
}
133142

134143
const uniqueSubfields = ['__typename', ...new Set(subfields)].join(' '); // remove duplicates
135-
fields.push(`${name} { ${uniqueSubfields} }`);
144+
fields.push(`${nameInFragment} { ${uniqueSubfields} }`);
136145
} else if (property.type === 'richText') {
137-
fields.push(`${name} { html, json }`);
146+
fields.push(`${nameInFragment} { html, json }`);
138147
} else if (property.type === 'url') {
139148
extraFragments.push(CONTENT_URL_FRAGMENT);
140-
fields.push(`${name} { ...ContentUrl }`);
149+
fields.push(`${nameInFragment} { ...ContentUrl }`);
141150
} else if (property.type === 'link') {
142151
extraFragments.push(CONTENT_URL_FRAGMENT);
143-
fields.push(`${name} { text title target url { ...ContentUrl }}`);
152+
fields.push(`${nameInFragment} { text title target url { ...ContentUrl }}`);
144153
} else if (property.type === 'contentReference') {
145154
extraFragments.push(CONTENT_URL_FRAGMENT);
146-
fields.push(`${name} { key url { ...ContentUrl }}`);
155+
fields.push(`${nameInFragment} { key url { ...ContentUrl }}`);
147156
} else if (property.type === 'array') {
148-
const f = convertProperty(name, property.items, rootName, visited);
157+
const f = convertProperty(name, property.items, rootName, suffix, visited);
149158
fields.push(...f.fields);
150159
extraFragments.push(...f.extraFragments);
151160
} else {
152-
fields.push(name);
161+
fields.push(nameInFragment);
153162
}
154163

155164
return {
@@ -202,7 +211,8 @@ function createExperienceFragments(visited: Set<string>): string[] {
202211
export function createFragment(
203212
contentTypeName: string,
204213
visited: Set<string> = new Set(), // shared across recursion
205-
suffix: string = ''
214+
suffix: string = '',
215+
includeBaseFragments: boolean = true
206216
): string[] {
207217
const fragmentName = `${contentTypeName}${suffix}`;
208218
if (visited.has(fragmentName)) return []; // cyclic ref guard
@@ -235,16 +245,19 @@ export function createFragment(
235245
propKey,
236246
prop,
237247
contentTypeName,
248+
suffix,
238249
visited
239250
);
240251
fields.push(...f);
241252
extraFragments.push(...e);
242253
}
243254

244255
// Add fragments for the base type of the user-defined content type
245-
const baseFragments = buildBaseTypeFragments();
246-
extraFragments.unshift(...baseFragments.extraFragments); // maintain order
247-
fields.push(...baseFragments.fields);
256+
if (includeBaseFragments) {
257+
const baseFragments = buildBaseTypeFragments();
258+
extraFragments.unshift(...baseFragments.extraFragments); // maintain order
259+
fields.push(...baseFragments.fields);
260+
}
248261

249262
if (ct.baseType === '_experience') {
250263
fields.push('..._IExperience');

0 commit comments

Comments
 (0)