@sanity/personalization-plugin 2.3.0-growthbook.1 → 2.3.0-launch-darkly.2

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.
@@ -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,
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
@@ -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,\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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"}
@@ -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,
68
+ label: variation.name ?? variation.value
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,\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":["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.0-growthbook.1",
3
+ "version": "2.3.0-launch-darkly.2",
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
- "./growthbook": {
28
- "source": "./src/growthbook/index.ts",
29
- "import": "./dist/growthbook/index.mjs",
30
- "default": "./dist/growthbook/index.js"
27
+ "./launchDarkly": {
28
+ "source": "./src/launchDarkly/index.ts",
29
+ "import": "./dist/launchDarkly/index.mjs",
30
+ "default": "./dist/launchDarkly/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.0",
53
+ "@sanity/studio-secrets": "^3.0.1",
54
54
  "@sanity/ui": "^2.8.19",
55
55
  "@sanity/uuid": "^3.0.2",
56
56
  "fast-deep-equal": "^3.1.3",
@@ -97,7 +97,10 @@ const createActions = ({
97
97
  experimentId,
98
98
  }),
99
99
  })
100
- return active ? removeAction : addAction
100
+ if (active) {
101
+ return removeAction
102
+ }
103
+ return addAction
101
104
  }
102
105
 
103
106
  export const ExperimentField = (
@@ -135,6 +138,5 @@ export const ExperimentField = (
135
138
  }),
136
139
  [props, memoizedActions],
137
140
  )
138
-
139
141
  return props.renderDefault(withActionProps)
140
142
  }
@@ -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
+ }
@@ -2,11 +2,10 @@ import {SettingsView, useSecrets} from '@sanity/studio-secrets'
2
2
  import {useEffect, useState} from 'react'
3
3
  import {ObjectInputProps} from 'sanity'
4
4
 
5
- import {useGrowthbookContext} from './GrowthbookContext'
5
+ import {useLaunchDarklyContext} from './LaunchDarklyContext'
6
6
 
