@sanity/personalization-plugin 2.5.0-field-level-personalization.1 → 2.5.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 +2 -1
- package/dist/growthbook/index.js +3 -3
- package/dist/growthbook/index.js.map +1 -1
- package/dist/growthbook/index.mjs +1 -1
- package/dist/index.d.mts +12 -33
- package/dist/index.d.ts +12 -33
- package/dist/index.js +280 -157
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +280 -159
- package/dist/index.mjs.map +1 -1
- package/dist/launchDarkly/index.d.mts +12 -0
- package/dist/launchDarkly/index.d.ts +12 -0
- package/dist/launchDarkly/index.js +103 -0
- package/dist/launchDarkly/index.js.map +1 -0
- package/dist/launchDarkly/index.mjs +107 -0
- package/dist/launchDarkly/index.mjs.map +1 -0
- package/package.json +10 -5
- package/src/components/{experiment/Array.tsx → Array.tsx} +2 -2
- package/src/components/{experiment/Context.tsx → ExperimentContext.tsx} +2 -2
- package/src/components/{experiment/Field.tsx → ExperimentField.tsx} +8 -11
- package/src/components/{experiment/Input.tsx → ExperimentInput.tsx} +4 -4
- package/src/components/{ArrayItem.tsx → ExperimentItem.tsx} +2 -1
- package/src/components/Select.tsx +1 -1
- package/src/components/{experiment/VariantPreview.tsx → VariantPreview.tsx} +2 -2
- package/src/fieldExperiments.tsx +13 -43
- package/src/index.ts +0 -1
- package/src/launchDarkly/components/LaunchDarklyContext.tsx +36 -0
- package/src/launchDarkly/components/Secrets.tsx +46 -0
- package/src/launchDarkly/index.ts +52 -0
- package/src/launchDarkly/types.ts +193 -0
- package/src/launchDarkly/utils.ts +54 -0
- package/src/types.ts +2 -20
- package/dist/_chunks-cjs/fieldExperiments.js +0 -507
- package/dist/_chunks-cjs/fieldExperiments.js.map +0 -1
- package/dist/_chunks-es/fieldExperiments.mjs +0 -511
- package/dist/_chunks-es/fieldExperiments.mjs.map +0 -1
- package/src/components/experiment/index.ts +0 -6
- package/src/components/personalization/Array.tsx +0 -59
- package/src/components/personalization/Context.tsx +0 -61
- package/src/components/personalization/Field.tsx +0 -134
- package/src/components/personalization/SegmentInput.tsx +0 -19
- package/src/components/personalization/SegmentPreview.tsx +0 -71
- package/src/components/personalization/index.ts +0 -5
- package/src/fieldPersonalization.tsx +0 -254
- package/src/utils/clearChildGroups.ts +0 -33
- /package/src/components/{experiment/VariantInput.tsx → VariantInput.tsx} +0 -0
|
@@ -0,0 +1,12 @@
|
|
|
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 {}
|
|
@@ -0,0 +1,12 @@
|
|
|
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 {}
|
|
@@ -0,0 +1,103 @@
|
|
|
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.toString(),
|
|
66
|
+
label: variation.name ?? variation.value.toString()
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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.toString(),\n label: variation.name ?? variation.value.toString(),\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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"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { definePlugin, isObjectInputProps } from "sanity";
|
|
2
|
+
import { fieldLevelExperiments as fieldLevelExperiments$1, flattenSchemaType } from "../index.mjs";
|
|
3
|
+
import { jsxs, Fragment, jsx } from "react/jsx-runtime";
|
|
4
|
+
import { useState, useEffect, createContext, useMemo, useContext } from "react";
|
|
5
|
+
import { useSecrets, SettingsView } from "@sanity/studio-secrets";
|
|
6
|
+
const namespace = "launchdarkly", pluginConfigKeys = [
|
|
7
|
+
{
|
|
8
|
+
key: "apiKey",
|
|
9
|
+
title: "Your secret API key"
|
|
10
|
+
}
|
|
11
|
+
], Secrets = (props) => {
|
|
12
|
+
const { secrets, loading } = useSecrets(namespace), { setSecret } = useLaunchDarklyContext(), [showSettings, setShowSettings] = useState(!1);
|
|
13
|
+
return useEffect(() => {
|
|
14
|
+
if (!loading)
|
|
15
|
+
return !secrets && !loading ? (setSecret(void 0), setShowSettings(!0)) : (setSecret(secrets.apiKey), setShowSettings(!1));
|
|
16
|
+
}, [secrets, loading, setSecret]), showSettings ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
17
|
+
/* @__PURE__ */ jsx(
|
|
18
|
+
SettingsView,
|
|
19
|
+
{
|
|
20
|
+
title: `${namespace} api key`,
|
|
21
|
+
namespace,
|
|
22
|
+
keys: pluginConfigKeys,
|
|
23
|
+
onClose: () => {
|
|
24
|
+
setShowSettings(!1);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
),
|
|
28
|
+
props.renderDefault(props)
|
|
29
|
+
] }) : props.renderDefault(props);
|
|
30
|
+
}, LAUNCHDARKLY_CONFIG_DEFAULT = {}, LaunchDarklyContext = createContext({
|
|
31
|
+
setSecret: () => {
|
|
32
|
+
},
|
|
33
|
+
secret: void 0
|
|
34
|
+
});
|
|
35
|
+
function useLaunchDarklyContext() {
|
|
36
|
+
return useContext(LaunchDarklyContext);
|
|
37
|
+
}
|
|
38
|
+
function LaunchDarklyProvider(props) {
|
|
39
|
+
const { launchDarklyFieldPluginConfig } = props, [secret, setSecret] = useState(), context = useMemo(
|
|
40
|
+
() => ({ ...launchDarklyFieldPluginConfig, secret, setSecret }),
|
|
41
|
+
[launchDarklyFieldPluginConfig, secret, setSecret]
|
|
42
|
+
);
|
|
43
|
+
return /* @__PURE__ */ jsx(LaunchDarklyContext.Provider, { value: context, children: /* @__PURE__ */ jsx(Secrets, { ...props }) });
|
|
44
|
+
}
|
|
45
|
+
const getExperiments = async ({
|
|
46
|
+
client,
|
|
47
|
+
projectKey,
|
|
48
|
+
tags
|
|
49
|
+
}) => {
|
|
50
|
+
const secret = await client.fetch("*[_id == 'secrets.launchdarkly'][0].secrets.apiKey");
|
|
51
|
+
if (!secret) return [];
|
|
52
|
+
const url = new URL(`https://app.launchdarkly.com/api/v2/flags/${projectKey}`);
|
|
53
|
+
tags && url.searchParams.set("filter", `tags:${tags.join("+")}`);
|
|
54
|
+
const featureExperiments = [];
|
|
55
|
+
let hasMore = !0;
|
|
56
|
+
const offset = 0, limit = 10;
|
|
57
|
+
for (; hasMore; ) {
|
|
58
|
+
url.searchParams.set("offset", offset.toString()), url.searchParams.set("limit", limit.toString());
|
|
59
|
+
const responseFlags = await fetch(url, {
|
|
60
|
+
headers: {
|
|
61
|
+
Authorization: secret
|
|
62
|
+
}
|
|
63
|
+
}), { items } = await responseFlags.json(), experiments = items.map((flag) => ({
|
|
64
|
+
id: flag.key,
|
|
65
|
+
label: flag.name,
|
|
66
|
+
variants: flag.variations.map((variation) => ({
|
|
67
|
+
id: variation.value.toString(),
|
|
68
|
+
label: variation.name ?? variation.value.toString()
|
|
69
|
+
}))
|
|
70
|
+
}));
|
|
71
|
+
featureExperiments.push(...experiments), items.length !== limit && (hasMore = !1);
|
|
72
|
+
}
|
|
73
|
+
return featureExperiments;
|
|
74
|
+
}, fieldLevelExperiments = definePlugin((config) => {
|
|
75
|
+
const pluginConfig = { ...LAUNCHDARKLY_CONFIG_DEFAULT, ...config }, { fields, projectKey, tags } = pluginConfig;
|
|
76
|
+
return {
|
|
77
|
+
name: "sanity-growthbook-personalistaion-plugin-field-level-experiments",
|
|
78
|
+
plugins: [
|
|
79
|
+
fieldLevelExperiments$1({
|
|
80
|
+
fields,
|
|
81
|
+
experiments: (client) => getExperiments({ client, projectKey, tags }),
|
|
82
|
+
experimentNameOverride: "flag"
|
|
83
|
+
})
|
|
84
|
+
],
|
|
85
|
+
form: {
|
|
86
|
+
components: {
|
|
87
|
+
input: (props) => {
|
|
88
|
+
if (!(props.id === "root" && isObjectInputProps(props)) || !flattenSchemaType(props.schemaType).map(
|
|
89
|
+
(field) => field.type.name
|
|
90
|
+
).some((name) => name.startsWith("flag")))
|
|
91
|
+
return props.renderDefault(props);
|
|
92
|
+
const providerProps = {
|
|
93
|
+
...props,
|
|
94
|
+
launchDarklyFieldPluginConfig: {
|
|
95
|
+
...pluginConfig
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
return LaunchDarklyProvider(providerProps);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
});
|
|
104
|
+
export {
|
|
105
|
+
fieldLevelExperiments
|
|
106
|
+
};
|
|
107
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","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.toString(),\n label: variation.name ?? variation.value.toString(),\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":["baseFieldLevelExperiments"],"mappings":";;;;;AAMA,MAAM,YAAY,gBACZ,mBAAmB;AAAA,EACvB;AAAA,IACE,KAAK;AAAA,IACL,OAAO;AAAA,EAAA;AAEX,GAEa,UAAU,CAAC,UAA4B;AAClD,QAAM,EAAC,SAAS,QAAA,IAAW,WAAW,SAAS,GACzC,EAAC,UAAA,IAAa,0BACd,CAAC,cAAc,eAAe,IAAI,SAAkB,EAAK;AAY/D,SAVA,UAAU,MAAM;AACd,QAAI,CAAA;AACJ,aAAI,CAAC,WAAW,CAAC,WACf,UAAU,MAAS,GACZ,gBAAgB,EAAI,MAE7B,UAAU,QAAQ,MAAM,GACjB,gBAAgB,EAAK;AAAA,EAC9B,GAAG,CAAC,SAAS,SAAS,SAAS,CAAC,GAE3B,eAIH,qBAAA,UAAA,EACE,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,OAAO,GAAG,SAAS;AAAA,QACnB;AAAA,QACA,MAAM;AAAA,QACN,SAAS,MAAM;AACb,0BAAgB,EAAK;AAAA,QACvB;AAAA,MAAA;AAAA,IAAA;AAAA,IAED,MAAM,cAAc,KAAK;AAAA,EAAA,EAAA,CAC5B,IAbO,MAAM,cAAc,KAAK;AAepC,GCvCa,8BAA8B,CAAA,GAE9B,sBAAsB,cAAwC;AAAA,EACzE,WAAW,MAAG;AAAA,EAAA;AAAA,EACd,QAAQ;AACV,CAAC;AAEM,SAAS,yBAAyB;AACvC,SAAO,WAAW,mBAAmB;AACvC;AAMO,SAAS,qBAAqB,OAA0B;AAC7D,QAAM,EAAC,kCAAiC,OAClC,CAAC,QAAQ,SAAS,IAAI,YAEtB,UAAU;AAAA,IACd,OAAO,EAAC,GAAG,+BAA+B,QAAQ,UAAA;AAAA,IAClD,CAAC,+BAA+B,QAAQ,SAAS;AAAA,EAAA;AAGnD,SACE,oBAAC,oBAAoB,UAApB,EAA6B,OAAO,SACnC,UAAA,oBAAC,SAAA,EAAS,GAAG,MAAA,CAAO,EAAA,CACtB;AAEJ;AC9BO,MAAM,iBAAiB,OAAO;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AACF,MAEK;AAGH,QAAM,SAAS,MAAM,OAAO,MAFd,oDAEyB;AACvC,MAAI,CAAC,OAAQ,QAAO,CAAA;AAEpB,QAAM,MAAM,IAAI,IAAI,6CAA6C,UAAU,EAAE;AAEzE,UACF,IAAI,aAAa,IAAI,UAAU,QAAQ,KAAK,KAAK,GAAG,CAAC,EAAE;AAGzD,QAAM,qBAAuC,CAAA;AAC7C,MAAI,UAAU;AACd,QAAM,SAAS,GACT,QAAQ;AAEd,SAAO,WAAS;AACd,QAAI,aAAa,IAAI,UAAU,OAAO,SAAA,CAAU,GAChD,IAAI,aAAa,IAAI,SAAS,MAAM,UAAU;AAC9C,UAAM,gBAAgB,MAAM,MAAM,KAAK;AAAA,MACrC,SAAS;AAAA,QACP,eAAe;AAAA,MAAA;AAAA,IACjB,CACD,GAEK,EAAC,UAAS,MAAM,cAAc,QAC9B,cAAc,MAAM,IAAI,CAAC,UAAgC;AAAA,MAC7D,IAAI,KAAK;AAAA,MACT,OAAO,KAAK;AAAA,MACZ,UAAU,KAAK,WAAW,IAAI,CAAC,eAAe;AAAA,QAC5C,IAAI,UAAU,MAAM,SAAA;AAAA,QACpB,OAAO,UAAU,QAAQ,UAAU,MAAM,SAAA;AAAA,MAAS,EAClD;AAAA,IAAA,EACF;AACF,uBAAmB,KAAK,GAAG,WAAW,GAClC,MAAM,WAAW,UACnB,UAAU;AAAA,EAEd;AAEA,SAAO;AACT,GC7Ca,wBAAwB,aAA2C,CAAC,WAAW;AAC1F,QAAM,eAAe,EAAC,GAAG,6BAA6B,GAAG,UACnD,EAAC,QAAQ,YAAY,KAAA,IAAQ;AACnC,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,MACPA,wBAA0B;AAAA,QACxB;AAAA,QACA,aAAa,CAAC,WAAW,eAAe,EAAC,QAAQ,YAAY,MAAK;AAAA,QAClE,wBAAwB;AAAA,MAAA,CACzB;AAAA,IAAA;AAAA,IAGH,MAAM;AAAA,MACJ,YAAY;AAAA,QACV,OAAO,CAAC,UAAU;AAahB,cAVI,EAFgB,MAAM,OAAO,UAAU,mBAAmB,KAAK,MAY/D,CANuB,kBAAkB,MAAM,UAAU,EAAE;AAAA,YAC7D,CAAC,UAAU,MAAM,KAAK;AAAA,UAAA,EAGiB,KAAK,CAAC,SAAS,KAAK,WAAW,MAAM,CAAC;AAG7E,mBAAO,MAAM,cAAc,KAAK;AAGlC,gBAAM,gBAAgB;AAAA,YACpB,GAAG;AAAA,YACH,+BAA+B;AAAA,cAC7B,GAAG;AAAA,YAAA;AAAA,UACL;AAEF,iBAAO,qBAAqB,aAAa;AAAA,QAC3C;AAAA,MAAA;AAAA,IACF;AAAA,EACF;AAEJ,CAAC;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/personalization-plugin",
|
|
3
|
-
"version": "2.5.0
|
|
3
|
+
"version": "2.5.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
|
+
"./launchDarkly": {
|
|
28
|
+
"source": "./src/launchDarkly/index.ts",
|
|
29
|
+
"import": "./dist/launchDarkly/index.mjs",
|
|
30
|
+
"default": "./dist/launchDarkly/index.js"
|
|
31
|
+
},
|
|
27
32
|
"./growthbook": {
|
|
28
33
|
"source": "./src/growthbook/index.ts",
|
|
29
34
|
"import": "./dist/growthbook/index.mjs",
|
|
@@ -76,16 +81,16 @@
|
|
|
76
81
|
"lint-staged": "^15.2.10",
|
|
77
82
|
"prettier": "^3.6.2",
|
|
78
83
|
"prettier-plugin-packagejson": "^2.5.19",
|
|
79
|
-
"react": "^
|
|
80
|
-
"react-dom": "^
|
|
81
|
-
"sanity": "^
|
|
84
|
+
"react": "^19.2.3",
|
|
85
|
+
"react-dom": "^19.2.3",
|
|
86
|
+
"sanity": "^5.0.1",
|
|
82
87
|
"semantic-release": "^24.2.7",
|
|
83
88
|
"styled-components": "^6.1.19",
|
|
84
89
|
"typescript": "^5.9.2"
|
|
85
90
|
},
|
|
86
91
|
"peerDependencies": {
|
|
87
92
|
"react": "^18 || ^19",
|
|
88
|
-
"sanity": "^3 || ^4.0.0-0"
|
|
93
|
+
"sanity": "^3 || ^4.0.0-0 || ^5.0.0"
|
|
89
94
|
},
|
|
90
95
|
"engines": {
|
|
91
96
|
"node": ">=18"
|
|
@@ -3,8 +3,8 @@ import {uuid} from '@sanity/uuid'
|
|
|
3
3
|
import {useCallback} from 'react'
|
|
4
4
|
import {useFormValue} from 'sanity'
|
|
5
5
|
|
|
6
|
-
import {ArrayInputProps, VariantType} from '
|
|
7
|
-
import {useExperimentContext} from './
|
|
6
|
+
import {ArrayInputProps, VariantType} from '../types'
|
|
7
|
+
import {useExperimentContext} from './ExperimentContext'
|
|
8
8
|
|
|
9
9
|
export const ArrayInput = (props: ArrayInputProps) => {
|
|
10
10
|
const fieldPath = props.path.slice(0, -1)
|
|
@@ -3,7 +3,7 @@ import {createContext, useContext, useMemo} from 'react'
|
|
|
3
3
|
import {type ObjectInputProps, useClient, useWorkspace} from 'sanity'
|
|
4
4
|
import {suspend} from 'suspend-react'
|
|
5
5
|
|
|
6
|
-
import {ExperimentContextProps,
|
|
6
|
+
import {ExperimentContextProps, FieldPluginConfig} from '../types'
|
|
7
7
|
|
|
8
8
|
// This provider makes the plugin config available to all components in the document form
|
|
9
9
|
// But with experiments resolved
|
|
@@ -28,7 +28,7 @@ export function useExperimentContext() {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
type ExperimentProps = ObjectInputProps & {
|
|
31
|
-
experimentFieldPluginConfig: Required<
|
|
31
|
+
experimentFieldPluginConfig: Required<FieldPluginConfig>
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
export function ExperimentProvider(props: ExperimentProps) {
|
|
@@ -11,8 +11,6 @@ import {
|
|
|
11
11
|
set,
|
|
12
12
|
unset,
|
|
13
13
|
} from 'sanity'
|
|
14
|
-
|
|
15
|
-
import {clearChildrenGroups} from '../../utils/clearChildGroups'
|
|
16
14
|
type PatchStuff = {onChange: (patch: FormPatch | FormPatch[] | PatchEvent) => void; inputId: string}
|
|
17
15
|
|
|
18
16
|
const useAddExperimentAction = (
|
|
@@ -101,7 +99,7 @@ const createActions = ({
|
|
|
101
99
|
return active ? removeAction : addAction
|
|
102
100
|
}
|
|
103
101
|
|
|
104
|
-
export const
|
|
102
|
+
export const ExperimentField = (
|
|
105
103
|
props: ObjectFieldProps & {
|
|
106
104
|
experimentNameOverride: string
|
|
107
105
|
experimentId: string
|
|
@@ -129,13 +127,12 @@ export const Field = (
|
|
|
129
127
|
return [createActions(actionProps), ...oldActions]
|
|
130
128
|
}, [actionProps, props.actions])
|
|
131
129
|
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
...propsWithClearedGroups,
|
|
130
|
+
const withActionProps = useMemo(
|
|
131
|
+
() => ({
|
|
132
|
+
...props,
|
|
136
133
|
actions: memoizedActions,
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return props.renderDefault(
|
|
134
|
+
}),
|
|
135
|
+
[props, memoizedActions],
|
|
136
|
+
)
|
|
137
|
+
return props.renderDefault(withActionProps)
|
|
141
138
|
}
|
|
@@ -11,9 +11,9 @@ import {
|
|
|
11
11
|
useFormValue,
|
|
12
12
|
} from 'sanity'
|
|
13
13
|
|
|
14
|
-
import {ExperimentType} from '
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
14
|
+
import {ExperimentType} from '..'
|
|
15
|
+
import {useExperimentContext} from './ExperimentContext'
|
|
16
|
+
import {Select} from './Select'
|
|
17
17
|
|
|
18
18
|
export type SelectOption = {title: string; value: string}
|
|
19
19
|
const formatlistOptions = (experiments: ExperimentType[]): SelectOption[] =>
|
|
@@ -22,7 +22,7 @@ const formatlistOptions = (experiments: ExperimentType[]): SelectOption[] =>
|
|
|
22
22
|
value: experiment.id,
|
|
23
23
|
}))
|
|
24
24
|
|
|
25
|
-
export const
|
|
25
|
+
export const ExperimentInput = (
|
|
26
26
|
props: StringInputProps & {variantNameOverride: string; experimentNameOverride: string},
|
|
27
27
|
) => {
|
|
28
28
|
const {experiments} = useExperimentContext()
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import {ObjectItem, ObjectItemProps, set} from 'sanity'
|
|
2
2
|
|
|
3
|
-
export const
|
|
3
|
+
export const ExperimentItem = (props: ObjectItemProps) => {
|
|
4
4
|
const {active} = props.value as ObjectItem & {active: boolean}
|
|
5
5
|
if (!active) {
|
|
6
6
|
props.inputProps.onChange(set(true, ['active']))
|
|
7
7
|
}
|
|
8
|
+
|
|
8
9
|
return props.renderDefault(props)
|
|
9
10
|
}
|
|
@@ -2,7 +2,7 @@ import {Select as SanitySelect} from '@sanity/ui'
|
|
|
2
2
|
import {FormEvent} from 'react'
|
|
3
3
|
import {FormPatch, PatchEvent, Path, StringInputProps} from 'sanity'
|
|
4
4
|
|
|
5
|
-
import {SelectOption} from './
|
|
5
|
+
import {SelectOption} from './ExperimentInput'
|
|
6
6
|
|
|
7
7
|
export const Select = (
|
|
8
8
|
props: StringInputProps & {
|
|
@@ -8,8 +8,8 @@ import {
|
|
|
8
8
|
useClient,
|
|
9
9
|
} from 'sanity'
|
|
10
10
|
|
|
11
|
-
import {VariantPreviewProps} from '
|
|
12
|
-
import {useExperimentContext} from './
|
|
11
|
+
import {VariantPreviewProps} from '../types'
|
|
12
|
+
import {useExperimentContext} from './ExperimentContext'
|
|
13
13
|
|
|
14
14
|
export const VariantPreview = (props: PreviewProps) => {
|
|
15
15
|
const [subtitle, setSubtitle] = useState<string | undefined>(undefined)
|
package/src/fieldExperiments.tsx
CHANGED
|
@@ -7,17 +7,14 @@ import {
|
|
|
7
7
|
isObjectInputProps,
|
|
8
8
|
} from 'sanity'
|
|
9
9
|
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
VariantPreview,
|
|
19
|
-
} from './components/experiment'
|
|
20
|
-
import {ExperimentFieldPluginConfig} from './types'
|
|
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'
|
|
21
18
|
import {flattenSchemaType} from './utils/flattenSchemaType'
|
|
22
19
|
|
|
23
20
|
const createExperimentType = ({
|
|
@@ -42,39 +39,16 @@ const createExperimentType = ({
|
|
|
42
39
|
return defineType({
|
|
43
40
|
name: `${experimentNameOverride}${usedName}`,
|
|
44
41
|
type: 'object',
|
|
45
|
-
groups: [
|
|
46
|
-
{
|
|
47
|
-
name: 'default',
|
|
48
|
-
title: 'Default',
|
|
49
|
-
hidden: ({parent}) => {
|
|
50
|
-
return !Array.isArray(parent)
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
name: 'experiments',
|
|
55
|
-
title: 'Experiments',
|
|
56
|
-
hidden: ({parent}) => {
|
|
57
|
-
return !Array.isArray(parent)
|
|
58
|
-
},
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
name: 'all-fields',
|
|
62
|
-
title: 'All fields',
|
|
63
|
-
hidden: ({parent}) => {
|
|
64
|
-
return Array.isArray(parent)
|
|
65
|
-
},
|
|
66
|
-
},
|
|
67
|
-
],
|
|
68
42
|
components: {
|
|
69
43
|
field: (props) => (
|
|
70
|
-
<
|
|
44
|
+
<ExperimentField
|
|
71
45
|
{...props}
|
|
72
46
|
experimentId={experimentId}
|
|
73
47
|
experimentNameOverride={experimentNameOverride}
|
|
74
48
|
variantNameOverride={variantNameOverride}
|
|
75
49
|
/>
|
|
76
50
|
),
|
|
77
|
-
item:
|
|
51
|
+
item: ExperimentItem,
|
|
78
52
|
},
|
|
79
53
|
fields: [
|
|
80
54
|
typeof field === `string`
|
|
@@ -82,13 +56,11 @@ const createExperimentType = ({
|
|
|
82
56
|
defineField({
|
|
83
57
|
name: 'default',
|
|
84
58
|
type: field,
|
|
85
|
-
group: 'default',
|
|
86
59
|
})
|
|
87
60
|
: // Pass in the configured options, but overwrite the name
|
|
88
61
|
{
|
|
89
62
|
...field,
|
|
90
63
|
name: 'default',
|
|
91
|
-
group: 'default',
|
|
92
64
|
},
|
|
93
65
|
defineField({
|
|
94
66
|
name: 'active',
|
|
@@ -99,10 +71,9 @@ const createExperimentType = ({
|
|
|
99
71
|
defineField({
|
|
100
72
|
name: experimentId,
|
|
101
73
|
type: 'string',
|
|
102
|
-
group: 'experiments',
|
|
103
74
|
components: {
|
|
104
75
|
input: (props) => (
|
|
105
|
-
<
|
|
76
|
+
<ExperimentInput
|
|
106
77
|
{...props}
|
|
107
78
|
experimentNameOverride={experimentNameOverride}
|
|
108
79
|
variantNameOverride={variantNameOverride}
|
|
@@ -116,7 +87,6 @@ const createExperimentType = ({
|
|
|
116
87
|
defineField({
|
|
117
88
|
name: variantArrayName,
|
|
118
89
|
type: 'array',
|
|
119
|
-
group: 'experiments',
|
|
120
90
|
hidden: ({parent}) => {
|
|
121
91
|
return !parent?.[experimentId]
|
|
122
92
|
},
|
|
@@ -220,7 +190,7 @@ const fieldSchema = ({
|
|
|
220
190
|
variantId,
|
|
221
191
|
variantArrayName,
|
|
222
192
|
experimentId,
|
|
223
|
-
}: Required<Omit<
|
|
193
|
+
}: Required<Omit<FieldPluginConfig, 'apiVersion' | 'experiments'>>) => {
|
|
224
194
|
return [
|
|
225
195
|
...fields.map((field) =>
|
|
226
196
|
createVariantType({field, variantNameOverride, variantId, experimentId}),
|
|
@@ -238,7 +208,7 @@ const fieldSchema = ({
|
|
|
238
208
|
]
|
|
239
209
|
}
|
|
240
210
|
|
|
241
|
-
export const fieldLevelExperiments = definePlugin<
|
|
211
|
+
export const fieldLevelExperiments = definePlugin<FieldPluginConfig>((config) => {
|
|
242
212
|
const pluginConfig = {...CONFIG_DEFAULT, ...config}
|
|
243
213
|
const {fields, experimentNameOverride, variantNameOverride} = pluginConfig
|
|
244
214
|
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,36 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
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
|
+
}
|