@sanity/personalization-plugin 2.4.1 → 2.4.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/README.md +107 -5
- package/dist/index.js +7 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +7 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +20 -20
- package/src/components/ExperimentItem.tsx +10 -0
- package/src/components/VariantInput.tsx +2 -1
- package/src/fieldExperiments.tsx +3 -1
package/README.md
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
## Previously know as @sanity/personalisation-plugin
|
|
4
4
|
|
|
5
|
-
> This is a **Sanity Studio v3** plugin.
|
|
6
|
-
|
|
7
5
|
This plugin allows users to add a/b/n testing experiments to individual fields.
|
|
8
6
|
|
|
9
7
|

|
|
@@ -20,6 +18,7 @@ Once configured you can query the values using the ids of the experiment and var
|
|
|
20
18
|
- [Validation of individual array items](#validation-of-individual-array-items)
|
|
21
19
|
- [Shape of stored data](#shape-of-stored-data)
|
|
22
20
|
- [Querying data](#querying-data)
|
|
21
|
+
- [Split testing](#split-testing)
|
|
23
22
|
- [Using experiment fields in an array](#using-experiment-fields-in-an-array)
|
|
24
23
|
- [License](#license)
|
|
25
24
|
- [Develop \& test](#develop--test)
|
|
@@ -208,7 +207,6 @@ This would also create two new fields in your schema.
|
|
|
208
207
|
|
|
209
208
|
Note that the name key in the field gets rewritten to value and is instead used to name the object field.
|
|
210
209
|
|
|
211
|
-
|
|
212
210
|
## Validation of individual array items
|
|
213
211
|
|
|
214
212
|
You may wish to validate individual fields for various reasons. From the variant array field, add a validation rule that can look through all the array items, and return item-specific validation messages at the path of that array item.
|
|
@@ -260,7 +258,8 @@ The custom input contains buttons which will add new array items with the experi
|
|
|
260
258
|
}
|
|
261
259
|
```
|
|
262
260
|
|
|
263
|
-
Querying data
|
|
261
|
+
## Querying data
|
|
262
|
+
|
|
264
263
|
Using GROQ filters you can query for a specific experiment, with a fallback to default value like so:
|
|
265
264
|
|
|
266
265
|
```ts
|
|
@@ -269,9 +268,112 @@ Using GROQ filters you can query for a specific experiment, with a fallback to d
|
|
|
269
268
|
}
|
|
270
269
|
```
|
|
271
270
|
|
|
271
|
+
## Split testing
|
|
272
|
+
|
|
273
|
+
Split testing involves splitting traffic for one url over 2+ pages, this is used when you want to test more than just a single field in an experiment.
|
|
274
|
+
|
|
275
|
+
### Studio Setup
|
|
276
|
+
|
|
277
|
+
To do split testing using this plugin define a type that can store a url path
|
|
278
|
+
|
|
279
|
+
```ts
|
|
280
|
+
export const path = defineType({
|
|
281
|
+
name: 'path',
|
|
282
|
+
type: 'string',
|
|
283
|
+
validation: (Rule) =>
|
|
284
|
+
Rule.required().custom(async (value: string | undefined, context) => {
|
|
285
|
+
if (!value) return true
|
|
286
|
+
if (!value.startsWith('/')) return 'Must start with "/"'
|
|
287
|
+
return true
|
|
288
|
+
}),
|
|
289
|
+
})
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
add the type to the studio `schema.types` and the plugin config fields:
|
|
293
|
+
|
|
294
|
+
```ts
|
|
295
|
+
fieldLevelExperiments({
|
|
296
|
+
fields: ['path', ...otherFields],
|
|
297
|
+
experiments: getExperiments,
|
|
298
|
+
}),
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
and then create a document type that stores the routing information:
|
|
302
|
+
|
|
303
|
+
```ts
|
|
304
|
+
export const routing = defineType({
|
|
305
|
+
name: 'routing',
|
|
306
|
+
type: 'document',
|
|
307
|
+
title: 'Routing Experiments',
|
|
308
|
+
fields: [
|
|
309
|
+
{
|
|
310
|
+
name: 'pathExperiment',
|
|
311
|
+
type: 'experimentPath',
|
|
312
|
+
initialValue: {active: true},
|
|
313
|
+
},
|
|
314
|
+
],
|
|
315
|
+
preview: {
|
|
316
|
+
select: {
|
|
317
|
+
path: 'pathExperiment.default',
|
|
318
|
+
experiment: 'pathExperiment.experimentId',
|
|
319
|
+
},
|
|
320
|
+
prepare({path, experiment}) {
|
|
321
|
+
return {
|
|
322
|
+
title: `${path} - ${experiment}`,
|
|
323
|
+
}
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
})
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### Frontend usage
|
|
330
|
+
|
|
331
|
+
In your frontend you will need some middleware that can intercept the request for the page and check if the route is included in your split page testing and if so decide which page the user should see.
|
|
332
|
+
|
|
333
|
+
In Next.js Middleware it could be something like
|
|
334
|
+
|
|
335
|
+
```ts
|
|
336
|
+
const ROUTING_QUERY = defineQuery(`*[
|
|
337
|
+
_type == "routing" &&
|
|
338
|
+
pathExperiment.default == $path
|
|
339
|
+
][0]{
|
|
340
|
+
"route": coalesce(pathExperiment.variants[experimentId == $experimentId && variantId == $variantId][0].value, pathExperiment.default)
|
|
341
|
+
}`)
|
|
342
|
+
|
|
343
|
+
export async function middleware(request: NextRequest) {
|
|
344
|
+
let response = NextResponse.next()
|
|
345
|
+
const path = request.nextUrl.pathname
|
|
346
|
+
// getExperimentValue is a function that will determine a variant based on some user properties
|
|
347
|
+
const {variant} = await getExperimentValue(path)
|
|
348
|
+
|
|
349
|
+
const queryParams = {
|
|
350
|
+
path,
|
|
351
|
+
experimentId: 'homepage',
|
|
352
|
+
variantId: variant?.id || '',
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Instead of doing a query for every page this could be queried at build time and stored in a file.
|
|
356
|
+
const data = await client.fetch(ROUTING_QUERY, queryParams)
|
|
357
|
+
if (data?.route) {
|
|
358
|
+
const url = request.nextUrl.clone()
|
|
359
|
+
url.pathname = data.route
|
|
360
|
+
const rewrite = NextResponse.rewrite(url)
|
|
361
|
+
return rewrite
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return response
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export const config = {
|
|
368
|
+
//only run the middleware on pages
|
|
369
|
+
matcher: ['/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)'],
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
272
373
|
## Using experiment fields in an array
|
|
273
374
|
|
|
274
375
|
You may want to add experiment fields as a type with in an array in oder to do this you would need to set an initial value for the experiment to active the schema would be something like:
|
|
376
|
+
|
|
275
377
|
```ts
|
|
276
378
|
defineField({
|
|
277
379
|
name: 'components',
|
|
@@ -286,7 +388,7 @@ defineField({
|
|
|
286
388
|
}),
|
|
287
389
|
```
|
|
288
390
|
|
|
289
|
-
You can then use a groq filter to return the base version of you array member so you don't have to create an experiment specific version
|
|
391
|
+
You can then use a groq filter to return the base version of you array member so you don't have to create an experiment specific version
|
|
290
392
|
|
|
291
393
|
```ts
|
|
292
394
|
*[
|
package/dist/index.js
CHANGED
|
@@ -201,8 +201,11 @@ const ArrayInput = (props) => {
|
|
|
201
201
|
props.experimentNameOverride,
|
|
202
202
|
"s"
|
|
203
203
|
] }) });
|
|
204
|
+
}, ExperimentItem = (props) => {
|
|
205
|
+
const { active } = props.value;
|
|
206
|
+
return active || props.inputProps.onChange(sanity.set(!0, ["active"])), props.renderDefault(props);
|
|
204
207
|
}, VariantInput = (props) => {
|
|
205
|
-
const defaultValue = sanity.useFormValue([
|
|
208
|
+
const experimentPath = props.path.slice(0, -2), defaultValue = sanity.useFormValue([...experimentPath, "default"]), handleClick = () => {
|
|
206
209
|
props.onChange(sanity.set(defaultValue, ["value"]));
|
|
207
210
|
};
|
|
208
211
|
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 3, children: [
|
|
@@ -278,7 +281,8 @@ const createExperimentType = ({
|
|
|
278
281
|
experimentNameOverride,
|
|
279
282
|
variantNameOverride
|
|
280
283
|
}
|
|
281
|
-
)
|
|
284
|
+
),
|
|
285
|
+
item: ExperimentItem
|
|
282
286
|
},
|
|
283
287
|
fields: [
|
|
284
288
|
typeof field == "string" ? (
|
|
@@ -344,7 +348,7 @@ const createExperimentType = ({
|
|
|
344
348
|
experiment: experimentId
|
|
345
349
|
},
|
|
346
350
|
prepare: ({ base, experiment }) => {
|
|
347
|
-
const title = base?.title || base?.name || "", experimentTitle = experiment ? `Experiment: ${experiment}` : "", media = base?.image || base?.photo || base?.media || "";
|
|
351
|
+
const title = base?.title || base?.name || typeof base == "string" ? base : "", experimentTitle = experiment ? `Experiment: ${experiment}` : "", media = base?.image || base?.photo || base?.media || "";
|
|
348
352
|
return {
|
|
349
353
|
title: title || experimentTitle,
|
|
350
354
|
subtitle: title ? experimentTitle : "",
|