7
- export const namespace = 'growthbook'
8
-
9
- export const pluginConfigKeys = [
7
+ const namespace = 'launchdarkly'
8
+ const pluginConfigKeys = [
10
9
  {
11
10
  key: 'apiKey',
12
11
  title: 'Your secret API key',
@@ -15,7 +14,7 @@ export const pluginConfigKeys = [
15
14
 
16
15
  export const Secrets = (props: ObjectInputProps) => {
17
16
  const {secrets, loading} = useSecrets(namespace) as {secrets: {apiKey: string}; loading: boolean}
18
- const {setSecret} = useGrowthbookContext()
17
+ const {setSecret} = useLaunchDarklyContext()
19
18
  const [showSettings, setShowSettings] = useState<boolean>(false)
20
19
 
21
20
  useEffect(() => {
@@ -34,7 +33,7 @@ export const Secrets = (props: ObjectInputProps) => {
34
33
  return (
35
34
  <>
36
35
  <SettingsView
37
- title={'Growthbook secret'}
36
+ title={`${namespace} api key`}
38
37
  namespace={namespace}
39
38
  keys={pluginConfigKeys}
40
39
  onClose={() => {
@@ -2,20 +2,20 @@ import {definePlugin, isObjectInputProps} from 'sanity'
2
2
 
3
3
  import {fieldLevelExperiments as baseFieldLevelExperiments} from '../fieldExperiments'
4
4
  import {flattenSchemaType} from '../utils/flattenSchemaType'
5
- import {GROWTHBOOK_CONFIG_DEFAULT, GrowthbookProvider} from './Components/GrowthbookContext'
6
- import {GrowthbookABConfig} from './types'
5
+ import {LAUNCHDARKLY_CONFIG_DEFAULT, LaunchDarklyProvider} from './components/LaunchDarklyContext'
6
+ import {LaunchDarklyFieldLevelConfig} from './types'
7
7
  import {getExperiments} from './utils'
8
8
 
9
- export const fieldLevelExperiments = definePlugin<GrowthbookABConfig>((config) => {
10
- const pluginConfig = {...GROWTHBOOK_CONFIG_DEFAULT, ...config}
11
- const {fields, environment, project, convertBooleans, baseUrl, tags} = pluginConfig
9
+ export const fieldLevelExperiments = definePlugin<LaunchDarklyFieldLevelConfig>((config) => {
10
+ const pluginConfig = {...LAUNCHDARKLY_CONFIG_DEFAULT, ...config}
11
+ const {fields, projectKey, tags} = pluginConfig
12
12
  return {
13
13
  name: 'sanity-growthbook-personalistaion-plugin-field-level-experiments',
14
14
  plugins: [
15
15
  baseFieldLevelExperiments({
16
16
  fields,
17
- experiments: (client) =>
18
- getExperiments({client, environment, baseUrl, project, convertBooleans, tags}),
17
+ experiments: (client) => getExperiments({client, projectKey, tags}),
18
+ experimentNameOverride: 'flag',
19
19
  }),
20
20
  ],
21
21
 
@@ -32,7 +32,7 @@ export const fieldLevelExperiments = definePlugin<GrowthbookABConfig>((config) =
32
32
  (field) => field.type.name,
33
33
  )
34
34
 
35
- const hasExperiment = flatFieldTypeNames.some((name) => name.startsWith('experiment'))
35
+ const hasExperiment = flatFieldTypeNames.some((name) => name.startsWith('flag'))
36
36
 
37
37
  if (!hasExperiment) {
38
38
  return props.renderDefault(props)
@@ -40,11 +40,11 @@ export const fieldLevelExperiments = definePlugin<GrowthbookABConfig>((config) =
40
40
 
41
41
  const providerProps = {
42
42
  ...props,
43
- growthbookFieldPluginConfig: {
43
+ launchDarklyFieldPluginConfig: {
44
44
  ...pluginConfig,
45
45
  },
46
46
  }
47
- return GrowthbookProvider(providerProps)
47
+ return LaunchDarklyProvider(providerProps)
48
48
  },
49
49
  },
50
50
  },
@@ -0,0 +1,193 @@
1
+ import {FieldDefinition} from 'sanity'
2
+
3
+ export type LaunchDarklyFieldLevelConfig = {
4
+ fields: (string | FieldDefinition)[]
5
+ projectKey: string
6
+ tags?: string[]
7
+ }
8
+ export type LaunchDarklyContextProps = {
9
+ setSecret: (secret: string | undefined) => void
10
+ secret: string | undefined
11
+ }
12
+
13
+ export type LaunchDarklyFlagItem = {
14
+ name: string
15
+ kind: string
16
+ key: string
17
+ _version: number
18
+ creationDate: number
19
+ variations: Array<{
20
+ value: boolean
21
+ _id: string
22
+ name: string
23
+ }>
24
+ temporary: boolean
25
+ tags: string[]
26
+ _links: {
27
+ parent: {
28
+ href: string
29
+ type: string
30
+ }
31
+ self: {
32
+ href: string
33
+ type: string
34
+ }
35
+ }
36
+ experiments: {
37
+ baselineIdx: number
38
+ items: Array<{
39
+ metricKey: string
40
+ _metric: {
41
+ _id: string
42
+ _versionId: string
43
+ key: string
44
+ name: string
45
+ kind: string
46
+ _links: {
47
+ parent: {
48
+ href: string
49
+ type: string
50
+ }
51
+ self: {
52
+ href: string
53
+ type: string
54
+ }
55
+ }
56
+ tags: string[]
57
+ _creationDate: number
58
+ experimentCount: number
59
+ metricGroupCount: number
60
+ _attachedFlagCount: number
61
+ maintainerId: string
62
+ _maintainer: {
63
+ _links: {
64
+ self: {
65
+ href: string
66
+ type: string
67
+ }
68
+ }
69
+ _id: string
70
+ role: string
71
+ email: string
72
+ firstName: string
73
+ lastName: string
74
+ }
75
+ category: string
76
+ isNumeric: boolean
77
+ percentileValue: number
78
+ }
79
+ }>
80
+ }
81
+ customProperties: {
82
+ key: {
83
+ name: string
84
+ value: string[]
85
+ }
86
+ }
87
+ archived: boolean
88
+ description: string
89
+ maintainerId: string
90
+ _maintainer: {
91
+ _links: {
92
+ self: {
93
+ href: string
94
+ type: string
95
+ }
96
+ }
97
+ _id: string
98
+ role: string
99
+ email: string
100
+ firstName: string
101
+ lastName: string
102
+ }
103
+ maintainerTeamKey: string
104
+ _maintainerTeam: {
105
+ key: string
106
+ name: string
107
+ _links: {
108
+ parent: {
109
+ href: string
110
+ type: string
111
+ }
112
+ roles: {
113
+ href: string
114
+ type: string
115
+ }
116
+ self: {
117
+ href: string
118
+ type: string
119
+ }
120
+ }
121
+ }
122
+ archivedDate: number
123
+ deprecated: boolean
124
+ deprecatedDate: number
125
+ defaults: {
126
+ onVariation: number
127
+ offVariation: number
128
+ }
129
+ _purpose: string
130
+ migrationSettings: {
131
+ contextKind: string
132
+ stageCount: number
133
+ }
134
+ environments: {
135
+ [key: string]: {
136
+ on: boolean
137
+ archived: boolean
138
+ salt: string
139
+ sel: string
140
+ lastModified: number
141
+ version: number
142
+ _site: {
143
+ href: string
144
+ type: string
145
+ }
146
+ _environmentName: string
147
+ trackEvents: boolean
148
+ trackEventsFallthrough: boolean
149
+ targets: Array<{
150
+ values: string[]
151
+ variation: number
152
+ contextKind: string
153
+ }>
154
+ contextTargets: Array<{
155
+ values: string[]
156
+ variation: number
157
+ contextKind: string
158
+ }>
159
+ rules: Array<{
160
+ clauses: Array<{
161
+ attribute: string
162
+ op: string
163
+ values: unknown[]
164
+ negate: boolean
165
+ }>
166
+ trackEvents: boolean
167
+ }>
168
+ fallthrough: {
169
+ variation: number
170
+ }
171
+ offVariation: number
172
+ prerequisites: Array<{
173
+ key: string
174
+ variation: number
175
+ }>
176
+ _summary: {
177
+ variations: {
178
+ [key: string]: {
179
+ rules: number
180
+ nullRules: number
181
+ targets: number
182
+ contextTargets: number
183
+ isFallthrough?: boolean
184
+ isOff?: boolean
185
+ }
186
+ }
187
+ prerequisites: number
188
+ }
189
+ }
190
+ }
191
+ includeInSnippet: boolean
192
+ goalIds: string[]
193
+ }