@sanity/personalization-plugin 2.4.1 → 2.4.3

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 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
  ![image](./overview.gif)
@@ -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([props.path[0], "default"]), handleClick = () => {
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 : "",