Skip to content
This repository was archived by the owner on May 4, 2024. It is now read-only.

Commit a3c6fe4

Browse files
committed
Show upload status on frontend
1 parent cb1fc44 commit a3c6fe4

File tree

7 files changed

+223
-55
lines changed

7 files changed

+223
-55
lines changed

backend/src/upload/file.processor.ts

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,9 @@ export class FileProcessor {
4646
console.error(`Error unzipping file: ${filePath}`, error);
4747
})
4848
.on('close', async () => {
49-
await this.handleUnzippedContent(tempUnzipPath, filePath);
5049
await job.progress(60);
50+
await this.handleUnzippedContent(tempUnzipPath, filePath);
51+
await job.progress(100);
5152
});
5253
} catch (error) {
5354
console.error(`Error processing file: ${job.data.filePath}`, error);
@@ -103,6 +104,7 @@ export class FileProcessor {
103104

104105
//here we make sure that there is at least one RSP file and a HDC directory
105106
let surveys = find_surveys(filePath, debug);
107+
await job.progress(10);
106108
if (debug) {
107109
printJobInfo(surveys);
108110
}
@@ -111,7 +113,6 @@ export class FileProcessor {
111113
printJobInfo('No valid data found in directory: ' + filePath);
112114
}
113115

114-
// TODO: split process here instead of for the all zip file (one process per survey)
115116
for (let i = 0; i < surveys.length; i++) {
116117
// upload the survey data and get the id back
117118
surveys[i].fk_survey_id = await this.service.db_insert_survey_data(
@@ -120,21 +121,40 @@ export class FileProcessor {
120121
);
121122

122123
const data = extract_measurements_data(surveys[i], debug);
124+
await job.progress(
125+
(95 - 10) * ((i + 1) / surveys.length) * (1 / 7) + 10,
126+
);
127+
123128
if (!(await this.service.mapMatch(surveys[i], data))) {
124129
printJobError('Failed to map match data.');
125130
}
131+
await job.progress(
132+
(95 - 10) * ((i + 1) / surveys.length) * (2 / 7) + 10,
133+
);
126134

127135
const roadImages = extract_road_image_data(surveys[i], debug);
136+
await job.progress(
137+
(95 - 10) * ((i + 1) / surveys.length) * (3 / 7) + 10,
138+
);
139+
128140
if (!(await this.service.mapMatch(surveys[i], roadImages))) {
129141
printJobError('Failed to map match road images.');
130142
}
143+
await job.progress(
144+
(95 - 10) * ((i + 1) / surveys.length) * (4 / 7) + 10,
145+
);
131146

132147
const dashcameraImages = extract_dashcam_image_data(surveys[i], debug);
148+
await job.progress(
149+
(95 - 10) * ((i + 1) / surveys.length) * (5 / 7) + 10,
150+
);
151+
133152
if (!(await this.service.mapMatch(surveys[i], dashcameraImages))) {
134153
printJobError('Failed to map match dashcam images.');
135154
}
136-
137-
await job.progress(65);
155+
await job.progress(
156+
(95 - 10) * ((i + 1) / surveys.length) * (6 / 7) + 10,
157+
);
138158

139159
// Upload all data and images to the database
140160
await Promise.all([
@@ -160,8 +180,12 @@ export class FileProcessor {
160180
);
161181
}),
162182
]);
163-
await job.progress(99);
183+
184+
await job.progress(
185+
(95 - 10) * ((i + 1) / surveys.length) * (7 / 7) + 10,
186+
);
164187
}
188+
await job.progress(95);
165189

166190
// Delete the unzipped file
167191
try {

backend/src/upload/upload.controller.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import {
22
Body,
33
Controller,
4+
Get,
45
HttpException,
56
HttpStatus,
67
InternalServerErrorException,
78
Post,
89
UploadedFile,
910
UseInterceptors,
10-
Get,
1111
} from '@nestjs/common';
1212
import { FileInterceptor } from '@nestjs/platform-express';
1313
import { InjectQueue } from '@nestjs/bull';
@@ -88,17 +88,14 @@ export class UploadController {
8888
'completed',
8989
'failed',
9090
]);
91-
const jobStatus = await Promise.all(
91+
return await Promise.all(
9292
jobs.map(async (job) => ({
9393
id: job.id,
9494
name: job.name,
9595
timestamp: job.timestamp,
96-
jobData: job.data,
9796
status: await job.getState(),
9897
progress: await job.progress(),
9998
})),
10099
);
101-
console.log('Job Status:', jobStatus);
102-
return jobStatus;
103100
}
104101
}

frontend/src/Components/Conditions/UploadPanel.tsx

Lines changed: 132 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
1-
import { FC, useRef, useState } from 'react';
1+
import { FC, useCallback, useEffect, useRef, useState } from 'react';
22
import SingleFileInput from '../Inputs/SingleFileInput';
33

44
import '../../css/upload_panel.css';
5-
import { uploadSurvey } from '../../queries/upload';
5+
import { getUploadStatus, uploadSurvey } from '../../queries/upload';
6+
import { UploadStatus } from '../../models/models';
67

78
interface Props {
89
/** Event when user wants to close the panel */
910
close: () => void;
1011
}
1112

