Skip to content

Commit

Permalink
recruiter: add loading bar when downloading all resumes. (#47)
Browse files Browse the repository at this point in the history
* add loading bar

* run prettier

* add resume disclaimer

* fix message part 3

* fix tests

* fix/add tests

* fix tests part 17
  • Loading branch information
devksingh4 authored Sep 12, 2024
1 parent 5205f02 commit 5828309
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 12 deletions.
32 changes: 32 additions & 0 deletions clientv2/src/components/AuthContext/ProgressLoadingScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react';
import { LoadingOverlay, Progress, useMantineColorScheme } from '@mantine/core';

interface ProgressFullScreenLoaderProps {
totalItems: number;
currentItems: number;
title?: string;
}

const ProgressFullScreenLoader: React.FC<ProgressFullScreenLoaderProps> = ({
totalItems,
currentItems,
title,
}) => {
const { colorScheme } = useMantineColorScheme();
return (
<LoadingOverlay
visible
loaderProps={{
color: colorScheme === 'dark' ? 'white' : 'black',
children: (
<>
<h1>{title || 'Downloading...'}</h1>
<Progress size="lg" radius="lg" value={(currentItems / totalItems) * 100} animated />
</>
),
}}
/>
);
};

export default ProgressFullScreenLoader;
41 changes: 32 additions & 9 deletions clientv2/src/components/SearchProfiles/Results.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { saveAs } from 'file-saver';
import { DegreeLevel } from '../ProfileViewer/options';
import { ViewStudentProfile } from '@/pages/recruiter/ViewStudentProfile.page';
import { useApi } from '@/util/api';
import ProgressFullScreenLoader from '@/components/AuthContext/ProgressLoadingScreen';

const MAX_RESUMES_DOWNLAOD = 2000;

Expand All @@ -43,6 +44,7 @@ export const ProfileSearchResults: React.FC<ProfileSearchResultsProp> = ({ data
const [modalOpened, setModalOpened] = useState(false);
const [selectedUsername, setSelectedUsername] = useState<string | null>(null);
const [activePage, setActivePage] = useState(1);
const [totalDownloaded, setTotalDownloaded] = useState(-1);
const itemsPerPage = 10;
const api = useApi();

Expand Down Expand Up @@ -78,7 +80,7 @@ export const ProfileSearchResults: React.FC<ProfileSearchResultsProp> = ({ data
notifications.show({
title: `Error downloading ${partial ? 'some' : ''} resumes`,
color: numErrored ? 'yellow' : 'red',
message: `There was an error downloading ${numErrored ? numErrored.toString() : 'the selected'} resumes.`,
message: `There was an error downloading resumes for ${numErrored ? numErrored.toString() : 'the selected'} profiles. These profiles likely don't have resumes.`,
});
};
const massDownloadSuccessNotification = (numSuccess: number) => {
Expand All @@ -97,10 +99,12 @@ export const ProfileSearchResults: React.FC<ProfileSearchResultsProp> = ({ data
message: `You cannot download more than ${MAX_RESUMES_DOWNLAOD} in one request.`,
});
}
setTotalDownloaded(0);
let urls: string[];
try {
urls = (await api.post('/recruiter/mass_download', { usernames: selectedRows })).data;
} catch (e) {
setTotalDownloaded(-1);
return massDownloadErrorNotification();
}
let numError = 0;
Expand All @@ -109,17 +113,29 @@ export const ProfileSearchResults: React.FC<ProfileSearchResultsProp> = ({ data
for (let i = 0; i < urls.length; i++) {
urlMapper[urls[i]] = selectedRows[i];
}
const allPromises = await Promise.allSettled(urls.map((x) => ({ url: x, promise: fetch(x) })));
const allPromises = urls.map((url) =>
fetch(url)
.then((response) => {
// Increment the counter when the promise resolves
setTotalDownloaded((prev) => prev + 1);
return { url, status: 'fulfilled', value: response };
})
.catch((error) => {
// Increment the counter when the promise rejects
setTotalDownloaded((prev) => prev + 1);
return { url, status: 'rejected', value: error };
})
);
const realBlobs = [];
for (const outerPromise of allPromises) {
if (
outerPromise.status === 'fulfilled' &&
(await outerPromise.value.promise).status === 200
(await outerPromise).status === 'fulfilled' &&
(await outerPromise).value.status === 200
) {
numSuccess += 1;
realBlobs.push({
blob: (await outerPromise.value.promise).blob(),
filename: `${urlMapper[outerPromise.value.url].replace('@illinois.edu', '')}.pdf`,
blob: (await outerPromise).value.blob(),
filename: `${urlMapper[(await outerPromise).url].replace('@illinois.edu', '')}.pdf`,
});
} else {
numError += 1;
Expand All @@ -128,15 +144,18 @@ export const ProfileSearchResults: React.FC<ProfileSearchResultsProp> = ({ data
if (numError > 0) {
massDownloadErrorNotification(numError, !(numSuccess === 0));
}
if (numSuccess === 0) return [numSuccess, numError];
if (numSuccess === 0) {
setTotalDownloaded(-1);
return [numSuccess, numError];
}
const zip = new JSZip();
const yourDate = new Date().toISOString().split('T')[0];
const folderName = `ACM_UIUC_Resumes-${yourDate}`;
for (const { blob, filename } of realBlobs) {
zip.file(`${folderName}/${filename}`, blob);
}
const zipContent = await zip.generateAsync({ type: 'blob' });

setTotalDownloaded(-1);
saveAs(zipContent, `${folderName}.zip`);
massDownloadSuccessNotification(numSuccess);
return [numSuccess, numError];
Expand Down Expand Up @@ -184,7 +203,11 @@ export const ProfileSearchResults: React.FC<ProfileSearchResultsProp> = ({ data
</Table.Td>
</Table.Tr>
));

if (totalDownloaded > -1) {
return (
<ProgressFullScreenLoader totalItems={selectedRows.length} currentItems={totalDownloaded} />
);
}
return (
<>
<div>
Expand Down
15 changes: 12 additions & 3 deletions e2e/tests/student/edit.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,23 @@ describe("Test that users can edit their profile", () => {
expect(page.getByText('Skills')).toBeTruthy()
expect(page.getByText('Botting')).toBeTruthy()
await page.getByRole('button', { name: 'Edit' }).click();
try {
const alreadyHasDegree = await page.getByText('Degree Level').isVisible();
if (!alreadyHasDegree) {
await page.getByRole('button', { name: 'Add Degree' }).click();
await page.getByRole('textbox', { name: 'Major' }).click();
await page.getByRole('option', { name: 'Computer Science', exact: true }).click();
} catch (e) {
console.log("user already has a degree, not adding another one.")
}
await page.getByRole('button', { name: 'Save' }).click();
expect(await page.waitForSelector('text="Profile saved!"')).toBeTruthy();
});
test('Profiles with no degrees fail to save', async ({ page, becomeUser }) => {
await becomeUser(page, {email: '[email protected]', role: 'student'})
await page.getByRole('link', { name: 'My Profile' }).click();
expect(page.getByText('Resume Book User')).toBeTruthy()
expect(page.getByText('Skills')).toBeTruthy()
expect(page.getByText('Botting')).toBeTruthy()
await page.getByRole('button', { name: 'Edit' }).click();
await page.getByRole('button', { name: 'Save' }).click();
expect(await page.waitForSelector('text="You must specify at least one degree."')).toBeTruthy();
});
})

0 comments on commit 5828309

Please sign in to comment.