@reactionary/source 0.3.0 → 0.3.2
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/core/src/client/client-builder.ts +3 -7
- package/core/src/client/client.ts +2 -3
- package/core/src/decorators/reactionary.decorator.ts +2 -2
- package/core/src/initialization.ts +11 -3
- package/core/src/providers/analytics.provider.ts +75 -0
- package/core/src/providers/cart.provider.ts +3 -0
- package/core/src/providers/category.provider.ts +1 -0
- package/core/src/providers/identity.provider.ts +5 -0
- package/core/src/schemas/errors/invalid-input.error.ts +1 -1
- package/core/src/schemas/errors/invalid-output.error.ts +1 -1
- package/core/src/schemas/models/identifiers.model.ts +3 -0
- package/core/src/schemas/models/order.model.ts +2 -2
- package/core/src/schemas/mutations/analytics/index.ts +23 -0
- package/core/src/schemas/mutations/analytics/product-add-to-cart.mutation.ts +25 -0
- package/core/src/schemas/mutations/analytics/product-details-view.mutation.ts +14 -0
- package/core/src/schemas/mutations/analytics/product-summary-click.mutation.ts +26 -0
- package/core/src/schemas/mutations/analytics/product-summary-view.mutation.ts +25 -0
- package/core/src/schemas/mutations/analytics/purchase.mutation.ts +14 -0
- package/core/src/schemas/mutations/index.ts +1 -1
- package/core/src/schemas/queries/order-search.query.ts +3 -0
- package/core/src/schemas/session.schema.ts +21 -9
- package/core/src/test/client-builder.spec.ts +60 -0
- package/core/src/zod-utils.ts +3 -1
- package/documentation/{1-purpose.md → docs/1-purpose.md} +4 -0
- package/documentation/docs/8-tracking.md +9 -0
- package/documentation/docs/providers/analytics.provider.md +297 -0
- package/documentation/docs/providers/base.provider.md +118 -0
- package/documentation/docs/providers/cart.provider.md +305 -0
- package/documentation/docs/providers/category.provider.md +244 -0
- package/documentation/docs/providers/checkout.provider.md +315 -0
- package/documentation/docs/providers/identity.provider.md +194 -0
- package/documentation/docs/providers/inventory.provider.md +162 -0
- package/documentation/docs/providers/order-search.provider.md +155 -0
- package/documentation/docs/providers/order.provider.md +160 -0
- package/documentation/docs/providers/price.provider.md +197 -0
- package/documentation/docs/providers/product-search.provider.md +265 -0
- package/documentation/docs/providers/product.provider.md +204 -0
- package/documentation/docs/providers/profile.provider.md +283 -0
- package/documentation/docs/providers/store.provider.md +146 -0
- package/documentation/docs/schemas/schemas.md +1862 -0
- package/documentation/docusaurus.config.js +33 -0
- package/documentation/scripts/generate.ts +52 -0
- package/documentation/sidebars.js +8 -0
- package/documentation/src/css/custom.css +3 -0
- package/documentation/src/pages/index.js +12 -0
- package/eslint.config.mjs +1 -1
- package/examples/node/package.json +6 -6
- package/examples/node/src/basic/basic-node-provider-model-extension.spec.ts +0 -2
- package/examples/node/src/basic/client-creation.spec.ts +2 -2
- package/package.json +19 -5
- package/providers/algolia/README.md +12 -4
- package/providers/algolia/project.json +1 -1
- package/providers/algolia/src/core/initialize.ts +7 -2
- package/providers/algolia/src/providers/analytics.provider.ts +114 -0
- package/providers/algolia/src/providers/index.ts +1 -0
- package/providers/algolia/src/providers/product-search.provider.ts +5 -4
- package/providers/algolia/src/test/analytics.spec.ts +138 -0
- package/providers/commercetools/project.json +1 -1
- package/providers/commercetools/src/providers/identity.provider.ts +8 -1
- package/providers/commercetools/src/providers/profile.provider.ts +1 -4
- package/providers/commercetools/src/test/caching.spec.ts +3 -3
- package/providers/commercetools/src/test/identity.spec.ts +2 -2
- package/providers/fake/project.json +1 -1
- package/providers/fake/src/providers/analytics.provider.ts +5 -0
- package/providers/fake/src/providers/checkout.provider.ts +5 -2
- package/providers/fake/src/providers/product.provider.ts +18 -8
- package/providers/fake/src/test/cart.provider.spec.ts +0 -2
- package/providers/fake/src/test/category.provider.spec.ts +3 -3
- package/providers/fake/src/test/checkout.provider.spec.ts +3 -7
- package/providers/google-analytics/README.md +11 -0
- package/providers/google-analytics/eslint.config.mjs +25 -0
- package/providers/google-analytics/package.json +12 -0
- package/providers/google-analytics/project.json +33 -0
- package/providers/google-analytics/src/core/initialize.ts +16 -0
- package/providers/google-analytics/src/index.ts +4 -0
- package/providers/google-analytics/src/providers/analytics.provider.ts +162 -0
- package/providers/google-analytics/src/schema/capabilities.schema.ts +10 -0
- package/providers/google-analytics/src/schema/configuration.schema.ts +9 -0
- package/providers/google-analytics/src/test/analytics.provider.spec.ts +93 -0
- package/providers/google-analytics/tsconfig.json +24 -0
- package/providers/google-analytics/tsconfig.lib.json +23 -0
- package/providers/google-analytics/tsconfig.spec.json +28 -0
- package/providers/google-analytics/vite.config.ts +26 -0
- package/providers/google-analytics/vitest.config.mts +21 -0
- package/providers/medusa/package.json +3 -10
- package/providers/medusa/project.json +1 -1
- package/providers/medusa/src/providers/identity.provider.ts +34 -10
- package/providers/medusa/src/providers/profile.provider.ts +5 -15
- package/providers/medusa/src/test/test-utils.ts +0 -1
- package/providers/medusa/tsconfig.json +3 -0
- package/providers/medusa/tsconfig.lib.json +16 -1
- package/providers/meilisearch/project.json +1 -1
- package/providers/posthog/project.json +1 -1
- package/tsconfig.base.json +4 -1
- package/.claude/settings.local.json +0 -28
- package/core/src/schemas/mutations/analytics.mutation.ts +0 -23
- package/providers/algolia/src/test/test-utils.ts +0 -31
- /package/documentation/{2-getting-started.md → docs/2-getting-started.md} +0 -0
- /package/documentation/{3-querying-and-changing-data.md → docs/3-querying-and-changing-data.md} +0 -0
- /package/documentation/{4-product-data.md → docs/4-product-data.md} +0 -0
- /package/documentation/{5-cart-and-checkout.md → docs/5-cart-and-checkout.md} +0 -0
- /package/documentation/{6-product-search.md → docs/6-product-search.md} +0 -0
- /package/documentation/{7-marketing.md → docs/7-marketing.md} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import 'dotenv/config';
|
|
2
2
|
import { describe, expect, it } from 'vitest';
|
|
3
|
-
import {
|
|
3
|
+
import { CommercetoolsAPI } from '../core/client.js';
|
|
4
4
|
import { getCommercetoolsTestConfiguration } from './test-utils.js';
|
|
5
5
|
import {
|
|
6
6
|
createInitialRequestContext,
|
|
@@ -12,7 +12,7 @@ import type { CommercetoolsConfiguration } from '../schema/configuration.schema.
|
|
|
12
12
|
function setup() {
|
|
13
13
|
const config = getCommercetoolsTestConfiguration();
|
|
14
14
|
const context = createInitialRequestContext();
|
|
15
|
-
const root = new
|
|
15
|
+
const root = new CommercetoolsAPI(config, context);
|
|
16
16
|
|
|
17
17
|
return {
|
|
18
18
|
config,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
AnalyticsMutation,
|
|
2
3
|
Cache,
|
|
3
4
|
RequestContext} from '@reactionary/core';
|
|
4
5
|
import {
|
|
@@ -15,4 +16,8 @@ export class FakeAnalyticsProvider extends AnalyticsProvider {
|
|
|
15
16
|
|
|
16
17
|
this.config = config;
|
|
17
18
|
}
|
|
19
|
+
|
|
20
|
+
public override async track(event: AnalyticsMutation): Promise<void> {
|
|
21
|
+
// No-op
|
|
22
|
+
}
|
|
18
23
|
}
|
|
@@ -29,9 +29,12 @@ import {
|
|
|
29
29
|
CheckoutMutationFinalizeCheckoutSchema,
|
|
30
30
|
success,
|
|
31
31
|
type CheckoutIdentifier,
|
|
32
|
+
PaymentMethodSchema,
|
|
33
|
+
ShippingMethodSchema,
|
|
32
34
|
} from '@reactionary/core';
|
|
33
35
|
import type { FakeConfiguration } from '../schema/configuration.schema.js';
|
|
34
36
|
import { base, en, Faker } from '@faker-js/faker';
|
|
37
|
+
import z from 'zod';
|
|
35
38
|
|
|
36
39
|
export class FakeCheckoutProvider extends CheckoutProvider {
|
|
37
40
|
protected config: FakeConfiguration;
|
|
@@ -90,7 +93,7 @@ export class FakeCheckoutProvider extends CheckoutProvider {
|
|
|
90
93
|
|
|
91
94
|
@Reactionary({
|
|
92
95
|
inputSchema: CheckoutQueryForAvailableShippingMethodsSchema,
|
|
93
|
-
outputSchema:
|
|
96
|
+
outputSchema: z.array(ShippingMethodSchema),
|
|
94
97
|
})
|
|
95
98
|
public override async getAvailableShippingMethods(
|
|
96
99
|
payload: CheckoutQueryForAvailableShippingMethods
|
|
@@ -116,7 +119,7 @@ export class FakeCheckoutProvider extends CheckoutProvider {
|
|
|
116
119
|
|
|
117
120
|
@Reactionary({
|
|
118
121
|
inputSchema: CheckoutQueryForAvailablePaymentMethodsSchema,
|
|
119
|
-
outputSchema:
|
|
122
|
+
outputSchema: z.array(PaymentMethodSchema),
|
|
120
123
|
})
|
|
121
124
|
public override async getAvailablePaymentMethods(
|
|
122
125
|
payload: CheckoutQueryForAvailablePaymentMethods
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
type Result,
|
|
15
15
|
error,
|
|
16
16
|
success,
|
|
17
|
-
type NotFoundError
|
|
17
|
+
type NotFoundError,
|
|
18
18
|
} from '@reactionary/core';
|
|
19
19
|
import type z from 'zod';
|
|
20
20
|
import type { FakeConfiguration } from '../schema/configuration.schema.js';
|
|
@@ -23,7 +23,11 @@ import { base, en, Faker } from '@faker-js/faker';
|
|
|
23
23
|
export class FakeProductProvider extends ProductProvider {
|
|
24
24
|
protected config: FakeConfiguration;
|
|
25
25
|
|
|
26
|
-
constructor(
|
|
26
|
+
constructor(
|
|
27
|
+
config: FakeConfiguration,
|
|
28
|
+
cache: ReactinaryCache,
|
|
29
|
+
context: RequestContext
|
|
30
|
+
) {
|
|
27
31
|
super(cache, context);
|
|
28
32
|
|
|
29
33
|
this.config = config;
|
|
@@ -31,7 +35,11 @@ export class FakeProductProvider extends ProductProvider {
|
|
|
31
35
|
|
|
32
36
|
@Reactionary({
|
|
33
37
|
inputSchema: ProductQueryByIdSchema,
|
|
34
|
-
outputSchema: ProductSchema
|
|
38
|
+
outputSchema: ProductSchema,
|
|
39
|
+
cache: true,
|
|
40
|
+
cacheTimeToLiveInSeconds: 300,
|
|
41
|
+
currencyDependentCaching: false,
|
|
42
|
+
localeDependentCaching: true,
|
|
35
43
|
})
|
|
36
44
|
public override async getById(
|
|
37
45
|
payload: ProductQueryById
|
|
@@ -41,7 +49,7 @@ export class FakeProductProvider extends ProductProvider {
|
|
|
41
49
|
|
|
42
50
|
@Reactionary({
|
|
43
51
|
inputSchema: ProductQueryBySlugSchema,
|
|
44
|
-
outputSchema: ProductSchema
|
|
52
|
+
outputSchema: ProductSchema,
|
|
45
53
|
})
|
|
46
54
|
public override async getBySlug(
|
|
47
55
|
payload: ProductQueryBySlug
|
|
@@ -53,7 +61,9 @@ export class FakeProductProvider extends ProductProvider {
|
|
|
53
61
|
inputSchema: ProductQueryBySKUSchema,
|
|
54
62
|
outputSchema: ProductSchema,
|
|
55
63
|
})
|
|
56
|
-
public override async getBySKU(
|
|
64
|
+
public override async getBySKU(
|
|
65
|
+
payload: ProductQueryBySKU
|
|
66
|
+
): Promise<Result<Product>> {
|
|
57
67
|
return success(this.parseSingle(payload.variant.sku));
|
|
58
68
|
}
|
|
59
69
|
|
|
@@ -79,12 +89,12 @@ export class FakeProductProvider extends ProductProvider {
|
|
|
79
89
|
ean: '',
|
|
80
90
|
gtin: '',
|
|
81
91
|
identifier: {
|
|
82
|
-
sku: ''
|
|
92
|
+
sku: '',
|
|
83
93
|
},
|
|
84
94
|
images: [],
|
|
85
95
|
name: '',
|
|
86
96
|
options: [],
|
|
87
|
-
upc: ''
|
|
97
|
+
upc: '',
|
|
88
98
|
},
|
|
89
99
|
description: generator.commerce.productDescription(),
|
|
90
100
|
manufacturer: '',
|
|
@@ -93,7 +103,7 @@ export class FakeProductProvider extends ProductProvider {
|
|
|
93
103
|
published: true,
|
|
94
104
|
sharedAttributes: [],
|
|
95
105
|
variants: [],
|
|
96
|
-
} satisfies Product
|
|
106
|
+
} satisfies Product;
|
|
97
107
|
|
|
98
108
|
return result;
|
|
99
109
|
}
|
|
@@ -72,8 +72,6 @@ describe('Fake Cart Provider', () => {
|
|
|
72
72
|
expect(updatedCart.value.items.length).toBe(1);
|
|
73
73
|
expect(updatedCart.value.items[0].variant.sku).toBe(testData.skuWithoutTiers);
|
|
74
74
|
expect(updatedCart.value.items[0].quantity).toBe(3);
|
|
75
|
-
expect(updatedCart.value.items[0].price.totalPrice.value).toBe(cart.value.items[0].price.totalPrice.value * 3);
|
|
76
|
-
expect(updatedCart.value.items[0].price.unitPrice.value).toBe(cart.value.items[0].price.unitPrice.value);
|
|
77
75
|
});
|
|
78
76
|
|
|
79
77
|
it('should be able to remove an item from a cart', async () => {
|
|
@@ -158,16 +158,16 @@ describe('Faker Category Provider', () => {
|
|
|
158
158
|
expect(result.value.text).not.toBe('');
|
|
159
159
|
});
|
|
160
160
|
|
|
161
|
-
it('returns a
|
|
161
|
+
it('returns a not found error for categories that do not exist', async () => {
|
|
162
162
|
const result = await provider.getById({
|
|
163
163
|
id: { key: 'non-existent-category' },
|
|
164
164
|
});
|
|
165
165
|
|
|
166
|
-
if (
|
|
166
|
+
if (result.success) {
|
|
167
167
|
assert.fail();
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
expect(result.
|
|
170
|
+
expect(result.error.type).toBe('NotFound');
|
|
171
171
|
});
|
|
172
172
|
|
|
173
173
|
describe('caching', () => {
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
import 'dotenv/config';
|
|
2
2
|
import type { RequestContext } from '@reactionary/core';
|
|
3
3
|
import {
|
|
4
|
-
CartSchema,
|
|
5
|
-
IdentitySchema,
|
|
6
4
|
NoOpCache,
|
|
7
5
|
createInitialRequestContext,
|
|
8
6
|
} from '@reactionary/core';
|
|
9
7
|
import { getFakerTestConfiguration } from './test-utils.js';
|
|
10
|
-
import {
|
|
11
|
-
import { FakeIdentityProvider } from '../providers/index.js';
|
|
12
|
-
import { describe, expect, it, beforeAll, beforeEach, assert } from 'vitest';
|
|
8
|
+
import { describe, expect, it, beforeEach, assert } from 'vitest';
|
|
13
9
|
import { FakeCheckoutProvider } from '../providers/checkout.provider.js';
|
|
14
10
|
|
|
15
11
|
describe('Fake Checkout Provider', () => {
|
|
@@ -200,7 +196,7 @@ describe('Fake Checkout Provider', () => {
|
|
|
200
196
|
});
|
|
201
197
|
|
|
202
198
|
if (!result.success) {
|
|
203
|
-
assert.fail();
|
|
199
|
+
assert.fail(JSON.stringify(result.error));
|
|
204
200
|
}
|
|
205
201
|
|
|
206
202
|
expect(result.value.length).toBeGreaterThan(0);
|
|
@@ -214,7 +210,7 @@ describe('Fake Checkout Provider', () => {
|
|
|
214
210
|
});
|
|
215
211
|
|
|
216
212
|
if (!result.success) {
|
|
217
|
-
assert.fail();
|
|
213
|
+
assert.fail(JSON.stringify(result.error));
|
|
218
214
|
}
|
|
219
215
|
|
|
220
216
|
expect(result.value.length).toBeGreaterThan(0);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# google-analytics
|
|
2
|
+
|
|
3
|
+
This library was generated with [Nx](https://nx.dev).
|
|
4
|
+
|
|
5
|
+
## Building
|
|
6
|
+
|
|
7
|
+
Run `nx build google-analytics` to build the library.
|
|
8
|
+
|
|
9
|
+
## Running unit tests
|
|
10
|
+
|
|
11
|
+
Run `nx test google-analytics` to execute the unit tests via [Vitest](https://vitest.dev/).
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import baseConfig from '../../eslint.config.mjs';
|
|
2
|
+
|
|
3
|
+
export default [
|
|
4
|
+
...baseConfig,
|
|
5
|
+
{
|
|
6
|
+
files: ['**/*.json'],
|
|
7
|
+
rules: {
|
|
8
|
+
'@nx/dependency-checks': [
|
|
9
|
+
'error',
|
|
10
|
+
{
|
|
11
|
+
ignoredFiles: [
|
|
12
|
+
'{projectRoot}/eslint.config.{js,cjs,mjs,ts,cts,mts}',
|
|
13
|
+
'{projectRoot}/esbuild.config.{js,ts,mjs,mts}',
|
|
14
|
+
'{projectRoot}/vite.config.{js,ts,mjs,mts}',
|
|
15
|
+
'{projectRoot}/**/*.spec.ts',
|
|
16
|
+
],
|
|
17
|
+
ignoredDependencies: ['vitest', '@nx/vite'],
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
languageOptions: {
|
|
22
|
+
parser: await import('jsonc-eslint-parser'),
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
];
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "google-analytics",
|
|
3
|
+
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
|
4
|
+
"sourceRoot": "providers/google-analytics/src",
|
|
5
|
+
"projectType": "library",
|
|
6
|
+
"release": {
|
|
7
|
+
"version": {
|
|
8
|
+
"manifestRootsToUpdate": ["dist/{projectRoot}"],
|
|
9
|
+
"currentVersionResolver": "git-tag",
|
|
10
|
+
"fallbackCurrentVersionResolver": "disk"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"tags": [],
|
|
14
|
+
"targets": {
|
|
15
|
+
"build": {
|
|
16
|
+
"executor": "@nx/esbuild:esbuild",
|
|
17
|
+
"outputs": ["{options.outputPath}"],
|
|
18
|
+
"options": {
|
|
19
|
+
"outputPath": "dist/providers/google-analytics",
|
|
20
|
+
"main": "providers/google-analytics/src/index.ts",
|
|
21
|
+
"tsConfig": "providers/google-analytics/tsconfig.lib.json",
|
|
22
|
+
"assets": ["providers/google-analytics/*.md"],
|
|
23
|
+
"format": ["esm"],
|
|
24
|
+
"bundle": false
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"nx-release-publish": {
|
|
28
|
+
"options": {
|
|
29
|
+
"packageRoot": "dist/{projectRoot}"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Cache, ClientFromCapabilities, RequestContext } from "@reactionary/core";
|
|
2
|
+
import type { GoogleAnalyticsCapabilities } from "../schema/capabilities.schema.js";
|
|
3
|
+
import type { GoogleAnalyticsConfiguration } from "../schema/configuration.schema.js";
|
|
4
|
+
import { GoogleAnalyticsAnalyticsProvider } from "../providers/analytics.provider.js";
|
|
5
|
+
|
|
6
|
+
export function googleAnalyticsCapabilities<T extends GoogleAnalyticsCapabilities>(configuration: GoogleAnalyticsConfiguration, capabilities: T) {
|
|
7
|
+
return (cache: Cache, context: RequestContext): ClientFromCapabilities<T> => {
|
|
8
|
+
const client: any = {};
|
|
9
|
+
|
|
10
|
+
if (capabilities.analytics) {
|
|
11
|
+
client.analytics = new GoogleAnalyticsAnalyticsProvider(cache, context, configuration);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return client;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AnalyticsProvider,
|
|
3
|
+
type RequestContext,
|
|
4
|
+
type Cache,
|
|
5
|
+
type AnalyticsMutationProductSummaryViewEvent,
|
|
6
|
+
type AnalyticsMutationProductSummaryClickEvent,
|
|
7
|
+
type AnalyticsMutationProductDetailsViewEvent,
|
|
8
|
+
type AnalyticsMutationProductAddToCartEvent,
|
|
9
|
+
type AnalyticsMutationPurchaseEvent,
|
|
10
|
+
} from '@reactionary/core';
|
|
11
|
+
import type { GoogleAnalyticsConfiguration } from '../schema/configuration.schema.js';
|
|
12
|
+
|
|
13
|
+
export class GoogleAnalyticsAnalyticsProvider extends AnalyticsProvider {
|
|
14
|
+
protected config: GoogleAnalyticsConfiguration;
|
|
15
|
+
|
|
16
|
+
constructor(
|
|
17
|
+
cache: Cache,
|
|
18
|
+
context: RequestContext,
|
|
19
|
+
configuration: GoogleAnalyticsConfiguration
|
|
20
|
+
) {
|
|
21
|
+
super(cache, context);
|
|
22
|
+
|
|
23
|
+
this.config = configuration;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
protected override async processProductSummaryView(
|
|
27
|
+
event: AnalyticsMutationProductSummaryViewEvent
|
|
28
|
+
) {
|
|
29
|
+
const gaEvent = {
|
|
30
|
+
client_id: this.context.session.identityContext.personalizationKey,
|
|
31
|
+
user_id: this.context.session.identityContext.personalizationKey,
|
|
32
|
+
events: [
|
|
33
|
+
{
|
|
34
|
+
name: 'view_item_list',
|
|
35
|
+
params: {
|
|
36
|
+
currency: this.context.languageContext.currencyCode,
|
|
37
|
+
items: event.products.map((x) => {
|
|
38
|
+
return {
|
|
39
|
+
item_id: x.key,
|
|
40
|
+
};
|
|
41
|
+
}),
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
await this.sendEvent(gaEvent);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
protected override async processProductSummaryClick(
|
|
51
|
+
event: AnalyticsMutationProductSummaryClickEvent
|
|
52
|
+
) {
|
|
53
|
+
const gaEvent = {
|
|
54
|
+
client_id: this.context.session.identityContext.personalizationKey,
|
|
55
|
+
user_id: this.context.session.identityContext.personalizationKey,
|
|
56
|
+
events: [
|
|
57
|
+
{
|
|
58
|
+
name: 'select_item',
|
|
59
|
+
params: {
|
|
60
|
+
currency: this.context.languageContext.currencyCode,
|
|
61
|
+
items: [
|
|
62
|
+
{
|
|
63
|
+
item_id: event.product.key,
|
|
64
|
+
index: event.position,
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
await this.sendEvent(gaEvent);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
protected override async processProductDetailsView(
|
|
76
|
+
event: AnalyticsMutationProductDetailsViewEvent
|
|
77
|
+
) {
|
|
78
|
+
const gaEvent = {
|
|
79
|
+
client_id: this.context.session.identityContext.personalizationKey,
|
|
80
|
+
user_id: this.context.session.identityContext.personalizationKey,
|
|
81
|
+
events: [
|
|
82
|
+
{
|
|
83
|
+
name: 'view_item',
|
|
84
|
+
params: {
|
|
85
|
+
currency: this.context.languageContext.currencyCode,
|
|
86
|
+
items: [
|
|
87
|
+
{
|
|
88
|
+
item_id: event.product.key,
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
await this.sendEvent(gaEvent);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
protected override async processProductAddToCart(
|
|
100
|
+
event: AnalyticsMutationProductAddToCartEvent
|
|
101
|
+
) {
|
|
102
|
+
const gaEvent = {
|
|
103
|
+
client_id: this.context.session.identityContext.personalizationKey,
|
|
104
|
+
user_id: this.context.session.identityContext.personalizationKey,
|
|
105
|
+
events: [
|
|
106
|
+
{
|
|
107
|
+
name: 'add_to_cart',
|
|
108
|
+
params: {
|
|
109
|
+
currency: this.context.languageContext.currencyCode,
|
|
110
|
+
items: [
|
|
111
|
+
{
|
|
112
|
+
item_id: event.product.key,
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
await this.sendEvent(gaEvent);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
protected override async processPurchase(
|
|
124
|
+
event: AnalyticsMutationPurchaseEvent
|
|
125
|
+
) {
|
|
126
|
+
const gaEvent = {
|
|
127
|
+
client_id: this.context.session.identityContext.personalizationKey,
|
|
128
|
+
user_id: this.context.session.identityContext.personalizationKey,
|
|
129
|
+
events: [
|
|
130
|
+
{
|
|
131
|
+
name: 'purchase',
|
|
132
|
+
params: {
|
|
133
|
+
currency: this.context.languageContext.currencyCode,
|
|
134
|
+
transaction_id: event.order.identifier.key,
|
|
135
|
+
value: event.order.price.grandTotal.value,
|
|
136
|
+
tax: event.order.price.totalTax.value,
|
|
137
|
+
shipping: event.order.price.totalShipping.value,
|
|
138
|
+
items: event.order.items.map((item) => ({
|
|
139
|
+
item_id: item.variant.sku,
|
|
140
|
+
quantity: item.quantity,
|
|
141
|
+
price: item.price.unitPrice.value,
|
|
142
|
+
})),
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
],
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
await this.sendEvent(gaEvent);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
protected async sendEvent(event: unknown) {
|
|
152
|
+
const url = `${this.config.url}?measurement_id=${this.config.measurementId}&api_secret=${this.config.apiSecret}`;
|
|
153
|
+
|
|
154
|
+
await fetch(url, {
|
|
155
|
+
method: 'POST',
|
|
156
|
+
headers: {
|
|
157
|
+
'Content-Type': 'application/json',
|
|
158
|
+
},
|
|
159
|
+
body: JSON.stringify(event),
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { CapabilitiesSchema } from '@reactionary/core';
|
|
2
|
+
import type { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
export const GoogleAnalyticsCapabilitiesSchema = CapabilitiesSchema.pick({
|
|
5
|
+
analytics: true,
|
|
6
|
+
}).partial();
|
|
7
|
+
|
|
8
|
+
export type GoogleAnalyticsCapabilities = z.infer<
|
|
9
|
+
typeof GoogleAnalyticsCapabilitiesSchema
|
|
10
|
+
>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
export const GoogleAnalyticsConfigurationSchema = z.looseObject({
|
|
4
|
+
url: z.string(),
|
|
5
|
+
measurementId: z.string(),
|
|
6
|
+
apiSecret: z.string()
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export type GoogleAnalyticsConfiguration = z.infer<typeof GoogleAnalyticsConfigurationSchema>;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import 'dotenv/config';
|
|
2
|
+
import type { AnalyticsMutationProductSummaryViewEvent, AnalyticsMutationPurchaseEvent, RequestContext } from '@reactionary/core';
|
|
3
|
+
import {
|
|
4
|
+
CartSchema,
|
|
5
|
+
IdentitySchema,
|
|
6
|
+
NoOpCache,
|
|
7
|
+
createInitialRequestContext,
|
|
8
|
+
} from '@reactionary/core';
|
|
9
|
+
import { describe, expect, it, beforeAll, beforeEach, assert } from 'vitest';
|
|
10
|
+
import { GoogleAnalyticsAnalyticsProvider } from '../providers/analytics.provider.js';
|
|
11
|
+
import type { GoogleAnalyticsConfiguration } from '../schema/configuration.schema.js';
|
|
12
|
+
|
|
13
|
+
describe('Google Analytics Analytics Provider', () => {
|
|
14
|
+
let provider: GoogleAnalyticsAnalyticsProvider;
|
|
15
|
+
let reqCtx: RequestContext;
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
reqCtx = createInitialRequestContext();
|
|
19
|
+
const config = {
|
|
20
|
+
apiSecret: process.env['GOOGLE_ANALYTICS_API_SECRET'] || '',
|
|
21
|
+
measurementId: process.env['GOOGLE_ANALYTICS_MEASUREMENT_ID'] || '',
|
|
22
|
+
url: process.env['GOOGLE_ANALYTICS_URL'] || '',
|
|
23
|
+
} satisfies GoogleAnalyticsConfiguration;
|
|
24
|
+
|
|
25
|
+
provider = new GoogleAnalyticsAnalyticsProvider(
|
|
26
|
+
new NoOpCache(),
|
|
27
|
+
reqCtx,
|
|
28
|
+
config
|
|
29
|
+
);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('tracking', () => {
|
|
33
|
+
it('should be able to add an item to a cart', async () => {
|
|
34
|
+
const event = {
|
|
35
|
+
event: 'product-summary-view',
|
|
36
|
+
products: [{
|
|
37
|
+
key: 'P-5000'
|
|
38
|
+
}]
|
|
39
|
+
} satisfies AnalyticsMutationProductSummaryViewEvent;
|
|
40
|
+
|
|
41
|
+
const result = await provider.track(event);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should be able to track a purchase', async () => {
|
|
45
|
+
const event = {
|
|
46
|
+
event: 'purchase',
|
|
47
|
+
order: {
|
|
48
|
+
identifier: { key: 'ORDER-12345' },
|
|
49
|
+
userId: { userId: 'test-user-123' },
|
|
50
|
+
items: [
|
|
51
|
+
{
|
|
52
|
+
identifier: { key: 'item-1' },
|
|
53
|
+
variant: { sku: 'SKU-001' },
|
|
54
|
+
quantity: 2,
|
|
55
|
+
price: {
|
|
56
|
+
unitPrice: { value: 29.99, currency: 'EUR' },
|
|
57
|
+
unitDiscount: { value: 0, currency: 'EUR' },
|
|
58
|
+
totalPrice: { value: 59.98, currency: 'EUR' },
|
|
59
|
+
totalDiscount: { value: 0, currency: 'EUR' },
|
|
60
|
+
},
|
|
61
|
+
inventoryStatus: 'Allocated',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
identifier: { key: 'item-2' },
|
|
65
|
+
variant: { sku: 'SKU-002' },
|
|
66
|
+
quantity: 1,
|
|
67
|
+
price: {
|
|
68
|
+
unitPrice: { value: 49.99, currency: 'EUR' },
|
|
69
|
+
unitDiscount: { value: 5, currency: 'EUR' },
|
|
70
|
+
totalPrice: { value: 44.99, currency: 'EUR' },
|
|
71
|
+
totalDiscount: { value: 5, currency: 'EUR' },
|
|
72
|
+
},
|
|
73
|
+
inventoryStatus: 'Allocated',
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
price: {
|
|
77
|
+
totalTax: { value: 21.99, currency: 'EUR' },
|
|
78
|
+
totalDiscount: { value: 5, currency: 'EUR' },
|
|
79
|
+
totalSurcharge: { value: 0, currency: 'EUR' },
|
|
80
|
+
totalShipping: { value: 4.99, currency: 'EUR' },
|
|
81
|
+
totalProductPrice: { value: 104.97, currency: 'EUR' },
|
|
82
|
+
grandTotal: { value: 126.95, currency: 'EUR' },
|
|
83
|
+
},
|
|
84
|
+
orderStatus: 'AwaitingPayment',
|
|
85
|
+
inventoryStatus: 'Allocated',
|
|
86
|
+
paymentInstructions: [],
|
|
87
|
+
},
|
|
88
|
+
} satisfies AnalyticsMutationPurchaseEvent;
|
|
89
|
+
|
|
90
|
+
await provider.track(event);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"module": "nodenext",
|
|
5
|
+
"moduleResolution": "nodenext",
|
|
6
|
+
"forceConsistentCasingInFileNames": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"importHelpers": true,
|
|
9
|
+
"noImplicitOverride": true,
|
|
10
|
+
"noImplicitReturns": true,
|
|
11
|
+
"noFallthroughCasesInSwitch": true,
|
|
12
|
+
"noPropertyAccessFromIndexSignature": true
|
|
13
|
+
},
|
|
14
|
+
"files": [],
|
|
15
|
+
"include": [],
|
|
16
|
+
"references": [
|
|
17
|
+
{
|
|
18
|
+
"path": "./tsconfig.lib.json"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"path": "./tsconfig.spec.json"
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "../../dist/out-tsc",
|
|
5
|
+
"declaration": true,
|
|
6
|
+
"types": ["node"]
|
|
7
|
+
},
|
|
8
|
+
"include": ["src/**/*.ts"],
|
|
9
|
+
"exclude": [
|
|
10
|
+
"vite.config.ts",
|
|
11
|
+
"vite.config.mts",
|
|
12
|
+
"vitest.config.ts",
|
|
13
|
+
"vitest.config.mts",
|
|
14
|
+
"src/**/*.test.ts",
|
|
15
|
+
"src/**/*.spec.ts",
|
|
16
|
+
"src/**/*.test.tsx",
|
|
17
|
+
"src/**/*.spec.tsx",
|
|
18
|
+
"src/**/*.test.js",
|
|
19
|
+
"src/**/*.spec.js",
|
|
20
|
+
"src/**/*.test.jsx",
|
|
21
|
+
"src/**/*.spec.jsx"
|
|
22
|
+
]
|
|
23
|
+
}
|