@sanity/personalization-plugin 2.5.0 → 3.0.0
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/LICENSE +1 -1
- package/README.md +570 -144
- package/dist/growthbook/index.d.ts +12 -15
- package/dist/growthbook/index.d.ts.map +1 -0
- package/dist/growthbook/index.js +104 -68
- package/dist/growthbook/index.js.map +1 -1
- package/dist/index.d.ts +212 -252
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +398 -301
- package/dist/index.js.map +1 -1
- package/dist/launchDarkly/index.d.ts +9 -12
- package/dist/launchDarkly/index.d.ts.map +1 -0
- package/dist/launchDarkly/index.js +74 -46
- package/dist/launchDarkly/index.js.map +1 -1
- package/package.json +35 -77
- package/dist/growthbook/index.d.mts +0 -15
- package/dist/growthbook/index.mjs +0 -124
- package/dist/growthbook/index.mjs.map +0 -1
- package/dist/index.d.mts +0 -267
- package/dist/index.mjs +0 -472
- package/dist/index.mjs.map +0 -1
- package/dist/launchDarkly/index.d.mts +0 -12
- package/dist/launchDarkly/index.mjs +0 -107
- package/dist/launchDarkly/index.mjs.map +0 -1
- package/sanity.json +0 -8
- package/src/components/Array.tsx +0 -68
- package/src/components/ExperimentContext.tsx +0 -65
- package/src/components/ExperimentField.tsx +0 -138
- package/src/components/ExperimentInput.tsx +0 -75
- package/src/components/ExperimentItem.tsx +0 -10
- package/src/components/Select.tsx +0 -43
- package/src/components/VariantInput.tsx +0 -19
- package/src/components/VariantPreview.tsx +0 -75
- package/src/fieldExperiments.tsx +0 -266
- package/src/growthbook/Components/GrowthbookContext.tsx +0 -38
- package/src/growthbook/Components/Secrets.tsx +0 -47
- package/src/growthbook/index.ts +0 -54
- package/src/growthbook/types.ts +0 -15
- package/src/growthbook/utils.ts +0 -94
- package/src/index.ts +0 -3
- package/src/launchDarkly/components/LaunchDarklyContext.tsx +0 -36
- package/src/launchDarkly/components/Secrets.tsx +0 -46
- package/src/launchDarkly/index.ts +0 -52
- package/src/launchDarkly/types.ts +0 -193
- package/src/launchDarkly/utils.ts +0 -54
- package/src/types.ts +0 -245
- package/src/utils/flattenSchemaType.ts +0 -47
- package/v2-incompatible.js +0 -11
package/src/fieldExperiments.tsx
DELETED
|
@@ -1,266 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ArrayOfObjectsInputProps,
|
|
3
|
-
defineField,
|
|
4
|
-
definePlugin,
|
|
5
|
-
defineType,
|
|
6
|
-
FieldDefinition,
|
|
7
|
-
isObjectInputProps,
|
|
8
|
-
} from 'sanity'
|
|
9
|
-
|
|
10
|
-
import {ArrayInput} from './components/Array'
|
|
11
|
-
import {CONFIG_DEFAULT, ExperimentProvider} from './components/ExperimentContext'
|
|
12
|
-
import {ExperimentField} from './components/ExperimentField'
|
|
13
|
-
import {ExperimentInput} from './components/ExperimentInput'
|
|
14
|
-
import {ExperimentItem} from './components/ExperimentItem'
|
|
15
|
-
import {VariantInput} from './components/VariantInput'
|
|
16
|
-
import {VariantPreview} from './components/VariantPreview'
|
|
17
|
-
import {FieldPluginConfig} from './types'
|
|
18
|
-
import {flattenSchemaType} from './utils/flattenSchemaType'
|
|
19
|
-
|
|
20
|
-
const createExperimentType = ({
|
|
21
|
-
field,
|
|
22
|
-
experimentNameOverride,
|
|
23
|
-
variantNameOverride,
|
|
24
|
-
variantId,
|
|
25
|
-
variantArrayName,
|
|
26
|
-
experimentId,
|
|
27
|
-
}: {
|
|
28
|
-
field: string | FieldDefinition
|
|
29
|
-
experimentNameOverride: string
|
|
30
|
-
variantNameOverride: string
|
|
31
|
-
variantId: string
|
|
32
|
-
variantArrayName: string
|
|
33
|
-
experimentId: string
|
|
34
|
-
}) => {
|
|
35
|
-
const typeName = typeof field === `string` ? field : field.name
|
|
36
|
-
const usedName = String(typeName[0]).toUpperCase() + String(typeName).slice(1)
|
|
37
|
-
const variantName = `${variantNameOverride}${usedName}`
|
|
38
|
-
|
|
39
|
-
return defineType({
|
|
40
|
-
name: `${experimentNameOverride}${usedName}`,
|
|
41
|
-
type: 'object',
|
|
42
|
-
components: {
|
|
43
|
-
field: (props) => (
|
|
44
|
-
<ExperimentField
|
|
45
|
-
{...props}
|
|
46
|
-
experimentId={experimentId}
|
|
47
|
-
experimentNameOverride={experimentNameOverride}
|
|
48
|
-
variantNameOverride={variantNameOverride}
|
|
49
|
-
/>
|
|
50
|
-
),
|
|
51
|
-
item: ExperimentItem,
|
|
52
|
-
},
|
|
53
|
-
fields: [
|
|
54
|
-
typeof field === `string`
|
|
55
|
-
? // Define a simple field if all we have is the name as a string
|
|
56
|
-
defineField({
|
|
57
|
-
name: 'default',
|
|
58
|
-
type: field,
|
|
59
|
-
})
|
|
60
|
-
: // Pass in the configured options, but overwrite the name
|
|
61
|
-
{
|
|
62
|
-
...field,
|
|
63
|
-
name: 'default',
|
|
64
|
-
},
|
|
65
|
-
defineField({
|
|
66
|
-
name: 'active',
|
|
67
|
-
type: 'boolean',
|
|
68
|
-
hidden: true,
|
|
69
|
-
initialValue: false,
|
|
70
|
-
}),
|
|
71
|
-
defineField({
|
|
72
|
-
name: experimentId,
|
|
73
|
-
type: 'string',
|
|
74
|
-
components: {
|
|
75
|
-
input: (props) => (
|
|
76
|
-
<ExperimentInput
|
|
77
|
-
{...props}
|
|
78
|
-
experimentNameOverride={experimentNameOverride}
|
|
79
|
-
variantNameOverride={variantNameOverride}
|
|
80
|
-
/>
|
|
81
|
-
),
|
|
82
|
-
},
|
|
83
|
-
hidden: ({parent}) => {
|
|
84
|
-
return !parent?.active
|
|
85
|
-
},
|
|
86
|
-
}),
|
|
87
|
-
defineField({
|
|
88
|
-
name: variantArrayName,
|
|
89
|
-
type: 'array',
|
|
90
|
-
hidden: ({parent}) => {
|
|
91
|
-
return !parent?.[experimentId]
|
|
92
|
-
},
|
|
93
|
-
components: {
|
|
94
|
-
input: (props: ArrayOfObjectsInputProps) => (
|
|
95
|
-
<ArrayInput
|
|
96
|
-
{...props}
|
|
97
|
-
variantName={variantName}
|
|
98
|
-
variantId={variantId}
|
|
99
|
-
experimentId={experimentId}
|
|
100
|
-
/>
|
|
101
|
-
),
|
|
102
|
-
},
|
|
103
|
-
of: [
|
|
104
|
-
defineField({
|
|
105
|
-
name: variantName,
|
|
106
|
-
type: variantName,
|
|
107
|
-
}),
|
|
108
|
-
],
|
|
109
|
-
}),
|
|
110
|
-
],
|
|
111
|
-
preview: {
|
|
112
|
-
select: {
|
|
113
|
-
base: 'default',
|
|
114
|
-
experiment: experimentId,
|
|
115
|
-
},
|
|
116
|
-
prepare: ({base, experiment}) => {
|
|
117
|
-
const title = base?.title || base?.name || typeof base === 'string' ? base : ''
|
|
118
|
-
const experimentTitle = experiment ? `Experiment: ${experiment}` : ''
|
|
119
|
-
const media = base?.image || base?.photo || base?.media || ''
|
|
120
|
-
return {
|
|
121
|
-
title: title || experimentTitle,
|
|
122
|
-
subtitle: title ? experimentTitle : '',
|
|
123
|
-
media,
|
|
124
|
-
}
|
|
125
|
-
},
|
|
126
|
-
},
|
|
127
|
-
})
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const createVariantType = ({
|
|
131
|
-
field,
|
|
132
|
-
variantNameOverride,
|
|
133
|
-
variantId,
|
|
134
|
-
experimentId,
|
|
135
|
-
}: {
|
|
136
|
-
field: string | FieldDefinition
|
|
137
|
-
variantNameOverride: string
|
|
138
|
-
variantId: string
|
|
139
|
-
experimentId: string
|
|
140
|
-
}) => {
|
|
141
|
-
const typeName = typeof field === `string` ? field : field.name
|
|
142
|
-
const usedName = String(typeName[0]).toUpperCase() + String(typeName).slice(1)
|
|
143
|
-
return defineType({
|
|
144
|
-
name: `${variantNameOverride}${usedName}`,
|
|
145
|
-
title: `${variantNameOverride} array ${usedName}`,
|
|
146
|
-
type: 'object',
|
|
147
|
-
components: {
|
|
148
|
-
preview: VariantPreview,
|
|
149
|
-
input: VariantInput,
|
|
150
|
-
},
|
|
151
|
-
fields: [
|
|
152
|
-
{
|
|
153
|
-
type: 'string',
|
|
154
|
-
name: variantId,
|
|
155
|
-
readOnly: true,
|
|
156
|
-
},
|
|
157
|
-
{
|
|
158
|
-
type: 'string',
|
|
159
|
-
name: experimentId,
|
|
160
|
-
hidden: true,
|
|
161
|
-
},
|
|
162
|
-
typeof field === `string`
|
|
163
|
-
? // Define a simple field if all we have is the name as a string
|
|
164
|
-
defineField({
|
|
165
|
-
name: 'value',
|
|
166
|
-
type: field,
|
|
167
|
-
// hidden: ({parent}) => !parent?.[`${objectNameOverride}Id`],
|
|
168
|
-
})
|
|
169
|
-
: // Pass in the configured options, but overwrite the name
|
|
170
|
-
{
|
|
171
|
-
...field,
|
|
172
|
-
name: 'value',
|
|
173
|
-
// hidden: ({parent}) => !parent?.[`${objectNameOverride}Id`],
|
|
174
|
-
},
|
|
175
|
-
],
|
|
176
|
-
preview: {
|
|
177
|
-
select: {
|
|
178
|
-
variant: variantId,
|
|
179
|
-
experiment: experimentId,
|
|
180
|
-
value: 'value',
|
|
181
|
-
},
|
|
182
|
-
},
|
|
183
|
-
})
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const fieldSchema = ({
|
|
187
|
-
fields,
|
|
188
|
-
experimentNameOverride,
|
|
189
|
-
variantNameOverride,
|
|
190
|
-
variantId,
|
|
191
|
-
variantArrayName,
|
|
192
|
-
experimentId,
|
|
193
|
-
}: Required<Omit<FieldPluginConfig, 'apiVersion' | 'experiments'>>) => {
|
|
194
|
-
return [
|
|
195
|
-
...fields.map((field) =>
|
|
196
|
-
createVariantType({field, variantNameOverride, variantId, experimentId}),
|
|
197
|
-
),
|
|
198
|
-
...fields.map((field) =>
|
|
199
|
-
createExperimentType({
|
|
200
|
-
field,
|
|
201
|
-
experimentNameOverride,
|
|
202
|
-
variantNameOverride,
|
|
203
|
-
variantId,
|
|
204
|
-
variantArrayName,
|
|
205
|
-
experimentId,
|
|
206
|
-
}),
|
|
207
|
-
),
|
|
208
|
-
]
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
export const fieldLevelExperiments = definePlugin<FieldPluginConfig>((config) => {
|
|
212
|
-
const pluginConfig = {...CONFIG_DEFAULT, ...config}
|
|
213
|
-
const {fields, experimentNameOverride, variantNameOverride} = pluginConfig
|
|
214
|
-
|
|
215
|
-
const experimentId = `${experimentNameOverride}Id`
|
|
216
|
-
const variantArrayName = `${variantNameOverride}s`
|
|
217
|
-
const variantId = `${variantNameOverride}Id`
|
|
218
|
-
|
|
219
|
-
const fieldSchemaConfig = fieldSchema({
|
|
220
|
-
fields,
|
|
221
|
-
experimentNameOverride,
|
|
222
|
-
variantNameOverride,
|
|
223
|
-
variantId,
|
|
224
|
-
variantArrayName,
|
|
225
|
-
experimentId,
|
|
226
|
-
})
|
|
227
|
-
return {
|
|
228
|
-
name: 'sanity-personalistaion-plugin-field-level-experiments',
|
|
229
|
-
schema: {
|
|
230
|
-
types: fieldSchemaConfig,
|
|
231
|
-
},
|
|
232
|
-
form: {
|
|
233
|
-
components: {
|
|
234
|
-
input: (props) => {
|
|
235
|
-
const isRootInput = props.id === 'root' && isObjectInputProps(props)
|
|
236
|
-
|
|
237
|
-
if (!isRootInput) {
|
|
238
|
-
return props.renderDefault(props)
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
const flatFields = flattenSchemaType(props.schemaType)
|
|
242
|
-
const hasExperiment = flatFields.some(
|
|
243
|
-
(field) =>
|
|
244
|
-
field.type.name.startsWith(experimentNameOverride) ||
|
|
245
|
-
field.name.startsWith(experimentNameOverride),
|
|
246
|
-
)
|
|
247
|
-
|
|
248
|
-
if (!hasExperiment) {
|
|
249
|
-
return props.renderDefault(props)
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
const providerProps = {
|
|
253
|
-
...props,
|
|
254
|
-
experimentFieldPluginConfig: {
|
|
255
|
-
...pluginConfig,
|
|
256
|
-
variantId,
|
|
257
|
-
variantArrayName,
|
|
258
|
-
experimentId,
|
|
259
|
-
},
|
|
260
|
-
}
|
|
261
|
-
return ExperimentProvider(providerProps)
|
|
262
|
-
},
|
|
263
|
-
},
|
|
264
|
-
},
|
|
265
|
-
}
|
|
266
|
-
})
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import {createContext, useContext, useMemo, useState} from 'react'
|
|
2
|
-
import {ObjectInputProps} from 'sanity'
|
|
3
|
-
|
|
4
|
-
import {GrowthbookContextProps, GrowthbookExperimentFieldPluginConfig} from '../types'
|
|
5
|
-
import {Secrets} from './Secrets'
|
|
6
|
-
|
|
7
|
-
export const GROWTHBOOK_CONFIG_DEFAULT = {
|
|
8
|
-
baseUrl: 'https://api.growthbook.io/api/v1',
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export const GrowthbookContext = createContext<GrowthbookContextProps>({
|
|
12
|
-
setSecret: () => undefined,
|
|
13
|
-
secret: undefined,
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
export function useGrowthbookContext() {
|
|
17
|
-
return useContext(GrowthbookContext)
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
type GrowthbookProps = ObjectInputProps & {
|
|
21
|
-
growthbookFieldPluginConfig: GrowthbookExperimentFieldPluginConfig
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function GrowthbookProvider(props: GrowthbookProps) {
|
|
25
|
-
const {growthbookFieldPluginConfig} = props
|
|
26
|
-
const [secret, setSecret] = useState<string | undefined>()
|
|
27
|
-
|
|
28
|
-
const context = useMemo(
|
|
29
|
-
() => ({...growthbookFieldPluginConfig, secret, setSecret}),
|
|
30
|
-
[growthbookFieldPluginConfig, secret, setSecret],
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
return (
|
|
34
|
-
<GrowthbookContext.Provider value={context}>
|
|
35
|
-
<Secrets {...props} />
|
|
36
|
-
</GrowthbookContext.Provider>
|
|
37
|
-
)
|
|
38
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import {SettingsView, useSecrets} from '@sanity/studio-secrets'
|
|
2
|
-
import {useEffect, useState} from 'react'
|
|
3
|
-
import {ObjectInputProps} from 'sanity'
|
|
4
|
-
|
|
5
|
-
import {useGrowthbookContext} from './GrowthbookContext'
|
|
6
|
-
|
|
7
|
-
export const namespace = 'growthbook'
|
|
8
|
-
|
|
9
|
-
export const pluginConfigKeys = [
|
|
10
|
-
{
|
|
11
|
-
key: 'apiKey',
|
|
12
|
-
title: 'Your secret API key',
|
|
13
|
-
},
|
|
14
|
-
]
|
|
15
|
-
|
|
16
|
-
export const Secrets = (props: ObjectInputProps) => {
|
|
17
|
-
const {secrets, loading} = useSecrets(namespace) as {secrets: {apiKey: string}; loading: boolean}
|
|
18
|
-
const {setSecret} = useGrowthbookContext()
|
|
19
|
-
const [showSettings, setShowSettings] = useState<boolean>(false)
|
|
20
|
-
|
|
21
|
-
useEffect(() => {
|
|
22
|
-
if (loading) return undefined
|
|
23
|
-
if (!secrets && !loading) {
|
|
24
|
-
setSecret(undefined)
|
|
25
|
-
return setShowSettings(true)
|
|
26
|
-
}
|
|
27
|
-
setSecret(secrets.apiKey)
|
|
28
|
-
return setShowSettings(false)
|
|
29
|
-
}, [secrets, loading, setSecret])
|
|
30
|
-
|
|
31
|
-
if (!showSettings) {
|
|
32
|
-
return props.renderDefault(props)
|
|
33
|
-
}
|
|
34
|
-
return (
|
|
35
|
-
<>
|
|
36
|
-
<SettingsView
|
|
37
|
-
title={'Growthbook secret'}
|
|
38
|
-
namespace={namespace}
|
|
39
|
-
keys={pluginConfigKeys}
|
|
40
|
-
onClose={() => {
|
|
41
|
-
setShowSettings(false)
|
|
42
|
-
}}
|
|
43
|
-
/>
|
|
44
|
-
{props.renderDefault(props)}
|
|
45
|
-
</>
|
|
46
|
-
)
|
|
47
|
-
}
|
package/src/growthbook/index.ts
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import {definePlugin, isObjectInputProps} from 'sanity'
|
|
2
|
-
|
|
3
|
-
import {fieldLevelExperiments as baseFieldLevelExperiments} from '../fieldExperiments'
|
|
4
|
-
import {flattenSchemaType} from '../utils/flattenSchemaType'
|
|
5
|
-
import {GROWTHBOOK_CONFIG_DEFAULT, GrowthbookProvider} from './Components/GrowthbookContext'
|
|
6
|
-
import {GrowthbookExperimentFieldPluginConfig} from './types'
|
|
7
|
-
import {getExperiments} from './utils'
|
|
8
|
-
|
|
9
|
-
export const fieldLevelExperiments = definePlugin<GrowthbookExperimentFieldPluginConfig>(
|
|
10
|
-
(config) => {
|
|
11
|
-
const pluginConfig = {...GROWTHBOOK_CONFIG_DEFAULT, ...config}
|
|
12
|
-
const {fields, environment, project, convertBooleans, baseUrl, tags} = pluginConfig
|
|
13
|
-
return {
|
|
14
|
-
name: 'sanity-growthbook-personalistaion-plugin-field-level-experiments',
|
|
15
|
-
plugins: [
|
|
16
|
-
baseFieldLevelExperiments({
|
|
17
|
-
fields,
|
|
18
|
-
experiments: (client) =>
|
|
19
|
-
getExperiments({client, environment, baseUrl, project, convertBooleans, tags}),
|
|
20
|
-
}),
|
|
21
|
-
],
|
|
22
|
-
|
|
23
|
-
form: {
|
|
24
|
-
components: {
|
|
25
|
-
input: (props) => {
|
|
26
|
-
const isRootInput = props.id === 'root' && isObjectInputProps(props)
|
|
27
|
-
|
|
28
|
-
if (!isRootInput) {
|
|
29
|
-
return props.renderDefault(props)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const flatFieldTypeNames = flattenSchemaType(props.schemaType).map(
|
|
33
|
-
(field) => field.type.name,
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
const hasExperiment = flatFieldTypeNames.some((name) => name.startsWith('experiment'))
|
|
37
|
-
|
|
38
|
-
if (!hasExperiment) {
|
|
39
|
-
return props.renderDefault(props)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const providerProps = {
|
|
43
|
-
...props,
|
|
44
|
-
growthbookFieldPluginConfig: {
|
|
45
|
-
...pluginConfig,
|
|
46
|
-
},
|
|
47
|
-
}
|
|
48
|
-
return GrowthbookProvider(providerProps)
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
},
|
|
52
|
-
}
|
|
53
|
-
},
|
|
54
|
-
)
|
package/src/growthbook/types.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import {FieldDefinition} from 'sanity'
|
|
2
|
-
|
|
3
|
-
export type GrowthbookExperimentFieldPluginConfig = {
|
|
4
|
-
fields: (string | FieldDefinition)[]
|
|
5
|
-
environment: string
|
|
6
|
-
baseUrl?: string
|
|
7
|
-
project?: string
|
|
8
|
-
convertBooleans?: boolean
|
|
9
|
-
tags?: string[]
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export type GrowthbookContextProps = {
|
|
13
|
-
setSecret: (secret: string | undefined) => void
|
|
14
|
-
secret: string | undefined
|
|
15
|
-
}
|
package/src/growthbook/utils.ts
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import {SanityClient} from 'sanity'
|
|
2
|
-
|
|
3
|
-
import {ExperimentType, GrowthbookFeature, VariantType} from '../types'
|
|
4
|
-
import {namespace, pluginConfigKeys} from './Components/Secrets'
|
|
5
|
-
import {GrowthbookExperimentFieldPluginConfig} from './types'
|
|
6
|
-
|
|
7
|
-
const getBooleanConversion = (value: string) => {
|
|
8
|
-
// control is false
|
|
9
|
-
if (value === 'true') {
|
|
10
|
-
return 'variant'
|
|
11
|
-
} else if (value === 'false') {
|
|
12
|
-
return 'control'
|
|
13
|
-
}
|
|
14
|
-
return value
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export const getExperiments = async ({
|
|
18
|
-
client,
|
|
19
|
-
environment,
|
|
20
|
-
baseUrl,
|
|
21
|
-
project,
|
|
22
|
-
convertBooleans,
|
|
23
|
-
tags,
|
|
24
|
-
}: Omit<GrowthbookExperimentFieldPluginConfig, 'fields' | 'baseUrl'> & {
|
|
25
|
-
client: SanityClient
|
|
26
|
-
baseUrl: string
|
|
27
|
-
}): Promise<ExperimentType[]> => {
|
|
28
|
-
const query = `*[_id == 'secrets.${namespace}'][0].secrets.${pluginConfigKeys[0].key}`
|
|
29
|
-
|
|
30
|
-
const secret = await client.fetch(query) // secret is stored in the content lake using @sanity/studio-secrets
|
|
31
|
-
if (!secret) return []
|
|
32
|
-
|
|
33
|
-
const featureExperiments: ExperimentType[] = []
|
|
34
|
-
let hasMore = true
|
|
35
|
-
let offset = 0
|
|
36
|
-
const url = new URL(`${baseUrl}/features`)
|
|
37
|
-
if (project) {
|
|
38
|
-
url.searchParams.set('projectId', project)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
while (hasMore) {
|
|
42
|
-
url.searchParams.set('offset', offset.toString())
|
|
43
|
-
const response = await fetch(url, {
|
|
44
|
-
headers: {
|
|
45
|
-
Authorization: `Bearer ${secret}`,
|
|
46
|
-
},
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
const {features, hasMore: responseHasMore, nextOffset} = await response.json()
|
|
50
|
-
|
|
51
|
-
hasMore = responseHasMore
|
|
52
|
-
offset = nextOffset
|
|
53
|
-
if (!features) continue
|
|
54
|
-
|
|
55
|
-
features.forEach((feature: GrowthbookFeature) => {
|
|
56
|
-
if (feature.archived) {
|
|
57
|
-
return undefined
|
|
58
|
-
}
|
|
59
|
-
if (tags && feature.tags && !feature.tags.some((tag) => tags.includes(tag))) {
|
|
60
|
-
return undefined
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const experiments = feature.environments[environment]?.rules.filter(
|
|
64
|
-
(experiment) => experiment.type === 'experiment-ref' || experiment.type === 'experiment',
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
if (!experiments) {
|
|
68
|
-
return undefined
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const variations: VariantType[] = []
|
|
72
|
-
const uniqueValues = new Set<string>()
|
|
73
|
-
|
|
74
|
-
experiments.forEach((experiment) => {
|
|
75
|
-
experiment?.variations.forEach((variant) => {
|
|
76
|
-
const value = convertBooleans ? getBooleanConversion(variant.value) : variant.value
|
|
77
|
-
if (!uniqueValues.has(value)) {
|
|
78
|
-
uniqueValues.add(value)
|
|
79
|
-
variations.push({
|
|
80
|
-
id: value,
|
|
81
|
-
label: value,
|
|
82
|
-
})
|
|
83
|
-
}
|
|
84
|
-
})
|
|
85
|
-
})
|
|
86
|
-
const value = {id: feature.id, label: feature.id, variants: variations}
|
|
87
|
-
|
|
88
|
-
featureExperiments.push(value)
|
|
89
|
-
return undefined
|
|
90
|
-
})
|
|
91
|
-
}
|
|
92
|
-
const sortedFeatureExperiments = featureExperiments.sort((a, b) => a.id.localeCompare(b.id))
|
|
93
|
-
return sortedFeatureExperiments
|
|
94
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import {createContext, useContext, useMemo, useState} from 'react'
|
|
2
|
-
import {ObjectInputProps} from 'sanity'
|
|
3
|
-
|
|
4
|
-
import {LaunchDarklyContextProps, LaunchDarklyFieldLevelConfig} from '../types'
|
|
5
|
-
import {Secrets} from './Secrets'
|
|
6
|
-
|
|
7
|
-
export const LAUNCHDARKLY_CONFIG_DEFAULT = {}
|
|
8
|
-
|
|
9
|
-
export const LaunchDarklyContext = createContext<LaunchDarklyContextProps>({
|
|
10
|
-
setSecret: () => undefined,
|
|
11
|
-
secret: undefined,
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
export function useLaunchDarklyContext() {
|
|
15
|
-
return useContext(LaunchDarklyContext)
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
type LaunchDarklyProps = ObjectInputProps & {
|
|
19
|
-
launchDarklyFieldPluginConfig: LaunchDarklyFieldLevelConfig
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function LaunchDarklyProvider(props: LaunchDarklyProps) {
|
|
23
|
-
const {launchDarklyFieldPluginConfig} = props
|
|
24
|
-
const [secret, setSecret] = useState<string | undefined>()
|
|
25
|
-
|
|
26
|
-
const context = useMemo(
|
|
27
|
-
() => ({...launchDarklyFieldPluginConfig, secret, setSecret}),
|
|
28
|
-
[launchDarklyFieldPluginConfig, secret, setSecret],
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
return (
|
|
32
|
-
<LaunchDarklyContext.Provider value={context}>
|
|
33
|
-
<Secrets {...props} />
|
|
34
|
-
</LaunchDarklyContext.Provider>
|
|
35
|
-
)
|
|
36
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import {SettingsView, useSecrets} from '@sanity/studio-secrets'
|
|
2
|
-
import {useEffect, useState} from 'react'
|
|
3
|
-
import {ObjectInputProps} from 'sanity'
|
|
4
|
-
|
|
5
|
-
import {useLaunchDarklyContext} from './LaunchDarklyContext'
|
|
6
|
-
|
|
7
|
-
const namespace = 'launchdarkly'
|
|
8
|
-
const pluginConfigKeys = [
|
|
9
|
-
{
|
|
10
|
-
key: 'apiKey',
|
|
11
|
-
title: 'Your secret API key',
|
|
12
|
-
},
|
|
13
|
-
]
|
|
14
|
-
|
|
15
|
-
export const Secrets = (props: ObjectInputProps) => {
|
|
16
|
-
const {secrets, loading} = useSecrets(namespace) as {secrets: {apiKey: string}; loading: boolean}
|
|
17
|
-
const {setSecret} = useLaunchDarklyContext()
|
|
18
|
-
const [showSettings, setShowSettings] = useState<boolean>(false)
|
|
19
|
-
|
|
20
|
-
useEffect(() => {
|
|
21
|
-
if (loading) return undefined
|
|
22
|
-
if (!secrets && !loading) {
|
|
23
|
-
setSecret(undefined)
|
|
24
|
-
return setShowSettings(true)
|
|
25
|
-
}
|
|
26
|
-
setSecret(secrets.apiKey)
|
|
27
|
-
return setShowSettings(false)
|
|
28
|
-
}, [secrets, loading, setSecret])
|
|
29
|
-
|
|
30
|
-
if (!showSettings) {
|
|
31
|
-
return props.renderDefault(props)
|
|
32
|
-
}
|
|
33
|
-
return (
|
|
34
|
-
<>
|
|
35
|
-
<SettingsView
|
|
36
|
-
title={`${namespace} api key`}
|
|
37
|
-
namespace={namespace}
|
|
38
|
-
keys={pluginConfigKeys}
|
|
39
|
-
onClose={() => {
|
|
40
|
-
setShowSettings(false)
|
|
41
|
-
}}
|
|
42
|
-
/>
|
|
43
|
-
{props.renderDefault(props)}
|
|
44
|
-
</>
|
|
45
|
-
)
|
|
46
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import {definePlugin, isObjectInputProps} from 'sanity'
|
|
2
|
-
|
|
3
|
-
import {fieldLevelExperiments as baseFieldLevelExperiments} from '../fieldExperiments'
|
|
4
|
-
import {flattenSchemaType} from '../utils/flattenSchemaType'
|
|
5
|
-
import {LAUNCHDARKLY_CONFIG_DEFAULT, LaunchDarklyProvider} from './components/LaunchDarklyContext'
|
|
6
|
-
import {LaunchDarklyFieldLevelConfig} from './types'
|
|
7
|
-
import {getExperiments} from './utils'
|
|
8
|
-
|
|
9
|
-
export const fieldLevelExperiments = definePlugin<LaunchDarklyFieldLevelConfig>((config) => {
|
|
10
|
-
const pluginConfig = {...LAUNCHDARKLY_CONFIG_DEFAULT, ...config}
|
|
11
|
-
const {fields, projectKey, tags} = pluginConfig
|
|
12
|
-
return {
|
|
13
|
-
name: 'sanity-growthbook-personalistaion-plugin-field-level-experiments',
|
|
14
|
-
plugins: [
|
|
15
|
-
baseFieldLevelExperiments({
|
|
16
|
-
fields,
|
|
17
|
-
experiments: (client) => getExperiments({client, projectKey, tags}),
|
|
18
|
-
experimentNameOverride: 'flag',
|
|
19
|
-
}),
|
|
20
|
-
],
|
|
21
|
-
|
|
22
|
-
form: {
|
|
23
|
-
components: {
|
|
24
|
-
input: (props) => {
|
|
25
|
-
const isRootInput = props.id === 'root' && isObjectInputProps(props)
|
|
26
|
-
|
|
27
|
-
if (!isRootInput) {
|
|
28
|
-
return props.renderDefault(props)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const flatFieldTypeNames = flattenSchemaType(props.schemaType).map(
|
|
32
|
-
(field) => field.type.name,
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
const hasExperiment = flatFieldTypeNames.some((name) => name.startsWith('flag'))
|
|
36
|
-
|
|
37
|
-
if (!hasExperiment) {
|
|
38
|
-
return props.renderDefault(props)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const providerProps = {
|
|
42
|
-
...props,
|
|
43
|
-
launchDarklyFieldPluginConfig: {
|
|
44
|
-
...pluginConfig,
|
|
45
|
-
},
|
|
46
|
-
}
|
|
47
|
-
return LaunchDarklyProvider(providerProps)
|
|
48
|
-
},
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
}
|
|
52
|
-
})
|