From 8317981c1195c1d72a220f9f990d7dc6d6aef7ca Mon Sep 17 00:00:00 2001 From: Scott J Dickerson Date: Wed, 27 Sep 2023 10:35:29 -0400 Subject: [PATCH 1/4] Applications form: Fix contributors field handling (and other stuff) The `contributors` field on the `Application` payload needs to be a pure `Ref` object or it will be rejected by the REST API call. Adopt the same set of data transforms used in the archetype-form to handle getting the correct set of data. Related changes: - REST `createApplication()` function updated to have the proper return type (response data is not unwrapped) - Query `useCreateApplicationMutation()` updated to properly pass the newly created `Application` to the `onSuccess()` handler - `onCreateApplicationSuccess()` in the application form updated to use the correct onSuccess() response data Signed-off-by: Scott J Dickerson --- client/src/app/api/rest.ts | 4 ++-- .../application-form/application-form.tsx | 24 ++++++++++++------- client/src/app/queries/applications.ts | 8 +++---- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/client/src/app/api/rest.ts b/client/src/app/api/rest.ts index 701d558452..14e1cb1ebf 100644 --- a/client/src/app/api/rest.ts +++ b/client/src/app/api/rest.ts @@ -271,8 +271,8 @@ export const deleteIdentity = (identity: Identity): AxiosPromise => { // Axios direct -export const createApplication = (obj: Application): Promise => - axios.post(`${APPLICATIONS}`, obj); +export const createApplication = (data: Application) => + axios.post(`${APPLICATIONS}`, data); export const deleteApplication = (id: number): Promise => axios.delete(`${APPLICATIONS}/${id}`); diff --git a/client/src/app/pages/applications/components/application-form/application-form.tsx b/client/src/app/pages/applications/components/application-form/application-form.tsx index c82a6eb00c..ec28e3fca0 100644 --- a/client/src/app/pages/applications/components/application-form/application-form.tsx +++ b/client/src/app/pages/applications/components/application-form/application-form.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { AxiosError, AxiosResponse } from "axios"; +import { AxiosError } from "axios"; import { object, string } from "yup"; import { ActionGroup, @@ -16,7 +16,7 @@ import { yupResolver } from "@hookform/resolvers/yup"; import { SimpleSelect, OptionWithValue } from "@app/components/SimpleSelect"; import { DEFAULT_SELECT_MAX_HEIGHT } from "@app/Constants"; -import { Application, TagRef } from "@app/api/models"; +import { Application, Ref, TagRef } from "@app/api/models"; import { customURLValidation, duplicateNameCheck, @@ -104,7 +104,7 @@ export const ApplicationForm: React.FC = ({ if (tagCategories) { setTags(tagCategories.flatMap((f) => f.tags || [])); } - }, []); + }, [tagCategories]); const tagOptions = new Set( (tags || []).reduce( @@ -271,11 +271,11 @@ export const ApplicationForm: React.FC = ({ } }; - const onCreateApplicationSuccess = (response: AxiosResponse) => { + const onCreateApplicationSuccess = (data: Application) => { pushNotification({ title: t("toastr.success.createWhat", { type: t("terms.application"), - what: response.data.name, + what: data.name, }), variant: "success", }); @@ -319,9 +319,15 @@ export const ApplicationForm: React.FC = ({ (stakeholder) => formValues?.owner === stakeholder.name ); - const matchingContributors = stakeholders?.filter((stakeholder) => - formValues.contributors.includes(stakeholder.name) - ); + const contributors = + formValues.contributors === undefined + ? undefined + : (formValues.contributors + .map((name) => stakeholders.find((s) => s.name === name)) + .map((sh) => + !sh ? undefined : { id: sh.id, name: sh.name } + ) + .filter(Boolean) as Ref[]); const payload: Application = { name: formValues.name.trim(), @@ -337,7 +343,7 @@ export const ApplicationForm: React.FC = ({ owner: matchingOwner ? { id: matchingOwner.id, name: matchingOwner.name } : undefined, - contributors: matchingContributors, + contributors, ...(formValues.sourceRepository ? { repository: { diff --git a/client/src/app/queries/applications.ts b/client/src/app/queries/applications.ts index 10211f6813..b4a403a698 100644 --- a/client/src/app/queries/applications.ts +++ b/client/src/app/queries/applications.ts @@ -1,7 +1,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { AxiosError } from "axios"; -import { MimeType } from "@app/api/models"; +import { Application, MimeType } from "@app/api/models"; import { createApplication, deleteApplication, @@ -89,14 +89,14 @@ export const useUpdateAllApplicationsMutation = ( }; export const useCreateApplicationMutation = ( - onSuccess: (res: any) => void, + onSuccess: (data: Application) => void, onError: (err: AxiosError) => void ) => { const queryClient = useQueryClient(); return useMutation({ mutationFn: createApplication, - onSuccess: (res) => { - onSuccess(res); + onSuccess: ({ data }) => { + onSuccess(data); queryClient.invalidateQueries([ApplicationsQueryKey]); }, onError: onError, From 14e1a8e96e2cbd688d1016b5ec315f623611149d Mon Sep 17 00:00:00 2001 From: Scott J Dickerson Date: Thu, 28 Sep 2023 10:50:14 -0400 Subject: [PATCH 2/4] Application Form: Tweak tagCategory, tags, tagOptions handling Signed-off-by: Scott J Dickerson --- .../application-form/application-form.tsx | 29 ++++--------------- 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/client/src/app/pages/applications/components/application-form/application-form.tsx b/client/src/app/pages/applications/components/application-form/application-form.tsx index ec28e3fca0..e0f4fccb79 100644 --- a/client/src/app/pages/applications/components/application-form/application-form.tsx +++ b/client/src/app/pages/applications/components/application-form/application-form.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React from "react"; import { useTranslation } from "react-i18next"; import { AxiosError } from "axios"; import { object, string } from "yup"; @@ -74,9 +74,7 @@ export const ApplicationForm: React.FC = ({ const { businessServices } = useFetchBusinessServices(); const { stakeholders } = useFetchStakeholders(); - - const { tagCategories: tagCategories, refetch: fetchTagCategories } = - useFetchTagCategories(); + const { tagCategories } = useFetchTagCategories(); const businessServiceOptions = businessServices.map((businessService) => { return { @@ -92,26 +90,9 @@ export const ApplicationForm: React.FC = ({ }; }); - useEffect(() => { - fetchTagCategories(); - }, [fetchTagCategories]); - // Tags - - const [tags, setTags] = useState(); - - useEffect(() => { - if (tagCategories) { - setTags(tagCategories.flatMap((f) => f.tags || [])); - } - }, [tagCategories]); - - const tagOptions = new Set( - (tags || []).reduce( - (acc, curr) => (!curr.source ? [...acc, curr.name] : acc), - [] - ) - ); + const tags: TagRef[] = tagCategories.flatMap((f) => f.tags || []); + const tagOptions = new Set(tags.map((tag) => tag.name)); const getBinaryInitialValue = ( application: Application | null, @@ -450,7 +431,7 @@ export const ApplicationForm: React.FC = ({ name="tags" label={t("terms.tags")} fieldId="tags" - renderInput={({ field: { value, name, onChange } }) => { + renderInput={({ field: { value, onChange } }) => { const selections = value.reduce( (acc, curr) => curr.source === "" && tagOptions.has(curr.name) From d8d5cb8b69f397f5dadd312311c01cdfa3fd5fe3 Mon Sep 17 00:00:00 2001 From: Scott J Dickerson Date: Thu, 28 Sep 2023 15:56:57 -0400 Subject: [PATCH 3/4] Refactor `ApplicationForm` Followup #1404 to update the way tags are updated to an existing application. Tags that are from an analysis need to be included with the update payload or they will be removed. This is different behavior from archetype or assessment tags. No updates to an application can remove archetype or assessment tags. Summary of changes and refactoring: - As form values, keep the tags using just the tag name as a string - Moved all data access/mutation code to hook `useApplicationFormData()` to logically divide concerns (data access v. UI handling) - Tags dropdown will only display/edit the application's "Manual tags" - `onSubmit()`'s payload building simplified using partially curried helper functions - Migrate to use `ItemsSelect` for handling the Tags Update `useUpdateApplicationMutation()` to provide the mutation's payload to the `onSuccess()` function. This allows the `onSuccess()` function to toast a message with the application's name. Add utility functions to `model-utils.tsx`: - convert objects that look like `Ref` object into exactly a `Ref` object: - toRef() - toRefs() - Match a set of items that look like `Ref` objects against a (set of) values and the matching matches as exactly `Ref` objects: items into exactly `Ref` objects: - matchItemsToRef() - matchItemsToRefs() Signed-off-by: Scott J Dickerson --- .../application-form/application-form.tsx | 301 +++++++++--------- client/src/app/queries/applications.ts | 6 +- client/src/app/utils/model-utils.tsx | 68 ++++ 3 files changed, 224 insertions(+), 151 deletions(-) diff --git a/client/src/app/pages/applications/components/application-form/application-form.tsx b/client/src/app/pages/applications/components/application-form/application-form.tsx index e0f4fccb79..4d2f4a149f 100644 --- a/client/src/app/pages/applications/components/application-form/application-form.tsx +++ b/client/src/app/pages/applications/components/application-form/application-form.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useMemo } from "react"; import { useTranslation } from "react-i18next"; import { AxiosError } from "axios"; import { object, string } from "yup"; @@ -16,13 +16,17 @@ import { yupResolver } from "@hookform/resolvers/yup"; import { SimpleSelect, OptionWithValue } from "@app/components/SimpleSelect"; import { DEFAULT_SELECT_MAX_HEIGHT } from "@app/Constants"; -import { Application, Ref, TagRef } from "@app/api/models"; +import { Application, Tag } from "@app/api/models"; import { customURLValidation, duplicateNameCheck, getAxiosErrorMessage, } from "@app/utils/utils"; -import { toOptionLike } from "@app/utils/model-utils"; +import { + matchItemsToRef, + matchItemsToRefs, + toOptionLike, +} from "@app/utils/model-utils"; import { useCreateApplicationMutation, useFetchApplications, @@ -39,14 +43,15 @@ import { import { QuestionCircleIcon } from "@patternfly/react-icons"; import { useFetchStakeholders } from "@app/queries/stakeholders"; import { NotificationsContext } from "@app/components/NotificationsContext"; -import { Autocomplete } from "@app/components/Autocomplete"; +import ItemsSelect from "@app/components/items-select/items-select"; export interface FormValues { + id: number; name: string; description: string; comments: string; businessServiceName: string; - tags: TagRef[]; + tags: string[]; owner: string | null; contributors: string[]; kind: string; @@ -57,7 +62,6 @@ export interface FormValues { artifact: string; version: string; packaging: string; - id: number; } export interface ApplicationFormProps { @@ -70,11 +74,20 @@ export const ApplicationForm: React.FC = ({ onClose, }) => { const { t } = useTranslation(); - const { pushNotification } = React.useContext(NotificationsContext); - - const { businessServices } = useFetchBusinessServices(); - const { stakeholders } = useFetchStakeholders(); - const { tagCategories } = useFetchTagCategories(); + const { + existingApplications, + businessServices, + businessServicesToRef, + stakeholders, + stakeholdersToRef, + stakeholdersToRefs, + tags, + tagsToRefs, + createApplication, + updateApplication, + } = useApplicationFormData({ + onActionSuccess: onClose, + }); const businessServiceOptions = businessServices.map((businessService) => { return { @@ -90,9 +103,12 @@ export const ApplicationForm: React.FC = ({ }; }); - // Tags - const tags: TagRef[] = tagCategories.flatMap((f) => f.tags || []); - const tagOptions = new Set(tags.map((tag) => tag.name)); + const manualTags = application?.tags?.filter((t) => t.source === "") ?? []; + + const nonManualTags = application?.tags?.filter((t) => t.source !== "") ?? []; + + // TODO: Filter this if we want to exclude non-manual tags from manual tag selection + const allowedManualTags = tags; const getBinaryInitialValue = ( application: Application | null, @@ -113,8 +129,6 @@ export const ApplicationForm: React.FC = ({ } }; - const { data: applications } = useFetchApplications(); - const validationSchema = object().shape( { name: string() @@ -127,7 +141,7 @@ export const ApplicationForm: React.FC = ({ "An application with this name already exists. Use a different name.", (value) => duplicateNameCheck( - applications ? applications : [], + existingApplications, application || null, value || "" ) @@ -222,7 +236,7 @@ export const ApplicationForm: React.FC = ({ id: application?.id || 0, comments: application?.comments || "", businessServiceName: application?.businessService?.name || "", - tags: application?.tags || [], + tags: manualTags.map((tag) => tag.name) || [], owner: application?.owner?.name || undefined, contributors: application?.contributors?.map((contributor) => contributor.name) || [], @@ -239,113 +253,36 @@ export const ApplicationForm: React.FC = ({ mode: "all", }); - const buildBinaryFieldString = ( - group: string, - artifact: string, - version: string, - packaging: string - ) => { - if (packaging) { - return `${group}:${artifact}:${version}:${packaging}`; - } else { - return `${group}:${artifact}:${version}`; - } - }; - - const onCreateApplicationSuccess = (data: Application) => { - pushNotification({ - title: t("toastr.success.createWhat", { - type: t("terms.application"), - what: data.name, - }), - variant: "success", - }); - onClose(); - }; - - const onUpdateApplicationSuccess = () => { - pushNotification({ - title: t("toastr.success.save", { - type: t("terms.application"), - }), - variant: "success", - }); - onClose(); - }; - - const onCreateUpdateApplicationError = (error: AxiosError) => { - pushNotification({ - title: getAxiosErrorMessage(error), - variant: "danger", - }); - }; - - const { mutate: createApplication } = useCreateApplicationMutation( - onCreateApplicationSuccess, - onCreateUpdateApplicationError - ); - - const { mutate: updateApplication } = useUpdateApplicationMutation( - onUpdateApplicationSuccess, - onCreateUpdateApplicationError - ); - const onSubmit = (formValues: FormValues) => { - const matchingBusinessService = businessServices.find( - (businessService) => - formValues?.businessServiceName === businessService.name - ); - - const matchingOwner = stakeholders.find( - (stakeholder) => formValues?.owner === stakeholder.name - ); - - const contributors = - formValues.contributors === undefined - ? undefined - : (formValues.contributors - .map((name) => stakeholders.find((s) => s.name === name)) - .map((sh) => - !sh ? undefined : { id: sh.id, name: sh.name } - ) - .filter(Boolean) as Ref[]); + // Note: We need to manually retain the tags with source != "" in the payload + const tags = [...(tagsToRefs(formValues.tags) ?? []), ...nonManualTags]; const payload: Application = { + id: formValues.id, name: formValues.name.trim(), description: formValues.description.trim(), comments: formValues.comments.trim(), - businessService: matchingBusinessService + + businessService: businessServicesToRef(formValues.businessServiceName), + tags, + owner: stakeholdersToRef(formValues.owner), + contributors: stakeholdersToRefs(formValues.contributors), + + repository: formValues.sourceRepository ? { - id: matchingBusinessService.id, - name: matchingBusinessService.name, + kind: formValues.kind.trim(), + url: formValues.sourceRepository.trim(), + branch: formValues.branch.trim(), + path: formValues.rootPath.trim(), } : undefined, - tags: formValues.tags, - owner: matchingOwner - ? { id: matchingOwner.id, name: matchingOwner.name } - : undefined, - contributors, - ...(formValues.sourceRepository - ? { - repository: { - kind: formValues.kind.trim(), - url: formValues.sourceRepository - ? formValues.sourceRepository.trim() - : undefined, - branch: formValues.branch.trim(), - path: formValues.rootPath.trim(), - }, - } - : { repository: undefined }), - binary: buildBinaryFieldString( - formValues.group, - formValues.artifact, - formValues.version, - formValues.packaging - ), - id: formValues.id, - migrationWave: application ? application.migrationWave : null, - identities: application?.identities ? application.identities : undefined, + binary: formValues.packaging + ? `${formValues.group}:${formValues.artifact}:${formValues.version}:${formValues.packaging}` + : `${formValues.group}:${formValues.artifact}:${formValues.version}`, + + // Values not editable on the form but still need to be passed through + identities: application?.identities ?? undefined, + migrationWave: application?.migrationWave ?? null, }; if (application) { @@ -370,9 +307,6 @@ export const ApplicationForm: React.FC = ({ }, ]; - const getTagRef = (tagName: string) => - Object.assign({ source: "" }, tags?.find((tag) => tag.name === tagName)); - return (
= ({ )} /> - + items={allowedManualTags} control={control} name="tags" label={t("terms.tags")} fieldId="tags" - renderInput={({ field: { value, onChange } }) => { - const selections = value.reduce( - (acc, curr) => - curr.source === "" && tagOptions.has(curr.name) - ? [...acc, curr.name] - : acc, - [] - ); - - return ( - { - onChange( - selections - .map((sel) => getTagRef(sel)) - .filter((sel) => sel !== undefined) as TagRef[] - ); - }} - options={Array.from(tagOptions)} - placeholderText={t("composed.selectMany", { - what: t("terms.tags").toLowerCase(), - })} - searchInputAriaLabel="tags-select-toggle" - selections={selections} - /> - ); - }} + noResultsMessage={t("message.noResultsFoundTitle")} + placeholderText={t("composed.selectMany", { + what: t("terms.tags").toLowerCase(), + })} + searchInputAriaLabel="tags-select-toggle" /> + = ({ ); }; + +const useApplicationFormData = ({ + onActionSuccess = () => {}, + onActionFail = () => {}, +}: { + onActionSuccess?: () => void; + onActionFail?: () => void; +}) => { + const { t } = useTranslation(); + const { pushNotification } = React.useContext(NotificationsContext); + + // Fetch data + const { tagCategories } = useFetchTagCategories(); + const tags = useMemo( + () => tagCategories.flatMap((tc) => tc.tags).filter(Boolean) as Tag[], + [tagCategories] + ); + + const { businessServices } = useFetchBusinessServices(); + const { stakeholders } = useFetchStakeholders(); + const { data: existingApplications } = useFetchApplications(); + + // Helpers + const tagsToRefs = (names: string[] | undefined | null) => + matchItemsToRefs(tags, (i) => i.name, names); + + const businessServicesToRef = (name: string | undefined | null) => + matchItemsToRef(businessServices, (i) => i.name, name); + + const stakeholdersToRef = (name: string | undefined | null) => + matchItemsToRef(stakeholders, (i) => i.name, name); + + const stakeholdersToRefs = (names: string[] | undefined | null) => + matchItemsToRefs(stakeholders, (i) => i.name, names); + + // Mutation notification handlers + const onCreateApplicationSuccess = (data: Application) => { + pushNotification({ + title: t("toastr.success.createWhat", { + type: t("terms.application"), + what: data.name, + }), + variant: "success", + }); + onActionSuccess(); + }; + + const onUpdateApplicationSuccess = (payload: Application) => { + pushNotification({ + title: t("toastr.success.saveWhat", { + type: t("terms.application"), + what: payload.name, + }), + variant: "success", + }); + onActionSuccess(); + }; + + const onCreateUpdateApplicationError = (error: AxiosError) => { + pushNotification({ + title: getAxiosErrorMessage(error), + variant: "danger", + }); + onActionFail(); + }; + + // Mutations + const { mutate: createApplication } = useCreateApplicationMutation( + onCreateApplicationSuccess, + onCreateUpdateApplicationError + ); + + const { mutate: updateApplication } = useUpdateApplicationMutation( + onUpdateApplicationSuccess, + onCreateUpdateApplicationError + ); + + // Send back source data and action that are needed by the ApplicationForm + return { + businessServices, + businessServicesToRef, + stakeholders, + stakeholdersToRef, + stakeholdersToRefs, + existingApplications, + tagCategories, + tags, + tagsToRefs, + createApplication, + updateApplication, + }; +}; diff --git a/client/src/app/queries/applications.ts b/client/src/app/queries/applications.ts index b4a403a698..197ab755c0 100644 --- a/client/src/app/queries/applications.ts +++ b/client/src/app/queries/applications.ts @@ -59,14 +59,14 @@ export const useFetchApplicationById = (id?: number | string) => { }; export const useUpdateApplicationMutation = ( - onSuccess: () => void, + onSuccess: (payload: Application) => void, onError: (err: AxiosError) => void ) => { const queryClient = useQueryClient(); return useMutation({ mutationFn: updateApplication, - onSuccess: () => { - onSuccess(); + onSuccess: (_res, payload) => { + onSuccess(payload); queryClient.invalidateQueries([ApplicationsQueryKey]); }, onError: onError, diff --git a/client/src/app/utils/model-utils.tsx b/client/src/app/utils/model-utils.tsx index 03d2d1a6ac..5488be59b6 100644 --- a/client/src/app/utils/model-utils.tsx +++ b/client/src/app/utils/model-utils.tsx @@ -7,6 +7,7 @@ import { IdentityKind, IssueManagerKind, JobFunction, + Ref, Stakeholder, StakeholderGroup, TagCategory, @@ -211,3 +212,70 @@ export const IssueManagerOptions: OptionWithValue[] = [ toString: () => "Jira Server/Datacenter", }, ]; + +/** + * Convert any object that looks like a `Ref` into a `Ref`. If the source object + * is `undefined`, or doesn't look like a `Ref`, return `undefined`. + */ +export const toRef = ( + source: RefLike | undefined +): Ref | undefined => + source?.id && source?.name ? { id: source.id, name: source.name } : undefined; + +/** + * Convert an iterable collection of `Ref`-like objects to a `Ref[]`. Any items in the + * collection that cannot be converted to a `Ref` will be filtered out. + */ +export const toRefs = ( + source: Iterable +): Array | undefined => + !source ? undefined : ([...source].map(toRef).filter(Boolean) as Ref[]); + +/** + * Take an array of source items that look like a `Ref`, find the first one that matches + * a given value, and return it as a `Ref`. If no items match the value, or if the value + * is `undefined` or `null`, then return `undefined`. + * + * @param items Array of source items whose first matching item will be returned as a `Ref` + * @param itemMatchFn Function to extract data from each `item` that will be sent to `matchOperator` + * @param matchValue The single value to match every item against + * @param matchOperator Function to determine if `itemMatchFn` and `matchValue` match + */ +export const matchItemsToRef = ( + items: Array, + itemMatchFn: (item: RefLike) => V, + matchValue: V | undefined | null, + matchOperator?: (a: V, b: V) => boolean +): Ref | undefined => + !matchValue + ? undefined + : matchItemsToRefs(items, itemMatchFn, [matchValue], matchOperator)?.[0] ?? + undefined; + +/** + * Take an array of source items that look like a `Ref`, find the item that matches one + * of a given array of values, and return them all as a `Ref[]`. Any values without a + * match will be filtered out of the resulting `Ref[]`. If the array of values is + * `undefined` or `null`, then return `undefined`. + * + * @param items Array of source items whose first matching item will be returned as a `Ref` + * @param itemMatchFn Function to extract data from each `item` that will be sent to `matchOperator` + * @param matchValues The array of values to match every item against + * @param matchOperator Function to determine if `itemMatchFn` and `matchValue` match + */ +export const matchItemsToRefs = ( + items: Array, + itemMatchFn: (item: RefLike) => V, + matchValues: Array | undefined | null, + matchOperator: (a: V, b: V) => boolean = (a, b) => a === b +): Array | undefined => + !matchValues + ? undefined + : (matchValues + .map((toMatch) => + !toMatch + ? undefined + : items.find((item) => matchOperator(itemMatchFn(item), toMatch)) + ) + .map(toRef) + .filter(Boolean) as Ref[]); From 05f0f8aa970c6bc11dd241041c25eb39f48b4ceb Mon Sep 17 00:00:00 2001 From: Scott J Dickerson Date: Thu, 28 Sep 2023 17:17:41 -0400 Subject: [PATCH 4/4] Fixup naming of *toRef() helper functions Signed-off-by: Scott J Dickerson --- .../application-form/application-form.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/client/src/app/pages/applications/components/application-form/application-form.tsx b/client/src/app/pages/applications/components/application-form/application-form.tsx index 4d2f4a149f..476175bea0 100644 --- a/client/src/app/pages/applications/components/application-form/application-form.tsx +++ b/client/src/app/pages/applications/components/application-form/application-form.tsx @@ -77,9 +77,9 @@ export const ApplicationForm: React.FC = ({ const { existingApplications, businessServices, - businessServicesToRef, + businessServiceToRef, stakeholders, - stakeholdersToRef, + stakeholderToRef, stakeholdersToRefs, tags, tagsToRefs, @@ -263,9 +263,9 @@ export const ApplicationForm: React.FC = ({ description: formValues.description.trim(), comments: formValues.comments.trim(), - businessService: businessServicesToRef(formValues.businessServiceName), + businessService: businessServiceToRef(formValues.businessServiceName), tags, - owner: stakeholdersToRef(formValues.owner), + owner: stakeholderToRef(formValues.owner), contributors: stakeholdersToRefs(formValues.contributors), repository: formValues.sourceRepository @@ -609,10 +609,10 @@ const useApplicationFormData = ({ const tagsToRefs = (names: string[] | undefined | null) => matchItemsToRefs(tags, (i) => i.name, names); - const businessServicesToRef = (name: string | undefined | null) => + const businessServiceToRef = (name: string | undefined | null) => matchItemsToRef(businessServices, (i) => i.name, name); - const stakeholdersToRef = (name: string | undefined | null) => + const stakeholderToRef = (name: string | undefined | null) => matchItemsToRef(stakeholders, (i) => i.name, name); const stakeholdersToRefs = (names: string[] | undefined | null) => @@ -663,9 +663,9 @@ const useApplicationFormData = ({ // Send back source data and action that are needed by the ApplicationForm return { businessServices, - businessServicesToRef, + businessServiceToRef, stakeholders, - stakeholdersToRef, + stakeholderToRef, stakeholdersToRefs, existingApplications, tagCategories,