@sanity/personalization-plugin 2.1.0-field-names.1 → 2.1.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/dist/index.mjs CHANGED
@@ -2,50 +2,49 @@ 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, useState, useEffect } from "react";
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
- apiVersion: "2024-11-07",
12
- experimentNameOverride: "experiment",
13
- variantNameOverride: "variant",
14
- variantId: "variantId",
15
- variantArrayName: "variants",
16
- experimentId: "experimentId"
12
+ apiVersion: "2024-11-07"
17
13
  }, ExperimentContext = createContext({
18
14
  ...CONFIG_DEFAULT,
19
- experiments: []
15
+ experiments: [],
16
+ setSecret: () => {
17
+ },
18
+ secret: void 0
20
19
  });
21
20
  function useExperimentContext() {
22
21
  return useContext(ExperimentContext);
23
22
  }
24
23
  function ExperimentProvider(props) {
25
- const { experimentFieldPluginConfig } = props, client = useClient({ apiVersion: experimentFieldPluginConfig.apiVersion }), workspace = useWorkspace(), experiments = Array.isArray(experimentFieldPluginConfig.experiments) ? experimentFieldPluginConfig.experiments : suspend(
24
+ const { experimentFieldPluginConfig } = props, [secret, setSecret] = useState(), client = useClient({ apiVersion: experimentFieldPluginConfig.apiVersion }), workspace = useWorkspace(), experiments = Array.isArray(experimentFieldPluginConfig.experiments) ? experimentFieldPluginConfig.experiments : suspend(
26
25
  // eslint-disable-next-line require-await
27
- async () => typeof experimentFieldPluginConfig.experiments == "function" ? experimentFieldPluginConfig.experiments(client) : experimentFieldPluginConfig.experiments,
28
- [workspace],
26
+ async () => typeof experimentFieldPluginConfig.experiments == "function" ? experimentFieldPluginConfig.experiments(client, secret) : experimentFieldPluginConfig.experiments,
27
+ [workspace, secret],
29
28
  { equal }
30
29
  ), context = useMemo(
31
- () => ({ ...experimentFieldPluginConfig, experiments }),
32
- [experimentFieldPluginConfig, experiments]
30
+ () => ({ ...experimentFieldPluginConfig, experiments, secret, setSecret }),
31
+ [experimentFieldPluginConfig, experiments, secret, setSecret]
33
32
  );
34
33
  return /* @__PURE__ */ jsx(ExperimentContext.Provider, { value: context, children: props.renderDefault(props) });
35
34
  }
36
35
  const ArrayInput = (props) => {
37
- const fieldPath = props.path.slice(0, -1), { onItemAppend, variantName, variantId, experimentId } = props, experimentValue = useFormValue([...fieldPath, experimentId]), { experiments } = useExperimentContext(), handleClick = useCallback(
36
+ const fieldPath = props.path.slice(0, -1), experimentId = useFormValue([...fieldPath, "experimentId"]), { experiments } = useExperimentContext(), { onItemAppend, objectName } = props, handleClick = useCallback(
38
37
  async (variant) => {
39
38
  const item = {
40
39
  _key: uuid(),
41
- [variantId]: variant.id,
42
- [experimentId]: experimentValue,
43
- _type: variantName
40
+ variantId: variant.id,
41
+ experimentId,
42
+ _type: objectName
44
43
  };
45
44
  onItemAppend(item);
46
45
  },
47
- [variantId, experimentId, experimentValue, variantName, onItemAppend]
48
- ), filteredVariants = experiments.find((option) => option.id === experimentValue)?.variants || [], usedVariants = (props.value || [])?.map((variant) => variant[variantId]);
46
+ [experimentId, objectName, onItemAppend]
47
+ ), filteredVariants = experiments.find((option) => option.id === experimentId)?.variants || [], usedVariants = props.value?.map((variant) => variant.variantId);
49
48
  return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
50
49
  props.renderDefault({ ...props, arrayFunctions: () => null }),
51
50
  /* @__PURE__ */ jsx(Inline, { space: 1, children: filteredVariants.map((variant) => /* @__PURE__ */ jsx(
@@ -56,7 +55,7 @@ const ArrayInput = (props) => {
56
55
  disabled: usedVariants?.includes(variant.id),
57
56
  onClick: () => handleClick(variant)
58
57
  },
59
- `${experimentValue}-${variant.id}`
58
+ `${experimentId}-${variant.id}`
60
59
  )) })
61
60
  ] });
