@sanity/personalization-plugin 2.1.0-growthbook.1 → 2.1.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/dist/index.mjs CHANGED
@@ -2,49 +2,50 @@ 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, useState, useMemo, useContext, useCallback, forwardRef, useEffect } from "react";
5
+ import { createContext, useMemo, useContext, useCallback, forwardRef, useState, 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";
10
9
  const CONFIG_DEFAULT = {
11
10
  fields: [],
12
- apiVersion: "2024-11-07"
11
+ apiVersion: "2024-11-07",
12
+ experimentNameOverride: "experiment",
13
+ variantNameOverride: "variant",
14
+ variantId: "variantId",
15
+ variantArrayName: "variants",
16
+ experimentId: "experimentId"
13
17
  }, ExperimentContext = createContext({
14
18
  ...CONFIG_DEFAULT,
15
- experiments: [],
16
- setSecret: () => {
17
- },
18
- secret: void 0
19
+ experiments: []
19
20
  });
20
21
  function useExperimentContext() {
21
22
  return useContext(ExperimentContext);
22
23
  }
23
24
  function ExperimentProvider(props) {
24
- const { experimentFieldPluginConfig } = props, [secret, setSecret] = useState(), client = useClient({ apiVersion: experimentFieldPluginConfig.apiVersion }), workspace = useWorkspace(), experiments = Array.isArray(experimentFieldPluginConfig.experiments) ? experimentFieldPluginConfig.experiments : suspend(
25
+ const { experimentFieldPluginConfig } = props, client = useClient({ apiVersion: experimentFieldPluginConfig.apiVersion }), workspace = useWorkspace(), experiments = Array.isArray(experimentFieldPluginConfig.experiments) ? experimentFieldPluginConfig.experiments : suspend(
25
26
  // eslint-disable-next-line require-await
26
- async () => typeof experimentFieldPluginConfig.experiments == "function" ? experimentFieldPluginConfig.experiments(client, secret) : experimentFieldPluginConfig.experiments,
27
- [workspace, secret],
27
+ async () => typeof experimentFieldPluginConfig.experiments == "function" ? experimentFieldPluginConfig.experiments(client) : experimentFieldPluginConfig.experiments,
28
+ [workspace],
28
29
  { equal }
29
30
  ), context = useMemo(
30
- () => ({ ...experimentFieldPluginConfig, experiments, secret, setSecret }),
31
- [experimentFieldPluginConfig, experiments, secret, setSecret]
31
+ () => ({ ...experimentFieldPluginConfig, experiments }),
32
+ [experimentFieldPluginConfig, experiments]
32
33
  );
33
34
  return /* @__PURE__ */ jsx(ExperimentContext.Provider, { value: context, children: props.renderDefault(props) });
34
35
  }
35
36
  const ArrayInput = (props) => {
36
- const fieldPath = props.path.slice(0, -1), experimentId = useFormValue([...fieldPath, "experimentId"]), { experiments } = useExperimentContext(), { onItemAppend, objectName } = props, handleClick = useCallback(
37
+ const fieldPath = props.path.slice(0, -1), { onItemAppend, variantName, variantId, experimentId } = props, experimentValue = useFormValue([...fieldPath, experimentId]), { experiments } = useExperimentContext(), handleClick = useCallback(
37
38
  async (variant) => {
38
39
  const item = {
39
40
  _key: uuid(),
40
- variantId: variant.id,
41
- experimentId,
42
- _type: objectName
41
+ [variantId]: variant.id,
42
+ [experimentId]: experimentValue,
43
+ _type: variantName
43
44
  };
44
45
  onItemAppend(item);
45
46
  },
46
- [experimentId, objectName, onItemAppend]
47
- ), filteredVariants = experiments.find((option) => option.id === experimentId)?.variants || [], usedVariants = props.value?.map((variant) => variant.variantId);
47
+ [variantId, experimentId, experimentValue, variantName, onItemAppend]
48
+ ), filteredVariants = experiments.find((option) => option.id === experimentValue)?.variants || [], usedVariants = (props.value || [])?.map((variant) => variant[variantId]);
48
49
  return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
49
50
  props.renderDefault({ ...props, arrayFunctions: () => null }),
50
51
  /* @__PURE__ */ jsx(Inline, { space: 1, children: filteredVariants.map((variant) => /* @__PURE__ */ jsx(
@@ -55,7 +56,7 @@ const ArrayInput = (props) => {
55
56
  disabled: usedVariants?.includes(variant.id),
56
57
  onClick: () => handleClick(variant)
57
58
  },
58
- `${experimentId}-${variant.id}`
59
+ `${experimentValue}-${variant.id}`
59
60
  )) })
60
61
  ] });
