Created
September 17, 2023 04:54
-
-
Save frueda1/a3827471a7a33cc452e717254a92c30f to your computer and use it in GitHub Desktop.
XM Cloud integration with Sitecore Personalize, and call GraphQL query with Decision Model Result as parameter.
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
export const ARTICLE_TEMPLATE = '4A7C0D95-EC1C-4C89-8C08-AA07248A9F0B'; | |
export const ENGAGE_SETTINGS = { | |
clientKey: `${process.env.NEXT_PUBLIC_ENGAGE_CLIENT_KEY}`, | |
targetURL: `${process.env.NEXT_PUBLIC_ENGAGE_TARGET_URL}`, | |
pointOfSale: `${process.env.NEXT_PUBLIC_ENGAGE_POS}`, | |
forceServerCookieMode: true, | |
includeUTMParameters: true, | |
webPersonalization: true, | |
}; |
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
import { NextRequest, NextResponse } from 'next/server'; | |
import { MiddlewarePlugin } from '..'; | |
import { initServer } from '@sitecore/engage'; | |
class EngagePlugin implements MiddlewarePlugin { | |
order = 2; | |
async exec(req: NextRequest, res?: NextResponse): Promise<NextResponse> { | |
res = NextResponse.next(); | |
const engageSettings = { | |
clientKey: `${process.env.NEXT_PUBLIC_ENGAGE_CLIENT_KEY}`, | |
targetURL: `${process.env.NEXT_PUBLIC_ENGAGE_TARGET_URL}`, | |
pointOfSale: `${process.env.NEXT_PUBLIC_ENGAGE_POS}`, | |
cookieDomain: `${process.env.NEXT_PUBLIC_ENGAGE_DOMAIN}`, | |
cookieExpiryDays: 365, | |
forceServerCookieMode: true, | |
}; | |
const engageServer = initServer(engageSettings); | |
await engageServer.handleCookie(req, res); | |
return res; | |
} | |
} | |
export const engagePlugin = new EngagePlugin(); |
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
import { GraphQLClient } from 'graphql-request'; | |
import gql from 'graphql-tag'; | |
import { sitecoreApiKey, graphQLEndpoint } from 'temp/config'; | |
function getGraphQLClient(): GraphQLClient { | |
if (!sitecoreApiKey || !graphQLEndpoint) { | |
console.error('No Sitecore API Key, Sitemap Root ID and/or public URL configured for the site'); | |
return {} as GraphQLClient; | |
} | |
const client = new GraphQLClient(graphQLEndpoint); | |
client.setHeader('sc_apikey', sitecoreApiKey); | |
return client; | |
} | |
const getRelatedItems = async ( | |
mostVisitedPage: string, | |
templateId: string | |
): Promise<RelatedItemsQueryResult> => { | |
const graphQLClient = getGraphQLClient(); | |
const visitedPage = mostVisitedPage !== '' ? mostVisitedPage.split('|')[0] : ''; | |
const template = mostVisitedPage !== '' ? mostVisitedPage.split('|')[1] : ''; | |
let conditions = ''; | |
let templateIdsConditions = ''; | |
templateIdsConditions = ` { OR: [ { name: "_templates", value: "${templateId}", operator: CONTAINS } ] } `; | |
if (template === 'IndustryPage') { | |
conditions = `{ OR: [ { name: "industry", value: "${visitedPage}", operator: CONTAINS } ] }`; | |
} | |
const queryToGQL = getQueryToGQL(conditions, templateIdsConditions); | |
const result = await graphQLClient.request<RelatedItemsQueryResult>( | |
gql` | |
${queryToGQL} | |
` | |
); | |
return result; | |
}; | |
const getQueryToGQL = (conditions: string, templateIdsConditions: string) => { | |
return ` | |
query { | |
search( | |
where: { | |
AND: [ | |
${templateIdsConditions} | |
${conditions} | |
] | |
} | |
orderBy:{name:"publicationDate", direction:DESC} | |
# defaults to 10 | |
first: 10 | |
after: "" | |
) { | |
total | |
pageInfo { | |
endCursor | |
hasNext | |
} | |
results { | |
name: field(name: "name") { | |
jsonValue | |
} | |
id | |
url { | |
path | |
} | |
} | |
} | |
} | |
`; | |
}; | |
export type RelatedItemsQueryResult = { | |
search: { | |
total: number; | |
pageInfo: { | |
endCursor: string; | |
hasNext: boolean; | |
}; | |
results: [ | |
{ | |
name: { | |
jsonValue: { | |
value: string; | |
}; | |
}; | |
id: string; | |
url: { | |
path: string; | |
}; | |
} | |
]; | |
}; | |
}; | |
export { getRelatedItems }; |
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 Layout is needed for Starter Kit. | |
*/ | |
import React from 'react'; | |
import Head from 'next/head'; | |
import { | |
Placeholder, | |
getPublicUrl, | |
LayoutServiceData, | |
Field, | |
} from '@sitecore-jss/sitecore-jss-nextjs'; | |
import Scripts from 'src/Scripts'; | |
import { useEffect } from 'react'; | |
import { ICustomEventInput, init } from '@sitecore/engage'; | |
import { ENGAGE_SETTINGS } from 'lib/constants'; | |
import { useRouter } from 'next/router'; | |
// Prefix public assets with a public URL to enable compatibility with Sitecore Experience Editor. | |
// If you're not supporting the Experience Editor, you can remove this. | |
const publicUrl = getPublicUrl(); | |
interface LayoutProps { | |
layoutData: LayoutServiceData; | |
} | |
interface RouteFields { | |
[key: string]: unknown; | |
Title?: Field; | |
} | |
const Layout = ({ layoutData }: LayoutProps): JSX.Element => { | |
const { route } = layoutData.sitecore; | |
const router = useRouter(); | |
const fields = route?.fields as RouteFields; | |
const isPageEditing = layoutData.sitecore.context.pageEditing; | |
const mainClassPageEditing = isPageEditing ? 'editing-mode' : 'prod-mode'; | |
useEffect(() => { | |
InitEngage(); | |
}, [router, route, route?.itemId, route?.templateName, route?.displayName]); | |
const InitEngage = () => { | |
if (router.isReady && route && route.itemId && route.templateName && route.displayName) { | |
init(ENGAGE_SETTINGS).then((engage) => { | |
const eventData: ICustomEventInput = { | |
channel: 'WEB', | |
currency: 'USD', | |
pointOfSale: `${process.env.NEXT_PUBLIC_ENGAGE_POS}`, | |
page: route.itemId, | |
}; | |
const extensionData = { | |
template: route.templateName, | |
pageName: route.displayName, | |
}; | |
// Send VIEW events | |
engage.event('PAGE_VIEW', eventData, extensionData); | |
}); | |
} | |
}; | |
return ( | |
<> | |
<Scripts /> | |
<Head> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"></meta> | |
<title>{fields.Title?.value?.toString() || 'Page'}</title> | |
<link rel="icon" href={`${publicUrl}/favicon.ico`} /> | |
</Head> | |
{/* root placeholder for the app, which we add components to using route data */} | |
<div className={mainClassPageEditing}> | |
<header> | |
<div id="header"> | |
{route && ( | |
<Placeholder name="headless-header" rendering={route} layoutData={layoutData} /> | |
)} | |
</div> | |
</header> | |
<main id="top"> | |
<div id="content"> | |
{route && ( | |
<Placeholder name="headless-main" rendering={route} layoutData={layoutData} /> | |
)} | |
</div> | |
</main> | |
<footer> | |
<div id="footer"> | |
{route && ( | |
<Placeholder name="headless-footer" rendering={route} layoutData={layoutData} /> | |
)} | |
</div> | |
</footer> | |
</div> | |
</> | |
); | |
}; | |
export default Layout; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment