@sanity/personalization-plugin 2.2.0-launch-darkly.1 → 2.2.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/dist/index.d.mts +0 -194
- package/dist/index.d.ts +0 -194
- package/dist/index.js +60 -109
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +64 -114
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -3
- package/src/components/ExperimentContext.tsx +4 -7
- package/src/components/ExperimentField.tsx +53 -28
- package/src/components/ExperimentInput.tsx +19 -8
- package/src/fieldExperiments.tsx +6 -1
- package/src/index.ts +0 -1
- package/src/types.ts +0 -185
- package/src/components/Secrets.tsx +0 -45
- package/src/launchDarklyExperiments.tsx +0 -48
- package/src/utils/launchDarkly.ts +0 -54
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/personalization-plugin",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.1",
|
|
4
4
|
"description": "Plugin to help with personalization, a/b testing when using Sanity",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -45,7 +45,6 @@
|
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@sanity/incompatible-plugin": "^1.0.4",
|
|
48
|
-
"@sanity/studio-secrets": "^3.0.1",
|
|
49
48
|
"@sanity/ui": "^2.8.19",
|
|
50
49
|
"@sanity/uuid": "^3.0.2",
|
|
51
50
|
"fast-deep-equal": "^3.1.3",
|
|
@@ -79,7 +78,7 @@
|
|
|
79
78
|
"typescript": "^5.7.3"
|
|
80
79
|
},
|
|
81
80
|
"peerDependencies": {
|
|
82
|
-
"react": "^18",
|
|
81
|
+
"react": "^18 || ^19",
|
|
83
82
|
"sanity": "^3"
|
|
84
83
|
},
|
|
85
84
|
"engines": {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import equal from 'fast-deep-equal'
|
|
2
|
-
import {createContext, useContext, useMemo
|
|
2
|
+
import {createContext, useContext, useMemo} from 'react'
|
|
3
3
|
import {type ObjectInputProps, useClient, useWorkspace} from 'sanity'
|
|
4
4
|
import {suspend} from 'suspend-react'
|
|
5
5
|
|
|
@@ -21,8 +21,6 @@ export const CONFIG_DEFAULT = {
|
|
|
21
21
|
export const ExperimentContext = createContext<ExperimentContextProps>({
|
|
22
22
|
...CONFIG_DEFAULT,
|
|
23
23
|
experiments: [],
|
|
24
|
-
setSecret: () => undefined,
|
|
25
|
-
secret: undefined,
|
|
26
24
|
})
|
|
27
25
|
|
|
28
26
|
export function useExperimentContext() {
|
|
@@ -35,7 +33,6 @@ type ExperimentProps = ObjectInputProps & {
|
|
|
35
33
|
|
|
36
34
|
export function ExperimentProvider(props: ExperimentProps) {
|
|
37
35
|
const {experimentFieldPluginConfig} = props
|
|
38
|
-
const [secret, setSecret] = useState<string | undefined>()
|
|
39
36
|
|
|
40
37
|
const client = useClient({apiVersion: experimentFieldPluginConfig.apiVersion})
|
|
41
38
|
const workspace = useWorkspace()
|
|
@@ -51,13 +48,13 @@ export function ExperimentProvider(props: ExperimentProps) {
|
|
|
51
48
|
}
|
|
52
49
|
return experimentFieldPluginConfig.experiments
|
|
53
50
|
},
|
|
54
|
-
[workspace
|
|
51
|
+
[workspace],
|
|
55
52
|
{equal},
|
|
56
53
|
)
|
|
57
54
|
|
|
58
55
|
const context = useMemo(
|
|
59
|
-
() => ({...experimentFieldPluginConfig, experiments
|
|
60
|
-
[experimentFieldPluginConfig, experiments
|
|
56
|
+
() => ({...experimentFieldPluginConfig, experiments}),
|
|
57
|
+
[experimentFieldPluginConfig, experiments],
|
|
61
58
|
)
|
|
62
59
|
|
|
63
60
|
return (
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {CloseIcon} from '@sanity/icons'
|
|
2
|
+
import {useCallback, useMemo} from 'react'
|
|
2
3
|
import {GiSoapExperiment} from 'react-icons/gi'
|
|
3
4
|
import {
|
|
4
5
|
defineDocumentFieldAction,
|
|
@@ -18,9 +19,9 @@ const useAddExperimentAction = (
|
|
|
18
19
|
): DocumentFieldActionItem => {
|
|
19
20
|
const {onChange, active, experimentNameOverride} = props
|
|
20
21
|
|
|
21
|
-
const handleAddAction = () => {
|
|
22
|
+
const handleAddAction = useCallback(() => {
|
|
22
23
|
onChange([set(!active, ['active'])])
|
|
23
|
-
}
|
|
24
|
+
}, [onChange, active])
|
|
24
25
|
|
|
25
26
|
return {
|
|
26
27
|
title: `Add ${experimentNameOverride}`,
|
|
@@ -33,23 +34,21 @@ const useAddExperimentAction = (
|
|
|
33
34
|
|
|
34
35
|
const useRemoveExperimentAction = (
|
|
35
36
|
props: DocumentFieldActionProps &
|
|
36
|
-
PatchStuff & {
|
|
37
|
+
PatchStuff & {
|
|
38
|
+
experimentNameOverride: string
|
|
39
|
+
experimentId: string
|
|
40
|
+
active: boolean
|
|
41
|
+
variantNameOverride: string
|
|
42
|
+
},
|
|
37
43
|
): DocumentFieldActionItem => {
|
|
38
|
-
const {onChange, active, experimentId, experimentNameOverride} = props
|
|
39
|
-
const
|
|
44
|
+
const {onChange, active, experimentId, experimentNameOverride, variantNameOverride} = props
|
|
45
|
+
const handleClearAction = useCallback(() => {
|
|
40
46
|
const activeId = ['active']
|
|
41
|
-
return set(!active, activeId)
|
|
42
|
-
}
|
|
43
|
-
const patchClearEvent = () => {
|
|
44
47
|
const experiment = [experimentId]
|
|
45
|
-
const variants = [
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const clearEvents = patchClearEvent()
|
|
50
|
-
const activeEvent = patchActiveFalseEvent()
|
|
51
|
-
onChange([activeEvent, ...clearEvents])
|
|
52
|
-
}
|
|
48
|
+
const variants = [`${variantNameOverride}s`]
|
|
49
|
+
onChange([set(!active, activeId), unset(experiment), unset(variants)])
|
|
50
|
+
}, [onChange, active, experimentId, experimentNameOverride, variantNameOverride])
|
|
51
|
+
|
|
53
52
|
return {
|
|
54
53
|
title: `Remove ${experimentNameOverride}`,
|
|
55
54
|
type: 'action',
|
|
@@ -59,13 +58,19 @@ const useRemoveExperimentAction = (
|
|
|
59
58
|
}
|
|
60
59
|
}
|
|
61
60
|
|
|
62
|
-
const
|
|
61
|
+
const createActions = ({
|
|
63
62
|
onChange,
|
|
64
63
|
inputId,
|
|
65
64
|
active,
|
|
66
65
|
experimentNameOverride,
|
|
67
66
|
experimentId,
|
|
68
|
-
|
|
67
|
+
variantNameOverride,
|
|
68
|
+
}: PatchStuff & {
|
|
69
|
+
active?: boolean
|
|
70
|
+
experimentNameOverride: string
|
|
71
|
+
experimentId: string
|
|
72
|
+
variantNameOverride: string
|
|
73
|
+
}) => {
|
|
69
74
|
const removeAction = defineDocumentFieldAction({
|
|
70
75
|
name: `Remove ${experimentNameOverride}`,
|
|
71
76
|
useAction: (props) =>
|
|
@@ -76,6 +81,7 @@ const newActions = ({
|
|
|
76
81
|
inputId,
|
|
77
82
|
experimentNameOverride,
|
|
78
83
|
experimentId,
|
|
84
|
+
variantNameOverride,
|
|
79
85
|
}),
|
|
80
86
|
})
|
|
81
87
|
const addAction = defineDocumentFieldAction({
|
|
@@ -97,20 +103,39 @@ const newActions = ({
|
|
|
97
103
|
}
|
|
98
104
|
|
|
99
105
|
export const ExperimentField = (
|
|
100
|
-
props: ObjectFieldProps & {
|
|
106
|
+
props: ObjectFieldProps & {
|
|
107
|
+
experimentNameOverride: string
|
|
108
|
+
experimentId: string
|
|
109
|
+
variantNameOverride: string
|
|
110
|
+
},
|
|
101
111
|
) => {
|
|
102
112
|
const {onChange} = props.inputProps
|
|
103
|
-
const {inputId, experimentNameOverride, experimentId} = props
|
|
113
|
+
const {inputId, experimentNameOverride, experimentId, variantNameOverride} = props
|
|
104
114
|
const active = props.value?.active as boolean | undefined
|
|
105
115
|
|
|
106
|
-
const
|
|
116
|
+
const actionProps = useMemo(
|
|
117
|
+
() => ({
|
|
118
|
+
onChange,
|
|
119
|
+
inputId,
|
|
120
|
+
active,
|
|
121
|
+
experimentNameOverride,
|
|
122
|
+
experimentId,
|
|
123
|
+
variantNameOverride,
|
|
124
|
+
}),
|
|
125
|
+
[onChange, inputId, active, experimentNameOverride, experimentId, variantNameOverride],
|
|
126
|
+
)
|
|
107
127
|
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
128
|
+
const memoizedActions = useMemo(() => {
|
|
129
|
+
const oldActions = props.actions || []
|
|
130
|
+
return [createActions(actionProps), ...oldActions]
|
|
131
|
+
}, [actionProps, props.actions])
|
|
132
|
+
|
|
133
|
+
const withActionProps = useMemo(
|
|
134
|
+
() => ({
|
|
135
|
+
...props,
|
|
136
|
+
actions: memoizedActions,
|
|
137
|
+
}),
|
|
138
|
+
[props, memoizedActions],
|
|
139
|
+
)
|
|
115
140
|
return props.renderDefault(withActionProps)
|
|
116
141
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import {Card, Text} from '@sanity/ui'
|
|
1
2
|
import {FormEvent, useCallback, useMemo} from 'react'
|
|
2
3
|
import {
|
|
3
4
|
FormPatch,
|
|
5
|
+
getPublishedId,
|
|
4
6
|
PatchEvent,
|
|
5
7
|
set,
|
|
6
8
|
StringInputProps,
|
|
@@ -20,17 +22,19 @@ const formatlistOptions = (experiments: ExperimentType[]): SelectOption[] =>
|
|
|
20
22
|
value: experiment.id,
|
|
21
23
|
}))
|
|
22
24
|
|
|
23
|
-
export const ExperimentInput = (
|
|
25
|
+
export const ExperimentInput = (
|
|
26
|
+
props: StringInputProps & {variantNameOverride: string; experimentNameOverride: string},
|
|
27
|
+
) => {
|
|
24
28
|
const {experiments} = useExperimentContext()
|
|
25
29
|
|
|
26
30
|
const id = useFormValue(['_id']) as string
|
|
27
|
-
const
|
|
28
|
-
() => [...props.path.slice(0, -1), props.variantNameOverride],
|
|
31
|
+
const additionalChangePath = useMemo(
|
|
32
|
+
() => [...props.path.slice(0, -1), `${props.variantNameOverride}s`],
|
|
29
33
|
[props.variantNameOverride, props.path],
|
|
30
34
|
)
|
|
31
|
-
const subValues = useFormValue(
|
|
35
|
+
const subValues = useFormValue(additionalChangePath)
|
|
32
36
|
|
|
33
|
-
const {patch} = useDocumentOperation(id
|
|
37
|
+
const {patch} = useDocumentOperation(getPublishedId(id), props.schemaType.name)
|
|
34
38
|
|
|
35
39
|
const handleChange = useCallback(
|
|
36
40
|
(
|
|
@@ -48,15 +52,22 @@ export const ExperimentInput = (props: StringInputProps & {variantNameOverride:
|
|
|
48
52
|
|
|
49
53
|
if (subValues) {
|
|
50
54
|
const patchEvent = {
|
|
51
|
-
unset: [
|
|
55
|
+
unset: [additionalChangePath.join('.')],
|
|
52
56
|
}
|
|
53
57
|
patch.execute([patchEvent])
|
|
54
58
|
}
|
|
55
59
|
},
|
|
56
|
-
[patch, subValues,
|
|
60
|
+
[patch, subValues, additionalChangePath],
|
|
57
61
|
)
|
|
58
62
|
|
|
59
|
-
if (!experiments.length)
|
|
63
|
+
if (!experiments.length)
|
|
64
|
+
return (
|
|
65
|
+
<Card padding={[3, 3, 4]} radius={2} shadow={1} tone="caution">
|
|
66
|
+
<Text align="center" size={[2, 2, 3]}>
|
|
67
|
+
There are no defined {props.experimentNameOverride}s
|
|
68
|
+
</Text>
|
|
69
|
+
</Card>
|
|
70
|
+
)
|
|
60
71
|
|
|
61
72
|
return (
|
|
62
73
|
<Select {...props} listOptions={formatlistOptions(experiments)} handleChange={handleChange} />
|
package/src/fieldExperiments.tsx
CHANGED
|
@@ -44,6 +44,7 @@ const createExperimentType = ({
|
|
|
44
44
|
{...props}
|
|
45
45
|
experimentId={experimentId}
|
|
46
46
|
experimentNameOverride={experimentNameOverride}
|
|
47
|
+
variantNameOverride={variantNameOverride}
|
|
47
48
|
/>
|
|
48
49
|
),
|
|
49
50
|
},
|
|
@@ -69,7 +70,11 @@ const createExperimentType = ({
|
|
|
69
70
|
type: 'string',
|
|
70
71
|
components: {
|
|
71
72
|
input: (props) => (
|
|
72
|
-
<ExperimentInput
|
|
73
|
+
<ExperimentInput
|
|
74
|
+
{...props}
|
|
75
|
+
experimentNameOverride={experimentNameOverride}
|
|
76
|
+
variantNameOverride={variantNameOverride}
|
|
77
|
+
/>
|
|
73
78
|
),
|
|
74
79
|
},
|
|
75
80
|
hidden: ({parent}) => {
|
package/src/index.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import {Dispatch, SetStateAction} from 'react'
|
|
2
1
|
import {
|
|
3
2
|
ArrayOfObjectsInputProps,
|
|
4
3
|
FieldDefinition,
|
|
@@ -38,8 +37,6 @@ export type VariantPreviewProps = Omit<PreviewProps, 'SchemaType'> & {
|
|
|
38
37
|
|
|
39
38
|
export type ExperimentContextProps = Required<FieldPluginConfig> & {
|
|
40
39
|
experiments: ExperimentType[]
|
|
41
|
-
setSecret: Dispatch<SetStateAction<string | undefined>>
|
|
42
|
-
secret: string | undefined
|
|
43
40
|
}
|
|
44
41
|
|
|
45
42
|
export type ArrayInputProps = ArrayOfObjectsInputProps & {
|
|
@@ -70,185 +67,3 @@ export type ExperimentGeneric<T> = {
|
|
|
70
67
|
| T
|
|
71
68
|
| undefined
|
|
72
69
|
}
|
|
73
|
-
|
|
74
|
-
export type LaunchDarklyFlagItem = {
|
|
75
|
-
name: string
|
|
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
|
-
}
|
|
116
|
-
}
|
|
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
|
|
135
|
-
}
|
|
136
|
-
category: string
|
|
137
|
-
isNumeric: boolean
|
|
138
|
-
percentileValue: number
|
|
139
|
-
}
|
|
140
|
-
}>
|
|
141
|
-
}
|
|
142
|
-
customProperties: {
|
|
143
|
-
key: {
|
|
144
|
-
name: string
|
|
145
|
-
value: string[]
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
archived: boolean
|
|
149
|
-
description: 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
|
-
}
|
|
195
|
-
environments: {
|
|
196
|
-
[key: string]: {
|
|
197
|
-
on: boolean
|
|
198
|
-
archived: boolean
|
|
199
|
-
salt: string
|
|
200
|
-
sel: string
|
|
201
|
-
lastModified: number
|
|
202
|
-
version: number
|
|
203
|
-
_site: {
|
|
204
|
-
href: string
|
|
205
|
-
type: 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
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
includeInSnippet: boolean
|
|
253
|
-
goalIds: string[]
|
|
254
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import {SettingsView, useSecrets} from '@sanity/studio-secrets'
|
|
2
|
-
import {useEffect, useState} from 'react'
|
|
3
|
-
import {ObjectInputProps} from 'sanity'
|
|
4
|
-
|
|
5
|
-
import {useExperimentContext} from './ExperimentContext'
|
|
6
|
-
|
|
7
|
-
const pluginConfigKeys = [
|
|
8
|
-
{
|
|
9
|
-
key: 'apiKey',
|
|
10
|
-
title: 'Your secret API key',
|
|
11
|
-
},
|
|
12
|
-
]
|
|
13
|
-
|
|
14
|
-
export const Secrets = (props: ObjectInputProps, namespace: string) => {
|
|
15
|
-
const {secrets, loading} = useSecrets(namespace) as {secrets: {apiKey: string}; loading: boolean}
|
|
16
|
-
const {setSecret} = useExperimentContext()
|
|
17
|
-
const [showSettings, setShowSettings] = useState<boolean>(false)
|
|
18
|
-
|
|
19
|
-
useEffect(() => {
|
|
20
|
-
if (loading) return undefined
|
|
21
|
-
if (!secrets && !loading) {
|
|
22
|
-
setSecret(undefined)
|
|
23
|
-
return setShowSettings(true)
|
|
24
|
-
}
|
|
25
|
-
setSecret(secrets.apiKey)
|
|
26
|
-
return setShowSettings(false)
|
|
27
|
-
}, [secrets, loading, setSecret])
|
|
28
|
-
|
|
29
|
-
if (!showSettings) {
|
|
30
|
-
return props.renderDefault(props)
|
|
31
|
-
}
|
|
32
|
-
return (
|
|
33
|
-
<>
|
|
34
|
-
<SettingsView
|
|
35
|
-
title={`${namespace} api key`}
|
|
36
|
-
namespace={namespace}
|
|
37
|
-
keys={pluginConfigKeys}
|
|
38
|
-
onClose={() => {
|
|
39
|
-
setShowSettings(false)
|
|
40
|
-
}}
|
|
41
|
-
/>
|
|
42
|
-
{props.renderDefault(props)}
|
|
43
|
-
</>
|
|
44
|
-
)
|
|
45
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import {definePlugin, FieldDefinition, isObjectInputProps} from 'sanity'
|
|
2
|
-
|
|
3
|
-
import {Secrets} from './components/Secrets'
|
|
4
|
-
import {fieldLevelExperiments} from './fieldExperiments'
|
|
5
|
-
import {flattenSchemaType} from './utils/flattenSchemaType'
|
|
6
|
-
import {getExperiments} from './utils/launchDarkly'
|
|
7
|
-
|
|
8
|
-
export type LaunchDarklyFieldLevelConfig = {
|
|
9
|
-
fields: (string | FieldDefinition)[]
|
|
10
|
-
projectKey: string
|
|
11
|
-
tags?: string[]
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export const launchDarklyFieldLevel = definePlugin<LaunchDarklyFieldLevelConfig>((config) => {
|
|
15
|
-
const {fields, projectKey, tags} = config
|
|
16
|
-
return {
|
|
17
|
-
name: 'sanity-growthbook-personalistaion-plugin-field-level-experiments',
|
|
18
|
-
plugins: [
|
|
19
|
-
fieldLevelExperiments({
|
|
20
|
-
fields,
|
|
21
|
-
experiments: (client) => getExperiments({client, projectKey, tags}),
|
|
22
|
-
}),
|
|
23
|
-
],
|
|
24
|
-
|
|
25
|
-
form: {
|
|
26
|
-
components: {
|
|
27
|
-
input: (props) => {
|
|
28
|
-
const isRootInput = props.id === 'root' && isObjectInputProps(props)
|
|
29
|
-
|
|
30
|
-
if (!isRootInput) {
|
|
31
|
-
return props.renderDefault(props)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const flatFieldTypeNames = flattenSchemaType(props.schemaType).map(
|
|
35
|
-
(field) => field.type.name,
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
const hasExperiment = flatFieldTypeNames.some((name) => name.startsWith('experiment'))
|
|
39
|
-
|
|
40
|
-
if (!hasExperiment) {
|
|
41
|
-
return props.renderDefault(props)
|
|
42
|
-
}
|
|
43
|
-
return Secrets(props, 'launchdarkly')
|
|
44
|
-
},
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
}
|
|
48
|
-
})
|
|
@@ -1,54 +0,0 @@
|
|
|
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
|
-
}
|