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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -25,6 +25,8 @@ Once configured you can query the values using the ids of the experiment and var
25
25
  - [Release new version](#release-new-version)
26
26
  - [License](#license-1)
27
27
 
28
+ For Specific information about the growthbookFieldLevel export see its [readme](/growthbook.md)
29
+
28
30
  ## Installation
29
31
 
30
32
  ```sh
@@ -0,0 +1,15 @@
1
+ import {FieldDefinition} from 'sanity'
2
+ import {Plugin as Plugin_2} from 'sanity'
3
+
4
+ export declare const fieldLevelExperiments: Plugin_2<GrowthbookExperimentFieldPluginConfig>
5
+
6
+ declare type GrowthbookExperimentFieldPluginConfig = {
7
+ fields: (string | FieldDefinition)[]
8
+ environment: string
9
+ baseUrl?: string
10
+ project?: string
11
+ convertBooleans?: boolean
12
+ tags?: string[]
13
+ }
14
+
15
+ export {}
@@ -0,0 +1,15 @@
1
+ import {FieldDefinition} from 'sanity'
2
+ import {Plugin as Plugin_2} from 'sanity'
3
+
4
+ export declare const fieldLevelExperiments: Plugin_2<GrowthbookExperimentFieldPluginConfig>
5
+
6
+ declare type GrowthbookExperimentFieldPluginConfig = {
7
+ fields: (string | FieldDefinition)[]
8
+ environment: string
9
+ baseUrl?: string
10
+ project?: string
11
+ convertBooleans?: boolean
12
+ tags?: string[]
13
+ }
14
+
15
+ export {}
@@ -0,0 +1,120 @@
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 = "growthbook", pluginConfigKeys = [
5
+ {
6
+ key: "apiKey",
7
+ title: "Your secret API key"
8
+ }
9
+ ], Secrets = (props) => {
10
+ const { secrets, loading } = studioSecrets.useSecrets(namespace), { setSecret } = useGrowthbookContext(), [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: "Growthbook secret",
19
+ namespace,
20
+ keys: pluginConfigKeys,
21
+ onClose: () => {
22
+ setShowSettings(!1);
23
+ }
24
+ }
25
+ ),
26
+ props.renderDefault(props)
27
+ ] }) : props.renderDefault(props);
28
+ }, GROWTHBOOK_CONFIG_DEFAULT = {
29
+ baseUrl: "https://api.growthbook.io/api/v1"
30
+ }, GrowthbookContext = react.createContext({
31
+ setSecret: () => {
32
+ },
33
+ secret: void 0
34
+ });
35
+ function useGrowthbookContext() {
36
+ return react.useContext(GrowthbookContext);
37
+ }
38
+ function GrowthbookProvider(props) {
39
+ const { growthbookFieldPluginConfig } = props, [secret, setSecret] = react.useState(), context = react.useMemo(
40
+ () => ({ ...growthbookFieldPluginConfig, secret, setSecret }),
41
+ [growthbookFieldPluginConfig, secret, setSecret]
42
+ );
43
+ return /* @__PURE__ */ jsxRuntime.jsx(GrowthbookContext.Provider, { value: context, children: /* @__PURE__ */ jsxRuntime.jsx(Secrets, { ...props }) });
44
+ }
45
+ const getBooleanConversion = (value) => value === "true" ? "variant" : value === "false" ? "control" : value, getExperiments = async ({
46
+ client,
47
+ environment,
48
+ baseUrl,
49
+ project,
50
+ convertBooleans,
51
+ tags
52
+ }) => {
53
+ const query = `*[_id == 'secrets.${namespace}'][0].secrets.${pluginConfigKeys[0].key}`, secret = await client.fetch(query);
54
+ if (!secret) return [];
55
+ const featureExperiments = [];
56
+ let hasMore = !0, offset = 0;
57
+ const url = new URL(`${baseUrl}/features`);
58
+ for (project && url.searchParams.set("projectId", project); hasMore; ) {
59
+ url.searchParams.set("offset", offset.toString());
60
+ const response = await fetch(url, {
61
+ headers: {
62
+ Authorization: `Bearer ${secret}`
63
+ }
64
+ }), { features, hasMore: responseHasMore, nextOffset } = await response.json();
65
+ hasMore = responseHasMore, offset = nextOffset, features && features.forEach((feature) => {
66
+ if (feature.archived || tags && feature.tags && !feature.tags.some((tag) => tags.includes(tag)))
67
+ return;
68
+ const experiments = feature.environments[environment]?.rules.filter(
69
+ (experiment) => experiment.type === "experiment-ref" || experiment.type === "experiment"
70
+ );
71
+ if (!experiments)
72
+ return;
73
+ const variations = [], uniqueValues = /* @__PURE__ */ new Set();
74
+ experiments.forEach((experiment) => {
75
+ experiment?.variations.forEach((variant) => {
76
+ const value2 = convertBooleans ? getBooleanConversion(variant.value) : variant.value;
77
+ uniqueValues.has(value2) || (uniqueValues.add(value2), variations.push({
78
+ id: value2,
79
+ label: value2
80
+ }));
81
+ });
82
+ });
83
+ const value = { id: feature.id, label: feature.id, variants: variations };
84
+ featureExperiments.push(value);
85
+ });
86
+ }
87
+ return featureExperiments.sort((a, b) => a.id.localeCompare(b.id));
88
+ }, fieldLevelExperiments = sanity.definePlugin(
89
+ (config) => {
90
+ const pluginConfig = { ...GROWTHBOOK_CONFIG_DEFAULT, ...config }, { fields, environment, project, convertBooleans, baseUrl, tags } = pluginConfig;
91
+ return {
92
+ name: "sanity-growthbook-personalistaion-plugin-field-level-experiments",
93
+ plugins: [
94
+ index.fieldLevelExperiments({
95
+ fields,
96
+ experiments: (client) => getExperiments({ client, environment, baseUrl, project, convertBooleans, tags })
97
+ })
98
+ ],
99
+ form: {
100
+ components: {
101
+ input: (props) => {
102
+ if (!(props.id === "root" && sanity.isObjectInputProps(props)) || !index.flattenSchemaType(props.schemaType).map(
103
+ (field) => field.type.name
104
+ ).some((name) => name.startsWith("experiment")))
105
+ return props.renderDefault(props);
106
+ const providerProps = {
107
+ ...props,
108
+ growthbookFieldPluginConfig: {
109
+ ...pluginConfig
110
+ }
111
+ };
112
+ return GrowthbookProvider(providerProps);
113
+ }
114
+ }
115
+ }
116
+ };
117
+ }
118
+ );
119
+ exports.fieldLevelExperiments = fieldLevelExperiments;
120
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../../src/growthbook/Components/Secrets.tsx","../../src/growthbook/Components/GrowthbookContext.tsx","../../src/growthbook/utils.ts","../../src/growthbook/index.ts"],"sourcesContent":["import {SettingsView, useSecrets} from '@sanity/studio-secrets'\nimport {useEffect, useState} from 'react'\nimport {ObjectInputProps} from 'sanity'\n\nimport {useGrowthbookContext} from './GrowthbookContext'\n\nexport const namespace = 'growthbook'\n\nexport const 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} = useGrowthbookContext()\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={'Growthbook secret'}\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 {GrowthbookContextProps, GrowthbookExperimentFieldPluginConfig} from '../types'\nimport {Secrets} from './Secrets'\n\nexport const GROWTHBOOK_CONFIG_DEFAULT = {\n baseUrl: 'https://api.growthbook.io/api/v1',\n}\n\nexport const GrowthbookContext = createContext<GrowthbookContextProps>({\n setSecret: () => undefined,\n secret: undefined,\n})\n\nexport function useGrowthbookContext() {\n return useContext(GrowthbookContext)\n}\n\ntype GrowthbookProps = ObjectInputProps & {\n growthbookFieldPluginConfig: GrowthbookExperimentFieldPluginConfig\n}\n\nexport function GrowthbookProvider(props: GrowthbookProps) {\n const {growthbookFieldPluginConfig} = props\n const [secret, setSecret] = useState<string | undefined>()\n\n const context = useMemo(\n () => ({...growthbookFieldPluginConfig, secret, setSecret}),\n [growthbookFieldPluginConfig, secret, setSecret],\n )\n\n return (\n <GrowthbookContext.Provider value={context}>\n <Secrets {...props} />\n </GrowthbookContext.Provider>\n )\n}\n","import {SanityClient} from 'sanity'\n\nimport {ExperimentType, GrowthbookFeature, VariantType} from '../types'\nimport {namespace, pluginConfigKeys} from './Components/Secrets'\nimport {GrowthbookExperimentFieldPluginConfig} from './types'\n\nconst getBooleanConversion = (value: string) => {\n // control is false\n if (value === 'true') {\n return 'variant'\n } else if (value === 'false') {\n return 'control'\n }\n return value\n}\n\nexport const getExperiments = async ({\n client,\n environment,\n baseUrl,\n project,\n convertBooleans,\n tags,\n}: Omit<GrowthbookExperimentFieldPluginConfig, 'fields' | 'baseUrl'> & {\n client: SanityClient\n baseUrl: string\n}): Promise<ExperimentType[]> => {\n const query = `*[_id == 'secrets.${namespace}'][0].secrets.${pluginConfigKeys[0].key}`\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 featureExperiments: ExperimentType[] = []\n let hasMore = true\n let offset = 0\n const url = new URL(`${baseUrl}/features`)\n if (project) {\n url.searchParams.set('projectId', project)\n }\n\n while (hasMore) {\n url.searchParams.set('offset', offset.toString())\n const response = await fetch(url, {\n headers: {\n Authorization: `Bearer ${secret}`,\n },\n })\n\n const {features, hasMore: responseHasMore, nextOffset} = await response.json()\n\n hasMore = responseHasMore\n offset = nextOffset\n if (!features) continue\n\n features.forEach((feature: GrowthbookFeature) => {\n if (feature.archived) {\n return undefined\n }\n if (tags && feature.tags && !feature.tags.some((tag) => tags.includes(tag))) {\n return undefined\n }\n\n const experiments = feature.environments[environment]?.rules.filter(\n (experiment) => experiment.type === 'experiment-ref' || experiment.type === 'experiment',\n )\n\n if (!experiments) {\n return undefined\n }\n\n const variations: VariantType[] = []\n const uniqueValues = new Set<string>()\n\n experiments.forEach((experiment) => {\n experiment?.variations.forEach((variant) => {\n const value = convertBooleans ? getBooleanConversion(variant.value) : variant.value\n if (!uniqueValues.has(value)) {\n uniqueValues.add(value)\n variations.push({\n id: value,\n label: value,\n })\n }\n })\n })\n const value = {id: feature.id, label: feature.id, variants: variations}\n\n featureExperiments.push(value)\n return undefined\n })\n }\n const sortedFeatureExperiments = featureExperiments.sort((a, b) => a.id.localeCompare(b.id))\n return sortedFeatureExperiments\n}\n","import {definePlugin, isObjectInputProps} from 'sanity'\n\nimport {fieldLevelExperiments as baseFieldLevelExperiments} from '../fieldExperiments'\nimport {flattenSchemaType} from '../utils/flattenSchemaType'\nimport {GROWTHBOOK_CONFIG_DEFAULT, GrowthbookProvider} from './Components/GrowthbookContext'\nimport {GrowthbookExperimentFieldPluginConfig} from './types'\nimport {getExperiments} from './utils'\n\nexport const fieldLevelExperiments = definePlugin<GrowthbookExperimentFieldPluginConfig>(\n (config) => {\n const pluginConfig = {...GROWTHBOOK_CONFIG_DEFAULT, ...config}\n const {fields, environment, project, convertBooleans, baseUrl, tags} = pluginConfig\n return {\n name: 'sanity-growthbook-personalistaion-plugin-field-level-experiments',\n plugins: [\n baseFieldLevelExperiments({\n fields,\n experiments: (client) =>\n getExperiments({client, environment, baseUrl, project, convertBooleans, tags}),\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('experiment'))\n\n if (!hasExperiment) {\n return props.renderDefault(props)\n }\n\n const providerProps = {\n ...props,\n growthbookFieldPluginConfig: {\n ...pluginConfig,\n },\n }\n return GrowthbookProvider(providerProps)\n },\n },\n },\n }\n },\n)\n"],"names":["useSecrets","useState","useEffect","jsxs","Fragment","jsx","SettingsView","createContext","useContext","useMemo","value","definePlugin","baseFieldLevelExperiments","isObjectInputProps","flattenSchemaType"],"mappings":";;;AAMa,MAAA,YAAY,cAEZ,mBAAmB;AAAA,EAC9B;AAAA,IACE,KAAK;AAAA,IACL,OAAO;AAAA,EAAA;AAEX,GAEa,UAAU,CAAC,UAA4B;AAClD,QAAM,EAAC,SAAS,QAAA,IAAWA,cAAAA,WAAW,SAAS,GACzC,EAAC,UAAS,IAAI,wBACd,CAAC,cAAc,eAAe,IAAIC,MAAAA,SAAkB,EAAK;AAY/D,SAVAC,gBAAU,MAAM;AACV,QAAA,CAAA;AACJ,aAAI,CAAC,WAAW,CAAC,WACf,UAAU,MAAS,GACZ,gBAAgB,EAAI,MAE7B,UAAU,QAAQ,MAAM,GACjB,gBAAgB,EAAK;AAAA,EAAA,GAC3B,CAAC,SAAS,SAAS,SAAS,CAAC,GAE3B,eAKDC,2BAAA,KAAAC,qBAAA,EAAA,UAAA;AAAA,IAAAC,2BAAA;AAAA,MAACC,cAAA;AAAA,MAAA;AAAA,QACC,OAAO;AAAA,QACP;AAAA,QACA,MAAM;AAAA,QACN,SAAS,MAAM;AACb,0BAAgB,EAAK;AAAA,QAAA;AAAA,MACvB;AAAA,IACF;AAAA,IACC,MAAM,cAAc,KAAK;AAAA,EAC5B,EAAA,CAAA,IAbO,MAAM,cAAc,KAAK;AAepC,GCxCa,4BAA4B;AAAA,EACvC,SAAS;AACX,GAEa,oBAAoBC,MAAAA,cAAsC;AAAA,EACrE,WAAW,MAAG;AAAA,EAAA;AAAA,EACd,QAAQ;AACV,CAAC;AAEM,SAAS,uBAAuB;AACrC,SAAOC,MAAAA,WAAW,iBAAiB;AACrC;AAMO,SAAS,mBAAmB,OAAwB;AACnD,QAAA,EAAC,gCAA+B,OAChC,CAAC,QAAQ,SAAS,IAAIP,MAAAA,YAEtB,UAAUQ,MAAA;AAAA,IACd,OAAO,EAAC,GAAG,6BAA6B,QAAQ,UAAS;AAAA,IACzD,CAAC,6BAA6B,QAAQ,SAAS;AAAA,EACjD;AAGE,SAAAJ,2BAAA,IAAC,kBAAkB,UAAlB,EAA2B,OAAO,SACjC,UAACA,2BAAAA,IAAA,SAAA,EAAS,GAAG,MAAA,CAAO,EACtB,CAAA;AAEJ;AC/BA,MAAM,uBAAuB,CAAC,UAExB,UAAU,SACL,YACE,UAAU,UACZ,YAEF,OAGI,iBAAiB,OAAO;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAGiC;AAC/B,QAAM,QAAQ,qBAAqB,SAAS,iBAAiB,iBAAiB,CAAC,EAAE,GAAG,IAE9E,SAAS,MAAM,OAAO,MAAM,KAAK;AACnC,MAAA,CAAC,OAAQ,QAAO,CAAC;AAErB,QAAM,qBAAuC,CAAC;AAC1C,MAAA,UAAU,IACV,SAAS;AACb,QAAM,MAAM,IAAI,IAAI,GAAG,OAAO,WAAW;AAKzC,OAJI,WACF,IAAI,aAAa,IAAI,aAAa,OAAO,GAGpC,WAAS;AACd,QAAI,aAAa,IAAI,UAAU,OAAO,UAAU;AAC1C,UAAA,WAAW,MAAM,MAAM,KAAK;AAAA,MAChC,SAAS;AAAA,QACP,eAAe,UAAU,MAAM;AAAA,MAAA;AAAA,IACjC,CACD,GAEK,EAAC,UAAU,SAAS,iBAAiB,eAAc,MAAM,SAAS,KAAK;AAE7E,cAAU,iBACV,SAAS,YACJ,YAEL,SAAS,QAAQ,CAAC,YAA+B;AAI/C,UAHI,QAAQ,YAGR,QAAQ,QAAQ,QAAQ,CAAC,QAAQ,KAAK,KAAK,CAAC,QAAQ,KAAK,SAAS,GAAG,CAAC;AACxE;AAGF,YAAM,cAAc,QAAQ,aAAa,WAAW,GAAG,MAAM;AAAA,QAC3D,CAAC,eAAe,WAAW,SAAS,oBAAoB,WAAW,SAAS;AAAA,MAC9E;AAEA,UAAI,CAAC;AACH;AAGF,YAAM,aAA4B,CAAA,GAC5B,mCAAmB,IAAY;AAEzB,kBAAA,QAAQ,CAAC,eAAe;AACtB,oBAAA,WAAW,QAAQ,CAAC,YAAY;AAC1C,gBAAMK,SAAQ,kBAAkB,qBAAqB,QAAQ,KAAK,IAAI,QAAQ;AACzE,uBAAa,IAAIA,MAAK,MACzB,aAAa,IAAIA,MAAK,GACtB,WAAW,KAAK;AAAA,YACd,IAAIA;AAAAA,YACJ,OAAOA;AAAAA,UAAA,CACR;AAAA,QAAA,CAEJ;AAAA,MAAA,CACF;AACK,YAAA,QAAQ,EAAC,IAAI,QAAQ,IAAI,OAAO,QAAQ,IAAI,UAAU,WAAU;AAEtE,yBAAmB,KAAK,KAAK;AAAA,IAAA,CAE9B;AAAA,EAAA;AAE8B,SAAA,mBAAmB,KAAK,CAAC,GAAG,MAAM,EAAE,GAAG,cAAc,EAAE,EAAE,CAAC;AAE7F,GCrFa,wBAAwBC,OAAA;AAAA,EACnC,CAAC,WAAW;AACV,UAAM,eAAe,EAAC,GAAG,2BAA2B,GAAG,OAAM,GACvD,EAAC,QAAQ,aAAa,SAAS,iBAAiB,SAAS,KAAQ,IAAA;AAChE,WAAA;AAAA,MACL,MAAM;AAAA,MACN,SAAS;AAAA,QACPC,4BAA0B;AAAA,UACxB;AAAA,UACA,aAAa,CAAC,WACZ,eAAe,EAAC,QAAQ,aAAa,SAAS,SAAS,iBAAiB,KAAK,CAAA;AAAA,QAChF,CAAA;AAAA,MACH;AAAA,MAEA,MAAM;AAAA,QACJ,YAAY;AAAA,UACV,OAAO,CAAC,UAAU;AAGZ,gBAAA,EAFgB,MAAM,OAAO,UAAUC,OAAAA,mBAAmB,KAAK,MAY/D,CANuBC,MAAA,kBAAkB,MAAM,UAAU,EAAE;AAAA,cAC7D,CAAC,UAAU,MAAM,KAAK;AAAA,YAAA,EAGiB,KAAK,CAAC,SAAS,KAAK,WAAW,YAAY,CAAC;AAG5E,qBAAA,MAAM,cAAc,KAAK;AAGlC,kBAAM,gBAAgB;AAAA,cACpB,GAAG;AAAA,cACH,6BAA6B;AAAA,gBAC3B,GAAG;AAAA,cAAA;AAAA,YAEP;AACA,mBAAO,mBAAmB,aAAa;AAAA,UAAA;AAAA,QACzC;AAAA,MACF;AAAA,IAEJ;AAAA,EAAA;AAEJ;;"}
@@ -0,0 +1,124 @@
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 = "growthbook", pluginConfigKeys = [
7
+ {
8
+ key: "apiKey",
9
+ title: "Your secret API key"
10
+ }
11
+ ], Secrets = (props) => {
12
+ const { secrets, loading } = useSecrets(namespace), { setSecret } = useGrowthbookContext(), [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: "Growthbook secret",
21
+ namespace,
22
+ keys: pluginConfigKeys,
23
+ onClose: () => {
24
+ setShowSettings(!1);
25
+ }
26
+ }
27
+ ),
28
+ props.renderDefault(props)
29
+ ] }) : props.renderDefault(props);
30
+ }, GROWTHBOOK_CONFIG_DEFAULT = {
31
+ baseUrl: "https://api.growthbook.io/api/v1"
32
+ }, GrowthbookContext = createContext({
33
+ setSecret: () => {
34
+ },
35
+ secret: void 0
36
+ });
37
+ function useGrowthbookContext() {
38
+ return useContext(GrowthbookContext);
39
+ }
40
+ function GrowthbookProvider(props) {
41
+ const { growthbookFieldPluginConfig } = props, [secret, setSecret] = useState(), context = useMemo(
42
+ () => ({ ...growthbookFieldPluginConfig, secret, setSecret }),
43
+ [growthbookFieldPluginConfig, secret, setSecret]
44
+ );
45
+ return /* @__PURE__ */ jsx(GrowthbookContext.Provider, { value: context, children: /* @__PURE__ */ jsx(Secrets, { ...props }) });
46
+ }
47
+ const getBooleanConversion = (value) => value === "true" ? "variant" : value === "false" ? "control" : value, getExperiments = async ({
48
+ client,
49
+ environment,
50
+ baseUrl,
51
+ project,
52
+ convertBooleans,
53
+ tags
54
+ }) => {
55
+ const query = `*[_id == 'secrets.${namespace}'][0].secrets.${pluginConfigKeys[0].key}`, secret = await client.fetch(query);
56
+ if (!secret) return [];
57
+ const featureExperiments = [];
58
+ let hasMore = !0, offset = 0;
59
+ const url = new URL(`${baseUrl}/features`);
60
+ for (project && url.searchParams.set("projectId", project); hasMore; ) {
61
+ url.searchParams.set("offset", offset.toString());
62
+ const response = await fetch(url, {
63
+ headers: {
64
+ Authorization: `Bearer ${secret}`
65
+ }
66
+ }), { features, hasMore: responseHasMore, nextOffset } = await response.json();
67
+ hasMore = responseHasMore, offset = nextOffset, features && features.forEach((feature) => {
68
+ if (feature.archived || tags && feature.tags && !feature.tags.some((tag) => tags.includes(tag)))
69
+ return;
70
+ const experiments = feature.environments[environment]?.rules.filter(
71
+ (experiment) => experiment.type === "experiment-ref" || experiment.type === "experiment"
72
+ );
73
+ if (!experiments)
74
+ return;
75
+ const variations = [], uniqueValues = /* @__PURE__ */ new Set();
76
+ experiments.forEach((experiment) => {
77
+ experiment?.variations.forEach((variant) => {
78
+ const value2 = convertBooleans ? getBooleanConversion(variant.value) : variant.value;
79
+ uniqueValues.has(value2) || (uniqueValues.add(value2), variations.push({
80
+ id: value2,
81
+ label: value2
82
+ }));
83
+ });
84
+ });
85
+ const value = { id: feature.id, label: feature.id, variants: variations };
86
+ featureExperiments.push(value);
87
+ });
88
+ }
89
+ return featureExperiments.sort((a, b) => a.id.localeCompare(b.id));
90
+ }, fieldLevelExperiments = definePlugin(
91
+ (config) => {
92
+ const pluginConfig = { ...GROWTHBOOK_CONFIG_DEFAULT, ...config }, { fields, environment, project, convertBooleans, baseUrl, tags } = pluginConfig;
93
+ return {
94
+ name: "sanity-growthbook-personalistaion-plugin-field-level-experiments",
95
+ plugins: [
96
+ fieldLevelExperiments$1({
97
+ fields,
98
+ experiments: (client) => getExperiments({ client, environment, baseUrl, project, convertBooleans, tags })
99
+ })
100
+ ],
101
+ form: {
102
+ components: {
103
+ input: (props) => {
104
+ if (!(props.id === "root" && isObjectInputProps(props)) || !flattenSchemaType(props.schemaType).map(
105
+ (field) => field.type.name
106
+ ).some((name) => name.startsWith("experiment")))
107
+ return props.renderDefault(props);
108
+ const providerProps = {
109
+ ...props,
110
+ growthbookFieldPluginConfig: {
111
+ ...pluginConfig
112
+ }
113
+ };
114
+ return GrowthbookProvider(providerProps);
115
+ }
116
+ }
117
+ }
118
+ };
119
+ }
120
+ );
121
+ export {
122
+ fieldLevelExperiments
123
+ };
124
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","sources":["../../src/growthbook/Components/Secrets.tsx","../../src/growthbook/Components/GrowthbookContext.tsx","../../src/growthbook/utils.ts","../../src/growthbook/index.ts"],"sourcesContent":["import {SettingsView, useSecrets} from '@sanity/studio-secrets'\nimport {useEffect, useState} from 'react'\nimport {ObjectInputProps} from 'sanity'\n\nimport {useGrowthbookContext} from './GrowthbookContext'\n\nexport const namespace = 'growthbook'\n\nexport const 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} = useGrowthbookContext()\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={'Growthbook secret'}\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 {GrowthbookContextProps, GrowthbookExperimentFieldPluginConfig} from '../types'\nimport {Secrets} from './Secrets'\n\nexport const GROWTHBOOK_CONFIG_DEFAULT = {\n baseUrl: 'https://api.growthbook.io/api/v1',\n}\n\nexport const GrowthbookContext = createContext<GrowthbookContextProps>({\n setSecret: () => undefined,\n secret: undefined,\n})\n\nexport function useGrowthbookContext() {\n return useContext(GrowthbookContext)\n}\n\ntype GrowthbookProps = ObjectInputProps & {\n growthbookFieldPluginConfig: GrowthbookExperimentFieldPluginConfig\n}\n\nexport function GrowthbookProvider(props: GrowthbookProps) {\n const {growthbookFieldPluginConfig} = props\n const [secret, setSecret] = useState<string | undefined>()\n\n const context = useMemo(\n () => ({...growthbookFieldPluginConfig, secret, setSecret}),\n [growthbookFieldPluginConfig, secret, setSecret],\n )\n\n return (\n <GrowthbookContext.Provider value={context}>\n <Secrets {...props} />\n </GrowthbookContext.Provider>\n )\n}\n","import {SanityClient} from 'sanity'\n\nimport {ExperimentType, GrowthbookFeature, VariantType} from '../types'\nimport {namespace, pluginConfigKeys} from './Components/Secrets'\nimport {GrowthbookExperimentFieldPluginConfig} from './types'\n\nconst getBooleanConversion = (value: string) => {\n // control is false\n if (value === 'true') {\n return 'variant'\n } else if (value === 'false') {\n return 'control'\n }\n return value\n}\n\nexport const getExperiments = async ({\n client,\n environment,\n baseUrl,\n project,\n convertBooleans,\n tags,\n}: Omit<GrowthbookExperimentFieldPluginConfig, 'fields' | 'baseUrl'> & {\n client: SanityClient\n baseUrl: string\n}): Promise<ExperimentType[]> => {\n const query = `*[_id == 'secrets.${namespace}'][0].secrets.${pluginConfigKeys[0].key}`\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 featureExperiments: ExperimentType[] = []\n let hasMore = true\n let offset = 0\n const url = new URL(`${baseUrl}/features`)\n if (project) {\n url.searchParams.set('projectId', project)\n }\n\n while (hasMore) {\n url.searchParams.set('offset', offset.toString())\n const response = await fetch(url, {\n headers: {\n Authorization: `Bearer ${secret}`,\n },\n })\n\n const {features, hasMore: responseHasMore, nextOffset} = await response.json()\n\n hasMore = responseHasMore\n offset = nextOffset\n if (!features) continue\n\n features.forEach((feature: GrowthbookFeature) => {\n if (feature.archived) {\n return undefined\n }\n if (tags && feature.tags && !feature.tags.some((tag) => tags.includes(tag))) {\n return undefined\n }\n\n const experiments = feature.environments[environment]?.rules.filter(\n (experiment) => experiment.type === 'experiment-ref' || experiment.type === 'experiment',\n )\n\n if (!experiments) {\n return undefined\n }\n\n const variations: VariantType[] = []\n const uniqueValues = new Set<string>()\n\n experiments.forEach((experiment) => {\n experiment?.variations.forEach((variant) => {\n const value = convertBooleans ? getBooleanConversion(variant.value) : variant.value\n if (!uniqueValues.has(value)) {\n uniqueValues.add(value)\n variations.push({\n id: value,\n label: value,\n })\n }\n })\n })\n const value = {id: feature.id, label: feature.id, variants: variations}\n\n featureExperiments.push(value)\n return undefined\n })\n }\n const sortedFeatureExperiments = featureExperiments.sort((a, b) => a.id.localeCompare(b.id))\n return sortedFeatureExperiments\n}\n","import {definePlugin, isObjectInputProps} from 'sanity'\n\nimport {fieldLevelExperiments as baseFieldLevelExperiments} from '../fieldExperiments'\nimport {flattenSchemaType} from '../utils/flattenSchemaType'\nimport {GROWTHBOOK_CONFIG_DEFAULT, GrowthbookProvider} from './Components/GrowthbookContext'\nimport {GrowthbookExperimentFieldPluginConfig} from './types'\nimport {getExperiments} from './utils'\n\nexport const fieldLevelExperiments = definePlugin<GrowthbookExperimentFieldPluginConfig>(\n (config) => {\n const pluginConfig = {...GROWTHBOOK_CONFIG_DEFAULT, ...config}\n const {fields, environment, project, convertBooleans, baseUrl, tags} = pluginConfig\n return {\n name: 'sanity-growthbook-personalistaion-plugin-field-level-experiments',\n plugins: [\n baseFieldLevelExperiments({\n fields,\n experiments: (client) =>\n getExperiments({client, environment, baseUrl, project, convertBooleans, tags}),\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('experiment'))\n\n if (!hasExperiment) {\n return props.renderDefault(props)\n }\n\n const providerProps = {\n ...props,\n growthbookFieldPluginConfig: {\n ...pluginConfig,\n },\n }\n return GrowthbookProvider(providerProps)\n },\n },\n },\n }\n },\n)\n"],"names":["value","baseFieldLevelExperiments"],"mappings":"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"}
package/dist/index.d.mts CHANGED
@@ -56,6 +56,194 @@ export declare type FieldPluginConfig = {
56
56
  */
57
57
  export declare function flattenSchemaType(schemaType: SchemaType): ObjectFieldWithPath[]
58
58
 
59
+ export declare type GrowthbookExperiment = {
60
+ id: string
61
+ dateCreated: string
62
+ dateUpdated: string
63
+ name: string
64
+ project: string
65
+ hypothesis: string
66
+ description: string
67
+ tags: [string]
68
+ owner: string
69
+ archived: boolean
70
+ status: string
71
+ autoRefresh: boolean
72
+ hashAttribute: string
73
+ fallbackAttribute: string
74
+ hashVersion: number
75
+ disableStickyBucketing: boolean
76
+ bucketVersion: number
77
+ minBucketVersion: number
78
+ variations: [
79
+ {
80
+ variationId: string
81
+ key: string
82
+ name: string
83
+ description: string
84
+ screenshots: [string]
85
+ },
86
+ ]
87
+ phases: [
88
+ {
89
+ name: string
90
+ dateStarted: string
91
+ dateEnded: string
92
+ reasonForStopping: string
93
+ seed: string
94
+ coverage: 0
95
+ trafficSplit: [
96
+ {
97
+ variationId: string
98
+ weight: 0
99
+ },
100
+ ]
101
+ namespace: {
102
+ namespaceId: string
103
+ range: []
104
+ }
105
+ targetingCondition: string
106
+ savedGroupTargeting: [
107
+ {
108
+ matchType: string
109
+ savedGroups: [string]
110
+ },
111
+ ]
112
+ },
113
+ ]
114
+ settings: {
115
+ datasourceId: string
116
+ assignmentQueryId: string
117
+ experimentId: string
118
+ segmentId: string
119
+ queryFilter: string
120
+ inProgressConversions: string
121
+ attributionModel: string
122
+ statsEngine: string
123
+ regressionAdjustmentEnabled: boolean
124
+ goals: [
125
+ {
126
+ metricId: string
127
+ overrides: {
128
+ delayHours: 0
129
+ windowHours: 0
130
+ window: string
131
+ winRiskThreshold: 0
132
+ loseRiskThreshold: 0
133
+ }
134
+ },
135
+ ]
136
+ secondaryMetrics: [
137
+ {
138
+ metricId: string
139
+ overrides: {
140
+ delayHours: 0
141
+ windowHours: 0
142
+ window: string
143
+ winRiskThreshold: 0
144
+ loseRiskThreshold: 0
145
+ }
146
+ },
147
+ ]
148
+ guardrails: [
149
+ {
150
+ metricId: string
151
+ overrides: {
152
+ delayHours: 0
153
+ windowHours: 0
154
+ window: string
155
+ winRiskThreshold: 0
156
+ loseRiskThreshold: 0
157
+ }
158
+ },
159
+ ]
160
+ activationMetric: {
161
+ metricId: string
162
+ overrides: {
163
+ delayHours: 0
164
+ windowHours: 0
165
+ window: string
166
+ winRiskThreshold: 0
167
+ loseRiskThreshold: 0
168
+ }
169
+ }
170
+ }
171
+ resultSummary: {
172
+ status: string
173
+ winner: string
174
+ conclusions: string
175
+ releasedVariationId: string
176
+ excludeFromPayload: boolean
177
+ }
178
+ }
179
+
180
+ export declare type GrowthbookFeature = {
181
+ id: string
182
+ dateCreated: string
183
+ dateUpdated: string
184
+ archived: boolean
185
+ description: string
186
+ owner: string
187
+ project: string
188
+ valueType: string
189
+ defaultValue: string
190
+ tags: string[]
191
+ environments: {
192
+ [key: string]: {
193
+ enabled: boolean
194
+ defaultValue: string
195
+ rules: {
196
+ description: string
197
+ condition: string
198
+ savedGroupTargeting: {
199
+ matchType: string
200
+ savedGroups: string[]
201
+ }[]
202
+ id: string
203
+ enabled: boolean
204
+ type: string
205
+ value: string
206
+ variations: {
207
+ value: string
208
+ variationId: string
209
+ }[]
210
+ }[]
211
+ definition: string
212
+ draft: {
213
+ enabled: boolean
214
+ defaultValue: string
215
+ rules: {
216
+ description: string
217
+ condition: string
218
+ savedGroupTargeting: {
219
+ matchType: string
220
+ savedGroups: string[]
221
+ }[]
222
+ id: string
223
+ enabled: boolean
224
+ type: string
225
+ value: string
226
+ variations: {
227
+ value: string
228
+ variationId: string
229
+ }[]
230
+ }[]
231
+ definition: string
232
+ }
233
+ }
234
+ }
235
+ prerequisites: {
236
+ parentId: string
237
+ parentCondition: string
238
+ }[]
239
+ revision: {
240
+ version: number
241
+ comment: string
242
+ date: string
243
+ publishedBy: string
244
+ }
245
+ }
246
+
59
247
  export declare type ObjectFieldWithPath = ObjectField<SchemaType> & {
60
248
  path: Path
61
249
  }