Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

360 polling #362

Merged
merged 4 commits into from
Jun 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions src/hooks/useInterval.ts
Original file line number Diff line number Diff line change
@@ -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<Callback | null>(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;
3 changes: 1 addition & 2 deletions src/locales/de/admin.json
Original file line number Diff line number Diff line change
Expand Up @@ -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."
}
3 changes: 1 addition & 2 deletions src/locales/en/admin.json
Original file line number Diff line number Diff line change
Expand Up @@ -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."
}
77 changes: 22 additions & 55 deletions src/pages/Admin/components/Submissions/Submissions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -39,6 +39,7 @@ export default function Submissions() {
*/
const { gridLocale } = useAGGridLocaleContext();

// Refs
/**
* AG Grid Reference
* @author Timo Hauser
Expand All @@ -47,6 +48,15 @@ export default function Submissions() {
*/
const gridRef: LegacyRef<AgGridReact> = useRef<AgGridReact>(null);

// States
/**
* Row Data State
* @author Timo Hauser
*
* @type {UserSubmissionTableElement[]}
*/
const [rowData, setRowData] = useState<UserSubmissionTableElement[]>([]);

/**
* AG Grid Options
* @author Timo Hauser
Expand All @@ -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<UserSubmissionTableElement[]>([]);

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
Expand Down Expand Up @@ -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
Expand Down
24 changes: 0 additions & 24 deletions src/services/submission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Response>} 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
Expand Down Expand Up @@ -182,7 +159,6 @@ export default {
getStatus,
list,
sendSubmission,
heartbeat,
reviewSubmission,
getResultPageLink,
getLinterResult,
Expand Down