From fae624bdf0a3381bd83ccbe84a5fea193d57f965 Mon Sep 17 00:00:00 2001 From: Ian Bolton Date: Thu, 5 Oct 2023 16:37:24 -0400 Subject: [PATCH] :bug: Shows client side loading state for jira tracker (#1409) Closes [False 'not connected' status for new Jira instance](https://issues.redhat.com/browse/MTA-1019) --------- Signed-off-by: ibolton336 --- .../jira/components/tracker-status.tsx | 21 +++++++--- .../app/pages/external/jira/tracker-form.tsx | 36 +++++++++------- .../src/app/pages/external/jira/trackers.tsx | 7 ++++ .../external/jira/useUpdatingTrackerIds.ts | 42 +++++++++++++++++++ client/src/app/queries/trackers.ts | 6 +-- 5 files changed, 89 insertions(+), 23 deletions(-) create mode 100644 client/src/app/pages/external/jira/useUpdatingTrackerIds.ts diff --git a/client/src/app/pages/external/jira/components/tracker-status.tsx b/client/src/app/pages/external/jira/components/tracker-status.tsx index 28da0fdf29..ec94cb1153 100644 --- a/client/src/app/pages/external/jira/components/tracker-status.tsx +++ b/client/src/app/pages/external/jira/components/tracker-status.tsx @@ -1,5 +1,5 @@ +import "./tracker-status.css"; import React, { useState } from "react"; - import spacing from "@patternfly/react-styles/css/utilities/Spacing/spacing"; import { useTranslation } from "react-i18next"; import { @@ -10,18 +10,23 @@ import { Popover, Text, TextContent, + Spinner, } from "@patternfly/react-core"; import ExclamationCircleIcon from "@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon"; - import { IconedStatus } from "@app/components/IconedStatus"; -import "./tracker-status.css"; interface ITrackerStatusProps { name: string; connected: boolean; message: string; + isTrackerUpdating?: boolean; } -const TrackerStatus = ({ name, connected, message }: ITrackerStatusProps) => { +const TrackerStatus = ({ + name, + connected, + message, + isTrackerUpdating, +}: ITrackerStatusProps) => { const { t } = useTranslation(); const [isExpanded, setIsExpanded] = useState(false); @@ -29,7 +34,9 @@ const TrackerStatus = ({ name, connected, message }: ITrackerStatusProps) => { const messageFirst = message.slice(0, 300); const messageRest = message.slice(300); - return ( + return isTrackerUpdating ? ( + + ) : ( <> { aria-label="More information about no connection" alertSeverityVariant="danger" headerIcon={} - headerContent={t("composed.error", { what: t("terms.instance") })} + headerContent={t("composed.error", { + what: t("terms.instance"), + })} hasAutoWidth onHidden={() => setIsExpanded(false)} bodyContent={ diff --git a/client/src/app/pages/external/jira/tracker-form.tsx b/client/src/app/pages/external/jira/tracker-form.tsx index bc7dcd355d..fd161dd747 100644 --- a/client/src/app/pages/external/jira/tracker-form.tsx +++ b/client/src/app/pages/external/jira/tracker-form.tsx @@ -57,25 +57,26 @@ interface FormValues { } export interface TrackerFormProps { - tracker?: Tracker; onClose: () => void; + addUpdatingTrackerId: (id: number) => void; + tracker?: Tracker; } export const TrackerForm: React.FC = ({ tracker, onClose, + addUpdatingTrackerId, }) => { const { t } = useTranslation(); const [axiosError, setAxiosError] = useState(); - const [isLoading, setIsLoading] = useState(false); const { trackers: trackers } = useFetchTrackers(); const { identities } = useFetchIdentities(); const { pushNotification } = React.useContext(NotificationsContext); - const onCreateTrackerSuccess = (_: AxiosResponse) => + const onCreateTrackerSuccess = (_: AxiosResponse) => { pushNotification({ title: t("toastr.success.save", { type: t("terms.instance"), @@ -83,16 +84,13 @@ export const TrackerForm: React.FC = ({ variant: "success", }); - const onCreateUpdatetrackerError = (error: AxiosError) => { - setAxiosError(error); + addUpdatingTrackerId(_.data.id); }; - const { mutate: createTracker } = useCreateTrackerMutation( - onCreateTrackerSuccess, - onCreateUpdatetrackerError - ); - - const onUpdateTrackerSuccess = (_: AxiosResponse) => + const onUpdateTrackerSuccess = ( + _: AxiosResponse, + tracker: Tracker + ) => { pushNotification({ title: t("toastr.success.save", { type: t("terms.instance"), @@ -100,6 +98,18 @@ export const TrackerForm: React.FC = ({ variant: "success", }); + addUpdatingTrackerId(tracker.id); + }; + + const onCreateUpdatetrackerError = (error: AxiosError) => { + setAxiosError(error); + }; + + const { mutate: createTracker } = useCreateTrackerMutation( + onCreateTrackerSuccess, + onCreateUpdatetrackerError + ); + const { mutate: updateTracker } = useUpdateTrackerMutation( onUpdateTrackerSuccess, onCreateUpdatetrackerError @@ -311,9 +321,7 @@ export const TrackerForm: React.FC = ({ aria-label="submit" id="submit" variant={ButtonVariant.primary} - isDisabled={ - !isValid || isSubmitting || isValidating || isLoading || !isDirty - } + isDisabled={!isValid || isSubmitting || isValidating || !isDirty} > {!tracker ? "Create" : "Save"} diff --git a/client/src/app/pages/external/jira/trackers.tsx b/client/src/app/pages/external/jira/trackers.tsx index be35e04c48..cdde160ab2 100644 --- a/client/src/app/pages/external/jira/trackers.tsx +++ b/client/src/app/pages/external/jira/trackers.tsx @@ -44,6 +44,7 @@ import { ConditionalRender } from "@app/components/ConditionalRender"; import { AppPlaceholder } from "@app/components/AppPlaceholder"; import { ConfirmDialog } from "@app/components/ConfirmDialog"; import { AppTableActionButtons } from "@app/components/AppTableActionButtons"; +import useUpdatingTrackerIds from "./useUpdatingTrackerIds"; export const JiraTrackers: React.FC = () => { const { t } = useTranslation(); @@ -147,6 +148,8 @@ export const JiraTrackers: React.FC = () => { }, } = tableControls; + const [updatingTrackerIds, addUpdatingTrackerId] = useUpdatingTrackerIds(); + return ( <> @@ -263,6 +266,9 @@ export const JiraTrackers: React.FC = () => { name={tracker.name} connected={tracker.connected} message={tracker.message} + isTrackerUpdating={updatingTrackerIds.has( + tracker.id + )} /> @@ -307,6 +313,7 @@ export const JiraTrackers: React.FC = () => { > setTrackerModalState(null)} /> diff --git a/client/src/app/pages/external/jira/useUpdatingTrackerIds.ts b/client/src/app/pages/external/jira/useUpdatingTrackerIds.ts new file mode 100644 index 0000000000..7f0eb3331a --- /dev/null +++ b/client/src/app/pages/external/jira/useUpdatingTrackerIds.ts @@ -0,0 +1,42 @@ +import { useState } from "react"; +import dayjs from "dayjs"; + +export type UpdatableId = { + id: number; + expirationTime: dayjs.ConfigType; +}; + +const useUpdatingTrackerIds = () => { + const [updatingTrackerIds, setUpdatingTrackerIds] = useState< + Map + >(new Map()); + + const addUpdatingTrackerId = (id: number) => { + const now = dayjs(); + const existingExpiry = updatingTrackerIds.get(id); + + if (!existingExpiry || existingExpiry.isBefore(now)) { + const expiryDate = dayjs().add(8, "seconds"); + + setUpdatingTrackerIds((prevMap) => { + const updatedMap = new Map(prevMap); + updatedMap.set(id, expiryDate); + return updatedMap; + }); + + const timeRemaining = expiryDate.diff(now); + + setTimeout(() => { + setUpdatingTrackerIds((prevMap) => { + const updatedMap = new Map(prevMap); + updatedMap.delete(id); + return updatedMap; + }); + }, timeRemaining); + } + }; + + return [updatingTrackerIds, addUpdatingTrackerId] as const; +}; + +export default useUpdatingTrackerIds; diff --git a/client/src/app/queries/trackers.ts b/client/src/app/queries/trackers.ts index a0a05cffb2..c7706ff3fc 100644 --- a/client/src/app/queries/trackers.ts +++ b/client/src/app/queries/trackers.ts @@ -49,14 +49,14 @@ export const useCreateTrackerMutation = ( }; export const useUpdateTrackerMutation = ( - onSuccess: (res: any) => void, + onSuccess: (res: any, tracker: Tracker) => void, onError: (err: AxiosError) => void ) => { const queryClient = useQueryClient(); return useMutation({ mutationFn: updateTracker, - onSuccess: (res) => { - onSuccess(res); + onSuccess: (res, tracker) => { + onSuccess(res, tracker); queryClient.invalidateQueries([TrackersQueryKey]); }, onError: onError,