@sanity/personalization-plugin 2.1.0-growthbook.1 → 2.2.0-launch-darkly.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/src/types.ts CHANGED
@@ -21,15 +21,17 @@ export type ExperimentType = {
21
21
 
22
22
  export type FieldPluginConfig = {
23
23
  fields: (string | FieldDefinition)[]
24
- experiments:
25
- | ExperimentType[]
26
- | ((client: SanityClient, secret?: string) => Promise<ExperimentType[]>)
24
+ experiments: ExperimentType[] | ((client: SanityClient) => Promise<ExperimentType[]>)
27
25
  apiVersion?: string
26
+ experimentNameOverride?: string
27
+ variantNameOverride?: string
28
+ variantId?: string
29
+ variantArrayName?: string
30
+ experimentId?: string
28
31
  }
29
32
 
30
33
  export type VariantPreviewProps = Omit<PreviewProps, 'SchemaType'> & {
31
- experiment: string
32
- variant: string
34
+ [key: string]: string
33
35
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
34
36
  value: any
35
37
  }
@@ -41,15 +43,16 @@ export type ExperimentContextProps = Required<FieldPluginConfig> & {
41
43
  }
42
44
 
43
45
  export type ArrayInputProps = ArrayOfObjectsInputProps & {
44
- objectName: string
46
+ variantName: string
47
+ variantId: string
48
+ experimentId: string
45
49
  }
46
50
 
47
51
  export type ObjectFieldWithPath = ObjectField<SchemaType> & {path: Path}
48
52
 
49
53
  export type VariantGeneric<T> = {
54
+ [key: string]: string | T | undefined
50
55
  _type: string
51
- variantId?: string
52
- experimentId?: string
53
56
  value?: T
54
57
  }
55
58
 
@@ -57,185 +60,195 @@ export type ExperimentGeneric<T> = {
57
60
  _type: string
58
61
  default?: T
59
62
  experimentValue?: string
60
- variants?: Array<
61
- {
62
- _key: string
63
- } & VariantGeneric<T>
64
- >
63
+ [key: string]:
64
+ | Array<
65
+ {
66
+ _key: string
67
+ } & VariantGeneric<T>
68
+ >
69
+ | string
70
+ | T
71
+ | undefined
65
72
  }
66
73
 
67
- export type GrowthbookExperiment = {
68
- id: string
69
- dateCreated: string
70
- dateUpdated: string
74
+ export type LaunchDarklyFlagItem = {
71
75
  name: string
72
- project: string
73
- hypothesis: string
74
- description: string
75
- tags: [string]
76
- owner: string
77
- archived: boolean
78
- status: string
79
- autoRefresh: boolean
80
- hashAttribute: string
81
- fallbackAttribute: string
82
- hashVersion: number
83
- disableStickyBucketing: boolean
84
- bucketVersion: number
85
- minBucketVersion: number
86
- variations: [
87
- {
88
- variationId: string
89
- key: string
90
- name: string
91
- description: string
92
- screenshots: [string]
93
- },
94
- ]
95
- phases: [
96
- {
97
- name: string
98
- dateStarted: string
99
- dateEnded: string
100
- reasonForStopping: string
101
- seed: string
102
- coverage: 0
103
- trafficSplit: [
104
- {
105
- variationId: string
106
- weight: 0
107
- },
108
- ]
109
- namespace: {
110
- namespaceId: string
111
- range: []
112
- }
113
- targetingCondition: string
114
- savedGroupTargeting: [
115
- {
116
- matchType: string
117
- savedGroups: [string]
118
- },
119
- ]
120
- },
121
- ]
122
- settings: {
123
- datasourceId: string
124
- assignmentQueryId: string
125
- experimentId: string
126
- segmentId: string
127
- queryFilter: string
128
- inProgressConversions: string
129
- attributionModel: string
130
- statsEngine: string
131
- regressionAdjustmentEnabled: boolean
132
- goals: [
133
- {
134
- metricId: string
135
- overrides: {
136
- delayHours: 0
137
- windowHours: 0
138
- window: string
139
- winRiskThreshold: 0
140
- loseRiskThreshold: 0
141
- }
142
- },
143
- ]
144
- secondaryMetrics: [
145
- {
146
- metricId: string
147
- overrides: {
148
- delayHours: 0
149
- windowHours: 0
150
- window: string
151
- winRiskThreshold: 0
152
- loseRiskThreshold: 0
76
+ kind: string
77
+ key: string
78
+ _version: number
79
+ creationDate: number
80
+ variations: Array<{
81
+ value: boolean
82
+ _id: string
83
+ name: string
84
+ }>
85
+ temporary: boolean
86
+ tags: string[]
87
+ _links: {
88
+ parent: {
89
+ href: string
90
+ type: string
91
+ }
92
+ self: {
93
+ href: string
94
+ type: string
95
+ }
96
+ }
97
+ experiments: {
98
+ baselineIdx: number
99
+ items: Array<{
100
+ metricKey: string
101
+ _metric: {
102
+ _id: string
103
+ _versionId: string
104
+ key: string
105
+ name: string
106
+ kind: string
107
+ _links: {
108
+ parent: {
109
+ href: string
110
+ type: string
111
+ }
112
+ self: {
113
+ href: string
114
+ type: string
115
+ }
153
116
  }
154
- },
155
- ]
156
- guardrails: [
157
- {
158
- metricId: string
159
- overrides: {
160
- delayHours: 0
161
- windowHours: 0
162
- window: string
163
- winRiskThreshold: 0
164
- loseRiskThreshold: 0
117
+ tags: string[]
118
+ _creationDate: number
119
+ experimentCount: number
120
+ metricGroupCount: number
121
+ _attachedFlagCount: number
122
+ maintainerId: string
123
+ _maintainer: {
124
+ _links: {
125
+ self: {
126
+ href: string
127
+ type: string
128
+ }
129
+ }
130
+ _id: string
131
+ role: string
132
+ email: string
133
+ firstName: string
134
+ lastName: string
165
135
  }
166
- },
167
- ]
168
- activationMetric: {
169
- metricId: string
170
- overrides: {
171
- delayHours: 0
172
- windowHours: 0
173
- window: string
174
- winRiskThreshold: 0
175
- loseRiskThreshold: 0
136
+ category: string
137
+ isNumeric: boolean
138
+ percentileValue: number
176
139
  }
177
- }
140
+ }>
178
141
  }
179
- resultSummary: {
180
- status: string
181
- winner: string
182
- conclusions: string
183
- releasedVariationId: string
184
- excludeFromPayload: boolean
142
+ customProperties: {
143
+ key: {
144
+ name: string
145
+ value: string[]
146
+ }
185
147
  }
186
- }
187
-
188
- export type GrowthbookFeature = {
189
- id: string
190
- dateCreated: string
191
- dateUpdated: string
192
148
  archived: boolean
193
149
  description: string
194
- owner: string
195
- project: string
196
- valueType: string
197
- defaultValue: string
198
- tags: string[]
150
+ maintainerId: string
151
+ _maintainer: {
152
+ _links: {
153
+ self: {
154
+ href: string
155
+ type: string
156
+ }
157
+ }
158
+ _id: string
159
+ role: string
160
+ email: string
161
+ firstName: string
162
+ lastName: string
163
+ }
164
+ maintainerTeamKey: string
165
+ _maintainerTeam: {
166
+ key: string
167
+ name: string
168
+ _links: {
169
+ parent: {
170
+ href: string
171
+ type: string
172
+ }
173
+ roles: {
174
+ href: string
175
+ type: string
176
+ }
177
+ self: {
178
+ href: string
179
+ type: string
180
+ }
181
+ }
182
+ }
183
+ archivedDate: number
184
+ deprecated: boolean
185
+ deprecatedDate: number
186
+ defaults: {
187
+ onVariation: number
188
+ offVariation: number
189
+ }
190
+ _purpose: string
191
+ migrationSettings: {
192
+ contextKind: string
193
+ stageCount: number
194
+ }
199
195
  environments: {
200
196
  [key: string]: {
201
- enabled: boolean
202
- defaultValue: string
203
- rules: {
204
- description: string
205
- condition: string
206
- savedGroupTargeting: {matchType: string; savedGroups: string[]}[]
207
- id: string
208
- enabled: boolean
197
+ on: boolean
198
+ archived: boolean
199
+ salt: string
200
+ sel: string
201
+ lastModified: number
202
+ version: number
203
+ _site: {
204
+ href: string
209
205
  type: string
210
- value: string
211
- variations: {value: string; variationId: string}[]
212
- }[]
213
- definition: string
214
- draft: {
215
- enabled: boolean
216
- defaultValue: string
217
- rules: {
218
- description: string
219
- condition: string
220
- savedGroupTargeting: {matchType: string; savedGroups: string[]}[]
221
- id: string
222
- enabled: boolean
223
- type: string
224
- value: string
225
- variations: {value: string; variationId: string}[]
226
- }[]
227
- definition: string
206
+ }
207
+ _environmentName: string
208
+ trackEvents: boolean
209
+ trackEventsFallthrough: boolean
210
+ targets: Array<{
211
+ values: string[]
212
+ variation: number
213
+ contextKind: string
214
+ }>
215
+ contextTargets: Array<{
216
+ values: string[]
217
+ variation: number
218
+ contextKind: string
219
+ }>
220
+ rules: Array<{
221
+ clauses: Array<{
222
+ attribute: string
223
+ op: string
224
+ values: unknown[]
225
+ negate: boolean
226
+ }>
227
+ trackEvents: boolean
228
+ }>
229
+ fallthrough: {
230
+ variation: number
231
+ }
232
+ offVariation: number
233
+ prerequisites: Array<{
234
+ key: string
235
+ variation: number
236
+ }>
237
+ _summary: {
238
+ variations: {
239
+ [key: string]: {
240
+ rules: number
241
+ nullRules: number
242
+ targets: number
243
+ contextTargets: number
244
+ isFallthrough?: boolean
245
+ isOff?: boolean
246
+ }
247
+ }
248
+ prerequisites: number
228
249
  }
229
250
  }
230
251
  }
231
- prerequisites: {
232
- parentId: string
233
- parentCondition: string
234
- }[]
235
- revision: {
236
- version: number
237
- comment: string
238
- date: string
239
- publishedBy: string
240
- }
252
+ includeInSnippet: boolean
253
+ goalIds: string[]
241
254
  }
@@ -0,0 +1,54 @@
1
+ import {SanityClient} from 'sanity'
2
+
3
+ import {LaunchDarklyFieldLevelConfig} from '../launchDarklyExperiments'
4
+ import {ExperimentType, LaunchDarklyFlagItem} from '../types'
5
+
6
+ export const getExperiments = async ({
7
+ client,
8
+ projectKey,
9
+ tags,
10
+ }: Omit<LaunchDarklyFieldLevelConfig, 'fields'> & {client: SanityClient}): Promise<
11
+ ExperimentType[]
12
+ > => {
13
+ const query = `*[_id == 'secrets.launchdarkly'][0].secrets.apiKey`
14
+
15
+ const secret = await client.fetch(query) // secret is stored in the content lake using @sanity/studio-secrets
16
+ if (!secret) return []
17
+
18
+ const url = new URL(`https://app.launchdarkly.com/api/v2/flags/${projectKey}`)
19
+
20
+ if (tags) {
21
+ url.searchParams.set('filter', `tags:${tags.join('+')}`)
22
+ }
23
+
24
+ const featureExperiments: ExperimentType[] = []
25
+ let hasMore = true
26
+ const offset = 0
27
+ const limit = 10
28
+
29
+ while (hasMore) {
30
+ url.searchParams.set('offset', offset.toString())
31
+ url.searchParams.set('limit', limit.toString())
32
+ const responseFlags = await fetch(url, {
33
+ headers: {
34
+ Authorization: secret,
35
+ },
36
+ })
37
+
38
+ const {items} = await responseFlags.json()
39
+ const experiments = items.map((flag: LaunchDarklyFlagItem) => ({
40
+ id: flag.key,
41
+ label: flag.name,
42
+ variants: flag.variations.map((variation) => ({
43
+ id: variation.value,
44
+ label: variation.name,
45
+ })),
46
+ }))
47
+ featureExperiments.push(...experiments)
48
+ if (items.length !== limit) {
49
+ hasMore = false
50
+ }
51
+ }
52
+
53
+ return featureExperiments
54
+ }
@@ -1,78 +0,0 @@
1
- import {SanityClient} from 'sanity'
2
-
3
- import {GrowthbookABConfig} from '../growthbookFieldExperiments'
4
- import {ExperimentType, GrowthbookFeature, VariantType} from '../types'
5
-
6
- const getBooleanConversion = (value: string) => {
7
- // this way or the other way around?
8
- if (value === 'true') {
9
- return 'variant'
10
- } else if (value === 'false') {
11
- return 'control'
12
- }
13
- return value
14
- }
15
-
16
- export const getExperiments = async ({
17
- client,
18
- environment,
19
- baseUrl,
20
- project,
21
- convertBooleans,
22
- }: Omit<GrowthbookABConfig, 'fields'> & {client: SanityClient}): Promise<ExperimentType[]> => {
23
- const query = `*[_id == 'secrets.growthbook'][0].secrets.apiKey`
24
-
25
- const secret = await client.fetch(query) // secret is stored in the content lake using @sanity/studio-secrets
26
- if (!secret) return []
27
-
28
- const featureExperiments: ExperimentType[] = []
29
- let hasMore = true
30
- let offset = 0
31
- const url = new URL(baseUrl ?? 'https://api.growthbook.io/api/v1/features')
32
- if (project) {
33
- url.searchParams.set('projectId', project)
34
- }
35
-
36
- while (hasMore) {
37
- url.searchParams.set('offset', offset.toString())
38
- const response = await fetch(url, {
39
- headers: {
40
- Authorization: `Bearer ${secret}`,
41
- },
42
- })
43
-
44
- const {features, hasMore: responseHasMore, nextOffset} = await response.json()
45
-
46
- hasMore = responseHasMore
47
- offset = nextOffset
48
- if (!features) continue
49
-
50
- features.forEach((feature: GrowthbookFeature) => {
51
- if (feature.archived) {
52
- return undefined
53
- }
54
- const experiments = feature.environments[environment]?.rules.filter(
55
- (experiment) => experiment.type === 'experiment-ref' || experiment.type === 'experiment',
56
- )
57
-
58
- if (!experiments) {
59
- return undefined
60
- }
61
-
62
- const variations = new Set<VariantType>()
63
- experiments.forEach((experiment) => {
64
- experiment?.variations.forEach((variant) => {
65
- variations.add({
66
- id: convertBooleans ? getBooleanConversion(variant.value) : variant.value,
67
- label: convertBooleans ? getBooleanConversion(variant.value) : variant.value,
68
- })
69
- })
70
- })
71
- const value = {id: feature.id, label: feature.id, variants: [...variations]}
72
-
73
- featureExperiments.push(value)
74
- return undefined
75
- })
76
- }
77
- return featureExperiments
78
- }