Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: [ENG-8644] overriding omit in fetchOneEntry to be empty string #3975

Merged
merged 22 commits into from
Mar 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .changeset/slimy-islands-end.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
"@builder.io/sdk": patch
"@builder.io/react": patch
"@builder.io/sdk-angular": patch
"@builder.io/sdk-react-nextjs": patch
"@builder.io/sdk-qwik": patch
"@builder.io/sdk-react": patch
"@builder.io/sdk-react-native": patch
"@builder.io/sdk-solid": patch
"@builder.io/sdk-svelte": patch
"@builder.io/sdk-vue": patch
---

Fix: correctly set default value for `omit` field as `meta.componentsUsed` in Content API calls and preserve empty string
2 changes: 1 addition & 1 deletion packages/core/src/builder.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2503,7 +2503,7 @@ export class Builder {

const queryParams: ParamsMap = {
// TODO: way to force a request to be in a separate queue. or just lower queue limit to be 1 by default
omit: queue[0].omit || 'meta.componentsUsed',
omit: queue[0].omit ?? 'meta.componentsUsed',
apiKey: this.apiKey,
...queue[0].options,
...this.queryOptions,
Expand Down
93 changes: 92 additions & 1 deletion packages/sdks-tests/src/e2e-tests/hit-content-api.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ test.describe('Get Content', () => {
expect(await req!.postDataJSON()).toEqual({ test: 'test' });
expect(req!.method()).toBe('POST');
});
test('fetch symbol with query.id', async ({ page, sdk }) => {
test('fetch symbol with query.id', async ({ page, sdk, packageName }) => {
test.skip(!excludeGen1(sdk));
test.skip(packageName !== 'gen1-next14-pages');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

curious why this extra skip was added?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so only gen1-next14-pages is the one where API's are not getting called. That's the reason I added this skip. Please do let me know if otherwise

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused since the test has been running all this time without being skipped for Next14 Pages and passing.

I think so only gen1-next14-pages is the one where API's are not getting called

Ah. Not in this case. This test is checking content that has a symbol, BUT where the symbol's full content was not downloaded in the initial JSON:

'/get-content-with-symbol': { content: CONTENT_WITHOUT_SYMBOLS, target: 'gen1' },

// remove both symbol content from content
delete CONTENT_CLONE.data.blocks[1].component.options.symbol.content;
delete CONTENT_CLONE.data.blocks[2].component.options.symbol.content;
return { CONTENT_WITHOUT_SYMBOLS: CONTENT_CLONE, FIRST_SYMBOL_CONTENT, SECOND_SYMBOL_CONTENT };
};

So in this scenario, we're testing that the Symbol block correctly fetches its own content when it is not already present

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @samijaber
Could you clarify what you mean by the Symbol block fetching its own content when it's not already present? I’m a bit unclear on that part.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when data returned from Content API through fetchOneEntry includes a symbol, there are two ways it can play out:

  • the Content JSON contains the entire symbol reference, in which case the Symbol can render immediately
  • the Content JSON only contains the Symbol's metadata (model name, entry ID). In this case, the <Symbol> component ends up fetching its own content. See
    setContent() {
    if (state.contentToUse) return;
    fetchSymbolContent({
    symbol: props.symbol,
    builderContextValue: props.builderContext.value,
    }).then((newContent) => {
    if (newContent) {
    state.contentToUse = newContent;
    }
    });
    },
    (gen1 does something similar but with more steps and layers of abstraction)

The test where you added test.skip(packageName !== 'gen1-next14-pages'); is the second case: the Content JSON is missing the Symbol reference, so we are testing that the API calls to https://cdn.builder.io/api/v3/content/symbol are made correctly.

only gen1-next14-pages is the one where API's are not getting called

Essentially, I don't believe ^this statement is accurate: those API calls should be made in every gen1 test server

Copy link
Contributor Author

@yash-builder yash-builder Mar 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Essentially, I don't believe ^this statement is accurate: those API calls should be made in every gen1 test server

You're right—but after extensive debugging, I found that the API call doesn't happen the first time; it only triggers after a reload. Let me clarify this further.

Attaching loom for the reference: Loom


let x = 0;
let headers;
Expand Down Expand Up @@ -78,4 +79,94 @@ test.describe('Get Content', () => {
expect(headers?.['x-builder-sdk-gen']).toBe(getSdkGeneration(sdk));
expect(headers?.['x-builder-sdk-version']).toMatch(/\d+\.\d+\.\d+/); // Check for semver format
});

test('should NOT omit componentsUsed when omit parameter is explicitly set to empty string', async ({
page,
sdk,
}) => {
test.skip(!excludeGen1(sdk));

let builderRequestPromise: Promise<string> | undefined = undefined;
let requestUrl: string | undefined;

const builderApiRegex = /https:\/\/cdn\.builder\.io\/api\/v3\//;

await page.goto('/get-content-with-omit');

builderRequestPromise = new Promise<string>(resolve => {
page.on('request', request => {
const url = request.url();
if (builderApiRegex.test(url)) {
requestUrl = url;
resolve(url);
}
});
});

await page.reload({ waitUntil: 'networkidle' });
await builderRequestPromise;

expect(requestUrl).toBeDefined();
expect(requestUrl!).not.toContain('omit=meta.componentsUsed');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

confused by this test: title says should include componentsUsed by default when omit is empty string but here it's checking the opposite: .not.toContain('omit=meta.componentsUsed');

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh my bad....the test should revolve around both the cases where it should be present and not be present based o the given scenario

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah ok, could you update it to test both 🙏🏽

expect(requestUrl!.includes('omit=')).toBeTruthy();
expect(new URL(requestUrl!).searchParams.get('omit')).toBe('');
});

test('should omit the specified field when omit parameter has a defined value for gen1', async ({
page,
sdk,
}) => {
test.skip(!excludeGen1(sdk));

let builderRequestPromise: Promise<string> | undefined = undefined;
let requestUrl: string | undefined;

const builderApiRegex = /https:\/\/cdn\.builder\.io\/api\/v3\//;

await page.goto('/get-content-with-omit-name');

builderRequestPromise = new Promise<string>(resolve => {
page.on('request', request => {
const url = request.url();
if (builderApiRegex.test(url)) {
requestUrl = url;
resolve(url);
}
});
});

await page.reload({ waitUntil: 'networkidle' });
await builderRequestPromise;

expect(requestUrl).toBeDefined();
expect(requestUrl!).toContain('omit=name');
expect(new URL(requestUrl!).searchParams.get('omit')).toBe('name');
});

test('should use default omit value when omit parameter is undefined', async ({ page, sdk }) => {
test.skip(!excludeGen1(sdk));

let builderRequestPromise: Promise<string> | undefined = undefined;
let requestUrl: string | undefined;

const builderApiRegex = /https:\/\/cdn\.builder\.io\/api\/v3\//;

await page.goto('/get-content-default');

builderRequestPromise = new Promise<string>(resolve => {
page.on('request', request => {
const url = request.url();
if (builderApiRegex.test(url)) {
requestUrl = url;
resolve(url);
}
});
});

await page.reload({ waitUntil: 'networkidle' });
await builderRequestPromise;

const omitValue = new URL(requestUrl!).searchParams.get('omit');
expect(omitValue).toBe('meta.componentsUsed');
});
});
17 changes: 17 additions & 0 deletions packages/sdks-tests/src/specs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,23 @@ export const getProps = async (args: {
apiEndpoint: 'content',
};
break;
case '/get-content-with-omit':
extraProps = {
apiEndpoint: 'content',
omit: '',
};
break;
case '/get-content-with-omit-name':
extraProps = {
apiEndpoint: 'content',
omit: 'name',
};
break;
case '/get-content-default':
extraProps = {
apiEndpoint: 'content',
};
break;
case '/get-query':
extraProps = {
options: { apiEndpoint: 'query', format: 'html', model: 'abcd', key: 'abcd' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,6 @@ exports[`Component Registration and Serialization > serializeComponentInfo handl
}
`;

exports[`Component Registration and Serialization > serializeComponentInfo handles async arrow functions correctly 1`] = `
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why was this removed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh didn't notice that.
Thanks for the heads up. Let me add it 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm....It seems this was added twice that's the reason it is being removed

{
"inputs": [
{
"name": "testInput",
"onChange": "return (async (value) => {
return value.toUpperCase();
}).apply(this, arguments)",
"type": "string",
},
],
"name": "TestComponent",
}
`;

exports[`Component Registration and Serialization > serializeComponentInfo handles async arrow functions without parenthesis correctly 1`] = `
{
"inputs": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,10 @@ exports[`Generate Content URL > generate content url with omit, fields, offset,

exports[`Generate Content URL > generates the proper value for a simple query 1`] = `"https://cdn.builder.io/api/v3/content/page?apiKey=YJIGb4i01jvw0SRdL5Bt&limit=30&noTraverse=true&includeRefs=true&omit=meta.componentsUsed&query.id=%22c1b81bab59704599b997574eb0736def%22"`;

exports[`Generate Content URL > handles defined as omit parameter values 1`] = `"https://cdn.builder.io/api/v3/content/page?apiKey=YJIGb4i01jvw0SRdL5Bt&limit=30&noTraverse=true&includeRefs=true&omit=name"`;

exports[`Generate Content URL > handles empty string as omit parameter values 1`] = `"https://cdn.builder.io/api/v3/content/page?apiKey=YJIGb4i01jvw0SRdL5Bt&limit=30&noTraverse=true&includeRefs=true&omit="`;

exports[`Generate Content URL > handles undefined as omit parameter values 1`] = `"https://cdn.builder.io/api/v3/content/page?apiKey=YJIGb4i01jvw0SRdL5Bt&limit=30&noTraverse=true&includeRefs=true&omit=meta.componentsUsed"`;

exports[`Generate Content URL > preserves both userAttributes.locale and top-level locale when both provided 1`] = `"https://cdn.builder.io/api/v3/content/page?apiKey=YJIGb4i01jvw0SRdL5Bt&limit=30&noTraverse=true&includeRefs=true&locale=en-US&omit=meta.componentsUsed&userAttributes=%7B%22locale%22%3A%22es-ES%22%2C%22foo%22%3A%22bar%22%7D"`;
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,33 @@ describe('Generate Content URL', () => {
expect(output).toMatchSnapshot();
});

test('handles empty string as omit parameter values', () => {
const outputEmptyString = generateContentUrl({
apiKey: testKey,
model: testModel,
omit: '',
});
expect(outputEmptyString).toMatchSnapshot();
});

test('handles undefined as omit parameter values', () => {
const outputUndefined = generateContentUrl({
apiKey: testKey,
model: testModel,
omit: undefined,
});
expect(outputUndefined).toMatchSnapshot();
});

test('handles defined as omit parameter values', () => {
const outputDefined = generateContentUrl({
apiKey: testKey,
model: testModel,
omit: 'name',
});
expect(outputDefined).toMatchSnapshot();
});

test('generate content url with apiVersion as default', () => {
const output = generateContentUrl({
apiKey: testKey,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export const generateContentUrl = (options: GetContentOptions): URL => {
}
if (enrich) url.searchParams.set('enrich', String(enrich));

url.searchParams.set('omit', omit || 'meta.componentsUsed');
url.searchParams.set('omit', omit ?? 'meta.componentsUsed');

if (fields) {
url.searchParams.set('fields', fields);
Expand Down
Loading