Skip to content

Commit

Permalink
fix[gen1][core] ENG-7507 added class field apiEndpoint$ (#3748)
Browse files Browse the repository at this point in the history
## Description

API Endpoint should be set at the class level and not at the options
level

**Jira ticket**
https://builder-io.atlassian.net/browse/ENG-7507

**Loom**
https://www.loom.com/share/ffe8bd20e576420f89606a62387f05f5

---------

Co-authored-by: Clyde Mendonca <clydemendonca@Clydes-MacBook-Pro.local>
Co-authored-by: Sami Jaber <me@sami.website>
  • Loading branch information
3 people authored Dec 10, 2024
1 parent 3e9455b commit 56f9461
Show file tree
Hide file tree
Showing 9 changed files with 136 additions and 17 deletions.
7 changes: 7 additions & 0 deletions .changeset/lazy-rocks-explain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@builder.io/sdk': major
'@builder.io/react': major
---

- Adds `apiEndpoint` prop to `builder` instance with permitted values being `'content'` or `'query'`. It dictates which API endpoint is used for fetching Builder content
- Breaking Change 🧨: Removes `apiEndpoint` argument from `builder.get()`, `builder.getAll()`, and the `options` prop of `<BuilderContent>` component. NOTE: this argument was not working as expected.
8 changes: 4 additions & 4 deletions packages/core/src/builder.class.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -536,9 +536,9 @@ describe('flushGetContentQueue', () => {
test("hits content url when apiEndpoint is 'content' and format is 'html'", async () => {
const expectedFormat = 'html';

builder.apiEndpoint = 'content';
const result = await builder['flushGetContentQueue'](true, [
{
apiEndpoint: 'content',
model: MODEL,
format: expectedFormat,
key: MODEL,
Expand Down Expand Up @@ -602,9 +602,9 @@ describe('flushGetContentQueue', () => {
test("hits content url when apiEndpoint is 'content' and format is 'amp'", async () => {
const expectedFormat = 'amp';

builder.apiEndpoint = 'content';
const result = await builder['flushGetContentQueue'](true, [
{
apiEndpoint: 'content',
model: MODEL,
format: expectedFormat,
key: MODEL,
Expand Down Expand Up @@ -696,9 +696,9 @@ describe('flushGetContentQueue', () => {
test("hits content url when apiEndpoint is 'content' and format is 'email'", async () => {
const expectedFormat = 'email';

builder.apiEndpoint = 'content';
const result = await builder['flushGetContentQueue'](true, [
{
apiEndpoint: 'content',
model: MODEL,
format: expectedFormat,
key: MODEL,
Expand Down Expand Up @@ -734,9 +734,9 @@ describe('flushGetContentQueue', () => {
test("hits content url when apiEndpoint is 'content' and format is 'email' and url is passed instead of userAttributes", async () => {
const expectedFormat = 'email';

builder.apiEndpoint = 'content';
const result = await builder['flushGetContentQueue'](true, [
{
apiEndpoint: 'content',
model: MODEL,
format: expectedFormat,
key: MODEL,
Expand Down
33 changes: 21 additions & 12 deletions packages/core/src/builder.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,12 +275,6 @@ type AllowEnrich =
| { apiVersion?: never; enrich?: boolean };

export type GetContentOptions = AllowEnrich & {
/**
* Dictates which API endpoint is used when fetching content. Allows `'content'` and `'query'`.
* Defaults to `'query'`.
*/
apiEndpoint?: 'content' | 'query';

/**
* Optional fetch options to be passed as the second argument to the `fetch` function.
*/
Expand Down Expand Up @@ -1298,6 +1292,21 @@ export class Builder {
}
}

get apiEndpoint() {
return this.apiEndpoint$.value;
}

set apiEndpoint(apiEndpoint: 'content' | 'query') {
if (this.apiEndpoint !== apiEndpoint) {
this.apiEndpoint$.next(apiEndpoint);
}
}

/**
* Dictates which API endpoint is used when fetching content. Allows `'content'` and `'query'`.
* Defaults to `'query'`.
*/
private apiEndpoint$ = new BehaviorSubject<'content' | 'query'>('query');
private apiVersion$ = new BehaviorSubject<ApiVersion | undefined>(undefined);
private canTrack$ = new BehaviorSubject(!this.browserTrackingDisabled);
private apiKey$ = new BehaviorSubject<string | null>(null);
Expand Down Expand Up @@ -2231,6 +2240,7 @@ export class Builder {
options.authToken || this.authToken,
options.apiVersion || this.apiVersion
);
instance.apiEndpoint = this.apiEndpoint;
instance.setUserAttributes(this.getUserAttributes());
} else {
// NOTE: All these are when .init is not called and the customer
Expand Down Expand Up @@ -2462,8 +2472,6 @@ export class Builder {

const queue = useQueue || (usePastQueue ? this.priorContentQueue : this.getContentQueue) || [];

const apiEndpoint = queue[0].apiEndpoint || 'query';

// TODO: do this on every request send?
this.getOverridesFromQueryString();

Expand Down Expand Up @@ -2592,10 +2600,11 @@ export class Builder {
'rev',
'static',
];

for (const key of properties) {
const value = options[key];
if (value !== undefined) {
if (apiEndpoint === 'query') {
if (this.apiEndpoint === 'query') {
queryParams.options = queryParams.options || {};
queryParams.options[options.key!] = queryParams.options[options.key!] || {};
queryParams.options[options.key!][key] = JSON.stringify(value);
Expand All @@ -2622,9 +2631,9 @@ export class Builder {

const format = queryParams.format;
const isApiCallForCodegen = format === 'solid' || format === 'react';
const isApiCallForCodegenOrQuery = isApiCallForCodegen || apiEndpoint === 'query';
const isApiCallForCodegenOrQuery = isApiCallForCodegen || this.apiEndpoint === 'query';

if (apiEndpoint === 'content') {
if (this.apiEndpoint === 'content') {
queryParams.enrich = true;
if (queue[0].query) {
const flattened = this.flattenMongoQuery({ query: queue[0].query });
Expand All @@ -2648,7 +2657,7 @@ export class Builder {
let url;
if (isApiCallForCodegen) {
url = `${host}/api/v1/codegen/${this.apiKey}/${keyNames}`;
} else if (apiEndpoint === 'query') {
} else if (this.apiEndpoint === 'query') {
url = `${host}/api/v3/query/${this.apiKey}/${keyNames}`;
} else {
url = `${host}/api/v3/content/${queue[0].model}`;
Expand Down
2 changes: 2 additions & 0 deletions packages/react-tests/nextjs/pages/[[...index]].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ export default function Page(props: PageProps & { apiVersion: any }) {
.promise()
.then();
} else if (router.asPath.includes('get-content')) {
builder.apiEndpoint = props.apiEndpoint;
delete props.apiEndpoint;
builder
.get('', {
...props,
Expand Down
4 changes: 4 additions & 0 deletions packages/react-tests/react-remix/app/routes/($slug).tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ export default function Page() {
window.location.pathname.includes('get-query') ||
window.location.pathname.includes('get-content')
) {
if (props.apiEndpoint) {
builder.apiEndpoint = props.apiEndpoint;
delete props.apiEndpoint;
}
builder
.get('', {
...props,
Expand Down
4 changes: 4 additions & 0 deletions packages/react-tests/react-vite/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ function App() {
window.location.pathname.includes('get-query') ||
window.location.pathname.includes('get-content')
) {
if (resp.apiEndpoint) {
builder.apiEndpoint = resp.apiEndpoint;
delete resp.apiEndpoint;
}
builder
.get('', {
...resp,
Expand Down
39 changes: 39 additions & 0 deletions packages/sdks-tests/src/snippet-tests/api-endpoint.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { expect } from '@playwright/test';
import { findTextInPage, test } from '../helpers/index.js';

test.describe('API Endpoint', () => {
test.describe('Live', () => {
test('loads symbol', async ({ page }) => {
await page.goto('/contentwithsymbol');

await findTextInPage({ page, text: 'This is my Symbol!' });
});

test('records API calls after page load', async ({ page, packageName }) => {
test.skip(packageName !== 'gen1-next');
// Start monitoring network requests
const urlMatchForContentAPI = /https:\/\/cdn\.builder\.io\/api\/v3\/content/;
const urlMatchForQueryAPI = /https:\/\/cdn\.builder\.io\/api\/v3\/query/;

let queryApiInvocations = 0;

const responsePromise = page.waitForResponse(urlMatchForContentAPI);

await page.route(urlMatchForQueryAPI, route => {
queryApiInvocations++;
return route.continue();
});

await page.goto(
'/contentwithsymbol?builder.space=ee9f13b4981e489a9a1209887695ef2b&builder.user.permissions=read%2Ccreate%2Cpublish%2CeditDesigns%2CeditLayouts%2CeditLayers%2CeditContentPriority&builder.user.role.name=Designer&builder.user.role.id=creator&builder.cachebust=true&builder.preview=page&builder.noCache=true&builder.allowTextEdit=true&__builder_editing__=true&builder.overrides.page=6a2f7e1a9ab2454ba6766522d6d99f0d&builder.overrides.6a2f7e1a9ab2454ba6766522d6d99f0d=6a2f7e1a9ab2454ba6766522d6d99f0d&builder.overrides.page:/contentwithsymbol=6a2f7e1a9ab2454ba6766522d6d99f0d&builder.frameEditing=page&builder.options.locale=Default',
{ waitUntil: 'networkidle' }
);

// Verify API calls were recorded
const req = (await responsePromise).request();
expect(req).toBeDefined();
expect(req!.method()).toBe('GET');
expect(queryApiInvocations).toBe(0);
});
});
});
2 changes: 1 addition & 1 deletion packages/sdks-tests/src/specs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ export const getProps = async (args: {
break;
case '/get-content':
extraProps = {
options: { apiEndpoint: 'content' },
apiEndpoint: 'content',
};
break;
case '/get-query':
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* Quickstart snippet
* snippets/gen1-nextjs/pages/[[...index]].tsx
*/
import { BuilderComponent, builder, useIsPreviewing } from '@builder.io/react';
import type { BuilderContent } from '@builder.io/sdk';
import type { GetStaticProps } from 'next';
import DefaultErrorPage from 'next/error';
import React from 'react';

builder.init('ee9f13b4981e489a9a1209887695ef2b');
builder.apiEndpoint = 'content';

export const getStaticProps: GetStaticProps = async () => {
const urlPath = '/contentwithsymbol';

const page = await builder
.get('page', {
userAttributes: {
urlPath,
},
})
.promise();

return {
props: {
page: page || null,
},
revalidate: 5,
};
};

export const getStaticPaths = async () => {
return {
paths: [],
fallback: 'blocking',
};
};

export default function Page({ page }: { page: BuilderContent | null }) {
const isPreviewing = useIsPreviewing();

if (!page && !isPreviewing) {
return <DefaultErrorPage statusCode={404} />;
}

return (
<>
<h1>CORRECT</h1>
{/* Render the Builder page */}
<BuilderComponent model="page" content={page || undefined} />
</>
);
}

0 comments on commit 56f9461

Please sign in to comment.