Skip to content

Commit

Permalink
Code reviews implemented (review state buttons, adding/editing/removi…
Browse files Browse the repository at this point in the history
…ng actions and forms on the source codes page, indicators, messages and notifications).
  • Loading branch information
krulis-martin committed Oct 30, 2022
1 parent 751d84b commit 101286e
Show file tree
Hide file tree
Showing 34 changed files with 1,471 additions and 506 deletions.
4 changes: 2 additions & 2 deletions recodex-web.spec
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
%define name recodex-web
%define short_name web-app
%define version 2.4.0
%define unmangled_version 5de194c7ea0249e347c35ca1fe66ffb62e9f83b9
%define release 1
%define unmangled_version 4371fe38d12c44840702abed4f865d7b49bfb162
%define release 2

Summary: ReCodEx web-app component
Name: %{name}
Expand Down
29 changes: 10 additions & 19 deletions src/components/Assignments/SolutionsTable/SolutionTableRowIcons.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { OverlayTrigger, Tooltip } from 'react-bootstrap';

import Icon from '../../icons';
import SolutionReviewIcon from '../../Solutions/SolutionReviewIcon';
import AssignmentStatusIcon, { getStatusDesc } from '../Assignment/AssignmentStatusIcon';
import CommentsIcon from './CommentsIcon';

