Skip to content

Commit

Permalink
System form/vendor selector fixes (#4420)
Browse files Browse the repository at this point in the history
  • Loading branch information
jpople authored Nov 13, 2023
1 parent b0ffcf5 commit c3cff68
Show file tree
Hide file tree
Showing 6 changed files with 260 additions and 128 deletions.
12 changes: 0 additions & 12 deletions clients/admin-ui/cypress/e2e/systems-plus.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,6 @@ describe("System management with Plus features", () => {
);
});

it("can switch entries", () => {
cy.getSelectValueContainer("input-vendor_id").type("Aniview{enter}");
cy.getSelectValueContainer("input-vendor_id").contains("Aniview LTD");

cy.getSelectValueContainer("input-vendor_id").type("Anzu{enter}");
cy.getSelectValueContainer("input-vendor_id").contains(
"Anzu Virtual Reality LTD"
);
});

it("locks editing for a GVL vendor when TCF is enabled", () => {
cy.getSelectValueContainer("input-vendor_id").type("Aniview{enter}");
cy.getByTestId("locked-for-GVL-notice");
Expand All @@ -66,8 +56,6 @@ describe("System management with Plus features", () => {
it("can switch between tabs after populating from dictionary", () => {
cy.wait("@getSystems");
cy.getSelectValueContainer("input-vendor_id").type("Anzu{enter}");
cy.getByTestId("dict-suggestions-btn").click();
cy.getByTestId("toggle-dict-suggestions").click();
// the form fetches the system again after saving, so update the intercept with dictionary values
cy.fixture("systems/dictionary-system.json").then((dictSystem) => {
cy.fixture("systems/system.json").then((origSystem) => {
Expand Down
60 changes: 42 additions & 18 deletions clients/admin-ui/src/features/system/SystemInformationForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import {
} from "~/features/common/custom-fields";
import { useFeatures } from "~/features/common/features/features.slice";
import {
CustomCreatableSelect,
CustomSelect,
CustomSwitch,
CustomTextInput,
Expand All @@ -29,6 +28,7 @@ import {
extractVendorSource,
getErrorMessage,
isErrorResult,
isFetchBaseQueryError,
VendorSources,
} from "~/features/common/helpers";
import { FormGuard } from "~/features/common/hooks/useIsAnyFormDirty";
Expand All @@ -43,6 +43,7 @@ import {
setSuggestions,
} from "~/features/system/dictionary-form/dict-suggestion.slice";
import {
DictSuggestionCreatableSelect,
DictSuggestionNumberInput,
DictSuggestionSelect,
DictSuggestionSwitch,
Expand Down Expand Up @@ -72,11 +73,6 @@ import {
responsibilityOptions,
} from "./SystemInformationFormSelectOptions";

const ValidationSchema = Yup.object().shape({
name: Yup.string().required().label("System name"),
privacy_policy: Yup.string().min(1).url().nullable(),
});

const SystemHeading = ({ system }: { system?: SystemResponse }) => {
const isManual = !system;
const headingName = isManual
Expand All @@ -103,6 +99,8 @@ const SystemInformationForm = ({
withHeader,
children,
}: Props) => {
const systems = useAppSelector(selectAllSystems);

const dispatch = useAppDispatch();
const customFields = useCustomFields({
resourceType: ResourceTypes.SYSTEM,
Expand All @@ -125,6 +123,23 @@ const SystemInformationForm = ({
[passedInSystem, customFields.customFieldValues]
);

const ValidationSchema = useMemo(
() =>
Yup.object().shape({
name: Yup.string()
.required()
.label("System name")
.notOneOf(
systems
.filter((s) => s.name !== initialValues.name)
.map((s) => s.name),
"System must have a unique name"
),
privacy_policy: Yup.string().min(1).url().nullable(),
}),
[systems, initialValues.name]
);

const features = useFeatures();

const [createSystemMutationTrigger, createSystemMutationResult] =
Expand All @@ -139,7 +154,6 @@ const SystemInformationForm = ({
const dictionaryOptions = useAppSelector(selectAllDictEntries);
const lockedForGVL = useAppSelector(selectLockedForGVL);

const systems = useAppSelector(selectAllSystems);
const isEditing = useMemo(
() =>
Boolean(
Expand Down Expand Up @@ -167,16 +181,21 @@ const SystemInformationForm = ({
formikHelpers: FormikHelpers<FormValues>
) => {
let dictionaryDeclarations;
if (lockedForGVL && values.privacy_declarations.length === 0) {
if (values.vendor_id && values.privacy_declarations.length === 0) {
const dataUseQueryResult = await getDictionaryDataUseTrigger({
vendor_id: values.vendor_id!,
});
if (dataUseQueryResult.isError) {
const dataUseErrorMsg = getErrorMessage(
dataUseQueryResult.error,
`A problem occurred while fetching data uses from the GVL for your system. Please try again.`
);
toast({ status: "error", description: dataUseErrorMsg });
const isNotFoundError =
isFetchBaseQueryError(dataUseQueryResult.error) &&
dataUseQueryResult.error.status === 404;
if (!isNotFoundError) {
const dataUseErrorMsg = getErrorMessage(
dataUseQueryResult.error,
`A problem occurred while fetching data uses from Fides Compass for your system. Please try again.`
);
toast({ status: "error", description: dataUseErrorMsg });
}
} else if (
dataUseQueryResult.data &&
dataUseQueryResult.data.items.length > 0
Expand Down Expand Up @@ -232,12 +251,17 @@ const SystemInformationForm = ({
handleResult(result);
};

const handleVendorSelected = (newVendorId: string) => {
const handleVendorSelected = (newVendorId: string | undefined) => {
if (!newVendorId) {
dispatch(setSuggestions("hiding"));
dispatch(setLockedForGVL(false));
return;
}
dispatch(setSuggestions("showing"));
if (
features.tcf &&
extractVendorSource(newVendorId) === VendorSources.GVL
) {
dispatch(setSuggestions("showing"));
dispatch(setLockedForGVL(true));
} else {
dispatch(setLockedForGVL(false));
Expand Down Expand Up @@ -275,6 +299,7 @@ const SystemInformationForm = ({
<VendorSelector
options={dictionaryOptions}
onVendorSelected={handleVendorSelected}
disabled={!!passedInSystem && lockedForGVL}
/>
) : null}
<DictSuggestionTextInput
Expand Down Expand Up @@ -303,11 +328,10 @@ const SystemInformationForm = ({
tooltip="What services does this system perform?"
disabled={lockedForGVL}
/>
<CustomCreatableSelect
<DictSuggestionCreatableSelect
id="tags"
name="tags"
label="System Tags"
variant="stacked"
options={
initialValues.tags
? initialValues.tags.map((s) => ({
Expand All @@ -318,7 +342,7 @@ const SystemInformationForm = ({
}
tooltip="Are there any tags to associate with this system?"
isMulti
isDisabled={lockedForGVL}
disabled={lockedForGVL}
/>
</SystemFormInputGroup>
<SystemFormInputGroup heading="Dataset reference">
Expand Down
51 changes: 44 additions & 7 deletions clients/admin-ui/src/features/system/VendorSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,47 @@
import { Flex, FormControl, HStack, Text, VStack } from "@fidesui/react";
import { Select, SingleValue } from "chakra-react-select";
import {
ActionMeta,
chakraComponents,
GroupBase,
OptionProps,
Select,
SingleValue,
} from "chakra-react-select";
import { useField, useFormikContext } from "formik";
import { useState } from "react";

import { ErrorMessage, Label, Option } from "~/features/common/form/inputs";
import QuestionTooltip from "~/features/common/QuestionTooltip";
import { DictOption } from "~/features/plus/plus.slice";
import { DictSuggestionToggle } from "~/features/system/dictionary-form/ToggleDictSuggestions";

interface Props {
disabled?: boolean;
options: DictOption[];
onVendorSelected: (vendorId: string) => void;
onVendorSelected: (vendorId: string | undefined) => void;
}

const CustomDictOption: React.FC<
OptionProps<Option, false, GroupBase<Option>>
> = ({ children, ...props }) => (
<chakraComponents.Option {...props} type="option">
<Flex flexDirection="column" padding={2}>
<Text color="gray.700" fontSize="14px" lineHeight={5} fontWeight="medium">
{props.data.label}
</Text>

{props.data.description ? (
<Text
color="gray.500"
fontSize="12px"
lineHeight={4}
fontWeight="normal"
>
{props.data.description}
</Text>
) : null}
</Flex>
</chakraComponents.Option>
);
const VendorSelector = ({ disabled, options, onVendorSelected }: Props) => {
const [initialField, meta, { setValue }] = useField({ name: "vendor_id" });
const isInvalid = !!(meta.touched && meta.error);
Expand All @@ -26,17 +54,25 @@ const VendorSelector = ({ disabled, options, onVendorSelected }: Props) => {
opt.label.toLowerCase().startsWith(searchParam.toLowerCase())
);

const selected = options.find((o) => o.value === field.value);

const handleTabPressed = () => {
if (suggestions.length > 0 && searchParam !== suggestions[0].label) {
setSearchParam(suggestions[0].label);
setValue(suggestions[0].value);
}
};

const handleChange = (newValue: SingleValue<Option>) => {
const handleChange = (
newValue: SingleValue<Option>,
actionMeta: ActionMeta<Option>
) => {
if (newValue) {
setValue(newValue.value);
onVendorSelected(newValue.value);
} else if (actionMeta.action === "clear") {
setValue("");
onVendorSelected(undefined);
}
};

Expand All @@ -45,7 +81,7 @@ const VendorSelector = ({ disabled, options, onVendorSelected }: Props) => {
<FormControl isInvalid={isInvalid}>
<VStack alignItems="start" position="relative">
<Flex alignItems="center">
<Label htmlFor="vendor" fontSize="xs" my={0} mr={1}>
<Label htmlFor="vendor_id" fontSize="xs" my={0} mr={1}>
Vendor
</Label>
<QuestionTooltip label="Enter the vendor to associate with the system" />
Expand All @@ -57,8 +93,9 @@ const VendorSelector = ({ disabled, options, onVendorSelected }: Props) => {
>
<Select
options={suggestions}
value={selected}
onBlur={(e) => {
setTouched({ ...touched, test_vendor: true });
setTouched({ ...touched, vendor_id: true });
field.onBlur(e);
}}
onChange={handleChange}
Expand Down Expand Up @@ -102,6 +139,7 @@ const VendorSelector = ({ disabled, options, onVendorSelected }: Props) => {
display: "none",
}),
}}
components={{ Option: CustomDictOption }}
/>
<Text
aria-hidden
Expand Down Expand Up @@ -131,7 +169,6 @@ const VendorSelector = ({ disabled, options, onVendorSelected }: Props) => {
/>
</VStack>
</FormControl>
<DictSuggestionToggle />
</HStack>
);
};
Expand Down
Loading

0 comments on commit c3cff68

Please sign in to comment.