@sanity/personalization-plugin 2.0.0 → 2.1.0-field-names.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/personalization-plugin",
3
- "version": "2.0.0",
3
+ "version": "2.1.0-field-names.1",
4
4
  "description": "Plugin to help with personalization, a/b testing when using Sanity",
5
5
  "keywords": [
6
6
  "sanity",
@@ -8,44 +8,43 @@ import {useExperimentContext} from './ExperimentContext'
8
8
 
9
9
  export const ArrayInput = (props: ArrayInputProps) => {
10
10
  const fieldPath = props.path.slice(0, -1)
11
- const experimentId = useFormValue([...fieldPath, 'experimentId'])
11
+ const {onItemAppend, variantName, variantId, experimentId} = props
12
+ const experimentValue = useFormValue([...fieldPath, experimentId])
12
13
 
13
14
  const {experiments} = useExperimentContext()
14
15
 
15
- const {onItemAppend, objectName} = props
16
-
17
16
  const handleClick = useCallback(
18
17
  async (variant: VariantType) => {
19
18
  const item = {
20
19
  _key: uuid(),
21
- variantId: variant.id,
22
- experimentId: experimentId,
23
- _type: objectName,
20
+ [variantId]: variant.id,
21
+ [experimentId]: experimentValue,
22
+ _type: variantName,
24
23
  }
25
24
 
26
25
  // Patch the document
27
26
  onItemAppend(item)
28
27
  },
29
- [experimentId, objectName, onItemAppend],
28
+ [variantId, experimentId, experimentValue, variantName, onItemAppend],
30
29
  )
31
30
 
32
31
  const filteredVariants =
33
32
  experiments.find((option) => {
34
- return option.id === experimentId
33
+ return option.id === experimentValue
35
34
  })?.variants || []
36
35
 
37
36
  type Value = {
38
- experimentId: string
39
37
  value?: unknown
38
+ [key: string]: string | unknown
40
39
  variantId: string
41
40
  _key: string
42
41
  _type: string
43
42
  }
44
43
 
45
44
  // there is probably some better was of getting the type of this?
46
- const values = props.value as Value[] | []
45
+ const values = (props.value as Value[]) || []
47
46
 
48
- const usedVariants = values?.map((variant) => variant.variantId)
47
+ const usedVariants = values?.map((variant) => variant[variantId])
49
48
 
50
49
  return (
51
50
  <Stack space={3}>
@@ -55,7 +54,7 @@ export const ArrayInput = (props: ArrayInputProps) => {
55
54
  {filteredVariants.map((variant) => {
56
55
  return (
57
56
  <Button
58
- key={`${experimentId}-${variant.id}`}
57
+ key={`${experimentValue}-${variant.id}`}
59
58
  text={`Add ${variant.label}`}
60
59
  mode="ghost"
61
60
  disabled={usedVariants?.includes(variant.id)}
@@ -11,6 +11,11 @@ import {ExperimentContextProps, FieldPluginConfig} from '../types'
11
11
  export const CONFIG_DEFAULT = {
12
12
  fields: [],
13
13
  apiVersion: '2024-11-07',
14
+ experimentNameOverride: 'experiment',
15
+ variantNameOverride: 'variant',
16
+ variantId: 'variantId',
17
+ variantArrayName: 'variants',
18
+ experimentId: 'experimentId',
14
19
  }
15
20
 
16
21
  export const ExperimentContext = createContext<ExperimentContextProps>({
@@ -1,5 +1,4 @@
1
1
  import {CloseIcon} from '@sanity/icons'
2
- import {useCallback, useMemo} from 'react'
3
2
  import {GiSoapExperiment} from 'react-icons/gi'
4
3
  import {
5
4
  defineDocumentFieldAction,
@@ -14,71 +13,105 @@ import {
14
13
  type PatchStuff = {onChange: (patch: FormPatch | FormPatch[] | PatchEvent) => void; inputId: string}
15
14
 
16
15
  const useAddExperimentAction = (
17
- props: DocumentFieldActionProps & PatchStuff,
16
+ props: DocumentFieldActionProps &
17
+ PatchStuff & {experimentNameOverride: string; experimentId: string; active: boolean},
18
18
  ): DocumentFieldActionItem => {
19
- const patchActiveEvent = useMemo(() => {
20
- return set(true, ['active'])
21
- }, [])
22
- const handleAction = useCallback(() => {
23
- props.onChange([patchActiveEvent])
24
- }, [patchActiveEvent, props])
19
+ const {onChange, active, experimentNameOverride} = props
20
+
21
+ const handleAddAction = () => {
22
+ // console.log('showing experiment', patchActiveTrueEvent)
23
+ onChange([set(!active, ['active'])])
24
+ }
25
25
 
26
26
  return {
27
- title: 'Add experiment',
27
+ title: `Add ${experimentNameOverride}`,
28
28
  type: 'action',
29
29
  icon: GiSoapExperiment,
30
- onAction: handleAction,
30
+ onAction: handleAddAction,
31
31
  renderAsButton: true,
32
32
  }
33
33
  }
34
34
 
35
35
  const useRemoveExperimentAction = (
36
- props: DocumentFieldActionProps & PatchStuff,
36
+ props: DocumentFieldActionProps &
37
+ PatchStuff & {experimentNameOverride: string; experimentId: string; active: boolean},
37
38
  ): DocumentFieldActionItem => {
38
- const patchActiveEvent = useMemo(() => {
39
+ const {onChange, active, experimentId, experimentNameOverride} = props
40
+ const patchActiveFalseEvent = () => {
39
41
  const activeId = ['active']
40
- return set(false, activeId)
41
- }, [])
42
-
43
- const patchClearEvent = useMemo(() => {
44
- const experimentId = ['experimentId'] // `${props.inputId}.experimentId`
45
- const variants = ['variants'] //`${props.inputId}.variants`
46
- return [unset(experimentId), unset(variants)]
47
- }, [])
48
- const handleAction = useCallback(() => {
49
- props.onChange([patchActiveEvent, ...patchClearEvent])
50
- }, [patchActiveEvent, patchClearEvent, props])
51
-
42
+ return set(!active, activeId)
43
+ }
44
+ const patchClearEvent = () => {
45
+ const experiment = [experimentId]
46
+ const variants = [experimentNameOverride]
47
+ return [unset(experiment), unset(variants)]
48
+ }
49
+ const handleClearAction = () => {
50
+ const clearEvents = patchClearEvent()
51
+ const activeEvent = patchActiveFalseEvent()
52
+ onChange([activeEvent, ...clearEvents])
53
+ }
52
54
  return {
53
- title: 'Remove experiment',
55
+ title: `Remove ${experimentNameOverride}`,
54
56
  type: 'action',
55
57
  icon: CloseIcon,
56
- onAction: handleAction,
58
+ onAction: handleClearAction,
57
59
  renderAsButton: true,
58
60
  }
59
61
  }
60
62
 
61
- const newActions = ({onChange, inputId, active}: PatchStuff & {active?: boolean}) =>
62
- active
63
- ? defineDocumentFieldAction({
64
- name: 'Experiment',
65
- useAction: (props) => useRemoveExperimentAction({...props, onChange, inputId}),
66
- })
67
- : defineDocumentFieldAction({
68
- name: 'Experiment',
69
- useAction: (props) => useAddExperimentAction({...props, onChange, inputId}),
70
- })
63
+ const newActions = ({
64
+ onChange,
65
+ inputId,
66
+ active,
67
+ experimentNameOverride,
68
+ experimentId,
69
+ }: PatchStuff & {active?: boolean; experimentNameOverride: string; experimentId: string}) => {
70
+ const removeAction = defineDocumentFieldAction({
71
+ name: `Remove ${experimentNameOverride}`,
72
+ useAction: (props) =>
73
+ useRemoveExperimentAction({
74
+ ...props,
75
+ active: true,
76
+ onChange,
77
+ inputId,
78
+ experimentNameOverride,
79
+ experimentId,
80
+ }),
81
+ })
82
+ const addAction = defineDocumentFieldAction({
83
+ name: `Add ${experimentNameOverride}`,
84
+ useAction: (props) =>
85
+ useAddExperimentAction({
86
+ ...props,
87
+ active: false,
88
+ onChange,
89
+ inputId,
90
+ experimentNameOverride,
91
+ experimentId,
92
+ }),
93
+ })
94
+ if (active) {
95
+ return removeAction
96
+ }
97
+ return addAction
98
+ }
71
99
 
72
- export const ExperimentField = (props: ObjectFieldProps) => {
100
+ export const ExperimentField = (
101
+ props: ObjectFieldProps & {experimentNameOverride: string; experimentId: string},
102
+ ) => {
73
103
  const {onChange} = props.inputProps
74
- const {inputId} = props
104
+ const {inputId, experimentNameOverride, experimentId} = props
75
105
  const active = props.value?.active as boolean | undefined
76
106
 
77
107
  const oldActions = props.actions || []
78
108
 
79
109
  const withActionProps = {
80
110
  ...props,
81
- actions: [newActions({onChange, inputId, active}), ...oldActions],
111
+ actions: [
112
+ newActions({onChange, inputId, active, experimentNameOverride, experimentId}),
113
+ ...oldActions,
114
+ ],
82
115
  }
83
116
  return props.renderDefault(withActionProps)
84
117
  }
@@ -20,11 +20,14 @@ const formatlistOptions = (experiments: ExperimentType[]): SelectOption[] =>
20
20
  value: experiment.id,
21
21
  }))
22
22
 
23
- export const ExperimentInput = (props: StringInputProps) => {
23
+ export const ExperimentInput = (props: StringInputProps & {variantNameOverride: string}) => {
24
24
  const {experiments} = useExperimentContext()
25
25
 
26
26
  const id = useFormValue(['_id']) as string
27
- const aditionalChangePath = useMemo(() => [...props.path.slice(0, -1), 'variants'], [props.path])
27
+ const aditionalChangePath = useMemo(
28
+ () => [...props.path.slice(0, -1), props.variantNameOverride],
29
+ [props.variantNameOverride, props.path],
30
+ )
28
31
  const subValues = useFormValue(aditionalChangePath)
29
32
 
30
33
  const {patch} = useDocumentOperation(id.replace('drafts.', ''), props.schemaType.name)
@@ -5,7 +5,6 @@ import {
5
5
  defineType,
6
6
  FieldDefinition,
7
7
  isObjectInputProps,
8
- SanityClient,
9
8
  } from 'sanity'
10
9
 
11
10
  import {ArrayInput} from './components/Array'
@@ -13,24 +12,39 @@ import {CONFIG_DEFAULT, ExperimentProvider} from './components/ExperimentContext
13
12
  import {ExperimentField} from './components/ExperimentField'
14
13
  import {ExperimentInput} from './components/ExperimentInput'
15
14
  import {VariantPreview} from './components/VariantPreview'
16
- import {ExperimentType, FieldPluginConfig} from './types'
15
+ import {FieldPluginConfig} from './types'
17
16
  import {flattenSchemaType} from './utils/flattenSchemaType'
18
17
 
19
- const createFieldType = ({
18
+ const createExperimentType = ({
20
19
  field,
20
+ experimentNameOverride,
21
+ variantNameOverride,
22
+ variantId,
23
+ variantArrayName,
24
+ experimentId,
21
25
  }: {
22
26
  field: string | FieldDefinition
23
- experiments: ExperimentType[] | ((client: SanityClient) => Promise<ExperimentType[]>)
27
+ experimentNameOverride: string
28
+ variantNameOverride: string
29
+ variantId: string
30
+ variantArrayName: string
31
+ experimentId: string
24
32
  }) => {
25
33
  const typeName = typeof field === `string` ? field : field.name
26
34
  const usedName = String(typeName[0]).toUpperCase() + String(typeName).slice(1)
27
- const objectName = `variant${usedName}`
35
+ const variantName = `${variantNameOverride}${usedName}`
28
36
 
29
37
  return defineType({
30
- name: `experiment${usedName}`,
38
+ name: `${experimentNameOverride}${usedName}`,
31
39
  type: 'object',
32
40
  components: {
33
- field: ExperimentField,
41
+ field: (props) => (
42
+ <ExperimentField
43
+ {...props}
44
+ experimentId={experimentId}
45
+ experimentNameOverride={experimentNameOverride}
46
+ />
47
+ ),
34
48
  },
35
49
  fields: [
36
50
  typeof field === `string`
@@ -50,31 +64,37 @@ const createFieldType = ({
50
64
  hidden: true,
51
65
  }),
52
66
  defineField({
53
- title: 'Experiment',
54
- name: 'experimentId',
67
+ name: experimentId,
55
68
  type: 'string',
56
69
  components: {
57
- input: ExperimentInput,
70
+ input: (props) => (
71
+ <ExperimentInput {...props} variantNameOverride={variantNameOverride} />
72
+ ),
58
73
  },
59
74
  hidden: ({parent}) => {
60
75
  return !parent?.active
61
76
  },
62
77
  }),
63
78
  defineField({
64
- name: 'variants',
79
+ name: variantArrayName,
65
80
  type: 'array',
66
81
  hidden: ({parent}) => {
67
- return !parent?.experimentId
82
+ return !parent?.[experimentId]
68
83
  },
69
84
  components: {
70
85
  input: (props: ArrayOfObjectsInputProps) => (
71
- <ArrayInput {...props} objectName={objectName} />
86
+ <ArrayInput
87
+ {...props}
88
+ variantName={variantName}
89
+ variantId={variantId}
90
+ experimentId={experimentId}
91
+ />
72
92
  ),
73
93
  },
74
94
  of: [
75
95
  defineField({
76
- name: objectName,
77
- type: objectName,
96
+ name: variantName,
97
+ type: variantName,
78
98
  }),
79
99
  ],
80
100
  }),
@@ -82,17 +102,22 @@ const createFieldType = ({
82
102
  })
83
103
  }
84
104
 
85
- const createFieldObjectType = ({
105
+ const createVariantType = ({
86
106
  field,
107
+ variantNameOverride,
108
+ variantId,
109
+ experimentId,
87
110
  }: {
88
111
  field: string | FieldDefinition
89
- experiments: ExperimentType[] | ((client: SanityClient) => Promise<ExperimentType[]>)
112
+ variantNameOverride: string
113
+ variantId: string
114
+ experimentId: string
90
115
  }) => {
91
116
  const typeName = typeof field === `string` ? field : field.name
92
117
  const usedName = String(typeName[0]).toUpperCase() + String(typeName).slice(1)
93
118
  return defineType({
94
- name: `variant${usedName}`,
95
- title: `Experiment array ${usedName}`,
119
+ name: `${variantNameOverride}${usedName}`,
120
+ title: `${variantNameOverride} array ${usedName}`,
96
121
  type: 'object',
97
122
  components: {
98
123
  preview: VariantPreview,
@@ -100,12 +125,12 @@ const createFieldObjectType = ({
100
125
  fields: [
101
126
  {
102
127
  type: 'string',
103
- name: 'variantId',
128
+ name: variantId,
104
129
  readOnly: true,
105
130
  },
106
131
  {
107
132
  type: 'string',
108
- name: 'experimentId',
133
+ name: experimentId,
109
134
  hidden: true,
110
135
  },
111
136
  typeof field === `string`
@@ -113,36 +138,66 @@ const createFieldObjectType = ({
113
138
  defineField({
114
139
  name: 'value',
115
140
  type: field,
116
- hidden: ({parent}) => !parent?.variantId,
141
+ // hidden: ({parent}) => !parent?.[`${objectNameOverride}Id`],
117
142
  })
118
143
  : // Pass in the configured options, but overwrite the name
119
144
  {
120
145
  ...field,
121
146
  name: 'value',
122
- hidden: ({parent}) => !parent?.variantId,
147
+ // hidden: ({parent}) => !parent?.[`${objectNameOverride}Id`],
123
148
  },
124
149
  ],
125
150
  preview: {
126
151
  select: {
127
- variant: 'variantId',
128
- experiment: 'experimentId',
152
+ variant: variantId,
153
+ experiment: experimentId,
129
154
  value: 'value',
130
155
  },
131
156
  },
132
157
  })
133
158
  }
134
159
 
135
- const fieldSchema = ({fields, experiments}: FieldPluginConfig) => {
160
+ const fieldSchema = ({
161
+ fields,
162
+ experimentNameOverride,
163
+ variantNameOverride,
164
+ variantId,
165
+ variantArrayName,
166
+ experimentId,
167
+ }: Required<Omit<FieldPluginConfig, 'apiVersion' | 'experiments'>>) => {
136
168
  return [
137
- ...fields.map((field) => createFieldObjectType({field, experiments})),
138
- ...fields.map((field) => createFieldType({field, experiments})),
169
+ ...fields.map((field) =>
170
+ createVariantType({field, variantNameOverride, variantId, experimentId}),
171
+ ),
172
+ ...fields.map((field) =>
173
+ createExperimentType({
174
+ field,
175
+ experimentNameOverride,
176
+ variantNameOverride,
177
+ variantId,
178
+ variantArrayName,
179
+ experimentId,
180
+ }),
181
+ ),
139
182
  ]
140
183
  }
141
184
 
142
185
  export const fieldLevelExperiments = definePlugin<FieldPluginConfig>((config) => {
143
186
  const pluginConfig = {...CONFIG_DEFAULT, ...config}
144
- const {fields, experiments} = pluginConfig
145
- const fieldSchemaConfig = fieldSchema({fields, experiments})
187
+ const {fields, experimentNameOverride, variantNameOverride} = pluginConfig
188
+
189
+ const experimentId = `${experimentNameOverride}Id`
190
+ const variantArrayName = `${variantNameOverride}s`
191
+ const variantId = `${variantNameOverride}Id`
192
+
193
+ const fieldSchemaConfig = fieldSchema({
194
+ fields,
195
+ experimentNameOverride,
196
+ variantNameOverride,
197
+ variantId,
198
+ variantArrayName,
199
+ experimentId,
200
+ })
146
201
  return {
147
202
  name: 'sanity-personalistaion-plugin-field-level-experiments',
148
203
  schema: {
@@ -160,12 +215,22 @@ export const fieldLevelExperiments = definePlugin<FieldPluginConfig>((config) =>
160
215
  const flatFieldTypeNames = flattenSchemaType(props.schemaType).map(
161
216
  (field) => field.type.name,
162
217
  )
163
- const hasExperiment = flatFieldTypeNames.some((name) => name.startsWith('experiment'))
218
+ const hasExperiment = flatFieldTypeNames.some((name) =>
219
+ name.startsWith(experimentNameOverride),
220
+ )
164
221
 
165
222
  if (!hasExperiment) {
166
223
  return props.renderDefault(props)
167
224
  }
168
- const providerProps = {...props, experimentFieldPluginConfig: pluginConfig}
225
+ const providerProps = {
226
+ ...props,
227
+ experimentFieldPluginConfig: {
228
+ ...pluginConfig,
229
+ variantId,
230
+ variantArrayName,
231
+ experimentId,
232
+ },
233
+ }
169
234
  return ExperimentProvider(providerProps)
170
235
  },
171
236
  },
package/src/types.ts CHANGED
@@ -22,11 +22,15 @@ export type FieldPluginConfig = {
22
22
  fields: (string | FieldDefinition)[]
23
23
  experiments: ExperimentType[] | ((client: SanityClient) => Promise<ExperimentType[]>)
24
24
  apiVersion?: string
25
+ experimentNameOverride?: string
26
+ variantNameOverride?: string
27
+ variantId?: string
28
+ variantArrayName?: string
29
+ experimentId?: string
25
30
  }
26
31
 
27
32
  export type VariantPreviewProps = Omit<PreviewProps, 'SchemaType'> & {
28
- experiment: string
29
- variant: string
33
+ [key: string]: string
30
34
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
31
35
  value: any
32
36
  }
@@ -36,15 +40,16 @@ export type ExperimentContextProps = Required<FieldPluginConfig> & {
36
40
  }
37
41
 
38
42
  export type ArrayInputProps = ArrayOfObjectsInputProps & {
39
- objectName: string
43
+ variantName: string
44
+ variantId: string
45
+ experimentId: string
40
46
  }
41
47
 
42
48
  export type ObjectFieldWithPath = ObjectField<SchemaType> & {path: Path}
43
49
 
44
50
  export type VariantGeneric<T> = {
51
+ [key: string]: string | T | undefined
45
52
  _type: string
46
- variantId?: string
47
- experimentId?: string
48
53
  value?: T
49
54
  }
50
55
 
@@ -52,9 +57,13 @@ export type ExperimentGeneric<T> = {
52
57
  _type: string
53
58
  default?: T
54
59
  experimentValue?: string
55
- variants?: Array<
56
- {
57
- _key: string
58
- } & VariantGeneric<T>
59
- >
60
+ [key: string]:
61
+ | Array<
62
+ {
63
+ _key: string
64
+ } & VariantGeneric<T>
65
+ >
66
+ | string
67
+ | T
68
+ | undefined
60
69
  }