62
61
  }, AccessDeniedIcon = forwardRef(function(props, ref) {
@@ -6406,60 +6405,40 @@ const icons = {
6406
6405
  });
6407
6406
  Icon.displayName = "ForwardRef(Icon)";
6408
6407
  const useAddExperimentAction = (props) => {
6409
- const { onChange, experimentNameOverride } = props, handleAddAction = () => {
6410
- onChange([set(!0, ["active"])]);
6411
- };
6408
+ const patchActiveEvent = useMemo(() => set(!0, ["active"]), []), handleAction = useCallback(() => {
6409
+ props.onChange([patchActiveEvent]);
6410
+ }, [patchActiveEvent, props]);
6412
6411
  return {
6413
- title: `Add ${experimentNameOverride}`,
6412
+ title: "Add experiment",
6414
6413
  type: "action",
6415
6414
  icon: GiSoapExperiment,
6416
- onAction: handleAddAction,
6415
+ onAction: handleAction,
6417
6416
  renderAsButton: !0
6418
6417
  };
6419
6418
  }, useRemoveExperimentAction = (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
- };
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]);
6427
6425
  return {
6428
- title: `Remove ${experimentNameOverride}`,
6426
+ title: "Remove experiment",
6429
6427
  type: "action",
6430
6428
  icon: CloseIcon,
6431
- onAction: handleClearAction,
6429
+ onAction: handleAction,
6432
6430
  renderAsButton: !0
6433
6431
  };
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 = {
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 = {
6458
6440
  ...props,
6459
- actions: [
6460
- newActions({ onChange, inputId, active, experimentNameOverride, experimentId }),
6461
- ...oldActions
6462
- ]
6441
+ actions: [newActions({ onChange, inputId, active }), ...oldActions]
6463
6442
  };
6464
6443
  return props.renderDefault(withActionProps);
6465
6444
  }, Select = (props) => {
@@ -6491,10 +6470,7 @@ const useAddExperimentAction = (props) => {
6491
6470
  title: experiment.label,
6492
6471
  value: experiment.id
6493
6472
  })), ExperimentInput = (props) => {
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(
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(
6498
6474
  (event, onChange) => {
6499
6475
  const inputValue = event.currentTarget.value;
6500
6476
  if (onChange(inputValue ? set(inputValue) : unset()), subValues) {
@@ -6554,27 +6530,15 @@ function extractInnerFields(fields, path, maxDepth) {
6554
6530
  return [...acc, thisFieldWithPath];
6555
6531
  }, []);
6556
6532
  }
6557
- const createExperimentType = ({
6558
- field,
6559
- experimentNameOverride,
6560
- variantNameOverride,
6561
- variantId,
6562
- variantArrayName,
6563
- experimentId
6533
+ const createFieldType = ({
6534
+ field
6564
6535
  }) => {
6565
- const typeName = typeof field == "string" ? field : field.name, usedName = String(typeName[0]).toUpperCase() + String(typeName).slice(1), variantName = `${variantNameOverride}${usedName}`;
6536
+ const typeName = typeof field == "string" ? field : field.name, usedName = String(typeName[0]).toUpperCase() + String(typeName).slice(1), objectName = `variant${usedName}`;
6566
6537
  return defineType({
6567
- name: `${experimentNameOverride}${usedName}`,
6538
+ name: `experiment${usedName}`,
6568
6539
  type: "object",
6569
6540
  components: {
6570
- field: (props) => /* @__PURE__ */ jsx(
6571
- ExperimentField,
6572
- {
6573
- ...props,
6574
- experimentId,
6575
- experimentNameOverride
6576
- }
6577
- )
6541
+ field: ExperimentField
6578
6542
  },
6579
6543
  fields: [
6580
6544
  typeof field == "string" ? (
@@ -6596,47 +6560,37 @@ const createExperimentType = ({
6596
6560
  hidden: !0
6597
6561
  }),
6598
6562
  defineField({
6599
- name: experimentId,
6563
+ title: "Experiment",
6564
+ name: "experimentId",
6600
6565
  type: "string",
6601
6566
  components: {
6602
- input: (props) => /* @__PURE__ */ jsx(ExperimentInput, { ...props, variantNameOverride })
6567
+ input: ExperimentInput
6603
6568
  },
6604
6569
  hidden: ({ parent }) => !parent?.active
6605
6570
  }),
6606
6571
  defineField({
6607
- name: variantArrayName,
6572
+ name: "variants",
6608
6573
  type: "array",
6609
- hidden: ({ parent }) => !parent?.[experimentId],
6574
+ hidden: ({ parent }) => !parent?.experimentId,
6610
6575
  components: {
6611
- input: (props) => /* @__PURE__ */ jsx(
6612
- ArrayInput,
6613
- {
6614
- ...props,
6615
- variantName,
6616
- variantId,
6617
- experimentId
6618
- }
6619
- )
6576
+ input: (props) => /* @__PURE__ */ jsx(ArrayInput, { ...props, objectName })
6620
6577
  },
6621
6578
  of: [
6622
6579
  defineField({
6623
- name: variantName,
6624
- type: variantName
6580
+ name: objectName,
6581
+ type: objectName
6625
6582
  })
6626
6583
  ]
6627
6584
  })
6628
6585
  ]
6629
6586
  });
6630
- }, createVariantType = ({
6631
- field,
6632
- variantNameOverride,
6633
- variantId,
6634
- experimentId
6587
+ }, createFieldObjectType = ({
6588
+ field
6635
6589
  }) => {
6636
6590
  const typeName = typeof field == "string" ? field : field.name, usedName = String(typeName[0]).toUpperCase() + String(typeName).slice(1);
6637
6591
  return defineType({
6638
- name: `${variantNameOverride}${usedName}`,
6639
- title: `${variantNameOverride} array ${usedName}`,
6592
+ name: `variant${usedName}`,
6593
+ title: `Experiment array ${usedName}`,
6640
6594
  type: "object",
6641
6595
  components: {
6642
6596
  preview: VariantPreview
@@ -6644,99 +6598,148 @@ const createExperimentType = ({
6644
6598
  fields: [
6645
6599
  {
6646
6600
  type: "string",
6647
- name: variantId,
6601
+ name: "variantId",
6648
6602
  readOnly: !0
6649
6603
  },
6650
6604
  {
6651
6605
  type: "string",
6652
- name: experimentId,
6606
+ name: "experimentId",
6653
6607
  hidden: !0
6654
6608
  },
6655
6609
  typeof field == "string" ? (
6656
6610
  // Define a simple field if all we have is the name as a string
6657
6611
  defineField({
6658
6612
  name: "value",
6659
- type: field
6660
- // hidden: ({parent}) => !parent?.[`${objectNameOverride}Id`],
6613
+ type: field,
6614
+ hidden: ({ parent }) => !parent?.variantId
6661
6615
  })
6662
6616
  ) : (
6663
6617
  // Pass in the configured options, but overwrite the name
6664
6618
  {
6665
6619
  ...field,
6666
- name: "value"
6667
- // hidden: ({parent}) => !parent?.[`${objectNameOverride}Id`],
6620
+ name: "value",
6621
+ hidden: ({ parent }) => !parent?.variantId
6668
6622
  }
6669
6623
  )
6670
6624
  ],
6671
6625
  preview: {
6672
6626
  select: {
6673
- variant: variantId,
6674
- experiment: experimentId,
6627
+ variant: "variantId",
6628
+ experiment: "experimentId",
6675
6629
  value: "value"
6676
6630
  }
6677
6631
  }
6678
6632
  });
6679
- }, fieldSchema = ({
6680
- fields,
6681
- experimentNameOverride,
6682
- variantNameOverride,
6683
- variantId,
6684
- variantArrayName,
6685
- experimentId
6686
- }) => [
6687
- ...fields.map(
6688
- (field) => createVariantType({ field, variantNameOverride, variantId, experimentId })
6689
- ),
6690
- ...fields.map(
6691
- (field) => createExperimentType({
6692
- field,
6693
- experimentNameOverride,
6694
- variantNameOverride,
6695
- variantId,
6696
- variantArrayName,
6697
- experimentId
6698
- })
6699
- )
6633
+ }, fieldSchema = ({ fields, experiments }) => [
6634
+ ...fields.map((field) => createFieldObjectType({ field })),
6635
+ ...fields.map((field) => createFieldType({ field }))
6700
6636
  ], fieldLevelExperiments = definePlugin((config) => {
6701
- const pluginConfig = { ...CONFIG_DEFAULT, ...config }, { fields, experimentNameOverride, variantNameOverride } = pluginConfig, experimentId = `${experimentNameOverride}Id`, variantArrayName = `${variantNameOverride}s`, variantId = `${variantNameOverride}Id`;
6637
+ const pluginConfig = { ...CONFIG_DEFAULT, ...config }, { fields, experiments } = pluginConfig;
6702
6638
  return {
6703
6639
  name: "sanity-personalistaion-plugin-field-level-experiments",
6704
6640
  schema: {
6705
- types: fieldSchema({
6706
- fields,
6707
- experimentNameOverride,
6708
- variantNameOverride,
6709
- variantId,
6710
- variantArrayName,
6711
- experimentId
6712
- })
6641
+ types: fieldSchema({ fields, experiments })
6713
6642
  },
6714
6643
  form: {
6715
6644
  components: {
6716
6645
  input: (props) => {
6717
6646
  if (!(props.id === "root" && isObjectInputProps(props)) || !flattenSchemaType(props.schemaType).map(
6718
6647
  (field) => field.type.name
6719
- ).some(
6720
- (name) => name.startsWith(experimentNameOverride)
6721
- ))
6648
+ ).some((name) => name.startsWith("experiment")))
6722
6649
  return props.renderDefault(props);
6723
- const providerProps = {
6724
- ...props,
6725
- experimentFieldPluginConfig: {
6726
- ...pluginConfig,
6727
- variantId,
6728
- variantArrayName,
6729
- experimentId
6730
- }
6731
- };
6650
+ const providerProps = { ...props, experimentFieldPluginConfig: pluginConfig };
6732
6651
  return ExperimentProvider(providerProps);
6733
6652
  }
6734
6653
  }
6735
6654
  }
6736
6655
  };
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
+ };
6737
6739
  });
6738
6740
  export {
6739
6741
  fieldLevelExperiments,
6740
- flattenSchemaType
6742
+ flattenSchemaType,
6743
+ growthbookFieldLevel
6741
6744
  };
6742
6745
  //# sourceMappingURL=index.mjs.map