@sanity/personalization-plugin 2.0.0 → 2.1.0-growthbook.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/README.md CHANGED
@@ -25,6 +25,8 @@ Once configured you can query the values using the ids of the experiment and var
25
25
  - [Release new version](#release-new-version)
26
26
  - [License](#license-1)
27
27
 
28
+ For Specific information about the growthbookFieldLevel export see its [readme](/growthbook.md)
29
+
28
30
  ## Installation
29
31
 
30
32
  ```sh
@@ -143,23 +145,21 @@ experiments: async () => {
143
145
  const response = await fetch('https://example.com/experiments')
144
146
  const {externalExperiments} = await response.json()
145
147
 
146
- const experiments: ExperimentType[] = externalExperiments?.map(
147
- (experiment) => {
148
- const experimentId = experiment.id
149
- const experimentLabel = experiment.name
150
- const variants = experiment.variations?.map((variant) => {
151
- return {
152
- id: variant.variationId,
153
- label: variant.name,
154
- }
155
- })
148
+ const experiments: ExperimentType[] = externalExperiments?.map((experiment) => {
149
+ const experimentId = experiment.id
150
+ const experimentLabel = experiment.name
151
+ const variants = experiment.variations?.map((variant) => {
156
152
  return {
157
- id: experimentId,
158
- label: experimentLabel,
159
- variants,
153
+ id: variant.variationId,
154
+ label: variant.name,
160
155
  }
161
- },
162
- )
156
+ })
157
+ return {
158
+ id: experimentId,
159
+ label: experimentLabel,
160
+ variants,
161
+ }
162
+ })
163
163
  return experiments
164
164
  }
165
165
  ```
package/dist/index.d.mts CHANGED
@@ -1,4 +1,5 @@
1
1
  import {ArrayOfObjectsInputProps} from 'sanity'
2
+ import {Dispatch} from 'react'
2
3
  import {FieldDefinition} from 'sanity'
3
4
  import {ObjectField} from 'sanity'
4
5
  import {Path} from 'sanity'
@@ -6,6 +7,7 @@ import {Plugin as Plugin_2} from 'sanity'
6
7
  import {PreviewProps} from 'sanity'
7
8
  import {SanityClient} from 'sanity'
8
9
  import {SchemaType} from 'sanity'
10
+ import {SetStateAction} from 'react'
9
11
 
10
12
  export declare type ArrayInputProps = ArrayOfObjectsInputProps & {
11
13
  objectName: string
@@ -13,6 +15,8 @@ export declare type ArrayInputProps = ArrayOfObjectsInputProps & {
13
15
 
14
16
  export declare type ExperimentContextProps = Required<FieldPluginConfig> & {
15
17
  experiments: ExperimentType[]
18
+ setSecret: Dispatch<SetStateAction<string | undefined>>
19
+ secret: string | undefined
16
20
  }
17
21
 
18
22
  export declare type ExperimentGeneric<T> = {
@@ -36,7 +40,9 @@ export declare const fieldLevelExperiments: Plugin_2<FieldPluginConfig>
36
40
 
37
41
  export declare type FieldPluginConfig = {
38
42
  fields: (string | FieldDefinition)[]
39
- experiments: ExperimentType[] | ((client: SanityClient) => Promise<ExperimentType[]>)
43
+ experiments:
44
+ | ExperimentType[]
45
+ | ((client: SanityClient, secret?: string) => Promise<ExperimentType[]>)
40
46
  apiVersion?: string
41
47
  }
42
48
 
@@ -45,6 +51,204 @@ export declare type FieldPluginConfig = {
45
51
  */
46
52
  export declare function flattenSchemaType(schemaType: SchemaType): ObjectFieldWithPath[]
47
53
 
54
+ export declare type GrowthbookABConfig = {
55
+ fields: (string | FieldDefinition)[]
56
+ environment: string
57
+ baseUrl?: string
58
+ project?: string
59
+ convertBooleans?: boolean
60
+ }
61
+
62
+ export declare type GrowthbookExperiment = {
63
+ id: string
64
+ dateCreated: string
65
+ dateUpdated: string
66
+ name: string
67
+ project: string
68
+ hypothesis: string
69
+ description: string
70
+ tags: [string]
71
+ owner: string
72
+ archived: boolean
73
+ status: string
74
+ autoRefresh: boolean
75
+ hashAttribute: string
76
+ fallbackAttribute: string
77
+ hashVersion: number
78
+ disableStickyBucketing: boolean
79
+ bucketVersion: number
80
+ minBucketVersion: number
81
+ variations: [
82
+ {
83
+ variationId: string
84
+ key: string
85
+ name: string
86
+ description: string
87
+ screenshots: [string]
88
+ },
89
+ ]
90
+ phases: [
91
+ {
92
+ name: string
93
+ dateStarted: string
94
+ dateEnded: string
95
+ reasonForStopping: string
96
+ seed: string
97
+ coverage: 0
98
+ trafficSplit: [
99
+ {
100
+ variationId: string
101
+ weight: 0
102
+ },
103
+ ]
104
+ namespace: {
105
+ namespaceId: string
106
+ range: []
107
+ }
108
+ targetingCondition: string
109
+ savedGroupTargeting: [
110
+ {
111
+ matchType: string
112
+ savedGroups: [string]
113
+ },
114
+ ]
115
+ },
116
+ ]
117
+ settings: {
118
+ datasourceId: string
119
+ assignmentQueryId: string
120
+ experimentId: string
121
+ segmentId: string
122
+ queryFilter: string
123
+ inProgressConversions: string
124
+ attributionModel: string
125
+ statsEngine: string
126
+ regressionAdjustmentEnabled: boolean
127
+ goals: [
128
+ {
129
+ metricId: string
130
+ overrides: {
131
+ delayHours: 0
132
+ windowHours: 0
133
+ window: string
134
+ winRiskThreshold: 0
135
+ loseRiskThreshold: 0
136
+ }
137
+ },
138
+ ]
139
+ secondaryMetrics: [
140
+ {
141
+ metricId: string
142
+ overrides: {
143
+ delayHours: 0
144
+ windowHours: 0
145
+ window: string
146
+ winRiskThreshold: 0
147
+ loseRiskThreshold: 0
148
+ }
149
+ },
150
+ ]
151
+ guardrails: [
152
+ {
153
+ metricId: string
154
+ overrides: {
155
+ delayHours: 0
156
+ windowHours: 0
157
+ window: string
158
+ winRiskThreshold: 0
159
+ loseRiskThreshold: 0
160
+ }
161
+ },
162
+ ]
163
+ activationMetric: {
164
+ metricId: string
165
+ overrides: {
166
+ delayHours: 0
167
+ windowHours: 0
168
+ window: string
169
+ winRiskThreshold: 0
170
+ loseRiskThreshold: 0
171
+ }
172
+ }
173
+ }
174
+ resultSummary: {
175
+ status: string
176
+ winner: string
177
+ conclusions: string
178
+ releasedVariationId: string
179
+ excludeFromPayload: boolean
180
+ }
181
+ }
182
+
183
+ export declare type GrowthbookFeature = {
184
+ id: string
185
+ dateCreated: string
186
+ dateUpdated: string
187
+ archived: boolean
188
+ description: string
189
+ owner: string
190
+ project: string
191
+ valueType: string
192
+ defaultValue: string
193
+ tags: string[]
194
+ environments: {
195
+ [key: string]: {
196
+ enabled: boolean
197
+ defaultValue: string
198
+ rules: {
199
+ description: string
200
+ condition: string
201
+ savedGroupTargeting: {
202
+ matchType: string
203
+ savedGroups: string[]
204
+ }[]
205
+ id: string
206
+ enabled: boolean
207
+ type: string
208
+ value: string
209
+ variations: {
210
+ value: string
211
+ variationId: string
212
+ }[]
213
+ }[]
214
+ definition: string
215
+ draft: {
216
+ enabled: boolean
217
+ defaultValue: string
218
+ rules: {
219
+ description: string
220
+ condition: string
221
+ savedGroupTargeting: {
222
+ matchType: string
223
+ savedGroups: string[]
224
+ }[]
225
+ id: string
226
+ enabled: boolean
227
+ type: string
228
+ value: string
229
+ variations: {
230
+ value: string
231
+ variationId: string
232
+ }[]
233
+ }[]
234
+ definition: string
235
+ }
236
+ }
237
+ }
238
+ prerequisites: {
239
+ parentId: string
240
+ parentCondition: string
241
+ }[]
242
+ revision: {
243
+ version: number
244
+ comment: string
245
+ date: string
246
+ publishedBy: string
247
+ }
248
+ }
249
+
250
+ export declare const growthbookFieldLevel: Plugin_2<GrowthbookABConfig>
251
+
48
252
  export declare type ObjectFieldWithPath = ObjectField<SchemaType> & {
49
253
  path: Path
50
254
  }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import {ArrayOfObjectsInputProps} from 'sanity'
2
+ import {Dispatch} from 'react'
2
3
  import {FieldDefinition} from 'sanity'
3
4
  import {ObjectField} from 'sanity'
4
5
  import {Path} from 'sanity'
@@ -6,6 +7,7 @@ import {Plugin as Plugin_2} from 'sanity'
6
7
  import {PreviewProps} from 'sanity'
7
8
  import {SanityClient} from 'sanity'
8
9
  import {SchemaType} from 'sanity'
10
+ import {SetStateAction} from 'react'
9
11
 
10
12
  export declare type ArrayInputProps = ArrayOfObjectsInputProps & {
11
13
  objectName: string
@@ -13,6 +15,8 @@ export declare type ArrayInputProps = ArrayOfObjectsInputProps & {
13
15
 
14
16
  export declare type ExperimentContextProps = Required<FieldPluginConfig> & {
15
17
  experiments: ExperimentType[]
18
+ setSecret: Dispatch<SetStateAction<string | undefined>>
19
+ secret: string | undefined
16
20
  }
17
21
 
18
22
  export declare type ExperimentGeneric<T> = {
@@ -36,7 +40,9 @@ export declare const fieldLevelExperiments: Plugin_2<FieldPluginConfig>
36
40
 
37
41
  export declare type FieldPluginConfig = {
38
42
  fields: (string | FieldDefinition)[]
39
- experiments: ExperimentType[] | ((client: SanityClient) => Promise<ExperimentType[]>)
43
+ experiments:
44
+ | ExperimentType[]
45
+ | ((client: SanityClient, secret?: string) => Promise<ExperimentType[]>)
40
46
  apiVersion?: string
41
47
  }
42
48
 
@@ -45,6 +51,204 @@ export declare type FieldPluginConfig = {
45
51
  */
46
52
  export declare function flattenSchemaType(schemaType: SchemaType): ObjectFieldWithPath[]
47
53
 
54
+ export declare type GrowthbookABConfig = {
55
+ fields: (string | FieldDefinition)[]
56
+ environment: string
57
+ baseUrl?: string
58
+ project?: string
59
+ convertBooleans?: boolean
60
+ }
61
+
62
+ export declare type GrowthbookExperiment = {
63
+ id: string
64
+ dateCreated: string
65
+ dateUpdated: string
66
+ name: string
67
+ project: string
68
+ hypothesis: string
69
+ description: string
70
+ tags: [string]
71
+ owner: string
72
+ archived: boolean
73
+ status: string
74
+ autoRefresh: boolean
75
+ hashAttribute: string
76
+ fallbackAttribute: string
77
+ hashVersion: number
78
+ disableStickyBucketing: boolean
79
+ bucketVersion: number
80
+ minBucketVersion: number
81
+ variations: [
82
+ {
83
+ variationId: string
84
+ key: string
85
+ name: string
86
+ description: string
87
+ screenshots: [string]
88
+ },
89
+ ]
90
+ phases: [
91
+ {
92
+ name: string
93
+ dateStarted: string
94
+ dateEnded: string
95
+ reasonForStopping: string
96
+ seed: string
97
+ coverage: 0
98
+ trafficSplit: [
99
+ {
100
+ variationId: string
101
+ weight: 0
102
+ },
103
+ ]
104
+ namespace: {
105
+ namespaceId: string
106
+ range: []
107
+ }
108
+ targetingCondition: string
109
+ savedGroupTargeting: [
110
+ {
111
+ matchType: string
112
+ savedGroups: [string]
113
+ },
114
+ ]
115
+ },
116
+ ]
117
+ settings: {
118
+ datasourceId: string
119
+ assignmentQueryId: string
120
+ experimentId: string
121
+ segmentId: string
122
+ queryFilter: string
123
+ inProgressConversions: string
124
+ attributionModel: string
125
+ statsEngine: string
126
+ regressionAdjustmentEnabled: boolean
127
+ goals: [
128
+ {
129
+ metricId: string
130
+ overrides: {
131
+ delayHours: 0
132
+ windowHours: 0
133
+ window: string
134
+ winRiskThreshold: 0
135
+ loseRiskThreshold: 0
136
+ }
137
+ },
138
+ ]
139
+ secondaryMetrics: [
140
+ {
141
+ metricId: string
142
+ overrides: {
143
+ delayHours: 0
144
+ windowHours: 0
145
+ window: string
146
+ winRiskThreshold: 0
147
+ loseRiskThreshold: 0
148
+ }
149
+ },
150
+ ]
151
+ guardrails: [
152
+ {
153
+ metricId: string
154
+ overrides: {
155
+ delayHours: 0
156
+ windowHours: 0
157
+ window: string
158
+ winRiskThreshold: 0
159
+ loseRiskThreshold: 0
160
+ }
161
+ },
162
+ ]
163
+ activationMetric: {
164
+ metricId: string
165
+ overrides: {
166
+ delayHours: 0
167
+ windowHours: 0
168
+ window: string
169
+ winRiskThreshold: 0
170
+ loseRiskThreshold: 0
171
+ }
172
+ }
173
+ }
174
+ resultSummary: {
175
+ status: string
176
+ winner: string
177
+ conclusions: string
178
+ releasedVariationId: string
179
+ excludeFromPayload: boolean
180
+ }
181
+ }
182
+
183
+ export declare type GrowthbookFeature = {
184
+ id: string
185
+ dateCreated: string
186
+ dateUpdated: string
187
+ archived: boolean
188
+ description: string
189
+ owner: string
190
+ project: string
191
+ valueType: string
192
+ defaultValue: string
193
+ tags: string[]
194
+ environments: {
195
+ [key: string]: {
196
+ enabled: boolean
197
+ defaultValue: string
198
+ rules: {
199
+ description: string
200
+ condition: string
201
+ savedGroupTargeting: {
202
+ matchType: string
203
+ savedGroups: string[]
204
+ }[]
205
+ id: string
206
+ enabled: boolean
207
+ type: string
208
+ value: string
209
+ variations: {
210
+ value: string
211
+ variationId: string
212
+ }[]
213
+ }[]
214
+ definition: string
215
+ draft: {
216
+ enabled: boolean
217
+ defaultValue: string
218
+ rules: {
219
+ description: string
220
+ condition: string
221
+ savedGroupTargeting: {
222
+ matchType: string
223
+ savedGroups: string[]
224
+ }[]
225
+ id: string
226
+ enabled: boolean
227
+ type: string
228
+ value: string
229
+ variations: {
230
+ value: string
231
+ variationId: string
232
+ }[]
233
+ }[]
234
+ definition: string
235
+ }
236
+ }
237
+ }
238
+ prerequisites: {
239
+ parentId: string
240
+ parentCondition: string
241
+ }[]
242
+ revision: {
243
+ version: number
244
+ comment: string
245
+ date: string
246
+ publishedBy: string
247
+ }
248
+ }
249
+
250
+ export declare const growthbookFieldLevel: Plugin_2<GrowthbookABConfig>
251
+
48
252
  export declare type ObjectFieldWithPath = ObjectField<SchemaType> & {
49
253
  path: Path
50
254
  }
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: !0 });
3
- var jsxRuntime = require("react/jsx-runtime"), sanity = require("sanity"), ui = require("@sanity/ui"), uuid = require("@sanity/uuid"), react = require("react"), equal = require("fast-deep-equal"), suspendReact = require("suspend-react"), gi = require("react-icons/gi");
3
+ var jsxRuntime = require("react/jsx-runtime"), sanity = require("sanity"), ui = require("@sanity/ui"), uuid = require("@sanity/uuid"), react = require("react"), equal = require("fast-deep-equal"), suspendReact = require("suspend-react"), gi = require("react-icons/gi"), studioSecrets = require("@sanity/studio-secrets");
4
4
  function _interopDefaultCompat(e) {
5
5
  return e && typeof e == "object" && "default" in e ? e : { default: e };
6
6
  }
@@ -10,20 +10,23 @@ const CONFIG_DEFAULT = {
10
10
  apiVersion: "2024-11-07"
11
11
  }, ExperimentContext = react.createContext({
12
12
  ...CONFIG_DEFAULT,
13
- experiments: []
13
+ experiments: [],
14
+ setSecret: () => {
15
+ },
16
+ secret: void 0
14
17
  });
15
18
  function useExperimentContext() {
16
19
  return react.useContext(ExperimentContext);
17
20
  }
18
21
  function ExperimentProvider(props) {
19
- const { experimentFieldPluginConfig } = props, client = sanity.useClient({ apiVersion: experimentFieldPluginConfig.apiVersion }), workspace = sanity.useWorkspace(), experiments = Array.isArray(experimentFieldPluginConfig.experiments) ? experimentFieldPluginConfig.experiments : suspendReact.suspend(
22
+ const { experimentFieldPluginConfig } = props, [secret, setSecret] = react.useState(), client = sanity.useClient({ apiVersion: experimentFieldPluginConfig.apiVersion }), workspace = sanity.useWorkspace(), experiments = Array.isArray(experimentFieldPluginConfig.experiments) ? experimentFieldPluginConfig.experiments : suspendReact.suspend(
20
23
  // eslint-disable-next-line require-await
21
- async () => typeof experimentFieldPluginConfig.experiments == "function" ? experimentFieldPluginConfig.experiments(client) : experimentFieldPluginConfig.experiments,
22
- [workspace],
24
+ async () => typeof experimentFieldPluginConfig.experiments == "function" ? experimentFieldPluginConfig.experiments(client, secret) : experimentFieldPluginConfig.experiments,
25
+ [workspace, secret],
23
26
  { equal: equal__default.default }
24
27
  ), context = react.useMemo(
25
- () => ({ ...experimentFieldPluginConfig, experiments }),
26
- [experimentFieldPluginConfig, experiments]
28
+ () => ({ ...experimentFieldPluginConfig, experiments, secret, setSecret }),
29
+ [experimentFieldPluginConfig, experiments, secret, setSecret]
27
30
  );
28
31
  return /* @__PURE__ */ jsxRuntime.jsx(ExperimentContext.Provider, { value: context, children: props.renderDefault(props) });
29
32
  }
@@ -6648,7 +6651,91 @@ const createFieldType = ({
6648
6651
  }
6649
6652
  }
6650
6653
  };
6654
+ }), namespace = "growthbook", pluginConfigKeys = [
6655
+ {
6656
+ key: "apiKey",
6657
+ title: "Your secret API key"
6658
+ }
6659
+ ], Secrets = (props) => {
6660
+ const { secrets, loading } = studioSecrets.useSecrets(namespace), { setSecret } = useExperimentContext(), [showSettings, setShowSettings] = react.useState(!1);
6661
+ return react.useEffect(() => {
6662
+ if (!loading)
6663
+ return !secrets && !loading ? (setSecret(void 0), setShowSettings(!0)) : (setSecret(secrets.apiKey), setShowSettings(!1));
6664
+ }, [secrets, loading, setSecret]), showSettings ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6665
+ /* @__PURE__ */ jsxRuntime.jsx(
6666
+ studioSecrets.SettingsView,
6667
+ {
6668
+ title: "Growthbook secret",
6669
+ namespace,
6670
+ keys: pluginConfigKeys,
6671
+ onClose: () => {
6672
+ setShowSettings(!1);
6673
+ }
6674
+ }
6675
+ ),
6676
+ props.renderDefault(props)
6677
+ ] }) : props.renderDefault(props);
6678
+ }, getBooleanConversion = (value) => value === "true" ? "variant" : value === "false" ? "control" : value, getExperiments = async ({
6679
+ client,
6680
+ environment,
6681
+ baseUrl,
6682
+ project,
6683
+ convertBooleans
6684
+ }) => {
6685
+ const secret = await client.fetch("*[_id == 'secrets.growthbook'][0].secrets.apiKey");
6686
+ if (!secret) return [];
6687
+ const featureExperiments = [];
6688
+ let hasMore = !0, offset = 0;
6689
+ const url = new URL(baseUrl ?? "https://api.growthbook.io/api/v1/features");
6690
+ for (project && url.searchParams.set("projectId", project); hasMore; ) {
6691
+ url.searchParams.set("offset", offset.toString());
6692
+ const response = await fetch(url, {
6693
+ headers: {
6694
+ Authorization: `Bearer ${secret}`
6695
+ }
6696
+ }), { features, hasMore: responseHasMore, nextOffset } = await response.json();
6697
+ hasMore = responseHasMore, offset = nextOffset, features && features.forEach((feature) => {
6698
+ if (feature.archived)
6699
+ return;
6700
+ const experiments = feature.environments[environment]?.rules.filter(
6701
+ (experiment) => experiment.type === "experiment-ref" || experiment.type === "experiment"
6702
+ );
6703
+ if (!experiments)
6704
+ return;
6705
+ const variations = /* @__PURE__ */ new Set();
6706
+ experiments.forEach((experiment) => {
6707
+ experiment?.variations.forEach((variant) => {
6708
+ variations.add({
6709
+ id: convertBooleans ? getBooleanConversion(variant.value) : variant.value,
6710
+ label: convertBooleans ? getBooleanConversion(variant.value) : variant.value
6711
+ });
6712
+ });
6713
+ });
6714
+ const value = { id: feature.id, label: feature.id, variants: [...variations] };
6715
+ featureExperiments.push(value);
6716
+ });
6717
+ }
6718
+ return featureExperiments;
6719
+ }, growthbookFieldLevel = sanity.definePlugin((config) => {
6720
+ const { fields, environment, project, convertBooleans, baseUrl } = config;
6721
+ return {
6722
+ name: "sanity-growthbook-personalistaion-plugin-field-level-experiments",
6723
+ plugins: [
6724
+ fieldLevelExperiments({
6725
+ fields,
6726
+ experiments: (client) => getExperiments({ client, environment, baseUrl, project, convertBooleans })
6727
+ })
6728
+ ],
6729
+ form: {
6730
+ components: {
6731
+ input: (props) => !(props.id === "root" && sanity.isObjectInputProps(props)) || !flattenSchemaType(props.schemaType).map(
6732
+ (field) => field.type.name
6733
+ ).some((name) => name.startsWith("experiment")) ? props.renderDefault(props) : Secrets(props)
6734
+ }
6735
+ }
6736
+ };
6651
6737
  });
6652
6738
  exports.fieldLevelExperiments = fieldLevelExperiments;
6653
6739
  exports.flattenSchemaType = flattenSchemaType;
6740
+ exports.growthbookFieldLevel = growthbookFieldLevel;
6654
6741
  //# sourceMappingURL=index.js.map