From 3168897b38c8ba2320169be2b0b76a151b8e2a57 Mon Sep 17 00:00:00 2001 From: Mark Phelps <209477+markphelps@users.noreply.github.com> Date: Thu, 12 Jan 2023 15:46:49 -0500 Subject: [PATCH 1/2] fix: dependency warnings in useEffectHooks --- src/app/console/Console.tsx | 2 +- src/app/flags/Evaluation.tsx | 4 ++-- src/app/flags/Flag.tsx | 2 +- src/app/flags/Flags.tsx | 2 +- src/app/segments/Segment.tsx | 8 ++++---- src/app/segments/Segments.tsx | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/app/console/Console.tsx b/src/app/console/Console.tsx index c7661a6..3031354 100644 --- a/src/app/console/Console.tsx +++ b/src/app/console/Console.tsx @@ -53,7 +53,7 @@ export default function Console() { } catch (err) { setError(err instanceof Error ? err : Error(String(err))); } - }, []); + }, [clearError, setError]); const handleSubmit = (values: IConsole) => { const { flagKey, entityId, context } = values; diff --git a/src/app/flags/Evaluation.tsx b/src/app/flags/Evaluation.tsx index 0aa3b0c..096cc4f 100644 --- a/src/app/flags/Evaluation.tsx +++ b/src/app/flags/Evaluation.tsx @@ -102,7 +102,7 @@ export default function Evaluation() { }); setRules(rules); - }, [rulesVersion]); + }, [flag]); const incrementRulesVersion = () => { setRulesVersion(rulesVersion + 1); @@ -156,7 +156,7 @@ export default function Evaluation() { useEffect(() => { loadData(); - }, [rulesVersion]); + }, [loadData, rulesVersion]); return ( <> diff --git a/src/app/flags/Flag.tsx b/src/app/flags/Flag.tsx index 6d07549..77db371 100644 --- a/src/app/flags/Flag.tsx +++ b/src/app/flags/Flag.tsx @@ -42,7 +42,7 @@ export default function Flag() { .catch((err) => { setError(err); }); - }, [flagVersion]); + }, [clearError, flag.key, setError]); const incrementFlagVersion = () => { setFlagVersion(flagVersion + 1); diff --git a/src/app/flags/Flags.tsx b/src/app/flags/Flags.tsx index bd899f8..18a9f97 100644 --- a/src/app/flags/Flags.tsx +++ b/src/app/flags/Flags.tsx @@ -20,7 +20,7 @@ export default function Flags() { return; } clearError(); - }, error); + }, [clearError, error, setError]); return ( <> diff --git a/src/app/segments/Segment.tsx b/src/app/segments/Segment.tsx index a34b4e8..f491052 100644 --- a/src/app/segments/Segment.tsx +++ b/src/app/segments/Segment.tsx @@ -37,7 +37,7 @@ export default function Segment() { const navigate = useNavigate(); const [segment, setSegment] = useState(useLoaderData() as ISegment); - const [segmentVerison, setSegmentVersion] = useState(0); + const [segmentVersion, setSegmentVersion] = useState(0); const [showConstraintForm, setShowConstraintForm] = useState(false); const [editingConstraint, setEditingConstraint] = @@ -60,15 +60,15 @@ export default function Segment() { .catch((err) => { setError(err); }); - }, [segmentVerison]); + }, [clearError, segment.key, setError]); const incrementSegmentVersion = () => { - setSegmentVersion(segmentVerison + 1); + setSegmentVersion(segmentVersion + 1); }; useEffect(() => { fetchSegment(); - }, [segmentVerison, fetchSegment]); + }, [segmentVersion, fetchSegment]); const constraintTypeToLabel = (t: string) => ComparisonType[t as keyof typeof ComparisonType]; diff --git a/src/app/segments/Segments.tsx b/src/app/segments/Segments.tsx index 368b150..e11be53 100644 --- a/src/app/segments/Segments.tsx +++ b/src/app/segments/Segments.tsx @@ -20,7 +20,7 @@ export default function Segments() { return; } clearError(); - }, error); + }, [clearError, error, setError]); return ( <> From 7ad6396ef78f0874c629111bfcd1245d950ef885 Mon Sep 17 00:00:00 2001 From: Mark Phelps <209477+markphelps@users.noreply.github.com> Date: Thu, 12 Jan 2023 16:45:13 -0500 Subject: [PATCH 2/2] chore: show help text when creating rule w no variants --- src/components/rules/RuleForm.tsx | 448 +++++++++++++++++------------- 1 file changed, 258 insertions(+), 190 deletions(-) diff --git a/src/components/rules/RuleForm.tsx b/src/components/rules/RuleForm.tsx index 2aa1111..4874c16 100644 --- a/src/components/rules/RuleForm.tsx +++ b/src/components/rules/RuleForm.tsx @@ -2,11 +2,14 @@ import { Dialog } from '@headlessui/react'; import { XMarkIcon } from '@heroicons/react/24/outline'; import { Form, Formik } from 'formik'; import { useState } from 'react'; +import { Link } from 'react-router-dom'; +import * as Yup from 'yup'; import Button from '~/components/forms/Button'; import Combobox, { ISelectable } from '~/components/forms/Combobox'; import MoreInfo from '~/components/MoreInfo'; import { createDistribution, createRule } from '~/data/api'; import useError from '~/data/hooks/errors'; +import { keyValidation } from '~/data/validations'; import { IFlag } from '~/types/Flag'; import { ISegment } from '~/types/Segment'; import { IVariant } from '~/types/Variant'; @@ -58,6 +61,168 @@ type SelectableSegment = ISegment & ISelectable; type SelectableVariant = IVariant & ISelectable; +type DistributionFormProps = { + flag: IFlag; + ruleType: string; + setRuleType: (ruleType: string) => void; + distributions?: Distribution[]; + setDistributions: (distributions: Distribution[]) => void; + selectedVariant: SelectableVariant | null; + setSelectedVariant: (variant: SelectableVariant | null) => void; +}; + +function DistributionForm(props: DistributionFormProps) { + const { + flag, + ruleType, + setRuleType, + distributions, + setDistributions, + selectedVariant, + setSelectedVariant + } = props; + + return ( + <> +
+
+ +
+
+
+ Type +
+ {distTypes.map((dist) => ( +
+
+ { + setRuleType(dist.id); + }} + checked={dist.id === ruleType} + value={dist.id} + /> +
+
+ +

+ {dist.description} +

+
+
+ ))} +
+
+
+
+ + <> + {ruleType === 'single' && ( +
+
+ +
+
+ + id="variant" + name="variant" + placeholder="Select or search for a variant" + values={ + flag?.variants?.map((v) => ({ + ...v, + filterValue: v.key, + displayValue: v.name + })) || [] + } + selected={selectedVariant} + setSelected={setSelectedVariant} + /> +
+
+ )} + + + {ruleType === 'multi' && ( +
+
+
+ +
+
+ {distributions?.map((dist, index) => ( +
+
+ +
+
+ { + const newDistributions = [...distributions]; + newDistributions[index].rollout = parseFloat( + e.target.value + ); + setDistributions(newDistributions); + }} + /> +
+ + % + +
+
+
+ ))} +
+ )} + + ); +} + export default function RuleForm(props: RuleFormProps) { const { setOpen, rulesChanged, flag, rank, segments } = props; const { setError, clearError } = useError(); @@ -69,6 +234,8 @@ export default function RuleForm(props: RuleFormProps) { const [selectedVariant, setSelectedVariant] = useState(null); + const hasVariants = flag.variants && flag.variants.length > 0; + const [distributions, setDistributions] = useState(() => { const percentages = computePercentages(flag.variants?.length || 0); @@ -80,7 +247,10 @@ export default function RuleForm(props: RuleFormProps) { }); const handleSubmit = async () => { - if (!selectedSegment) throw new Error('No segment selected'); + if (!selectedSegment) { + throw new Error('No segment selected'); + } + const rule = await createRule(flag.key, { flagKey: flag.key, segmentKey: selectedSegment.key, @@ -96,12 +266,14 @@ export default function RuleForm(props: RuleFormProps) { ); if (distPromises) await Promise.all(distPromises); } else { - if (!selectedVariant) throw new Error('No variant selected'); + if (selectedVariant) { + // we allow creating rules without variants - await createDistribution(flag.key, rule.id, { - variantId: selectedVariant.id, - rollout: 100 - }); + await createDistribution(flag.key, rule.id, { + variantId: selectedVariant.id, + rollout: 100 + }); + } } rulesChanged(); @@ -114,208 +286,104 @@ export default function RuleForm(props: RuleFormProps) { initialValues={{ segmentKey: selectedSegment?.key || '' }} + validationSchema={Yup.object({ + segmentKey: keyValidation + })} onSubmit={() => { handleSubmit().catch((err) => { setError(err); }); }} > -
-
-
-
-
- - New Rule - - - Learn more about rules - -
-
- -
-
-
-
-
-
- -
-
- - id="segmentKey" - name="segmentKey" - placeholder="Select or search for a segment" - values={segments.map((s) => ({ - ...s, - filterValue: s.key, - displayValue: s.name - }))} - selected={selectedSegment} - setSelected={setSelectedSegment} - /> -
-
-
-
- -
-
-
- Type -
- {distTypes.map((dist) => ( -
-
- { - setRuleType(dist.id); - }} - checked={dist.id === ruleType} - value={dist.id} - /> -
-
- -

- {dist.description} -

-
-
- ))} + {(formik) => { + return ( + +
+
+
+
+ + New Rule + + + Learn more about rules + +
+
+
-
-
-
- - {ruleType === 'single' && ( -
-
- -
-
- - id="variant" - name="variant" - placeholder="Select or search for a variant" - values={ - flag?.variants?.map((v) => ({ - ...v, - filterValue: v.key, - displayValue: v.name - })) || [] - } - selected={selectedVariant} - setSelected={setSelectedVariant} - />
- )} - - {ruleType === 'multi' && ( -
+
-
- {distributions?.map((dist, index) => ( -
-
- -
-
- { - const newDistributions = [...distributions]; - newDistributions[index].rollout = parseFloat( - e.target.value - ); - setDistributions(newDistributions); - }} - /> -
- - % - -
-
+
+ + id="segmentKey" + name="segmentKey" + placeholder="Select or search for a segment" + values={segments.map((s) => ({ + ...s, + filterValue: s.key, + displayValue: s.name + }))} + selected={selectedSegment} + setSelected={setSelectedSegment} + />
- ))} +
+ {hasVariants && ( + + )} + {!hasVariants && ( +

+ Flag{' '} + + {flag.key} + {' '} + has no variants. You can add variants in the details + section. +

+ )}
- )} -
-
-
-
- - -
-
- +
+
+
+ + +
+
+ + ); + }} ); }