diff --git a/src/shared/actions/page/challenge-details.js b/src/shared/actions/page/challenge-details.js
index 4130d79e6c..a06218e8c8 100644
--- a/src/shared/actions/page/challenge-details.js
+++ b/src/shared/actions/page/challenge-details.js
@@ -1,7 +1,7 @@
/**
* Actions related to the UI state of challenge details page.
*/
-
+import _ from 'lodash';
import { createActions } from 'redux-actions';
/**
@@ -62,6 +62,15 @@ function toggleCheckpointFeedback(id, open) {
return { id, open };
}
+/**
+ * Creates action that toggle the submission testcase..
+ * @param {Number} index of submission testcase.
+ * @return {Action}
+ */
+function toggleSubmissionTestCase(index) {
+ return index;
+}
+
export default createActions({
PAGE: {
CHALLENGE_DETAILS: {
@@ -70,6 +79,8 @@ export default createActions({
TOGGLE_CHECKPOINT_FEEDBACK: toggleCheckpointFeedback,
SUBMISSIONS: {
TOGGLE_SUBMISSION_HISTORY: toggleSubmissionHistory,
+ TOGGLE_SUBMISSION_TESTCASE: toggleSubmissionTestCase,
+ CLEAR_SUBMISSION_TESTCASE_OPEN: _.identity,
},
},
},
diff --git a/src/shared/components/challenge-detail/Submissions/SubmissionInformationModal/index.jsx b/src/shared/components/challenge-detail/Submissions/SubmissionInformationModal/index.jsx
new file mode 100644
index 0000000000..c2eefda850
--- /dev/null
+++ b/src/shared/components/challenge-detail/Submissions/SubmissionInformationModal/index.jsx
@@ -0,0 +1,178 @@
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+import _ from 'lodash';
+import React from 'react';
+import PT from 'prop-types';
+import moment from 'moment';
+import { Modal, PrimaryButton } from 'topcoder-react-ui-kit';
+import ArrowUp from 'assets/images/icon-arrow-up.svg';
+import ArrowDown from 'assets/images/icon-arrow-down.svg';
+import LoadingIndicator from 'components/LoadingIndicator';
+
+import modal from './style.scss';
+
+class SubmissionInformationModal extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.getTestCaseOpen = this.getTestCaseOpen.bind(this);
+ this.getSubmissionBasicInfo = this.getSubmissionBasicInfo.bind(this);
+ this.getTestcases = this.getTestcases.bind(this);
+ }
+
+ componentWillUnmount() {
+ const { clearTestcaseOpen } = this.props;
+ clearTestcaseOpen();
+ }
+
+ getTestCaseOpen(index) {
+ const { openTestcase } = this.props;
+ return openTestcase[index.toString()] || false;
+ }
+
+
+ getSubmissionBasicInfo() {
+ const { submission, submissionInformation } = this.props;
+ return _.find(submission.submissions, item => item.submissionId === submissionInformation.id);
+ }
+
+ getTestcases() {
+ const { submissionInformation } = this.props;
+
+ let list = [];
+ _.forEach(submissionInformation.review, (item) => {
+ if (_.has(item, 'metadata') && _.has(item.metadata, 'testcases') && item.metadata.testcases.length > 0) {
+ list = list.concat(item.metadata.testcases);
+ }
+ });
+
+ return list;
+ }
+
+ /* eslint-disable class-methods-use-this */
+ renderCase(key, value) {
+ return (
+
+ {key}
+ {value}
+
+ );
+ }
+
+ render() {
+ const {
+ toggleTestcase, onClose, isLoadingSubmissionInformation,
+ submissionInformation, isReviewPhaseComplete,
+ } = this.props;
+ const submissionBasicInfo = isLoadingSubmissionInformation
+ ? null : this.getSubmissionBasicInfo();
+ const testcases = isLoadingSubmissionInformation ? [] : this.getTestcases();
+
+ return (
+ onClose(false)}>
+ {
+ !isLoadingSubmissionInformation && (
+
+ Advanced Details
+
+
+ Submission:
+ {submissionInformation.id}
+
+
+
+
Final Score
+
Provissional Score
+
Time
+
+
+
+ {(!submissionBasicInfo.finalScore && submissionBasicInfo.finalScore !== 0) || !isReviewPhaseComplete ? '-' : submissionBasicInfo.finalScore}
+
+
+ {(!submissionBasicInfo.provisionalScore && submissionBasicInfo.provisionalScore !== 0) ? '-' : submissionBasicInfo.provisionalScore}
+
+
+ {moment(submissionBasicInfo.submissionTime)
+ .format('DD MMM YYYY')} {moment(submissionBasicInfo.submissionTime)
+ .format('HH:mm:ss')}
+
+
+
+
+ {
+ testcases.length > 0 && (
+
+
+ Test cases executed
+
+ {
+ _.map(testcases, (item, index) => (
+
+
+
Test case #{index + 1}
+
toggleTestcase(index)} role="button" tabIndex={0}>
+ {
+ this.getTestCaseOpen(index) ? :
+ }
+
+
+ {
+ _.keys(item).length > 0 && (
+
+ {
+ _.map(_.keys(item), (key, caseIndex) => (
+
+ {
+ this.renderCase(key, item[key])
+ }
+
+ ))
+ }
+
+ )
+ }
+
+ ))
+ }
+
+ )
+ }
+
+
+
onClose(false)}>Dismiss
+
+
+
+ )
+ }
+ {
+ isLoadingSubmissionInformation && (
+
+ )
+ }
+
+ );
+ }
+}
+
+SubmissionInformationModal.defaultProps = {
+ isLoadingSubmissionInformation: false,
+ submissionInformation: null,
+};
+
+SubmissionInformationModal.propTypes = {
+ isLoadingSubmissionInformation: PT.bool,
+ submissionInformation: PT.shape(),
+ onClose: PT.func.isRequired,
+ toggleTestcase: PT.func.isRequired,
+ openTestcase: PT.shape({}).isRequired,
+ clearTestcaseOpen: PT.func.isRequired,
+ submission: PT.shape().isRequired,
+ isReviewPhaseComplete: PT.bool.isRequired,
+};
+
+export default SubmissionInformationModal;
diff --git a/src/shared/components/challenge-detail/Submissions/SubmissionInformationModal/style.scss b/src/shared/components/challenge-detail/Submissions/SubmissionInformationModal/style.scss
new file mode 100644
index 0000000000..ade6604e7e
--- /dev/null
+++ b/src/shared/components/challenge-detail/Submissions/SubmissionInformationModal/style.scss
@@ -0,0 +1,197 @@
+@import "~styles/mixins";
+
+.container {
+ @include roboto-regular;
+
+ max-height: 75vh;
+ overflow: auto;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ align-items: center;
+ background-color: $tc-white;
+ opacity: 1;
+ border-radius: 4px;
+ padding: 20px;
+ max-width: 100%;
+ box-shadow: 0 0 5px 0 silver, 0 0 1px 0 #d5d5d5;
+}
+
+.testcase-items {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+
+ .testcase-header {
+ display: flex;
+ justify-content: space-between;
+ background: #faf9fa;
+ height: 40px;
+ padding: 0 20px;
+ color: $tc-gray-50;
+ font-size: 16px;
+ font-weight: bold;
+ border-bottom: 1px solid $tc-gray-10;
+ align-items: center;
+
+ span {
+ cursor: pointer;
+ }
+ }
+
+ .testcase-grid {
+ width: 100%;
+ display: none;
+
+ .testcase-row {
+ width: 100%;
+ display: flex;
+ height: 30px;
+ justify-content: space-between;
+ align-items: center;
+ color: $tc-gray-50;
+ font-size: 13px;
+ padding: 0 20px;
+ border-bottom: 1px solid $tc-gray-10;
+ font-weight: bold;
+
+ span:last-child {
+ color: $tc-gray-90;
+ }
+ }
+
+ &.active {
+ display: flex;
+ flex-direction: column;
+ }
+ }
+}
+
+.submission-information-title {
+ font-size: 20px;
+ line-height: 24px;
+ color: $tc-gray-90;
+ text-transform: none;
+}
+
+.submission-information-details {
+ margin: 10px 0;
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+
+ .submission-information-details-row1 {
+ display: flex;
+ justify-content: flex-start;
+ width: 100%;
+ height: 30px;
+ color: $tc-gray-50;
+ font-size: 13px;
+ align-items: center;
+ margin-bottom: 10px;
+
+ .submission-information-details-title {
+ color: $tc-gray-90;
+ font-size: 16px;
+ font-weight: bold;
+ }
+
+ .submission-information-details-id {
+ color: $tc-dark-blue;
+ font-size: 16px;
+ }
+ }
+
+ .submission-information-details-row2 {
+ display: flex;
+ justify-content: space-between;
+ flex-direction: column;
+ width: 100%;
+ color: $tc-gray-50;
+ font-size: 13px;
+
+ .details-header {
+ width: 100%;
+ display: flex;
+ justify-content: space-between;
+ background: #faf9fa;
+ height: 30px;
+ padding-right: 20px;
+
+ .header-item {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 43%;
+ font-weight: bold;
+ }
+
+ .header-item:first-child {
+ width: 23%;
+ }
+
+ .header-item:last-child {
+ justify-content: flex-end;
+ width: 33%;
+ }
+ }
+
+ .details-grid {
+ width: 100%;
+ display: flex;
+ justify-content: space-between;
+ background-color: $tc-white;
+ border-bottom: 1px solid $tc-gray-10;
+ height: 30px;
+ padding-right: 20px;
+ align-items: center;
+ font-weight: bold;
+
+ .details-item {
+ width: 43%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ }
+
+ .details-item:first-child {
+ width: 23%;
+ }
+
+ .details-item:last-child {
+ justify-content: flex-end;
+ width: 33%;
+ min-width: 170px;
+ }
+ }
+ }
+}
+
+.testcase-title {
+ font-size: 16px;
+ line-height: 24px;
+ color: $tc-gray-90;
+ text-transform: none;
+ font-weight: bold;
+ display: flex;
+ justify-content: flex-start;
+ align-items: center;
+ margin-bottom: 10px;
+ margin-top: 25px;
+}
+
+.submission-information-buttons {
+ margin-top: 15px;
+
+ .submission-information-button-close {
+ button {
+ @include roboto-regular;
+
+ height: 45px;
+ width: 200px;
+ font-size: 16px;
+ background: #3b87f7;
+ border-radius: 5px;
+ }
+ }
+}
diff --git a/src/shared/components/challenge-detail/Submissions/SubmissionRow/SubmissionHistoryRow/index.jsx b/src/shared/components/challenge-detail/Submissions/SubmissionRow/SubmissionHistoryRow/index.jsx
index 4ae74f0d55..2e7f3d05b9 100644
--- a/src/shared/components/challenge-detail/Submissions/SubmissionRow/SubmissionHistoryRow/index.jsx
+++ b/src/shared/components/challenge-detail/Submissions/SubmissionRow/SubmissionHistoryRow/index.jsx
@@ -1,4 +1,5 @@
/* eslint jsx-a11y/no-static-element-interactions:0 */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
/**
* SubmissionHistoryRow component.
*/
@@ -16,6 +17,9 @@ export default function SubmissionHistoryRow({
provisionalScore,
submissionTime,
isReviewPhaseComplete,
+ onShowPopup,
+ submissionId,
+ member,
}) {
return (
@@ -32,11 +36,25 @@ export default function SubmissionHistoryRow({
{(!provisionalScore && provisionalScore !== 0) ? '-' : provisionalScore}
-
+
{moment(submissionTime).format('DD MMM YYYY')} {moment(submissionTime).format('HH:mm:ss')}
+ {
+ isMM && (
+
+
onShowPopup(true, submissionId, member)}
+ >
+ View Details
+
+
+ )
+ }
);
@@ -49,10 +67,13 @@ SubmissionHistoryRow.defaultProps = {
};
SubmissionHistoryRow.propTypes = {
+ member: PT.string.isRequired,
isMM: PT.bool.isRequired,
submission: PT.number.isRequired,
finalScore: PT.number,
provisionalScore: PT.number,
submissionTime: PT.string.isRequired,
isReviewPhaseComplete: PT.bool,
+ submissionId: PT.string.isRequired,
+ onShowPopup: PT.func.isRequired,
};
diff --git a/src/shared/components/challenge-detail/Submissions/SubmissionRow/SubmissionHistoryRow/style.scss b/src/shared/components/challenge-detail/Submissions/SubmissionRow/SubmissionHistoryRow/style.scss
index c1af985423..75014ca1ad 100644
--- a/src/shared/components/challenge-detail/Submissions/SubmissionRow/SubmissionHistoryRow/style.scss
+++ b/src/shared/components/challenge-detail/Submissions/SubmissionRow/SubmissionHistoryRow/style.scss
@@ -114,6 +114,18 @@
flex: 1;
}
}
+
+ &.mm {
+ flex: 25;
+ }
+ }
+
+ &.col-5 {
+ flex: 10;
+ color: #0a71e6;
+ cursor: pointer;
+ min-width: 83px;
+ outline: none;
}
}
}
diff --git a/src/shared/components/challenge-detail/Submissions/SubmissionRow/index.jsx b/src/shared/components/challenge-detail/Submissions/SubmissionRow/index.jsx
index 7987c3531a..6275ee55c0 100644
--- a/src/shared/components/challenge-detail/Submissions/SubmissionRow/index.jsx
+++ b/src/shared/components/challenge-detail/Submissions/SubmissionRow/index.jsx
@@ -14,8 +14,8 @@ import SubmissionHistoryRow from './SubmissionHistoryRow';
import './style.scss';
export default function SubmissionRow({
- isMM, openHistory, member, submissions, score, toggleHistory, colorStyle, isReviewPhaseComplete,
- finalRank, provisionalRank,
+ isMM, openHistory, member, submissions, score, toggleHistory, colorStyle,
+ isReviewPhaseComplete, finalRank, provisionalRank, onShowPopup,
}) {
const {
submissionTime, provisionalScore,
@@ -89,9 +89,14 @@ export default function SubmissionRow({
Provisional
-
+
Time
+ {
+ isMM && (
+
+ )
+ }
{
@@ -102,6 +107,8 @@ export default function SubmissionRow({
submission={submissions.length - index}
{...submissionHistory}
key={submissionHistory.submissionId}
+ onShowPopup={onShowPopup}
+ member={member}
/>
))
}
@@ -141,4 +148,5 @@ SubmissionRow.propTypes = {
isReviewPhaseComplete: PT.bool,
finalRank: PT.number,
provisionalRank: PT.number,
+ onShowPopup: PT.func.isRequired,
};
diff --git a/src/shared/components/challenge-detail/Submissions/SubmissionRow/style.scss b/src/shared/components/challenge-detail/Submissions/SubmissionRow/style.scss
index 45836a95b4..8dc28d04ae 100644
--- a/src/shared/components/challenge-detail/Submissions/SubmissionRow/style.scss
+++ b/src/shared/components/challenge-detail/Submissions/SubmissionRow/style.scss
@@ -167,6 +167,14 @@
}
}
}
+
+ &.mm {
+ flex: 25;
+ }
+ }
+
+ &.col-5 {
+ flex: 10;
}
}
diff --git a/src/shared/components/challenge-detail/Submissions/index.jsx b/src/shared/components/challenge-detail/Submissions/index.jsx
index 2b19e755e0..bb501a2c32 100644
--- a/src/shared/components/challenge-detail/Submissions/index.jsx
+++ b/src/shared/components/challenge-detail/Submissions/index.jsx
@@ -16,11 +16,21 @@ import LoadingIndicator from 'components/LoadingIndicator';
import { goToLogin } from 'utils/tc';
import Lock from '../icons/lock.svg';
import SubmissionRow from './SubmissionRow';
+import SubmissionInformationModal from './SubmissionInformationModal';
import './style.scss';
const { getProvisionalScore, getFinalScore } = submissionUtils;
class SubmissionsComponent extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ isShowInformation: false,
+ memberOfModal: '',
+ };
+ this.onHandleInformationPopup = this.onHandleInformationPopup.bind(this);
+ }
+
componentDidMount() {
const { challenge, loadMMSubmissions, auth } = this.props;
const isMM = challenge.subTrack.indexOf('MARATHON_MATCH') > -1;
@@ -31,8 +41,21 @@ class SubmissionsComponent extends React.Component {
return;
}
- if (isMM) {
- loadMMSubmissions(challenge.id, challenge.registrants, auth.tokenV3);
+ if (isMM && _.has(challenge, 'submissions') && challenge.submissions.length > 0) {
+ const submitterIds = _.map(challenge.submissions, item => item.submitterId);
+ loadMMSubmissions(challenge.id, submitterIds, challenge.registrants, auth.tokenV3);
+ }
+ }
+
+ onHandleInformationPopup(status, submissionId = null, member = '') {
+ const { loadSubmissionInformation, auth } = this.props;
+ this.setState({
+ isShowInformation: status,
+ memberOfModal: member,
+ });
+
+ if (status) {
+ loadSubmissionInformation(submissionId, auth.tokenV3);
}
}
@@ -42,6 +65,11 @@ class SubmissionsComponent extends React.Component {
submissionHistoryOpen,
mmSubmissions,
loadingMMSubmissionsForChallengeId,
+ isLoadingSubmissionInformation,
+ submissionInformation,
+ toggleSubmissionTestcase,
+ submissionTestcaseOpen,
+ clearSubmissionTestcaseOpen,
} = this.props;
const {
checkpoints,
@@ -50,6 +78,11 @@ class SubmissionsComponent extends React.Component {
allPhases,
} = challenge;
+ const { isShowInformation, memberOfModal } = this.state;
+
+ const modalSubmissionBasicInfo = () => _.find(mmSubmissions,
+ item => item.member === memberOfModal);
+
const renderSubmission = s => (
);
}
}
+SubmissionsComponent.defaultProps = {
+ isLoadingSubmissionInformation: false,
+ submissionInformation: null,
+};
+
SubmissionsComponent.propTypes = {
auth: PT.shape().isRequired,
challenge: PT.shape({
@@ -310,6 +365,12 @@ SubmissionsComponent.propTypes = {
loadMMSubmissions: PT.func.isRequired,
mmSubmissions: PT.arrayOf(PT.shape()).isRequired,
loadingMMSubmissionsForChallengeId: PT.string.isRequired,
+ isLoadingSubmissionInformation: PT.bool,
+ submissionInformation: PT.shape(),
+ loadSubmissionInformation: PT.func.isRequired,
+ toggleSubmissionTestcase: PT.func.isRequired,
+ clearSubmissionTestcaseOpen: PT.func.isRequired,
+ submissionTestcaseOpen: PT.shape({}).isRequired,
};
function mapDispatchToProps(dispatch) {
@@ -317,12 +378,22 @@ function mapDispatchToProps(dispatch) {
toggleSubmissionHistory: index => dispatch(
challengeDetailsActions.page.challengeDetails.submissions.toggleSubmissionHistory(index),
),
+ toggleSubmissionTestcase: index => dispatch(
+ challengeDetailsActions.page.challengeDetails.submissions.toggleSubmissionTestcase(index),
+ ),
+ clearSubmissionTestcaseOpen: () => dispatch(
+ challengeDetailsActions.page.challengeDetails.submissions.clearSubmissionTestcaseOpen(),
+ ),
};
}
function mapStateToProps(state) {
return {
submissionHistoryOpen: state.page.challengeDetails.submissionHistoryOpen,
+ submissionTestcaseOpen: state.page.challengeDetails.submissionTestcaseOpen,
+ isLoadingSubmissionInformation:
+ Boolean(state.challenge.loadingSubmissionInformationForSubmissionId),
+ submissionInformation: state.challenge.submissionInformation,
};
}
diff --git a/src/shared/containers/challenge-detail/index.jsx b/src/shared/containers/challenge-detail/index.jsx
index bc41f704de..58e4d2f551 100644
--- a/src/shared/containers/challenge-detail/index.jsx
+++ b/src/shared/containers/challenge-detail/index.jsx
@@ -238,6 +238,9 @@ class ChallengeDetailPageContainer extends React.Component {
loadMMSubmissions,
mmSubmissions,
loadingMMSubmissionsForChallengeId,
+ isLoadingSubmissionInformation,
+ submissionInformation,
+ loadSubmissionInformation,
} = this.props;
const {
@@ -386,6 +389,9 @@ class ChallengeDetailPageContainer extends React.Component {
mmSubmissions={mmSubmissions}
loadMMSubmissions={loadMMSubmissions}
auth={auth}
+ isLoadingSubmissionInformation={isLoadingSubmissionInformation}
+ submssionInformation={submissionInformation}
+ loadSubmissionInformation={loadSubmissionInformation}
/>
)
}
@@ -430,6 +436,8 @@ ChallengeDetailPageContainer.defaultProps = {
isMenuOpened: false,
loadingMMSubmissionsForChallengeId: '',
mmSubmissions: [],
+ isLoadingSubmissionInformation: false,
+ submissionInformation: null,
};
ChallengeDetailPageContainer.propTypes = {
@@ -476,6 +484,9 @@ ChallengeDetailPageContainer.propTypes = {
loadingMMSubmissionsForChallengeId: PT.string,
mmSubmissions: PT.arrayOf(PT.shape()),
loadMMSubmissions: PT.func.isRequired,
+ isLoadingSubmissionInformation: PT.bool,
+ submissionInformation: PT.shape(),
+ loadSubmissionInformation: PT.func.isRequired,
};
function mapStateToProps(state, props) {
@@ -511,6 +522,9 @@ function mapStateToProps(state, props) {
unregistering: state.challenge.unregistering,
isMenuOpened: !!state.topcoderHeader.openedMenu,
loadingMMSubmissionsForChallengeId: state.challenge.loadingMMSubmissionsForChallengeId,
+ isLoadingSubmissionInformation:
+ Boolean(state.challenge.loadingSubmissionInformationForSubmissionId),
+ submissionInformation: state.challenge.submissionInformation,
mmSubmissions: state.challenge.mmSubmissions,
};
}
@@ -609,10 +623,15 @@ const mapDispatchToProps = (dispatch) => {
dispatch(a.updateChallengeInit(uuid));
dispatch(a.updateChallengeDone(uuid, challenge, tokenV3));
},
- loadMMSubmissions: (challengeId, registrants, tokenV3) => {
+ loadMMSubmissions: (challengeId, submitterIds, registrants, tokenV3) => {
const a = actions.challenge;
dispatch(a.getMmSubmissionsInit(challengeId));
- dispatch(a.getMmSubmissionsDone(challengeId, registrants, tokenV3));
+ dispatch(a.getMmSubmissionsDone(challengeId, submitterIds, registrants, tokenV3));
+ },
+ loadSubmissionInformation: (submissionId, tokenV3) => {
+ const a = actions.challenge;
+ dispatch(a.getSubmissionInformationInit(submissionId));
+ dispatch(a.getSubmissionInformationDone(submissionId, tokenV3));
},
};
};
diff --git a/src/shared/reducers/page/challenge-details.js b/src/shared/reducers/page/challenge-details.js
index 46ac60cbef..2c19a68b8b 100644
--- a/src/shared/reducers/page/challenge-details.js
+++ b/src/shared/reducers/page/challenge-details.js
@@ -56,6 +56,27 @@ function toggleSubmissionHistory(state, { payload }) {
return { ...state, submissionHistoryOpen: newSubmissionHistoryOpen };
}
+/**
+ * Handler for open state of testcase of submission.
+ * @param {Object} state
+ * @param {Object} action
+ * @return {Object} New state.
+ */
+function toggleSubmissionTestcase(state, { payload }) {
+ const newSubmissionTestcaseOpen = _.clone(state.submissionTestcaseOpen);
+ newSubmissionTestcaseOpen[payload.toString()] = !newSubmissionTestcaseOpen[payload.toString()];
+ return { ...state, submissionTestcaseOpen: newSubmissionTestcaseOpen };
+}
+
+/**
+ * Handler for clear state of testcase open of submission.
+ * @param {Object} state
+ * @return {Object} New state.
+ */
+function clearSubmissionTestcaseOpen(state) {
+ return { ...state, submissionTestcaseOpen: {} };
+}
+
/**
* Creates a new reducer.
* @param {Object} state Optional. Initial state.
@@ -68,10 +89,13 @@ function create(state = {}) {
[a.setSpecsTabState]: onSetSpecsTabState,
[a.toggleCheckpointFeedback]: onToggleCheckpointFeedback,
[a.submissions.toggleSubmissionHistory]: toggleSubmissionHistory,
+ [a.submissions.toggleSubmissionTestcase]: toggleSubmissionTestcase,
+ [a.submissions.clearSubmissionTestcaseOpen]: clearSubmissionTestcaseOpen,
}, _.defaults(state, {
checkpoints: {},
specsTabState: SPECS_TAB_STATES.VIEW,
submissionHistoryOpen: {},
+ submissionTestcaseOpen: {},
}));
}