Skip to content

Commit

Permalink
Adding collective update functions for exercise assignments (e.g., sy…
Browse files Browse the repository at this point in the history
…ncing multiple assignments with the exercise at once).
  • Loading branch information
krulis-martin committed Dec 4, 2022
1 parent 1d36bc4 commit 6a635c8
Show file tree
Hide file tree
Showing 18 changed files with 425 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,57 +6,7 @@ import { Row, Col } from 'react-bootstrap';
import Button from '../../../widgets/TheButton';
import Callout from '../../../widgets/Callout';
import DateTime from '../../../widgets/DateTime';
import Explanation from '../../../widgets/Explanation';

const syncMessages = {
supplementaryFiles: (
<FormattedMessage id="app.assignment.syncSupplementaryFiles" defaultMessage="Supplementary files" />
),
attachmentFiles: <FormattedMessage id="app.assignment.syncAttachmentFiles" defaultMessage="Text attachment files" />,
exerciseTests: <FormattedMessage id="app.assignment.syncExerciseTests" defaultMessage="Exercise tests" />,
localizedTexts: <FormattedMessage id="app.assignment.syncLocalizedTexts" defaultMessage="Localized texts" />,
configurationType: (
<FormattedMessage
id="app.assignment.syncConfigurationType"
defaultMessage="Configuration was switched to advanced mode"
/>
),
scoreConfig: <FormattedMessage id="app.assignment.syncScoreConfig" defaultMessage="Score configuration" />,
exerciseConfig: <FormattedMessage id="app.assignment.syncExerciseConfig" defaultMessage="Exercise configuration" />,
runtimeEnvironments: (
<FormattedMessage id="app.assignment.syncRuntimeEnvironments" defaultMessage="Selection of runtime environments" />
),
exerciseEnvironmentConfigs: (
<FormattedMessage id="app.assignment.syncExerciseEnvironmentConfigs" defaultMessage="Environment configuration" />
),
hardwareGroups: <FormattedMessage id="app.assignment.syncHardwareGroups" defaultMessage="Hardware groups" />,
limits: <FormattedMessage id="app.assignment.syncLimits" defaultMessage="Limits" />,
mergeJudgeLogs: (
<span>
<FormattedMessage id="app.assignment.syncMergeJudgeLogs" defaultMessage="Judge logs merge flag" />
<Explanation id="syncMergeJudgeLogs">
<FormattedMessage
id="app.exercise.mergeJudgeLogsExplanation"
defaultMessage="The merge flag indicates whether primary (stdout) and secondary (stderr) judge logs are are concatenated in one log (which should be default for built-in judges). If the logs are separated, the visibility of each part may be controlled idividually in assignments. That might be helpful if you need to pass two separate logs from a custom judge (e.g., one is for students and one is for supervisors)."
/>
</Explanation>
</span>
),
};

const getSyncMessages = syncInfo => {
const res = [];
for (const field in syncMessages) {
if (!syncInfo[field]) {
continue;
}

if (!syncInfo[field].upToDate) {
res.push(<li key={field}>{syncMessages[field]}</li>);
}
}
return res;
};
import { getSyncMessages } from '../assignmentSyncHelper';

