diff --git a/src/hooks/useInterval.ts b/src/hooks/useInterval.ts new file mode 100644 index 0000000..ab563c2 --- /dev/null +++ b/src/hooks/useInterval.ts @@ -0,0 +1,35 @@ +import { useEffect, useRef } from 'react'; + +type Callback = () => void; + +/** + * useInterval custom hook - used similar to useEffect, but for intervals (auto cleanup). + * @author David Linhardt + * + * @param {Callback} callback - callback function + * @param {(number | null)} delay - delay in milliseconds + */ +function useInterval(callback: Callback, delay: number | null): void { + const savedCallback = useRef(null); + + // Remember the latest callback if it changes. + useEffect(() => { + savedCallback.current = callback; + }, [callback]); + + // Set up the interval. + useEffect(() => { + function tick() { + if (savedCallback.current) { + savedCallback.current(); + } + } + + if (delay !== null) { + const id = setInterval(tick, delay); + return () => clearInterval(id); // cleanup + } + }, [delay]); +} + +export default useInterval; diff --git a/src/locales/de/admin.json b/src/locales/de/admin.json index 68f43db..bc47455 100644 --- a/src/locales/de/admin.json +++ b/src/locales/de/admin.json @@ -55,6 +55,5 @@ "setReviewedNotFound": "Die Abgabe mit der E-Mail Adresse {{email}} wurde nicht gefunden.", "setReviewedUnprocessableEntity": "Die E-Mail Adresse {{email}} ist nicht gültig.", "setReviewedError": "Ein Fehler ist beim aktiv setzen der Abgabe mit der E-Mail Adresse {{email}} aufgetreten. Bitte versuche es später erneut.", - "errorFetchingChallenges": "Ein Fehler ist beim Abrufen der Challenges aufgetreten. Bitte versuche es später erneut.", - "sseError": "Ein Fehler ist beim Verbinden zum Server aufgetreten ({{event}}). Bitte versuche es später erneut." + "errorFetchingChallenges": "Ein Fehler ist beim Abrufen der Challenges aufgetreten. Bitte versuche es später erneut." } diff --git a/src/locales/en/admin.json b/src/locales/en/admin.json index fc513ef..0235e2e 100644 --- a/src/locales/en/admin.json +++ b/src/locales/en/admin.json @@ -55,6 +55,5 @@ "setReviewedNotFound": "The submission from email {{email}} could not be found.", "setReviewedUnprocessableEntity": "The supplied email address {{email}} is not valid.", "setReviewedError": "An error occurred while trying to set the submission from email {{email}} to reviewed. Please try again later.", - "errorFetchingChallenges": "An error occurred while trying to fetch the challenges. Please try again later.", - "sseError": "An error occurred while trying to establish a connection to the server ({{event}}). Please try again later." + "errorFetchingChallenges": "An error occurred while trying to fetch the challenges. Please try again later." } diff --git a/src/pages/Admin/components/Submissions/Submissions.tsx b/src/pages/Admin/components/Submissions/Submissions.tsx index f5a2f0d..18e2f51 100644 --- a/src/pages/Admin/components/Submissions/Submissions.tsx +++ b/src/pages/Admin/components/Submissions/Submissions.tsx @@ -13,8 +13,8 @@ import { ToastType } from '../../../../interfaces/ToastType'; import moment from 'moment'; import { useAGGridLocaleContext } from '../../../../components/Context/AGGridLocaleContext/useAGGridLocaleContext'; import { StatusCodes } from 'http-status-codes'; +import useInterval from '../../../../hooks/useInterval'; -const baseURL = import.meta.env.VITE_API_URL; /** * Submissions component used in the admin submissions page. * @author Timo Hauser @@ -39,6 +39,7 @@ export default function Submissions() { */ const { gridLocale } = useAGGridLocaleContext(); + // Refs /** * AG Grid Reference * @author Timo Hauser @@ -47,6 +48,15 @@ export default function Submissions() { */ const gridRef: LegacyRef = useRef(null); + // States + /** + * Row Data State + * @author Timo Hauser + * + * @type {UserSubmissionTableElement[]} + */ + const [rowData, setRowData] = useState([]); + /** * AG Grid Options * @author Timo Hauser @@ -65,60 +75,21 @@ export default function Submissions() { }; /** - * Row Data State + * get the submission status from the backend on page load * @author Timo Hauser - * - * @type {UserSubmissionTableElement[]} */ - const [rowData, setRowData] = useState([]); - useEffect(() => { - /** - * connect SSE to the backend to get the submission status changes in real time and update the table accordingly - * @author Timo Hauser - * @author David Linhardt - * - * @async - * @returns {void} - */ - const connect = async () => { - try { - const res = await submission.heartbeat(); - if (!res.ok) { - const data = await res.json(); - toast.showToast( - ToastType.ERROR, - toast.httpError(res.status, data.error) - ); - } else { - const sse = new EventSource( - `${baseURL}/v1/admin/submission/status/subscribe`, - { withCredentials: true } - ); - sse.addEventListener( - 'submission-status-changed', - (event) => { - if (event.data) { - loadSubmissionData(); - } else { - console.log('no data'); - } - } - ); - sse.onerror = () => { - connect(); - }; - } - } catch (e: unknown) { - toast.showToast(ToastType.ERROR, t('connectionError', { ns: 'main' })); - } - }; - connect(); + loadSubmissionData(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); - return () => { - // clean up - }; - }); + /** + * get the submission status from the backend (polling every 4 seconds) using useInterval hook + * @author David Linhardt + */ + useInterval(async () => { + loadSubmissionData(); + }, 4000); /** * set the state of a submission to reviewed in the backend and update the table afterwards @@ -220,10 +191,6 @@ export default function Submissions() { }; } - useEffect(() => { - loadSubmissionData(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); /** * JSON Submission Item Interface for the backend response data. * @author Timo Hauser diff --git a/src/services/submission.ts b/src/services/submission.ts index 72e4d2f..2416110 100644 --- a/src/services/submission.ts +++ b/src/services/submission.ts @@ -86,29 +86,6 @@ const list = async () => { return res; }; -/** - * List service : update point for frontend, sends a heartbeat every 15 second - * http://localhost:8080/swagger-ui/index.html#/admin-controller/subscribeToSubmissionStatus - * @author Timo Hauser - * - * @async - * @returns {Promise} HTTP response - * @throws {any} connection error - */ -const heartbeat = async () => { - const url = `${baseURL}/v1/admin/submission/status/subscribe`; - - const res = await fetch(url, { - method: 'GET', - credentials: 'include', - headers: { - 'Content-Type': 'application/json', - }, - }); - - return res; -}; - /** * Change state service : set the submission state to reviewed, needs email as input * http://localhost:8080/swagger-ui/index.html#/admin-controller/changeSubmissionStateReviewed @@ -182,7 +159,6 @@ export default { getStatus, list, sendSubmission, - heartbeat, reviewSubmission, getResultPageLink, getLinterResult,