Skip to content

Commit c72fc43

Browse files
committed
Implementing comparison of files from different solutions using diff-view component.
1 parent 7964edd commit c72fc43

File tree

14 files changed

+879
-284
lines changed

14 files changed

+879
-284
lines changed

src/components/Assignments/SolutionsTable/SolutionsTable.js

Lines changed: 112 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@ import PropTypes from 'prop-types';
33
import ImmutablePropTypes from 'react-immutable-proptypes';
44
import { FormattedMessage } from 'react-intl';
55
import { Table } from 'react-bootstrap';
6+
import { defaultMemoize } from 'reselect';
67

78
import NoSolutionYetTableRow from './NoSolutionYetTableRow';
89
import SolutionsTableRow from './SolutionsTableRow';
910
import { LoadingIcon } from '../../icons';
11+
import { EMPTY_ARRAY } from '../../../helpers/common';
1012

1113
import styles from './SolutionsTable.less';
1214

15+
const createHighlightsIndex = defaultMemoize(highlights => new Set(highlights));
16+
1317
const SolutionsTable = ({
1418
assignmentId,
1519
groupId,
@@ -18,122 +22,128 @@ const SolutionsTable = ({
1822
noteMaxlen = null,
1923
compact = false,
2024
selected = null,
25+
highlights = EMPTY_ARRAY,
2126
assignmentSolver = null,
2227
assignmentSolversLoading = false,
2328
showActionButtons = true,
2429
onSelect = null,
25-
}) => (
26-
<Table responsive className={styles.solutionsTable}>
27-
<thead>
28-
<tr>
29-
<th />
30-
<th />
31-
<th>
32-
<FormattedMessage id="app.solutionsTable.submissionDate" defaultMessage="Date of submission" />
33-
</th>
34-
<th className="text-center">
35-
<FormattedMessage id="app.solutionsTable.solutionValidity" defaultMessage="Validity" />
36-
</th>
37-
<th className="text-center">
38-
<FormattedMessage id="app.solutionsTable.receivedPoints" defaultMessage="Points" />
39-
</th>
40-
<th className="text-center">
41-
<FormattedMessage id="app.solutionsTable.environment" defaultMessage="Target language" />
42-
</th>
30+
}) => {
31+
const highlightsIndex = createHighlightsIndex(highlights);
4332

44-
{!compact && (
33+
return (
34+
<Table responsive className={styles.solutionsTable}>
35+
<thead>
36+
<tr>
37+
<th />
38+
<th />
4539
<th>
46-
<FormattedMessage id="app.solutionsTable.note" defaultMessage="Note" />
40+
<FormattedMessage id="app.solutionsTable.submissionDate" defaultMessage="Date of submission" />
41+
</th>
42+
<th className="text-center">
43+
<FormattedMessage id="app.solutionsTable.solutionValidity" defaultMessage="Validity" />
44+
</th>
45+
<th className="text-center">
46+
<FormattedMessage id="app.solutionsTable.receivedPoints" defaultMessage="Points" />
47+
</th>
48+
<th className="text-center">
49+
<FormattedMessage id="app.solutionsTable.environment" defaultMessage="Target language" />
4750
</th>
48-
)}
4951

50-
{(!compact || showActionButtons) && (
51-
<td className="text-right text-muted small">
52-
{assignmentSolversLoading ? (
53-
<LoadingIcon />
54-
) : (
55-
<>
56-
{assignmentSolver &&
57-
(assignmentSolver.get('lastAttemptIndex') > 5 ||
58-
assignmentSolver.get('lastAttemptIndex') > solutions.size) && (
59-
<>
60-
{!compact && (
61-
<FormattedMessage
62-
id="app.solutionsTable.attemptsCount"
63-
defaultMessage="Solutions submitted: {count}"
64-
values={{ count: assignmentSolver.get('lastAttemptIndex') }}
65-
/>
66-
)}
52+
{!compact && (
53+
<th>
54+
<FormattedMessage id="app.solutionsTable.note" defaultMessage="Note" />
55+
</th>
56+
)}
6757

68-
{assignmentSolver.get('lastAttemptIndex') > solutions.size && (
69-
<span>
70-
{!compact && <>&nbsp;&nbsp;</>}(
58+
{(!compact || showActionButtons) && (
59+
<td className="text-right text-muted small">
60+
{assignmentSolversLoading ? (
61+
<LoadingIcon />
62+
) : (
63+
<>
64+
{assignmentSolver &&
65+
(assignmentSolver.get('lastAttemptIndex') > 5 ||
66+
assignmentSolver.get('lastAttemptIndex') > solutions.size) && (
67+
<>
68+
{!compact && (
7169
<FormattedMessage
72-
id="app.solutionsTable.attemptsDeleted"
73-
defaultMessage="{deleted} deleted"
74-
values={{ deleted: assignmentSolver.get('lastAttemptIndex') - solutions.size }}
70+
id="app.solutionsTable.attemptsCount"
71+
defaultMessage="Solutions submitted: {count}"
72+
values={{ count: assignmentSolver.get('lastAttemptIndex') }}
7573
/>
76-
)
77-
</span>
78-
)}
79-
</>
74+
)}
75+
76+
{assignmentSolver.get('lastAttemptIndex') > solutions.size && (
77+
<span>
78+
{!compact && <>&nbsp;&nbsp;</>}(
79+
<FormattedMessage
80+
id="app.solutionsTable.attemptsDeleted"
81+
defaultMessage="{deleted} deleted"
82+
values={{ deleted: assignmentSolver.get('lastAttemptIndex') - solutions.size }}
83+
/>
84+
)
85+
</span>
86+
)}
87+
</>
88+
)}
89+
90+
{!compact && !assignmentSolver && solutions.size > 5 && (
91+
<FormattedMessage
92+
id="app.solutionsTable.rowsCount"
93+
defaultMessage="Total records: {count}"
94+
values={{ count: solutions.size }}
95+
/>
8096
)}
97+
</>
98+
)}
99+
</td>
100+
)}
101+
</tr>
102+
</thead>
103+
{solutions.size === 0 ? (
104+
<NoSolutionYetTableRow />
105+
) : (
106+
solutions.map((data, idx) => {
107+
if (!data) {
108+
return (
109+
<tbody key={idx}>
110+
<tr>
111+
<td colSpan={compact ? 6 : 7} className="text-center">
112+
<LoadingIcon size="xs" />
113+
</td>
114+
</tr>
115+
</tbody>
116+
);
117+
}
118+
119+
const id = data.id;
120+
const runtimeEnvironment =
121+
data.runtimeEnvironmentId &&
122+
runtimeEnvironments &&
123+
runtimeEnvironments.find(({ id }) => id === data.runtimeEnvironmentId);
81124

82-
{!compact && !assignmentSolver && solutions.size > 5 && (
83-
<FormattedMessage
84-
id="app.solutionsTable.rowsCount"
85-
defaultMessage="Total records: {count}"
86-
values={{ count: solutions.size }}
87-
/>
88-
)}
89-
</>
90-
)}
91-
</td>
92-
)}
93-
</tr>
94-
</thead>
95-
{solutions.size === 0 ? (
96-
<NoSolutionYetTableRow />
97-
) : (
98-
solutions.map((data, idx) => {
99-
if (!data) {
100125
return (
101-
<tbody key={idx}>
102-
<tr>
103-
<td colSpan={compact ? 6 : 7} className="text-center">
104-
<LoadingIcon size="xs" />
105-
</td>
106-
</tr>
107-
</tbody>
126+
<SolutionsTableRow
127+
key={id}
128+
id={id}
129+
status={data.lastSubmission ? data.lastSubmission.evaluationStatus : null}
130+
runtimeEnvironment={runtimeEnvironment}
131+
assignmentId={assignmentId}
132+
groupId={groupId}
133+
noteMaxlen={noteMaxlen}
134+
compact={compact}
135+
selected={id === selected}
136+
highlighted={highlightsIndex.has(id)}
137+
showActionButtons={showActionButtons}
138+
onSelect={onSelect}
139+
{...data}
140+
/>
108141
);
109-
}
110-
111-
const id = data.id;
112-
const runtimeEnvironment =
113-
data.runtimeEnvironmentId &&
114-
runtimeEnvironments &&
115-
runtimeEnvironments.find(({ id }) => id === data.runtimeEnvironmentId);
116-
117-
return (
118-
<SolutionsTableRow
119-
key={id}
120-
id={id}
121-
status={data.lastSubmission ? data.lastSubmission.evaluationStatus : null}
122-
runtimeEnvironment={runtimeEnvironment}
123-
assignmentId={assignmentId}
124-
groupId={groupId}
125-
noteMaxlen={noteMaxlen}
126-
compact={compact}
127-
selected={id === selected}
128-
showActionButtons={showActionButtons}
129-
onSelect={onSelect}
130-
{...data}
131-
/>
132-
);
133-
})
134-
)}
135-
</Table>
136-
);
142+
})
143+
)}
144+
</Table>
145+
);
146+
};
137147

138148
SolutionsTable.propTypes = {
139149
assignmentId: PropTypes.string.isRequired,
@@ -143,6 +153,7 @@ SolutionsTable.propTypes = {
143153
noteMaxlen: PropTypes.number,
144154
compact: PropTypes.bool,
145155
selected: PropTypes.string,
156+
highlights: PropTypes.array,
146157
assignmentSolver: ImmutablePropTypes.map,
147158
assignmentSolversLoading: PropTypes.bool,
148159
showActionButtons: PropTypes.bool,

src/components/Assignments/SolutionsTable/SolutionsTableRow.js

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import DeleteSolutionButtonContainer from '../../../containers/DeleteSolutionBut
1111
import AcceptSolutionContainer from '../../../containers/AcceptSolutionContainer';
1212
import ReviewSolutionContainer from '../../../containers/ReviewSolutionContainer';
1313

14-
import { DetailIcon } from '../../icons';
14+
import { DetailIcon, CodeFileIcon } from '../../icons';
1515
import DateTime from '../../widgets/DateTime';
1616
import OptionalTooltipWrapper from '../../widgets/OptionalTooltipWrapper';
1717
import Button, { TheButtonGroup } from '../../widgets/TheButton';
@@ -43,9 +43,10 @@ const SolutionsTableRow = ({
4343
noteMaxlen = null,
4444
compact = false,
4545
selected = false,
46+
highlighted = false,
4647
showActionButtons = true,
4748
onSelect = null,
48-
links: { SOLUTION_DETAIL_URI_FACTORY },
49+
links: { SOLUTION_DETAIL_URI_FACTORY, SOLUTION_SOURCE_CODES_URI_FACTORY },
4950
intl: { locale },
5051
}) => {
5152
const trimmedNote = note && note.trim();
@@ -63,7 +64,11 @@ const SolutionsTableRow = ({
6364
return (
6465
<tbody>
6566
<tr
66-
className={selected ? 'table-active' : onSelect ? 'clickable' : ''}
67+
className={classnames({
68+
'table-primary': selected,
69+
clickable: !selected && onSelect,
70+
'table-warning': highlighted,
71+
})}
6772
onClick={!selected && onSelect ? () => onSelect(id) : null}>
6873
<td className="text-nowrap valign-middle text-bold">{attemptIndex}.</td>
6974

@@ -132,17 +137,31 @@ const SolutionsTableRow = ({
132137
rowSpan={splitOnTwoLines ? 2 : 1}>
133138
<TheButtonGroup>
134139
{permissionHints && permissionHints.viewDetail && (
135-
<OptionalTooltipWrapper
136-
tooltip={<FormattedMessage id="generic.detail" defaultMessage="Detail" />}
137-
hide={!compact}
138-
tooltipId={`detail-${id}`}>
139-
<Link to={SOLUTION_DETAIL_URI_FACTORY(assignmentId, id)}>
140-
<Button size="xs" variant="secondary" disabled={selected}>
141-
<DetailIcon gapRight={!compact} />
142-
{!compact && <FormattedMessage id="generic.detail" defaultMessage="Detail" />}
143-
</Button>
144-
</Link>
145-
</OptionalTooltipWrapper>
140+
<>
141+
<OptionalTooltipWrapper
142+
tooltip={<FormattedMessage id="generic.detail" defaultMessage="Detail" />}
143+
hide={!compact}
144+
tooltipId={`detail-${id}`}>
145+
<Link to={SOLUTION_DETAIL_URI_FACTORY(assignmentId, id)}>
146+
<Button size="xs" variant="secondary" disabled={selected}>
147+
<DetailIcon gapRight={!compact} />
148+
{!compact && <FormattedMessage id="generic.detail" defaultMessage="Detail" />}
149+
</Button>
150+
</Link>
151+
</OptionalTooltipWrapper>
152+
153+
<OptionalTooltipWrapper
154+
tooltip={<FormattedMessage id="app.navigation.solutionFiles" defaultMessage="Submitted Files" />}
155+
hide={!compact}
156+
tooltipId={`codes-${id}`}>
157+
<Link to={SOLUTION_SOURCE_CODES_URI_FACTORY(assignmentId, id)}>
158+
<Button size="xs" variant="primary" disabled={selected}>
159+
<CodeFileIcon fixedWidth gapRight={!compact} />
160+
{!compact && <FormattedMessage id="generic.files" defaultMessage="Files" />}
161+
</Button>
162+
</Link>
163+
</OptionalTooltipWrapper>
164+
</>
146165
)}
147166

148167
{permissionHints && permissionHints.setFlag && (
@@ -207,6 +226,7 @@ SolutionsTableRow.propTypes = {
207226
noteMaxlen: PropTypes.number,
208227
compact: PropTypes.bool.isRequired,
209228
selected: PropTypes.bool,
229+
highlighted: PropTypes.bool,
210230
showActionButtons: PropTypes.bool,
211231
onSelect: PropTypes.func,
212232
links: PropTypes.object,

0 commit comments

Comments
 (0)