13+
/** Interval in seconds to refresh the upload status */
14+
const REFRESH_UPLOAD_STATUS_INTERVAL = 60;
15+
1216
/**
1317
* Create a panel where the user can upload a zip file
1418
*
@@ -19,6 +23,49 @@ const UploadPanel: FC<Props> = ({ close }) => {
1923
'waiting' | 'sending' | 'sent' | 'sent-error' | 'invalid-password'
2024
>('waiting');
2125

26+
const [uploadStatus, setUploadStatus] = useState<UploadStatus[]>([]);
27+
28+
const [waitingForUploadStatus, setWaitingForUploadStatus] =
29+
useState<boolean>(false);
30+
31+
const actualizeUploadStatus = useCallback(() => {
32+
getUploadStatus((status) => {
33+
let nonActiveNumberToShow = 4;
34+
status.sort((a, b) => (a.timestamp < b.timestamp ? 1 : -1));
35+
36+
setUploadStatus(
37+
status.filter((s) => {
38+
if (s.status === 'active') {
39+
return true;
40+
}
41+
42+
if (nonActiveNumberToShow > 0) {
43+
nonActiveNumberToShow--;
44+
return true;
45+
}
46+
47+
return false;
48+
}),
49+
);
50+
});
51+
}, []);
52+
53+
// First time
54+
useEffect(() => {
55+
if (actualizeUploadStatus) actualizeUploadStatus();
56+
}, []);
57+
58+
// Refresh periodically
59+
useEffect(() => {
60+
if (actualizeUploadStatus)
61+
setTimeout(() => {
62+
if (waitingForUploadStatus) return;
63+
setWaitingForUploadStatus(true);
64+
actualizeUploadStatus();
65+
setWaitingForUploadStatus(false);
66+
}, REFRESH_UPLOAD_STATUS_INTERVAL * 1000);
67+
}, [uploadStatus]);
68+
2269
const passwordRef = useRef<HTMLInputElement>(null);
2370

2471
return (
@@ -39,39 +86,89 @@ const UploadPanel: FC<Props> = ({ close }) => {
3986
<input ref={passwordRef} name="password" type="password" />
4087
</div>
4188

42-
{state === 'waiting' && (
43-
<SingleFileInput
44-
className="upload-input"
45-
displayName="Upload a .zip"
46-
onFileDrop={(file: File) => {
47-
setState('sending');
48-
49-
uploadSurvey(
50-
file,
51-
passwordRef.current?.value || '',
52-
() => {
53-
setState('sent');
54-
setTimeout(() => setState('waiting'), 3000);
55-
},
56-
(error) => {
57-
if (error.response.status === 403) {
58-
setState('invalid-password');
59-
} else {
60-
setState('sent-error');
61-
}
62-
console.warn('Error while uploading new survey: ', error);
63-
setTimeout(() => setState('waiting'), 3000);
64-
},
65-
);
66-
}}
67-
/>
68-
)}
69-
{state === 'sending' && <p>Sending...</p>}
70-
{state === 'sent' && <p>Sent!</p>}
71-
{state === 'sent-error' && (
72-
<p>Something went wrong while sending the file!</p>
73-
)}
74-
{state === 'invalid-password' && <p>Invalid password!</p>}
89+
<div className="upload-input-container">
90+
{state === 'waiting' && (
91+
<SingleFileInput
92+
className="upload-input"
93+
displayName="Upload a .zip"
94+
onFileDrop={(file: File) => {
95+
setState('sending');
96+
97+
uploadSurvey(
98+
file,
99+
passwordRef.current?.value || '',
100+
() => {
101+
setState('sent');
102+
setTimeout(() => setState('waiting'), 3000);
103+
},
104+
(error) => {
105+
if (error.response.status === 403) {
106+
setState('invalid-password');
107+
} else {
108+
setState('sent-error');
109+
}
110+
console.warn('Error while uploading new survey: ', error);
111+
setTimeout(() => setState('waiting'), 3000);
112+
},
113+
);
114+
}}
115+
/>
116+
)}
117+
{state === 'sending' && <p>Sending...</p>}
118+
{state === 'sent' && <p>Sent!</p>}
119+
{state === 'sent-error' && (
120+
<p>Something went wrong while sending the file!</p>
121+
)}
122+
{state === 'invalid-password' && <p>Invalid password!</p>}
123+
</div>
124+
<div className="upload-status">
125+
<div className="upload-status-title-and-btn">
126+
<h4>Upload tasks status:</h4>
127+
<span
128+
title="Manually refresh the list"
129+
onClick={() => {
130+
if (waitingForUploadStatus) return;
131+
setWaitingForUploadStatus(true);
132+
actualizeUploadStatus();
133+
setWaitingForUploadStatus(false);
134+
}}
135+
>
136+
&#x21bb;
137+
</span>
138+
</div>
139+
{uploadStatus.length == 0 && <p>No recent upload</p>}
140+
{uploadStatus.length > 0 && (
141+
<table>
142+
<thead>
143+
<tr>
144+
<th></th>
145+
<th>Type</th>
146+
<th>Date</th>
147+
<th>Status</th>
148+
<th>Progress</th>
149+
</tr>
150+
</thead>
151+
<tbody>
152+
{uploadStatus.map((status) => (
153+
<tr key={status.id}>
154+
<td>{status.id}</td>
155+
<td>
156+
{status.name === 'process-file'
157+
? 'Data extraction'
158+
: 'Unzip'}
159+
</td>
160+
<td>
161+
{new Date(status.timestamp).toLocaleDateString()}{' '}
162+
{new Date(status.timestamp).toLocaleTimeString()}
163+
</td>
164+
<td>{status.status}</td>
165+
<td>{Math.round(status.progress)}%</td>
166+
</tr>
167+
))}
168+
</tbody>
169+
</table>
170+
)}
171+
</div>
75172
</div>
76173
</div>
77174
);

frontend/src/css/single_file_input.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
appearance: none;
99
-webkit-appearance: none;
1010
border: #0076d3 2px solid;
11-
padding: 10px 0 25px 0;
11+
padding: 10px 0 30px 0;
1212
}
1313

1414
.file p {

0 commit comments

Comments
 (0)