const AssignmentSync = ({ syncInfo, exerciseSync }) => {
const messages = getSyncMessages(syncInfo);
Expand All @@ -68,16 +18,12 @@ const AssignmentSync = ({ syncInfo, exerciseSync }) => {
<FormattedMessage
id="app.assignment.syncRequiredTitle"
defaultMessage="The exercise data are newer than assignment data"
values={{
exerciseUpdated: <DateTime unixts={syncInfo.updatedAt.exercise} emptyPlaceholder="??" />,
assignmentUpdated: <DateTime unixts={syncInfo.updatedAt.assignment} emptyPlaceholder="??" />,
}}
/>
</h4>
<p>
<FormattedMessage
id="app.assignment.syncRequired"
defaultMessage="Exercise was updated <strong>{exerciseUpdated}</strong>, but the assignment was last updated <strong>{assignmentUpdated}</strong>!"
defaultMessage="Exercise was updated at <strong>{exerciseUpdated}</strong>, but the assignment was synchronized with the exercise at <strong>{assignmentUpdated}</strong>!"
values={{
exerciseUpdated: <DateTime unixts={syncInfo.updatedAt.exercise} emptyPlaceholder="??" />,
assignmentUpdated: <DateTime unixts={syncInfo.updatedAt.assignment} emptyPlaceholder="??" />,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { OverlayTrigger, Popover } from 'react-bootstrap';

import DateTime from '../../../widgets/DateTime';
import { WarningIcon } from '../../../icons';
import { isUpToDate } from '../assignmentSyncHelper';

const AssignmentSyncIcon = ({ id, syncInfo, ...props }) => {
return !isUpToDate(syncInfo) ? (
<OverlayTrigger
placement="bottom"
overlay={
<Popover id={`assginmentsync-${id}`}>
<Popover.Title>
<FormattedMessage
id="app.assignment.syncRequiredTitle"
defaultMessage="The exercise data are newer than assignment data"
/>
</Popover.Title>
<Popover.Content>
<p>
<FormattedMessage
id="app.assignment.syncRequired"
defaultMessage="Exercise was updated at <strong>{exerciseUpdated}</strong>, but the assignment was synchronized with the exercise at <strong>{assignmentUpdated}</strong>!"
values={{
exerciseUpdated: <DateTime unixts={syncInfo.updatedAt.exercise} emptyPlaceholder="??" />,
assignmentUpdated: <DateTime unixts={syncInfo.updatedAt.assignment} emptyPlaceholder="??" />,
strong: contents => <strong>{contents}</strong>,
}}
/>
</p>

{!syncInfo.isSynchronizationPossible && (
<p>
<WarningIcon className="text-danger" gapRight />
<FormattedMessage
id="app.assignment.syncIsNotPossible"
defaultMessage="The exercise is not in a consistent state, synchronization is not possible at the moment."
/>
</p>
)}
</Popover.Content>
</Popover>
}>
<WarningIcon {...props} className={syncInfo.isSynchronizationPossible ? 'text-warning' : 'text-danger'} />
</OverlayTrigger>
) : null;
};

AssignmentSyncIcon.propTypes = {
id: PropTypes.string.isRequired,
syncInfo: PropTypes.object.isRequired,
};

export default AssignmentSyncIcon;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import AssignmentSyncIcon from './AssignmentSyncIcon';
export default AssignmentSyncIcon;
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@ import { Link } from 'react-router-dom';
import { FormattedMessage, injectIntl } from 'react-intl';
import { OverlayTrigger, Tooltip } from 'react-bootstrap';

import AssignmentStatusIcon from '../AssignmentStatusIcon/AssignmentStatusIcon';
import AssignmentSyncIcon from '../AssignmentSyncIcon';
import AssignmentStatusIcon from '../AssignmentStatusIcon';
import AssignmentMaxPoints from '../AssignmentMaxPoints';
import withLinks from '../../../../helpers/withLinks';
import { LocalizedExerciseName } from '../../../helpers/LocalizedNames';
import { ChatIcon, EditIcon, ResultsIcon, MaybeBonusAssignmentIcon, MaybeVisibleAssignmentIcon } from '../../../icons';
import DeleteAssignmentButtonContainer from '../../../../containers/DeleteAssignmentButtonContainer';
import Button, { TheButtonGroup } from '../../../widgets/TheButton';
import DateTime from '../../../widgets/DateTime';
import NiceCheckbox from '../../../forms/NiceCheckbox';
import EnvironmentsList from '../../../helpers/EnvironmentsList';
import ResourceRenderer from '../../../helpers/ResourceRenderer';
import { getGroupCanonicalLocalizedName } from '../../../../helpers/localizedData';
Expand All @@ -32,6 +34,7 @@ const AssignmentTableRow = ({
visibleFrom,
accepted,
permissionHints,
exerciseSynchronizationInfo,
},
runtimeEnvironments = null,
status,
Expand All @@ -43,6 +46,8 @@ const AssignmentTableRow = ({
showSecondDeadline = true,
groupsAccessor = null,
discussionOpen,
setSelected = null,
selected = false,
intl: { locale },
links: {
ASSIGNMENT_DETAIL_URI_FACTORY,
Expand All @@ -53,13 +58,21 @@ const AssignmentTableRow = ({
},
}) => (
<tr>
{setSelected && (
<td className="text-nowrap shrink-col">
<NiceCheckbox name={id} checked={selected} onChange={setSelected} />
</td>
)}

<td className="text-nowrap shrink-col">
{permissionHints.update || permissionHints.viewAssignmentSolutions ? (
<MaybeVisibleAssignmentIcon id={id} isPublic={isPublic} visibleFrom={visibleFrom} />
) : (
<AssignmentStatusIcon id={id} status={status} accepted={accepted} />
)}
<MaybeBonusAssignmentIcon gapLeft id={id} isBonus={isBonus} />

{exerciseSynchronizationInfo && <AssignmentSyncIcon id={id} syncInfo={exerciseSynchronizationInfo} gapLeft />}
</td>

{showNames && (
Expand Down Expand Up @@ -192,6 +205,7 @@ AssignmentTableRow.propTypes = {
visibleFrom: PropTypes.number,
accepted: PropTypes.bool,
permissionHints: PropTypes.object,
exerciseSynchronizationInfo: PropTypes.object,
}).isRequired,
runtimeEnvironments: PropTypes.array,
status: PropTypes.string,
Expand All @@ -201,6 +215,8 @@ AssignmentTableRow.propTypes = {
showNames: PropTypes.bool,
showGroups: PropTypes.bool,
showSecondDeadline: PropTypes.bool,
setSelected: PropTypes.func,
selected: PropTypes.bool,
groupsAccessor: PropTypes.func,
discussionOpen: PropTypes.func,
links: PropTypes.object,
Expand Down
Loading

0 comments on commit 6a635c8

Please sign in to comment.