@sanity/personalization-plugin 2.1.0 → 2.2.0-growthbook.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 +2 -0
- package/dist/index.d.mts +205 -1
- package/dist/index.d.ts +205 -1
- package/dist/index.js +131 -29
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +133 -30
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
- package/src/components/ExperimentContext.tsx +8 -5
- package/src/components/ExperimentField.tsx +56 -32
- package/src/components/ExperimentInput.tsx +2 -2
- package/src/components/Secrets.tsx +47 -0
- package/src/fieldExperiments.tsx +1 -0
- package/src/growthbookFieldExperiments.tsx +51 -0
- package/src/index.ts +1 -0
- package/src/types.ts +182 -1
- package/src/utils/growthbook.ts +78 -0
package/dist/index.mjs
CHANGED
|
@@ -2,10 +2,11 @@ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
|
2
2
|
import { useClient, useWorkspace, useFormValue, defineDocumentFieldAction, set, unset, useDocumentOperation, isReference, isImage, isDocumentSchemaType, definePlugin, isObjectInputProps, defineType, defineField } from "sanity";
|
|
3
3
|
import { Stack, Inline, Button, Select as Select$1 } from "@sanity/ui";
|
|
4
4
|
import { uuid } from "@sanity/uuid";
|
|
5
|
-
import { createContext, useMemo, useContext, useCallback, forwardRef,
|
|
5
|
+
import { createContext, useState, useMemo, useContext, useCallback, forwardRef, useEffect } from "react";
|
|
6
6
|
import equal from "fast-deep-equal";
|
|
7
7
|
import { suspend } from "suspend-react";
|
|
8
8
|
import { GiSoapExperiment } from "react-icons/gi";
|
|
9
|
+
import { useSecrets, SettingsView } from "@sanity/studio-secrets";
|
|
9
10
|
const CONFIG_DEFAULT = {
|
|
10
11
|
fields: [],
|
|
11
12
|
apiVersion: "2024-11-07",
|
|
@@ -16,20 +17,23 @@ const CONFIG_DEFAULT = {
|
|
|
16
17
|
experimentId: "experimentId"
|
|
17
18
|
}, ExperimentContext = createContext({
|
|
18
19
|
...CONFIG_DEFAULT,
|
|
19
|
-
experiments: []
|
|
20
|
+
experiments: [],
|
|
21
|
+
setSecret: () => {
|
|
22
|
+
},
|
|
23
|
+
secret: void 0
|
|
20
24
|
});
|
|
21
25
|
function useExperimentContext() {
|
|
22
26
|
return useContext(ExperimentContext);
|
|
23
27
|
}
|
|
24
28
|
function ExperimentProvider(props) {
|
|
25
|
-
const { experimentFieldPluginConfig } = props, client = useClient({ apiVersion: experimentFieldPluginConfig.apiVersion }), workspace = useWorkspace(), experiments = Array.isArray(experimentFieldPluginConfig.experiments) ? experimentFieldPluginConfig.experiments : suspend(
|
|
29
|
+
const { experimentFieldPluginConfig } = props, [secret, setSecret] = useState(), client = useClient({ apiVersion: experimentFieldPluginConfig.apiVersion }), workspace = useWorkspace(), experiments = Array.isArray(experimentFieldPluginConfig.experiments) ? experimentFieldPluginConfig.experiments : suspend(
|
|
26
30
|
// eslint-disable-next-line require-await
|
|
27
|
-
async () => typeof experimentFieldPluginConfig.experiments == "function" ? experimentFieldPluginConfig.experiments(client) : experimentFieldPluginConfig.experiments,
|
|
28
|
-
[workspace],
|
|
31
|
+
async () => typeof experimentFieldPluginConfig.experiments == "function" ? experimentFieldPluginConfig.experiments(client, secret) : experimentFieldPluginConfig.experiments,
|
|
32
|
+
[workspace, secret],
|
|
29
33
|
{ equal }
|
|
30
34
|
), context = useMemo(
|
|
31
|
-
() => ({ ...experimentFieldPluginConfig, experiments }),
|
|
32
|
-
[experimentFieldPluginConfig, experiments]
|
|
35
|
+
() => ({ ...experimentFieldPluginConfig, experiments, secret, setSecret }),
|
|
36
|
+
[experimentFieldPluginConfig, experiments, secret, setSecret]
|
|
33
37
|
);
|
|
34
38
|
return /* @__PURE__ */ jsx(ExperimentContext.Provider, { value: context, children: props.renderDefault(props) });
|
|
35
39
|
}
|
|
@@ -6406,9 +6410,9 @@ const icons = {
|
|
|
6406
6410
|
});
|
|
6407
6411
|
Icon.displayName = "ForwardRef(Icon)";
|
|
6408
6412
|
const useAddExperimentAction = (props) => {
|
|
6409
|
-
const { onChange, experimentNameOverride } = props, handleAddAction = () => {
|
|
6410
|
-
onChange([set(!
|
|
6411
|
-
};
|
|
6413
|
+
const { onChange, active, experimentNameOverride } = props, handleAddAction = useCallback(() => {
|
|
6414
|
+
onChange([set(!active, ["active"])]);
|
|
6415
|
+
}, [onChange, active]);
|
|
6412
6416
|
return {
|
|
6413
6417
|
title: `Add ${experimentNameOverride}`,
|
|
6414
6418
|
type: "action",
|
|
@@ -6417,13 +6421,10 @@ const useAddExperimentAction = (props) => {
|
|
|
6417
6421
|
renderAsButton: !0
|
|
6418
6422
|
};
|
|
6419
6423
|
}, useRemoveExperimentAction = (props) => {
|
|
6420
|
-
const { onChange, experimentId, experimentNameOverride } = props,
|
|
6421
|
-
const experiment = [experimentId], variants = [
|
|
6422
|
-
|
|
6423
|
-
},
|
|
6424
|
-
const clearEvents = patchClearEvent(), activeEvent = patchActiveFalseEvent();
|
|
6425
|
-
onChange([activeEvent, ...clearEvents]);
|
|
6426
|
-
};
|
|
6424
|
+
const { onChange, active, experimentId, experimentNameOverride, variantNameOverride } = props, handleClearAction = useCallback(() => {
|
|
6425
|
+
const activeId = ["active"], experiment = [experimentId], variants = [`${variantNameOverride}s`];
|
|
6426
|
+
onChange([set(!active, activeId), unset(experiment), unset(variants)]);
|
|
6427
|
+
}, [onChange, active, experimentId, variantNameOverride]);
|
|
6427
6428
|
return {
|
|
6428
6429
|
title: `Remove ${experimentNameOverride}`,
|
|
6429
6430
|
type: "action",
|
|
@@ -6431,36 +6432,53 @@ const useAddExperimentAction = (props) => {
|
|
|
6431
6432
|
onAction: handleClearAction,
|
|
6432
6433
|
renderAsButton: !0
|
|
6433
6434
|
};
|
|
6434
|
-
},
|
|
6435
|
+
}, createActions = ({
|
|
6435
6436
|
onChange,
|
|
6436
6437
|
inputId,
|
|
6437
6438
|
active,
|
|
6438
6439
|
experimentNameOverride,
|
|
6439
|
-
experimentId
|
|
6440
|
+
experimentId,
|
|
6441
|
+
variantNameOverride
|
|
6440
6442
|
}) => {
|
|
6441
6443
|
const removeAction = defineDocumentFieldAction({
|
|
6442
6444
|
name: `Remove ${experimentNameOverride}`,
|
|
6443
6445
|
useAction: (props) => useRemoveExperimentAction({
|
|
6446
|
+
active: !0,
|
|
6444
6447
|
onChange,
|
|
6445
6448
|
experimentNameOverride,
|
|
6446
|
-
experimentId
|
|
6449
|
+
experimentId,
|
|
6450
|
+
variantNameOverride
|
|
6447
6451
|
})
|
|
6448
6452
|
}), addAction = defineDocumentFieldAction({
|
|
6449
6453
|
name: `Add ${experimentNameOverride}`,
|
|
6450
6454
|
useAction: (props) => useAddExperimentAction({
|
|
6455
|
+
active: !1,
|
|
6451
6456
|
onChange,
|
|
6452
6457
|
experimentNameOverride
|
|
6453
6458
|
})
|
|
6454
6459
|
});
|
|
6455
6460
|
return active ? removeAction : addAction;
|
|
6456
6461
|
}, ExperimentField = (props) => {
|
|
6457
|
-
const { onChange } = props.inputProps, { inputId, experimentNameOverride, experimentId } = props, active = props.value?.active,
|
|
6458
|
-
|
|
6459
|
-
|
|
6460
|
-
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
6462
|
+
const { onChange } = props.inputProps, { inputId, experimentNameOverride, experimentId, variantNameOverride } = props, active = props.value?.active, actionProps = useMemo(
|
|
6463
|
+
() => ({
|
|
6464
|
+
onChange,
|
|
6465
|
+
inputId,
|
|
6466
|
+
active,
|
|
6467
|
+
experimentNameOverride,
|
|
6468
|
+
experimentId,
|
|
6469
|
+
variantNameOverride
|
|
6470
|
+
}),
|
|
6471
|
+
[onChange, inputId, active, experimentNameOverride, experimentId, variantNameOverride]
|
|
6472
|
+
), memoizedActions = useMemo(() => {
|
|
6473
|
+
const oldActions = props.actions || [];
|
|
6474
|
+
return [createActions(actionProps), ...oldActions];
|
|
6475
|
+
}, [actionProps, props.actions]), withActionProps = useMemo(
|
|
6476
|
+
() => ({
|
|
6477
|
+
...props,
|
|
6478
|
+
actions: memoizedActions
|
|
6479
|
+
}),
|
|
6480
|
+
[props, memoizedActions]
|
|
6481
|
+
);
|
|
6464
6482
|
return props.renderDefault(withActionProps);
|
|
6465
6483
|
}, Select = (props) => {
|
|
6466
6484
|
const {
|
|
@@ -6492,7 +6510,7 @@ const useAddExperimentAction = (props) => {
|
|
|
6492
6510
|
value: experiment.id
|
|
6493
6511
|
})), ExperimentInput = (props) => {
|
|
6494
6512
|
const { experiments } = useExperimentContext(), id = useFormValue(["_id"]), aditionalChangePath = useMemo(
|
|
6495
|
-
() => [...props.path.slice(0, -1), props.variantNameOverride],
|
|
6513
|
+
() => [...props.path.slice(0, -1), `${props.variantNameOverride}s`],
|
|
6496
6514
|
[props.variantNameOverride, props.path]
|
|
6497
6515
|
), subValues = useFormValue(aditionalChangePath), { patch } = useDocumentOperation(id.replace("drafts.", ""), props.schemaType.name), handleChange = useCallback(
|
|
6498
6516
|
(event, onChange) => {
|
|
@@ -6580,7 +6598,8 @@ const createExperimentType = ({
|
|
|
6580
6598
|
{
|
|
6581
6599
|
...props,
|
|
6582
6600
|
experimentId,
|
|
6583
|
-
experimentNameOverride
|
|
6601
|
+
experimentNameOverride,
|
|
6602
|
+
variantNameOverride
|
|
6584
6603
|
}
|
|
6585
6604
|
)
|
|
6586
6605
|
},
|
|
@@ -6743,9 +6762,93 @@ const createExperimentType = ({
|
|
|
6743
6762
|
}
|
|
6744
6763
|
}
|
|
6745
6764
|
};
|
|
6765
|
+
}), namespace = "growthbook", pluginConfigKeys = [
|
|
6766
|
+
{
|
|
6767
|
+
key: "apiKey",
|
|
6768
|
+
title: "Your secret API key"
|
|
6769
|
+
}
|
|
6770
|
+
], Secrets = (props) => {
|
|
6771
|
+
const { secrets, loading } = useSecrets(namespace), { setSecret } = useExperimentContext(), [showSettings, setShowSettings] = useState(!1);
|
|
6772
|
+
return useEffect(() => {
|
|
6773
|
+
if (!loading)
|
|
6774
|
+
return !secrets && !loading ? (setSecret(void 0), setShowSettings(!0)) : (setSecret(secrets.apiKey), setShowSettings(!1));
|
|
6775
|
+
}, [secrets, loading, setSecret]), showSettings ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
6776
|
+
/* @__PURE__ */ jsx(
|
|
6777
|
+
SettingsView,
|
|
6778
|
+
{
|
|
6779
|
+
title: "Growthbook secret",
|
|
6780
|
+
namespace,
|
|
6781
|
+
keys: pluginConfigKeys,
|
|
6782
|
+
onClose: () => {
|
|
6783
|
+
setShowSettings(!1);
|
|
6784
|
+
}
|
|
6785
|
+
}
|
|
6786
|
+
),
|
|
6787
|
+
props.renderDefault(props)
|
|
6788
|
+
] }) : props.renderDefault(props);
|
|
6789
|
+
}, getBooleanConversion = (value) => value === "true" ? "variant" : value === "false" ? "control" : value, getExperiments = async ({
|
|
6790
|
+
client,
|
|
6791
|
+
environment,
|
|
6792
|
+
baseUrl,
|
|
6793
|
+
project,
|
|
6794
|
+
convertBooleans
|
|
6795
|
+
}) => {
|
|
6796
|
+
const secret = await client.fetch("*[_id == 'secrets.growthbook'][0].secrets.apiKey");
|
|
6797
|
+
if (!secret) return [];
|
|
6798
|
+
const featureExperiments = [];
|
|
6799
|
+
let hasMore = !0, offset = 0;
|
|
6800
|
+
const url = new URL(baseUrl ?? "https://api.growthbook.io/api/v1/features");
|
|
6801
|
+
for (project && url.searchParams.set("projectId", project); hasMore; ) {
|
|
6802
|
+
url.searchParams.set("offset", offset.toString());
|
|
6803
|
+
const response = await fetch(url, {
|
|
6804
|
+
headers: {
|
|
6805
|
+
Authorization: `Bearer ${secret}`
|
|
6806
|
+
}
|
|
6807
|
+
}), { features, hasMore: responseHasMore, nextOffset } = await response.json();
|
|
6808
|
+
hasMore = responseHasMore, offset = nextOffset, features && features.forEach((feature) => {
|
|
6809
|
+
if (feature.archived)
|
|
6810
|
+
return;
|
|
6811
|
+
const experiments = feature.environments[environment]?.rules.filter(
|
|
6812
|
+
(experiment) => experiment.type === "experiment-ref" || experiment.type === "experiment"
|
|
6813
|
+
);
|
|
6814
|
+
if (!experiments)
|
|
6815
|
+
return;
|
|
6816
|
+
const variations = /* @__PURE__ */ new Set();
|
|
6817
|
+
experiments.forEach((experiment) => {
|
|
6818
|
+
experiment?.variations.forEach((variant) => {
|
|
6819
|
+
variations.add({
|
|
6820
|
+
id: convertBooleans ? getBooleanConversion(variant.value) : variant.value,
|
|
6821
|
+
label: convertBooleans ? getBooleanConversion(variant.value) : variant.value
|
|
6822
|
+
});
|
|
6823
|
+
});
|
|
6824
|
+
});
|
|
6825
|
+
const value = { id: feature.id, label: feature.id, variants: [...variations] };
|
|
6826
|
+
featureExperiments.push(value);
|
|
6827
|
+
});
|
|
6828
|
+
}
|
|
6829
|
+
return featureExperiments;
|
|
6830
|
+
}, growthbookFieldLevel = definePlugin((config) => {
|
|
6831
|
+
const { fields, environment, project, convertBooleans, baseUrl } = config;
|
|
6832
|
+
return {
|
|
6833
|
+
name: "sanity-growthbook-personalistaion-plugin-field-level-experiments",
|
|
6834
|
+
plugins: [
|
|
6835
|
+
fieldLevelExperiments({
|
|
6836
|
+
fields,
|
|
6837
|
+
experiments: (client) => getExperiments({ client, environment, baseUrl, project, convertBooleans })
|
|
6838
|
+
})
|
|
6839
|
+
],
|
|
6840
|
+
form: {
|
|
6841
|
+
components: {
|
|
6842
|
+
input: (props) => !(props.id === "root" && isObjectInputProps(props)) || !flattenSchemaType(props.schemaType).map(
|
|
6843
|
+
(field) => field.type.name
|
|
6844
|
+
).some((name) => name.startsWith("experiment")) ? props.renderDefault(props) : Secrets(props)
|
|
6845
|
+
}
|
|
6846
|
+
}
|
|
6847
|
+
};
|
|
6746
6848
|
});
|
|
6747
6849
|
export {
|
|
6748
6850
|
fieldLevelExperiments,
|
|
6749
|
-
flattenSchemaType
|
|
6851
|
+
flattenSchemaType,
|
|
6852
|
+
growthbookFieldLevel
|
|
6750
6853
|
};
|
|
6751
6854
|
//# sourceMappingURL=index.mjs.map
|