Skip to content

feat[react-gen2]: support for variant containers #3828

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

Merged
merged 52 commits into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
a7416ca
init
sidmohanty11 Jan 15, 2025
966ff90
f
sidmohanty11 Jan 16, 2025
6359fa9
try
sidmohanty11 Jan 16, 2025
2ca5359
fix
sidmohanty11 Jan 16, 2025
b17d1f4
try
sidmohanty11 Jan 17, 2025
c40d989
checkpoint
sidmohanty11 Jan 20, 2025
9623628
remove
sidmohanty11 Jan 20, 2025
bcfae30
fix
sidmohanty11 Jan 20, 2025
fead5df
fix
sidmohanty11 Jan 20, 2025
0f3820f
fix
sidmohanty11 Jan 20, 2025
3d107ca
remove
sidmohanty11 Jan 20, 2025
7e74e7d
saf
sidmohanty11 Jan 20, 2025
27fcb28
remove
sidmohanty11 Jan 20, 2025
bbed638
fix
sidmohanty11 Jan 20, 2025
c876a01
fix
sidmohanty11 Jan 20, 2025
97aa51c
fix gen1 subscriber
sidmohanty11 Jan 21, 2025
1d80e11
update
sidmohanty11 Jan 21, 2025
1e859dd
remove logs
sidmohanty11 Jan 21, 2025
6af09aa
fix
sidmohanty11 Jan 21, 2025
e4dbd8d
remove only
sidmohanty11 Jan 21, 2025
00e347c
fix
sidmohanty11 Jan 21, 2025
c12fdee
fix
sidmohanty11 Jan 21, 2025
af3dc67
fix
sidmohanty11 Jan 21, 2025
248b481
custom events for analytics and tracking
sidmohanty11 Jan 21, 2025
6778086
fix
sidmohanty11 Jan 21, 2025
7f2a51e
fix
sidmohanty11 Jan 21, 2025
ae6e032
fix
sidmohanty11 Jan 21, 2025
62c4857
fix
sidmohanty11 Jan 21, 2025
2f2ba53
remove fragment
sidmohanty11 Jan 21, 2025
da82b19
add e2e
sidmohanty11 Jan 21, 2025
1419e94
remove
sidmohanty11 Jan 21, 2025
fe32d67
update
sidmohanty11 Jan 21, 2025
84608cb
merge main
sidmohanty11 Jan 21, 2025
ca536d4
simplify blocks
sidmohanty11 Jan 21, 2025
c19849f
changesets
sidmohanty11 Jan 21, 2025
c96ba14
use inlined-fns approach
sidmohanty11 Jan 22, 2025
0afe591
fix
sidmohanty11 Jan 22, 2025
ea38c22
fix
sidmohanty11 Jan 22, 2025
6ae66e5
update
sidmohanty11 Jan 22, 2025
ce06409
update build-inline-fns to keep fns persisted if they are exported
sidmohanty11 Jan 22, 2025
5df82bf
Merge remote-tracking branch 'origin/main' into feat/variant-containe…
sidmohanty11 Jan 27, 2025
163a330
fix
sidmohanty11 Jan 27, 2025
1975d8a
break changesets and hydration fix gen1
sidmohanty11 Jan 27, 2025
e468dd0
fix
sidmohanty11 Jan 27, 2025
14e3aa9
update
sidmohanty11 Jan 28, 2025
e4042f3
more tests
sidmohanty11 Jan 28, 2025
f72cd45
apply to gen1 next pages as well
sidmohanty11 Jan 28, 2025
a1e44ea
fix
sidmohanty11 Jan 28, 2025
bbeb527
update comment
sidmohanty11 Jan 28, 2025
f2a8a74
merge main
sidmohanty11 Jan 31, 2025
f608250
fix root styles to get applied to variant containers
sidmohanty11 Jan 31, 2025
90620c7
fix
sidmohanty11 Jan 31, 2025
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
15 changes: 15 additions & 0 deletions .changeset/beige-grapes-cheer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
'@builder.io/sdk-solid': patch
---

Feat: exports `setClientUserAttributes` helper that can be used to set and update Builder's user attributes cookie. This cookie is used by Builder's Personalization Containers to decide which variant to render.

Usage example:

```ts
import { setClientUserAttributes } from '@builder.io/sdk-solid';

setClientUserAttributes({
device: 'tablet',
});
```
15 changes: 15 additions & 0 deletions .changeset/dirty-teachers-tell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
'@builder.io/sdk-svelte': patch
---

