From 864658dada477dceb0b68aed56313d93e4349313 Mon Sep 17 00:00:00 2001 From: Gilles Dubreuil Date: Thu, 27 Jul 2023 15:55:52 +0200 Subject: [PATCH] :sparkles: BusinessServices: Update Notification and refactors update/create modal and queries (#1210) While resolving the related notification issues (see associated MTA-1024) this also refactors create/update modal and tries to bring more consistency. Includes following changes : - Push edit/create notifications down to BusinessServiceForm - Use onClose to replace onSave and onCancel - Replace REST functions to use axios instead of deprecated APIClient(). - Use React Query mutations instead of legacy queries - Consolidate New/Edit Modal duplication Partially address https://issues.redhat.com/browse/MTA-1024 --- client/src/app/api/models.ts | 2 - client/src/app/api/rest.ts | 54 +++++----- .../business-services/business-services.tsx | 91 +++++++---------- .../business-service-form.tsx | 99 +++++++++++++------ .../components/business-service-form/index.ts | 1 - .../new-business-service-modal/index.ts | 1 - .../new-business-service-modal.tsx | 32 ------ .../update-business-service-modal/index.ts | 1 - .../update-business-service-modal.tsx | 36 ------- client/src/app/queries/businessservices.ts | 60 ++++++++--- 10 files changed, 169 insertions(+), 208 deletions(-) rename client/src/app/pages/controls/business-services/components/{business-service-form => }/business-service-form.tsx (70%) delete mode 100644 client/src/app/pages/controls/business-services/components/business-service-form/index.ts delete mode 100644 client/src/app/pages/controls/business-services/components/new-business-service-modal/index.ts delete mode 100644 client/src/app/pages/controls/business-services/components/new-business-service-modal/new-business-service-modal.tsx delete mode 100644 client/src/app/pages/controls/business-services/components/update-business-service-modal/index.ts delete mode 100644 client/src/app/pages/controls/business-services/components/update-business-service-modal/update-business-service-modal.tsx diff --git a/client/src/app/api/models.ts b/client/src/app/api/models.ts index c00f6f54d..11ee14af4 100644 --- a/client/src/app/api/models.ts +++ b/client/src/app/api/models.ts @@ -1,5 +1,3 @@ -import { BusinessServiceForm } from "@app/pages/controls/business-services/components/business-service-form"; - export type New = Omit; export interface HubFilter { diff --git a/client/src/app/api/rest.ts b/client/src/app/api/rest.ts index 43fc80500..03391f943 100644 --- a/client/src/app/api/rest.ts +++ b/client/src/app/api/rest.ts @@ -135,34 +135,6 @@ const buildQuery = (params: any) => { return query; }; -// Business services - -export const getBusinessServices = (): AxiosPromise> => { - return APIClient.get(`${BUSINESS_SERVICES}`, jsonHeaders); -}; - -export const deleteBusinessService = (id: number | string): AxiosPromise => { - return APIClient.delete(`${BUSINESS_SERVICES}/${id}`); -}; - -export const createBusinessService = ( - obj: New -): AxiosPromise => { - return APIClient.post(`${BUSINESS_SERVICES}`, obj); -}; - -export const updateBusinessService = ( - obj: BusinessService -): AxiosPromise => { - return APIClient.put(`${BUSINESS_SERVICES}/${obj.id}`, obj); -}; - -export const getBusinessServiceById = ( - id: number | string -): AxiosPromise => { - return APIClient.get(`${BUSINESS_SERVICES}/${id}`); -}; - // Job functions export enum JobFunctionSortBy { @@ -214,7 +186,7 @@ export const getApplicationById = ( return APIClient.get(`${APPLICATIONS}/${id}`); }; -// +// Applications Dependencies export const getApplicationDependencies = (): AxiosPromise< ApplicationDependency[] @@ -232,7 +204,7 @@ export const deleteApplicationDependency = (id: number): AxiosPromise => { return APIClient.delete(`${APPLICATION_DEPENDENCY}/${id}`); }; -// +// Reviews export const getReviews = (): AxiosPromise => { return APIClient.get(`${REVIEWS}`); @@ -721,6 +693,28 @@ export const updateStakeholderGroup = ( ): Promise => axios.put(`${STAKEHOLDER_GROUPS}/${obj.id}`, obj); +// Business services + +export const getBusinessServices = (): Promise => + axios.get(BUSINESS_SERVICES).then((response) => response.data); + +export const deleteBusinessService = ( + id: number | string +): Promise => axios.delete(`${BUSINESS_SERVICES}/${id}`); + +export const createBusinessService = ( + obj: New +): Promise => axios.post(BUSINESS_SERVICES, obj); + +export const updateBusinessService = ( + obj: BusinessService +): Promise => axios.put(`${BUSINESS_SERVICES}/${obj.id}`, obj); + +export const getBusinessServiceById = ( + id: number | string +): Promise => + axios.get(`${BUSINESS_SERVICES}/${id}`).then((response) => response.data); + // Tags export const getTags = (): Promise => diff --git a/client/src/app/pages/controls/business-services/business-services.tsx b/client/src/app/pages/controls/business-services/business-services.tsx index cdba48236..d6a02e42f 100644 --- a/client/src/app/pages/controls/business-services/business-services.tsx +++ b/client/src/app/pages/controls/business-services/business-services.tsx @@ -1,10 +1,10 @@ -import React, { useState } from "react"; -import { AxiosError, AxiosResponse } from "axios"; +import React from "react"; +import { AxiosError } from "axios"; import { useTranslation } from "react-i18next"; - import { Button, ButtonVariant, + Modal, ToolbarGroup, ToolbarItem, } from "@patternfly/react-core"; @@ -24,12 +24,9 @@ import { NoDataEmptyState, ConfirmDialog, } from "@app/shared/components"; - import { BusinessService } from "@app/api/models"; import { getAxiosErrorMessage } from "@app/utils/utils"; - -import { NewBusinessServiceModal } from "./components/new-business-service-modal"; -import { UpdateBusinessServiceModal } from "./components/update-business-service-modal"; +import { BusinessServiceForm } from "./components/business-service-form"; import { useLegacyPaginationState } from "@app/shared/hooks/useLegacyPaginationState"; import { FilterCategory, @@ -50,6 +47,7 @@ const ENTITY_FIELD = "entity"; export const BusinessServices: React.FC = () => { const { t } = useTranslation(); + const { pushNotification } = React.useContext(NotificationsContext); const [isConfirmDialogOpen, setIsConfirmDialogOpen] = React.useState(false); @@ -57,10 +55,12 @@ export const BusinessServices: React.FC = () => { const [businessServiceIdToDelete, setBusinessServiceIdToDelete] = React.useState(); - const { pushNotification } = React.useContext(NotificationsContext); - - const [isNewModalOpen, setIsNewModalOpen] = useState(false); - const [rowToUpdate, setRowToUpdate] = useState(); + const [createUpdateModalState, setCreateUpdateModalState] = React.useState< + "create" | BusinessService | null + >(null); + const isCreateUpdateModalOpen = createUpdateModalState !== null; + const businessServiceToUpdate = + createUpdateModalState !== "create" ? createUpdateModalState : null; const onDeleteBusinessServiceSuccess = (response: any) => { pushNotification({ @@ -185,7 +185,7 @@ export const BusinessServices: React.FC = () => { editRow(item)} + onEdit={() => setCreateUpdateModalState(item)} onDelete={() => deleteRow(item)} /> ), @@ -194,10 +194,6 @@ export const BusinessServices: React.FC = () => { }); }); - const editRow = (row: BusinessService) => { - setRowToUpdate(row); - }; - const deleteRow = (row: BusinessService) => { setBusinessServiceIdToDelete(row.id); setIsConfirmDialogOpen(true); @@ -209,39 +205,11 @@ export const BusinessServices: React.FC = () => { setFilterValues({}); }; - // Create Modal - - const handleOnOpenCreateNewBusinessServiceModal = () => { - setIsNewModalOpen(true); - }; - - const handleOnBusinessServiceCreated = ( - response: AxiosResponse - ) => { - setIsNewModalOpen(false); - refetch(); - pushNotification({ - title: t("toastr.success.saveWhat", { - what: response.data.name, - type: t("terms.businessService"), - }), - variant: "success", - }); - }; - - const handleOnCancelCreateBusinessService = () => { - setIsNewModalOpen(false); - }; - // Update Modal - const handleOnBusinessServiceUpdated = () => { - setRowToUpdate(undefined); - refetch(); - }; - - const handleOnCancelUpdateBusinessService = () => { - setRowToUpdate(undefined); + const closeCreateUpdateModal = () => { + setCreateUpdateModalState(null); + refetch; }; return ( @@ -281,7 +249,7 @@ export const BusinessServices: React.FC = () => { id="create-business-service" aria-label="Create business service" variant={ButtonVariant.primary} - onClick={handleOnOpenCreateNewBusinessServiceModal} + onClick={() => setCreateUpdateModalState("create")} > {t("actions.createNew")} @@ -304,16 +272,23 @@ export const BusinessServices: React.FC = () => { /> - - + + + {isConfirmDialogOpen && ( ) => void; - onCancel: () => void; + businessService: BusinessService | null; + onClose: () => void; } export const BusinessServiceForm: React.FC = ({ businessService, - onSaved, - onCancel, + onClose, }) => { const { t } = useTranslation(); - - const [error, setError] = useState(); + const { pushNotification } = React.useContext(NotificationsContext); const { businessServices } = useFetchBusinessServices(); - const { stakeholders } = useFetchStakeholders(); const stakeholdersOptions = stakeholders.map((stakeholder) => { @@ -94,6 +92,57 @@ export const BusinessServiceForm: React.FC = ({ mode: "onChange", }); + const onCreateBusinessServiceSuccess = ( + response: AxiosResponse + ) => { + pushNotification({ + title: t("toastr.success.createWhat", { + type: t("terms.businessService"), + what: response.data.name, + }), + variant: "success", + }); + onClose(); + }; + + const onUpdateBusinessServiceSuccess = () => { + pushNotification({ + title: t("toastr.success.save", { + type: t("terms.businessService"), + }), + variant: "success", + }); + onClose(); + }; + + const onCreateBusinessServiceError = (error: AxiosError) => { + pushNotification({ + title: t("toastr.fail.create", { + type: t("terms.businessService").toLowerCase(), + }), + variant: "danger", + }); + }; + + const { mutate: createBusinessService } = useCreateBusinessServiceMutation( + onCreateBusinessServiceSuccess, + onCreateBusinessServiceError + ); + + const onUpdateBusinessServiceError = (error: AxiosError) => { + pushNotification({ + title: t("toastr.fail.save", { + type: t("terms.businessService").toLowerCase(), + }), + variant: "danger", + }); + }; + + const { mutate: updateBusinessService } = useUpdateBusinessServiceMutation( + onUpdateBusinessServiceSuccess, + onUpdateBusinessServiceError + ); + const onSubmit = (formValues: FormValues) => { const matchingStakeholderRef = stakeholders.find( (stakeholder) => stakeholder.name === formValues.owner @@ -104,30 +153,16 @@ export const BusinessServiceForm: React.FC = ({ owner: matchingStakeholderRef, }; - let promise: AxiosPromise; if (businessService) { - promise = updateBusinessService({ - ...businessService, - ...payload, - }); + updateBusinessService({ id: businessService.id, ...payload }); } else { - promise = createBusinessService(payload); + createBusinessService(payload); } - - promise - .then((response) => { - onSaved(response); - }) - .catch((error) => { - setError(error); - }); + onClose(); }; return (
- {error && ( - - )} = ({ aria-label="cancel" variant={ButtonVariant.link} isDisabled={isSubmitting || isValidating} - onClick={onCancel} + onClick={onClose} > {t("actions.cancel")} diff --git a/client/src/app/pages/controls/business-services/components/business-service-form/index.ts b/client/src/app/pages/controls/business-services/components/business-service-form/index.ts deleted file mode 100644 index d49c8a450..000000000 --- a/client/src/app/pages/controls/business-services/components/business-service-form/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { BusinessServiceForm } from "./business-service-form"; diff --git a/client/src/app/pages/controls/business-services/components/new-business-service-modal/index.ts b/client/src/app/pages/controls/business-services/components/new-business-service-modal/index.ts deleted file mode 100644 index 449dd7264..000000000 --- a/client/src/app/pages/controls/business-services/components/new-business-service-modal/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { NewBusinessServiceModal } from "./new-business-service-modal"; diff --git a/client/src/app/pages/controls/business-services/components/new-business-service-modal/new-business-service-modal.tsx b/client/src/app/pages/controls/business-services/components/new-business-service-modal/new-business-service-modal.tsx deleted file mode 100644 index e01c16b4b..000000000 --- a/client/src/app/pages/controls/business-services/components/new-business-service-modal/new-business-service-modal.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import React from "react"; -import { AxiosResponse } from "axios"; -import { useTranslation } from "react-i18next"; - -import { Modal, ModalVariant } from "@patternfly/react-core"; - -import { BusinessService } from "@app/api/models"; - -import { BusinessServiceForm } from "../business-service-form"; - -export interface NewBusinessServiceModalProps { - isOpen: boolean; - onSaved: (response: AxiosResponse) => void; - onCancel: () => void; -} - -export const NewBusinessServiceModal: React.FC< - NewBusinessServiceModalProps -> = ({ isOpen, onSaved, onCancel }) => { - const { t } = useTranslation(); - - return ( - - - - ); -}; diff --git a/client/src/app/pages/controls/business-services/components/update-business-service-modal/index.ts b/client/src/app/pages/controls/business-services/components/update-business-service-modal/index.ts deleted file mode 100644 index 4f3983d90..000000000 --- a/client/src/app/pages/controls/business-services/components/update-business-service-modal/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { UpdateBusinessServiceModal } from "./update-business-service-modal"; diff --git a/client/src/app/pages/controls/business-services/components/update-business-service-modal/update-business-service-modal.tsx b/client/src/app/pages/controls/business-services/components/update-business-service-modal/update-business-service-modal.tsx deleted file mode 100644 index 8d24956d1..000000000 --- a/client/src/app/pages/controls/business-services/components/update-business-service-modal/update-business-service-modal.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React from "react"; -import { AxiosResponse } from "axios"; -import { useTranslation } from "react-i18next"; - -import { Modal, ModalVariant } from "@patternfly/react-core"; - -import { BusinessService } from "@app/api/models"; - -import { BusinessServiceForm } from "../business-service-form"; - -export interface UpdateBusinessServiceModalProps { - businessService?: BusinessService; - onSaved: (response: AxiosResponse) => void; - onCancel: () => void; -} - -export const UpdateBusinessServiceModal: React.FC< - UpdateBusinessServiceModalProps -> = ({ businessService, onSaved, onCancel }) => { - const { t } = useTranslation(); - - return ( - - - - ); -}; diff --git a/client/src/app/queries/businessservices.ts b/client/src/app/queries/businessservices.ts index d135bbfbb..772ee682c 100644 --- a/client/src/app/queries/businessservices.ts +++ b/client/src/app/queries/businessservices.ts @@ -1,24 +1,23 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { AxiosError } from "axios"; import { + createBusinessService, deleteBusinessService, getBusinessServiceById, getBusinessServices, + updateBusinessService, } from "@app/api/rest"; -import { BusinessService } from "@app/api/models"; -import { AxiosError } from "axios"; export const BusinessServicesQueryKey = "businessservices"; export const BusinessServiceQueryKey = "businessservice"; export const useFetchBusinessServices = () => { - const { data, isLoading, error, refetch } = useQuery( - [BusinessServicesQueryKey], - async () => (await getBusinessServices()).data, - { - onError: (error) => console.log("error, ", error), - } - ); + const { data, isLoading, error, refetch } = useQuery({ + queryKey: [BusinessServicesQueryKey], + queryFn: getBusinessServices, + onError: (error: AxiosError) => console.log("error, ", error), + }); return { businessServices: data || [], isFetching: isLoading, @@ -26,13 +25,13 @@ export const useFetchBusinessServices = () => { refetch, }; }; -export const useFetchBusinessServiceByID = (id: number | string) => { - const { data, isLoading, error } = useQuery( - [BusinessServicesQueryKey, id], - async () => (await getBusinessServiceById(id)).data, - { onError: (error) => console.log(error) } - ); +export const useFetchBusinessServiceByID = (id: number | string) => { + const { data, isLoading, error } = useQuery({ + queryKey: [BusinessServicesQueryKey, id], + queryFn: () => getBusinessServiceById(id), + onError: (error: AxiosError) => console.log("error, ", error), + }); return { businessService: data, isFetching: isLoading, @@ -40,6 +39,37 @@ export const useFetchBusinessServiceByID = (id: number | string) => { }; }; +export const useCreateBusinessServiceMutation = ( + onSuccess: (res: any) => void, + onError: (err: AxiosError) => void +) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: createBusinessService, + onSuccess: (res) => { + onSuccess(res); + queryClient.invalidateQueries([BusinessServicesQueryKey]); + }, + onError, + }); +}; + +export const useUpdateBusinessServiceMutation = ( + onSuccess: () => void, + onError: (err: AxiosError) => void +) => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: updateBusinessService, + onSuccess: () => { + onSuccess(); + queryClient.invalidateQueries([BusinessServicesQueryKey]); + }, + onError: onError, + }); +}; + export const useDeleteBusinessServiceMutation = ( onSuccess: (res: any) => void, onError: (err: AxiosError) => void