@sanity/personalization-plugin 2.4.1 → 2.5.0-launch-darkly.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/README.md +0 -2
- package/dist/index.js +6882 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +6882 -3
- 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 +7 -2
- 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/launchdarkly.md +76 -0
- package/src/launchDarkly/types.ts +193 -0
- package/src/launchDarkly/utils.ts +54 -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":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/personalization-plugin",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0-launch-darkly.1",
|
|
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",
|
|
@@ -50,7 +55,7 @@
|
|
|
50
55
|
},
|
|
51
56
|
"dependencies": {
|
|
52
57
|
"@sanity/incompatible-plugin": "^1.0.4",
|
|
53
|
-
"@sanity/studio-secrets": "^3.0.
|
|
58
|
+
"@sanity/studio-secrets": "^3.0.1",
|
|
54
59
|
"@sanity/ui": "^2.8.19",
|
|
55
60
|
"@sanity/uuid": "^3.0.2",
|
|
56
61
|
"fast-deep-equal": "^3.1.3",
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import {definePlugin, isObjectInputProps} from 'sanity'
|
|
2
|
+
|
|
3
|
+
import {fieldLevelExperiments as baseFieldLevelExperiments} from '../fieldExperiments'
|
|
4
|
+
import {flattenSchemaType} from '../utils/flattenSchemaType'
|
|
5
|
+
import {LAUNCHDARKLY_CONFIG_DEFAULT, LaunchDarklyProvider} from './components/LaunchDarklyContext'
|
|
6
|
+
import {LaunchDarklyFieldLevelConfig} from './types'
|
|
7
|
+
import {getExperiments} from './utils'
|
|
8
|
+
|
|
9
|
+
export const fieldLevelExperiments = definePlugin<LaunchDarklyFieldLevelConfig>((config) => {
|
|
10
|
+
const pluginConfig = {...LAUNCHDARKLY_CONFIG_DEFAULT, ...config}
|
|
11
|
+
const {fields, projectKey, tags} = pluginConfig
|
|
12
|
+
return {
|
|
13
|
+
name: 'sanity-growthbook-personalistaion-plugin-field-level-experiments',
|
|
14
|
+
plugins: [
|
|
15
|
+
baseFieldLevelExperiments({
|
|
16
|
+
fields,
|
|
17
|
+
experiments: (client) => getExperiments({client, projectKey, tags}),
|
|
18
|
+
experimentNameOverride: 'flag',
|
|
19
|
+
}),
|
|
20
|
+
],
|
|
21
|
+
|
|
22
|
+
form: {
|
|
23
|
+
components: {
|
|
24
|
+
input: (props) => {
|
|
25
|
+
const isRootInput = props.id === 'root' && isObjectInputProps(props)
|
|
26
|
+
|
|
27
|
+
if (!isRootInput) {
|
|
28
|
+
return props.renderDefault(props)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const flatFieldTypeNames = flattenSchemaType(props.schemaType).map(
|
|
32
|
+
(field) => field.type.name,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
const hasExperiment = flatFieldTypeNames.some((name) => name.startsWith('flag'))
|
|
36
|
+
|
|
37
|
+
if (!hasExperiment) {
|
|
38
|
+
return props.renderDefault(props)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const providerProps = {
|
|
42
|
+
...props,
|
|
43
|
+
launchDarklyFieldPluginConfig: {
|
|
44
|
+
...pluginConfig,
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
return LaunchDarklyProvider(providerProps)
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
})
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# @sanity/personalization-plugin - launchDarklyFieldLevel
|
|
2
|
+
|
|
3
|
+
## Previously know as @sanity/personalisation-plugin
|
|
4
|
+
|
|
5
|
+
> This is a **Sanity Studio v3** plugin.
|
|
6
|
+
|
|
7
|
+
This plugin allows users to add a/b/n testing experiments to individual fields connecting to the [LaunchDarkly](https://launchdarkly.com//) A/B testing service.
|
|
8
|
+
|
|
9
|
+
- [@sanity/personalization-plugin - launchDarklyFieldLevel](#sanitypersonalization-plugin---launchDarklyFieldLevel)
|
|
10
|
+
- [Installation](#installation)
|
|
11
|
+
- [Usage](#usage)
|
|
12
|
+
- [Loading Experiments](#loading-experiments)
|
|
13
|
+
|
|
14
|
+
This plugin is built on top of the `fieldLevelExperiments` export so see the main readme for details of:
|
|
15
|
+
|
|
16
|
+
- [Using complex field configurations](/#using-complex-field-configurations)
|
|
17
|
+
- [Validation of individual array items](/#validation-of-individual-array-items)
|
|
18
|
+
- [Shape of stored data](/#shape-of-stored-data)
|
|
19
|
+
- [Querying data](/#querying-data)
|
|
20
|
+
- [License](#license)
|
|
21
|
+
- [Develop \& test](#develop--test)
|
|
22
|
+
- [Release new version](#release-new-version)
|
|
23
|
+
- [License](#license-1)
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
npm install @sanity/personalization-plugin
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
Add it as a plugin in `sanity.config.ts` (or .js):
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import {defineConfig} from 'sanity'
|
|
37
|
+
import {fieldLevelExperiments} from '@sanity/personalization-plugin/launchDarkly'
|
|
38
|
+
|
|
39
|
+
export default defineConfig({
|
|
40
|
+
//...
|
|
41
|
+
plugins: [
|
|
42
|
+
//...
|
|
43
|
+
launchDarklyFieldLevel({
|
|
44
|
+
fields: ['string'],
|
|
45
|
+
projectKey: 'string', // required filter parameter for fetching features/variants
|
|
46
|
+
tags: ['string'] //optional parameter that filters the list to flags that have all of the tags in the list
|
|
47
|
+
}),
|
|
48
|
+
],
|
|
49
|
+
})
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
This will register two new fields to the schema., based on the setting passed into `fields:`
|
|
53
|
+
|
|
54
|
+
- `flagString` an Object field with `string` field called `default`, a `string` field called `flagId` and an array field of type:
|
|
55
|
+
- `variantsString` an object field with a `string` field called `value`, a string field called `variantId`, a `string` field called `flagId`.
|
|
56
|
+
|
|
57
|
+
Use the flag field in your schema like this:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
//for Example in post.ts
|
|
61
|
+
|
|
62
|
+
fields: [
|
|
63
|
+
defineField({
|
|
64
|
+
name: 'title',
|
|
65
|
+
type: 'flagString',
|
|
66
|
+
}),
|
|
67
|
+
]
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Loading Experiments
|
|
71
|
+
|
|
72
|
+
This plugin uses [@sanity/studio-secrets](https://www.npmjs.com/package/@sanity/studio-secrets) for storing your Launch Darkly API key. The first time you open a document that has an experiment you will be asked to provide your API key. This is stored in a private document on the dataset.
|
|
73
|
+
|
|
74
|
+
Once you have entered you API key the plugin will fetch feature flags and variants flags. If features/variants are updated on LaunchDarkly you will need to refresh the page.
|
|
75
|
+
|
|
76
|
+
The values stored for an flag will be the Feature Key amd the variants will stored the variation value.
|