Skip to content

Commit 596b8c4

Browse files
authored
Merge pull request #1612 from topcoder-platform/pm-810
feat(PM-810) List and download artifacts in WM
2 parents 971f208 + a1ff894 commit 596b8c4

File tree

15 files changed

+827
-186
lines changed

15 files changed

+827
-186
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-810"]
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",
Lines changed: 4 additions & 0 deletions
Loading
Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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+
}
47+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
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 && <Loader />
92+
}
93+
</div>
94+
</div>
95+
</Modal>
96+
)
97+
}
98+
99+
ArtifactsListModal.defaultProps = {
100+
onClose: () => {},
101+
submissionId: '',
102+
token: '',
103+
theme: ''
104+
}
105+
106+
ArtifactsListModal.propTypes = {
107+
onClose: PropTypes.func,
108+
submissionId: PropTypes.string,
109+
token: PropTypes.string,
110+
theme: PropTypes.shape()
111+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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-item {
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+
.list-col-item {
39+
display: flex;
40+
flex: 1;
41+
}
42+
}
43+
}
44+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import React, { useCallback, useEffect, useState } from 'react'
2+
import Modal from '../../Modal'
3+
4+
import styles from './RatingsListModal.module.scss'
5+
import PropTypes from 'prop-types'
6+
import { getTopcoderReactLib } from '../../../util/topcoder-react-lib'
7+
import Loader from '../../Loader'
8+
import { getReviewTypes } from '../../../services/challenges'
9+
import { SystemReviewers } from '../../../config/constants'
10+
11+
export const RatingsListModal = ({ onClose, theme, token, submissionId, challengeId }) => {
12+
const [reviews, setReviews] = useState([])
13+
const [loading, setLoading] = useState(false)
14+
15+
const enrichSources = useCallback(async (submissionReviews, reviewSummation) => {
16+
const reactLib = getTopcoderReactLib()
17+
const { getService } = reactLib.services.members
18+
const membersService = getService(token)
19+
const resources = await membersService.getChallengeResources(challengeId)
20+
const reviewTypes = await getReviewTypes()
21+
22+
const finalReview = {
23+
reviewType: 'Final score',
24+
reviewer: '',
25+
score: reviewSummation ? reviewSummation.aggregateScore : 'N/A',
26+
isPassing: reviewSummation ? reviewSummation.isPassing : undefined
27+
}
28+
29+
return [...submissionReviews.map(review => {
30+
const reviewType = reviewTypes.find(rt => rt.id === review.typeId)
31+
const reviewer = resources.find(resource => resource.memberHandle === review.reviewerId) || SystemReviewers.Default
32+
return {
33+
...review,
34+
reviewType: reviewType ? reviewType.name : '',
35+
reviewer
36+
}
37+
}), finalReview]
38+
}, [token])
39+
40+
const getSubmission = useCallback(async () => {
41+
const reactLib = getTopcoderReactLib()
42+
const { getService } = reactLib.services.submissions
43+
const submissionsService = getService(token)
44+
const submissionInfo = await submissionsService.getSubmissionInformation(submissionId)
45+
setReviews(await enrichSources(submissionInfo.review, submissionInfo.reviewSummation[0]))
46+
setLoading(false)
47+
}, [submissionId, token])
48+
49+
useEffect(() => {
50+
setLoading(true)
51+
getSubmission()
52+
}, [submissionId])
53+
54+
return (
55+
<Modal theme={theme} onCancel={onClose}>
56+
<div className={styles['container']}>
57+
<div className={styles['list']}>
58+
<div className={styles['header']}>
59+
<div className={styles['header-item']}>Review Type</div>
60+
<div className={styles['header-item']}>Reviewer</div>
61+
<div className={styles['header-item']}>Score</div>
62+
<div className={styles['header-item']}>Status</div>
63+
</div>
64+
{reviews.map(review => {
65+
const { isPassing } = review
66+
const isFailed = isPassing === false
67+
const isPassed = isPassing === true
68+
const statusIsDefined = isPassed || isFailed
69+
const status = isPassing ? 'Passed' : 'Failed'
70+
71+
return (
72+
<div className={styles['list-item']}>
73+
<div className={styles['list-col-item']}>
74+
{review.reviewType}
75+
</div>
76+
<div className={styles['list-col-item']}>
77+
<strong>{review.reviewer}</strong>
78+
</div>
79+
<div className={styles['list-col-item']}>
80+
{review.score}
81+
</div>
82+
<div className={styles['list-col-item']}>
83+
{statusIsDefined ? status : 'N/A'}
84+
</div>
85+
</div>
86+
)
87+
})}
88+
</div>
89+
90+
{
91+
loading && <Loader />
92+
}
93+
</div>
94+
</Modal>
95+
)
96+
}
97+
98+
RatingsListModal.defaultProps = {
99+
onClose: () => {},
100+
theme: '',
101+
token: '',
102+
submissionId: '',
103+
challengeId: ''
104+
}
105+
106+
RatingsListModal.propTypes = {
107+
onClose: PropTypes.func,
108+
theme: PropTypes.shape(),
109+
token: PropTypes.string,
110+
submissionId: PropTypes.string,
111+
challengeId: PropTypes.string
112+
}

src/components/ChallengeEditor/Submissions/Submissions.module.scss

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,11 +239,24 @@ $base-unit: 5px;
239239
}
240240

241241
.col-8Table {
242-
button {
242+
.button-wrapper {
243+
display: flex;
244+
align-items: center;
245+
}
246+
.download-submission-button {
243247
padding: 0;
244248
border: none;
245249
background-color: transparent;
246250
outline: none;
251+
margin-right: 14px;
252+
width: 24px;
253+
svg {
254+
width: 24px;
255+
}
256+
}
257+
258+
.download-artifacts-button {
259+
height: 40px;
247260
}
248261
}
249262

0 commit comments

Comments
 (0)