Skip to content

Commit

Permalink
enable recruiter mass resume downloads (#44)
Browse files Browse the repository at this point in the history
* enable recruiter resume downloads

* dont keep @illinois.edu in filenames

* fix linting issues

* fix prettier
  • Loading branch information
devksingh4 authored Sep 10, 2024
1 parent 7b4d9e8 commit 39e13a6
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 7 deletions.
3 changes: 3 additions & 0 deletions clientv2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
"@ungap/with-resolvers": "^0.1.0",
"axios": "^1.7.2",
"dotenv": "^16.4.5",
"file-saver": "^2.0.5",
"jszip": "^3.10.1",
"pdfjs-dist": "^4.5.136",
"react": "^18.3.1",
"react-dom": "^18.3.1",
Expand All @@ -48,6 +50,7 @@
"@testing-library/jest-dom": "^6.4.6",
"@testing-library/react": "^16.0.0",
"@testing-library/user-event": "^14.5.2",
"@types/file-saver": "^2",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^7.13.1",
Expand Down
76 changes: 71 additions & 5 deletions clientv2/src/components/SearchProfiles/Results.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,13 @@ import {
} from '@mantine/core';
import { IconQuestionMark } from '@tabler/icons-react';
import { notifications } from '@mantine/notifications';
import JSZip from 'jszip';
import { saveAs } from 'file-saver';
import { DegreeLevel } from '../ProfileViewer/options';
import { ViewStudentProfile } from '@/pages/recruiter/ViewStudentProfile.page';
import { useApi } from '@/util/api';

const MAX_RESUMES_DOWNLAOD = 2000;

export interface ProfileSearchDegreeEntry {
level: DegreeLevel;
Expand All @@ -39,6 +44,7 @@ export const ProfileSearchResults: React.FC<ProfileSearchResultsProp> = ({ data
const [selectedUsername, setSelectedUsername] = useState<string | null>(null);
const [activePage, setActivePage] = useState(1);
const itemsPerPage = 10;
const api = useApi();

if (data === null) {
return null;
Expand Down Expand Up @@ -68,13 +74,73 @@ export const ProfileSearchResults: React.FC<ProfileSearchResultsProp> = ({ data
setSelectedUsername(username);
setModalOpened(true);
};
const notImplError = () => {
const massDownloadErrorNotification = (numErrored?: number, partial: boolean = false) => {
notifications.show({
title: `Error downloading ${partial ? 'some' : ''} resumes`,
color: numErrored ? 'yellow' : 'red',
message: `There was an error downloading ${numErrored ? numErrored.toString() : 'the selected'} resumes.`,
});
};
const massDownloadSuccessNotification = (numSuccess: number) => {
notifications.show({
color: 'red',
title: 'Not Implemented Yet',
message: 'This feature still in the works. Check back later.',
title: 'Downloaded resumes',
color: 'blue',
message: `Successfully downloaded ${numSuccess} resumes.`,
});
};

const downloadResumes = async () => {
if (selectedRows.length > MAX_RESUMES_DOWNLAOD) {
return notifications.show({
title: 'Error downloading resumes',
color: 'red',
message: `You cannot download more than ${MAX_RESUMES_DOWNLAOD} in one request.`,
});
}
let urls: string[];
try {
urls = (await api.post('/recruiter/mass_download', { usernames: selectedRows })).data;
} catch (e) {
return massDownloadErrorNotification();
}
let numError = 0;
let numSuccess = 0;
const urlMapper: Record<string, string> = {};
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 realBlobs = [];
for (const outerPromise of allPromises) {
if (
outerPromise.status === 'fulfilled' &&
(await outerPromise.value.promise).status === 200
) {
numSuccess += 1;
realBlobs.push({
blob: (await outerPromise.value.promise).blob(),
filename: `${urlMapper[outerPromise.value.url].replace('@illinois.edu', '')}.pdf`,
});
} else {
numError += 1;
}
}
if (numError > 0) {
massDownloadErrorNotification(numError, !(numSuccess === 0));
}
if (numSuccess === 0) 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' });

saveAs(zipContent, `${folderName}.zip`);
massDownloadSuccessNotification(numSuccess);
return [numSuccess, numError];
};
const handleRowSelect = (id: string) => {
setSelectedRows((prevSelectedRows) =>
prevSelectedRows.includes(id)
Expand Down Expand Up @@ -131,7 +197,7 @@ export const ProfileSearchResults: React.FC<ProfileSearchResultsProp> = ({ data
<Button
style={{ marginLeft: '2vw' }}
onClick={() => {
notImplError();
downloadResumes();
}}
>
Download Selected Profiles
Expand Down
115 changes: 113 additions & 2 deletions clientv2/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2984,6 +2984,13 @@ __metadata:
languageName: node
linkType: hard

"@types/file-saver@npm:^2":
version: 2.0.7
resolution: "@types/file-saver@npm:2.0.7"
checksum: 10c0/c6b88a1aea8eec58469da2a90828fef6e9d5d590c7094fb959783d7c32878af80d39439734f3d41b78355dadb507f606e3d04a29a160c85411c65251e58df847
languageName: node
linkType: hard

"@types/find-cache-dir@npm:^3.2.1":
version: 3.2.1
resolution: "@types/find-cache-dir@npm:3.2.1"
Expand Down Expand Up @@ -3419,6 +3426,7 @@ __metadata:
"@testing-library/jest-dom": "npm:^6.4.6"
"@testing-library/react": "npm:^16.0.0"
"@testing-library/user-event": "npm:^14.5.2"
"@types/file-saver": "npm:^2"
"@types/react": "npm:^18.3.3"
"@types/react-dom": "npm:^18.3.0"
"@typescript-eslint/eslint-plugin": "npm:^7.13.1"
Expand All @@ -3435,8 +3443,10 @@ __metadata:
eslint-plugin-jsx-a11y: "npm:^6.9.0"
eslint-plugin-react: "npm:^7.34.3"
eslint-plugin-react-hooks: "npm:^4.6.2"
file-saver: "npm:^2.0.5"
identity-obj-proxy: "npm:^3.0.0"
jsdom: "npm:^24.1.0"
jszip: "npm:^3.10.1"
pdfjs-dist: "npm:^4.5.136"
postcss: "npm:^8.4.38"
postcss-preset-mantine: "npm:1.15.0"
Expand Down Expand Up @@ -4397,6 +4407,13 @@ __metadata:
languageName: node
linkType: hard

"core-util-is@npm:~1.0.0":
version: 1.0.3
resolution: "core-util-is@npm:1.0.3"
checksum: 10c0/90a0e40abbddfd7618f8ccd63a74d88deea94e77d0e8dbbea059fa7ebebb8fbb4e2909667fe26f3a467073de1a542ebe6ae4c73a73745ac5833786759cd906c9
languageName: node
linkType: hard

"cosmiconfig@npm:^9.0.0":
version: 9.0.0
resolution: "cosmiconfig@npm:9.0.0"
Expand Down Expand Up @@ -5636,6 +5653,13 @@ __metadata:
languageName: node
linkType: hard

"file-saver@npm:^2.0.5":
version: 2.0.5
resolution: "file-saver@npm:2.0.5"
checksum: 10c0/0a361f683786c34b2574aea53744cb70d0a6feb0fa5e3af00f2fcb6c9d40d3049cc1470e38c6c75df24219f247f6fb3076f86943958f580e62ee2ffe897af8b1
languageName: node
linkType: hard

"fill-range@npm:^7.1.1":
version: 7.1.1
resolution: "fill-range@npm:7.1.1"
Expand Down Expand Up @@ -6376,6 +6400,13 @@ __metadata:
languageName: node
linkType: hard

"immediate@npm:~3.0.5":
version: 3.0.6
resolution: "immediate@npm:3.0.6"
checksum: 10c0/f8ba7ede69bee9260241ad078d2d535848745ff5f6995c7c7cb41cfdc9ccc213f66e10fa5afb881f90298b24a3f7344b637b592beb4f54e582770cdce3f1f039
languageName: node
linkType: hard

"import-fresh@npm:^3.2.1, import-fresh@npm:^3.3.0":
version: 3.3.0
resolution: "import-fresh@npm:3.3.0"
Expand Down Expand Up @@ -6410,7 +6441,7 @@ __metadata:
languageName: node
linkType: hard

"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.3, inherits@npm:^2.0.4":
"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.3":
version: 2.0.4
resolution: "inherits@npm:2.0.4"
checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2
Expand Down Expand Up @@ -6788,6 +6819,13 @@ __metadata:
languageName: node
linkType: hard

"isarray@npm:~1.0.0":
version: 1.0.0
resolution: "isarray@npm:1.0.0"
checksum: 10c0/18b5be6669be53425f0b84098732670ed4e727e3af33bc7f948aac01782110eb9a18b3b329c5323bcdd3acdaae547ee077d3951317e7f133bff7105264b3003d
languageName: node
linkType: hard

"isexe@npm:^2.0.0":
version: 2.0.0
resolution: "isexe@npm:2.0.0"
Expand Down Expand Up @@ -7034,6 +7072,18 @@ __metadata:
languageName: node
linkType: hard

"jszip@npm:^3.10.1":
version: 3.10.1
resolution: "jszip@npm:3.10.1"
dependencies:
lie: "npm:~3.3.0"
pako: "npm:~1.0.2"
readable-stream: "npm:~2.3.6"
setimmediate: "npm:^1.0.5"
checksum: 10c0/58e01ec9c4960383fb8b38dd5f67b83ccc1ec215bf74c8a5b32f42b6e5fb79fada5176842a11409c4051b5b94275044851814a31076bf49e1be218d3ef57c863
languageName: node
linkType: hard

"jwt-decode@npm:^4.0.0":
version: 4.0.0
resolution: "jwt-decode@npm:4.0.0"
Expand Down Expand Up @@ -7118,6 +7168,15 @@ __metadata:
languageName: node
linkType: hard

"lie@npm:~3.3.0":
version: 3.3.0
resolution: "lie@npm:3.3.0"
dependencies:
immediate: "npm:~3.0.5"
checksum: 10c0/56dd113091978f82f9dc5081769c6f3b947852ecf9feccaf83e14a123bc630c2301439ce6182521e5fbafbde88e88ac38314327a4e0493a1bea7e0699a7af808
languageName: node
linkType: hard

"lines-and-columns@npm:^1.1.6":
version: 1.2.4
resolution: "lines-and-columns@npm:1.2.4"
Expand Down Expand Up @@ -8052,6 +8111,13 @@ __metadata:
languageName: node
linkType: hard

"pako@npm:~1.0.2":
version: 1.0.11
resolution: "pako@npm:1.0.11"
checksum: 10c0/86dd99d8b34c3930345b8bbeb5e1cd8a05f608eeb40967b293f72fe469d0e9c88b783a8777e4cc7dc7c91ce54c5e93d88ff4b4f060e6ff18408fd21030d9ffbe
languageName: node
linkType: hard

"parent-module@npm:^1.0.0":
version: 1.0.1
resolution: "parent-module@npm:1.0.1"
Expand Down Expand Up @@ -8433,6 +8499,13 @@ __metadata:
languageName: node
linkType: hard

"process-nextick-args@npm:~2.0.0":
version: 2.0.1
resolution: "process-nextick-args@npm:2.0.1"
checksum: 10c0/bec089239487833d46b59d80327a1605e1c5287eaad770a291add7f45fda1bb5e28b38e0e061add0a1d0ee0984788ce74fa394d345eed1c420cacf392c554367
languageName: node
linkType: hard

"process@npm:^0.11.10":
version: 0.11.10
resolution: "process@npm:0.11.10"
Expand Down Expand Up @@ -8833,6 +8906,21 @@ __metadata:
languageName: node
linkType: hard

"readable-stream@npm:~2.3.6":
version: 2.3.8
resolution: "readable-stream@npm:2.3.8"
dependencies:
core-util-is: "npm:~1.0.0"
inherits: "npm:~2.0.3"
isarray: "npm:~1.0.0"
process-nextick-args: "npm:~2.0.0"
safe-buffer: "npm:~5.1.1"
string_decoder: "npm:~1.1.1"
util-deprecate: "npm:~1.0.1"
checksum: 10c0/7efdb01f3853bc35ac62ea25493567bf588773213f5f4a79f9c365e1ad13bab845ac0dae7bc946270dc40c3929483228415e92a3fc600cc7e4548992f41ee3fa
languageName: node
linkType: hard

"readdirp@npm:~3.6.0":
version: 3.6.0
resolution: "readdirp@npm:3.6.0"
Expand Down Expand Up @@ -9207,6 +9295,13 @@ __metadata:
languageName: node
linkType: hard

"safe-buffer@npm:~5.1.0, safe-buffer@npm:~5.1.1":
version: 5.1.2
resolution: "safe-buffer@npm:5.1.2"
checksum: 10c0/780ba6b5d99cc9a40f7b951d47152297d0e260f0df01472a1b99d4889679a4b94a13d644f7dbc4f022572f09ae9005fa2fbb93bbbd83643316f365a3e9a45b21
languageName: node
linkType: hard

"safe-regex-test@npm:^1.0.3":
version: 1.0.3
resolution: "safe-regex-test@npm:1.0.3"
Expand Down Expand Up @@ -9336,6 +9431,13 @@ __metadata:
languageName: node
linkType: hard

"setimmediate@npm:^1.0.5":
version: 1.0.5
resolution: "setimmediate@npm:1.0.5"
checksum: 10c0/5bae81bfdbfbd0ce992893286d49c9693c82b1bcc00dcaaf3a09c8f428fdeacf4190c013598b81875dfac2b08a572422db7df779a99332d0fce186d15a3e4d49
languageName: node
linkType: hard

"setprototypeof@npm:1.2.0":
version: 1.2.0
resolution: "setprototypeof@npm:1.2.0"
Expand Down Expand Up @@ -9707,6 +9809,15 @@ __metadata:
languageName: node
linkType: hard

"string_decoder@npm:~1.1.1":
version: 1.1.1
resolution: "string_decoder@npm:1.1.1"
dependencies:
safe-buffer: "npm:~5.1.0"
checksum: 10c0/b4f89f3a92fd101b5653ca3c99550e07bdf9e13b35037e9e2a1c7b47cec4e55e06ff3fc468e314a0b5e80bfbaf65c1ca5a84978764884ae9413bec1fc6ca924e
languageName: node
linkType: hard

"strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1":
version: 6.0.1
resolution: "strip-ansi@npm:6.0.1"
Expand Down Expand Up @@ -10574,7 +10685,7 @@ __metadata:
languageName: node
linkType: hard

"util-deprecate@npm:^1.0.1, util-deprecate@npm:^1.0.2":
"util-deprecate@npm:^1.0.1, util-deprecate@npm:^1.0.2, util-deprecate@npm:~1.0.1":
version: 1.0.2
resolution: "util-deprecate@npm:1.0.2"
checksum: 10c0/41a5bdd214df2f6c3ecf8622745e4a366c4adced864bc3c833739791aeeeb1838119af7daed4ba36428114b5c67dcda034a79c882e97e43c03e66a4dd7389942
Expand Down

0 comments on commit 39e13a6

Please sign in to comment.