diff --git a/__tests__/shared/components/SubmissionManagement/SubmissionsTable.jsx b/__tests__/shared/components/SubmissionManagement/SubmissionsTable.jsx
index 51a7ecdf36..86f3ec3c08 100644
--- a/__tests__/shared/components/SubmissionManagement/SubmissionsTable.jsx
+++ b/__tests__/shared/components/SubmissionManagement/SubmissionsTable.jsx
@@ -6,6 +6,7 @@ test('Matches shallow shapshot', () => {
const renderer = new Renderer();
renderer.render((
{
renderer.render((
diff --git a/__tests__/shared/components/SubmissionManagement/__snapshots__/ScreeningDetails.jsx.snap b/__tests__/shared/components/SubmissionManagement/__snapshots__/ScreeningDetails.jsx.snap
index 07413c50a2..158305923c 100644
--- a/__tests__/shared/components/SubmissionManagement/__snapshots__/ScreeningDetails.jsx.snap
+++ b/__tests__/shared/components/SubmissionManagement/__snapshots__/ScreeningDetails.jsx.snap
@@ -14,7 +14,7 @@ exports[`Snapshot match 1`] = `
- Your submission has been received, and will be evaluated during Review phase.
+ Your submission has been received and may undergo AI-assisted review during Submission phase. Results will be available for inspection in the review app and final evaluation occurs during Review phase.
+
+
+ Review
+
+
+
+
+ Review
+
+
+
res.json())
+ .then(data => ({
+ submissionId,
+ runs: data,
+ }))
+ .catch((err) => {
+ throw err;
+ });
+}
export default redux.createActions({
PAGE: {
@@ -7,6 +27,8 @@ export default redux.createActions({
SHOW_DETAILS: _.identity,
CANCEL_DELETE: _.noop,
CONFIRM_DELETE: _.identity,
+ LOAD_AI_WORKFLOW_RUNS_INIT: loadAiWorkflowRunsInit,
+ LOAD_AI_WORKFLOW_RUNS_DONE: loadAiWorkflowRunsDone,
},
},
});
diff --git a/src/shared/components/SubmissionManagement/ScreeningDetails/index.jsx b/src/shared/components/SubmissionManagement/ScreeningDetails/index.jsx
index 37dd7102a1..600ef10ae6 100644
--- a/src/shared/components/SubmissionManagement/ScreeningDetails/index.jsx
+++ b/src/shared/components/SubmissionManagement/ScreeningDetails/index.jsx
@@ -59,7 +59,7 @@ export default function ScreeningDetails(props) {
return {
title: '',
classname: '',
- message: 'Your submission has been received, and will be evaluated during Review phase.',
+ message: 'Your submission has been received and may undergo AI-assisted review during Submission phase. Results will be available for inspection in the review app and final evaluation occurs during Review phase.',
};
};
diff --git a/src/shared/components/SubmissionManagement/Submission/index.jsx b/src/shared/components/SubmissionManagement/Submission/index.jsx
index 27b82fe3db..93829d147c 100644
--- a/src/shared/components/SubmissionManagement/Submission/index.jsx
+++ b/src/shared/components/SubmissionManagement/Submission/index.jsx
@@ -15,6 +15,7 @@ import _ from 'lodash';
import moment from 'moment';
import React from 'react';
import { CHALLENGE_STATUS, COMPETITION_TRACKS, safeForDownload } from 'utils/tc';
+import { config } from 'topcoder-react-utils';
import PT from 'prop-types';
@@ -28,7 +29,6 @@ import ScreeningStatus from '../ScreeningStatus';
import './styles.scss';
-
export default function Submission(props) {
const {
challenge,
@@ -48,6 +48,14 @@ export default function Submission(props) {
const safeForDownloadCheck = safeForDownload(submissionObject.url);
const onDownloadArtifacts = onOpenDownloadArtifactsModal.bind(1, submissionObject.id);
const onOpenRatingsList = onOpenRatingsListModal.bind(1, submissionObject.id);
+ const onOpenReviewApp = () => {
+ if (!challenge || !challenge.id) return;
+ const tab = submissionObject.type === 'CHECKPOINT_SUBMISSION'
+ ? 'checkpoint-submission'
+ : 'submission';
+ const url = `${config.REVIEW_APP_URL}/active-challenges/${challenge.id}/challenge-details?tab=${tab}`;
+ window.open(url, '_blank', 'noopener,noreferrer');
+ };
// Determine if a challenge is for Topcrowd so we can edit the UI accordingly
let isTopCrowdChallenge = false;
@@ -117,7 +125,7 @@ export default function Submission(props) {
: }
{ !isTopCrowdChallenge
? (
- Show Scores
}>
+ Show scores
}>
onOpenRatingsList()}
type="button"
@@ -151,6 +159,19 @@ export default function Submission(props) {
)
}
+ { !isTopCrowdChallenge
+ ? (
+ View Review Info
}>
+ onOpenReviewApp()}
+ type="button"
+ styleName="review-button"
+ >
+ Review
+
+
+ )
+ : }
onShowDetails(submissionObject.id)}
diff --git a/src/shared/components/SubmissionManagement/Submission/styles.scss b/src/shared/components/SubmissionManagement/Submission/styles.scss
index 9650a9c9cd..d51d25a383 100644
--- a/src/shared/components/SubmissionManagement/Submission/styles.scss
+++ b/src/shared/components/SubmissionManagement/Submission/styles.scss
@@ -176,9 +176,17 @@ $submission-space-50: $base-unit * 10;
.download-artifacts-button {
svg {
width: 24px;
+ fill: $color-turq-160;
}
}
+ .review-button {
+ cursor: pointer;
+ color: $color-turq-160;
+ font-size: medium;
+ font-weight: 700;
+ }
+
.download-button {
@include button;
diff --git a/src/shared/components/SubmissionManagement/SubmissionManagement/index.jsx b/src/shared/components/SubmissionManagement/SubmissionManagement/index.jsx
index 6cbecd938e..c0f962fe35 100644
--- a/src/shared/components/SubmissionManagement/SubmissionManagement/index.jsx
+++ b/src/shared/components/SubmissionManagement/SubmissionManagement/index.jsx
@@ -33,6 +33,7 @@ export default function SubmissionManagement(props) {
submissions,
loadingSubmissions,
showDetails,
+ submissionWorkflowRuns,
onDelete,
helpPageUrl,
onDownload,
@@ -182,6 +183,7 @@ export default function SubmissionManagement(props) {
);
submissionsWithDetails.push(submission);
+ const workflowRunsForSubmission = submissionWorkflowRuns
+ && submissionWorkflowRuns[subObject.id]
+ ? submissionWorkflowRuns[subObject.id]
+ : null;
const submissionDetail = (
{showDetails[subObject.id]
&& (
+
+
{
+ if (!run) return '';
+
+ if (run.status === 'IN_PROGRESS' || run.status === 'QUEUED') return 'PENDING';
+ if (run.status === 'FAILED') return 'FAILED';
+ if (run.status === 'SUCCESS') {
+ const passingScore = run.workflow && run.workflow.scorecard
+ ? run.workflow.scorecard.minimumPassingScore
+ : 0;
+ return run.score >= passingScore ? 'PASSED' : 'FAILED';
+ }
+
+ return run.status;
+};
+
+export default function TableWorkflowRuns(props) {
+ const { workflowRuns, challengeId } = props;
+ if (!workflowRuns || Object.keys(workflowRuns).length === 0) {
+ return null;
+ }
+ return (
+
+
+
+
+ AI Reviewer
+ Review Date
+ Score
+ Result
+
+
+
+ {Object.entries(workflowRuns).map(([workflowId, run]) => (
+
+ {run.workflow.name}
+ {run.status === 'SUCCESS' ? (
+ moment(run.completedAt)
+ .local()
+ .format(TABLE_DATE_FORMAT)
+ ) : '-'}
+
+
+ {(() => {
+ if (run.status !== 'SUCCESS') return '-';
+ if (run.workflow.id) {
+ return (
+
+ {run.score}
+
+ );
+ }
+ return run.score;
+ })()}
+
+ {getRunStatusText(run)}
+
+ ))}
+
+
+
+ );
+}
+
+TableWorkflowRuns.defaultProps = {
+ workflowRuns: [],
+ challengeId: '',
+};
+
+TableWorkflowRuns.propTypes = {
+ workflowRuns: PT.shape(),
+ challengeId: PT.string,
+};
diff --git a/src/shared/components/SubmissionManagement/TableWorkflowRuns/styles.scss b/src/shared/components/SubmissionManagement/TableWorkflowRuns/styles.scss
new file mode 100644
index 0000000000..e161d39a33
--- /dev/null
+++ b/src/shared/components/SubmissionManagement/TableWorkflowRuns/styles.scss
@@ -0,0 +1,38 @@
+@import '~styles/mixins';
+$status-space-10: $base-unit * 2;
+$status-space-20: $base-unit * 4;
+$status-space-25: $base-unit * 5;
+$submission-space-10: $base-unit * 2;
+$submission-space-20: $base-unit * 4;
+$submission-space-25: $base-unit * 5;
+$submission-space-50: $base-unit * 10;
+
+.workflow-table {
+ margin: 20px 0;
+ border-collapse: collapse;
+
+ th,
+ td {
+ border-top: 1px solid #ccc;
+ border-bottom: 1px solid #ccc;
+ text-align: left;
+ font-weight: 600;
+ line-height: $status-space-20;
+ vertical-align: middle;
+ padding: 12px !important;
+ }
+
+ th {
+ background-color: #f5f5f5;
+ font-weight: 600;
+ }
+
+ a {
+ color: $tc-dark-blue-110;
+ text-decoration: none;
+
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+}
diff --git a/src/shared/containers/SubmissionManagement/index.jsx b/src/shared/containers/SubmissionManagement/index.jsx
index 0b0676b186..3a05e73693 100644
--- a/src/shared/containers/SubmissionManagement/index.jsx
+++ b/src/shared/containers/SubmissionManagement/index.jsx
@@ -151,6 +151,8 @@ class SubmissionManagementPageContainer extends React.Component {
this.pendingReviewSummationChallengeId = null;
this.isComponentMounted = false;
+ this.loadedWorkflowKeys = new Set();
+ this.firstSubmissionExpanded = false;
this.state = {
needReload: false,
@@ -208,9 +210,23 @@ class SubmissionManagementPageContainer extends React.Component {
mySubmissions,
authTokens,
challengeId,
+ challenge,
+ loadAiWorkflowRuns,
+ onShowDetails,
} = this.props;
const { initialState } = this.state;
+ if (!challenge || !Array.isArray(challenge.reviewers) || !mySubmissions) return;
+
+ mySubmissions.forEach((submission) => {
+ const key = `${submission.id}`;
+ if (this.loadedWorkflowKeys.has(key)) return;
+
+ this.loadedWorkflowKeys.add(key);
+ loadAiWorkflowRuns(authTokens, submission.id);
+ });
+
+
if (initialState && mySubmissions) {
// eslint-disable-next-line react/no-did-update-set-state
this.setState({
@@ -220,6 +236,11 @@ class SubmissionManagementPageContainer extends React.Component {
return;
}
+ if (mySubmissions.length && !this.firstSubmissionExpanded) {
+ onShowDetails(mySubmissions[0].id); // expand first submission
+ this.firstSubmissionExpanded = true; // mark that we've expanded it
+ }
+
if (challengeId !== prevProps.challengeId
|| _.get(authTokens, 'tokenV3') !== _.get(prevProps.authTokens, 'tokenV3')) {
this.loadReviewSummations(_.get(authTokens, 'tokenV3'), challengeId);
@@ -355,6 +376,7 @@ class SubmissionManagementPageContainer extends React.Component {
onSubmissionDelete,
onSubmissionDeleteConfirmed,
showDetails,
+ submissionWorkflowRuns,
showModal,
toBeDeletedId,
} = this.props;
@@ -425,6 +447,7 @@ class SubmissionManagementPageContainer extends React.Component {
loadingSubmissions={Boolean(loadingSubmissionsForChallengeId)}
submissions={submissions}
showDetails={showDetails}
+ submissionWorkflowRuns={submissionWorkflowRuns}
submissionPhaseStartDate={submissionPhaseStartDate}
{...smConfig}
/>
@@ -527,6 +550,8 @@ SubmissionManagementPageContainer.propTypes = {
onSubmissionDelete: PT.func.isRequired,
onDownloadSubmission: PT.func.isRequired,
showDetails: PT.shape().isRequired,
+ submissionWorkflowRuns: PT.shape().isRequired,
+ loadAiWorkflowRuns: PT.func.isRequired,
showModal: PT.bool,
onCancelSubmissionDelete: PT.func.isRequired,
toBeDeletedId: PT.string,
@@ -562,6 +587,8 @@ function mapStateToProps(state, props) {
showDetails: state.page.submissionManagement.showDetails,
+ submissionWorkflowRuns: state.page.submissionManagement.submissionWorkflowRuns,
+
showModal: state.page.submissionManagement.showModal,
toBeDeletedId: state.page.submissionManagement.toBeDeletedId,
deletionSucceed: state.page.submissionManagement.deletionSucceed,
@@ -605,6 +632,13 @@ const mapDispatchToProps = dispatch => ({
dispatch(a.getSubmissionsInit(challengeId));
dispatch(a.getSubmissionsDone(challengeId, tokens.tokenV3));
},
+
+ loadAiWorkflowRuns: (tokens, submissionId) => {
+ dispatch(smpActions.page.submissionManagement.loadAiWorkflowRunsInit());
+ dispatch(smpActions.page.submissionManagement.loadAiWorkflowRunsDone(
+ tokens.tokenV3, submissionId,
+ ));
+ },
});
const SubmissionManagementContainer = connect(
diff --git a/src/shared/reducers/page/submission_management.js b/src/shared/reducers/page/submission_management.js
index 1b943c5ef6..7473be5e17 100644
--- a/src/shared/reducers/page/submission_management.js
+++ b/src/shared/reducers/page/submission_management.js
@@ -55,12 +55,24 @@ function create(initialState = {}) {
toBeDeletedId: payload,
deletionSucceed: true,
}),
+ [a.loadAiWorkflowRunsInit]: state => ({ ...state, isLoadingDetails: true }),
+ [a.loadAiWorkflowRunsDone]: (state, { payload }) => {
+ const { submissionId, runs } = payload;
+ return {
+ ...state,
+ submissionWorkflowRuns: {
+ ...state.submissionWorkflowRuns,
+ [submissionId]: runs,
+ },
+ };
+ },
}, _.defaults(initialState, {
showDetails: {},
showModal: false,
toBeDeletedId: '',
deletionSucceed: false,
+ submissionWorkflowRuns: {},
}));
}