diff --git a/__tests__/shared/components/SubmissionManagement/__snapshots__/SubmissionsTable.jsx.snap b/__tests__/shared/components/SubmissionManagement/__snapshots__/SubmissionsTable.jsx.snap
index ab31d14b9..d8f5971b3 100644
--- a/__tests__/shared/components/SubmissionManagement/__snapshots__/SubmissionsTable.jsx.snap
+++ b/__tests__/shared/components/SubmissionManagement/__snapshots__/SubmissionsTable.jsx.snap
@@ -30,6 +30,7 @@ exports[`Matches shallow shapshot 1`] = `
"id": "test-challenge",
}
}
+ isWorkflowRunComplete={true}
onDelete={[Function]}
onDownload={[Function]}
onOpenDownloadArtifactsModal={[Function]}
diff --git a/src/shared/components/SubmissionManagement/Submission/index.jsx b/src/shared/components/SubmissionManagement/Submission/index.jsx
index 93829d147..2c15defb4 100644
--- a/src/shared/components/SubmissionManagement/Submission/index.jsx
+++ b/src/shared/components/SubmissionManagement/Submission/index.jsx
@@ -42,6 +42,7 @@ export default function Submission(props) {
onOpenRatingsListModal,
status,
allowDelete,
+ isWorkflowRunComplete,
} = props;
const formatDate = date => moment(+new Date(date)).format('MMM DD, YYYY hh:mm A');
const onDownloadSubmission = onDownload.bind(1, submissionObject.id);
@@ -66,6 +67,10 @@ export default function Submission(props) {
}
}
+ const showDeleteButton = status !== CHALLENGE_STATUS.COMPLETED
+ && track === COMPETITION_TRACKS.DES
+ && safeForDownloadCheck === true;
+
return (
|
@@ -146,17 +151,33 @@ export default function Submission(props) {
onClick={() => onDownload(submissionObject.id)}
>
*/ }
- {status !== CHALLENGE_STATUS.COMPLETED
- && track === COMPETITION_TRACKS.DES
- && safeForDownloadCheck === true && (
-
+ {showDeleteButton && (
+ isWorkflowRunComplete ? (
+
+ ) : (
+ // Disabled delete button with tooltip when workflow run is pending
+ (
+
+ You can delete this submission only after the review is complete.
+
+ )}
+ >
+
+
+ )
)
}
{ !isTopCrowdChallenge
@@ -217,4 +238,5 @@ Submission.propTypes = {
allowDelete: PT.bool.isRequired,
onOpenDownloadArtifactsModal: PT.func,
onOpenRatingsListModal: PT.func,
+ isWorkflowRunComplete: PT.bool.isRequired,
};
diff --git a/src/shared/components/SubmissionManagement/SubmissionsTable/index.jsx b/src/shared/components/SubmissionManagement/SubmissionsTable/index.jsx
index 9f36dfedd..a9ca5652f 100644
--- a/src/shared/components/SubmissionManagement/SubmissionsTable/index.jsx
+++ b/src/shared/components/SubmissionManagement/SubmissionsTable/index.jsx
@@ -74,9 +74,29 @@ export default function SubmissionsTable(props) {
submissionObjects.forEach((subObject) => {
// submissionPhaseStartDate will be the start date of
// the current submission/checkpoint or empty string if any other phase
+
+ const TERMINAL_STATUSES = [
+ 'COMPLETED',
+ 'FAILURE',
+ 'CANCELLED',
+ 'SUCCESS',
+ ];
+
+ const workflowRunsForSubmission = submissionWorkflowRuns
+ && submissionWorkflowRuns[subObject.id]
+ ? submissionWorkflowRuns[subObject.id]
+ : null;
+
+ const hasRuns = workflowRunsForSubmission && workflowRunsForSubmission.length > 0;
+
+ const isWorkflowRunComplete = !hasRuns
+ ? true
+ : workflowRunsForSubmission.every(run => TERMINAL_STATUSES.includes(run.status));
+
const allowDelete = submissionPhaseStartDate
&& moment(subObject.submissionDate).isAfter(submissionPhaseStartDate);
+
const submission = (
);
submissionsWithDetails.push(submission);
- const workflowRunsForSubmission = submissionWorkflowRuns
- && submissionWorkflowRuns[subObject.id]
- ? submissionWorkflowRuns[subObject.id]
- : null;
const submissionDetail = (
|
diff --git a/src/shared/services/reviewOpportunities.js b/src/shared/services/reviewOpportunities.js
index d9e525e65..6fcbd7ffd 100644
--- a/src/shared/services/reviewOpportunities.js
+++ b/src/shared/services/reviewOpportunities.js
@@ -1,4 +1,5 @@
import { config } from 'topcoder-react-utils';
+import { withEstimatedReviewerPayments } from 'utils/reviewOpportunities';
const v6ApiUrl = config.API.V6;
@@ -22,7 +23,10 @@ export default async function getReviewOpportunities(page, pageSize) {
throw new Error(res.statusText);
}
- return res.json();
+ const data = await res.json();
+
+ const opportunities = Array.isArray(data) ? data : [];
+ return opportunities.map(opportunity => withEstimatedReviewerPayments(opportunity));
}
/**
@@ -51,7 +55,7 @@ export async function getDetails(challengeId, opportunityId) {
const challengeData = await challengeRes.json();
return {
- ...opportunityData.result.content,
+ ...withEstimatedReviewerPayments(opportunityData.result.content),
challenge: challengeData,
};
} catch (err) {
diff --git a/src/shared/utils/reviewOpportunities.js b/src/shared/utils/reviewOpportunities.js
index f2731bdd2..c92fbeea0 100644
--- a/src/shared/utils/reviewOpportunities.js
+++ b/src/shared/utils/reviewOpportunities.js
@@ -5,6 +5,54 @@ import _ from 'lodash';
import moment from 'moment';
import { REVIEW_OPPORTUNITY_TYPES } from './tc';
+export const DEFAULT_ESTIMATED_SUBMISSIONS = 2;
+
+export const calculateEstimatedReviewerPayment = (
+ basePayment,
+ incrementalPayment,
+ estimatedSubmissions = DEFAULT_ESTIMATED_SUBMISSIONS,
+) => {
+ const base = _.toNumber(basePayment);
+ const incremental = _.toNumber(incrementalPayment);
+ const submissions = _.toNumber(estimatedSubmissions);
+
+ if (_.isNaN(base) || _.isNaN(submissions)) {
+ return null;
+ }
+
+ const incrementalValue = _.isNaN(incremental) ? 0 : incremental;
+ return base + (submissions * incrementalValue);
+};
+
+export const withEstimatedReviewerPayments = (
+ opportunity,
+ estimatedSubmissions = DEFAULT_ESTIMATED_SUBMISSIONS,
+) => {
+ if (!opportunity) return opportunity;
+
+ const estimatedPayment = calculateEstimatedReviewerPayment(
+ _.get(opportunity, 'basePayment', _.get(opportunity, 'payments[0].payment')),
+ _.get(opportunity, 'incrementalPayment', 0),
+ estimatedSubmissions,
+ );
+
+ if (!_.isNumber(estimatedPayment)) {
+ return opportunity;
+ }
+
+ const payments = Array.isArray(opportunity.payments)
+ ? opportunity.payments.map(payment => ({
+ ...payment,
+ payment: estimatedPayment,
+ }))
+ : opportunity.payments;
+
+ return {
+ ...opportunity,
+ payments,
+ };
+};
+
/**
* Infers open positions using review opportunity details and organizes them by role
*
@@ -14,7 +62,10 @@ import { REVIEW_OPPORTUNITY_TYPES } from './tc';
export const openPositionsByRole = (details) => {
if (!details.payments) return [];
- const roleCount = details.payments.length;
+ const detailsWithPayments = withEstimatedReviewerPayments(details);
+ const payments = detailsWithPayments.payments || [];
+
+ const roleCount = payments.length;
let approved;
if (details.applications && details.openPositions === 1 && roleCount === 2) {
@@ -28,7 +79,7 @@ export const openPositionsByRole = (details) => {
return details.openPositions / roleCount;
};
- return details.payments.map(({ role, roleId, payment }) => ({
+ return payments.map(({ role, roleId, payment }) => ({
role,
roleId,
payment,