Skip to content

[PROD RELEASE] - WorkManager Changes - Connect Decommission #1633

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 44 commits into from
Apr 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
ff855cf
Allow hyphen in url - asset library
Mar 26, 2025
a70e1a5
git copilot suggestion
Mar 26, 2025
4d24155
Redo the fix with minimal change
Mar 26, 2025
6a43f63
Codeql test
Mar 26, 2025
619b5c0
re-iterate with codeql
Mar 26, 2025
1dee45f
Retest performance
Mar 26, 2025
a0fe205
Potential fix for code scanning alert no. 27: Inefficient regular exp…
himaniraghav3 Mar 26, 2025
374be0c
revert codeql suggestion
Mar 26, 2025
c7d59d7
Merge pull request #1621 from topcoder-platform/PM-971
himaniraghav3 Mar 26, 2025
1b719fb
feat: added show only my projects for project managers
hentrymartin Mar 27, 2025
441e6d7
fix: removed console log
hentrymartin Mar 27, 2025
592a0f3
fix: lint
hentrymartin Mar 27, 2025
4fd1543
PM-973 - move add user to own component
vas3a Mar 28, 2025
81a1173
PM-973 - invite user modal
vas3a Mar 28, 2025
06ab559
PM-973 - invite by email
vas3a Mar 30, 2025
90f0395
Merge pull request #1622 from topcoder-platform/pm-974_1
hentrymartin Mar 31, 2025
41329e1
feat: allow PM to view users and delete users from project
hentrymartin Apr 2, 2025
7eccfe3
fix: all projects challenges
hentrymartin Apr 3, 2025
18768fd
Merge pull request #1624 from topcoder-platform/pm-974_2
hentrymartin Apr 3, 2025
592b787
PM-973 - add invitation dialog after user accepts invitation through …
vas3a Apr 7, 2025
5823808
Merge remote-tracking branch 'origin/develop' into PM-973_invite-by-mail
vas3a Apr 7, 2025
b184e86
fix: projects list in challenges tab
hentrymartin Apr 7, 2025
125cf7d
Merge pull request #1625 from topcoder-platform/pm-974_3
hentrymartin Apr 7, 2025
8af3cf0
Merge pull request #1623 from topcoder-platform/PM-973_invite-by-mail
vas3a Apr 8, 2025
9605fa2
add AI PR Reviewer action
kkartunov Apr 8, 2025
530c136
rename job
kkartunov Apr 8, 2025
3ddfca7
adds clear cache step
kkartunov Apr 8, 2025
cf7491a
workflow update
kkartunov Apr 8, 2025
eedc3ff
add @master ref
kkartunov Apr 8, 2025
ac1de1a
job rename
kkartunov Apr 8, 2025
cf73dbc
restore name
kkartunov Apr 8, 2025
392e5f4
add missing assets
vas3a Apr 8, 2025
de59094
Merge pull request #1629 from topcoder-platform/PM-973_invite-by-mail
vas3a Apr 8, 2025
52fb0ab
code review permissions
kkartunov Apr 8, 2025
91b2eb2
exclude image files from ai review
kkartunov Apr 9, 2025
d907e6e
fix awaiting for project updates to propagate
vas3a Apr 9, 2025
7c2a793
PR feedback
vas3a Apr 9, 2025
8255022
Merge pull request #1634 from topcoder-platform/PM-973_invite-by-mail
vas3a Apr 9, 2025
e6f13e0
PM-973 - fetch project invites sepparately
vas3a Apr 9, 2025
de8f892
Merge pull request #1635 from topcoder-platform/PM-973_invite-by-mail
vas3a Apr 9, 2025
21d0574
PM-973 - fix checkIsUserInvitedToProject
vas3a Apr 9, 2025
2422565
Merge pull request #1636 from topcoder-platform/PM-973_invite-by-mail
vas3a Apr 9, 2025
5334423
PM-973 - Update label for "cancel" on invitation modal
vas3a Apr 10, 2025
7d0fcd9
Merge pull request #1637 from topcoder-platform/PM-973_invite-by-mail
vas3a Apr 10, 2025
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
22 changes: 22 additions & 0 deletions .github/workflows/code_reviewer.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: AI PR Reviewer

on:
pull_request:
types:
- opened
- synchronize
permissions:
pull-requests: write
jobs:
tc-ai-pr-review:
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v3

- name: TC AI PR Reviewer
uses: topcoder-platform/tc-ai-pr-reviewer@master
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # The GITHUB_TOKEN is there by default so you just need to keep it like it is and not necessarily need to add it as secret as it will throw an error. [More Details](https://docs.github.com/en/actions/security-guides/automatic-token-authentication#about-the-github_token-secret)
LAB45_API_KEY: ${{ secrets.LAB45_API_KEY }}
exclude: "**/*.json, **/*.md, **/*.jpg, **/*.png, **/*.jpeg, **/*.bmp, **/*.webp" # Optional: exclude patterns separated by commas
Binary file added public/static/comment.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/static/logo.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 6 additions & 2 deletions src/actions/challenges.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ import {
} from '../config/constants'
import { loadProject } from './projects'
import { removeChallengeFromPhaseProduct, saveChallengeAsPhaseProduct } from '../services/projects'
import { checkAdmin } from '../util/tc'
import { checkAdmin, checkManager } from '../util/tc'

/**
* Member challenges related redux actions
Expand Down Expand Up @@ -159,7 +159,11 @@ export function loadChallengesByPage (
filters['projectId'] = projectId
} else if (_.isObject(projectId) && projectId.value > 0) {
filters['projectId'] = projectId.value
} else if (!checkAdmin(getState().auth.token) && userId) {
} else if (
!checkAdmin(getState().auth.token) &&
!checkManager(getState().auth.token) &&
Copy link

Choose a reason for hiding this comment

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

Consider checking if checkManager function is defined and imported correctly to ensure it works as expected.

userId
) {
// Note that we only add the memberId field if *no* project ID is given,
// so that the list of *all challenges shows only those that the member is on
filters['memberId'] = userId
Expand Down
22 changes: 18 additions & 4 deletions src/actions/projects.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import {
UPDATE_PROJECT_FAILURE,
ADD_PROJECT_ATTACHMENT_SUCCESS,
UPDATE_PROJECT_ATTACHMENT_SUCCESS,
REMOVE_PROJECT_ATTACHMENT_SUCCESS
REMOVE_PROJECT_ATTACHMENT_SUCCESS,
LOAD_PROJECT_INVITES
Copy link

Choose a reason for hiding this comment

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

It looks like a new constant LOAD_PROJECT_INVITES has been added. Ensure that this constant is defined in the ../config/constants file and is being used appropriately in the codebase.

} from '../config/constants'
import {
fetchProjectById,
Expand All @@ -30,9 +31,10 @@ import {
createProjectApi,
fetchBillingAccounts,
fetchMemberProjects,
updateProjectApi
updateProjectApi,
getProjectInvites
Copy link

Choose a reason for hiding this comment

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

Please ensure that getProjectInvites is actually used in the code. If it's not used, consider removing it to keep the imports clean and avoid unnecessary dependencies.

} from '../services/projects'
import { checkAdmin } from '../util/tc'
import { checkAdmin, checkManager } from '../util/tc'

function _loadProjects (projectNameOrIdFilter = '', paramFilters = {}) {
return (dispatch, getState) => {
Expand All @@ -54,7 +56,7 @@ function _loadProjects (projectNameOrIdFilter = '', paramFilters = {}) {
}
}

if (!checkAdmin(getState().auth.token)) {
if (!checkAdmin(getState().auth.token) && !checkManager(getState().auth.token)) {
Copy link

Choose a reason for hiding this comment

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

Consider checking if checkManager function is defined and properly handles cases where the token might be invalid or undefined. This will ensure that the logic for checking manager roles is robust and does not introduce any unexpected behavior.

filters['memberOnly'] = true
}

Expand Down Expand Up @@ -171,6 +173,18 @@ export function loadProjectTypes () {
}
}

/**
* Loads project invites
*/
export function loadProjectInvites (projectId) {
return (dispatch) => {
return dispatch({
type: LOAD_PROJECT_INVITES,
payload: getProjectInvites(projectId)
})
}
}

