Skip to content

Commit

Permalink
🐛 Business services: Fix create/edit when owner is included (#1418)
Browse files Browse the repository at this point in the history
The `owner` field on the `BusinessService` 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 application-form to handle getting
the correct set of data.

Related changes:
- Business services related REST API functions updated to have the
correct response types

- Business services queries updated to pass REST API response and input
values to `onSuccess()` and `onError()` handlers

- `BusinessServiceForm` updated to use mutation response data to display
the name of the business service in success messages

- Refactored `business-service-form.tsx` to move all data
access/mutation code to hook `useApplicationFormData() to logically
divide concerns (data access v. UI handling)

Resolves: https://issues.redhat.com/browse/MTA-1346

Signed-off-by: Scott J Dickerson <[email protected]>
  • Loading branch information
sjd78 authored Sep 29, 2023
1 parent 76a2645 commit 8130ba2
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 92 deletions.
2 changes: 1 addition & 1 deletion client/src/app/api/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export interface BusinessService {
id: number;
name: string;
description?: string;
owner?: Stakeholder;
owner?: Ref;
}

export interface Stakeholder {
Expand Down
32 changes: 16 additions & 16 deletions client/src/app/api/rest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -618,27 +618,27 @@ export const updateStakeholderGroup = (
): Promise<StakeholderGroup> =>
axios.put(`${STAKEHOLDER_GROUPS}/${obj.id}`, obj);

// ---------------------------------------
// Business services
//
export const getBusinessServices = () =>
axios
.get<BusinessService[]>(BUSINESS_SERVICES)
.then((response) => response.data);

export const getBusinessServices = (): Promise<BusinessService[]> =>
axios.get(BUSINESS_SERVICES).then((response) => response.data);

export const deleteBusinessService = (
id: number | string
): Promise<BusinessService> => axios.delete(`${BUSINESS_SERVICES}/${id}`);
export const getBusinessServiceById = (id: number | string) =>
axios
.get<BusinessService>(`${BUSINESS_SERVICES}/${id}`)
.then((response) => response.data);

export const createBusinessService = (
obj: New<BusinessService>
): Promise<BusinessService> => axios.post(BUSINESS_SERVICES, obj);
export const createBusinessService = (obj: New<BusinessService>) =>
axios.post<BusinessService>(BUSINESS_SERVICES, obj);

export const updateBusinessService = (
obj: BusinessService
): Promise<BusinessService> => axios.put(`${BUSINESS_SERVICES}/${obj.id}`, obj);
export const updateBusinessService = (obj: BusinessService) =>
axios.put<void>(`${BUSINESS_SERVICES}/${obj.id}`, obj);

export const getBusinessServiceById = (
id: number | string
): Promise<BusinessService> =>
axios.get(`${BUSINESS_SERVICES}/${id}`).then((response) => response.data);
export const deleteBusinessService = (id: number | string) =>
axios.delete<void>(`${BUSINESS_SERVICES}/${id}`);

// Job functions

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from "react";
import { useTranslation } from "react-i18next";
import { AxiosError, AxiosResponse } from "axios";
import { AxiosError } from "axios";
import { object, string } from "yup";

import {
Expand All @@ -27,6 +27,7 @@ import {
} from "@app/components/HookFormPFFields";
import { OptionWithValue, SimpleSelect } from "@app/components/SimpleSelect";
import { NotificationsContext } from "@app/components/NotificationsContext";
import { matchItemsToRef } from "@app/utils/model-utils";

export interface FormValues {
name: string;
Expand All @@ -44,10 +45,16 @@ export const BusinessServiceForm: React.FC<BusinessServiceFormProps> = ({
onClose,
}) => {
const { t } = useTranslation();
const { pushNotification } = React.useContext(NotificationsContext);

const { businessServices } = useFetchBusinessServices();
const { stakeholders } = useFetchStakeholders();
const {
businessServices,
stakeholders,
stakeholderToRef,
createBusinessService,
updateBusinessService,
} = useBusinessServiceFormData({
onActionSuccess: onClose,
});

const stakeholdersOptions = stakeholders.map((stakeholder) => {
return {
Expand Down Expand Up @@ -92,73 +99,18 @@ export const BusinessServiceForm: React.FC<BusinessServiceFormProps> = ({
mode: "all",
});

const onCreateBusinessServiceSuccess = (
response: AxiosResponse<BusinessService>
) => {
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
);
const payload: New<BusinessService> = {
name: formValues.name.trim(),
description: formValues.description.trim(),
owner: matchingStakeholderRef,
owner: stakeholderToRef(formValues.owner),
};

if (businessService) {
updateBusinessService({ id: businessService.id, ...payload });
} else {
createBusinessService(payload);
}
onClose();
};

return (
Expand Down Expand Up @@ -223,3 +175,91 @@ export const BusinessServiceForm: React.FC<BusinessServiceFormProps> = ({
</Form>
);
};

const useBusinessServiceFormData = ({
onActionSuccess = () => {},
onActionFail = () => {},
}: {
onActionSuccess?: () => void;
onActionFail?: () => void;
}) => {
const { t } = useTranslation();
const { pushNotification } = React.useContext(NotificationsContext);

// Fetch data
const { businessServices } = useFetchBusinessServices();
const { stakeholders } = useFetchStakeholders();

// Helpers
const stakeholderToRef = (name: string | undefined | null) =>
matchItemsToRef(stakeholders, (i) => i.name, name);

// Mutation notification handlers
const onCreateBusinessServiceSuccess = (data: BusinessService) => {
pushNotification({
title: t("toastr.success.createWhat", {
type: t("terms.businessService"),
what: data.name,
}),
variant: "success",
});
onActionSuccess();
};

const onCreateBusinessServiceError = (
_error: AxiosError,
_payload: New<BusinessService>
) => {
pushNotification({
title: t("toastr.fail.create", {
type: t("terms.businessService").toLowerCase(),
}),
variant: "danger",
});
onActionFail();
};

const onUpdateBusinessServiceSuccess = (payload: BusinessService) => {
pushNotification({
title: t("toastr.success.saveWhat", {
type: t("terms.businessService"),
what: payload.name,
}),
variant: "success",
});
onActionSuccess();
};

const onUpdateBusinessServiceError = (
_error: AxiosError,
_payload: New<BusinessService>
) => {
pushNotification({
title: t("toastr.fail.save", {
type: t("terms.businessService").toLowerCase(),
}),
variant: "danger",
});
onActionFail();
};

// Mutations
const { mutate: createBusinessService } = useCreateBusinessServiceMutation(
onCreateBusinessServiceSuccess,
onCreateBusinessServiceError
);

const { mutate: updateBusinessService } = useUpdateBusinessServiceMutation(
onUpdateBusinessServiceSuccess,
onUpdateBusinessServiceError
);

// Send back source data and action that are needed by the ApplicationForm
return {
businessServices,
stakeholders,
stakeholderToRef,
createBusinessService,
updateBusinessService,
};
};
32 changes: 17 additions & 15 deletions client/src/app/queries/businessservices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
getBusinessServices,
updateBusinessService,
} from "@app/api/rest";
import { BusinessService, New } from "@app/api/models";

export const BusinessServicesQueryKey = "businessservices";
export const BusinessServiceQueryKey = "businessservice";
Expand Down Expand Up @@ -40,49 +41,50 @@ export const useFetchBusinessServiceByID = (id: number | string) => {
};

export const useCreateBusinessServiceMutation = (
onSuccess: (res: any) => void,
onError: (err: AxiosError) => void
onSuccess: (res: BusinessService) => void,
onError: (err: AxiosError, payload: New<BusinessService>) => void
) => {
const queryClient = useQueryClient();

return useMutation({
mutationFn: createBusinessService,
onSuccess: (res) => {
onSuccess(res);
onSuccess: ({ data }, _payload) => {
onSuccess(data);
queryClient.invalidateQueries([BusinessServicesQueryKey]);
},
onError,
});
};

export const useUpdateBusinessServiceMutation = (
onSuccess: () => void,
onError: (err: AxiosError) => void
onSuccess: (payload: BusinessService) => void,
onError: (err: AxiosError, payload: BusinessService) => void
) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: updateBusinessService,
onSuccess: () => {
onSuccess();
onSuccess: (_res, payload) => {
onSuccess(payload);
queryClient.invalidateQueries([BusinessServicesQueryKey]);
},
onError: onError,
});
};

export const useDeleteBusinessServiceMutation = (
onSuccess: (res: any) => void,
onError: (err: AxiosError) => void
onSuccess: (id: number | string) => void,
onError: (err: AxiosError, id: number | string) => void
) => {
const queryClient = useQueryClient();

const { isLoading, mutate, error } = useMutation(deleteBusinessService, {
onSuccess: (res) => {
onSuccess(res);
const { isLoading, mutate, error } = useMutation({
mutationFn: deleteBusinessService,
onSuccess: (_res, id) => {
onSuccess(id);
queryClient.invalidateQueries([BusinessServicesQueryKey]);
},
onError: (err: AxiosError) => {
onError(err);
onError: (err: AxiosError, id) => {
onError(err, id);
queryClient.invalidateQueries([BusinessServicesQueryKey]);
},
});
Expand Down

0 comments on commit 8130ba2

Please sign in to comment.