Skip to content

Commit 33664b7

Browse files
clyde-builderioClyde Mendonca
andauthored
fix[gen1][core]: ENG-9774 properly handle query when $-mongo-operator is present for content API (BuilderIO#4102)
## Description `query` with `$or` is not part of Content API when we use `builder.get()` but Query API includes `query` with `$or` It works on Gen-2 SDK which also uses Content API **JIRA Ticket** https://builder-io.atlassian.net/browse/ENG-9774 **Steps to test** 1. Clone repo https://github.com/BuilderIO/oncall-repro-cases. 2. Run `cd eng-9774-gen1-vite-app`. 3. change the `package.json` to `link://` the `@builder.io/react` and `@builder.io/sdk` dependencies to this branch. 4. Run `yarn`. 5. Run `yarn dev`. **Loom** https://www.loom.com/share/051b8af3d9344c218d678a20d28b0fc4 --------- Co-authored-by: Clyde Mendonca <[email protected]>
1 parent 23c0a4b commit 33664b7

File tree

4 files changed

+125
-5
lines changed

4 files changed

+125
-5
lines changed

.changeset/silent-wombats-drum.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@builder.io/sdk": patch
3+
"@builder.io/react": patch
4+
---
5+
6+
Fix: Corrected the conversion of query-objects with $-mongo-operators which are passed to builder.get() with apiEndpoint is "content"

packages/core/src/builder.class.test.ts

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1017,7 +1017,112 @@ describe('flushGetContentQueue', () => {
10171017

10181018
expect(builder['makeFetchApiCall']).toBeCalledTimes(1);
10191019
expect(builder['makeFetchApiCall']).toBeCalledWith(
1020-
`https://cdn.builder.io/api/v3/content/${expectedModel}?omit=data.blocks&apiKey=${API_KEY}&fields=data&format=${expectedFormat}&userAttributes=%7B%22urlPath%22%3A%22%2F%22%2C%22host%22%3A%22localhost%22%2C%22device%22%3A%22desktop%22%7D&includeRefs=true&limit=10&model=%22${expectedModel}%22&entry=%22${expectedEntryId}%22&query.id=${expectedEntryId}`,
1020+
`https://cdn.builder.io/api/v3/content/${expectedModel}?omit=data.blocks&apiKey=${API_KEY}&fields=data&format=${expectedFormat}&userAttributes=%7B%22urlPath%22%3A%22%2F%22%2C%22host%22%3A%22localhost%22%2C%22device%22%3A%22desktop%22%7D&includeRefs=true&limit=10&model=%22${expectedModel}%22&entry=%22123%22&query.id=%22${expectedEntryId}%22`,
1021+
{ headers: { Authorization: `Bearer ${AUTH_TOKEN}` } }
1022+
);
1023+
});
1024+
1025+
test('hits content url with query as the same object as the one passed in options.query if query contains $ mongo-operator', async () => {
1026+
const expectedModel = 'symbol';
1027+
const expectedFormat = 'email';
1028+
const expectedEntryId = '123';
1029+
1030+
builder.apiEndpoint = 'content';
1031+
const result = await builder['flushGetContentQueue'](true, [
1032+
{
1033+
model: expectedModel,
1034+
format: expectedFormat,
1035+
key: expectedModel,
1036+
omit: OMIT,
1037+
fields: 'data',
1038+
limit: 10,
1039+
entry: expectedEntryId,
1040+
query: {
1041+
data: {
1042+
id: '123',
1043+
},
1044+
$or: [
1045+
{
1046+
data: {
1047+
sourceUrl: '/c/docs/develop',
1048+
},
1049+
},
1050+
{
1051+
data: {
1052+
sourceUrl: 'https://www.builder.io' + '/c/docs/develop',
1053+
},
1054+
},
1055+
],
1056+
},
1057+
},
1058+
]);
1059+
1060+
expect(builder['makeFetchApiCall']).toBeCalledTimes(1);
1061+
expect(builder['makeFetchApiCall']).toBeCalledWith(
1062+
`https://cdn.builder.io/api/v3/content/symbol?omit=data.blocks&apiKey=25608a566fbb654ea959c1b1729e370d&fields=data&format=email&userAttributes=%7B%22urlPath%22%3A%22%2F%22%2C%22host%22%3A%22localhost%22%2C%22device%22%3A%22desktop%22%7D&includeRefs=true&limit=10&model=%22symbol%22&entry=%22123%22&query=%7B%22data%22%3A%7B%22id%22%3A%22123%22%7D%2C%22%24or%22%3A%5B%7B%22data%22%3A%7B%22sourceUrl%22%3A%22%2Fc%2Fdocs%2Fdevelop%22%7D%7D%2C%7B%22data%22%3A%7B%22sourceUrl%22%3A%22https%3A%2F%2Fwww.builder.io%2Fc%2Fdocs%2Fdevelop%22%7D%7D%5D%7D`,
1063+
{ headers: { Authorization: `Bearer ${AUTH_TOKEN}` } }
1064+
);
1065+
});
1066+
1067+
test('hits content url with query as the same object as the one passed in options.query if query contains nested $ mongo-operator', async () => {
1068+
const expectedModel = 'symbol';
1069+
const expectedFormat = 'email';
1070+
const expectedEntryId = '123';
1071+
1072+
builder.apiEndpoint = 'content';
1073+
const result = await builder['flushGetContentQueue'](true, [
1074+
{
1075+
model: expectedModel,
1076+
format: expectedFormat,
1077+
key: expectedModel,
1078+
omit: OMIT,
1079+
fields: 'data',
1080+
limit: 10,
1081+
entry: expectedEntryId,
1082+
query: {
1083+
data: {
1084+
sourceUrl: { $eq: '/c/docs/develop' },
1085+
},
1086+
},
1087+
},
1088+
]);
1089+
1090+
expect(builder['makeFetchApiCall']).toBeCalledTimes(1);
1091+
expect(builder['makeFetchApiCall']).toBeCalledWith(
1092+
`https://cdn.builder.io/api/v3/content/symbol?omit=data.blocks&apiKey=25608a566fbb654ea959c1b1729e370d&fields=data&format=email&userAttributes=%7B%22urlPath%22%3A%22%2F%22%2C%22host%22%3A%22localhost%22%2C%22device%22%3A%22desktop%22%7D&includeRefs=true&limit=10&model=%22symbol%22&entry=%22123%22&query.data.sourceUrl=%7B%22%24eq%22%3A%22%2Fc%2Fdocs%2Fdevelop%22%7D`,
1093+
{ headers: { Authorization: `Bearer ${AUTH_TOKEN}` } }
1094+
);
1095+
});
1096+
1097+
test('hits content url with query as the flattened object as the one passed in options.query if query does not contain $ mongo-operator', async () => {
1098+
const expectedModel = 'symbol';
1099+
const expectedFormat = 'email';
1100+
const expectedEntryId = '123';
1101+
1102+
builder.apiEndpoint = 'content';
1103+
const result = await builder['flushGetContentQueue'](true, [
1104+
{
1105+
model: expectedModel,
1106+
format: expectedFormat,
1107+
key: expectedModel,
1108+
omit: OMIT,
1109+
fields: 'data',
1110+
limit: 10,
1111+
entry: expectedEntryId,
1112+
query: {
1113+
data: {
1114+
sourceUrl: '/c/docs/develop',
1115+
},
1116+
name: {
1117+
fullName: 'John Doe',
1118+
},
1119+
},
1120+
},
1121+
]);
1122+
1123+
expect(builder['makeFetchApiCall']).toBeCalledTimes(1);
1124+
expect(builder['makeFetchApiCall']).toBeCalledWith(
1125+
`https://cdn.builder.io/api/v3/content/symbol?omit=data.blocks&apiKey=25608a566fbb654ea959c1b1729e370d&fields=data&format=email&userAttributes=%7B%22urlPath%22%3A%22%2F%22%2C%22host%22%3A%22localhost%22%2C%22device%22%3A%22desktop%22%7D&includeRefs=true&limit=10&model=%22symbol%22&entry=%22123%22&query.data.sourceUrl=%22%2Fc%2Fdocs%2Fdevelop%22&query.name.fullName=%22John%20Doe%22`,
10211126
{ headers: { Authorization: `Bearer ${AUTH_TOKEN}` } }
10221127
);
10231128
});

packages/core/src/builder.class.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2502,6 +2502,13 @@ export class Builder {
25022502
return getFetch()(url, this.addSdkHeaders(requestOptions));
25032503
}
25042504

2505+
/**
2506+
* Flatten a nested MongoDB query object into a flat object with dot-separated keys.
2507+
* $ keys are not flattened and are left as is.
2508+
*
2509+
* { foo: { bar: { $gt: 5 }}} -> { 'foo.bar': { '$gt': 5 }}
2510+
* { foo: {'bar.id': { $elemMatch: { 'baz.id': { $in: ['abc', 'bcd'] }}}}} -> { 'foo.bar.id': { '$elemMatch': { 'baz.id': { '$in': ['abc', 'bcd'] }}}}
2511+
*/
25052512
private flattenMongoQuery(obj: any, _current?: any, _res: any = {}): { [key: string]: string } {
25062513
for (const key in obj) {
25072514
const value = obj[key];
@@ -2709,11 +2716,12 @@ export class Builder {
27092716

27102717
if (this.apiEndpoint === 'content') {
27112718
if (queue[0].query) {
2712-
const flattened = this.flattenMongoQuery({ query: queue[0].query });
2719+
delete queryParams.query;
2720+
const objectToFlatten = { query: queue[0].query };
2721+
const flattened = this.flattenMongoQuery(objectToFlatten);
27132722
for (const key in flattened) {
2714-
queryParams[key] = flattened[key];
2723+
queryParams[key] = JSON.stringify(flattened[key]);
27152724
}
2716-
delete queryParams.query;
27172725
}
27182726
}
27192727

packages/sdks-tests/src/e2e-tests/hit-content-api.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ test.describe('Get Content', () => {
7171
await expect(x).toBeGreaterThanOrEqual(2);
7272

7373
urls.forEach(url => {
74-
expect(url).toContain('query.id=29ab534d62c4406c8500e1cbfa609537');
74+
const urlParams = new URL(url).searchParams;
75+
expect(urlParams.get('query.id')).toBe(`"29ab534d62c4406c8500e1cbfa609537"`);
7576
});
7677

7778
// Check for new SDK headers

0 commit comments

Comments
 (0)