Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit 8ac598b

Browse files
committed
Implements tracking of resume link clicking.
1 parent aae6999 commit 8ac598b

File tree

4 files changed

+74
-6
lines changed

4 files changed

+74
-6
lines changed

src/routes/PositionDetails/components/PositionCandidates/index.jsx

+30-5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66
import React, { useMemo, useState, useCallback, useEffect } from "react";
77
import PT from "prop-types";
8+
import cn from "classnames";
89
import _ from "lodash";
910
import CardHeader from "components/CardHeader";
1011
import "./styles.module.scss";
@@ -24,6 +25,7 @@ import Pagination from "components/Pagination";
2425
import IconResume from "../../../../assets/images/icon-resume.svg";
2526
import { toastr } from "react-redux-toastr";
2627
import { getJobById } from "services/jobs";
28+
import { touchCandidateResume } from "services/teams";
2729
import { PERMISSIONS } from "constants/permissions";
2830
import { hasPermission } from "utils/permissions";
2931
import ActionsMenu from "components/ActionsMenu";
@@ -140,6 +142,27 @@ const PositionCandidates = ({ position, statusFilterKey, updateCandidate }) => {
140142
[setPage]
141143
);
142144

145+
const [isTouchingResume, setIsTouchingResume] = useState(false);
146+
const onClickResumeLink = (event) => {
147+
let targetData = event.target.dataset;
148+
let candidateId = targetData.candidateId;
149+
if (!candidateId) {
150+
return;
151+
}
152+
let resumeLink = targetData.resumeLink;
153+
setIsTouchingResume(true);
154+
touchCandidateResume(candidateId)
155+
.then(() => {
156+
if (resumeLink) {
157+
window.open(resumeLink, "_blank");
158+
}
159+
})
160+
.catch(console.error)
161+
.finally(() => {
162+
setIsTouchingResume(false);
163+
});
164+
};
165+
143166
const markCandidateSelected = useCallback(
144167
(candidate) => {
145168
return updateCandidate(candidate.id, {
@@ -222,14 +245,16 @@ const PositionCandidates = ({ position, statusFilterKey, updateCandidate }) => {
222245
limit={7}
223246
/>
224247
{candidate.resume && (
225-
<a
226-
href={`${candidate.resume}`}
227-
styleName="resume-link"
228-
target="_blank"
248+
<button
249+
type="button"
250+
data-candidate-id={candidate.id}
251+
data-resume-link={`${candidate.resume}`}
252+
onClick={onClickResumeLink}
253+
styleName={cn("resume-link", { busy: isTouchingResume })}
229254
>
230255
<IconResume />
231256
Download Resume
232-
</a>
257+
</button>
233258
)}
234259
</div>
235260
{statusFilterKey === CANDIDATE_STATUS_FILTER_KEY.INTERESTED && (

src/routes/PositionDetails/components/PositionCandidates/styles.module.scss

+12
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,24 @@
8181
display: inline-flex;
8282
align-items: center;
8383
margin-top: 5px;
84+
border: none;
85+
padding: 0;
86+
width: auto;
87+
height: auto;
88+
color: #0d61bf;
89+
background: transparent;
90+
outline: none !important;
91+
box-shadow: none !important;
8492

8593
> svg {
8694
margin-right: 10px;
8795
}
8896
}
8997

98+
.busy {
99+
cursor: wait;
100+
}
101+
90102
@media (max-width: 850px) {
91103
.table-row {
92104
flex-wrap: wrap;

src/services/requestInterceptor.js

+8
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,11 @@ axiosInstance.interceptors.response.use(
3535
return Promise.reject(error);
3636
}
3737
);
38+
39+
export const fetchCustom = async (url, init = {}) => {
40+
let { tokenV3 } = await getAuthUserTokens();
41+
let headers = init.headers || {};
42+
headers.Authorization = `Bearer ${tokenV3}`;
43+
init.headers = headers;
44+
return fetch(url, init);
45+
};

src/services/teams.js

+24-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
/**
22
* Topcoder TaaS Service
33
*/
4-
import { axiosInstance as axios } from "./requestInterceptor";
4+
import {
5+
axiosInstance as axios,
6+
fetchCustom as fetch,
7+
} from "./requestInterceptor";
8+
59
import config from "../../config";
610

711
/**
@@ -93,6 +97,25 @@ export const patchCandidateInterview = (candidateId, interviewData) => {
9397
);
9498
};
9599

100+
/**
101+
* Sends request to candidate's resume URL while trying to avoid downloading
102+
* the resume itself.
103+
*
104+
* @param {string} candidateId interview candidate id
105+
* @returns {Promise}
106+
*/
107+
export const touchCandidateResume = async (candidateId) => {
108+
try {
109+
// The result of redirect to different origin will not contain any useful
110+
// data. See https://fetch.spec.whatwg.org/#atomic-http-redirect-handling
111+
await fetch(`${config.API.V5}/jobCandidates/${candidateId}/resume`, {
112+
redirect: "manual",
113+
});
114+
} catch (error) {
115+
console.error(error);
116+
}
117+
};
118+
96119
/**
97120
* Get Team Members
98121
*

0 commit comments

Comments
 (0)