61
62
  }, AccessDeniedIcon = forwardRef(function(props, ref) {
@@ -6405,40 +6406,60 @@ const icons = {
6405
6406
  });
6406
6407
  Icon.displayName = "ForwardRef(Icon)";
6407
6408
  const useAddExperimentAction = (props) => {
6408
- const patchActiveEvent = useMemo(() => set(!0, ["active"]), []), handleAction = useCallback(() => {
6409
- props.onChange([patchActiveEvent]);
6410
- }, [patchActiveEvent, props]);
6409
+ const { onChange, experimentNameOverride } = props, handleAddAction = () => {
6410
+ onChange([set(!0, ["active"])]);
6411
+ };
6411
6412
  return {
6412
- title: "Add experiment",
6413
+ title: `Add ${experimentNameOverride}`,
6413
6414
  type: "action",
6414
6415
  icon: GiSoapExperiment,
6415
- onAction: handleAction,
6416
+ onAction: handleAddAction,
6416
6417
  renderAsButton: !0
6417
6418
  };
6418
6419
  }, useRemoveExperimentAction = (props) => {
6419
- const patchActiveEvent = useMemo(() => set(!1, ["active"]), []), patchClearEvent = useMemo(() => {
6420
- const experimentId = ["experimentId"], variants = ["variants"];
6421
- return [unset(experimentId), unset(variants)];
6422
- }, []), handleAction = useCallback(() => {
6423
- props.onChange([patchActiveEvent, ...patchClearEvent]);
6424
- }, [patchActiveEvent, patchClearEvent, props]);
6420
+ const { onChange, experimentId, experimentNameOverride } = props, patchActiveFalseEvent = () => set(!1, ["active"]), patchClearEvent = () => {
6421
+ const experiment = [experimentId], variants = [experimentNameOverride];
6422
+ return [unset(experiment), unset(variants)];
6423
+ }, handleClearAction = () => {
6424
+ const clearEvents = patchClearEvent(), activeEvent = patchActiveFalseEvent();
6425
+ onChange([activeEvent, ...clearEvents]);
6426
+ };
6425
6427
  return {
6426
- title: "Remove experiment",
6428
+ title: `Remove ${experimentNameOverride}`,
6427
6429
  type: "action",
6428
6430
  icon: CloseIcon,
6429
- onAction: handleAction,
6431
+ onAction: handleClearAction,
6430
6432
  renderAsButton: !0
6431
6433
  };
6432
- }, newActions = ({ onChange, inputId, active }) => active ? defineDocumentFieldAction({
6433
- name: "Experiment",
6434
- useAction: (props) => useRemoveExperimentAction({ ...props, onChange, inputId })
6435
- }) : defineDocumentFieldAction({
6436
- name: "Experiment",
6437
- useAction: (props) => useAddExperimentAction({ ...props, onChange, inputId })
6438
- }), ExperimentField = (props) => {
6439
- const { onChange } = props.inputProps, { inputId } = props, active = props.value?.active, oldActions = props.actions || [], withActionProps = {
6434
+ }, newActions = ({
6435
+ onChange,
6436
+ inputId,
6437
+ active,
6438
+ experimentNameOverride,
6439
+ experimentId
6440
+ }) => {
6441
+ const removeAction = defineDocumentFieldAction({
6442
+ name: `Remove ${experimentNameOverride}`,
6443
+ useAction: (props) => useRemoveExperimentAction({
6444
+ onChange,
6445
+ experimentNameOverride,
6446
+ experimentId
6447
+ })
6448
+ }), addAction = defineDocumentFieldAction({
6449
+ name: `Add ${experimentNameOverride}`,
6450
+ useAction: (props) => useAddExperimentAction({
6451
+ onChange,
6452
+ experimentNameOverride
6453
+ })
6454
+ });
6455
+ return active ? removeAction : addAction;
6456
+ }, ExperimentField = (props) => {
6457
+ const { onChange } = props.inputProps, { inputId, experimentNameOverride, experimentId } = props, active = props.value?.active, oldActions = props.actions || [], withActionProps = {
6440
6458
  ...props,
6441
- actions: [newActions({ onChange, inputId, active }), ...oldActions]
6459
+ actions: [
6460
+ newActions({ onChange, inputId, active, experimentNameOverride, experimentId }),
6461
+ ...oldActions
6462
+ ]
6442
6463
  };
6443
6464
  return props.renderDefault(withActionProps);
6444
6465
  }, Select = (props) => {
@@ -6470,7 +6491,10 @@ const useAddExperimentAction = (props) => {
6470
6491
  title: experiment.label,
6471
6492
  value: experiment.id
6472
6493
  })), ExperimentInput = (props) => {
6473
- const { experiments } = useExperimentContext(), id = useFormValue(["_id"]), aditionalChangePath = useMemo(() => [...props.path.slice(0, -1), "variants"], [props.path]), subValues = useFormValue(aditionalChangePath), { patch } = useDocumentOperation(id.replace("drafts.", ""), props.schemaType.name), handleChange = useCallback(
6494
+ const { experiments } = useExperimentContext(), id = useFormValue(["_id"]), aditionalChangePath = useMemo(
6495
+ () => [...props.path.slice(0, -1), props.variantNameOverride],
6496
+ [props.variantNameOverride, props.path]
6497
+ ), subValues = useFormValue(aditionalChangePath), { patch } = useDocumentOperation(id.replace("drafts.", ""), props.schemaType.name), handleChange = useCallback(
6474
6498
  (event, onChange) => {
6475
6499
  const inputValue = event.currentTarget.value;
6476
6500
  if (onChange(inputValue ? set(inputValue) : unset()), subValues) {
@@ -6483,6 +6507,14 @@ const useAddExperimentAction = (props) => {
6483
6507
  [patch, subValues, aditionalChangePath]
6484
6508
  );
6485
6509
  return experiments.length ? /* @__PURE__ */ jsx(Select, { ...props, listOptions: formatlistOptions(experiments), handleChange }) : /* @__PURE__ */ jsx(Fragment, {});
6510
+ }, VariantInput = (props) => {
6511
+ const defaultValue = useFormValue([props.path[0], "default"]), handleClick = () => {
6512
+ props.onChange(set(defaultValue, ["value"]));
6513
+ };
6514
+ return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
6515
+ props.renderDefault(props),
6516
+ /* @__PURE__ */ jsx(Inline, { space: 1, children: /* @__PURE__ */ jsx(Button, { text: "Copy default", mode: "ghost", onClick: () => handleClick() }) })
6517
+ ] });
6486
6518
  }, VariantPreview = (props) => {
6487
6519
  const [subtitle, setSubtitle] = useState(void 0), [title, setTitle] = useState(void 0), [media, setMedia] = useState(void 0), client = useClient({ apiVersion: "2025-01-01" }), { experiments } = useExperimentContext(), { experiment, variant, value } = props, selectedExperiment = experiments.find((experimentItem) => experimentItem.id === experiment), selectedVariant = selectedExperiment?.variants.find((variantItem) => variantItem.id === variant);
6488
6520
  useEffect(() => {
@@ -6530,15 +6562,27 @@ function extractInnerFields(fields, path, maxDepth) {
6530
6562
  return [...acc, thisFieldWithPath];
6531
6563
  }, []);
6532
6564
  }
6533
- const createFieldType = ({
6534
- field
6565
+ const createExperimentType = ({
6566
+ field,
6567
+ experimentNameOverride,
6568
+ variantNameOverride,
6569
+ variantId,
6570
+ variantArrayName,
6571
+ experimentId
6535
6572
  }) => {
6536
- const typeName = typeof field == "string" ? field : field.name, usedName = String(typeName[0]).toUpperCase() + String(typeName).slice(1), objectName = `variant${usedName}`;
6573
+ const typeName = typeof field == "string" ? field : field.name, usedName = String(typeName[0]).toUpperCase() + String(typeName).slice(1), variantName = `${variantNameOverride}${usedName}`;
6537
6574
  return defineType({
6538
- name: `experiment${usedName}`,
6575
+ name: `${experimentNameOverride}${usedName}`,
6539
6576
  type: "object",
6540
6577
  components: {
6541
- field: ExperimentField
6578
+ field: (props) => /* @__PURE__ */ jsx(
6579
+ ExperimentField,
6580
+ {
6581
+ ...props,
6582
+ experimentId,
6583
+ experimentNameOverride
6584
+ }
6585
+ )
6542
6586
  },
6543
6587
  fields: [
6544
6588
  typeof field == "string" ? (
@@ -6560,186 +6604,148 @@ const createFieldType = ({
6560
6604
  hidden: !0
6561
6605
  }),
6562
6606
  defineField({
6563
- title: "Experiment",
6564
- name: "experimentId",
6607
+ name: experimentId,
6565
6608
  type: "string",
6566
6609
  components: {
6567
- input: ExperimentInput
6610
+ input: (props) => /* @__PURE__ */ jsx(ExperimentInput, { ...props, variantNameOverride })
6568
6611
  },
6569
6612
  hidden: ({ parent }) => !parent?.active
6570
6613
  }),
6571
6614
  defineField({
6572
- name: "variants",
6615
+ name: variantArrayName,
6573
6616
  type: "array",
6574
- hidden: ({ parent }) => !parent?.experimentId,
6617
+ hidden: ({ parent }) => !parent?.[experimentId],
6575
6618
  components: {
6576
- input: (props) => /* @__PURE__ */ jsx(ArrayInput, { ...props, objectName })
6619
+ input: (props) => /* @__PURE__ */ jsx(
6620
+ ArrayInput,
6621
+ {
6622
+ ...props,
6623
+ variantName,
6624
+ variantId,
6625
+ experimentId
6626
+ }
6627
+ )
6577
6628
  },
6578
6629
  of: [
6579
6630
  defineField({
6580
- name: objectName,
6581
- type: objectName
6631
+ name: variantName,
6632
+ type: variantName
6582
6633
  })
6583
6634
  ]
6584
6635
  })
6585
6636
  ]
6586
6637
  });
6587
- }, createFieldObjectType = ({
6588
- field
6638
+ }, createVariantType = ({
6639
+ field,
6640
+ variantNameOverride,
6641
+ variantId,
6642
+ experimentId
6589
6643
  }) => {
6590
6644
  const typeName = typeof field == "string" ? field : field.name, usedName = String(typeName[0]).toUpperCase() + String(typeName).slice(1);
6591
6645
  return defineType({
6592
- name: `variant${usedName}`,
6593
- title: `Experiment array ${usedName}`,
6646
+ name: `${variantNameOverride}${usedName}`,
6647
+ title: `${variantNameOverride} array ${usedName}`,
6594
6648
  type: "object",
6595
6649
  components: {
6596
- preview: VariantPreview
6650
+ preview: VariantPreview,
6651
+ input: VariantInput
6597
6652
  },
6598
6653
  fields: [
6599
6654
  {
6600
6655
  type: "string",
6601
- name: "variantId",
6656
+ name: variantId,
6602
6657
  readOnly: !0
6603
6658
  },
6604
6659
  {
6605
6660
  type: "string",
6606
- name: "experimentId",
6661
+ name: experimentId,
6607
6662
  hidden: !0
6608
6663
  },
6609
6664
  typeof field == "string" ? (
6610
6665
  // Define a simple field if all we have is the name as a string
6611
6666
  defineField({
6612
6667
  name: "value",
6613
- type: field,
6614
- hidden: ({ parent }) => !parent?.variantId
6668
+ type: field
6669
+ // hidden: ({parent}) => !parent?.[`${objectNameOverride}Id`],
6615
6670
  })
6616
6671
  ) : (
6617
6672
  // Pass in the configured options, but overwrite the name
6618
6673
  {
6619
6674
  ...field,
6620
- name: "value",
6621
- hidden: ({ parent }) => !parent?.variantId
6675
+ name: "value"
6676
+ // hidden: ({parent}) => !parent?.[`${objectNameOverride}Id`],
6622
6677
  }
6623
6678
  )
6624
6679
  ],
6625
6680
  preview: {
6626
6681
  select: {
6627
- variant: "variantId",
6628
- experiment: "experimentId",
6682
+ variant: variantId,
6683
+ experiment: experimentId,
6629
6684
  value: "value"
6630
6685
  }
6631
6686
  }
6632
6687
  });
6633
- }, fieldSchema = ({ fields, experiments }) => [
6634
- ...fields.map((field) => createFieldObjectType({ field })),
6635
- ...fields.map((field) => createFieldType({ field }))
6688
+ }, fieldSchema = ({
6689
+ fields,
6690
+ experimentNameOverride,
6691
+ variantNameOverride,
6692
+ variantId,
6693
+ variantArrayName,
6694
+ experimentId
6695
+ }) => [
6696
+ ...fields.map(
6697
+ (field) => createVariantType({ field, variantNameOverride, variantId, experimentId })
6698
+ ),
6699
+ ...fields.map(
6700
+ (field) => createExperimentType({
6701
+ field,
6702
+ experimentNameOverride,
6703
+ variantNameOverride,
6704
+ variantId,
6705
+ variantArrayName,
6706
+ experimentId
6707
+ })
6708
+ )
6636
6709
  ], fieldLevelExperiments = definePlugin((config) => {
6637
- const pluginConfig = { ...CONFIG_DEFAULT, ...config }, { fields, experiments } = pluginConfig;
6710
+ const pluginConfig = { ...CONFIG_DEFAULT, ...config }, { fields, experimentNameOverride, variantNameOverride } = pluginConfig, experimentId = `${experimentNameOverride}Id`, variantArrayName = `${variantNameOverride}s`, variantId = `${variantNameOverride}Id`;
6638
6711
  return {
6639
6712
  name: "sanity-personalistaion-plugin-field-level-experiments",
6640
6713
  schema: {
6641
- types: fieldSchema({ fields, experiments })
6714
+ types: fieldSchema({
6715
+ fields,
6716
+ experimentNameOverride,
6717
+ variantNameOverride,
6718
+ variantId,
6719
+ variantArrayName,
6720
+ experimentId
6721
+ })
6642
6722
  },
6643
6723
  form: {
6644
6724
  components: {
6645
6725
  input: (props) => {
6646
6726
  if (!(props.id === "root" && isObjectInputProps(props)) || !flattenSchemaType(props.schemaType).map(
6647
6727
  (field) => field.type.name
6648
- ).some((name) => name.startsWith("experiment")))
6728
+ ).some(
6729
+ (name) => name.startsWith(experimentNameOverride)
6730
+ ))
6649
6731
  return props.renderDefault(props);
6650
- const providerProps = { ...props, experimentFieldPluginConfig: pluginConfig };
6732
+ const providerProps = {
6733
+ ...props,
6734
+ experimentFieldPluginConfig: {
6735
+ ...pluginConfig,
6736
+ variantId,
6737
+ variantArrayName,
6738
+ experimentId
6739
+ }
6740
+ };
6651
6741
  return ExperimentProvider(providerProps);
6652
6742
  }
6653
6743
  }
6654
6744
  }
6655
6745
  };
6656
- }), namespace = "growthbook", pluginConfigKeys = [
6657
- {
6658
- key: "apiKey",
6659
- title: "Your secret API key"
6660
- }
6661
- ], Secrets = (props) => {
6662
- const { secrets, loading } = useSecrets(namespace), { setSecret } = useExperimentContext(), [showSettings, setShowSettings] = useState(!1);
6663
- return useEffect(() => {
6664
- if (!loading)
6665
- return !secrets && !loading ? (setSecret(void 0), setShowSettings(!0)) : (setSecret(secrets.apiKey), setShowSettings(!1));
6666
- }, [secrets, loading, setSecret]), showSettings ? /* @__PURE__ */ jsxs(Fragment, { children: [
6667
- /* @__PURE__ */ jsx(
6668
- SettingsView,
6669
- {
6670
- title: "Growthbook secret",
6671
- namespace,
6672
- keys: pluginConfigKeys,
6673
- onClose: () => {
6674
- setShowSettings(!1);
6675
- }
6676
- }
6677
- ),
6678
- props.renderDefault(props)
6679
- ] }) : props.renderDefault(props);
6680
- }, getBooleanConversion = (value) => value === "true" ? "variant" : value === "false" ? "control" : value, getExperiments = async ({
6681
- client,
6682
- environment,
6683
- baseUrl,
6684
- project,
6685
- convertBooleans
6686
- }) => {
6687
- const secret = await client.fetch("*[_id == 'secrets.growthbook'][0].secrets.apiKey");
6688
- if (!secret) return [];
6689
- const featureExperiments = [];
6690
- let hasMore = !0, offset = 0;
6691
- const url = new URL(baseUrl ?? "https://api.growthbook.io/api/v1/features");
6692
- for (project && url.searchParams.set("projectId", project); hasMore; ) {
6693
- url.searchParams.set("offset", offset.toString());
6694
- const response = await fetch(url, {
6695
- headers: {
6696
- Authorization: `Bearer ${secret}`
6697
- }
6698
- }), { features, hasMore: responseHasMore, nextOffset } = await response.json();
6699
- hasMore = responseHasMore, offset = nextOffset, features && features.forEach((feature) => {
6700
- if (feature.archived)
6701
- return;
6702
- const experiments = feature.environments[environment]?.rules.filter(
6703
- (experiment) => experiment.type === "experiment-ref" || experiment.type === "experiment"
6704
- );
6705
- if (!experiments)
6706
- return;
6707
- const variations = /* @__PURE__ */ new Set();
6708
- experiments.forEach((experiment) => {
6709
- experiment?.variations.forEach((variant) => {
6710
- variations.add({
6711
- id: convertBooleans ? getBooleanConversion(variant.value) : variant.value,
6712
- label: convertBooleans ? getBooleanConversion(variant.value) : variant.value
6713
- });
6714
- });
6715
- });
6716
- const value = { id: feature.id, label: feature.id, variants: [...variations] };
6717
- featureExperiments.push(value);
6718
- });
6719
- }
6720
- return featureExperiments;
6721
- }, growthbookFieldLevel = definePlugin((config) => {
6722
- const { fields, environment, project, convertBooleans, baseUrl } = config;
6723
- return {
6724
- name: "sanity-growthbook-personalistaion-plugin-field-level-experiments",
6725
- plugins: [
6726
- fieldLevelExperiments({
6727
- fields,
6728
- experiments: (client) => getExperiments({ client, environment, baseUrl, project, convertBooleans })
6729
- })
6730
- ],
6731
- form: {
6732
- components: {
6733
- input: (props) => !(props.id === "root" && isObjectInputProps(props)) || !flattenSchemaType(props.schemaType).map(
6734
- (field) => field.type.name
6735
- ).some((name) => name.startsWith("experiment")) ? props.renderDefault(props) : Secrets(props)
6736
- }
6737
- }
6738
- };
6739
6746
  });
6740
6747
  export {
6741
6748
  fieldLevelExperiments,
6742
- flattenSchemaType,
6743
- growthbookFieldLevel
6749
+ flattenSchemaType
6744
6750
  };
6745
6751
  //# sourceMappingURL=index.mjs.map