Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
52363ce
Missing assign members section on checkpoint reviewer setup (https://…
jmgasper Feb 6, 2026
7655a19
Show assignment of checkpoint reviewers on edit challenge page
jmgasper Feb 6, 2026
d44d9ba
v6 project API updates
jmgasper Feb 9, 2026
4105e35
Revert this change to not break dev environment
jmgasper Feb 9, 2026
2618327
Turn this down to avoid timeouts
jmgasper Feb 9, 2026
70fa065
Fix projects API route from local back to something that works.
jmgasper Feb 10, 2026
cc50fab
Change chunk size to avoid timeouts
jmgasper Feb 10, 2026
9263eb6
Change chunk size to avoid timeouts
jmgasper Feb 10, 2026
bcbcc8e
PM-3786 #time 5h Base service for calling workflow api
vas3a Feb 11, 2026
4638c97
PM-3786 #time 2h FE integration for ai-suggested skills for the skill…
vas3a Feb 11, 2026
ca3b234
Engagements updates for v2
jmgasper Feb 11, 2026
2989706
Further v2 engagement updates
jmgasper Feb 11, 2026
5aa2478
Checkbox alignment
jmgasper Feb 11, 2026
7d1d959
Merge branch 'develop' into chunk_size
jmgasper Feb 11, 2026
3d337d5
Merge pull request #1726 from topcoder-platform/chunk_size
jmgasper Feb 11, 2026
281a998
Build fix
jmgasper Feb 11, 2026
9613ac3
Build fix
jmgasper Feb 11, 2026
928a24d
Build fix
jmgasper Feb 11, 2026
ee9a63b
Build fix
jmgasper Feb 11, 2026
198c4dd
V2 engagement tweaks https://topcoder.atlassian.net/browse/PM-3699
jmgasper Feb 11, 2026
3a051ff
Further alignment tweak
jmgasper Feb 12, 2026
fd0ecba
Merge pull request #1725 from topcoder-platform/PM-3786_ai-assisted-s…
vas3a Feb 12, 2026
6feee77
PM-3786 #time 30min build & lint fixes
vas3a Feb 12, 2026
c091375
Merge pull request #1727 from topcoder-platform/PM-3786_ai-assisted-s…
vas3a Feb 12, 2026
052e0d5
PM-3485 #time 2h fixed columns and data mismatch in resources tab
hentrymartin Feb 12, 2026
c3a74f4
Update gitignore
jmgasper Feb 12, 2026
500e9b1
QA and requested tweaks
jmgasper Feb 12, 2026
aa60cbf
Add some prompts to the dropdowns to tell them apart
jmgasper Feb 12, 2026
cddfaf1
Merge pull request #1729 from topcoder-platform/pm-3485
hentrymartin Feb 13, 2026
015a292
clean up dist
kkartunov Feb 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ parameters:

defaults: &defaults
docker:
- image: cimg/python:3.11.11-browsers
- image: cimg/python:3.12.12-browsers

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[❗❗ correctness]
Upgrading the Python image from 3.11.11 to 3.12.12 may introduce compatibility issues with existing code or dependencies. Ensure that all dependencies and code are compatible with Python 3.12.12 and that thorough testing is conducted to verify this change.


test_defaults: &test_defaults
docker:
Expand Down
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ node_modules

# production
/build
/dist

# misc
.DS_Store
Expand All @@ -32,4 +33,6 @@ yarn-error.log*

# e2e test case
test-automation/temp
test-automation/test-results
test-automation/test-results

dist

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[💡 maintainability]
The entry dist is already included in the .gitignore under the # production section at line 15. This duplication is unnecessary and could lead to confusion. Consider removing the duplicate entry.

2 changes: 2 additions & 0 deletions config/constants/development.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ module.exports = {
ENGAGEMENTS_ROOT_API_URL: `${DEV_API_HOSTNAME}/v6/engagements`,
APPLICATIONS_API_URL: `${DEV_API_HOSTNAME}/v6/engagements/applications`,
TC_FINANCE_API_URL: process.env.TC_FINANCE_API_URL || `${API_V6}/finance`,
TC_AI_API_BASE_URL: process.env.TC_AI_API_BASE_URL || `${API_V6}/ai`,
TC_AI_SKILLS_EXTRACTION_WORKFLOW_ID: process.env.TC_AI_SKILLS_EXTRACTION_WORKFLOW_ID || 'skillExtractionWorkflow',
CHALLENGE_DEFAULT_REVIEWERS_URL: `${DEV_API_HOSTNAME}/v6/challenge/default-reviewers`,
CHALLENGE_API_VERSION: '1.1.0',
CHALLENGE_TIMELINE_TEMPLATES_URL: `${DEV_API_HOSTNAME}/v6/timeline-templates`,
Expand Down
2 changes: 2 additions & 0 deletions config/constants/production.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ module.exports = {
ENGAGEMENTS_ROOT_API_URL: `${PROD_API_HOSTNAME}/v6/engagements`,
APPLICATIONS_API_URL: `${PROD_API_HOSTNAME}/v6/engagements/applications`,
TC_FINANCE_API_URL: process.env.TC_FINANCE_API_URL || `${API_V6}/finance`,
TC_AI_API_BASE_URL: process.env.TC_AI_API_BASE_URL || `${API_V6}/ai`,
TC_AI_SKILLS_EXTRACTION_WORKFLOW_ID: process.env.TC_AI_SKILLS_EXTRACTION_WORKFLOW_ID || 'skillExtractionWorkflow',
CHALLENGE_DEFAULT_REVIEWERS_URL: `${PROD_API_HOSTNAME}/v6/challenge/default-reviewers`,
CHALLENGE_API_VERSION: '1.1.0',
CHALLENGE_TIMELINE_TEMPLATES_URL: `${PROD_API_HOSTNAME}/v6/timeline-templates`,
Expand Down
10 changes: 1 addition & 9 deletions config/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ module.exports = function (webpackEnv) {
const isEnvDevelopment = webpackEnv === 'development'
const isEnvProduction = webpackEnv === 'production'
const WM_DEBUG = /^(1|true|on|yes)$/i.test(String(process.env.WM_DEBUG || ''))
const reactDevUtilsContextRegExp = /[\\/]react-dev-utils[\\/]/

// Webpack uses `publicPath` to determine where the app is being served from.
// It requires a trailing slash, or the file assets will get an incorrect path.
Expand Down Expand Up @@ -150,7 +149,7 @@ module.exports = function (webpackEnv) {
// require.resolve('webpack-dev-server/client') + '?/',
// require.resolve('webpack/hot/dev-server'),
isEnvDevelopment &&
require.resolve('react-dev-utils/webpackHotDevClient'),
path.resolve(__dirname, 'webpackHotDevClient'),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[❗❗ correctness]
The change from require.resolve('react-dev-utils/webpackHotDevClient') to path.resolve(__dirname, 'webpackHotDevClient') could lead to potential issues if the webpackHotDevClient file is not present in the expected directory. Ensure that this file is correctly located and that any differences in behavior between the resolved module and the local file are intentional.

// Finally, this is your app's code:
paths.appIndexJs
// We include the app code last so that if there is a runtime error during
Expand Down Expand Up @@ -485,13 +484,6 @@ module.exports = function (webpackEnv) {
// This gives some necessary context to module not found errors, such as
// the requesting resource.
new ModuleNotFoundPlugin(paths.appPath),
// Ensure the dev client tolerates webpack 5 warning/error objects.
isEnvDevelopment &&
new webpack.NormalModuleReplacementPlugin(/\.\/formatWebpackMessages$/, (resource) => {
if (reactDevUtilsContextRegExp.test(resource.context || '')) {
resource.request = path.resolve(__dirname, 'formatWebpackMessages')
}
}),
// (DefinePlugin already added above with merged env)
// This is necessary to emit hot updates (currently CSS only):
isEnvDevelopment && new webpack.HotModuleReplacementPlugin(),
Expand Down
18 changes: 18 additions & 0 deletions config/webpackHotDevClient.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
'use strict'

var patchedFormatWebpackMessages = require('./formatWebpackMessages')

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[💡 maintainability]
Consider using const instead of var for patchedFormatWebpackMessages to prevent accidental reassignment and improve code clarity.

var originalFormatWebpackMessages = require('react-dev-utils/formatWebpackMessages')

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[💡 maintainability]
Consider using const instead of var for originalFormatWebpackMessages to prevent accidental reassignment and improve code clarity.


// webpackHotDevClient requires react-dev-utils/formatWebpackMessages internally.
// Replace that cached module export before loading the hot client so warnings
// and errors can be normalized for webpack 5 object payloads.
if (typeof __webpack_require__ === 'function' && __webpack_require__.c) {
Object.keys(__webpack_require__.c).forEach(function(id) {
var cachedModule = __webpack_require__.c[id]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[💡 maintainability]
Consider using const instead of var for cachedModule since it is not reassigned within the loop, which can help with readability and prevent potential errors.

if (cachedModule && cachedModule.exports === originalFormatWebpackMessages) {
cachedModule.exports = patchedFormatWebpackMessages
}
})
}

require('react-dev-utils/webpackHotDevClient')
2 changes: 1 addition & 1 deletion docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Use Node.js 22 base image
FROM node:22
FROM node:22.22.0
RUN useradd -m -s /bin/bash appuser
ARG NODE_ENV
ARG BABEL_ENV
Expand Down
1 change: 0 additions & 1 deletion docs/dev.env

This file was deleted.

70 changes: 69 additions & 1 deletion src/actions/engagements.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
patchEngagement,
deleteEngagement as deleteEngagementAPI
} from '../services/engagements'
import { fetchProjectById } from '../services/projects'
import { fetchSkillsByIds } from '../services/skills'
import {
normalizeEngagement,
Expand All @@ -33,6 +34,8 @@ import {
DELETE_ENGAGEMENT_FAILURE
} from '../config/constants'

const projectNameCache = {}

const getSkillId = (skill) => {
if (!skill) {
return null
Expand Down Expand Up @@ -93,6 +96,70 @@ const withSkillDetails = (engagement, skillsMap) => {
}
}

const getProjectId = (engagement) => {
if (!engagement || !engagement.projectId) {
return null
}
return String(engagement.projectId)
}

const getProjectName = (project) => {
if (!project || typeof project !== 'object') {
return null
}
if (typeof project.name === 'string' && project.name.trim()) {
return project.name
}
if (typeof project.projectName === 'string' && project.projectName.trim()) {
return project.projectName
}
return null
}

const hydrateEngagementProjectNames = async (engagements = []) => {
if (!Array.isArray(engagements) || !engagements.length) {
return []
}

const projectIds = Array.from(new Set(
engagements
.map(getProjectId)
.filter(Boolean)
))

if (!projectIds.length) {
return engagements
}

const uncachedProjectIds = projectIds.filter((projectId) => !projectNameCache[projectId])
if (uncachedProjectIds.length) {
const projectNameEntries = await Promise.all(
uncachedProjectIds.map(async (projectId) => {
try {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ maintainability]
Consider adding logging or handling for the error case in fetchProjectById. Currently, if an error occurs, it silently returns null, which might make debugging difficult.

const project = await fetchProjectById(projectId)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[❗❗ correctness]
Ensure that fetchProjectById handles network errors and returns a consistent response. If it throws an error, it might cause the hydrateEngagementProjectNames function to fail unexpectedly.

return [projectId, getProjectName(project)]
} catch (error) {
return [projectId, null]
}
})
)

projectNameEntries.forEach(([projectId, projectName]) => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ performance]
The projectNameCache is a global object that could grow indefinitely if the application runs for a long time or processes many projects. Consider implementing a cache eviction strategy to prevent potential memory issues.

if (projectName) {
projectNameCache[projectId] = projectName
}
})
}

return engagements.map((engagement) => {
const projectId = getProjectId(engagement)
return {
...engagement,
projectName: (projectId && projectNameCache[projectId]) || engagement.projectName || null
}
})
}

const hydrateEngagementSkills = async (engagements = []) => {
if (!Array.isArray(engagements) || !engagements.length) {
return []
Expand Down Expand Up @@ -206,7 +273,8 @@ export function loadEngagements (projectId, status = 'all', filterName = '', inc
} while (!totalPages || page <= totalPages)

const hydratedEngagements = await hydrateEngagementSkills(engagements)
const normalizedEngagements = normalizeEngagements(hydratedEngagements)
const engagementsWithProjectNames = await hydrateEngagementProjectNames(hydratedEngagements)
const normalizedEngagements = normalizeEngagements(engagementsWithProjectNames)
dispatch({
type: LOAD_ENGAGEMENTS_SUCCESS,
engagements: normalizedEngagements
Expand Down
28 changes: 24 additions & 4 deletions src/components/ApplicationsList/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import DateInput from '../DateInput'
import Handle from '../Handle'
import styles from './ApplicationsList.module.scss'
import { PROFILE_URL } from '../../config/constants'
import { serializeTentativeAssignmentDate } from '../../util/assignmentDates'

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

const getApplicationMobileNumber = (application) => {
if (!application) {
return null
}

const value = [
application.mobileNumber,
application.mobile_number,
application.phoneNumber,
application.phone
].find((phoneNumber) => phoneNumber != null && `${phoneNumber}`.trim() !== '')

return value ? `${value}`.trim() : null
}

const getApplicationRating = (application) => {
if (!application) {
return undefined
Expand Down Expand Up @@ -303,9 +319,11 @@ const ApplicationsList = ({

setIsAccepting(true)
try {
const startDate = serializeTentativeAssignmentDate(parsedStart)
const endDate = serializeTentativeAssignmentDate(parsedEnd)
await onUpdateStatus(acceptApplication.id, 'SELECTED', {
startDate: parsedStart.toISOString(),
endDate: parsedEnd.toISOString(),
startDate,
endDate,
agreementRate: normalizedRate,
...(normalizedOtherRemarks ? { otherRemarks: normalizedOtherRemarks } : {})
})
Expand Down Expand Up @@ -362,6 +380,7 @@ const ApplicationsList = ({
value={acceptStartDate}
dateFormat={INPUT_DATE_FORMAT}
timeFormat={INPUT_TIME_FORMAT}
preventViewportOverflow
minDateTime={getMinStartDateTime}
isValidDate={isAcceptStartDateValid}
onChange={(value) => {
Expand All @@ -385,6 +404,7 @@ const ApplicationsList = ({
value={acceptEndDate}
dateFormat={INPUT_DATE_FORMAT}
timeFormat={INPUT_TIME_FORMAT}
preventViewportOverflow
minDateTime={getMinEndDateTime}
isValidDate={isAcceptEndDateValid}
onChange={(value) => {
Expand Down Expand Up @@ -500,7 +520,7 @@ const ApplicationsList = ({
<th>Email</th>
<th>Applied Date</th>
<th>Years of Experience</th>
<th>Availability</th>
<th>Phone Number</th>
<th>Status</th>
<th>Actions</th>
</tr>
Expand Down Expand Up @@ -541,7 +561,7 @@ const ApplicationsList = ({
<td>{application.email || '-'}</td>
<td>{formatDateTime(application.createdAt)}</td>
<td>{application.yearsOfExperience != null ? application.yearsOfExperience : '-'}</td>
<td>{application.availability || '-'}</td>
<td>{getApplicationMobileNumber(application) || '-'}</td>
<td>
<span className={`${styles.status} ${statusClass}`}>
{statusLabel}
Expand Down
13 changes: 9 additions & 4 deletions src/components/ChallengeEditor/ChallengeReviewer-Field/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ class ChallengeReviewerField extends Component {
)
}

isPublicOpportunityOpen (reviewer) {
return reviewer && reviewer.shouldOpenOpportunity === true
}

getMissingRequiredPhases () {
const { challenge } = this.props
// Marathon Match does not require review configuration
Expand Down Expand Up @@ -747,7 +751,8 @@ class ChallengeReviewerField extends Component {
fixedAmount: currentReviewer.fixedAmount || 0,
baseCoefficient: currentReviewer.baseCoefficient || '0',
incrementalCoefficient: currentReviewer.incrementalCoefficient || 0,
type: isAI ? undefined : (currentReviewer.type || REVIEW_OPPORTUNITY_TYPES.REGULAR_REVIEW)
type: isAI ? undefined : (currentReviewer.type || REVIEW_OPPORTUNITY_TYPES.REGULAR_REVIEW),
shouldOpenOpportunity: isAI ? undefined : false
}

if (isAI) {
Expand Down Expand Up @@ -972,7 +977,7 @@ class ChallengeReviewerField extends Component {
<input
type='checkbox'
disabled={readOnly}
checked={reviewer.shouldOpenOpportunity !== false}
checked={this.isPublicOpportunityOpen(reviewer)}
onChange={(e) => {
const next = !!e.target.checked
this.handleToggleShouldOpen(index, next)
Expand All @@ -987,8 +992,8 @@ class ChallengeReviewerField extends Component {
</div>
)}

{/* Assignment controls when public opportunity is OFF */}
{!this.isAIReviewer(reviewer) && (reviewer.shouldOpenOpportunity === false) && (
{/* Design challenges do not expose public opportunity toggles, so always allow member assignment there. */}
{!this.isAIReviewer(reviewer) && (isDesignChallenge || !this.isPublicOpportunityOpen(reviewer)) && (
<div className={styles.formRow}>
<div className={styles.formGroup}>
<label>Assign member(s):</label>
Expand Down
8 changes: 5 additions & 3 deletions src/components/ChallengeEditor/Resources/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -461,9 +461,11 @@ export default class Resources extends React.Component {
{sortedResources.map(r => {
return (
<tr className={styles.rowTable} key={r.id} role='row'>
<td className={styles['col-2Table']}>
<span role='cell'>{r.role}</span>
</td>
{!isDesign && (
<td className={styles['col-2Table']}>
<span role='cell'>{r.role}</span>
</td>
)}
<td className={styles['col-3Table']}>
<span role='cell'>
<a
Expand Down
Loading
Loading