Skip to content

Commit 5aaff3d

Browse files
authored
Merge pull request #1620 from topcoder-platform/develop
PROD RELEASE - Features and fixes in WM related to deprecating Connect & Submission Review UI apps
2 parents 02d1dc4 + ffaa101 commit 5aaff3d

File tree

32 files changed

+1309
-311
lines changed

32 files changed

+1309
-311
lines changed

.circleci/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ workflows:
152152
context: org-global
153153
filters: &filters-dev
154154
branches:
155-
only: ["develop", "PM-803_wm-regression-fixes"]
155+
only: ["develop", "PM-803_wm-regression-fixes", "PM-902_show-all-projects-on-challenge-page"]
156156

157157
# Production builds are exectuted only on tagged commits to the
158158
# master branch.

config/constants/development.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ module.exports = {
2525
RESOURCES_API_URL: `${DEV_API_HOSTNAME}/v5/resources`,
2626
RESOURCE_ROLES_API_URL: `${DEV_API_HOSTNAME}/v5/resource-roles`,
2727
SUBMISSIONS_API_URL: `${DEV_API_HOSTNAME}/v5/submissions`,
28+
REVIEW_TYPE_API_URL: `${DEV_API_HOSTNAME}/v5/reviewTypes`,
2829
SUBMISSION_REVIEW_APP_URL: `https://submission-review.${DOMAIN}/challenges`,
2930
STUDIO_URL: `https://studio.${DOMAIN}`,
3031
CONNECT_APP_URL: `https://connect.${DOMAIN}`,

config/constants/production.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ module.exports = {
2424
RESOURCES_API_URL: `${PROD_API_HOSTNAME}/v5/resources`,
2525
RESOURCE_ROLES_API_URL: `${PROD_API_HOSTNAME}/v5/resource-roles`,
2626
SUBMISSIONS_API_URL: `${PROD_API_HOSTNAME}/v5/submissions`,
27+
REVIEW_TYPE_API_URL: `${PROD_API_HOSTNAME}/v5/reviewTypes`,
2728
SUBMISSION_REVIEW_APP_URL: `https://submission-review.${DOMAIN}/challenges`,
2829
STUDIO_URL: `https://studio.${DOMAIN}`,
2930
CONNECT_APP_URL: `https://connect.${DOMAIN}`,

package-lock.json

Lines changed: 367 additions & 148 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@
115115
"terser": "^3.16.1",
116116
"terser-webpack-plugin": "1.1.0",
117117
"topcoder-healthcheck-dropin": "^1.0.3",
118-
"topcoder-react-lib": "^1.2.10",
118+
"topcoder-react-lib": "github:topcoder-platform/topcoder-react-lib#1.2.18",
119119
"url-loader": "1.1.1",
120120
"webpack": "^4.43.0",
121121
"webpack-dev-server": "^3.11.0",

src/actions/challenges.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ import {
5757
} from '../config/constants'
5858
import { loadProject } from './projects'
5959
import { removeChallengeFromPhaseProduct, saveChallengeAsPhaseProduct } from '../services/projects'
60+
import { checkAdmin } from '../util/tc'
6061

6162
/**
6263
* Member challenges related redux actions
@@ -158,7 +159,7 @@ export function loadChallengesByPage (
158159
filters['projectId'] = projectId
159160
} else if (_.isObject(projectId) && projectId.value > 0) {
160161
filters['projectId'] = projectId.value
161-
} else if (userId) {
162+
} else if (!checkAdmin(getState().auth.token) && userId) {
162163
// Note that we only add the memberId field if *no* project ID is given,
163164
// so that the list of *all challenges shows only those that the member is on
164165
filters['memberId'] = userId

src/actions/projects.js

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1+
import _ from 'lodash'
2+
13
import {
4+
PROJECT_TYPE_TAAS,
5+
PROJECTS_PAGE_SIZE,
6+
LOAD_PROJECTS_PENDING,
7+
LOAD_PROJECTS_SUCCESS,
8+
UNLOAD_PROJECTS_SUCCESS,
9+
LOAD_PROJECTS_FAILURE,
210
LOAD_PROJECT_BILLING_ACCOUNT,
311
LOAD_CHALLENGE_MEMBERS_SUCCESS,
412
LOAD_PROJECT_DETAILS,
@@ -21,8 +29,96 @@ import {
2129
getProjectTypes,
2230
createProjectApi,
2331
fetchBillingAccounts,
32+
fetchMemberProjects,
2433
updateProjectApi
2534
} from '../services/projects'
35+
import { checkAdmin } from '../util/tc'
36+
37+
function _loadProjects (projectNameOrIdFilter = '', paramFilters = {}) {
38+
return (dispatch, getState) => {
39+
dispatch({
40+
type: LOAD_PROJECTS_PENDING
41+
})
42+
43+
const filters = {
44+
sort: 'lastActivityAt desc',
45+
perPage: PROJECTS_PAGE_SIZE,
46+
...paramFilters
47+
}
48+
49+
if (!_.isEmpty(projectNameOrIdFilter)) {
50+
if (!isNaN(projectNameOrIdFilter)) { // if it is number
51+
filters['id'] = parseInt(projectNameOrIdFilter, 10)
52+
} else { // text search
53+
filters['keyword'] = decodeURIComponent(projectNameOrIdFilter)
54+
}
55+
}
56+
57+
if (!checkAdmin(getState().auth.token)) {
58+
filters['memberOnly'] = true
59+
}
60+
61+
// eslint-disable-next-line no-debugger
62+
const state = getState().projects
63+
fetchMemberProjects(filters).then(({ projects, pagination }) => dispatch({
64+
filters,
65+
type: LOAD_PROJECTS_SUCCESS,
66+
projects: _.uniqBy((filters.page ? state.projects || [] : []).concat(projects), 'id'),
67+
total: pagination.xTotal,
68+
page: pagination.xPage
69+
})).catch(() => dispatch({
70+
type: LOAD_PROJECTS_FAILURE
71+
}))
72+
}
73+
}
74+
75+
export function loadProjects (projectNameOrIdFilter = '', paramFilters = {}) {
76+
return async (dispatch, getState) => {
77+
const _filters = _.assign({}, paramFilters)
78+
if (_.isEmpty(_filters) || !_filters.type) {
79+
let projectTypes = getState().projects.projectTypes
80+
81+
if (!projectTypes.length) {
82+
dispatch({
83+
type: LOAD_PROJECTS_PENDING
84+
})
85+
await loadProjectTypes()(dispatch)
86+
projectTypes = getState().projects.projectTypes
87+
}
88+
89+
_.assign(_filters, {
90+
type: projectTypes.filter(d => d.key !== PROJECT_TYPE_TAAS).map(d => d.key)
91+
})
92+
}
93+
94+
return _loadProjects(projectNameOrIdFilter, _filters)(dispatch, getState)
95+
}
96+
}
97+
98+
/**
99+
* Load more projects for the authenticated user
100+
*/
101+
export function loadMoreProjects () {
102+
return (dispatch, getState) => {
103+
const { projectFilters, projectsPage } = getState().projects
104+
105+
loadProjects('', _.assign({}, projectFilters, {
106+
perPage: PROJECTS_PAGE_SIZE,
107+
page: projectsPage + 1
108+
}))(dispatch, getState)
109+
}
110+
}
111+
112+
/**
113+
* Unloads projects of the authenticated user
114+
*/
115+
export function unloadProjects () {
116+
return (dispatch) => {
117+
dispatch({
118+
type: UNLOAD_PROJECTS_SUCCESS
119+
})
120+
}
121+
}
26122

27123
/**
28124
* Loads project details

src/actions/sidebar.js

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export function loadProjects (filterProjectName = '', paramFilters = {}) {
3737
})
3838

3939
const filters = {
40+
status: 'active',
4041
sort: 'lastActivityAt desc',
4142
perPage: PROJECTS_PAGE_SIZE,
4243
...paramFilters
@@ -66,26 +67,18 @@ export function loadProjects (filterProjectName = '', paramFilters = {}) {
6667
}
6768
}
6869

69-
/**
70-
* Load more projects for the authenticated user
71-
*/
72-
export function loadMoreProjects (filterProjectName = '', paramFilters = {}) {
70+
// Load next page of projects
71+
export function loadNextProjects () {
7372
return (dispatch, getState) => {
74-
const state = getState().sidebar
73+
const { projectFilters, projectsPage } = getState().sidebar
7574

76-
loadProjects(filterProjectName, _.assignIn({}, paramFilters, {
75+
loadProjects('', _.assign({}, projectFilters, {
7776
perPage: PROJECTS_PAGE_SIZE,
78-
page: state.page + 1
77+
page: projectsPage + 1
7978
}))(dispatch, getState)
8079
}
8180
}
8281

83-
export function loadTaasProjects (filterProjectName = '', paramFilters = {}) {
84-
return loadProjects(filterProjectName, Object.assign({
85-
type: 'talent-as-a-service'
86-
}, paramFilters))
87-
}
88-
8982
/**
9083
* Unloads projects of the authenticated user
9184
*/
Lines changed: 4 additions & 0 deletions
Loading
Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
@import "../../../styles/includes";
2+
3+
.container {
4+
box-sizing: border-box;
5+
background: $white;
6+
opacity: 1;
7+
position: relative;
8+
display: flex;
9+
flex-direction: column;
10+
justify-content: flex-start;
11+
border-radius: 6px;
12+
margin: 0 auto;
13+
width: 800px;
14+
min-height: 350px;
15+
padding-top: 60px;
16+
.list {
17+
.header {
18+
border-bottom: 1px solid $tc-gray-60;
19+
padding-bottom: 10px;
20+
display: flex;
21+
padding-left: 40px;
22+
padding-right: 40px;
23+
color: $tc-gray-70;
24+
font-weight: 500;
25+
26+
.header-title {
27+
flex: 1;
28+
}
29+
}
30+
.list-item {
31+
border-bottom: 1px solid $tc-gray-60;
32+
padding-bottom: 10px;
33+
padding-top: 10px;
34+
display: flex;
35+
padding-left: 40px;
36+
padding-right: 40px;
37+
color: $tc-gray-70;
38+
.artifact-name {
39+
display: flex;
40+
flex: 1;
41+
}
42+
.icon-download {
43+
cursor: pointer;
44+
}
45+
}
46+
.no-artifacts {
47+
@include roboto;
48+
49+
margin-top: 40px;
50+
text-align: center;
51+
}
52+
}
53+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import React, { useCallback, useEffect, useState } from 'react'
2+
import Modal from '../../Modal'
3+
4+
import styles from './ArtifactsListModal.module.scss'
5+
import PropTypes from 'prop-types'
6+
import ReactSVG from 'react-svg'
7+
import { getTopcoderReactLib, isValidDownloadFile } from '../../../util/topcoder-react-lib'
8+
import Loader from '../../Loader'
9+
const assets = require.context('../../../assets/images', false, /svg/)
10+
11+
export const ArtifactsListModal = ({ onClose, submissionId, token, theme }) => {
12+
const [artifacts, setArtifacts] = useState([])
13+
const [loading, setLoading] = useState(false)
14+
15+
const getArtifacts = useCallback(async () => {
16+
const reactLib = getTopcoderReactLib()
17+
const { getService } = reactLib.services.submissions
18+
const submissionsService = getService(token)
19+
const { artifacts: resp } = await submissionsService.getSubmissionArtifacts(submissionId)
20+
setArtifacts(resp)
21+
setLoading(false)
22+
}, [submissionId, token])
23+
24+
const getExtensionFromMime = useCallback((mimeType) => {
25+
const mimeMap = {
26+
'application/zip': 'zip',
27+
'application/pdf': 'pdf',
28+
'image/jpeg': 'jpg',
29+
'image/png': 'png',
30+
'text/plain': 'txt'
31+
}
32+
return mimeMap[mimeType] || 'zip'
33+
}, [])
34+
35+
useEffect(() => {
36+
setLoading(true)
37+
getArtifacts()
38+
}, [submissionId])
39+
40+
const onDownloadArtifact = useCallback((item) => {
41+
// download submission
42+
const reactLib = getTopcoderReactLib()
43+
const { getService } = reactLib.services.submissions
44+
const submissionsService = getService(token)
45+
submissionsService.downloadSubmissionArtifact(submissionId, item)
46+
.then((blob) => {
47+
isValidDownloadFile(blob).then((isValidFile) => {
48+
if (isValidFile.success) {
49+
// eslint-disable-next-line no-undef
50+
const blobFile = new Blob([blob])
51+
const url = window.URL.createObjectURL(blobFile)
52+
const link = document.createElement('a')
53+
link.href = url
54+
const extension = getExtensionFromMime(blob.type)
55+
const fileName = `${submissionId}.${extension}`
56+
link.setAttribute('download', `${fileName}`)
57+
document.body.appendChild(link)
58+
link.click()
59+
link.parentNode.removeChild(link)
60+
} else {
61+
console.log('failed to download artifact')
62+
}
63+
})
64+
})
65+
}, [submissionId, token])
66+
67+
return (
68+
<Modal theme={theme} onCancel={onClose}>
69+
<div className={styles['container']}>
70+
<div className={styles['list']}>
71+
<div className={styles['header']}>
72+
<div className={styles['header-title']}>Artifact ID</div>
73+
<div className={styles['header-action']}>Action</div>
74+
</div>
75+
{
76+
!loading && artifacts.map((item) => {
77+
return (
78+
<div className={styles['list-item']}>
79+
<div className={styles['artifact-name']}>{item}</div>
80+
<ReactSVG
81+
className={styles['icon-download']}
82+
path={assets('./IconSquareDownload.svg')}
83+
onClick={() => onDownloadArtifact(item)}
84+
/>
85+
</div>
86+
)
87+
})
88+
}
89+
90+
{
91+
!loading && artifacts.length === 0 && <div className={styles['no-artifacts']}>No artifacts found</div>
92+
}
93+
94+
{
95+
loading && <Loader />
96+
}
97+
</div>
98+
</div>
99+
</Modal>
100+
)
101+
}
102+
103+
ArtifactsListModal.defaultProps = {
104+
onClose: () => {},
105+
submissionId: '',
106+
token: '',
107+
theme: ''
108+
}
109+
110+
ArtifactsListModal.propTypes = {
111+
onClose: PropTypes.func,
112+
submissionId: PropTypes.string,
113+
token: PropTypes.string,
114+
theme: PropTypes.shape()
115+
}

0 commit comments

Comments
 (0)