Skip to content

Comments

New work app to replace work-manager, and v6 projects API usage in copilots app#1487

Open
jmgasper wants to merge 35 commits intodevfrom
work-manager
Open

New work app to replace work-manager, and v6 projects API usage in copilots app#1487
jmgasper wants to merge 35 commits intodevfrom
work-manager

Conversation

@jmgasper
Copy link
Collaborator

@jmgasper jmgasper commented Feb 20, 2026

@jmgasper jmgasper requested a review from kkartunov as a code owner February 20, 2026 03:14
@@ -0,0 +1,5 @@
REACT_APP_GROUPS_API_URL=https://api.topcoder.com/v6/groups
REACT_APP_TERMS_API_URL=https://api.topcoder.com/v5/terms

Choose a reason for hiding this comment

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

[❗❗ correctness]
The API version for REACT_APP_TERMS_API_URL is v5, while others are v6. Ensure this is intentional and that the correct API version is being used.

height: 400,
menubar: false,
plugins: ['table', 'link', 'textcolor', 'contextmenu'],
plugins: ['table', 'link'],

Choose a reason for hiding this comment

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

[⚠️ correctness]
The removal of the textcolor plugin from the TinyMCE editor configuration might impact users who rely on text color customization. If this change is intentional, ensure that there are no requirements for text color editing. Otherwise, consider re-adding the plugin to maintain existing functionality.

height: 400,
menubar: false,
plugins: ['table', 'link', 'textcolor', 'contextmenu'],
plugins: ['table', 'link'],

Choose a reason for hiding this comment

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

[⚠️ correctness]
The removal of the contextmenu plugin could affect the user experience by disabling right-click context menus in the editor. Verify if this change aligns with the desired functionality and user requirements.

