Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
8f5bca9
feat: artifacts download modal
hentrymartin Feb 24, 2025
c41bf69
feat: artifact download implementation
hentrymartin Feb 25, 2025
c2f2a0a
updated from develop
hentrymartin Feb 25, 2025
1f72e1d
fix: review comments
hentrymartin Feb 27, 2025
6ab92b8
fix: extracted download submission handler
hentrymartin Feb 27, 2025
212809d
fix: converted download artifacts button to icon
hentrymartin Feb 27, 2025
926dab0
fix: updated download artifact svg
hentrymartin Feb 27, 2025
eff3ee3
feat: list ratings
hentrymartin Mar 3, 2025
f5feace
PM-875 - move projects to new container, add filters
vas3a Mar 3, 2025
ead28d2
Merge pull request #1613 from topcoder-platform/PM-875_project-filters
vas3a Mar 4, 2025
b419b66
PM-878 - show BA name
vas3a Mar 4, 2025
4968fc3
Merge pull request #1614 from topcoder-platform/PM-878_show-ba-name
vas3a Mar 4, 2025
dc6a09a
PM-858 - limit challenges list
vas3a Mar 5, 2025
971f208
Merge pull request #1615 from topcoder-platform/PM-858_limit-challeng…
vas3a Mar 5, 2025
a1ff894
deploy to develop
hentrymartin Mar 6, 2025
596b8c4
Merge pull request #1612 from topcoder-platform/pm-810
hentrymartin Mar 6, 2025
caebce7
PM-898 Fix reset filter in Challenge listing
Mar 11, 2025
6434996
remove console log
Mar 11, 2025
acac808
PM-903 Add project link to challenge view
Mar 11, 2025
713ade8
Merge pull request #1616 from topcoder-platform/PM-898
himaniraghav3 Mar 12, 2025
b49d3eb
Merge pull request #1617 from topcoder-platform/PM-903
himaniraghav3 Mar 12, 2025
ce462b5
fix: tooltip and added empty content text
hentrymartin Mar 14, 2025
f9e5ac1
Merge pull request #1618 from topcoder-platform/pm-810_1
hentrymartin Mar 14, 2025
aa01e3d
PM-902 - load more projects on dropdown scroll
vas3a Mar 17, 2025
16437c8
deploy
vas3a Mar 17, 2025
ffaa101
Merge pull request #1619 from topcoder-platform/PM-902_show-all-proje…
vas3a Mar 17, 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
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ workflows:
context: org-global
filters: &filters-dev
branches:
only: ["develop", "PM-803_wm-regression-fixes"]
only: ["develop", "PM-803_wm-regression-fixes", "PM-902_show-all-projects-on-challenge-page"]

# Production builds are exectuted only on tagged commits to the
# master branch.
Expand Down
1 change: 1 addition & 0 deletions config/constants/development.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ module.exports = {
RESOURCES_API_URL: `${DEV_API_HOSTNAME}/v5/resources`,
RESOURCE_ROLES_API_URL: `${DEV_API_HOSTNAME}/v5/resource-roles`,
SUBMISSIONS_API_URL: `${DEV_API_HOSTNAME}/v5/submissions`,
REVIEW_TYPE_API_URL: `${DEV_API_HOSTNAME}/v5/reviewTypes`,
SUBMISSION_REVIEW_APP_URL: `https://submission-review.${DOMAIN}/challenges`,
STUDIO_URL: `https://studio.${DOMAIN}`,
CONNECT_APP_URL: `https://connect.${DOMAIN}`,
Expand Down
1 change: 1 addition & 0 deletions config/constants/production.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ module.exports = {
RESOURCES_API_URL: `${PROD_API_HOSTNAME}/v5/resources`,
RESOURCE_ROLES_API_URL: `${PROD_API_HOSTNAME}/v5/resource-roles`,
SUBMISSIONS_API_URL: `${PROD_API_HOSTNAME}/v5/submissions`,
REVIEW_TYPE_API_URL: `${PROD_API_HOSTNAME}/v5/reviewTypes`,
SUBMISSION_REVIEW_APP_URL: `https://submission-review.${DOMAIN}/challenges`,
STUDIO_URL: `https://studio.${DOMAIN}`,
CONNECT_APP_URL: `https://connect.${DOMAIN}`,
Expand Down
515 changes: 367 additions & 148 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@
"terser": "^3.16.1",
"terser-webpack-plugin": "1.1.0",
"topcoder-healthcheck-dropin": "^1.0.3",
"topcoder-react-lib": "^1.2.10",
"topcoder-react-lib": "github:topcoder-platform/topcoder-react-lib#1.2.18",
"url-loader": "1.1.1",
"webpack": "^4.43.0",
"webpack-dev-server": "^3.11.0",
Expand Down
3 changes: 2 additions & 1 deletion src/actions/challenges.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import {
} from '../config/constants'
import { loadProject } from './projects'
import { removeChallengeFromPhaseProduct, saveChallengeAsPhaseProduct } from '../services/projects'
import { checkAdmin } from '../util/tc'

/**
* Member challenges related redux actions
Expand Down Expand Up @@ -158,7 +159,7 @@ export function loadChallengesByPage (
filters['projectId'] = projectId
} else if (_.isObject(projectId) && projectId.value > 0) {
filters['projectId'] = projectId.value
} else if (userId) {
} else if (!checkAdmin(getState().auth.token) && 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
96 changes: 96 additions & 0 deletions src/actions/projects.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import _ from 'lodash'

import {
PROJECT_TYPE_TAAS,
PROJECTS_PAGE_SIZE,
LOAD_PROJECTS_PENDING,
LOAD_PROJECTS_SUCCESS,
UNLOAD_PROJECTS_SUCCESS,
LOAD_PROJECTS_FAILURE,
LOAD_PROJECT_BILLING_ACCOUNT,
LOAD_CHALLENGE_MEMBERS_SUCCESS,
LOAD_PROJECT_DETAILS,
Expand All @@ -21,8 +29,96 @@ import {
getProjectTypes,
createProjectApi,
fetchBillingAccounts,
fetchMemberProjects,
updateProjectApi
} from '../services/projects'
import { checkAdmin } from '../util/tc'

function _loadProjects (projectNameOrIdFilter = '', paramFilters = {}) {
return (dispatch, getState) => {
dispatch({
type: LOAD_PROJECTS_PENDING
})

const filters = {
sort: 'lastActivityAt desc',
perPage: PROJECTS_PAGE_SIZE,
...paramFilters
}

if (!_.isEmpty(projectNameOrIdFilter)) {
if (!isNaN(projectNameOrIdFilter)) { // if it is number
filters['id'] = parseInt(projectNameOrIdFilter, 10)
} else { // text search
filters['keyword'] = decodeURIComponent(projectNameOrIdFilter)
}
}

if (!checkAdmin(getState().auth.token)) {
filters['memberOnly'] = true
}

// eslint-disable-next-line no-debugger
const state = getState().projects
fetchMemberProjects(filters).then(({ projects, pagination }) => dispatch({
filters,
type: LOAD_PROJECTS_SUCCESS,
projects: _.uniqBy((filters.page ? state.projects || [] : []).concat(projects), 'id'),
total: pagination.xTotal,
page: pagination.xPage
})).catch(() => dispatch({
type: LOAD_PROJECTS_FAILURE
}))
}
}

export function loadProjects (projectNameOrIdFilter = '', paramFilters = {}) {
return async (dispatch, getState) => {
const _filters = _.assign({}, paramFilters)
if (_.isEmpty(_filters) || !_filters.type) {
let projectTypes = getState().projects.projectTypes

if (!projectTypes.length) {
dispatch({
type: LOAD_PROJECTS_PENDING
})
await loadProjectTypes()(dispatch)
projectTypes = getState().projects.projectTypes
}

_.assign(_filters, {
type: projectTypes.filter(d => d.key !== PROJECT_TYPE_TAAS).map(d => d.key)
})
}

return _loadProjects(projectNameOrIdFilter, _filters)(dispatch, getState)
}
}

/**
* Load more projects for the authenticated user
*/
export function loadMoreProjects () {
return (dispatch, getState) => {
const { projectFilters, projectsPage } = getState().projects

loadProjects('', _.assign({}, projectFilters, {
perPage: PROJECTS_PAGE_SIZE,
page: projectsPage + 1
}))(dispatch, getState)
}
}

/**
* Unloads projects of the authenticated user
*/
export function unloadProjects () {
return (dispatch) => {
dispatch({
type: UNLOAD_PROJECTS_SUCCESS
})
}
}

/**
* Loads project details
Expand Down
19 changes: 6 additions & 13 deletions src/actions/sidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export function loadProjects (filterProjectName = '', paramFilters = {}) {
})

const filters = {
status: 'active',
sort: 'lastActivityAt desc',
perPage: PROJECTS_PAGE_SIZE,
...paramFilters
Expand Down Expand Up @@ -66,26 +67,18 @@ export function loadProjects (filterProjectName = '', paramFilters = {}) {
}
}

/**
* Load more projects for the authenticated user
*/
export function loadMoreProjects (filterProjectName = '', paramFilters = {}) {
// Load next page of projects
export function loadNextProjects () {
return (dispatch, getState) => {
const state = getState().sidebar
const { projectFilters, projectsPage } = getState().sidebar

loadProjects(filterProjectName, _.assignIn({}, paramFilters, {
loadProjects('', _.assign({}, projectFilters, {
perPage: PROJECTS_PAGE_SIZE,
page: state.page + 1
page: projectsPage + 1
}))(dispatch, getState)
}
}

export function loadTaasProjects (filterProjectName = '', paramFilters = {}) {
return loadProjects(filterProjectName, Object.assign({
type: 'talent-as-a-service'
}, paramFilters))
}

/**
* Unloads projects of the authenticated user
*/
Expand Down
4 changes: 4 additions & 0 deletions src/assets/images/IconDownloadArtifacts.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/assets/images/IconReviewRatingList.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
@import "../../../styles/includes";

.container {
box-sizing: border-box;
background: $white;
opacity: 1;
position: relative;
display: flex;
flex-direction: column;
justify-content: flex-start;
border-radius: 6px;
margin: 0 auto;
width: 800px;
min-height: 350px;
padding-top: 60px;
.list {
.header {
border-bottom: 1px solid $tc-gray-60;
padding-bottom: 10px;
display: flex;
padding-left: 40px;
padding-right: 40px;
color: $tc-gray-70;
font-weight: 500;

.header-title {
flex: 1;
}
}
.list-item {
border-bottom: 1px solid $tc-gray-60;
padding-bottom: 10px;
padding-top: 10px;
display: flex;
padding-left: 40px;
padding-right: 40px;
color: $tc-gray-70;
.artifact-name {
display: flex;
flex: 1;
}
.icon-download {
cursor: pointer;
}
}
.no-artifacts {
@include roboto;

margin-top: 40px;
text-align: center;
}
}
}
115 changes: 115 additions & 0 deletions src/components/ChallengeEditor/ArtifactsListModal/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import React, { useCallback, useEffect, useState } from 'react'
import Modal from '../../Modal'

import styles from './ArtifactsListModal.module.scss'
import PropTypes from 'prop-types'
import ReactSVG from 'react-svg'
import { getTopcoderReactLib, isValidDownloadFile } from '../../../util/topcoder-react-lib'
import Loader from '../../Loader'
const assets = require.context('../../../assets/images', false, /svg/)

export const ArtifactsListModal = ({ onClose, submissionId, token, theme }) => {
const [artifacts, setArtifacts] = useState([])
const [loading, setLoading] = useState(false)

const getArtifacts = useCallback(async () => {
const reactLib = getTopcoderReactLib()
const { getService } = reactLib.services.submissions
const submissionsService = getService(token)
const { artifacts: resp } = await submissionsService.getSubmissionArtifacts(submissionId)
setArtifacts(resp)
setLoading(false)
}, [submissionId, token])

const getExtensionFromMime = useCallback((mimeType) => {
const mimeMap = {
'application/zip': 'zip',
'application/pdf': 'pdf',
'image/jpeg': 'jpg',
'image/png': 'png',
'text/plain': 'txt'
}
return mimeMap[mimeType] || 'zip'
}, [])

useEffect(() => {
setLoading(true)
getArtifacts()
}, [submissionId])

const onDownloadArtifact = useCallback((item) => {
// download submission
const reactLib = getTopcoderReactLib()
const { getService } = reactLib.services.submissions
const submissionsService = getService(token)
submissionsService.downloadSubmissionArtifact(submissionId, item)
.then((blob) => {
isValidDownloadFile(blob).then((isValidFile) => {
if (isValidFile.success) {
// eslint-disable-next-line no-undef
const blobFile = new Blob([blob])
const url = window.URL.createObjectURL(blobFile)
const link = document.createElement('a')
link.href = url
const extension = getExtensionFromMime(blob.type)
const fileName = `${submissionId}.${extension}`
link.setAttribute('download', `${fileName}`)
document.body.appendChild(link)
link.click()
link.parentNode.removeChild(link)
} else {
console.log('failed to download artifact')
}
})
})
}, [submissionId, token])

return (
<Modal theme={theme} onCancel={onClose}>
<div className={styles['container']}>
<div className={styles['list']}>
<div className={styles['header']}>
<div className={styles['header-title']}>Artifact ID</div>
<div className={styles['header-action']}>Action</div>
</div>
{
!loading && artifacts.map((item) => {
return (
<div className={styles['list-item']}>
<div className={styles['artifact-name']}>{item}</div>
<ReactSVG
className={styles['icon-download']}
path={assets('./IconSquareDownload.svg')}
onClick={() => onDownloadArtifact(item)}
/>
</div>
)
})
}

{
!loading && artifacts.length === 0 && <div className={styles['no-artifacts']}>No artifacts found</div>
}

{
loading && <Loader />
}
</div>
</div>
</Modal>
)
}

ArtifactsListModal.defaultProps = {
onClose: () => {},
submissionId: '',
token: '',
theme: ''
}

ArtifactsListModal.propTypes = {
onClose: PropTypes.func,
submissionId: PropTypes.string,
token: PropTypes.string,
theme: PropTypes.shape()
}
Loading