@sanity/personalization-plugin 2.3.0-launch-darkly.1 → 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/README.md +2 -0
- package/dist/growthbook/index.d.mts +15 -0
- package/dist/growthbook/index.d.ts +15 -0
- package/dist/growthbook/index.js +120 -0
- package/dist/growthbook/index.js.map +1 -0
- package/dist/growthbook/index.mjs +124 -0
- package/dist/growthbook/index.mjs.map +1 -0
- package/dist/index.d.mts +171 -177
- package/dist/index.d.ts +171 -177
- package/dist/index.js +47 -107
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +50 -111
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -2
- package/src/components/ExperimentContext.tsx +4 -7
- package/src/components/ExperimentField.tsx +54 -32
- package/src/components/ExperimentInput.tsx +7 -6
- package/src/fieldExperiments.tsx +1 -0
- package/src/growthbook/Components/GrowthbookContext.tsx +38 -0
- package/src/{components → growthbook/Components}/Secrets.tsx +7 -5
- package/src/growthbook/index.ts +54 -0
- package/src/growthbook/types.ts +15 -0
- package/src/growthbook/utils.ts +94 -0
- package/src/index.ts +0 -1
- package/src/types.ts +160 -169
- 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.3.0
|
|
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,6 +24,11 @@
|
|
|
24
24
|
"import": "./dist/index.mjs",
|
|
25
25
|
"default": "./dist/index.js"
|
|
26
26
|
},
|
|
27
|
+
"./growthbook": {
|
|
28
|
+
"source": "./src/growthbook/index.ts",
|
|
29
|
+
"import": "./dist/growthbook/index.mjs",
|
|
30
|
+
"default": "./dist/growthbook/index.js"
|
|
31
|
+
},
|
|
27
32
|
"./package.json": "./package.json"
|
|
28
33
|
},
|
|
29
34
|
"main": "./dist/index.js",
|
|
@@ -45,7 +50,7 @@
|
|
|
45
50
|
},
|
|
46
51
|
"dependencies": {
|
|
47
52
|
"@sanity/incompatible-plugin": "^1.0.4",
|
|
48
|
-
"@sanity/studio-secrets": "^3.0.
|
|
53
|
+
"@sanity/studio-secrets": "^3.0.0",
|
|
49
54
|
"@sanity/ui": "^2.8.19",
|
|
50
55
|
"@sanity/uuid": "^3.0.2",
|
|
51
56
|
"fast-deep-equal": "^3.1.3",
|
|
@@ -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, 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({
|
|
@@ -90,27 +96,43 @@ const newActions = ({
|
|
|
90
96
|
experimentId,
|
|
91
97
|
}),
|
|
92
98
|
})
|
|
93
|
-
|
|
94
|
-
return removeAction
|
|
95
|
-
}
|
|
96
|
-
return addAction
|
|
99
|
+
return active ? removeAction : addAction
|
|
97
100
|
}
|
|
98
101
|
|
|
99
102
|
export const ExperimentField = (
|
|
100
|
-
props: ObjectFieldProps & {
|
|
103
|
+
props: ObjectFieldProps & {
|
|
104
|
+
experimentNameOverride: string
|
|
105
|
+
experimentId: string
|
|
106
|
+
variantNameOverride: string
|
|
107
|
+
},
|
|
101
108
|
) => {
|
|
102
109
|
const {onChange} = props.inputProps
|
|
103
|
-
const {inputId, experimentNameOverride, experimentId} = props
|
|
110
|
+
const {inputId, experimentNameOverride, experimentId, variantNameOverride} = props
|
|
104
111
|
const active = props.value?.active as boolean | undefined
|
|
105
112
|
|
|
106
|
-
const
|
|
113
|
+
const actionProps = useMemo(
|
|
114
|
+
() => ({
|
|
115
|
+
onChange,
|
|
116
|
+
inputId,
|
|
117
|
+
active,
|
|
118
|
+
experimentNameOverride,
|
|
119
|
+
experimentId,
|
|
120
|
+
variantNameOverride,
|
|
121
|
+
}),
|
|
122
|
+
[onChange, inputId, active, experimentNameOverride, experimentId, variantNameOverride],
|
|
123
|
+
)
|
|
107
124
|
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
125
|
+
const memoizedActions = useMemo(() => {
|
|
126
|
+
const oldActions = props.actions || []
|
|
127
|
+
return [createActions(actionProps), ...oldActions]
|
|
128
|
+
}, [actionProps, props.actions])
|
|
129
|
+
|
|
130
|
+
const withActionProps = useMemo(
|
|
131
|
+
() => ({
|
|
132
|
+
...props,
|
|
133
|
+
actions: memoizedActions,
|
|
134
|
+
}),
|
|
135
|
+
[props, memoizedActions],
|
|
136
|
+
)
|
|
115
137
|
return props.renderDefault(withActionProps)
|
|
116
138
|
}
|
|
@@ -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
|
|
31
|
-
() => [...props.path.slice(0, -1), props.variantNameOverride],
|
|
31
|
+
const additionalChangePath = useMemo(
|
|
32
|
+
() => [...props.path.slice(0, -1), `${props.variantNameOverride}s`],
|
|
32
33
|
[props.variantNameOverride, props.path],
|
|
33
34
|
)
|
|
34
|
-
const subValues = useFormValue(
|
|
35
|
+
const subValues = useFormValue(additionalChangePath)
|
|
35
36
|
|
|
36
|
-
const {patch} = useDocumentOperation(id
|
|
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: [
|
|
55
|
+
unset: [additionalChangePath.join('.')],
|
|
55
56
|
}
|
|
56
57
|
patch.execute([patchEvent])
|
|
57
58
|
}
|
|
58
59
|
},
|
|
59
|
-
[patch, subValues,
|
|
60
|
+
[patch, subValues, additionalChangePath],
|
|
60
61
|
)
|
|
61
62
|
|
|
62
63
|
if (!experiments.length)
|
package/src/fieldExperiments.tsx
CHANGED
|
@@ -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,18 +2,20 @@ import {SettingsView, useSecrets} from '@sanity/studio-secrets'
|
|
|
2
2
|
import {useEffect, useState} from 'react'
|
|
3
3
|
import {ObjectInputProps} from 'sanity'
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import {useGrowthbookContext} from './GrowthbookContext'
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
export const namespace = 'growthbook'
|
|
8
|
+
|
|
9
|
+
export const pluginConfigKeys = [
|
|
8
10
|
{
|
|
9
11
|
key: 'apiKey',
|
|
10
12
|
title: 'Your secret API key',
|
|
11
13
|
},
|
|
12
14
|
]
|
|
13
15
|
|
|
14
|
-
export const Secrets = (props: ObjectInputProps
|
|
16
|
+
export const Secrets = (props: ObjectInputProps) => {
|
|
15
17
|
const {secrets, loading} = useSecrets(namespace) as {secrets: {apiKey: string}; loading: boolean}
|
|
16
|
-
const {setSecret} =
|
|
18
|
+
const {setSecret} = useGrowthbookContext()
|
|
17
19
|
const [showSettings, setShowSettings] = useState<boolean>(false)
|
|
18
20
|
|
|
19
21
|
useEffect(() => {
|
|
@@ -32,7 +34,7 @@ export const Secrets = (props: ObjectInputProps, namespace: string) => {
|
|
|
32
34
|
return (
|
|
33
35
|
<>
|
|
34
36
|
<SettingsView
|
|
35
|
-
title={
|
|
37
|
+
title={'Growthbook secret'}
|
|
36
38
|
namespace={namespace}
|
|
37
39
|
keys={pluginConfigKeys}
|
|
38
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/index.ts
CHANGED