const viewRequestDetails = useMemo(() => (
routeParams.requestId && find(requests, { id: +routeParams.requestId }) as CopilotRequest
routeParams.requestId
? find(requests, request => `${request.id}` === routeParams.requestId) as CopilotRequest

Choose a reason for hiding this comment

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

[💡 performance]
The use of string interpolation to compare request.id with routeParams.requestId could be avoided if routeParams.requestId is already a string. Consider ensuring request.id is a string when stored or retrieved, or use a more explicit conversion method if necessary.

import { CopilotRequest } from '../models/CopilotRequest'

const baseUrl = `${EnvironmentConfig.API.V5}/projects`
const baseUrl = `${EnvironmentConfig.API.V6}/projects`

Choose a reason for hiding this comment

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

[❗❗ correctness]
Ensure that all endpoints and related logic are compatible with the new API version V6. This change might require additional updates elsewhere in the codebase to handle any differences in API responses or behavior.

createdAt: new Date(data.createdAt),
data: undefined,
opportunity: data.copilotOpportunity?.[0],
projectId: String(data?.projectId ?? data?.data?.projectId ?? ''),

Choose a reason for hiding this comment

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

[⚠️ correctness]
The use of String(data?.projectId ?? data?.data?.projectId ?? '') could lead to unexpected results if projectId is 0 or false. Consider explicitly checking for undefined or null to avoid coercing falsy values.


export type ProjectsResponse = SWRResponse<Project[], Project[]>

const sleep = (ms: number): Promise<()=> void> => new Promise(resolve => { setTimeout(resolve, ms) })

Choose a reason for hiding this comment

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

[⚠️ correctness]
The sleep function returns a Promise<() => void>, which is misleading because the resolved value is actually undefined. Consider changing the return type to Promise<void> for clarity.

const sleep = (ms: number): Promise<()=> void> => new Promise(resolve => { setTimeout(resolve, ms) })
const normalizeProject = (project: Project): Project => ({
...project,
id: String(project.id),

Choose a reason for hiding this comment

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

[⚠️ correctness]
The normalizeProject function converts project.id to a string. Ensure that this conversion is necessary and that all parts of the application expect id to be a string. If id is used as a number elsewhere, this could lead to inconsistencies.

export const PageWrapper: FC<PropsWithChildren<Props>> = props => (
<div className={classNames(styles.container, props.className)}>
<BreadCrumb list={props.breadCrumb} />
{props.breadCrumb.length > 0 && (

Choose a reason for hiding this comment

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

[⚠️ correctness]
The check props.breadCrumb.length > 0 is a good optimization to avoid rendering an empty BreadCrumb component. However, ensure that props.breadCrumb is always initialized as an array to prevent potential runtime errors.

{props.pageTitle}
</h3>
</PageHeader>
{props.titleAction

Choose a reason for hiding this comment

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

[💡 readability]
The conditional rendering of props.titleAction using a ternary operator is correct, but the use of undefined as the fallback is unnecessary. Consider using a logical AND (&&) operator for cleaner code: {props.titleAction && <div className={styles.blockTitleAction}>{props.titleAction}</div>}.


const WorkApp: FC = () => {
const { getChildRoutes }: RouterContextData = useContext(routerContext)
const childRoutes = useMemo(() => getChildRoutes(toolTitle), [getChildRoutes])

Choose a reason for hiding this comment

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

[💡 performance]
Using useMemo here is unnecessary unless getChildRoutes is computationally expensive or has side effects. Consider removing useMemo if it's not needed for performance optimization.

const childRoutes = useMemo(() => getChildRoutes(toolTitle), [getChildRoutes])

useEffect(() => {
document.body.classList.add(WORK_APP_BODY_CLASS)

Choose a reason for hiding this comment

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

[⚠️ maintainability]
Directly manipulating the document.body.classList can lead to unexpected side effects, especially if multiple components are modifying the class list. Consider using a state management solution or a library like classnames to manage body classes more predictably.

export const TABLE_DATE_FORMAT = 'MMM DD YYYY, HH:mm A'

export const PAGINATION_PER_PAGE_OPTIONS = [
{ label: '5', value: '5' },

Choose a reason for hiding this comment

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

[⚠️ correctness]
The value in PAGINATION_PER_PAGE_OPTIONS is a string, but it might be more appropriate to use a number type for consistency and to avoid potential type-related issues when using these values for pagination logic.

@@ -0,0 +1,28 @@
import { AppSubdomain, EnvironmentConfig } from '~/config'

export const rootRoute: string

Choose a reason for hiding this comment

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

[💡 maintainability]
Consider using a function to determine rootRoute instead of a ternary operator. This could improve readability and maintainability, especially if the logic becomes more complex in the future.

const defaultDate = new Date()
defaultDate.setHours(0, 0, 0, 0)

const daysUntilSaturday = (SATURDAY_DAY_INDEX - defaultDate.getDay() + 7) % 7

Choose a reason for hiding this comment

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

[❗❗ correctness]
The calculation for daysUntilSaturday could be simplified by using (SATURDAY_DAY_INDEX - defaultDate.getDay() + 7) % 7. This ensures that the result is always non-negative, which is crucial for correctly setting the date.

const [remarks, setRemarks] = useState<string>('')
const [weekEndingDate, setWeekEndingDate] = useState<Date | null>(() => getDefaultWeekEndingDate())

const paymentTitle = useMemo(

Choose a reason for hiding this comment

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

[⚠️ correctness]
The useMemo hook for paymentTitle depends on weekEndingDate, props.projectName, and props.engagementName. Ensure that these dependencies are correctly updated to prevent stale values from being used.

}, [])

useEffect(() => {
if (!props.open) {

Choose a reason for hiding this comment

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

[⚠️ design]
The useEffect hook resets the state whenever props.open changes. Consider whether this is the desired behavior, as it might reset the form unexpectedly if open is toggled.

customInput={<WeekEndingInput />}
dateFormat='MM/dd/yyyy'
disabled={isSubmitting}
filterDate={isSaturday}

Choose a reason for hiding this comment

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

[💡 usability]
The filterDate function isSaturday ensures that only Saturdays can be selected. This is a good constraint, but ensure that users are aware of this restriction, perhaps through UI hints or documentation.

<ul className={styles.list}>
{paymentsResult.payments.map((payment, index) => {
const paymentStatus = getPaymentStatus(payment)
const normalizedPaymentStatus = paymentStatus

Choose a reason for hiding this comment

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

[⚠️ maintainability]
The trim() and toLowerCase() operations on paymentStatus could be moved into getPaymentStatus if this normalization is always required. This would improve maintainability by centralizing the logic.

import { xhrGetAsync } from '~/libs/core'

export interface BillingAccount {
active?: boolean

Choose a reason for hiding this comment

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

[⚠️ correctness]
The active property is optional, but its type is not specified. Consider specifying a boolean type for clarity and to avoid potential type-related issues.


export interface BillingAccount {
active?: boolean
endDate?: string

Choose a reason for hiding this comment

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

[⚠️ correctness]
The endDate property is optional and typed as a string. Consider specifying the expected date format or using a Date object to ensure consistent handling of date values.

endDate?: string
id: number | string
name: string
startDate?: string

Choose a reason for hiding this comment

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

[⚠️ correctness]
The startDate property is optional and typed as a string. Consider specifying the expected date format or using a Date object to ensure consistent handling of date values.

id: number | string
name: string
startDate?: string
status?: string

Choose a reason for hiding this comment

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

[⚠️ maintainability]
The status property is optional and typed as a string. Consider defining an enumeration for possible status values to improve type safety and maintainability.


await Promise.all(
projectIds.map(async projectId => {
if (projectNameByProjectId.has(projectId)) {

Choose a reason for hiding this comment

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

[⚠️ correctness]
Consider checking if projectId is a valid string before using it in fetchProjectById. This could prevent unnecessary API calls with invalid IDs.

projectNameByProjectId.set(projectId, projectName)
}
} catch {
// Project lookup failures should not break engagement loading.

Choose a reason for hiding this comment

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

[⚠️ maintainability]
Swallowing all errors in the catch block without logging or handling them might make debugging difficult. Consider logging the error or handling specific error cases.

}

const projectId = getEngagementProjectId(engagement)
const projectName = projectNameByProjectId.get(projectId)

Choose a reason for hiding this comment

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

[⚠️ correctness]
Accessing projectNameByProjectId without checking if projectId is a valid string could lead to unexpected behavior. Ensure projectId is valid before using it as a key.

}
}

if (typedError?.response?.status !== 403) {

Choose a reason for hiding this comment

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

[⚠️ correctness]
The check for typedError?.response?.status !== 403 should be more explicit. Consider using typedError?.response?.status !== 403 to ensure the status is exactly 403, as other 4xx errors might also indicate authorization issues.

}

if (
normalizedBillingAccount.active === undefined

Choose a reason for hiding this comment

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

[💡 maintainability]
The condition in the if statement checks for multiple properties being undefined or falsy. Consider using a utility function to improve readability and maintainability.


return extractProjectTypes(response)
} catch (error) {
if (hasAuthorizationMetadataError(error)) {

Choose a reason for hiding this comment

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

[⚠️ correctness]
The fallback mechanism for fetching project types when an authorization error occurs is a good approach. However, ensure that the fallback URL (PROJECT_METADATA_API_URL) is correct and that it provides the expected data structure.

size='sm'
/>
</Link>
{assignedStatus

Choose a reason for hiding this comment

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

[⚠️ maintainability]
The conditional rendering of the assignedStatus block is duplicated for both leftActions and rightActions. Consider refactoring this logic to avoid repetition and improve maintainability.


<PaymentFormModal
billingAccountId={projectResult.project?.billingAccountId}
engagementName={engagementResult.engagement?.title}

Choose a reason for hiding this comment

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

[💡 correctness]
Ensure that engagementResult.engagement?.title is always defined before passing it to PaymentFormModal. If there's a chance it could be undefined, consider providing a default value or handling the case where it is not available.

onCancel={() => setPaymentMember(undefined)}
onConfirm={handlePaymentSubmit}
open={!!paymentMember}
projectName={projectResult.project?.name}

Choose a reason for hiding this comment

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

[💡 correctness]
Ensure that projectResult.project?.name is always defined before passing it to PaymentFormModal. If there's a chance it could be undefined, consider providing a default value or handling the case where it is not available.

return (
<PageWrapper
backUrl={backUrl}
breadCrumb={[]}

Choose a reason for hiding this comment

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

[⚠️ maintainability]
The breadCrumb prop is now set to an empty array, which might lead to a loss of navigation context for users. If the breadcrumb functionality is intended to be removed, ensure that this change is communicated to the team and any dependent components or tests are updated accordingly.

}
}

const currentBillingAccount = params.billingAccounts.find(

Choose a reason for hiding this comment

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

[⚠️ correctness]
Consider using Array.prototype.find with a type guard or a more specific type assertion to ensure currentBillingAccount is of type BillingAccount. This will help prevent runtime errors if billingAccounts contains unexpected types.

const currentBillingAccount = params.billingAccounts.find(
billingAccount => String(billingAccount.id) === params.currentBillingAccountId,
)
const billingAccountWithDetails: ProjectBillingAccount | BillingAccount | undefined

Choose a reason for hiding this comment

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

[⚠️ design]
The variable billingAccountWithDetails is assigned using a fallback pattern. Ensure that params.projectBillingAccount and currentBillingAccount are mutually exclusive or handle cases where both might be defined to avoid unexpected behavior.

}

return {
endDate: getBillingAccountDate(billingAccountWithDetails, 'endDate'),

Choose a reason for hiding this comment

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

[⚠️ correctness]
The function getBillingAccountDate is called with billingAccountWithDetails which can be either ProjectBillingAccount or BillingAccount. Ensure that both types have the expected properties (endDate and startDate) to avoid potential runtime errors.

return {
endDate: getBillingAccountDate(billingAccountWithDetails, 'endDate'),
id: params.currentBillingAccountId,
name: resolvedBillingAccountName || getBillingAccountName(billingAccountWithDetails),

Choose a reason for hiding this comment

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

[💡 maintainability]
The resolvedBillingAccountName is computed using multiple fallbacks. Consider logging or handling cases where all fallbacks result in undefined to improve debugging and ensure data integrity.

const savedChallenge = await createChallenge({
name: formData.name,
projectId: createProjectId,
status: CHALLENGE_STATUS.NEW,

Choose a reason for hiding this comment

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

[❗❗ correctness]
The removal of roundType from the createChallenge payload may impact the challenge creation logic if roundType is required or used elsewhere in the application. Ensure that this change is intentional and that roundType is not needed for the challenge creation process.

}

downloadProfileAsync(application.handle)
.catch(() => undefined)

Choose a reason for hiding this comment

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

[⚠️ maintainability]
Catching and ignoring all errors with .catch(() => undefined) can make debugging difficult. Consider logging the error or handling specific error cases.

</div>
<div>
<span className={styles.label}>Experience (years)</span>
<span className={styles.value}>{application.yearsOfExperience ?? '-'}</span>

Choose a reason for hiding this comment

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

[⚠️ correctness]
Using the nullish coalescing operator (??) is appropriate here, but ensure that application.yearsOfExperience is not intended to be 0, as 0 will be replaced by '-'. If 0 is a valid value, consider using a different check.

@@ -0,0 +1,96 @@
import { BackendChallengeInfo, convertBackendChallengeInfo } from './BackendChallengeInfo.model'

jest.mock('~/config', () => ({

Choose a reason for hiding this comment

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

[⚠️ correctness]
The mock for EnvironmentConfig is an empty object. If any tests rely on specific configuration values, this could lead to unexpected test behavior. Ensure that this mock is sufficient for all test cases.

}), { virtual: true })

jest.mock('~/libs/core', () => ({
getRatingColor: jest.fn()

Choose a reason for hiding this comment

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

[⚠️ correctness]
The mock for getRatingColor always returns '#000000'. If the function is expected to return different values based on input, consider parameterizing the mock to test various scenarios.


import { formatDurationDate } from '../utils'
import { SUBMISSION_TYPE_CONTEST } from '../constants'
import { isContestSubmissionType } from '../constants'

Choose a reason for hiding this comment

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

[❗❗ correctness]
The change from SUBMISSION_TYPE_CONTEST to isContestSubmissionType suggests a shift from a constant to a function call. Ensure that isContestSubmissionType is correctly implemented and that it handles all necessary edge cases, as this could impact the correctness of filtering contest submissions.

(winner.type ?? SUBMISSION_TYPE_CONTEST) === SUBMISSION_TYPE_CONTEST
const contestWinners = winners.filter(winner => isContestSubmissionType(
winner.type,
{ defaultToContest: true },

Choose a reason for hiding this comment

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

[⚠️ design]
The use of { defaultToContest: true } in the isContestSubmissionType function call introduces a new behavior. Verify that this default behavior aligns with the intended logic and does not inadvertently include non-contest submissions.

scheduledStartDate: string,
overrides: Partial<BackendPhase> = {},
): BackendPhase => ({
actualEndDate: overrides.actualEndDate ?? overrides.scheduledEndDate ?? scheduledStartDate,

Choose a reason for hiding this comment

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

[⚠️ correctness]
The use of ?? for actualEndDate and actualStartDate is correct, but consider if there are any edge cases where scheduledEndDate or scheduledStartDate could be null or undefined. If so, ensure that the fallback logic is appropriate for all scenarios.

description: overrides.description ?? '',
duration: overrides.duration ?? 0,
id,
isOpen: overrides.isOpen ?? false,

Choose a reason for hiding this comment

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

[⚠️ correctness]
The isOpen property defaults to false. Ensure that this default behavior aligns with the intended logic for all phases, especially if there are phases that should be open by default.

isOpen: overrides.isOpen ?? false,
name,
phaseId: overrides.phaseId ?? id,
predecessor: overrides.predecessor,

Choose a reason for hiding this comment

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

[⚠️ security]
The predecessor property is directly assigned from overrides. Consider validating or sanitizing this input if it comes from an external or untrusted source to prevent potential issues.


const normalizedStatus = (status || '').toUpperCase()
if (normalizedStatus.startsWith('COMPLETED')) {
const hasOpenPhase = orderedPhases.some(phase => phase?.isOpen === true)

Choose a reason for hiding this comment

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

[⚠️ correctness]
The variable hasOpenPhase is calculated using orderedPhases, which is derived from phases. Ensure that orderPhasesForTabs does not alter the isOpen property of any phase, as this could lead to incorrect logic when determining if any phase is still open.

return identifiers
}

const phaseLookup = new Map<string, BackendPhase>()

Choose a reason for hiding this comment

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

[💡 readability]
Consider using phases.reduce instead of phases.forEach to populate phaseLookup. This can make the code more concise and potentially improve readability.

}

phases.forEach(phase => {
if (!phase?.isOpen) {

Choose a reason for hiding this comment

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

[⚠️ correctness]
The logic assumes that a phase's predecessor is always valid and present in phaseLookup. Consider adding a check to ensure that phaseLookup.get(predecessorId) returns a valid phase before calling addPhaseIdentifiers.

scheduledStartDate: '2026-01-01T00:00:00.000Z',
}

const buildSubmission = (type: string): SubmissionInfo => ({

Choose a reason for hiding this comment

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

[⚠️ maintainability]
The buildSubmission function uses hardcoded values for properties like id, memberId, and review.id. Consider parameterizing these values to make the function more flexible and reusable for different test scenarios.

BackendPhase,
SubmissionInfo,
} from '../models'
import { isContestSubmissionType } from '../constants'

Choose a reason for hiding this comment

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

[❗❗ correctness]
Ensure that isContestSubmissionType is correctly imported from ../constants. If this function is not defined or incorrectly implemented, it could lead to incorrect behavior in isContestReviewPhaseSubmission.


import { calculateReviewProgress } from './reviewProgress'

jest.mock('~/config', () => ({

Choose a reason for hiding this comment

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

[⚠️ correctness]
Mocking EnvironmentConfig as an empty object might lead to unexpected behavior if any tests rely on specific configuration values. Consider providing default values or mocking only the necessary parts.

}), { virtual: true })

jest.mock('~/libs/core', () => ({
getRatingColor: jest.fn()

Choose a reason for hiding this comment

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

[⚠️ correctness]
The mock for getRatingColor always returns #000000. If the function is expected to return different values based on input, consider parameterizing the mock to test different scenarios.


import { shouldIncludeInReviewPhase } from './reviewPhaseGuards'

const normalizeScreeningResult = (result?: string | null): string => (result ?? '')

Choose a reason for hiding this comment

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

[💡 correctness]
The normalizeScreeningResult function uses toUpperCase() without considering locale-specific variations. If the input might contain locale-specific characters, consider using toLocaleUpperCase() for better internationalization support.

.trim()
.toUpperCase()

const resolveReviewSubmissionIds = (submission: SubmissionInfo): string[] => {

Choose a reason for hiding this comment

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

[💡 performance]
The resolveReviewSubmissionIds function uses Set to collect candidate IDs, which is efficient for uniqueness. However, the function returns an array, which might lead to unnecessary conversion if the caller only needs to check for existence. Consider returning a Set directly if the use case allows.

return true
}

const isCompletedReviewSubmission = (submission: SubmissionInfo): boolean => {

Choose a reason for hiding this comment

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

[⚠️ correctness]
The isCompletedReviewSubmission function checks for committed and status in a nested manner. If both properties can exist simultaneously, consider clarifying the precedence or combining the checks to avoid potential logic errors.

return 0
}

const reviewPhaseRows = reviewRows.filter(submission => shouldIncludeInReviewPhase(

Choose a reason for hiding this comment

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

[❗❗ correctness]
The shouldIncludeInReviewPhase function is used to filter reviewRows. Ensure that this function is thoroughly tested, as it plays a critical role in determining which submissions are considered for review progress calculation.

return 0
}

const progressRows = isDesignChallenge

Choose a reason for hiding this comment

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

[💡 readability]
The logic for filtering progressRows based on isDesignChallenge could be more explicit. Consider using a named function or a clearer conditional structure to improve readability and maintainability.

isSubmitterView: boolean,
submissions: Array<Screening | SubmissionInfo>,
myMemberIds: Set<string>,
minimumPassingScore: number | null | undefined,

Choose a reason for hiding this comment

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

[⚠️ correctness]
The parameter minimumPassingScore is typed as number | null | undefined, but the function hasSubmitterPassedThreshold expects a number | null | undefined as well. Ensure that parseFiniteNumber is used consistently to handle null or undefined values safely within hasSubmitterPassedThreshold.

'Screening',
'scorecard-screening',
new Set(['phase-screening']),
{} as BackendSubmission,

Choose a reason for hiding this comment

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

[⚠️ correctness]
The use of {} as BackendSubmission to cast an empty object to BackendSubmission may hide potential issues if the selectBestReview function relies on specific properties of BackendSubmission. Consider providing a more realistic mock object that matches the expected structure of BackendSubmission.

return typeof score === 'number' ? score : -Infinity
},
(review: BackendReview) => {
const updatedAt = review.updatedAt || review.reviewDate || review.createdAt

Choose a reason for hiding this comment

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

[⚠️ correctness]
The updatedAt, reviewDate, and createdAt fields are used to determine the most recent review. Ensure these fields are always in a valid date format to avoid potential NaN values from Date.parse(), which could lead to incorrect sorting.


const createScreeningRow = (overrides: Partial<Screening> = {}): Screening => ({
challengeId: 'challenge-id',
createdAt: '2025-12-08T18:00:00.000Z',

Choose a reason for hiding this comment

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

[💡 maintainability]
The createdAt field is hardcoded with a future date (2025-12-08T18:00:00.000Z). Consider using a dynamic date or a more realistic static date to avoid confusion in tests that might rely on current date logic.

describe('resolveViewerReviewStatus', () => {
it('prefers myReviewStatus over reviewStatus and normalizes case', () => {
const row = createScreeningRow({
myReviewStatus: 'completed',

Choose a reason for hiding this comment

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

[⚠️ correctness]
The test case for resolveViewerReviewStatus assumes that myReviewStatus and reviewStatus values are always lowercase. If the function is expected to handle mixed-case inputs, consider adding a test case to verify this behavior.

entry.myReviewResourceId,
entry.screenerId,
]
.map(id => id?.trim())

Choose a reason for hiding this comment

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

[⚠️ correctness]
The map function is used to trim the IDs, but if id is null or undefined, the trim method will not be called. Consider explicitly checking for null or undefined before calling trim to avoid potential runtime errors.

entry.myReviewId,
entry.reviewId,
]
.map(id => id?.trim())

Choose a reason for hiding this comment

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

[⚠️ correctness]
The map function is used to trim the IDs, but if id is null or undefined, the trim method will not be called. Consider explicitly checking for null or undefined before calling trim to avoid potential runtime errors.

@@ -69,8 +70,6 @@ interface Props {

const normalizePhaseName = (name?: string): string => (name ? name.trim()
.toLowerCase() : '')

Choose a reason for hiding this comment

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

[⚠️ maintainability]
The removal of SUBMISSION_PHASE_NAMES and REGISTRATION_PHASE_NAME constants could lead to potential issues if these values are used elsewhere in the codebase. Ensure that these constants are not used in other parts of the application or have been replaced appropriately.

return allowed
}, [challengePhases, phaseLookup])
const reopenEligiblePhaseIds = useMemo(
() => collectReopenEligiblePhaseIds(challengePhases),

Choose a reason for hiding this comment

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

[⚠️ correctness]
The collectReopenEligiblePhaseIds function is used here, but it's not clear from the diff what this function does. Ensure that this function is well-tested and handles all edge cases, as it replaces a substantial block of logic.

})), [props.skills])
const canSearch = skills.length >= SKILL_SEARCH_MINIMUM

function onChange(ev: any): void {

Choose a reason for hiding this comment

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

[⚠️ maintainability]
Using any for the event type is not recommended as it can lead to runtime errors and makes the code less maintainable. Consider using a more specific type, such as React.ChangeEvent<HTMLInputElement>, to improve type safety.

className={classNames(styles.searchIcon, !skills.length && styles.disabled)}
className={classNames(styles.searchIcon, !canSearch && styles.disabled)}
onClick={handleSearchClick}
onTouchStart={handleSearchClick as any}

Choose a reason for hiding this comment

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

[⚠️ maintainability]
Casting handleSearchClick to any for onTouchStart bypasses type checking and can lead to runtime errors. Consider defining handleSearchClick with a type that is compatible with both onClick and onTouchStart.

isTalentSearchLoading,
} from './use-fetch-talent-matches'

jest.mock('~/config', () => ({

Choose a reason for hiding this comment

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

[⚠️ maintainability]
The mock for ~/config is using a hardcoded URL for the API. Consider using a configuration or environment variable to allow flexibility and avoid potential issues when the API endpoint changes.

xhrGetPaginatedAsync: jest.fn(),
}), { virtual: true })

jest.mock('swr', () => jest.fn())

Choose a reason for hiding this comment

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

[⚠️ correctness]
The mock for swr is currently set to a generic jest.fn(). If specific behavior is expected from this mock, consider defining it to ensure tests are meaningful and cover expected scenarios.

url,
xhrGetPaginatedAsync<Member[]>,
{
isPaused: () => !canSearch,

Choose a reason for hiding this comment

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

[⚠️ correctness]
The isPaused function now uses !canSearch instead of !skills?.length. Ensure that canSearch correctly reflects the intended conditions for pausing the SWR request. This change might alter the behavior if canSearch logic differs from simply checking the length of skills.

return {
error: !!error,
loading: !data?.data,
loading: isTalentSearchLoading(canSearch, data, error),

Choose a reason for hiding this comment

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

[⚠️ correctness]
The loading state now uses isTalentSearchLoading which includes canSearch in its logic. Verify that this change aligns with the intended behavior, as it might affect when the loading state is true, especially if canSearch is false.

const [skillsFilter, setSkillsFilter] = useState<UserSkill[]>([])

function navigateToResults(): void {
if (skillsFilter.length < SKILL_SEARCH_MINIMUM) {

Choose a reason for hiding this comment

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

[⚠️ usability]
The early return when skillsFilter.length is less than SKILL_SEARCH_MINIMUM is a good guard clause. However, consider logging or providing user feedback when this condition is met, so users understand why their search action did not proceed.

import { SearchInput } from '../../components/search-input'
import { useUrlQuerySearchParms } from '../../lib/utils/search-query'
import {
InfiniteTalentMatchesResposne,

Choose a reason for hiding this comment

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

[❗❗ correctness]
Typo in InfiniteTalentMatchesResposne. It should be InfiniteTalentMatchesResponse. This could lead to confusion and potential issues if this type is used elsewhere.

</span>
) : skills.length < SKILL_SEARCH_MINIMUM ? (
<span>
{`Please select at least ${SKILL_SEARCH_MINIMUM} skills to search`}

Choose a reason for hiding this comment

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

[💡 readability]
Consider using a template literal for the message: Please select at least ${SKILL_SEARCH_MINIMUM} skills to search. This ensures consistency and readability, especially if the message needs to be internationalized or changed in the future.

@@ -0,0 +1 @@
export const getLetsTalkUrl = (topcoderUrl: string): string => `${topcoderUrl}/lets-talk`

Choose a reason for hiding this comment

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

[⚠️ correctness]
Consider validating the topcoderUrl parameter to ensure it is a valid URL and does not contain any trailing slashes. This will prevent potential issues with malformed URLs and improve the robustness of the function.


table {
width: 100%;
min-width: 980px;

Choose a reason for hiding this comment

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

[⚠️ design]
Setting a min-width of 980px on the table may cause layout issues on smaller screens, especially if the container is narrower than 980px. Consider using responsive design techniques, such as media queries, to ensure the table displays correctly on all screen sizes.


table {
width: 100%;
min-width: 920px;

Choose a reason for hiding this comment

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

[⚠️ design]
Setting a min-width of 920px on the table could lead to horizontal scrolling on smaller screens, potentially affecting usability. Consider using a responsive design approach to ensure the table is accessible on all devices.

.toEqual(expect.arrayContaining([
'administrator',
'copilot',
'Project Manager',

Choose a reason for hiding this comment

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

[⚠️ correctness]
Consider normalizing the case of all role names to ensure consistency and avoid potential mismatches due to case sensitivity.

@@ -0,0 +1,15 @@
export const WORK_MANAGER_ALLOWED_ROLES: string[] = [
'administrator',

Choose a reason for hiding this comment

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

[⚠️ correctness]
Consider normalizing the case of all role names to ensure consistent access checks. Mixed casing could lead to potential mismatches if role checks are case-sensitive.

typeId: 'type-id',
}

const result = transformFormDataToChallenge(formData as any)

Choose a reason for hiding this comment

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

[⚠️ maintainability]
Using as any for type casting can lead to potential runtime errors and makes the code less type-safe. Consider defining a specific type for formData to ensure type safety.

wiproAllowed: true,
}

const result = transformFormDataToChallenge(formData as any)

Choose a reason for hiding this comment

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

[⚠️ maintainability]
Using as any for type casting can lead to potential runtime errors and makes the code less type-safe. Consider defining a specific type for formData to ensure type safety.

<ChallengeNameField />
<ChallengeTrackField disabled={isChallengeCreated} />
<ChallengeTypeField disabled={isChallengeCreated} />
<FunChallengeField />

Choose a reason for hiding this comment

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

[⚠️ design]
Ensure that FunChallengeField is necessary and correctly integrated into the form. If this field is optional or conditional, consider adding logic to handle its visibility or inclusion based on specific conditions.

<GroupsField />
<TermsField />
<NDAField />
<FormCheckboxField

Choose a reason for hiding this comment

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

[⚠️ correctness]
Consider validating the wiproAllowed field to ensure it aligns with business logic. If this checkbox affects other parts of the form or application, ensure that its state is properly handled.

}),
]

const result = recalculatePhases(phases, updatedStartDate, {

Choose a reason for hiding this comment

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

[⚠️ correctness]
The recalculatePhases function is called with an options object, but the test does not verify if the function correctly handles cases where the options object is not provided. Consider adding a test case to cover this scenario to ensure robustness.

useFetchChallengePhases,
useFetchChallengeTracks,
} from '../../../../../lib/hooks'
import { ChallengePhase } from '../../../../../lib/models'

Choose a reason for hiding this comment

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

[❗❗ correctness]
The removal of RecalculatePhasesResult and RecalculatePhasesOptions interfaces suggests a change in the structure of the recalculatePhases function. Ensure that the function's return type and parameter expectations are correctly updated to reflect these changes, as this could impact type safety and correctness.

'startDateOverride',
)
const recalculationResult = recalculatePhases(
nextPhases,

Choose a reason for hiding this comment

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

[⚠️ correctness]
The recalculatePhases function is now being called without explicitly specifying its return type. Ensure that the function's return type is correctly inferred or explicitly defined to maintain type safety and prevent potential runtime errors.

return
}

const recalculationResult = recalculatePhases(phases, startDate, {

Choose a reason for hiding this comment

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

[⚠️ correctness]
The recalculatePhases function is called without specifying the resetRootPhasesToStartDate option. Consider whether this omission is intentional or if it could lead to unexpected behavior in phase scheduling.

const parsedDuration = Number(duration)
const maxDuration = PHASE_DURATION_MAX_HOURS * 60

if (!Number.isFinite(parsedDuration)) {

Choose a reason for hiding this comment

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

[⚠️ correctness]
The normalizeDuration function uses Number.isFinite to check if parsedDuration is a valid number. However, Number.isFinite will return false for NaN, Infinity, and -Infinity. If the intention is to only check for NaN, consider using Number.isNaN instead, or clarify the behavior if Infinity values are expected.

const predecessorEndDate = toDate(predecessorPhase?.scheduledEndDate)

if (predecessorEndDate) {
phaseStartDate = isIterativeReviewPhase(phase.name)

Choose a reason for hiding this comment

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

[⚠️ correctness]
In the recalculatePhases function, when determining phaseStartDate for an iterative review phase, the logic defaults to predecessorStartDate || predecessorEndDate. This could lead to unexpected behavior if predecessorStartDate is undefined but predecessorEndDate is valid. Consider verifying if this logic aligns with the intended scheduling behavior.

? predecessorStartDate || predecessorEndDate
: predecessorEndDate
} else if (shouldScheduleDates && !calculationError) {
const phaseName = phase.name || phase.phaseId || `${index + 1}`

Choose a reason for hiding this comment

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

[⚠️ maintainability]
The error message for an invalid predecessor configuration is set but not immediately returned or logged. Ensure that the error is handled appropriately, either by returning it immediately or by logging it for debugging purposes.

domain: AppSubdomain.work,
element: <WorkApp />,
id: toolTitle,
rolesRequired: WORK_MANAGER_ALLOWED_ROLES,

Choose a reason for hiding this comment

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

[❗❗ security]
Ensure that WORK_MANAGER_ALLOWED_ROLES is correctly defined and includes all necessary roles for accessing this route. If this list is incomplete or incorrect, it could lead to unauthorized access or prevent authorized users from accessing the route.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant