Skip to content

Commit

Permalink
Adjusting UI to use new attemptIndices and assignment-solver records …
Browse files Browse the repository at this point in the history
…provided now in API to better track assignment solution attempts.
  • Loading branch information
krulis-martin committed Jul 25, 2022
1 parent 4775236 commit e6be966
Show file tree
Hide file tree
Showing 14 changed files with 242 additions and 31 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { Table, Modal } from 'react-bootstrap';
import { FormattedMessage } from 'react-intl';

Expand Down Expand Up @@ -34,6 +35,7 @@ const AssignmentDetails = ({
maxPointsBeforeSecondDeadline,
isBonus,
runtimeEnvironments,
assignmentSolver = null,
canSubmit,
pointsPercentualThreshold,
isPublic,
Expand All @@ -56,6 +58,8 @@ const AssignmentDetails = ({
maxPointsBeforeSecondDeadline,
});

const lastAttemptIndex = assignmentSolver && assignmentSolver.get('lastAttemptIndex');

return (
<Box
title={<FormattedMessage id="generic.details" defaultMessage="Details" />}
Expand Down Expand Up @@ -249,7 +253,7 @@ const AssignmentDetails = ({

<tr>
<td className="text-center text-muted shrink-col px-2">
{isStudent && canSubmit.canSubmit ? <SendIcon /> : <Icon icon="ban" />}
{isStudent && canSubmit.canSubmit ? <SendIcon /> : <Icon icon="hashtag" />}
</td>
<th>
<FormattedMessage id="app.assignment.submissionsCountLimit" defaultMessage="Submission attempts" />:
Expand All @@ -263,8 +267,19 @@ const AssignmentDetails = ({
<td>
{isStudent ? (
<>
{canSubmit.submittedCount}
{lastAttemptIndex || canSubmit.submittedCount}
{submissionsCountLimit !== null && ` / ${submissionsCountLimit}`}
{lastAttemptIndex && lastAttemptIndex > canSubmit.submittedCount && (
<small className="pl-2 text-muted">
(
<FormattedMessage
id="app.assignment.submissionCountLimitIncreasedByDeletion"
defaultMessage="+{count} {count, plural, one {attempt} other {attempts}} added by deleted solutions"
values={{ count: lastAttemptIndex - canSubmit.submittedCount }}
/>
)
</small>
)}
</>
) : (
<>{submissionsCountLimit === null ? '-' : submissionsCountLimit}</>
Expand All @@ -274,7 +289,7 @@ const AssignmentDetails = ({

<tr>
<td className="text-center text-muted shrink-col px-2">
<Icon icon={['far', 'folder-open']} />
<Icon icon={['far', 'file-code']} />
</td>
<th>
<FormattedMessage id="app.assignment.solutionFilesLimit" defaultMessage="Solution file restrictions" />:
Expand Down Expand Up @@ -358,6 +373,7 @@ AssignmentDetails.propTypes = {
permissionHints: PropTypes.object.isRequired,
isStudent: PropTypes.bool.isRequired,
className: PropTypes.string,
assignmentSolver: ImmutablePropTypes.map,
};

export default AssignmentDetails;
49 changes: 43 additions & 6 deletions src/components/Assignments/SolutionsTable/SolutionsTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@ const SolutionsTable = ({
noteMaxlen = null,
compact = false,
selected = null,
assignmentSolver = null,
assignmentSolversLoading = false,
}) => (
<Table responsive className={styles.solutionsTable}>
<thead>
<tr>
<th />
<th />
<th>
<FormattedMessage id="app.solutionsTable.submissionDate" defaultMessage="Date of submission" />
Expand All @@ -41,12 +44,44 @@ const SolutionsTable = ({
</th>
)}
<td className="text-right text-muted small">
{solutions.size > 5 && (
<FormattedMessage
id="app.solutionsTable.rowsCount"
defaultMessage="Total records: {count}"
values={{ count: solutions.size }}
/>
{assignmentSolversLoading ? (
<LoadingIcon />
) : (
<>
{assignmentSolver &&
(assignmentSolver.get('lastAttemptIndex') > 5 ||
assignmentSolver.get('lastAttemptIndex') > solutions.size) && (
<>
{!compact && (
<FormattedMessage
id="app.solutionsTable.attemptsCount"
defaultMessage="Solutions submitted: {count}"
values={{ count: assignmentSolver.get('lastAttemptIndex') }}
/>
)}

{assignmentSolver.get('lastAttemptIndex') > solutions.size && (
<span>
{!compact && <>&nbsp;&nbsp;</>}(
<FormattedMessage
id="app.solutionsTable.attemptsDeleted"
defaultMessage="{deleted} deleted"
values={{ deleted: assignmentSolver.get('lastAttemptIndex') - solutions.size }}
/>
)
</span>
)}
</>
)}

{!compact && !assignmentSolver && solutions.size > 5 && (
<FormattedMessage
id="app.solutionsTable.rowsCount"
defaultMessage="Total records: {count}"
values={{ count: solutions.size }}
/>
)}
</>
)}
</td>
</tr>
Expand Down Expand Up @@ -100,6 +135,8 @@ SolutionsTable.propTypes = {
noteMaxlen: PropTypes.number,
compact: PropTypes.bool,
selected: PropTypes.string,
assignmentSolver: ImmutablePropTypes.map,
assignmentSolversLoading: PropTypes.bool,
};

export default SolutionsTable;
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const showScoreAndPoints = status => status === 'done' || status === 'failed';

const SolutionsTableRow = ({
id,
attemptIndex,
assignmentId,
groupId,
status = null,
Expand Down Expand Up @@ -60,6 +61,8 @@ const SolutionsTableRow = ({
return (
<tbody>
<tr className={selected ? 'table-active' : ''}>
<td className="text-nowrap valign-middle text-bold">{attemptIndex}.</td>

<td
rowSpan={splitOnTwoLines ? 2 : 1}
className={classnames({
Expand Down Expand Up @@ -174,6 +177,7 @@ const SolutionsTableRow = ({

SolutionsTableRow.propTypes = {
id: PropTypes.string.isRequired,
attemptIndex: PropTypes.number.isRequired,
assignmentId: PropTypes.string.isRequired,
groupId: PropTypes.string.isRequired,
status: PropTypes.string,
Expand Down
9 changes: 9 additions & 0 deletions src/components/Solutions/SolutionDetail/SolutionDetail.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class SolutionDetail extends Component {
const {
solution: {
id,
attemptIndex,
note = '',
createdAt,
authorId,
Expand All @@ -82,6 +83,8 @@ class SolutionDetail extends Component {
refreshSolutionEvaluations = null,
scoreConfigSelector = null,
canResubmit = false,
assignmentSolversLoading,
assignmentSolverSelector,
} = this.props;

const { openFileId, openFileName, openZipEntry, scoreDialogOpened } = this.state;
Expand All @@ -105,6 +108,7 @@ class SolutionDetail extends Component {
<Col xl={6}>
<SolutionStatus
id={id}
attemptIndex={attemptIndex}
evaluation={evaluation}
evaluationStatus={safeGet(lastSubmission, ['evaluationStatus'], 'missing-submission')}
submittedAt={createdAt}
Expand All @@ -127,6 +131,8 @@ class SolutionDetail extends Component {
runtimeEnvironments.find(({ id }) => id === runtimeEnvironmentId)
}
otherSolutions={otherSolutions}
assignmentSolversLoading={assignmentSolversLoading}
assignmentSolverSelector={assignmentSolverSelector}
/>
</Col>
<Col xl={6}>
Expand Down Expand Up @@ -338,6 +344,7 @@ class SolutionDetail extends Component {
SolutionDetail.propTypes = {
solution: PropTypes.shape({
id: PropTypes.string.isRequired,
attemptIndex: PropTypes.number.isRequired,
note: PropTypes.string,
lastSubmission: PropTypes.shape({ id: PropTypes.string.isRequired }),
createdAt: PropTypes.number.isRequired,
Expand All @@ -354,6 +361,8 @@ SolutionDetail.propTypes = {
files: ImmutablePropTypes.map,
download: PropTypes.func,
otherSolutions: ImmutablePropTypes.list.isRequired,
assignmentSolversLoading: PropTypes.bool,
assignmentSolverSelector: PropTypes.func.isRequired,
assignment: PropTypes.object.isRequired,
evaluations: PropTypes.object.isRequired,
runtimeEnvironments: PropTypes.array,
Expand Down
34 changes: 25 additions & 9 deletions src/components/Solutions/SolutionStatus/SolutionStatus.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import Icon, {
FailureIcon,
CodeIcon,
LinkIcon,
LoadingIcon,
WarningIcon,
} from '../../icons';
import AssignmentDeadlinesGraph from '../../Assignments/Assignment/AssignmentDeadlinesGraph';
Expand Down Expand Up @@ -69,6 +70,7 @@ class SolutionStatus extends Component {
render() {
const {
id,
attemptIndex,
otherSolutions,
assignment: {
id: assignmentId,
Expand Down Expand Up @@ -96,13 +98,18 @@ class SolutionStatus extends Component {
actualPoints,
overriddenPoints = null,
editNote = null,
assignmentSolversLoading,
assignmentSolverSelector,
links: { SOLUTION_DETAIL_URI_FACTORY },
} = this.props;

const important = getImportantSolutions(otherSolutions, id);
const environment =
runtimeEnvironments && runtimeEnvironmentId && runtimeEnvironments.find(({ id }) => id === runtimeEnvironmentId);

const assignmentSolver = assignmentSolverSelector && assignmentSolverSelector(assignmentId, userId);
const lastAttemptIndex = assignmentSolver && assignmentSolver.get('lastAttemptIndex');

return (
<>
<Box
Expand Down Expand Up @@ -162,7 +169,7 @@ class SolutionStatus extends Component {
{note.length > 0 ? (
note
) : (
<em className="text-muted">
<em className="text-muted small">
<FormattedMessage id="app.solution.emptyNote" defaultMessage="empty" />
</em>
)}
Expand Down Expand Up @@ -348,14 +355,18 @@ class SolutionStatus extends Component {
<FormattedMessage id="app.solution.solutionAttempt" defaultMessage="Solution Attempt" />:
</th>
<td>
<FormattedMessage
id="app.solution.solutionAttemptValue"
defaultMessage="{index} of {count}"
values={{
index: otherSolutions.size - important.selectedIdx,
count: otherSolutions.size,
}}
/>
{!lastAttemptIndex ? (
<LoadingIcon />
) : (
<FormattedMessage
id="app.solution.solutionAttemptValue"
defaultMessage="{index} of {count}"
values={{
index: attemptIndex,
count: lastAttemptIndex,
}}
/>
)}

{otherSolutions && otherSolutions.size > 1 && (
<span
Expand Down Expand Up @@ -528,6 +539,8 @@ class SolutionStatus extends Component {
runtimeEnvironments={runtimeEnvironments}
noteMaxlen={32}
selected={id}
assignmentSolversLoading={assignmentSolversLoading}
assignmentSolver={assignmentSolver}
/>
</Modal.Body>
</Modal>
Expand All @@ -538,6 +551,7 @@ class SolutionStatus extends Component {

SolutionStatus.propTypes = {
id: PropTypes.string.isRequired,
attemptIndex: PropTypes.number.isRequired,
otherSolutions: ImmutablePropTypes.list.isRequired,
assignment: PropTypes.shape({
id: PropTypes.string.isRequired,
Expand Down Expand Up @@ -565,6 +579,8 @@ SolutionStatus.propTypes = {
actualPoints: PropTypes.number,
overriddenPoints: PropTypes.number,
editNote: PropTypes.func,
assignmentSolversLoading: PropTypes.bool,
assignmentSolverSelector: PropTypes.func.isRequired,
links: PropTypes.object.isRequired,
};

Expand Down
2 changes: 1 addition & 1 deletion src/helpers/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ const idSelector = obj => obj.id;
* @param {Function} predicate called on every entry, returns true should the entry remain
* @returns {Object} clone of obj with entries filtered out
*/
export const objectFilter = (obj, predicate) => {
export const objectFilter = (obj, predicate = val => Boolean(val)) => {
const res = {};
Object.keys(obj)
.filter(key => predicate(obj[key], key))
Expand Down
3 changes: 3 additions & 0 deletions src/locales/cs.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@
"app.assignment.solutionFilesLimitCount": "{count} {count, plural, one {soubor} =2 {soubory} =3 {soubory} =4 {soubory} other {souborů}}",
"app.assignment.solutionFilesLimitExplanation": "Omezení se uplatňují na počet odevzdaných souborů a jejich celkovou velikost.",
"app.assignment.solutionFilesLimitSize": "{size} KiB {count, plural, one {} other {celkem}}",
"app.assignment.submissionCountLimitIncreasedByDeletion": "+{count} {count, plural, one {pokus vytvořen} =2 {pokusy vytvořeny} =3 {pokusy vytvořeny} =4 {pokusy vytvořeny} other {pokusů vytvořeno}} smazáním řešení",
"app.assignment.submissionsCountLimit": "Počet pokusů",
"app.assignment.submissionsCountLimitExplanation": "Maximální počet odevzdaných řešení této úlohy od jednoho studenta. Vyučující můž udělit další pokusy tak, že smaže starší odevzdaná řešení.",
"app.assignment.syncAttachmentFiles": "Přiložené soubory",
Expand Down Expand Up @@ -1586,6 +1587,8 @@
"app.solutionFiles.title": "Odevzdané soubory",
"app.solutionFiles.total": "Celkem:",
"app.solutionsTable.assignment": "Úloha",
"app.solutionsTable.attemptsCount": "Odevzdaných řešení: {count}",
"app.solutionsTable.attemptsDeleted": "{deleted} {deleted, plural, =2 {smazána} =3 {smazána} =4 {smazána} other {smazáno}}",
"app.solutionsTable.commentsIcon.count": "Počet komentářů: {count}",
"app.solutionsTable.commentsIcon.last": "Poslední komentář: {last}",
"app.solutionsTable.environment": "Cílový jazyk",
Expand Down
3 changes: 3 additions & 0 deletions src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@
"app.assignment.solutionFilesLimitCount": "{count} {count, plural, one {file} other {files}}",
"app.assignment.solutionFilesLimitExplanation": "The restrictions may limit maximal number of submitted files and their total size.",
"app.assignment.solutionFilesLimitSize": "{size} KiB {count, plural, one {} other {total}}",
"app.assignment.submissionCountLimitIncreasedByDeletion": "+{count} {count, plural, one {attempt} other {attempts}} added by deleted solutions",
"app.assignment.submissionsCountLimit": "Submission attempts",
"app.assignment.submissionsCountLimitExplanation": "Maximal number of solutions logged by one student for this assignment. The teacher may choose to grant additional attempts by deleting old solutions.",
"app.assignment.syncAttachmentFiles": "Text attachment files",
Expand Down Expand Up @@ -1586,6 +1587,8 @@
"app.solutionFiles.title": "Submitted Files",
"app.solutionFiles.total": "Total:",
"app.solutionsTable.assignment": "Assignment",
"app.solutionsTable.attemptsCount": "Solutions submitted: {count}",
"app.solutionsTable.attemptsDeleted": "{deleted} deleted",
"app.solutionsTable.commentsIcon.count": "Total Comments: {count}",
"app.solutionsTable.commentsIcon.last": "Last Comment: {last}",
"app.solutionsTable.environment": "Target language",
Expand Down
Loading

0 comments on commit e6be966

Please sign in to comment.