/**
* Creates a project
*/
Expand Down
4 changes: 2 additions & 2 deletions src/actions/sidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
UNLOAD_PROJECTS_SUCCESS,
PROJECTS_PAGE_SIZE
} from '../config/constants'
import { checkAdmin } from '../util/tc'
import { checkAdmin, checkManager } from '../util/tc'
Copy link

Choose a reason for hiding this comment

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

The checkManager function is imported but not used in this file. If it's not needed, consider removing it to keep the code clean and avoid unnecessary imports.

import _ from 'lodash'

/**
Expand Down Expand Up @@ -50,7 +50,7 @@ export function loadProjects (filterProjectName = '', paramFilters = {}) {
}
}

if (!checkAdmin(getState().auth.token)) {
if (!checkAdmin(getState().auth.token) && !checkManager(getState().auth.token)) {
Copy link

Choose a reason for hiding this comment

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

Consider checking if checkManager function is defined and implemented correctly to ensure it accurately verifies manager roles. This is crucial to avoid potential security issues by inadvertently granting access to unauthorized users.

filters['memberOnly'] = true
}

Expand Down
34 changes: 28 additions & 6 deletions src/actions/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,33 +10,55 @@ import {
SEARCH_USER_PROJECTS_SUCCESS,
SEARCH_USER_PROJECTS_FAILURE
} from '../config/constants'
import _ from 'lodash'

/**
* Loads projects of the authenticated user
*/
export function loadAllUserProjects (isAdmin = true) {
return (dispatch) => {
export function loadAllUserProjects (params, isAdmin = true, isManager = true) {
Copy link

Choose a reason for hiding this comment

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

The loadAllUserProjects function signature has changed to include params, isAdmin, and isManager. Ensure that all calls to this function throughout the codebase are updated to pass the correct parameters.

return (dispatch, getState) => {
dispatch({
type: LOAD_ALL_USER_PROJECTS_PENDING
})

const state = getState().users

const filters = {
status: 'active',
sort: 'lastActivityAt desc'
sort: 'lastActivityAt desc',
perPage: 20,
...params
}
if (!isAdmin) {

if (!isAdmin && !isManager) {
Copy link

Choose a reason for hiding this comment

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

The condition if (!isAdmin && !isManager) implies that both isAdmin and isManager must be false to set filters['memberOnly'] = true. Verify that this logic aligns with the intended behavior, as previously it only checked for !isAdmin.

filters['memberOnly'] = true
}

fetchMemberProjects(filters).then(({ projects }) => dispatch({
fetchMemberProjects(filters).then(({ projects, pagination }) => dispatch({
type: LOAD_ALL_USER_PROJECTS_SUCCESS,
projects
projects: _.uniqBy((filters.page ? state.allUserProjects || [] : []).concat(projects), 'id'),
Copy link

Choose a reason for hiding this comment

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

The use of _.uniqBy to concatenate and deduplicate projects by 'id' is a good approach. However, ensure that state.allUserProjects is correctly initialized as an array to avoid potential issues with concat.

total: pagination.xTotal,
page: pagination.xPage
})).catch(() => dispatch({
type: LOAD_ALL_USER_PROJECTS_FAILURE
}))
}
}

export function loadNextProjects (isAdmin = true, isManager = true) {
return (dispatch, getState) => {
const { page, total, allUserProjects } = getState().users
Copy link

Choose a reason for hiding this comment

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

In loadNextProjects, the check if (allUserProjects.length >= total) prevents further loading if all projects are already loaded. Ensure that total is correctly set and updated to reflect the actual total number of projects.

if (allUserProjects.length >= total) {
return
}

loadAllUserProjects(_.assign({}, {
Copy link

Choose a reason for hiding this comment

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

Consider using a constant or configuration setting for perPage to avoid magic numbers and to facilitate easier adjustments in the future.

perPage: 20,
page: page + 1
}), isAdmin, isManager)(dispatch, getState)
}
}

/**
* Filter projects of the authenticated user
*
Expand Down
5 changes: 3 additions & 2 deletions src/components/ProjectCard/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import { PROJECT_STATUSES } from '../../config/constants'

import styles from './ProjectCard.module.scss'

const ProjectCard = ({ projectName, projectStatus, projectId, selected }) => {
const ProjectCard = ({ projectName, projectStatus, projectId, selected, isInvited }) => {
Copy link

Choose a reason for hiding this comment

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

Consider adding a default value for the isInvited prop to ensure it is always a boolean. This can prevent potential issues if the prop is undefined.

return (
<div className={styles.container}>
<Link
to={`/projects/${projectId}/challenges`}
to={`/projects/${projectId}/${isInvited ? 'invitation' : 'challenges'}`}
className={cn(styles.projectName, { [styles.selected]: selected })}
>
<div className={styles.name}>
Expand All @@ -28,6 +28,7 @@ ProjectCard.propTypes = {
projectStatus: PT.string.isRequired,
projectId: PT.number.isRequired,
projectName: PT.string.isRequired,
isInvited: PT.bool.isRequired,
selected: PT.bool
}

Expand Down
158 changes: 87 additions & 71 deletions src/components/UserCard/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import _ from 'lodash'
Copy link

Choose a reason for hiding this comment

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

It seems that lodash is imported but not used in this file. Consider removing the import if it's not needed.

import moment from 'moment'
Copy link

Choose a reason for hiding this comment

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

The 'moment' library is imported but not used in this file. Consider removing the import if it's not necessary.

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import cn from 'classnames'
Expand All @@ -6,7 +8,6 @@ import { PROJECT_ROLES } from '../../config/constants'
import PrimaryButton from '../Buttons/PrimaryButton'
import AlertModal from '../Modal/AlertModal'
Copy link

Choose a reason for hiding this comment

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

The lodash library import has been removed, but it's not clear if all usages of lodash have been refactored or removed from the code. Please ensure that any lodash functions previously used in this file are either replaced with native JavaScript alternatives or are no longer needed.

import { updateProjectMemberRole } from '../../services/projects'
import _ from 'lodash'

const theme = {
container: styles.modalContainer
Expand Down Expand Up @@ -58,7 +59,7 @@ class UserCard extends Component {
}

render () {
const { user, onRemoveClick, isEditable } = this.props
const { isInvite, user, onRemoveClick, isEditable } = this.props
Copy link

Choose a reason for hiding this comment

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

The isInvite prop is added to the destructuring assignment, but it is not used anywhere in the render method. If this prop is not needed, consider removing it to keep the code clean and maintainable.

const showRadioButtons = _.includes(_.values(PROJECT_ROLES), user.role)
return (
<div>
Expand Down Expand Up @@ -90,76 +91,90 @@ class UserCard extends Component {
)}
<div className={styles.item}>
<div className={cn(styles.col5)}>
{user.handle}
</div>
<div className={cn(styles.col5)}>
{showRadioButtons && (<div className={styles.tcRadioButton}>
<input
name={`user-${user.id}`}
type='radio'
id={`read-${user.id}`}
checked={user.role === PROJECT_ROLES.READ}
onChange={(e) => e.target.checked && this.updatePermission(PROJECT_ROLES.READ)}
/>
<label className={cn({ [styles.isDisabled]: !isEditable })} htmlFor={`read-${user.id}`}>
<div>
Read
</div>
<input type='hidden' />
</label>
</div>)}
</div>
<div className={cn(styles.col5)}>
{showRadioButtons && (<div className={styles.tcRadioButton}>
<input
name={`user-${user.id}`}
type='radio'
id={`write-${user.id}`}
checked={user.role === PROJECT_ROLES.WRITE}
onChange={(e) => e.target.checked && this.updatePermission(PROJECT_ROLES.WRITE)}
/>
<label className={cn({ [styles.isDisabled]: !isEditable })} htmlFor={`write-${user.id}`}>
<div>
Write
</div>
<input type='hidden' />
</label>
</div>)}
</div>
<div className={cn(styles.col5)}>
{showRadioButtons && (<div className={styles.tcRadioButton}>
<input
name={`user-${user.id}`}
type='radio'
id={`full-access-${user.id}`}
checked={user.role === PROJECT_ROLES.MANAGER}
onChange={(e) => e.target.checked && this.updatePermission(PROJECT_ROLES.MANAGER)}
/>
<label className={cn({ [styles.isDisabled]: !isEditable })} htmlFor={`full-access-${user.id}`}>
<div>
Full Access
</div>
<input type='hidden' />
</label>
</div>)}
</div>
<div className={cn(styles.col5)}>
{showRadioButtons && (<div className={styles.tcRadioButton}>
<input
name={`user-${user.id}`}
type='radio'
id={`copilot-${user.id}`}
checked={user.role === PROJECT_ROLES.COPILOT}
onChange={(e) => e.target.checked && this.updatePermission(PROJECT_ROLES.COPILOT)}
/>
<label className={cn({ [styles.isDisabled]: !isEditable })} htmlFor={`copilot-${user.id}`}>
<div>
Copilot
</div>
<input type='hidden' />
</label>
</div>)}
{isInvite ? user.email : user.handle}
</div>
{!isInvite && (
<>
<div className={cn(styles.col5)}>
{showRadioButtons && (<div className={styles.tcRadioButton}>
<input
name={`user-${user.id}`}
type='radio'
id={`read-${user.id}`}
checked={user.role === PROJECT_ROLES.READ}
onChange={(e) => e.target.checked && this.updatePermission(PROJECT_ROLES.READ)}
/>
<label className={cn({ [styles.isDisabled]: !isEditable })} htmlFor={`read-${user.id}`}>
<div>
Read
</div>
<input type='hidden' />
</label>
</div>)}
</div>
<div className={cn(styles.col5)}>
{showRadioButtons && (<div className={styles.tcRadioButton}>
<input
name={`user-${user.id}`}
type='radio'
id={`write-${user.id}`}
checked={user.role === PROJECT_ROLES.WRITE}
onChange={(e) => e.target.checked && this.updatePermission(PROJECT_ROLES.WRITE)}
/>
<label className={cn({ [styles.isDisabled]: !isEditable })} htmlFor={`write-${user.id}`}>
<div>
Write
</div>
<input type='hidden' />
</label>
</div>)}
</div>
<div className={cn(styles.col5)}>
{showRadioButtons && (<div className={styles.tcRadioButton}>
<input
name={`user-${user.id}`}
type='radio'
id={`full-access-${user.id}`}
checked={user.role === PROJECT_ROLES.MANAGER}
onChange={(e) => e.target.checked && this.updatePermission(PROJECT_ROLES.MANAGER)}
/>
<label className={cn({ [styles.isDisabled]: !isEditable })} htmlFor={`full-access-${user.id}`}>
<div>
Full Access
</div>
<input type='hidden' />
</label>
</div>)}
</div>
<div className={cn(styles.col5)}>
{showRadioButtons && (<div className={styles.tcRadioButton}>
<input
name={`user-${user.id}`}
type='radio'
id={`copilot-${user.id}`}
checked={user.role === PROJECT_ROLES.COPILOT}
onChange={(e) => e.target.checked && this.updatePermission(PROJECT_ROLES.COPILOT)}
/>
<label className={cn({ [styles.isDisabled]: !isEditable })} htmlFor={`copilot-${user.id}`}>
<div>
Copilot
</div>
<input type='hidden' />
</label>
</div>)}
</div>
</>
)}
{isInvite && (
<>
<div className={cn(styles.col5)} />
<div className={cn(styles.col5)}>
Invited {moment(user.createdAt).format('MMM D, YY')}
Copy link

Choose a reason for hiding this comment

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

Consider handling the case where user.createdAt might be undefined or null to avoid potential runtime errors when formatting the date.

</div>
<div className={cn(styles.col5)} />
<div className={cn(styles.col5)} />
</>
)}
{isEditable ? (<div className={cn(styles.col5)}>
<PrimaryButton
text={'Remove'}
Expand All @@ -173,6 +188,7 @@ class UserCard extends Component {
}

UserCard.propTypes = {
isInvite: PropTypes.bool,
user: PropTypes.object,
updateProjectNember: PropTypes.func.isRequired,
onRemoveClick: PropTypes.func.isRequired,
Expand Down
Loading