Feat: exports `setClientUserAttributes` helper that can be used to set and update Builder's user attributes cookie. This cookie is used by Builder's Personalization Containers to decide which variant to render.

Usage example:

```ts
import { setClientUserAttributes } from '@builder.io/sdk-svelte';

setClientUserAttributes({
device: 'tablet',
});
```
15 changes: 15 additions & 0 deletions .changeset/giant-lobsters-retire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
'@builder.io/sdk-vue': patch
---

Feat: exports `setClientUserAttributes` helper that can be used to set and update Builder's user attributes cookie. This cookie is used by Builder's Personalization Containers to decide which variant to render.

Usage example:

```ts
import { setClientUserAttributes } from '@builder.io/sdk-vue';

setClientUserAttributes({
device: 'tablet',
});
```
15 changes: 15 additions & 0 deletions .changeset/itchy-beers-marry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
'@builder.io/sdk-angular': patch
---

Feat: exports `setClientUserAttributes` helper that can be used to set and update Builder's user attributes cookie. This cookie is used by Builder's Personalization Containers to decide which variant to render.

Usage example:

```ts
import { setClientUserAttributes } from '@builder.io/sdk-angular';

setClientUserAttributes({
device: 'tablet',
});
```
15 changes: 15 additions & 0 deletions .changeset/lazy-waves-greet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
'@builder.io/sdk-react-native': patch
---

Feat: exports `setClientUserAttributes` helper that can be used to set and update Builder's user attributes cookie. This cookie is used by Builder's Personalization Containers to decide which variant to render.

Usage example:

```ts
import { setClientUserAttributes } from '@builder.io/sdk-react-native';

setClientUserAttributes({
device: 'tablet',
});
```
15 changes: 15 additions & 0 deletions .changeset/lemon-foxes-occur.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
'@builder.io/sdk-qwik': patch
---

Feat: exports `setClientUserAttributes` helper that can be used to set and update Builder's user attributes cookie. This cookie is used by Builder's Personalization Containers to decide which variant to render.

Usage example:

```ts
import { setClientUserAttributes } from '@builder.io/sdk-qwik';

setClientUserAttributes({
device: 'tablet',
});
```
15 changes: 15 additions & 0 deletions .changeset/nasty-ghosts-own.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
'@builder.io/sdk-react': patch
---

Feat: exports `setClientUserAttributes` helper that can be used to set and update Builder's user attributes cookie. This cookie is used by Builder's Personalization Containers to decide which variant to render.

Usage example:

```ts
import { setClientUserAttributes } from '@builder.io/sdk-react';

setClientUserAttributes({
device: 'tablet',
});
```
15 changes: 15 additions & 0 deletions .changeset/tidy-lions-burn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
'@builder.io/sdk-react-nextjs': patch
---

Feat: exports `setClientUserAttributes` helper that can be used to set and update Builder's user attributes cookie. This cookie is used by Builder's Personalization Containers to decide which variant to render.

Usage example:

```ts
import { setClientUserAttributes } from '@builder.io/sdk-react-nextjs';

setClientUserAttributes({
device: 'tablet',
});
```
5 changes: 5 additions & 0 deletions .changeset/tricky-hairs-march.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@builder.io/react': patch
---

Fix: hydration mismatch error and reactivity of Personalization Containers when `userAttributes` cookie value is updated.
5 changes: 5 additions & 0 deletions .changeset/wise-singers-tickle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@builder.io/sdk-react': patch
---

Feat: support of Variant Containers or Block level personalization
7 changes: 7 additions & 0 deletions packages/react-tests/next14-pages/next.config.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const path = require('path');

/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
externalDir: true,
},
webpack: config => {
config.resolve.alias['react'] = path.resolve(__dirname, './node_modules/react');
Copy link
Contributor

Choose a reason for hiding this comment

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

Was this to resolve the weird "multiple versions of React"/"Cannot import useContext of undefined" bugs we've seen in certain tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yup, while checking the error of useRef not defined in personalization spec I remembered we faced a similar case for useState a while back which was resolved with this

return config;
},
};