const SolutionTableRowIcons = ({
id,
accepted,
reviewed,
review = null,
isBestSolution,
status,
lastSubmission,
commentsStats = null,
isReviewer = false,
}) => (
<>
<AssignmentStatusIcon
Expand All @@ -24,20 +23,7 @@ const SolutionTableRowIcons = ({
isBestSolution={isBestSolution}
/>

{reviewed && (
<OverlayTrigger
placement="right"
overlay={
<Tooltip id={`reviewed-${id}`}>
<FormattedMessage
id="app.solutionsTable.reviewedTooltip"
defaultMessage="The solution has been reviewed by the supervisor."
/>
</Tooltip>
}>
<Icon icon="stamp" className="text-muted" gapLeft />
</OverlayTrigger>
)}
{review && <SolutionReviewIcon id={`review-${id}`} review={review} isReviewer={isReviewer} gapLeft />}

<CommentsIcon id={id} commentsStats={commentsStats} gapLeft />
</>
Expand All @@ -47,7 +33,11 @@ SolutionTableRowIcons.propTypes = {
id: PropTypes.string.isRequired,
commentsStats: PropTypes.object,
accepted: PropTypes.bool.isRequired,
reviewed: PropTypes.bool.isRequired,
review: PropTypes.shape({
startedAt: PropTypes.number,
closedAt: PropTypes.number,
issues: PropTypes.number,
}),
isBestSolution: PropTypes.bool.isRequired,
status: PropTypes.string,
lastSubmission: PropTypes.shape({
Expand All @@ -56,6 +46,7 @@ SolutionTableRowIcons.propTypes = {
points: PropTypes.number.isRequired,
}),
}),
isReviewer: PropTypes.bool,
};

export default SolutionTableRowIcons;
56 changes: 31 additions & 25 deletions src/components/Assignments/SolutionsTable/SolutionsTableRow.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const SolutionsTableRow = ({
actualPoints,
createdAt,
accepted = false,
reviewed = false,
review = null,
isBestSolution = false,
runtimeEnvironment = null,
commentsStats = null,
Expand All @@ -62,15 +62,16 @@ const SolutionsTableRow = ({
const splitOnTwoLines = hasNote && compact;

return (
<tbody>
<tbody onClick={!selected && onSelect ? () => onSelect(id) : null}>
<tr
className={classnames({
'table-primary': selected,
clickable: !selected && onSelect,
'table-warning': highlighted,
})}
onClick={!selected && onSelect ? () => onSelect(id) : null}>
<td className="text-nowrap valign-middle text-bold">{attemptIndex}.</td>
})}>
<td className="text-nowrap valign-middle text-bold" rowSpan={splitOnTwoLines ? 2 : 1}>
{attemptIndex}.
</td>

<td
rowSpan={splitOnTwoLines ? 2 : 1}
Expand All @@ -81,11 +82,12 @@ const SolutionsTableRow = ({
<SolutionTableRowIcons
id={id}
accepted={accepted}
reviewed={reviewed}
review={review}
isBestSolution={isBestSolution}
status={status}
lastSubmission={lastSubmission}
commentsStats={commentsStats}
isReviewer={permissionHints && permissionHints.review}
/>
</td>

Expand Down Expand Up @@ -128,13 +130,7 @@ const SolutionsTableRow = ({
)}

{showActionButtons && (
<td
className={classnames({
'text-right': true,
'valign-middle': true,
'text-nowrap': !splitOnTwoLines,
})}
rowSpan={splitOnTwoLines ? 2 : 1}>
<td className="text-right valign-middle text-nowrap" rowSpan={splitOnTwoLines ? 2 : 1}>
<TheButtonGroup>
{permissionHints && permissionHints.viewDetail && (
<>
Expand Down Expand Up @@ -165,16 +161,17 @@ const SolutionsTableRow = ({
)}

{permissionHints && permissionHints.setFlag && (
<>
<AcceptSolutionContainer
id={id}
locale={locale}
captionAsTooltip={compact}
shortLabel={!compact}
size="xs"
/>
<ReviewSolutionContainer id={id} locale={locale} captionAsTooltip={compact} size="xs" />
</>
<AcceptSolutionContainer
id={id}
locale={locale}
captionAsTooltip={compact}
shortLabel={!compact}
size="xs"
/>
)}

{permissionHints && permissionHints.review && (
<ReviewSolutionContainer id={id} locale={locale} captionAsTooltip={compact} size="xs" />
)}

{permissionHints && permissionHints.delete && (
Expand All @@ -186,7 +183,12 @@ const SolutionsTableRow = ({
</tr>

{splitOnTwoLines && (
<tr>
<tr
className={classnames({
'table-primary': selected,
clickable: !selected && onSelect,
'table-warning': highlighted,
})}>
<td colSpan={4} className={styles.noteRow}>
<b>
<FormattedMessage id="app.solutionsTable.note" defaultMessage="Note" />:
Expand Down Expand Up @@ -218,7 +220,11 @@ SolutionsTableRow.propTypes = {
}),
createdAt: PropTypes.number.isRequired,
accepted: PropTypes.bool,
reviewed: PropTypes.bool,
review: PropTypes.shape({
startedAt: PropTypes.number,
closedAt: PropTypes.number,
issues: PropTypes.number,
}),
isBestSolution: PropTypes.bool,
commentsStats: PropTypes.object,
runtimeEnvironment: PropTypes.object,
Expand Down
10 changes: 7 additions & 3 deletions src/components/Solutions/SolutionDetail/SolutionDetail.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class SolutionDetail extends Component {
bonusPoints,
actualPoints,
accepted,
reviewed,
review = null,
runtimeEnvironmentId,
lastSubmission,
permissionHints = EMPTY_OBJ,
Expand Down Expand Up @@ -117,7 +117,7 @@ class SolutionDetail extends Component {
note={note}
editNote={editNote}
accepted={accepted}
reviewed={reviewed}
review={review}
assignment={assignment}
actualPoints={actualPoints}
overriddenPoints={overriddenPoints}
Expand Down Expand Up @@ -356,7 +356,11 @@ SolutionDetail.propTypes = {
overriddenPoints: PropTypes.number,
actualPoints: PropTypes.number,
accepted: PropTypes.bool.isRequired,
reviewed: PropTypes.bool.isRequired,
review: PropTypes.shape({
startedAt: PropTypes.number,
closedAt: PropTypes.number,
issues: PropTypes.number,
}),
runtimeEnvironmentId: PropTypes.string,
permissionHints: PropTypes.object,
}).isRequired,
Expand Down
71 changes: 71 additions & 0 deletions src/components/Solutions/SolutionReviewIcon/SolutionReviewIcon.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { OverlayTrigger, Tooltip } from 'react-bootstrap';

import { ReviewIcon } from '../../icons';
import DateTime from '../../widgets/DateTime';

const SolutionReviewIcon = ({ id, review, isReviewer = false, placement = 'bottom', className = '', ...props }) => {
if (!review.startedAt) {
return null;
}

const pendinColorClass = isReviewer ? 'text-danger fa-beat' : 'text-muted';
const closedColorClass = review.issues > 0 ? 'text-warning' : 'text-success';
const colorClass = !review.closedAt ? pendinColorClass : `${closedColorClass} half-gray`;

return (
<OverlayTrigger
placement={placement}
overlay={
<Tooltip id={id}>
<>
{!review.closedAt ? (
<FormattedMessage
id="app.solutionReviewIcon.tooltip.startedAt"
defaultMessage="The review was started at {started} and has not been closed yet."
values={{ started: <DateTime unixts={review.startedAt} /> }}
/>
) : (
<>
<FormattedMessage
id="app.solutionReviewIcon.tooltip.closedAt"
defaultMessage="The review was closed at {closed}, the comments are available at source codes page."
values={{ closed: <DateTime unixts={review.closedAt} /> }}
/>{' '}
{review.issues > 0 ? (
<FormattedMessage
id="app.solutionReviewIcon.tooltip.issues"
defaultMessage="The reviewer created {issues} {issues, plural, one {issue} other {issues}}, please, fix {issues, plural, one {it} other {them}} in the next solution."
values={{ issues: review.issues }}
/>
) : (
<FormattedMessage
id="app.solutionReviewIcon.tooltip.noIssues"
defaultMessage="No issues were created."
/>
)}
</>
)}
</>
</Tooltip>
}>
<ReviewIcon {...props} review={review} className={`${className} ${colorClass}`} />
</OverlayTrigger>
);
};

SolutionReviewIcon.propTypes = {
id: PropTypes.string.isRequired,
review: PropTypes.shape({
startedAt: PropTypes.number,
closedAt: PropTypes.number,
issues: PropTypes.number,
}).isRequired,
placement: PropTypes.string,
className: PropTypes.string,
isReviewer: PropTypes.bool,
};

export default SolutionReviewIcon;
1 change: 1 addition & 0 deletions src/components/Solutions/SolutionReviewIcon/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './SolutionReviewIcon';
52 changes: 41 additions & 11 deletions src/components/Solutions/SolutionStatus/SolutionStatus.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ import Icon, {
NoteIcon,
UserIcon,
SupervisorIcon,
ReviewedIcon,
SuccessOrFailureIcon,
ReviewIcon,
SuccessIcon,
FailureIcon,
CodeIcon,
Expand All @@ -40,7 +39,7 @@ const getImportantSolutions = defaultMemoize((solutions, selectedSolutionId) =>
const selectedIdx = solutions.findIndex(s => s.id === selectedSolutionId);
const accepted = solutions.find(s => s.accepted && s.id !== selectedSolutionId) || null;
const best = solutions.find(s => s.isBestSolution && s.id !== selectedSolutionId) || null;
let lastReviewed = solutions.filter(s => s.reviewed).shift();
let lastReviewed = solutions.filter(s => s.review && s.review.closedAt).shift();
lastReviewed = lastReviewed && lastReviewed.id !== selectedSolutionId ? lastReviewed : null;
return { selectedIdx, accepted, best, lastReviewed };
});
Expand Down Expand Up @@ -90,7 +89,7 @@ class SolutionStatus extends Component {
submittedBy,
note,
accepted,
reviewed,
review = null,
runtimeEnvironmentId,
runtimeEnvironments,
maxPoints,
Expand Down Expand Up @@ -337,19 +336,45 @@ class SolutionStatus extends Component {

<tr>
<td className="text-center text-muted shrink-col px-2">
<ReviewedIcon />
<ReviewIcon review={review} />
</td>
<th className="text-nowrap">
<FormattedMessage id="app.solution.reviewed" defaultMessage="Reviewed" />:
<Explanation id="reviewed">
{review && review.closedAt ? (
<>
<FormattedMessage id="app.solution.reviewClosedAt" defaultMessage="Reviewed at" />:
</>
) : (
<>
<FormattedMessage id="app.solution.reviewStartedAt" defaultMessage="Review started at" />:
</>
)}
<Explanation id="reviews">
<FormattedMessage
id="app.solution.explanations.reviewed"
defaultMessage="A marker that indicates whether the teacher has personally reviewed the solution. It may help navigate through the history of solutions, especially when the students are expected to make revisions."
id="app.solution.explanations.reviews"
defaultMessage="Indicates last change in the review state. The review is started before the teacher can make any comments. When the review is closed, all comments become visible to the author. Review comments are visible at the submitted files page."
/>
</Explanation>
</th>
<td>
<SuccessOrFailureIcon success={reviewed} />
{review && review.startedAt ? (
<DateTime unixts={review.closedAt || review.startedAt} />
) : (
<i className="text-muted">
<FormattedMessage id="app.solution.reviewNotStartedYet" defaultMessage="not started yet" />
</i>
)}

{review && review.issues > 0 && (
<small className="text-muted">
(
<FormattedMessage
id="app.solution.reviewIssuesCount"
defaultMessage="{issues} {issues, plural, one {issue} other {issues}} to resolve"
values={{ issues: review.issues }}
/>
)
</small>
)}

{important.lastReviewed && (
<span className="small float-right mx-2">
Expand Down Expand Up @@ -556,6 +581,7 @@ class SolutionStatus extends Component {
selected={id}
assignmentSolversLoading={assignmentSolversLoading}
assignmentSolver={assignmentSolver}
compact
/>
</Modal.Body>
</Modal>
Expand Down Expand Up @@ -586,7 +612,11 @@ SolutionStatus.propTypes = {
submittedBy: PropTypes.string,
note: PropTypes.string,
accepted: PropTypes.bool.isRequired,
reviewed: PropTypes.bool.isRequired,
review: PropTypes.shape({
startedAt: PropTypes.number,
closedAt: PropTypes.number,
issues: PropTypes.number,
}),
runtimeEnvironmentId: PropTypes.string,
runtimeEnvironments: PropTypes.array,
maxPoints: PropTypes.number.isRequired,
Expand Down
Loading

0 comments on commit 101286e

Please sign in to comment.