From c3c60dfd797b20c68353f2ffa931c3bc18455a46 Mon Sep 17 00:00:00 2001 From: ibolton336 Date: Wed, 27 Sep 2023 14:40:05 -0400 Subject: [PATCH 1/3] :bug: Shows client side loading state to allow backend time to connect to instance Signed-off-by: ibolton336 --- .../app/pages/external/jira/tracker-form.tsx | 47 +++++++++++++------ .../src/app/pages/external/jira/trackers.tsx | 22 +++++++-- .../external/jira/useUpdatingTrackerId.ts | 27 +++++++++++ client/src/app/queries/trackers.ts | 6 +-- 4 files changed, 80 insertions(+), 22 deletions(-) create mode 100644 client/src/app/pages/external/jira/useUpdatingTrackerId.ts diff --git a/client/src/app/pages/external/jira/tracker-form.tsx b/client/src/app/pages/external/jira/tracker-form.tsx index bc7dcd355d..e17eedec44 100644 --- a/client/src/app/pages/external/jira/tracker-form.tsx +++ b/client/src/app/pages/external/jira/tracker-form.tsx @@ -57,25 +57,28 @@ interface FormValues { } export interface TrackerFormProps { - tracker?: Tracker; onClose: () => void; + setUpdatingTrackerId: React.Dispatch< + React.SetStateAction + >; + tracker?: Tracker; } export const TrackerForm: React.FC = ({ tracker, onClose, + setUpdatingTrackerId, }) => { 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 +86,17 @@ export const TrackerForm: React.FC = ({ variant: "success", }); - const onCreateUpdatetrackerError = (error: AxiosError) => { - setAxiosError(error); - }; + setUpdatingTrackerId(_.data.id); - const { mutate: createTracker } = useCreateTrackerMutation( - onCreateTrackerSuccess, - onCreateUpdatetrackerError - ); + setTimeout(() => { + setUpdatingTrackerId(null); + }, 5000); + }; - const onUpdateTrackerSuccess = (_: AxiosResponse) => + const onUpdateTrackerSuccess = ( + _: AxiosResponse, + tracker: Tracker + ) => { pushNotification({ title: t("toastr.success.save", { type: t("terms.instance"), @@ -100,6 +104,23 @@ export const TrackerForm: React.FC = ({ variant: "success", }); + setUpdatingTrackerId(tracker.id); + + setTimeout(() => { + setUpdatingTrackerId(null); + }, 5000); + }; + + const onCreateUpdatetrackerError = (error: AxiosError) => { + setAxiosError(error); + setUpdatingTrackerId(null); + }; + + const { mutate: createTracker } = useCreateTrackerMutation( + onCreateTrackerSuccess, + onCreateUpdatetrackerError + ); + const { mutate: updateTracker } = useUpdateTrackerMutation( onUpdateTrackerSuccess, onCreateUpdatetrackerError @@ -311,9 +332,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..1db6254be5 100644 --- a/client/src/app/pages/external/jira/trackers.tsx +++ b/client/src/app/pages/external/jira/trackers.tsx @@ -8,6 +8,7 @@ import { Modal, PageSection, PageSectionVariants, + Spinner, Text, TextContent, Title, @@ -44,6 +45,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 useUpdatingTrackerId from "./useUpdatingTrackerId"; export const JiraTrackers: React.FC = () => { const { t } = useTranslation(); @@ -147,6 +149,11 @@ export const JiraTrackers: React.FC = () => { }, } = tableControls; + //Handle tracker update temporary loading state + const [updatingTrackerId, setUpdatingTrackerId] = useUpdatingTrackerId(10000); + + // + return ( <> @@ -259,11 +266,15 @@ export const JiraTrackers: React.FC = () => { width={10} {...getTdProps({ columnKey: "connection" })} > - + {updatingTrackerId === tracker.id ? ( + + ) : ( + + )} { > setTrackerModalState(null)} /> diff --git a/client/src/app/pages/external/jira/useUpdatingTrackerId.ts b/client/src/app/pages/external/jira/useUpdatingTrackerId.ts new file mode 100644 index 0000000000..5ed1159506 --- /dev/null +++ b/client/src/app/pages/external/jira/useUpdatingTrackerId.ts @@ -0,0 +1,27 @@ +import { useState, useEffect } from "react"; + +type UpdatableId = string | number | null; + +const useUpdatingTrackerId = (delay: number = 5000) => { + const [updatingTrackerId, setUpdatingTrackerId] = useState(null); + + useEffect(() => { + let timerId: number | null = null; + + if (updatingTrackerId !== null) { + timerId = window.setTimeout(() => { + setUpdatingTrackerId(null); + }, delay); + } + + return () => { + if (timerId !== null) { + window.clearTimeout(timerId); + } + }; + }, [updatingTrackerId, delay]); + + return [updatingTrackerId, setUpdatingTrackerId] as const; +}; + +export default useUpdatingTrackerId; 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, From 41d47c3ffb0f617382126b3e47d593a4916b76d9 Mon Sep 17 00:00:00 2001 From: ibolton336 Date: Mon, 2 Oct 2023 13:22:58 -0400 Subject: [PATCH 2/3] Remove redundant calls Signed-off-by: ibolton336 --- client/src/app/pages/external/jira/tracker-form.tsx | 9 --------- 1 file changed, 9 deletions(-) diff --git a/client/src/app/pages/external/jira/tracker-form.tsx b/client/src/app/pages/external/jira/tracker-form.tsx index e17eedec44..bd90ca0c3a 100644 --- a/client/src/app/pages/external/jira/tracker-form.tsx +++ b/client/src/app/pages/external/jira/tracker-form.tsx @@ -87,10 +87,6 @@ export const TrackerForm: React.FC = ({ }); setUpdatingTrackerId(_.data.id); - - setTimeout(() => { - setUpdatingTrackerId(null); - }, 5000); }; const onUpdateTrackerSuccess = ( @@ -105,15 +101,10 @@ export const TrackerForm: React.FC = ({ }); setUpdatingTrackerId(tracker.id); - - setTimeout(() => { - setUpdatingTrackerId(null); - }, 5000); }; const onCreateUpdatetrackerError = (error: AxiosError) => { setAxiosError(error); - setUpdatingTrackerId(null); }; const { mutate: createTracker } = useCreateTrackerMutation( From 0eeb9152ddb271114157af7aebc871f783e37925 Mon Sep 17 00:00:00 2001 From: ibolton336 Date: Mon, 2 Oct 2023 13:39:53 -0400 Subject: [PATCH 3/3] Update to allow multiple ids to be edited Signed-off-by: ibolton336 --- .../jira/components/tracker-status.tsx | 21 +++++++--- .../app/pages/external/jira/tracker-form.tsx | 10 ++--- .../src/app/pages/external/jira/trackers.tsx | 27 +++++------- .../external/jira/useUpdatingTrackerId.ts | 27 ------------ .../external/jira/useUpdatingTrackerIds.ts | 42 +++++++++++++++++++ 5 files changed, 72 insertions(+), 55 deletions(-) delete mode 100644 client/src/app/pages/external/jira/useUpdatingTrackerId.ts 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 bd90ca0c3a..fd161dd747 100644 --- a/client/src/app/pages/external/jira/tracker-form.tsx +++ b/client/src/app/pages/external/jira/tracker-form.tsx @@ -58,16 +58,14 @@ interface FormValues { export interface TrackerFormProps { onClose: () => void; - setUpdatingTrackerId: React.Dispatch< - React.SetStateAction - >; + addUpdatingTrackerId: (id: number) => void; tracker?: Tracker; } export const TrackerForm: React.FC = ({ tracker, onClose, - setUpdatingTrackerId, + addUpdatingTrackerId, }) => { const { t } = useTranslation(); @@ -86,7 +84,7 @@ export const TrackerForm: React.FC = ({ variant: "success", }); - setUpdatingTrackerId(_.data.id); + addUpdatingTrackerId(_.data.id); }; const onUpdateTrackerSuccess = ( @@ -100,7 +98,7 @@ export const TrackerForm: React.FC = ({ variant: "success", }); - setUpdatingTrackerId(tracker.id); + addUpdatingTrackerId(tracker.id); }; const onCreateUpdatetrackerError = (error: AxiosError) => { diff --git a/client/src/app/pages/external/jira/trackers.tsx b/client/src/app/pages/external/jira/trackers.tsx index 1db6254be5..cdde160ab2 100644 --- a/client/src/app/pages/external/jira/trackers.tsx +++ b/client/src/app/pages/external/jira/trackers.tsx @@ -8,7 +8,6 @@ import { Modal, PageSection, PageSectionVariants, - Spinner, Text, TextContent, Title, @@ -45,7 +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 useUpdatingTrackerId from "./useUpdatingTrackerId"; +import useUpdatingTrackerIds from "./useUpdatingTrackerIds"; export const JiraTrackers: React.FC = () => { const { t } = useTranslation(); @@ -149,10 +148,7 @@ export const JiraTrackers: React.FC = () => { }, } = tableControls; - //Handle tracker update temporary loading state - const [updatingTrackerId, setUpdatingTrackerId] = useUpdatingTrackerId(10000); - - // + const [updatingTrackerIds, addUpdatingTrackerId] = useUpdatingTrackerIds(); return ( <> @@ -266,15 +262,14 @@ export const JiraTrackers: React.FC = () => { width={10} {...getTdProps({ columnKey: "connection" })} > - {updatingTrackerId === tracker.id ? ( - - ) : ( - - )} + { > setTrackerModalState(null)} /> diff --git a/client/src/app/pages/external/jira/useUpdatingTrackerId.ts b/client/src/app/pages/external/jira/useUpdatingTrackerId.ts deleted file mode 100644 index 5ed1159506..0000000000 --- a/client/src/app/pages/external/jira/useUpdatingTrackerId.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { useState, useEffect } from "react"; - -type UpdatableId = string | number | null; - -const useUpdatingTrackerId = (delay: number = 5000) => { - const [updatingTrackerId, setUpdatingTrackerId] = useState(null); - - useEffect(() => { - let timerId: number | null = null; - - if (updatingTrackerId !== null) { - timerId = window.setTimeout(() => { - setUpdatingTrackerId(null); - }, delay); - } - - return () => { - if (timerId !== null) { - window.clearTimeout(timerId); - } - }; - }, [updatingTrackerId, delay]); - - return [updatingTrackerId, setUpdatingTrackerId] as const; -}; - -export default useUpdatingTrackerId; 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;