Skip to content

Commit 38c5eab

Browse files
authored
Merge pull request #1728 from topcoder-platform/develop
[PROD RELEASE] - AI & V2 engagements & Fixes
2 parents 1bbcbe5 + 015a292 commit 38c5eab

31 files changed

Lines changed: 1151 additions & 432 deletions

File tree

.circleci/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ parameters:
99

1010
defaults: &defaults
1111
docker:
12-
- image: cimg/python:3.11.11-browsers
12+
- image: cimg/python:3.12.12-browsers
1313

1414
test_defaults: &test_defaults
1515
docker:

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ node_modules
1212

1313
# production
1414
/build
15+
/dist
1516

1617
# misc
1718
.DS_Store
@@ -32,4 +33,6 @@ yarn-error.log*
3233

3334
# e2e test case
3435
test-automation/temp
35-
test-automation/test-results
36+
test-automation/test-results
37+
38+
dist

config/constants/development.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ module.exports = {
2020
ENGAGEMENTS_ROOT_API_URL: `${DEV_API_HOSTNAME}/v6/engagements`,
2121
APPLICATIONS_API_URL: `${DEV_API_HOSTNAME}/v6/engagements/applications`,
2222
TC_FINANCE_API_URL: process.env.TC_FINANCE_API_URL || `${API_V6}/finance`,
23+
TC_AI_API_BASE_URL: process.env.TC_AI_API_BASE_URL || `${API_V6}/ai`,
24+
TC_AI_SKILLS_EXTRACTION_WORKFLOW_ID: process.env.TC_AI_SKILLS_EXTRACTION_WORKFLOW_ID || 'skillExtractionWorkflow',
2325
CHALLENGE_DEFAULT_REVIEWERS_URL: `${DEV_API_HOSTNAME}/v6/challenge/default-reviewers`,
2426
CHALLENGE_API_VERSION: '1.1.0',
2527
CHALLENGE_TIMELINE_TEMPLATES_URL: `${DEV_API_HOSTNAME}/v6/timeline-templates`,

config/constants/production.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ module.exports = {
1919
ENGAGEMENTS_ROOT_API_URL: `${PROD_API_HOSTNAME}/v6/engagements`,
2020
APPLICATIONS_API_URL: `${PROD_API_HOSTNAME}/v6/engagements/applications`,
2121
TC_FINANCE_API_URL: process.env.TC_FINANCE_API_URL || `${API_V6}/finance`,
22+
TC_AI_API_BASE_URL: process.env.TC_AI_API_BASE_URL || `${API_V6}/ai`,
23+
TC_AI_SKILLS_EXTRACTION_WORKFLOW_ID: process.env.TC_AI_SKILLS_EXTRACTION_WORKFLOW_ID || 'skillExtractionWorkflow',
2224
CHALLENGE_DEFAULT_REVIEWERS_URL: `${PROD_API_HOSTNAME}/v6/challenge/default-reviewers`,
2325
CHALLENGE_API_VERSION: '1.1.0',
2426
CHALLENGE_TIMELINE_TEMPLATES_URL: `${PROD_API_HOSTNAME}/v6/timeline-templates`,

config/webpack.config.js

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ module.exports = function (webpackEnv) {
3737
const isEnvDevelopment = webpackEnv === 'development'
3838
const isEnvProduction = webpackEnv === 'production'
3939
const WM_DEBUG = /^(1|true|on|yes)$/i.test(String(process.env.WM_DEBUG || ''))
40-
const reactDevUtilsContextRegExp = /[\\/]react-dev-utils[\\/]/
4140

4241
// Webpack uses `publicPath` to determine where the app is being served from.
4342
// It requires a trailing slash, or the file assets will get an incorrect path.
@@ -150,7 +149,7 @@ module.exports = function (webpackEnv) {
150149
// require.resolve('webpack-dev-server/client') + '?/',
151150
// require.resolve('webpack/hot/dev-server'),
152151
isEnvDevelopment &&
153-
require.resolve('react-dev-utils/webpackHotDevClient'),
152+
path.resolve(__dirname, 'webpackHotDevClient'),
154153
// Finally, this is your app's code:
155154
paths.appIndexJs
156155
// We include the app code last so that if there is a runtime error during
@@ -485,13 +484,6 @@ module.exports = function (webpackEnv) {
485484
// This gives some necessary context to module not found errors, such as
486485
// the requesting resource.
487486
new ModuleNotFoundPlugin(paths.appPath),
488-
// Ensure the dev client tolerates webpack 5 warning/error objects.
489-
isEnvDevelopment &&
490-
new webpack.NormalModuleReplacementPlugin(/\.\/formatWebpackMessages$/, (resource) => {
491-
if (reactDevUtilsContextRegExp.test(resource.context || '')) {
492-
resource.request = path.resolve(__dirname, 'formatWebpackMessages')
493-
}
494-
}),
495487
// (DefinePlugin already added above with merged env)
496488
// This is necessary to emit hot updates (currently CSS only):
497489
isEnvDevelopment && new webpack.HotModuleReplacementPlugin(),

config/webpackHotDevClient.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
'use strict'
2+
3+
var patchedFormatWebpackMessages = require('./formatWebpackMessages')
4+
var originalFormatWebpackMessages = require('react-dev-utils/formatWebpackMessages')
5+
6+
// webpackHotDevClient requires react-dev-utils/formatWebpackMessages internally.
7+
// Replace that cached module export before loading the hot client so warnings
8+
// and errors can be normalized for webpack 5 object payloads.
9+
if (typeof __webpack_require__ === 'function' && __webpack_require__.c) {
10+
Object.keys(__webpack_require__.c).forEach(function(id) {
11+
var cachedModule = __webpack_require__.c[id]
12+
if (cachedModule && cachedModule.exports === originalFormatWebpackMessages) {
13+
cachedModule.exports = patchedFormatWebpackMessages
14+
}
15+
})
16+
}
17+
18+
require('react-dev-utils/webpackHotDevClient')

docker/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Use Node.js 22 base image
2-
FROM node:22
2+
FROM node:22.22.0
33
RUN useradd -m -s /bin/bash appuser
44
ARG NODE_ENV
55
ARG BABEL_ENV

docs/dev.env

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/actions/engagements.js

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
patchEngagement,
88
deleteEngagement as deleteEngagementAPI
99
} from '../services/engagements'
10+
import { fetchProjectById } from '../services/projects'
1011
import { fetchSkillsByIds } from '../services/skills'
1112
import {
1213
normalizeEngagement,
@@ -33,6 +34,8 @@ import {
3334
DELETE_ENGAGEMENT_FAILURE
3435
} from '../config/constants'
3536

37+
const projectNameCache = {}
38+
3639
const getSkillId = (skill) => {
3740
if (!skill) {
3841
return null
@@ -93,6 +96,70 @@ const withSkillDetails = (engagement, skillsMap) => {
9396
}
9497
}
9598

99+
const getProjectId = (engagement) => {
100+
if (!engagement || !engagement.projectId) {
101+
return null
102+
}
103+
return String(engagement.projectId)
104+
}
105+
106+
const getProjectName = (project) => {
107+
if (!project || typeof project !== 'object') {
108+
return null
109+
}
110+
if (typeof project.name === 'string' && project.name.trim()) {
111+
return project.name
112+
}
113+
if (typeof project.projectName === 'string' && project.projectName.trim()) {
114+
return project.projectName
115+
}
116+
return null
117+
}
118+
119+
const hydrateEngagementProjectNames = async (engagements = []) => {
120+
if (!Array.isArray(engagements) || !engagements.length) {
121+
return []
122+
}
123+
124+
const projectIds = Array.from(new Set(
125+
engagements
126+
.map(getProjectId)
127+
.filter(Boolean)
128+
))
129+
130+
if (!projectIds.length) {
131+
return engagements
132+
}
133+
134+
const uncachedProjectIds = projectIds.filter((projectId) => !projectNameCache[projectId])
135+
if (uncachedProjectIds.length) {
136+
const projectNameEntries = await Promise.all(
137+
uncachedProjectIds.map(async (projectId) => {
138+
try {
139+
const project = await fetchProjectById(projectId)
140+
return [projectId, getProjectName(project)]
141+
} catch (error) {
142+
return [projectId, null]
143+
}
144+
})
145+
)
146+
147+
projectNameEntries.forEach(([projectId, projectName]) => {
148+
if (projectName) {
149+
projectNameCache[projectId] = projectName
150+
}
151+
})
152+
}
153+
154+
return engagements.map((engagement) => {
155+
const projectId = getProjectId(engagement)
156+
return {
157+
...engagement,
158+
projectName: (projectId && projectNameCache[projectId]) || engagement.projectName || null
159+
}
160+
})
161+
}
162+
96163
const hydrateEngagementSkills = async (engagements = []) => {
97164
if (!Array.isArray(engagements) || !engagements.length) {
98165
return []
@@ -206,7 +273,8 @@ export function loadEngagements (projectId, status = 'all', filterName = '', inc
206273
} while (!totalPages || page <= totalPages)
207274

208275
const hydratedEngagements = await hydrateEngagementSkills(engagements)
209-
const normalizedEngagements = normalizeEngagements(hydratedEngagements)
276+
const engagementsWithProjectNames = await hydrateEngagementProjectNames(hydratedEngagements)
277+
const normalizedEngagements = normalizeEngagements(engagementsWithProjectNames)
210278
dispatch({
211279
type: LOAD_ENGAGEMENTS_SUCCESS,
212280
engagements: normalizedEngagements

src/components/ApplicationsList/index.js

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import DateInput from '../DateInput'
1212
import Handle from '../Handle'
1313
import styles from './ApplicationsList.module.scss'
1414
import { PROFILE_URL } from '../../config/constants'
15+
import { serializeTentativeAssignmentDate } from '../../util/assignmentDates'
1516

1617
const STATUS_OPTIONS = [
1718
{ label: 'All', value: 'all' },
@@ -99,6 +100,21 @@ const getApplicationName = (application) => {
99100
return fullName || application.name || application.email || null
100101
}
101102

103+
const getApplicationMobileNumber = (application) => {
104+
if (!application) {
105+
return null
106+
}
107+
108+
const value = [
109+
application.mobileNumber,
110+
application.mobile_number,
111+
application.phoneNumber,
112+
application.phone
113+
].find((phoneNumber) => phoneNumber != null && `${phoneNumber}`.trim() !== '')
114+
115+
return value ? `${value}`.trim() : null
116+
}
117+
102118
const getApplicationRating = (application) => {
103119
if (!application) {
104120
return undefined
@@ -303,9 +319,11 @@ const ApplicationsList = ({
303319

304320
setIsAccepting(true)
305321
try {
322+
const startDate = serializeTentativeAssignmentDate(parsedStart)
323+
const endDate = serializeTentativeAssignmentDate(parsedEnd)
306324
await onUpdateStatus(acceptApplication.id, 'SELECTED', {
307-
startDate: parsedStart.toISOString(),
308-
endDate: parsedEnd.toISOString(),
325+
startDate,
326+
endDate,
309327
agreementRate: normalizedRate,
310328
...(normalizedOtherRemarks ? { otherRemarks: normalizedOtherRemarks } : {})
311329
})
@@ -362,6 +380,7 @@ const ApplicationsList = ({
362380
value={acceptStartDate}
363381
dateFormat={INPUT_DATE_FORMAT}
364382
timeFormat={INPUT_TIME_FORMAT}
383+
preventViewportOverflow
365384
minDateTime={getMinStartDateTime}
366385
isValidDate={isAcceptStartDateValid}
367386
onChange={(value) => {
@@ -385,6 +404,7 @@ const ApplicationsList = ({
385404
value={acceptEndDate}
386405
dateFormat={INPUT_DATE_FORMAT}
387406
timeFormat={INPUT_TIME_FORMAT}
407+
preventViewportOverflow
388408
minDateTime={getMinEndDateTime}
389409
isValidDate={isAcceptEndDateValid}
390410
onChange={(value) => {
@@ -500,7 +520,7 @@ const ApplicationsList = ({
500520
<th>Email</th>
501521
<th>Applied Date</th>
502522
<th>Years of Experience</th>
503-
<th>Availability</th>
523+
<th>Phone Number</th>
504524
<th>Status</th>
505525
<th>Actions</th>
506526
</tr>
@@ -541,7 +561,7 @@ const ApplicationsList = ({
541561
<td>{application.email || '-'}</td>
542562
<td>{formatDateTime(application.createdAt)}</td>
543563
<td>{application.yearsOfExperience != null ? application.yearsOfExperience : '-'}</td>
544-
<td>{application.availability || '-'}</td>
564+
<td>{getApplicationMobileNumber(application) || '-'}</td>
545565
<td>
546566
<span className={`${styles.status} ${statusClass}`}>
547567
{statusLabel}

0 commit comments

Comments
 (0)