Skip to content

Commit b31c01f

Browse files
authored
feat(coveo): added example usage coveo analytics (#99)
1 parent 7c0fb87 commit b31c01f

24 files changed

+1152
-386
lines changed

examples/coveo-starter/.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ UNIFORM_PREVIEW_SECRET=hello-world
88

99
COVEO_ORGANIZATION_ID=<COVEO_ORGANIZATION_ID>
1010
COVEO_API_KEY=<COVEO_API_KEY>
11+
COVEO_ANALYTICS_API_KEY=<COVEO_ANALYTICS_API_KEY>

examples/coveo-starter/components/Facet.tsx

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
import { FC, SyntheticEvent, useEffect, useMemo, useState } from "react";
21
import {
3-
FacetState,
4-
buildFacet,
5-
FacetValue,
6-
buildSearchBox,
7-
} from "@coveo/headless";
2+
FC,
3+
SyntheticEvent,
4+
useContext,
5+
useEffect,
6+
useMemo,
7+
useState,
8+
} from "react";
9+
import { FacetState, buildFacet, FacetValue } from "@coveo/headless";
810
import {
911
ComponentProps,
1012
registerUniformComponent,
@@ -23,8 +25,8 @@ import {
2325
TextField,
2426
Typography,
2527
} from "@mui/material";
26-
import headlessEngine from "../context/Engine";
2728
import { capitalizeFirstLetter } from "../utils";
29+
import { HeadlessEngineContext } from "../context/Engine";
2830

2931
interface FacetProps {
3032
field: string;
@@ -33,6 +35,7 @@ interface FacetProps {
3335
//Coveo Facet docs https://docs.coveo.com/en/headless/latest/reference/search/controllers/facet/
3436

3537
const Facet: FC<FacetProps> = ({ field }) => {
38+
const headlessEngine = useContext(HeadlessEngineContext);
3639
const facetsBuilder = useMemo(
3740
() =>
3841
buildFacet(headlessEngine, {
@@ -49,22 +52,11 @@ const Facet: FC<FacetProps> = ({ field }) => {
4952
inputValue: "",
5053
});
5154

52-
const headlessSearchBox = useMemo(
53-
() => buildSearchBox(headlessEngine),
54-
[headlessEngine]
55+
useEffect(
56+
() => facetsBuilder.subscribe(() => setState(facetsBuilder.state)),
57+
[facetsBuilder]
5558
);
5659

57-
useEffect(() => {
58-
const updateState = () => {
59-
setState(facetsBuilder.state);
60-
};
61-
facetsBuilder.subscribe(updateState);
62-
63-
if (!headlessSearchBox.state.isLoading) {
64-
headlessSearchBox.submit();
65-
}
66-
}, [field]);
67-
6860
const toggleSelect = (value: FacetValue) => {
6961
facetsBuilder.toggleSelect(value);
7062
};
@@ -78,7 +70,7 @@ const Facet: FC<FacetProps> = ({ field }) => {
7870
};
7971

8072
const getFacetValues = () => {
81-
if (!state.values.length && !headlessSearchBox.state.isLoading) {
73+
if (!state.values.length) {
8274
return <Typography>Values not found</Typography>;
8375
}
8476
return state.values.map((value: FacetValue) => (

examples/coveo-starter/components/FacetBreadcrumbs.tsx

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,27 @@
1-
import { FC, useEffect, useMemo, useState } from "react";
1+
import { FC, useContext, useEffect, useMemo, useState } from "react";
22
import { registerUniformComponent } from "@uniformdev/canvas-react";
33
import { buildBreadcrumbManager } from "@coveo/headless";
4-
import { Box, Breadcrumbs, Button, Typography } from "@mui/material";
5-
import headlessEngine from "../context/Engine";
4+
import { Breadcrumbs, Button, Typography } from "@mui/material";
5+
import { HeadlessEngineContext } from "../context/Engine";
66
import { capitalizeFirstLetter } from "../utils";
77

88
//Coveo Facet Breadcrumbs docs https://docs.coveo.com/en/headless/latest/reference/search/controllers/breadcrumb-manager/
99

1010
const FacetBreadcrumbs: FC = () => {
11+
const headlessEngine = useContext(HeadlessEngineContext);
12+
1113
const headlessBreadcrumbManager = useMemo(
1214
() => buildBreadcrumbManager(headlessEngine),
1315
[headlessEngine]
1416
);
1517

1618
const [state, setState] = useState(headlessBreadcrumbManager.state);
1719

18-
useEffect(() => {
19-
const updateState = () => {
20-
setState(headlessBreadcrumbManager.state);
21-
};
22-
headlessBreadcrumbManager.subscribe(updateState);
23-
}, []);
20+
useEffect(
21+
() =>
22+
headlessBreadcrumbManager.subscribe(() => setState(headlessBreadcrumbManager.state)),
23+
[headlessBreadcrumbManager]
24+
);
2425

2526
const deselectAll = () => headlessBreadcrumbManager.deselectAll();
2627

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,52 @@
1-
import { FC } from "react";
1+
import { FC, useEffect } from "react";
22
import { RootComponentInstance } from "@uniformdev/canvas";
33
import { UniformComposition, UniformSlot } from "@uniformdev/canvas-react";
4-
import { Box, CssBaseline, ThemeProvider, Typography } from "@mui/material";
5-
import { Container } from "@mui/system";
4+
import { CssBaseline, ThemeProvider, Container } from "@mui/material";
65
import Footer from "@/components/Footer";
76
import { theme } from "../context/theme";
87

98
export interface PageCompositionProps {
109
data: RootComponentInstance;
1110
}
1211

13-
const PageComposition: FC<PageCompositionProps> = ({ data: composition }) => (
14-
<CssBaseline>
15-
<ThemeProvider theme={theme}>
16-
<Container maxWidth="xl">
17-
<UniformComposition data={composition}>
18-
<UniformSlot name="search-content" />
19-
</UniformComposition>
20-
<Footer />
21-
</Container>
22-
</ThemeProvider>
23-
</CssBaseline>
24-
);
12+
const PageComposition: FC<PageCompositionProps> = ({ data: composition }) => {
13+
useEffect(() => {
14+
// Define the script content
15+
const analyticsScriptContent = `
16+
(function(c,o,v,e,O,u,a){
17+
a='coveoua';c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
18+
c[a].t=Date.now();u=o.createElement(v);u.async=1;u.src=e;
19+
O=o.getElementsByTagName(v)[0];O.parentNode.insertBefore(u,O)
20+
})(window,document,'script','https://static.cloud.coveo.com/coveo.analytics.js/2/coveoua.js');
21+
coveoua('set', 'currencyCode', 'USD');
22+
`;
23+
24+
// Create a script element
25+
const analyticsScript = document.createElement("script");
26+
analyticsScript.type = "text/javascript";
27+
analyticsScript.innerHTML = analyticsScriptContent;
28+
29+
// Append the script to the document's body
30+
document.body.appendChild(analyticsScript);
31+
32+
// Clean up the script element when the component unmounts
33+
return () => {
34+
document.body.removeChild(analyticsScript);
35+
};
36+
}, []);
37+
38+
return (
39+
<CssBaseline>
40+
<ThemeProvider theme={theme}>
41+
<Container maxWidth="xl">
42+
<UniformComposition data={composition}>
43+
<UniformSlot name="search-content" />
44+
</UniformComposition>
45+
<Footer />
46+
</Container>
47+
</ThemeProvider>
48+
</CssBaseline>
49+
);
50+
};
2551

2652
export default PageComposition;

examples/coveo-starter/components/Pager.tsx

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
1-
import { ChangeEvent, FC, useEffect, useMemo, useState } from "react";
1+
import {
2+
ChangeEvent,
3+
FC,
4+
useContext,
5+
useEffect,
6+
useMemo,
7+
useState,
8+
} from "react";
29
import {
310
ComponentProps,
411
registerUniformComponent,
512
} from "@uniformdev/canvas-react";
613
import { buildPager, buildResultsPerPage, PagerState } from "@coveo/headless";
714
import { Box, Grid, Pagination, Typography } from "@mui/material";
8-
import headlessEngine from "../context/Engine";
15+
import { HeadlessEngineContext } from "../context/Engine";
916

1017
type PagerProps = ComponentProps<{
1118
resultsPerPage?: string;
@@ -17,19 +24,20 @@ type PagerProps = ComponentProps<{
1724
//Coveo Result Per Page docs https://docs.coveo.com/en/headless/latest/reference/search/controllers/results-per-page/
1825

1926
const Pager: FC<PagerProps> = ({ resultsPerPage = "9", title = "" }) => {
27+
const headlessEngine = useContext(HeadlessEngineContext);
28+
2029
const headlessPager = useMemo(
2130
() => buildPager(headlessEngine),
2231
[headlessEngine]
2332
);
2433

2534
const [state, setState] = useState<PagerState>(headlessPager.state);
2635

27-
useEffect(() => {
28-
const updateState = () => {
29-
setState(headlessPager.state);
30-
};
31-
headlessPager.subscribe(updateState);
32-
}, []);
36+
useEffect(
37+
() =>
38+
headlessPager.subscribe(() => setState(headlessPager.state)),
39+
[headlessPager]
40+
);
3341

3442
const headlessResultsPerPage = useMemo(
3543
() => buildResultsPerPage(headlessEngine),
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import {
2+
ProductRecommendation,
3+
loadClickAnalyticsActions,
4+
buildFrequentlyViewedTogetherList,
5+
} from "@coveo/headless/product-recommendation";
6+
import { FC, useContext, useEffect, useMemo, useState } from "react";
7+
import { ProductRecommendationEngineContext } from "../context/PREngine";
8+
import {
9+
ComponentProps,
10+
registerUniformComponent,
11+
} from "@uniformdev/canvas-react";
12+
import { DEFAULT_NUMBER_OF_RECOMMENDATIONS } from "../constants";
13+
14+
type RecommendationsProps = ComponentProps<{
15+
maxNumberOfRecommendations?: string;
16+
title?: string;
17+
}>;
18+
19+
export const Recommendations: FC<RecommendationsProps> = ({
20+
maxNumberOfRecommendations,
21+
title,
22+
}) => {
23+
const productRecommendationsEngine = useContext(
24+
ProductRecommendationEngineContext
25+
);
26+
27+
const frequentlyViewedTogetherBuild = useMemo(
28+
() =>
29+
buildFrequentlyViewedTogetherList(productRecommendationsEngine, {
30+
options: {
31+
maxNumberOfRecommendations:
32+
Number(maxNumberOfRecommendations) ||
33+
DEFAULT_NUMBER_OF_RECOMMENDATIONS,
34+
},
35+
}),
36+
[productRecommendationsEngine, maxNumberOfRecommendations]
37+
);
38+
39+
const [state, setState] = useState(frequentlyViewedTogetherBuild.state);
40+
41+
useEffect(() => {
42+
frequentlyViewedTogetherBuild.subscribe(() =>
43+
setState(frequentlyViewedTogetherBuild.state)
44+
);
45+
frequentlyViewedTogetherBuild.refresh();
46+
}, [frequentlyViewedTogetherBuild]);
47+
48+
if (state.error) {
49+
return null;
50+
}
51+
const logClick = (recommendation: ProductRecommendation) => {
52+
if (!productRecommendationsEngine) {
53+
return;
54+
}
55+
56+
const { logProductRecommendationOpen } = loadClickAnalyticsActions(
57+
productRecommendationsEngine
58+
);
59+
productRecommendationsEngine.dispatch(
60+
logProductRecommendationOpen(recommendation)
61+
);
62+
};
63+
64+
return (
65+
<div className="recs-list">
66+
<h2>{title || "People also viewed"}</h2>
67+
<ul>
68+
{state.recommendations.map((recommendation) => {
69+
return (
70+
<li key={recommendation.permanentid}>
71+
<h2>
72+
<a
73+
onClick={() => logClick(recommendation)}
74+
onContextMenu={() => logClick(recommendation)}
75+
onMouseDown={() => logClick(recommendation)}
76+
onMouseUp={() => logClick(recommendation)}
77+
>
78+
{recommendation.ec_name}
79+
</a>
80+
</h2>
81+
</li>
82+
);
83+
})}
84+
</ul>
85+
</div>
86+
);
87+
};
88+
89+
registerUniformComponent({
90+
type: "coveo-productRecommendations",
91+
component: Recommendations,
92+
});
93+
94+
export default Recommendations;

examples/coveo-starter/components/QuerySummary.tsx

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import { FC, useEffect, useMemo, useState } from "react";
1+
import { FC, useContext, useEffect, useMemo, useState } from "react";
22
import {
33
ComponentProps,
44
registerUniformComponent,
55
} from "@uniformdev/canvas-react";
66
import { buildQuerySummary } from "@coveo/headless";
77
import { Box, Grid } from "@mui/material";
8-
import headlessEngine from "../context/Engine";
8+
import { HeadlessEngineContext } from "../context/Engine";
99

1010
type QuerySummaryProps = ComponentProps<{
1111
listName?: string;
@@ -23,20 +23,16 @@ const QuerySummary: FC<QuerySummaryProps> = ({
2323
durationSettings = "",
2424
listName = "",
2525
}) => {
26+
const headlessEngine = useContext(HeadlessEngineContext);
27+
2628
const headlessQuerySummary = useMemo(
2729
() => buildQuerySummary(headlessEngine),
2830
[headlessEngine]
2931
);
3032

3133
const [state, setState] = useState(headlessQuerySummary.state);
3234

33-
const updateState = () => {
34-
setState(headlessQuerySummary.state);
35-
};
36-
37-
useEffect(() => {
38-
headlessQuerySummary.subscribe(updateState);
39-
}, []);
35+
useEffect(() => headlessQuerySummary.subscribe(() => setState(headlessQuerySummary.state)), [headlessQuerySummary]);
4036

4137
const renderBold = (input: string) => (
4238
<Box component="span">

0 commit comments

Comments
 (0)