@sanity/personalization-plugin 2.3.0-launch-darkly.2 → 2.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/personalization-plugin",
3
- "version": "2.3.0-launch-darkly.2",
3
+ "version": "2.3.0",
4
4
  "description": "Plugin to help with personalization, a/b testing when using Sanity",
5
5
  "keywords": [
6
6
  "sanity",
@@ -24,10 +24,10 @@
24
24
  "import": "./dist/index.mjs",
25
25
  "default": "./dist/index.js"
26
26
  },
27
- "./launchDarkly": {
28
- "source": "./src/launchDarkly/index.ts",
29
- "import": "./dist/launchDarkly/index.mjs",
30
- "default": "./dist/launchDarkly/index.js"
27
+ "./growthbook": {
28
+ "source": "./src/growthbook/index.ts",
29
+ "import": "./dist/growthbook/index.mjs",
30
+ "default": "./dist/growthbook/index.js"
31
31
  },
32
32
  "./package.json": "./package.json"
33
33
  },
@@ -50,7 +50,7 @@
50
50
  },
51
51
  "dependencies": {
52
52
  "@sanity/incompatible-plugin": "^1.0.4",
53
- "@sanity/studio-secrets": "^3.0.1",
53
+ "@sanity/studio-secrets": "^3.0.0",
54
54
  "@sanity/ui": "^2.8.19",
55
55
  "@sanity/uuid": "^3.0.2",
56
56
  "fast-deep-equal": "^3.1.3",
@@ -42,7 +42,6 @@ const useRemoveExperimentAction = (
42
42
  },
43
43
  ): DocumentFieldActionItem => {
44
44
  const {onChange, active, experimentId, experimentNameOverride, variantNameOverride} = props
45
-
46
45
  const handleClearAction = useCallback(() => {
47
46
  const activeId = ['active']
48
47
  const experiment = [experimentId]
@@ -97,10 +96,7 @@ const createActions = ({
97
96
  experimentId,
98
97
  }),
99
98
  })
100
- if (active) {
101
- return removeAction
102
- }
103
- return addAction
99
+ return active ? removeAction : addAction
104
100
  }
105
101
 
106
102
  export const ExperimentField = (
@@ -2,6 +2,7 @@ import {Card, Text} from '@sanity/ui'
2
2
  import {FormEvent, useCallback, useMemo} from 'react'
3
3
  import {
4
4
  FormPatch,
5
+ getPublishedId,
5
6
  PatchEvent,
6
7
  set,
7
8
  StringInputProps,
@@ -27,13 +28,13 @@ export const ExperimentInput = (
27
28
  const {experiments} = useExperimentContext()
28
29
 
29
30
  const id = useFormValue(['_id']) as string
30
- const aditionalChangePath = useMemo(
31
+ const additionalChangePath = useMemo(
31
32
  () => [...props.path.slice(0, -1), `${props.variantNameOverride}s`],
32
33
  [props.variantNameOverride, props.path],
33
34
  )
35
+ const subValues = useFormValue(additionalChangePath)
34
36
 
35
- const subValues = useFormValue(aditionalChangePath)
36
- const {patch} = useDocumentOperation(id.replace('drafts.', ''), props.schemaType.name)
37
+ const {patch} = useDocumentOperation(getPublishedId(id), props.schemaType.name)
37
38
 
38
39
  const handleChange = useCallback(
39
40
  (
@@ -51,12 +52,12 @@ export const ExperimentInput = (
51
52
 
52
53
  if (subValues) {
53
54
  const patchEvent = {
54
- unset: [aditionalChangePath.join('.')],
55
+ unset: [additionalChangePath.join('.')],
55
56
  }
56
57
  patch.execute([patchEvent])
57
58
  }
58
59
  },
59
- [patch, subValues, aditionalChangePath],
60
+ [patch, subValues, additionalChangePath],
60
61
  )
61
62
 
62
63
  if (!experiments.length)
@@ -0,0 +1,38 @@
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
+ }
@@ -2,10 +2,11 @@ import {SettingsView, useSecrets} from '@sanity/studio-secrets'
2
2
  import {useEffect, useState} from 'react'
3
3
  import {ObjectInputProps} from 'sanity'
4
4
 
5
- import {useLaunchDarklyContext} from './LaunchDarklyContext'
5
+ import {useGrowthbookContext} from './GrowthbookContext'
6
6
 
7
- const namespace = 'launchdarkly'
8
- const pluginConfigKeys = [
7
+ export const namespace = 'growthbook'
8
+
9
+ export const pluginConfigKeys = [
9
10
  {
10
11
  key: 'apiKey',
11
12
  title: 'Your secret API key',
@@ -14,7 +15,7 @@ const pluginConfigKeys = [
14
15
 
15
16
  export const Secrets = (props: ObjectInputProps) => {
16
17
  const {secrets, loading} = useSecrets(namespace) as {secrets: {apiKey: string}; loading: boolean}
17
- const {setSecret} = useLaunchDarklyContext()
18
+ const {setSecret} = useGrowthbookContext()
18
19
  const [showSettings, setShowSettings] = useState<boolean>(false)
19
20
 
20
21
  useEffect(() => {
@@ -33,7 +34,7 @@ export const Secrets = (props: ObjectInputProps) => {
33
34
  return (
34
35
  <>
35
36
  <SettingsView
36
- title={`${namespace} api key`}
37
+ title={'Growthbook secret'}
37
38
  namespace={namespace}
38
39
  keys={pluginConfigKeys}
39
40
  onClose={() => {
@@ -0,0 +1,54 @@
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
+ )
@@ -0,0 +1,15 @@
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
+ }
@@ -0,0 +1,94 @@
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/types.ts CHANGED
@@ -67,3 +67,179 @@ export type ExperimentGeneric<T> = {
67
67
  | T
68
68
  | undefined
69
69
  }
70
+
71
+ export type GrowthbookExperiment = {
72
+ id: string
73
+ dateCreated: string
74
+ dateUpdated: string
75
+ name: string
76
+ project: string
77
+ hypothesis: string
78
+ description: string
79
+ tags: [string]
80
+ owner: string
81
+ archived: boolean
82
+ status: string
83
+ autoRefresh: boolean
84
+ hashAttribute: string
85
+ fallbackAttribute: string
86
+ hashVersion: number
87
+ disableStickyBucketing: boolean
88
+ bucketVersion: number
89
+ minBucketVersion: number
90
+ variations: [
91
+ {
92
+ variationId: string
93
+ key: string
94
+ name: string
95
+ description: string
96
+ screenshots: [string]
97
+ },
98
+ ]
99
+ phases: [
100
+ {
101
+ name: string
102
+ dateStarted: string
103
+ dateEnded: string
104
+ reasonForStopping: string
105
+ seed: string
106
+ coverage: 0
107
+ trafficSplit: [
108
+ {
109
+ variationId: string
110
+ weight: 0
111
+ },
112
+ ]
113
+ namespace: {
114
+ namespaceId: string
115
+ range: []
116
+ }
117
+ targetingCondition: string
118
+ savedGroupTargeting: [
119
+ {
120
+ matchType: string
121
+ savedGroups: [string]
122
+ },
123
+ ]
124
+ },
125
+ ]
126
+ settings: {
127
+ datasourceId: string
128
+ assignmentQueryId: string
129
+ experimentId: string
130
+ segmentId: string
131
+ queryFilter: string
132
+ inProgressConversions: string
133
+ attributionModel: string
134
+ statsEngine: string
135
+ regressionAdjustmentEnabled: boolean
136
+ goals: [
137
+ {
138
+ metricId: string
139
+ overrides: {
140
+ delayHours: 0
141
+ windowHours: 0
142
+ window: string
143
+ winRiskThreshold: 0
144
+ loseRiskThreshold: 0
145
+ }
146
+ },
147
+ ]
148
+ secondaryMetrics: [
149
+ {
150
+ metricId: string
151
+ overrides: {
152
+ delayHours: 0
153
+ windowHours: 0
154
+ window: string
155
+ winRiskThreshold: 0
156
+ loseRiskThreshold: 0
157
+ }
158
+ },
159
+ ]
160
+ guardrails: [
161
+ {
162
+ metricId: string
163
+ overrides: {
164
+ delayHours: 0
165
+ windowHours: 0
166
+ window: string
167
+ winRiskThreshold: 0
168
+ loseRiskThreshold: 0
169
+ }
170
+ },
171
+ ]
172
+ activationMetric: {
173
+ metricId: string
174
+ overrides: {
175
+ delayHours: 0
176
+ windowHours: 0
177
+ window: string
178
+ winRiskThreshold: 0
179
+ loseRiskThreshold: 0
180
+ }
181
+ }
182
+ }
183
+ resultSummary: {
184
+ status: string
185
+ winner: string
186
+ conclusions: string
187
+ releasedVariationId: string
188
+ excludeFromPayload: boolean
189
+ }
190
+ }
191
+
192
+ export type GrowthbookFeature = {
193
+ id: string
194
+ dateCreated: string
195
+ dateUpdated: string
196
+ archived: boolean
197
+ description: string
198
+ owner: string
199
+ project: string
200
+ valueType: string
201
+ defaultValue: string
202
+ tags: string[]
203
+ environments: {
204
+ [key: string]: {
205
+ enabled: boolean
206
+ defaultValue: string
207
+ rules: {
208
+ description: string
209
+ condition: string
210
+ savedGroupTargeting: {matchType: string; savedGroups: string[]}[]
211
+ id: string
212
+ enabled: boolean
213
+ type: string
214
+ value: string
215
+ variations: {value: string; variationId: string}[]
216
+ }[]
217
+ definition: string
218
+ draft: {
219
+ enabled: boolean
220
+ defaultValue: string
221
+ rules: {
222
+ description: string
223
+ condition: string
224
+ savedGroupTargeting: {matchType: string; savedGroups: string[]}[]
225
+ id: string
226
+ enabled: boolean
227
+ type: string
228
+ value: string
229
+ variations: {value: string; variationId: string}[]
230
+ }[]
231
+ definition: string
232
+ }
233
+ }
234
+ }
235
+ prerequisites: {
236
+ parentId: string
237
+ parentCondition: string
238
+ }[]
239
+ revision: {
240
+ version: number
241
+ comment: string
242
+ date: string
243
+ publishedBy: string
244
+ }
245
+ }
@@ -1,12 +0,0 @@
1
- import {FieldDefinition} from 'sanity'
2
- import {Plugin as Plugin_2} from 'sanity'
3
-
4
- export declare const fieldLevelExperiments: Plugin_2<LaunchDarklyFieldLevelConfig>
5
-
6
- declare type LaunchDarklyFieldLevelConfig = {
7
- fields: (string | FieldDefinition)[]
8
- projectKey: string
9
- tags?: string[]
10
- }
11
-
12
- export {}
@@ -1,12 +0,0 @@
1
- import {FieldDefinition} from 'sanity'
2
- import {Plugin as Plugin_2} from 'sanity'
3
-
4
- export declare const fieldLevelExperiments: Plugin_2<LaunchDarklyFieldLevelConfig>
5
-
6
- declare type LaunchDarklyFieldLevelConfig = {
7
- fields: (string | FieldDefinition)[]
8
- projectKey: string
9
- tags?: string[]
10
- }
11
-
12
- export {}
@@ -1,103 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: !0 });
3
- var sanity = require("sanity"), index = require("../index.js"), jsxRuntime = require("react/jsx-runtime"), react = require("react"), studioSecrets = require("@sanity/studio-secrets");
4
- const namespace = "launchdarkly", pluginConfigKeys = [
5
- {
6
- key: "apiKey",
7
- title: "Your secret API key"
8
- }
9
- ], Secrets = (props) => {
10
- const { secrets, loading } = studioSecrets.useSecrets(namespace), { setSecret } = useLaunchDarklyContext(), [showSettings, setShowSettings] = react.useState(!1);
11
- return react.useEffect(() => {
12
- if (!loading)
13
- return !secrets && !loading ? (setSecret(void 0), setShowSettings(!0)) : (setSecret(secrets.apiKey), setShowSettings(!1));
14
- }, [secrets, loading, setSecret]), showSettings ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
15
- /* @__PURE__ */ jsxRuntime.jsx(
16
- studioSecrets.SettingsView,
17
- {
18
- title: `${namespace} api key`,
19
- namespace,
20
- keys: pluginConfigKeys,
21
- onClose: () => {
22
- setShowSettings(!1);
23
- }
24
- }
25
- ),
26
- props.renderDefault(props)
27
- ] }) : props.renderDefault(props);
28
- }, LAUNCHDARKLY_CONFIG_DEFAULT = {}, LaunchDarklyContext = react.createContext({
29
- setSecret: () => {
30
- },
31
- secret: void 0
32
- });
33
- function useLaunchDarklyContext() {
34
- return react.useContext(LaunchDarklyContext);
35
- }
36
- function LaunchDarklyProvider(props) {
37
- const { launchDarklyFieldPluginConfig } = props, [secret, setSecret] = react.useState(), context = react.useMemo(
38
- () => ({ ...launchDarklyFieldPluginConfig, secret, setSecret }),
39
- [launchDarklyFieldPluginConfig, secret, setSecret]
40
- );
41
- return /* @__PURE__ */ jsxRuntime.jsx(LaunchDarklyContext.Provider, { value: context, children: /* @__PURE__ */ jsxRuntime.jsx(Secrets, { ...props }) });
42
- }
43
- const getExperiments = async ({
44
- client,
45
- projectKey,
46
- tags
47
- }) => {
48
- const secret = await client.fetch("*[_id == 'secrets.launchdarkly'][0].secrets.apiKey");
49
- if (!secret) return [];
50
- const url = new URL(`https://app.launchdarkly.com/api/v2/flags/${projectKey}`);
51
- tags && url.searchParams.set("filter", `tags:${tags.join("+")}`);
52
- const featureExperiments = [];
53
- let hasMore = !0;
54
- const offset = 0, limit = 10;
55
- for (; hasMore; ) {
56
- url.searchParams.set("offset", offset.toString()), url.searchParams.set("limit", limit.toString());
57
- const responseFlags = await fetch(url, {
58
- headers: {
59
- Authorization: secret
60
- }
61
- }), { items } = await responseFlags.json(), experiments = items.map((flag) => ({
62
- id: flag.key,
63
- label: flag.name,
64
- variants: flag.variations.map((variation) => ({
65
- id: variation.value,
66
- label: variation.name ?? variation.value
67
- }))
68
- }));
69
- featureExperiments.push(...experiments), items.length !== limit && (hasMore = !1);
70
- }
71
- return featureExperiments;
72
- }, fieldLevelExperiments = sanity.definePlugin((config) => {
73
- const pluginConfig = { ...LAUNCHDARKLY_CONFIG_DEFAULT, ...config }, { fields, projectKey, tags } = pluginConfig;
74
- return {
75
- name: "sanity-growthbook-personalistaion-plugin-field-level-experiments",
76
- plugins: [
77
- index.fieldLevelExperiments({
78
- fields,
79
- experiments: (client) => getExperiments({ client, projectKey, tags }),
80
- experimentNameOverride: "flag"
81
- })
82
- ],
83
- form: {
84
- components: {
85
- input: (props) => {
86
- if (!(props.id === "root" && sanity.isObjectInputProps(props)) || !index.flattenSchemaType(props.schemaType).map(
87
- (field) => field.type.name
88
- ).some((name) => name.startsWith("flag")))
89
- return props.renderDefault(props);
90
- const providerProps = {
91
- ...props,
92
- launchDarklyFieldPluginConfig: {
93
- ...pluginConfig
94
- }
95
- };
96
- return LaunchDarklyProvider(providerProps);
97
- }
98
- }
99
- }
100
- };
101
- });
102
- exports.fieldLevelExperiments = fieldLevelExperiments;
103
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sources":["../../src/launchDarkly/components/Secrets.tsx","../../src/launchDarkly/components/LaunchDarklyContext.tsx","../../src/launchDarkly/utils.ts","../../src/launchDarkly/index.ts"],"sourcesContent":["import {SettingsView, useSecrets} from '@sanity/studio-secrets'\nimport {useEffect, useState} from 'react'\nimport {ObjectInputProps} from 'sanity'\n\nimport {useLaunchDarklyContext} from './LaunchDarklyContext'\n\nconst namespace = 'launchdarkly'\nconst pluginConfigKeys = [\n {\n key: 'apiKey',\n title: 'Your secret API key',\n },\n]\n\nexport const Secrets = (props: ObjectInputProps) => {\n const {secrets, loading} = useSecrets(namespace) as {secrets: {apiKey: string}; loading: boolean}\n const {setSecret} = useLaunchDarklyContext()\n const [showSettings, setShowSettings] = useState<boolean>(false)\n\n useEffect(() => {\n if (loading) return undefined\n if (!secrets && !loading) {\n setSecret(undefined)\n return setShowSettings(true)\n }\n setSecret(secrets.apiKey)\n return setShowSettings(false)\n }, [secrets, loading, setSecret])\n\n if (!showSettings) {\n return props.renderDefault(props)\n }\n return (\n <>\n <SettingsView\n title={`${namespace} api key`}\n namespace={namespace}\n keys={pluginConfigKeys}\n onClose={() => {\n setShowSettings(false)\n }}\n />\n {props.renderDefault(props)}\n </>\n )\n}\n","import {createContext, useContext, useMemo, useState} from 'react'\nimport {ObjectInputProps} from 'sanity'\n\nimport {LaunchDarklyContextProps, LaunchDarklyFieldLevelConfig} from '../types'\nimport {Secrets} from './Secrets'\n\nexport const LAUNCHDARKLY_CONFIG_DEFAULT = {}\n\nexport const LaunchDarklyContext = createContext<LaunchDarklyContextProps>({\n setSecret: () => undefined,\n secret: undefined,\n})\n\nexport function useLaunchDarklyContext() {\n return useContext(LaunchDarklyContext)\n}\n\ntype LaunchDarklyProps = ObjectInputProps & {\n launchDarklyFieldPluginConfig: LaunchDarklyFieldLevelConfig\n}\n\nexport function LaunchDarklyProvider(props: LaunchDarklyProps) {\n const {launchDarklyFieldPluginConfig} = props\n const [secret, setSecret] = useState<string | undefined>()\n\n const context = useMemo(\n () => ({...launchDarklyFieldPluginConfig, secret, setSecret}),\n [launchDarklyFieldPluginConfig, secret, setSecret],\n )\n\n return (\n <LaunchDarklyContext.Provider value={context}>\n <Secrets {...props} />\n </LaunchDarklyContext.Provider>\n )\n}\n","import {SanityClient} from 'sanity'\n\nimport {ExperimentType} from '../types'\nimport {LaunchDarklyFieldLevelConfig, LaunchDarklyFlagItem} from './types'\n\nexport const getExperiments = async ({\n client,\n projectKey,\n tags,\n}: Omit<LaunchDarklyFieldLevelConfig, 'fields'> & {client: SanityClient}): Promise<\n ExperimentType[]\n> => {\n const query = `*[_id == 'secrets.launchdarkly'][0].secrets.apiKey`\n\n const secret = await client.fetch(query) // secret is stored in the content lake using @sanity/studio-secrets\n if (!secret) return []\n\n const url = new URL(`https://app.launchdarkly.com/api/v2/flags/${projectKey}`)\n\n if (tags) {\n url.searchParams.set('filter', `tags:${tags.join('+')}`)\n }\n\n const featureExperiments: ExperimentType[] = []\n let hasMore = true\n const offset = 0\n const limit = 10\n\n while (hasMore) {\n url.searchParams.set('offset', offset.toString())\n url.searchParams.set('limit', limit.toString())\n const responseFlags = await fetch(url, {\n headers: {\n Authorization: secret,\n },\n })\n\n const {items} = await responseFlags.json()\n const experiments = items.map((flag: LaunchDarklyFlagItem) => ({\n id: flag.key,\n label: flag.name,\n variants: flag.variations.map((variation) => ({\n id: variation.value,\n label: variation.name ?? variation.value,\n })),\n }))\n featureExperiments.push(...experiments)\n if (items.length !== limit) {\n hasMore = false\n }\n }\n\n return featureExperiments\n}\n","import {definePlugin, isObjectInputProps} from 'sanity'\n\nimport {fieldLevelExperiments as baseFieldLevelExperiments} from '../fieldExperiments'\nimport {flattenSchemaType} from '../utils/flattenSchemaType'\nimport {LAUNCHDARKLY_CONFIG_DEFAULT, LaunchDarklyProvider} from './components/LaunchDarklyContext'\nimport {LaunchDarklyFieldLevelConfig} from './types'\nimport {getExperiments} from './utils'\n\nexport const fieldLevelExperiments = definePlugin<LaunchDarklyFieldLevelConfig>((config) => {\n const pluginConfig = {...LAUNCHDARKLY_CONFIG_DEFAULT, ...config}\n const {fields, projectKey, tags} = pluginConfig\n return {\n name: 'sanity-growthbook-personalistaion-plugin-field-level-experiments',\n plugins: [\n baseFieldLevelExperiments({\n fields,\n experiments: (client) => getExperiments({client, projectKey, tags}),\n experimentNameOverride: 'flag',\n }),\n ],\n\n form: {\n components: {\n input: (props) => {\n const isRootInput = props.id === 'root' && isObjectInputProps(props)\n\n if (!isRootInput) {\n return props.renderDefault(props)\n }\n\n const flatFieldTypeNames = flattenSchemaType(props.schemaType).map(\n (field) => field.type.name,\n )\n\n const hasExperiment = flatFieldTypeNames.some((name) => name.startsWith('flag'))\n\n if (!hasExperiment) {\n return props.renderDefault(props)\n }\n\n const providerProps = {\n ...props,\n launchDarklyFieldPluginConfig: {\n ...pluginConfig,\n },\n }\n return LaunchDarklyProvider(providerProps)\n },\n },\n },\n }\n})\n"],"names":["useSecrets","useState","useEffect","jsxs","Fragment","jsx","SettingsView","createContext","useContext","useMemo","definePlugin","baseFieldLevelExperiments","isObjectInputProps","flattenSchemaType"],"mappings":"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"}