Skip to content

Commit 4fb72ea

Browse files
author
Martin Krulis
committed
Adding accept action buttons to assignment solutions overview.
1 parent cdb9459 commit 4fb72ea

File tree

5 files changed

+72
-35
lines changed

5 files changed

+72
-35
lines changed

src/components/Assignments/SolutionsTable/SolutionsTableRow.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react';
22
import PropTypes from 'prop-types';
3-
import { FormattedMessage, FormattedNumber } from 'react-intl';
3+
import { FormattedMessage, FormattedNumber, injectIntl, intlShape } from 'react-intl';
44
import { Link } from 'react-router';
55
import { OverlayTrigger, Tooltip } from 'react-bootstrap';
66
import classnames from 'classnames';
@@ -9,6 +9,8 @@ import AssignmentStatusIcon, { getStatusDesc } from '../Assignment/AssignmentSta
99
import Points from './Points';
1010
import EnvironmentsListItem from '../../helpers/EnvironmentsList/EnvironmentsListItem';
1111
import DeleteSolutionButtonContainer from '../../../containers/DeleteSolutionButtonContainer/DeleteSolutionButtonContainer';
12+
import AcceptSolutionContainer from '../../../containers/AcceptSolutionContainer';
13+
1214
import CommentsIcon from './CommentsIcon';
1315
import { SendIcon } from '../../icons';
1416
import DateTime from '../../widgets/DateTime';
@@ -36,6 +38,7 @@ const SolutionsTableRow = ({
3638
noteMaxlen = null,
3739
compact = false,
3840
links: { SOLUTION_DETAIL_URI_FACTORY },
41+
intl: { locale },
3942
}) => {
4043
const trimmedNote = note && note.trim();
4144
const hasNote = Boolean(trimmedNote);
@@ -118,6 +121,9 @@ const SolutionsTableRow = ({
118121
<FormattedMessage id="generic.detail" defaultMessage="Detail" />
119122
</Link>
120123
)}
124+
{permissionHints && permissionHints.setAccepted && (
125+
<AcceptSolutionContainer id={id} locale={locale} shortLabel bsSize="xs" />
126+
)}
121127
{permissionHints && permissionHints.delete && <DeleteSolutionButtonContainer id={id} bsSize="xs" />}
122128
</td>
123129
</tr>
@@ -162,6 +168,7 @@ SolutionsTableRow.propTypes = {
162168
noteMaxlen: PropTypes.number,
163169
compact: PropTypes.bool.isRequired,
164170
links: PropTypes.object,
171+
intl: intlShape,
165172
};
166173

167-
export default withLinks(SolutionsTableRow);
174+
export default withLinks(injectIntl(SolutionsTableRow));

src/components/buttons/AcceptSolution/AcceptSolution.js

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,24 @@ import { FormattedMessage } from 'react-intl';
44
import Button from '../../widgets/FlatButton';
55
import Icon from '../../icons';
66

7-
const AcceptSolution = ({ accepted, acceptPending, accept, unaccept }) =>
7+
const AcceptSolution = ({ accepted, acceptPending, accept, unaccept, shortLabel = false, bsSize = undefined }) =>
88
accepted === true ? (
9-
<Button bsStyle="info" onClick={unaccept} disabled={acceptPending}>
9+
<Button bsStyle="info" bsSize={bsSize} onClick={unaccept} disabled={acceptPending}>
1010
<Icon icon="check-circle" gapRight />
11-
<FormattedMessage id="app.acceptSolution.accepted" defaultMessage="Revoke as Final" />
11+
{shortLabel ? (
12+
<FormattedMessage id="app.acceptSolution.acceptedShort" defaultMessage="Revoke" />
13+
) : (
14+
<FormattedMessage id="app.acceptSolution.accepted" defaultMessage="Revoke as Final" />
15+
)}
1216
</Button>
1317
) : (
14-
<Button bsStyle="primary" onClick={accept} disabled={acceptPending}>
18+
<Button bsStyle="primary" bsSize={bsSize} onClick={accept} disabled={acceptPending}>
1519
<Icon icon={['far', 'check-circle']} gapRight />
16-
<FormattedMessage id="app.acceptSolution.notAccepted" defaultMessage="Accept as Final" />
20+
{shortLabel ? (
21+
<FormattedMessage id="app.acceptSolution.notAcceptedShort" defaultMessage="Accept" />
22+
) : (
23+
<FormattedMessage id="app.acceptSolution.notAccepted" defaultMessage="Accept as Final" />
24+
)}
1725
</Button>
1826
);
1927

@@ -22,6 +30,8 @@ AcceptSolution.propTypes = {
2230
acceptPending: PropTypes.bool.isRequired,
2331
accept: PropTypes.func.isRequired,
2432
unaccept: PropTypes.func.isRequired,
33+
shortLabel: PropTypes.bool,
34+
bsSize: PropTypes.string,
2535
};
2636

2737
export default AcceptSolution;

src/containers/AcceptSolutionContainer/AcceptedSolutionContainer.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ import AcceptSolution from '../../components/buttons/AcceptSolution';
66
import { acceptSolution, unacceptSolution } from '../../redux/modules/solutions';
77
import { isAccepted, isAcceptPending } from '../../redux/selectors/solutions';
88

9-
const AcceptSolutionContainer = ({ accepted, acceptPending, accept, unaccept }) => {
10-
return <AcceptSolution accepted={accepted} acceptPending={acceptPending} accept={accept} unaccept={unaccept} />;
9+
const AcceptSolutionContainer = ({ accepted, acceptPending, accept, unaccept, ...props }) => {
10+
return (
11+
<AcceptSolution accepted={accepted} acceptPending={acceptPending} accept={accept} unaccept={unaccept} {...props} />
12+
);
1113
};
1214

1315
AcceptSolutionContainer.propTypes = {

src/pages/AssignmentStats/AssignmentStats.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
33
import ImmutablePropTypes from 'react-immutable-proptypes';
44
import { Row, Col } from 'react-bootstrap';
55
import { connect } from 'react-redux';
6-
import { injectIntl, FormattedMessage, FormattedNumber } from 'react-intl';
6+
import { injectIntl, FormattedMessage, FormattedNumber, intlShape } from 'react-intl';
77
import { LinkContainer } from 'react-router-bootstrap';
88
import { Link } from 'react-router';
99
import { defaultMemoize } from 'reselect';
@@ -40,6 +40,7 @@ import DateTime from '../../components/widgets/DateTime';
4040
import Points from '../../components/Assignments/SolutionsTable/Points';
4141
import EnvironmentsListItem from '../../components/helpers/EnvironmentsList/EnvironmentsListItem';
4242
import DeleteSolutionButtonContainer from '../../containers/DeleteSolutionButtonContainer/DeleteSolutionButtonContainer';
43+
import AcceptSolutionContainer from '../../containers/AcceptSolutionContainer';
4344

4445
import { safeGet, identity } from '../../helpers/common';
4546
import { createUserNameComparator } from '../../components/helpers/users';
@@ -161,6 +162,9 @@ const prepareTableColumnDescriptors = defaultMemoize((loggedUserId, assignmentId
161162
<FormattedMessage id="generic.detail" defaultMessage="Detail" />
162163
</Link>
163164
)}
165+
{solution.permissionHints && solution.permissionHints.setAccepted && (
166+
<AcceptSolutionContainer id={solution.id} locale={locale} shortLabel bsSize="xs" />
167+
)}
164168
{solution.permissionHints && solution.permissionHints.delete && (
165169
<DeleteSolutionButtonContainer id={solution.id} bsSize="xs" />
166170
)}
@@ -442,7 +446,7 @@ AssignmentStats.propTypes = {
442446
loadAsync: PropTypes.func.isRequired,
443447
downloadBestSolutionsArchive: PropTypes.func.isRequired,
444448
fetchManyStatus: PropTypes.string,
445-
intl: PropTypes.shape({ locale: PropTypes.string.isRequired }).isRequired,
449+
intl: intlShape,
446450
links: PropTypes.object.isRequired,
447451
};
448452

src/redux/modules/solutions.js

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -164,37 +164,51 @@ const reducer = handleActions(
164164
[additionalActionTypes.ACCEPT_REJECTED]: (state, { meta: { id } }) =>
165165
state.setIn(['resources', id, 'data', 'accepted-pending'], false),
166166

167-
[additionalActionTypes.ACCEPT_FULFILLED]: (state, { meta: { id } }) =>
168-
state.update('resources', resources =>
169-
resources.map((item, itemId) =>
170-
item.get('data') !== null
171-
? item.update('data', data =>
172-
itemId === id
173-
? data.set('accepted', true).set('accepted-pending', false)
174-
: data.set('accepted', false).set('accepted-pending', false)
175-
)
176-
: item
177-
)
178-
),
167+
[additionalActionTypes.ACCEPT_FULFILLED]: (state, { meta: { id } }) => {
168+
const assignmentId = state.getIn(['resources', id, 'data', 'exerciseAssignmentId']);
169+
const userId = state.getIn(['resources', id, 'data', 'solution', 'userId']);
170+
return !assignmentId || !userId
171+
? state
172+
: state
173+
// Accepted solution needs to be updated
174+
.updateIn(['resources', id, 'data'], data =>
175+
data
176+
.set('accepted', true)
177+
.set('isBestSolution', true) // accepted also becomes best solution
178+
.set('accepted-pending', false)
179+
)
180+
// All other solutions from the same assignment by the same author needs to be updated
181+
.update('resources', resources =>
182+
resources.map((item, itemId) => {
183+
const aId = item.getIn(['data', 'exerciseAssignmentId']);
184+
const uId = item.getIn(['data', 'solution', 'userId']);
185+
return itemId === id || aId !== assignmentId || uId !== userId
186+
? item // no modification (either it is accepted solution, or it is solution from another assignment/by another user)
187+
: item.update('data', data => data.set('accepted', false).set('isBestSolution', false)); // no other solution can be accepted nor best
188+
})
189+
);
190+
},
179191

180192
[additionalActionTypes.UNACCEPT_PENDING]: (state, { meta: { id } }) =>
181193
state.setIn(['resources', id, 'data', 'accepted-pending'], true),
182194

183195
[additionalActionTypes.UNACCEPT_REJECTED]: (state, { meta: { id } }) =>
184196
state.setIn(['resources', id, 'data', 'accepted-pending'], false),
185197

186-
[additionalActionTypes.UNACCEPT_FULFILLED]: (state, { meta: { id } }) =>
187-
state.update('resources', resources =>
188-
resources.map((item, itemId) =>
189-
item.get('data') !== null
190-
? item.update('data', data =>
191-
itemId === id
192-
? data.set('accepted', false).set('accepted-pending', false)
193-
: data.set('accepted', true).set('accepted-pending', false)
194-
)
195-
: item
196-
)
197-
),
198+
[additionalActionTypes.UNACCEPT_FULFILLED]: (state, { payload: { assignments }, meta: { id } }) => {
199+
const assignmentId = state.getIn(['resources', id, 'data', 'exerciseAssignmentId']);
200+
const assignmentStats = assignments.find(a => a.id === assignmentId);
201+
const newBestSolutionId = assignmentStats && assignmentStats.bestSolutionId;
202+
state = state.updateIn(['resources', id, 'data'], data =>
203+
data
204+
.set('accepted', false)
205+
.set('isBestSolution', false)
206+
.set('accepted-pending', false)
207+
);
208+
return newBestSolutionId && state.hasIn(['resources', newBestSolutionId, 'data', 'isBestSolution'])
209+
? state.setIn(['resources', newBestSolutionId, 'data', 'isBestSolution'], true)
210+
: state;
211+
},
198212

199213
[submissionEvaluationActionTypes.REMOVE_FULFILLED]: (state, { meta: { solutionId, id: evaluationId } }) => {
200214
if (!solutionId || !evaluationId) {

0 commit comments

Comments
 (0)