-
Notifications
You must be signed in to change notification settings - Fork 276
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Nextjs][Personalize] Productize layout response transform (#962)
* created submodule for layout-personalizer * added unit tests * fixed nextjs middleware * added test data to diff folder, added doc comments * changed test data variable names * fixed review comments, added more test coverage * fixed review comments * updated jsDoc, refactored personalizePlaceholder function
- Loading branch information
1 parent
42c73e2
commit f43645f
Showing
12 changed files
with
264 additions
and
76 deletions.
There are no files selected for viewing
8 changes: 0 additions & 8 deletions
8
...eate-sitecore-jss/src/templates/nextjs-personalize/src/lib/component-props/personalize.ts
This file was deleted.
Oops, something went wrong.
64 changes: 0 additions & 64 deletions
64
packages/create-sitecore-jss/src/templates/nextjs-personalize/src/lib/layout-personalizer.ts
This file was deleted.
Oops, something went wrong.
2 changes: 1 addition & 1 deletion
2
...re-jss/src/templates/nextjs-personalize/src/lib/page-props-factory/plugins/personalize.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
6 changes: 3 additions & 3 deletions
6
packages/create-sitecore-jss/src/templates/nextjs/src/pages/_middleware.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
import type { NextRequest } from 'next/server'; | ||
import type { NextRequest, NextFetchEvent } from 'next/server'; | ||
import middleware from 'lib/middleware'; | ||
|
||
// eslint-disable-next-line | ||
export default async function (req: NextRequest) { | ||
return middleware(req); | ||
export default async function (req: NextRequest, ev: NextFetchEvent) { | ||
return middleware(req, ev); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './types/personalize/index'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
module.exports = require('./dist/cjs/personalize/index'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { personalizeLayout } from './layout-personalizer'; |
68 changes: 68 additions & 0 deletions
68
packages/sitecore-jss/src/personalize/layout-personalizer.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import { expect } from 'chai'; | ||
import * as personalize from './layout-personalizer'; | ||
import { ComponentRenderingWithExperiences } from './layout-personalizer'; | ||
import { | ||
layoutData, | ||
componentsArray, | ||
component, | ||
componentsWithExperiencesArray, | ||
componentWithExperiences, | ||
layoutDataWithoutPlaceholder, | ||
withoutComponentName, | ||
segmentIsNull, | ||
} from '../test-data/personalizeData'; | ||
|
||
const { personalizeLayout, personalizePlaceholder, personalizeComponent } = personalize; | ||
|
||
describe('layout-personalizer', () => { | ||
describe('personalizeLayout', () => { | ||
it('should not return anything', () => { | ||
const segment = 'test'; | ||
const personalizedLayoutResult = personalizeLayout(layoutData, segment); | ||
expect(personalizedLayoutResult).to.equal(undefined); | ||
}); | ||
|
||
it('should return undefined if no placeholders', () => { | ||
const segment = 'test'; | ||
const personalizedLayoutResult = personalizeLayout(layoutDataWithoutPlaceholder, segment); | ||
expect(personalizedLayoutResult).to.equal(undefined); | ||
}); | ||
}); | ||
|
||
describe('personalizePlaceholder', () => { | ||
it('should return array of personalized components', () => { | ||
const segment = 'mountain_bike_audience'; | ||
const personalizedPlaceholderResult = personalizePlaceholder(componentsArray, segment); | ||
expect(personalizedPlaceholderResult).to.deep.equal(componentsWithExperiencesArray); | ||
}); | ||
}); | ||
|
||
describe('personalizeComponent', () => { | ||
it('should return personalized component', () => { | ||
const segment = 'mountain_bike_audience'; | ||
const personalizedComponentResult = personalizeComponent( | ||
(component as unknown) as ComponentRenderingWithExperiences, | ||
segment | ||
); | ||
expect(personalizedComponentResult).to.deep.equal(componentWithExperiences); | ||
}); | ||
|
||
it('should return null when segmentVariant is null', () => { | ||
const segment = 'mountain_bike_audience'; | ||
const personalizedComponentResult = personalizeComponent( | ||
(segmentIsNull as unknown) as ComponentRenderingWithExperiences, | ||
segment | ||
); | ||
expect(personalizedComponentResult).to.equal(null); | ||
}); | ||
|
||
it('should return null when segmentVariant and componentName is undefined', () => { | ||
const segment = 'test'; | ||
const personalizedComponentResult = personalizeComponent( | ||
(withoutComponentName as unknown) as ComponentRenderingWithExperiences, | ||
segment | ||
); | ||
expect(personalizedComponentResult).to.equal(null); | ||
}); | ||
}); | ||
}); |
76 changes: 76 additions & 0 deletions
76
packages/sitecore-jss/src/personalize/layout-personalizer.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import { LayoutServiceData, ComponentRendering, HtmlElementRendering } from './../layout/models'; | ||
|
||
// NULL means Hidden by this experience | ||
export type ComponentRenderingWithExperiences = ComponentRendering & { | ||
experiences: { [name: string]: ComponentRenderingWithExperiences | null }; | ||
}; | ||
|
||
/** | ||
* Apply personalization to layout data. This will recursively go through all placeholders/components, check experiences nodes and replace default with object from specific experience. | ||
* @param {LayoutServiceData} layout Layout data | ||
* @param {string} segment segmentId | ||
*/ | ||
export function personalizeLayout(layout: LayoutServiceData, segment: string): void { | ||
const placeholders = layout.sitecore.route?.placeholders; | ||
if (Object.keys(placeholders ?? {}).length === 0) { | ||
return; | ||
} | ||
if (placeholders) { | ||
Object.keys(placeholders).forEach((placeholder) => { | ||
placeholders[placeholder] = personalizePlaceholder(placeholders[placeholder], segment); | ||
}); | ||
} | ||
} | ||
|
||
/** | ||
* @param {Array} components components within placeholder | ||
* @param {string} segment segmentId | ||
*/ | ||
export function personalizePlaceholder( | ||
components: Array<ComponentRendering | HtmlElementRendering>, | ||
segment: string | ||
): Array<ComponentRendering | HtmlElementRendering> { | ||
return components | ||
.map((component) => | ||
(component as ComponentRenderingWithExperiences).experiences !== undefined | ||
? (personalizeComponent(component as ComponentRenderingWithExperiences, segment) as | ||
| ComponentRendering | ||
| HtmlElementRendering) | ||
: component | ||
) | ||
.filter(Boolean); | ||
} | ||
|
||
/** | ||
* @param {ComponentRenderingWithExperiences} component component with experiences | ||
* @param {string} segment segmentId | ||
*/ | ||
export function personalizeComponent( | ||
component: ComponentRenderingWithExperiences, | ||
segment: string | ||
): ComponentRendering | null { | ||
const segmentVariant = component.experiences[segment]; | ||
if (segmentVariant === undefined && component.componentName === undefined) { | ||
// DEFAULT IS HIDDEN | ||
return null; | ||
} else if (Object.keys(segmentVariant ?? {}).length === 0) { | ||
// HIDDEN | ||
return null; | ||
} else if (segmentVariant) { | ||
component = segmentVariant; | ||
} | ||
|
||
if (!component.placeholders) return component; | ||
|
||
Object.keys(component?.placeholders).forEach((placeholder) => { | ||
if (component.placeholders) { | ||
component.placeholders[placeholder] = personalizePlaceholder( | ||
component.placeholders[placeholder], | ||
segment | ||
); | ||
} | ||
}); | ||
|
||
return component; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
const mountain_bike_audience = { | ||
uid: '0b6d23d8-c50e-4e79-9eca-317ec43e82b0', | ||
componentName: 'ContentBlock', | ||
dataSource: '20679cd4-356b-4452-b507-453beeb0be39', | ||
fields: { | ||
content: { | ||
value: | ||
'<p><img src="https://edge-beta.sitecorecloud.io/ser-edge-personalization/media/JssNextWeb/Mountain-Bike.jpg?h=675&w=1200" style="width:1200px;height:675px;" /></p>', | ||
}, | ||
heading: { value: 'Mountain Bike' }, | ||
}, | ||
}; | ||
|
||
const city_bike_audience = { | ||
uid: '0b6d23d8-c50e-4e79-9eca-317ec43e82b0', | ||
componentName: 'ContentBlock', | ||
dataSource: '36e02581-2056-4c55-a4d5-f4b700ba1ae2', | ||
fields: { | ||
content: { | ||
value: | ||
'<p><img src="https://edge-beta.sitecorecloud.io/ser-edge-personalization/media/JssNextWeb/Mountain-Bike.jpg?h=675&w=1200" style="width:1200px;height:675px;" /></p>', | ||
}, | ||
heading: { value: 'Mountain Bike' }, | ||
}, | ||
}; | ||
|
||
export const layoutData = { | ||
sitecore: { | ||
context: { | ||
pageEditing: false, | ||
site: { name: 'JssNextWeb' }, | ||
visitorIdentificationTimestamp: 1038543, | ||
language: 'en', | ||
}, | ||
route: { | ||
name: 'landingpage', | ||
placeholders: { | ||
'jss-main': [ | ||
{ | ||
uid: '0b6d23d8-c50e-4e79-9eca-317ec43e82b0', | ||
componentName: 'ContentBlock', | ||
dataSource: 'e020fb58-1be8-4537-aab8-67916452ecf2', | ||
fields: { content: { value: '' }, heading: { value: 'Default Content' } }, | ||
experiences: { | ||
mountain_bike_audience: mountain_bike_audience, | ||
city_bike_audience: city_bike_audience, | ||
}, | ||
}, | ||
], | ||
}, | ||
}, | ||
}, | ||
}; | ||
|
||
export const layoutDataWithoutPlaceholder = { | ||
sitecore: { | ||
context: { | ||
pageEditing: false, | ||
site: { name: 'JssNextWeb' }, | ||
visitorIdentificationTimestamp: 1038543, | ||
language: 'en', | ||
}, | ||
route: { | ||
name: 'landingpage', | ||
placeholders: {}, | ||
}, | ||
}, | ||
}; | ||
|
||
export const componentWithExperiences = { | ||
uid: '0b6d23d8-c50e-4e79-9eca-317ec43e82b0', | ||
componentName: 'ContentBlock', | ||
dataSource: '20679cd4-356b-4452-b507-453beeb0be39', | ||
fields: mountain_bike_audience.fields, | ||
}; | ||
|
||
export const componentsWithExperiencesArray = [componentWithExperiences]; | ||
|
||
export const component = { | ||
uid: '0b6d23d8-c50e-4e79-9eca-317ec43e82b0', | ||
componentName: 'ContentBlock', | ||
dataSource: 'e020fb58-1be8-4537-aab8-67916452ecf2', | ||
fields: { content: { value: '' }, heading: { value: 'Default Content' } }, | ||
experiences: { | ||
mountain_bike_audience: mountain_bike_audience, | ||
city_bike_audience: city_bike_audience, | ||
}, | ||
}; | ||
|
||
export const componentsArray = [component]; | ||
|
||
export const withoutComponentName = { | ||
uid: '0b6d23d8-c50e-4e79-9eca-317ec43e82b0', | ||
componentName: undefined, | ||
dataSource: 'e020fb58-1be8-4537-aab8-67916452ecf2', | ||
fields: { content: { value: '' }, heading: { value: 'Default Content' } }, | ||
experiences: { | ||
mountain_bike_audience: mountain_bike_audience, | ||
city_bike_audience: city_bike_audience, | ||
}, | ||
}; | ||
|
||
export const segmentIsNull = { | ||
uid: '0b6d23d8-c50e-4e79-9eca-317ec43e82b0', | ||
componentName: undefined, | ||
dataSource: 'e020fb58-1be8-4537-aab8-67916452ecf2', | ||
fields: { content: { value: '' }, heading: { value: 'Default Content' } }, | ||
experiences: { | ||
mountain_bike_audience: {}, | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters