@graphcommerce/algolia-personalization 9.0.0-canary.73 → 9.0.0-canary.74
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -0
- package/Config.graphqls +10 -0
- package/README.md +31 -2
- package/hooks/useSendAlgoliaEvent.ts +8 -8
- package/mesh/getUserToken.ts +12 -0
- package/package.json +9 -9
- package/plugins/InContextInputAlgoliaUserToken.ts +32 -0
- package/plugins/getSearchResultsAnalytics.ts +20 -0
- package/plugins/getSearchResultsPersonalization.ts +18 -0
- package/plugins/getSearchSuggestionsAnalytics.ts +18 -0
- package/plugins/getSearchSuggestionsPersonalization.ts +16 -0
- package/plugins/meshConfigAlgoliaInsights.ts +6 -12
- package/plugins/useSendEventAlgolia.ts +1 -0
- package/schema/InContext_algoliaUserToken.graphqls +3 -0
package/CHANGELOG.md
CHANGED
package/Config.graphqls
ADDED
package/README.md
CHANGED
|
@@ -1,7 +1,36 @@
|
|
|
1
1
|
# Algolia Magento 2
|
|
2
2
|
|
|
3
|
-
An implementation of Algolia personalization through the GraphQL Mesh.
|
|
3
|
+
An implementation of Algolia personalization/Analytics through the GraphQL Mesh.
|
|
4
4
|
|
|
5
5
|
## Prerequisites
|
|
6
6
|
|
|
7
|
-
Make sure you have configured @graphcommerce/algolia-mesh
|
|
7
|
+
1. Make sure you have configured @graphcommerce/algolia-mesh.
|
|
8
|
+
2. As this is an extension of the google-datalayer. Make sure you have installed
|
|
9
|
+
@graphcommerce/google-datalayer
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
1. Find current version of your `@graphcommerce/next-ui` in your package.json.
|
|
14
|
+
2. `yarn add @graphcommerce/algolia-personalization@9.0.0` (replace 9.0.0 with
|
|
15
|
+
the version of the step above)
|
|
16
|
+
|
|
17
|
+
## Configuration
|
|
18
|
+
|
|
19
|
+
1. See [Config](./Config.graphqls) for the configuration values. Add the
|
|
20
|
+
following to your graphcommerce.config.js:
|
|
21
|
+
|
|
22
|
+
```js
|
|
23
|
+
const config = {
|
|
24
|
+
// Even if you do not use personalization, enabling analytics still allows you to track events in Algolia.
|
|
25
|
+
algoliaEnableAnalytics: true,
|
|
26
|
+
algoliaPersonalizationEnabled: true,
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
2. In your algolia dashboard make sure, you have personalization enabled.
|
|
31
|
+
3. Continue to browse the site, and make sure your events are logged in de event
|
|
32
|
+
debugger in your algolia dashboard. In the under left corner.
|
|
33
|
+
`Data Sources > Events > Debugger`. Once you've collected several events, you
|
|
34
|
+
can start configuring personalization
|
|
35
|
+
4. in `Enchance > personalization` setup the strategies. Note: if you can't find
|
|
36
|
+
some events, make sure you have send several.
|
|
@@ -19,6 +19,8 @@ const getSHA256Hash = async (input: string) => {
|
|
|
19
19
|
return hash
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
export const ALGOLIA_USER_TOKEN_COOKIE_NAME = '_algolia_userToken'
|
|
23
|
+
|
|
22
24
|
function mapSelectedFiltersToAlgoliaEvent(filters: ProductFilterParams['filters']) {
|
|
23
25
|
const flattenedFilters: string[] = []
|
|
24
26
|
|
|
@@ -88,7 +90,6 @@ const dataLayerToAlgoliaMap: {
|
|
|
88
90
|
|
|
89
91
|
const events: AlgoliaEventsItems_Input[] = []
|
|
90
92
|
|
|
91
|
-
console.log(queryID)
|
|
92
93
|
if (
|
|
93
94
|
// Values of filters are different when using Algolia vs Magento, thus it does not make sense to send the filters
|
|
94
95
|
queryID &&
|
|
@@ -100,7 +101,7 @@ const dataLayerToAlgoliaMap: {
|
|
|
100
101
|
const newlyAppliedFilters = filters.filter((filter) => !prevFilters.includes(filter))
|
|
101
102
|
prevFilters = filters
|
|
102
103
|
|
|
103
|
-
if (newlyAppliedFilters) {
|
|
104
|
+
if (newlyAppliedFilters.length > 0) {
|
|
104
105
|
events.push({
|
|
105
106
|
Clicked_filters_Input: {
|
|
106
107
|
eventName: `${eventName}_filter_diff`,
|
|
@@ -309,7 +310,6 @@ export function useSendAlgoliaEvent() {
|
|
|
309
310
|
const client = useApolloClient()
|
|
310
311
|
const index = useAlgoliaIndexName()
|
|
311
312
|
const algoliaQuery = useAlgoliaQuery()
|
|
312
|
-
console.log(algoliaQuery)
|
|
313
313
|
|
|
314
314
|
const eventsBuffer = useRef<AlgoliaEventsItems_Input[]>([])
|
|
315
315
|
const submit = useDebounce(
|
|
@@ -350,16 +350,16 @@ export function useSendAlgoliaEvent() {
|
|
|
350
350
|
return useEventCallback<typeof sendEvent>(async (eventName, eventData) => {
|
|
351
351
|
const email = client.cache.readQuery({ query: CustomerDocument })?.customer?.email
|
|
352
352
|
const authenticatedUserToken = email ? await getSHA256Hash(email) : undefined
|
|
353
|
-
let userToken = cookie(
|
|
353
|
+
let userToken = cookie(ALGOLIA_USER_TOKEN_COOKIE_NAME)
|
|
354
354
|
if (!userToken) {
|
|
355
355
|
userToken = (Math.random() + 1).toString(36).substring(2)
|
|
356
|
-
cookie(
|
|
356
|
+
cookie(ALGOLIA_USER_TOKEN_COOKIE_NAME, userToken, { sameSite: true })
|
|
357
357
|
}
|
|
358
358
|
|
|
359
359
|
// todo check if valid
|
|
360
|
-
if (authenticatedUserToken) {
|
|
361
|
-
|
|
362
|
-
}
|
|
360
|
+
// if (authenticatedUserToken) {
|
|
361
|
+
// userToken = authenticatedUserToken
|
|
362
|
+
// }
|
|
363
363
|
|
|
364
364
|
const events = dataLayerToAlgoliaMap[eventName]?.(eventName, eventData, {
|
|
365
365
|
index,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { MeshContext } from '@graphcommerce/graphql-mesh'
|
|
2
|
+
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
3
|
+
import { parse } from 'cookie'
|
|
4
|
+
|
|
5
|
+
const ALGOLIA_USER_TOKEN_COOKIE_NAME = '_algolia_userToken'
|
|
6
|
+
export function getUserToken(context: MeshContext): string | undefined {
|
|
7
|
+
const { headers } = context as MeshContext & { headers?: Record<string, string | undefined> }
|
|
8
|
+
|
|
9
|
+
const cookie = headers?.cookie
|
|
10
|
+
if (cookie) return parse(cookie)[ALGOLIA_USER_TOKEN_COOKIE_NAME]
|
|
11
|
+
return undefined
|
|
12
|
+
}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@graphcommerce/algolia-personalization",
|
|
3
3
|
"homepage": "https://www.graphcommerce.org/",
|
|
4
4
|
"repository": "github:graphcommerce-org/graphcommerce",
|
|
5
|
-
"version": "9.0.0-canary.
|
|
5
|
+
"version": "9.0.0-canary.74",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"prettier": "@graphcommerce/prettier-config-pwa",
|
|
8
8
|
"eslintConfig": {
|
|
@@ -15,14 +15,14 @@
|
|
|
15
15
|
"generate-insights": "tsx scripts/generate-insights-spec.mts"
|
|
16
16
|
},
|
|
17
17
|
"peerDependencies": {
|
|
18
|
-
"@graphcommerce/algolia-mesh": "^9.0.0-canary.
|
|
19
|
-
"@graphcommerce/google-datalayer": "^9.0.0-canary.
|
|
20
|
-
"@graphcommerce/graphql": "^9.0.0-canary.
|
|
21
|
-
"@graphcommerce/graphql-mesh": "^9.0.0-canary.
|
|
22
|
-
"@graphcommerce/magento-customer": "^9.0.0-canary.
|
|
23
|
-
"@graphcommerce/magento-product": "^9.0.0-canary.
|
|
24
|
-
"@graphcommerce/next-config": "^9.0.0-canary.
|
|
25
|
-
"@graphcommerce/next-ui": "^9.0.0-canary.
|
|
18
|
+
"@graphcommerce/algolia-mesh": "^9.0.0-canary.74",
|
|
19
|
+
"@graphcommerce/google-datalayer": "^9.0.0-canary.74",
|
|
20
|
+
"@graphcommerce/graphql": "^9.0.0-canary.74",
|
|
21
|
+
"@graphcommerce/graphql-mesh": "^9.0.0-canary.74",
|
|
22
|
+
"@graphcommerce/magento-customer": "^9.0.0-canary.74",
|
|
23
|
+
"@graphcommerce/magento-product": "^9.0.0-canary.74",
|
|
24
|
+
"@graphcommerce/next-config": "^9.0.0-canary.74",
|
|
25
|
+
"@graphcommerce/next-ui": "^9.0.0-canary.74",
|
|
26
26
|
"react": "^18.2.0"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type getInContextInput as getInContextInputType,
|
|
3
|
+
type useInContextInput as useInContextInputType,
|
|
4
|
+
} from '@graphcommerce/graphql'
|
|
5
|
+
import type { InContextInput } from '@graphcommerce/graphql-mesh'
|
|
6
|
+
import type { FunctionPlugin, PluginConfig } from '@graphcommerce/next-config'
|
|
7
|
+
import { cookie } from '@graphcommerce/next-ui'
|
|
8
|
+
import { ALGOLIA_USER_TOKEN_COOKIE_NAME } from '../hooks/useSendAlgoliaEvent'
|
|
9
|
+
|
|
10
|
+
export const config: PluginConfig = {
|
|
11
|
+
type: 'function',
|
|
12
|
+
module: '@graphcommerce/graphql',
|
|
13
|
+
ifConfig: 'algolia.personalizationEnabled',
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const getInContextInput: FunctionPlugin<typeof getInContextInputType> = (
|
|
17
|
+
prev,
|
|
18
|
+
client,
|
|
19
|
+
...args
|
|
20
|
+
) => {
|
|
21
|
+
const algoliaUserToken = cookie(ALGOLIA_USER_TOKEN_COOKIE_NAME)
|
|
22
|
+
const res = prev(client, ...args)
|
|
23
|
+
if (!algoliaUserToken) return res
|
|
24
|
+
return { ...res, algoliaUserToken } satisfies InContextInput
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const useInContextInput: FunctionPlugin<typeof useInContextInputType> = (prev, ...args) => {
|
|
28
|
+
const algoliaUserToken = cookie(ALGOLIA_USER_TOKEN_COOKIE_NAME)
|
|
29
|
+
const res = prev(...args)
|
|
30
|
+
if (!algoliaUserToken) return res
|
|
31
|
+
return { ...res, algoliaUserToken }
|
|
32
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { getSearchResultsInput as getSearchResultsInputType } from '@graphcommerce/algolia-mesh'
|
|
2
|
+
import { FunctionPlugin, PluginConfig } from '@graphcommerce/next-config'
|
|
3
|
+
import { getUserToken } from '../mesh/getUserToken'
|
|
4
|
+
|
|
5
|
+
export const config: PluginConfig = {
|
|
6
|
+
type: 'function',
|
|
7
|
+
module: '@graphcommerce/algolia-mesh',
|
|
8
|
+
ifConfig: 'algolia.personalizationEnabled',
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const getSearchResultsInput: FunctionPlugin<typeof getSearchResultsInputType> = async (
|
|
12
|
+
prev,
|
|
13
|
+
args,
|
|
14
|
+
context,
|
|
15
|
+
) => ({
|
|
16
|
+
...(await prev(args, context)),
|
|
17
|
+
clickAnalytics: true,
|
|
18
|
+
analytics: true,
|
|
19
|
+
userToken: getUserToken(context),
|
|
20
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { getSearchResultsInput as getSearchResultsInputType } from '@graphcommerce/algolia-mesh'
|
|
2
|
+
import { FunctionPlugin, PluginConfig } from '@graphcommerce/next-config'
|
|
3
|
+
|
|
4
|
+
export const config: PluginConfig = {
|
|
5
|
+
type: 'function',
|
|
6
|
+
module: '@graphcommerce/algolia-mesh',
|
|
7
|
+
ifConfig: 'algolia.personalizationEnabled',
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const getSearchResultsInput: FunctionPlugin<typeof getSearchResultsInputType> = async (
|
|
11
|
+
prev,
|
|
12
|
+
args,
|
|
13
|
+
context,
|
|
14
|
+
) => ({
|
|
15
|
+
...(await prev(args, context)),
|
|
16
|
+
// enablePersonalization: true,
|
|
17
|
+
// personalizationImpact: 50,
|
|
18
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { getSearchSuggestionsInput as getSearchSuggestionsInputType } from '@graphcommerce/algolia-mesh'
|
|
2
|
+
import { FunctionPlugin, PluginConfig } from '@graphcommerce/next-config'
|
|
3
|
+
import { getUserToken } from '../mesh/getUserToken'
|
|
4
|
+
|
|
5
|
+
export const config: PluginConfig = {
|
|
6
|
+
type: 'function',
|
|
7
|
+
module: '@graphcommerce/algolia-mesh',
|
|
8
|
+
ifConfig: 'algolia.analyticsEnabled',
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const getSearchSuggestionsInput: FunctionPlugin<
|
|
12
|
+
typeof getSearchSuggestionsInputType
|
|
13
|
+
> = async (prev, search, context) => ({
|
|
14
|
+
...(await prev(search, context)),
|
|
15
|
+
clickAnalytics: true,
|
|
16
|
+
analytics: true,
|
|
17
|
+
userToken: getUserToken(context),
|
|
18
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { getSearchSuggestionsInput as getSearchSuggestionsInputType } from '@graphcommerce/algolia-mesh'
|
|
2
|
+
import { FunctionPlugin, PluginConfig } from '@graphcommerce/next-config'
|
|
3
|
+
|
|
4
|
+
export const config: PluginConfig = {
|
|
5
|
+
type: 'function',
|
|
6
|
+
module: '@graphcommerce/algolia-mesh',
|
|
7
|
+
ifConfig: 'algolia.personalizationEnabled',
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const getSearchSuggestionsInput: FunctionPlugin<
|
|
11
|
+
typeof getSearchSuggestionsInputType
|
|
12
|
+
> = async (prev, search, context) => ({
|
|
13
|
+
...(await prev(search, context)),
|
|
14
|
+
// enablePersonalization: true,
|
|
15
|
+
// personalizationImpact: 50,
|
|
16
|
+
})
|
|
@@ -10,13 +10,8 @@ export const meshConfig: FunctionPlugin<typeof meshConfigBase> = (
|
|
|
10
10
|
prev,
|
|
11
11
|
baseConfig,
|
|
12
12
|
graphCommerceConfig,
|
|
13
|
-
) =>
|
|
14
|
-
|
|
15
|
-
console.log('Algolia credentials not provided, skipping Algolia plugin')
|
|
16
|
-
return prev(baseConfig, graphCommerceConfig)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return prev(
|
|
13
|
+
) =>
|
|
14
|
+
prev(
|
|
20
15
|
{
|
|
21
16
|
...baseConfig,
|
|
22
17
|
sources: [
|
|
@@ -29,12 +24,12 @@ export const meshConfig: FunctionPlugin<typeof meshConfigBase> = (
|
|
|
29
24
|
source: '@graphcommerce/algolia-personalization/algolia-insights-spec.yaml',
|
|
30
25
|
ignoreErrorResponses: true,
|
|
31
26
|
schemaHeaders: {
|
|
32
|
-
'X-Algolia-Application-Id': graphCommerceConfig.
|
|
33
|
-
'X-Algolia-API-Key': graphCommerceConfig.
|
|
27
|
+
'X-Algolia-Application-Id': graphCommerceConfig.algolia.applicationId,
|
|
28
|
+
'X-Algolia-API-Key': graphCommerceConfig.algolia.searchOnlyApiKey,
|
|
34
29
|
},
|
|
35
30
|
operationHeaders: {
|
|
36
|
-
'X-Algolia-Application-Id': graphCommerceConfig.
|
|
37
|
-
'X-Algolia-API-Key': graphCommerceConfig.
|
|
31
|
+
'X-Algolia-Application-Id': graphCommerceConfig.algolia.applicationId,
|
|
32
|
+
'X-Algolia-API-Key': graphCommerceConfig.algolia.searchOnlyApiKey,
|
|
38
33
|
},
|
|
39
34
|
selectQueryOrMutationField: [
|
|
40
35
|
{ type: 'Query', fieldName: 'searchSingleIndex' },
|
|
@@ -69,4 +64,3 @@ export const meshConfig: FunctionPlugin<typeof meshConfigBase> = (
|
|
|
69
64
|
},
|
|
70
65
|
graphCommerceConfig,
|
|
71
66
|
)
|
|
72
|
-
}
|
|
@@ -9,6 +9,7 @@ import { useSendAlgoliaEvent } from '../hooks/useSendAlgoliaEvent'
|
|
|
9
9
|
export const config: PluginConfig = {
|
|
10
10
|
module: '@graphcommerce/google-datalayer',
|
|
11
11
|
type: 'function',
|
|
12
|
+
ifConfig: 'algolia.analyticsEnabled',
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export const useSendEvent: FunctionPlugin<typeof useSendEventBase> = (prev) => {
|