module.exports = nextConfig;
3 changes: 3 additions & 0 deletions packages/react-tests/next14-pages/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
"eslint-config-next": "14.0.3",
"typescript": "^5"
},
"installConfig": {
"hoistingLimits": "workspaces"
},
"nx": {
"targets": {
"build": {
Expand Down
6 changes: 6 additions & 0 deletions packages/react-tests/next15-app/src/components/builder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ if (typeof window !== 'undefined') {
) {
builder.canTrack = false;
}

if (window.location.pathname.includes('variant-containers')) {
builder.setUserAttributes({
device: 'tablet',
});
}
}

type BuilderPageProps = any;
Expand Down
13 changes: 8 additions & 5 deletions packages/react/src/blocks/PersonalizationContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,15 @@ export function PersonalizationContainer(props: PersonalizationContainerProps) {
);
const rootRef = useRef<HTMLDivElement>(null);
const [isClient, setIsClient] = useState(isBeingHydrated);
const [update, setUpdate] = useState(0);
const [isHydrated, setIsHydrated] = useState(false);
const [_, setUpdate] = useState(0);
const builderStore = useContext(BuilderStoreContext);

useEffect(() => {
setIsClient(true);
setIsHydrated(true);
const subscriber = builder.userAttributesChanged.subscribe(() => {
setUpdate(update + 1);
setUpdate(prev => prev + 1);
});
let unsubs = [() => subscriber.unsubscribe()];

Expand Down Expand Up @@ -156,10 +158,11 @@ export function PersonalizationContainer(props: PersonalizationContainerProps) {
}}
className={`builder-personalization-container ${
props.attributes.className
} ${isClient ? '' : 'builder-personalization-container-loading'}`}
}${isClient ? '' : ' builder-personalization-container-loading'}`}
>
{/* If editing a specific varient */}
{Builder.isEditing &&
{isHydrated &&
Builder.isEditing &&
typeof props.previewingIndex === 'number' &&
props.previewingIndex < (props.variants?.length || 0) ? (
<BuilderBlocks
Expand All @@ -169,7 +172,7 @@ export function PersonalizationContainer(props: PersonalizationContainerProps) {
child
/>
) : // If editing the default or we're on the server and there are no matching variants show the default
(Builder.isEditing && typeof props.previewingIndex !== 'number') ||
(isHydrated && Builder.isEditing && typeof props.previewingIndex !== 'number') ||
!isClient ||
!filteredVariants.length ? (
<BuilderBlocks
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Browser } from '@playwright/test';
import { expect } from '@playwright/test';
import { excludeGen2, test } from '../helpers/index.js';
import { excludeGen2, isSSRFramework, test } from '../helpers/index.js';
import { launchEmbedderAndWaitForSdk } from '../helpers/visual-editor.js';
const SELECTOR = 'div[builder-content-id]';

const createContextWithCookies = async ({
Expand Down Expand Up @@ -35,19 +36,17 @@ const initializeUserAttributes = async (
page: _page,
baseURL,
browser,
sdk,
packageName,
sdk,
}: Pick<
Parameters<Parameters<typeof test>[2]>[0],
'page' | 'baseURL' | 'browser' | 'packageName' | 'sdk'
>,
{ userAttributes }: { userAttributes: Record<string, string> }
) => {
// gen1-next likely have a config issue with SSR
test.skip(packageName === 'gen1-next14-pages');
// gen1-remix started failing on this test for an unknown reason.
test.skip(packageName === 'gen1-remix');
test.skip(excludeGen2(sdk));
test.skip(excludeGen2(sdk) && sdk !== 'react');

if (!baseURL) throw new Error('Missing baseURL');

Expand Down Expand Up @@ -162,4 +161,89 @@ test.describe('Personalization Container', () => {
});
}
});

test('setClientUserAttributes and builder.setUserAttributes sets cookie and renders variant after the first render', async ({
page,
packageName,
}) => {
// here we are checking specifically for winning variant content by setting the user attributes
test.skip(!['react-sdk-next-15-app', 'gen1-next15-app'].includes(packageName));
await page.goto('/variant-containers');

// content 1
await expect(page.getByText('My tablet content')).toBeVisible();
await expect(page.getByText('My mobile content updated')).not.toBeVisible();
await expect(page.getByText('My default content')).not.toBeVisible();

// content 2 - this has no targeting set, so the first variant should be the winning variant
await expect(page.getByText('Tablet content 2')).toBeVisible();
});

test('only default variants are ssred on the server', async ({ browser, packageName, sdk }) => {
test.skip(!isSSRFramework(packageName));
test.skip(!['react', 'oldReact'].includes(sdk));
// Cannot read properties of null (reading 'useContext')
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@samijaber we should create a ticket to move away from remix v1 for gen1 tests and use v2 that we already have in snippets. This will help us alias a single react and fix these tests

repo: https://github.com/sidmohanty11/builder-remix-gen1-example

Copy link
Contributor

Choose a reason for hiding this comment

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

yeah @sidmohanty11 , I actually spent a bunch of time doing this locally but hit some cascading issues that caused most (interactivity-related?) tests to fail for remix v2, so i ended up putting a pin in it for now. But would be good to sort it out

test.skip(packageName === 'gen1-remix');

const context = await browser.newContext({
javaScriptEnabled: false,
});

const page = await context.newPage();

await page.goto('/variant-containers');

await expect(page.getByText('My default content')).toBeVisible();
await expect(page.getByText('Default content 2')).toBeVisible();
});

test('root style attribute is correctly set', async ({ page, sdk, packageName }) => {
test.skip(!['react', 'oldReact'].includes(sdk));
// Cannot read properties of null (reading 'useContext')
test.skip(packageName === 'gen1-remix');

await page.goto('/variant-containers');

const secondPersonalizationContainer = page
.locator('.builder-personalization-container')
.nth(1);
await expect(secondPersonalizationContainer).toHaveCSS('background-color', 'rgb(255, 0, 0)');
});

test.describe('visual editing', () => {
test('correctly shows the variant that is being currently edited', async ({
page,
sdk,
basePort,
packageName,
}) => {
test.skip(!['react', 'oldReact'].includes(sdk));
// Cannot read properties of null (reading 'useContext')
test.skip(packageName === 'gen1-remix');

const paths = [
'/variant-containers-with-previewing-index-0',
'/variant-containers-with-previewing-index-1',
'/variant-containers-with-previewing-index-undefined',
];

const expectedTexts = [
'My tablet content',
'My mobile content updated',
'My default content',
];

for (let i = 0; i < paths.length; i++) {
const path = paths[i];
await launchEmbedderAndWaitForSdk({
path,
page,
sdk,
basePort,
});

await expect(page.frameLocator('iframe').getByText(expectedTexts[i])).toBeVisible();
}
});
});
});
18 changes: 8 additions & 10 deletions packages/sdks-tests/src/e2e-tests/slot.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import { checkIsRN, test } from '../helpers/index.js';

test.describe('Slot', () => {
test('slot should render', async ({ page, packageName }) => {
// gen1-remix and gen1-next skipped because React.useContext is not recognized
test.fail(['gen1-remix', 'gen1-next14-pages'].includes(packageName));
// gen1-remix skipped because React.useContext is not recognized
test.fail(['gen1-remix'].includes(packageName));
await page.goto('/slot');
await expect(page.locator('text=Inside a slot!!')).toBeVisible();
});

test('slot should render in the correct place', async ({ page, packageName, sdk }) => {
// gen1-remix and gen1-next skipped because React.useContext is not recognized
test.fail(['gen1-remix', 'gen1-next14-pages'].includes(packageName));
// gen1-remix skipped because React.useContext is not recognized
test.fail(['gen1-remix'].includes(packageName));
await page.goto('/slot');
const builderTextElements = checkIsRN(sdk)
? page.locator('[data-testid="div"]')
Expand All @@ -24,19 +24,17 @@ test.describe('Slot', () => {
});

test('slot should render with symbol (with content)', async ({ page, packageName }) => {
// gen1-remix and gen1-next skipped because React.useContext is not recognized
test.fail(['gen1-remix', 'gen1-next14-pages'].includes(packageName));
// gen1-remix skipped because React.useContext is not recognized
test.fail(['gen1-remix'].includes(packageName));
await page.goto('/slot-with-symbol');

await expect(page.locator('text=This is called recursion!')).toBeVisible();
});

test('slot should render with symbol (without content)', async ({ page, packageName, sdk }) => {
// gen1-remix and gen1-next skipped because React.useContext is not recognized
// gen1-remix skipped because React.useContext is not recognized
// ssr packages skipped because it fetches the slot content from the server
test.fail(
['gen1-remix', 'gen1-next14-pages', 'nextjs-sdk-next-app', 'qwik-city'].includes(packageName)
);
test.fail(['gen1-remix', 'nextjs-sdk-next-app', 'qwik-city'].includes(packageName));

let x = 0;

Expand Down
2 changes: 0 additions & 2 deletions packages/sdks-tests/src/e2e-tests/state-binding.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ test.describe('State binding', () => {
packageName === 'angular-16-ssr' || packageName === 'angular-16',
'Angular Gen2 event binding not working properly for repeat blocks.'
);
// hydration errors
test.fail(packageName === 'gen1-next14-pages');

// flaky, can't `test.fail()`
test.skip(
Expand Down
Loading
Loading