Skip to content

Commit a820846

Browse files
committed
refactor: use react-query for code submission/polling
1 parent 2cbb14d commit a820846

File tree

2 files changed

+120
-55
lines changed

2 files changed

+120
-55
lines changed

src/api/submissions.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import axios, { AxiosResponse } from 'axios';
2+
import { useState } from 'react';
3+
import { useMutation, useQuery } from 'react-query';
4+
import useToken from '../utils/useToken';
5+
6+
function useProcessInterval({
7+
onSuccess,
8+
onError,
9+
}: {
10+
onSuccess: any;
11+
onError: any;
12+
}) {
13+
const [processId, setProcessId] = useState('');
14+
const [isPollingEnabled, setIsPollingEnabled] = useState(false);
15+
const { token } = useToken();
16+
17+
// 1: Handle code submission
18+
async function createJob(code: string) {
19+
const response = await fetch('http://localhost:3000/submissions', {
20+
method: 'POST',
21+
headers: {
22+
'Content-Type': 'application/json',
23+
Authorization: `Bearer ${token}`,
24+
},
25+
body: JSON.stringify({
26+
language: 'cpython3',
27+
code: code,
28+
}),
29+
});
30+
return await response.json();
31+
}
32+
33+
const { mutate } = useMutation(createJob, {
34+
onMutate: () => {
35+
setIsPollingEnabled(true);
36+
},
37+
onError: (error) => {
38+
console.error(error);
39+
setIsPollingEnabled(false);
40+
onError();
41+
},
42+
onSuccess: (data) => {
43+
console.log(data);
44+
setProcessId(data.id);
45+
},
46+
});
47+
48+
// 2: Poll code execution job result until done or failed
49+
const { isLoading, data } = useQuery(
50+
['processProgress', token, processId],
51+
async () => {
52+
const res: AxiosResponse<{ status: string; output: string }> =
53+
await axios.get(`http://localhost:3000/submissions/${processId}`, {
54+
headers: {
55+
Authorization: `Bearer ${token}`,
56+
},
57+
});
58+
return res.data;
59+
},
60+
{
61+
onSuccess: (data) => {
62+
if (data.status === 'completed') {
63+
setIsPollingEnabled(false);
64+
onSuccess();
65+
}
66+
},
67+
onError: (error) => {
68+
console.error(error);
69+
setIsPollingEnabled(false);
70+
setProcessId('');
71+
},
72+
enabled: processId !== '',
73+
refetchInterval: isPollingEnabled ? 500 : false,
74+
refetchIntervalInBackground: true,
75+
refetchOnWindowFocus: false,
76+
},
77+
);
78+
79+
return { mutate, data, isLoading };
80+
}
81+
82+
export default useProcessInterval;

src/components/Dashboard.tsx

Lines changed: 38 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import { Disclosure, Menu, Transition } from '@headlessui/react';
22
import { BellIcon, MenuIcon, XIcon } from '@heroicons/react/outline';
33
import Editor from '@monaco-editor/react';
4-
import axios, { AxiosResponse } from 'axios';
5-
import { Fragment, useRef, useState } from 'react';
4+
import React, { Fragment, useRef } from 'react';
65
import Gravatar from 'react-gravatar';
6+
import useProcessInterval from '../api/submissions';
77
import useProfile from '../api/users';
8-
import useToken from '../utils/useToken';
98
import Result from './Result';
109

1110
const navigation = ['Dashboard', 'Team', 'Projects', 'Calendar', 'Reports'];
@@ -16,64 +15,45 @@ function classNames(...classes: any[]) {
1615
}
1716

1817
export default function Example() {
18+
// Get monaco instance to access code later
1919
const editorRef: any = useRef<null>(null);
20-
const [jobID, setJobID] = useState('');
21-
const [status, setStatus] = useState('');
22-
const [output, setOutput] = useState('');
23-
const { token } = useToken();
24-
25-
const { isLoading, isError, data, error } = useProfile();
26-
27-
if (isLoading) {
28-
return <span>Loading...</span>;
29-
}
30-
31-
if (isError) {
32-
if (error) {
33-
return <span>Error: {error.message}</span>;
34-
}
35-
}
36-
3720
function handleEditorDidMount(editor: any, monaco: any) {
3821
editorRef.current = editor;
3922
}
4023

41-
async function getJobStatus(id: string, timer: NodeJS.Timeout) {
42-
const res: AxiosResponse<{ status: string; output: string }> =
43-
await axios.get(`http://localhost:3000/submissions/${id}`, {
44-
headers: {
45-
Authorization: `Bearer ${token}`,
46-
},
47-
});
48-
setStatus(res.data.status);
49-
setOutput(res.data.output);
50-
if (res.data.status === 'completed') {
51-
clearInterval(timer);
52-
}
24+
// Handle code submission and job result polling
25+
const {
26+
mutate,
27+
data: jobData,
28+
isLoading: isProcessing,
29+
} = useProcessInterval({
30+
onSuccess: (data: any) => console.log('Process finished', data),
31+
onError: (err: any) => console.log('Error with process', err),
32+
});
33+
34+
let result;
35+
if (isProcessing) {
36+
result = 'Processing...';
37+
}
38+
if (jobData) {
39+
result = <Result status={jobData.status} output={jobData.output}></Result>;
5340
}
5441

55-
async function showValue() {
56-
if (editorRef) {
57-
const res: AxiosResponse<{ id: string }> = await axios.post(
58-
'http://localhost:3000/submissions',
59-
{
60-
language: 'cpython3',
61-
code: editorRef.current.getValue(),
62-
},
63-
{
64-
headers: {
65-
Authorization: `Bearer ${token}`,
66-
},
67-
},
68-
);
42+
// Get Profile
43+
const {
44+
isLoading: isProfileLoading,
45+
isError: isProfileError,
46+
data: profileData,
47+
error,
48+
} = useProfile();
6949

70-
setJobID(res.data.id);
71-
console.log(res.data.id);
72-
console.log(jobID);
50+
if (isProfileLoading) {
51+
return <span>Loading...</span>;
52+
}
7353

74-
const timer = setInterval(() => {
75-
getJobStatus(res.data.id, timer);
76-
}, 500);
54+
if (isProfileError) {
55+
if (error) {
56+
return <span>Error: {error.message}</span>;
7757
}
7858
}
7959

@@ -133,7 +113,7 @@ export default function Example() {
133113
<Menu.Button className="max-w-xs bg-gray-800 rounded-full flex items-center text-sm focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-white">
134114
<span className="sr-only">Open user menu</span>
135115
<Gravatar
136-
email={data?.email}
116+
email={profileData?.email}
137117
className="h-8 w-8 rounded-full"
138118
/>
139119
</Menu.Button>
@@ -272,11 +252,14 @@ export default function Example() {
272252
{/* /End replace */}
273253
<button
274254
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
275-
onClick={showValue}
255+
onClick={() => {
256+
mutate(editorRef.current.getValue());
257+
}}
276258
>
277259
Run code
278260
</button>
279-
<Result status={status} output={output}></Result>
261+
262+
{result && result}
280263
</div>
281264
</main>
282265
</div>

0 commit comments

Comments
 (0)