Skip to content

Commit

Permalink
Merge pull request #183 from ReCodEx/student-stats
Browse files Browse the repository at this point in the history
Student stats
  • Loading branch information
SemaiCZE committed Feb 24, 2018
2 parents fccb2bc + b3cfd0b commit 1302afc
Show file tree
Hide file tree
Showing 17 changed files with 113 additions and 122 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import Icon from 'react-fontawesome';
import { Table, Label } from 'react-bootstrap';
import {
Expand All @@ -10,7 +9,6 @@ import {
FormattedRelative
} from 'react-intl';
import classnames from 'classnames';
import ResourceRenderer from '../../../helpers/ResourceRenderer';
import { SuccessIcon, MaybeSucceededIcon } from '../../../icons';
import Box from '../../../widgets/Box';

Expand All @@ -27,8 +25,7 @@ const AssignmentDetails = ({
isBonus,
runtimeEnvironments,
canSubmit,
pointsPercentualThreshold,
alreadySubmitted
pointsPercentualThreshold
}) =>
<Box
title={
Expand Down Expand Up @@ -136,7 +133,7 @@ const AssignmentDetails = ({
/>
</td>
<td>
{alreadySubmitted}
{canSubmit.submittedCount}
</td>
</tr>
<tr>
Expand All @@ -150,9 +147,7 @@ const AssignmentDetails = ({
/>
</td>
<td>
<ResourceRenderer resource={canSubmit}>
{canSubmit => <MaybeSucceededIcon success={canSubmit} />}
</ResourceRenderer>
<MaybeSucceededIcon success={canSubmit.canSubmit} />
</td>
</tr>
{isBonus &&
Expand Down Expand Up @@ -218,9 +213,8 @@ AssignmentDetails.propTypes = {
isAfterSecondDeadline: PropTypes.bool.isRequired,
isBonus: PropTypes.bool,
runtimeEnvironments: PropTypes.array,
canSubmit: ImmutablePropTypes.map,
pointsPercentualThreshold: PropTypes.number,
alreadySubmitted: PropTypes.number.isRequired
canSubmit: PropTypes.object,
pointsPercentualThreshold: PropTypes.number
};

export default AssignmentDetails;
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,13 @@ const AssignmentsTable = ({
item={assignment}
userId={userId}
showGroup={showGroup}
status={statuses[assignment.id]}
status={
Array.isArray(statuses)
? statuses.find(
assignStatus => assignStatus.id === assignment.id
).status
: ''
}
locale={locale}
bestSubmission={bestSubmissions[assignment.id]}
/>
Expand Down
25 changes: 7 additions & 18 deletions src/components/Groups/ResultsTable/ResultsTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,15 @@ import { Link } from 'react-router';
import { FormattedMessage } from 'react-intl';

import ResultsTableRow from './ResultsTableRow';
import LoadingResultsTableRow from './LoadingResultsTableRow';
import NoResultsAvailableRow from './NoResultsAvailableRow';
import withLinks from '../../../helpers/withLinks';
import ResourceRenderer from '../../helpers/ResourceRenderer';
import { LocalizedExerciseName } from '../../helpers/LocalizedNames';
import styles from './ResultsTable.less';

const ResultsTable = ({
assignments = List(),
users = [],
submissions,
stats,
links: { SUPERVISOR_STATS_URI_FACTORY }
}) => {
const assignmentsArray = assignments.sort(
Expand Down Expand Up @@ -53,21 +51,12 @@ const ResultsTable = ({
{users.length !== 0 &&
assignments.length !== 0 &&
users.map(user =>
<ResourceRenderer
<ResultsTableRow
key={user.id}
resource={Object.keys(submissions[user.id]).map(
key => submissions[user.id][key]
)}
loading={<LoadingResultsTableRow />}
>
{(...userSubmissions) =>
<ResultsTableRow
key={user.id}
userId={user.id}
assignmentsIds={assignmentsIds}
submissions={userSubmissions}
/>}
</ResourceRenderer>
userId={user.id}
assignmentsIds={assignmentsIds}
userStats={stats.find(stat => stat.userId === user.id)}
/>
)}
</tbody>
</Table>
Expand All @@ -77,7 +66,7 @@ const ResultsTable = ({
ResultsTable.propTypes = {
assignments: PropTypes.array.isRequired,
users: PropTypes.array.isRequired,
submissions: PropTypes.object.isRequired,
stats: PropTypes.array.isRequired,
links: PropTypes.object
};

Expand Down
42 changes: 19 additions & 23 deletions src/components/Groups/ResultsTable/ResultsTableRow.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,41 @@ import React from 'react';
import PropTypes from 'prop-types';
import UsersNameContainer from '../../../containers/UsersNameContainer';

const ResultsTableRow = ({ userId, assignmentsIds, submissions }) => {
var totalPoints = 0;
const ResultsTableRow = ({ userId, assignmentsIds, userStats }) => {
return (
<tr>
<td>
<UsersNameContainer userId={userId} />
</td>
{assignmentsIds.map(assignmentId => {
const submission = submissions
.filter(s => s !== null)
.filter(s => s.exerciseAssignmentId === assignmentId)[0];
const points =
submission &&
submission !== null &&
submission.lastSubmission.evaluation &&
Number.isInteger(submission.lastSubmission.evaluation.points)
? submission.lastSubmission.evaluation.points
: '-';
const bonusPoints =
submission && submission !== null ? submission.bonusPoints : 0;
totalPoints += points !== '-' ? points : 0;
totalPoints += bonusPoints;
const assignmentData =
userStats && userStats.assignments
? userStats.assignments.find(
assignment => assignment.id === assignmentId
)
: {};
return (
<td key={assignmentId}>
{points}
{bonusPoints > 0 &&
{assignmentData.points &&
Number.isInteger(assignmentData.points.gained)
? assignmentData.points.gained
: '-'}
{assignmentData.points &&
assignmentData.points.bonus > 0 &&
<span style={{ color: 'green' }}>
+{bonusPoints}
+{assignmentData.points.bonus}
</span>}
{bonusPoints < 0 &&
{assignmentData.points &&
assignmentData.points.bonus < 0 &&
<span style={{ color: 'red' }}>
{bonusPoints}
{assignmentData.points.bonus}
</span>}
</td>
);
})}
<td style={{ textAlign: 'right' }}>
<b>
{totalPoints}
{userStats && userStats.points ? userStats.points.gained : '-'}/{userStats && userStats.points ? userStats.points.total : '-'}
</b>
</td>
</tr>
Expand All @@ -50,7 +46,7 @@ const ResultsTableRow = ({ userId, assignmentsIds, submissions }) => {
ResultsTableRow.propTypes = {
userId: PropTypes.string.isRequired,
assignmentsIds: PropTypes.array.isRequired,
submissions: PropTypes.array.isRequired
userStats: PropTypes.object
};

export default ResultsTableRow;
28 changes: 28 additions & 0 deletions src/components/Groups/StudentsView/StudentsView.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@ import AssignmentsTable from '../../Assignments/Assignment/AssignmentsTable';
import StudentsListContainer from '../../../containers/StudentsListContainer';
import LeaveJoinGroupButtonContainer from '../../../containers/LeaveJoinGroupButtonContainer';
import { getLocalizedName } from '../../../helpers/getLocalizedData';
import ResourceRenderer from '../../helpers/ResourceRenderer';
import ResultsTableContainer from '../../../containers/ResultsTableContainer';

const StudentsView = ({
group,
statuses = [],
assignments,
bestSubmissions,
isAdmin = false,
users,
intl: { locale }
}) =>
<div>
Expand Down Expand Up @@ -78,12 +81,37 @@ const StudentsView = ({
</Box>
</Col>
</Row>
<Row>
<Col xs={12}>
<Box
title={
<FormattedMessage
id="app.group.spervisorsView.resultsTable"
defaultMessage="Results"
/>
}
isOpen
unlimitedHeight
noPadding
>
<ResourceRenderer resource={assignments}>
{(...assignments) =>
<ResultsTableContainer
groupId={group.id}
users={users}
assignments={assignments}
/>}
</ResourceRenderer>
</Box>
</Col>
</Row>
</div>;

StudentsView.propTypes = {
group: PropTypes.object.isRequired,
assignments: ImmutablePropTypes.list.isRequired,
statuses: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
users: PropTypes.array.isRequired,
isAdmin: PropTypes.bool,
bestSubmissions: PropTypes.object,
intl: PropTypes.shape({ locale: PropTypes.string.isRequired }).isRequired
Expand Down
21 changes: 0 additions & 21 deletions src/components/Groups/SupervisorsView/SupervisorsView.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { LinkContainer } from 'react-router-bootstrap';

import DeleteExerciseButtonContainer from '../../../containers/DeleteExerciseButtonContainer';
import ExercisesSimpleList from '../../../components/Exercises/ExercisesSimpleList';
import ResultsTableContainer from '../../../containers/ResultsTableContainer';
import Button from '../../../components/widgets/FlatButton';
import { AddIcon, EditIcon } from '../../../components/icons';
import AssignExerciseButton from '../../../components/buttons/AssignExerciseButton';
Expand Down Expand Up @@ -50,26 +49,6 @@ const SupervisorsView = ({
</h3>
</Col>
</Row>
<Row>
<Col xs={12}>
<Box
title={
<FormattedMessage
id="app.group.spervisorsView.resultsTable"
defaultMessage="Results"
/>
}
isOpen
unlimitedHeight
noPadding
>
<ResourceRenderer resource={publicAssignments}>
{(...assignments) =>
<ResultsTableContainer users={users} assignments={assignments} />}
</ResourceRenderer>
</Box>
</Col>
</Row>
<Row>
<Col lg={6}>
<Box
Expand Down
4 changes: 0 additions & 4 deletions src/components/Users/StudentsListItem/StudentsListItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,6 @@ StudentsListItem.propTypes = {
fullName: PropTypes.string.isRequired,
avatarUrl: PropTypes.string.isRequired,
stats: PropTypes.shape({
assignments: PropTypes.shape({
total: PropTypes.number.isRequired,
completed: PropTypes.number.isRequired
}),
points: PropTypes.shape({
total: PropTypes.number.isRequired,
gained: PropTypes.number.isRequired
Expand Down
6 changes: 1 addition & 5 deletions src/components/Users/UsersStats/UsersStats.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const UsersStats = ({
id,
name,
localizedTexts,
stats: { assignments, points, hasLimit, passesLimit },
stats: { points, hasLimit, passesLimit },
intl: { locale }
}) => {
const localizedName = getLocalizedName({ name, localizedTexts }, locale);
Expand Down Expand Up @@ -42,10 +42,6 @@ UsersStats.propTypes = {
name: PropTypes.string.isRequired,
localizedTexts: PropTypes.array.isRequired,
stats: PropTypes.shape({
assignments: PropTypes.shape({
total: PropTypes.number.isRequired,
completed: PropTypes.number.isRequired
}),
points: PropTypes.shape({
total: PropTypes.number.isRequired,
gained: PropTypes.number.isRequired
Expand Down
44 changes: 21 additions & 23 deletions src/containers/ResultsTableContainer/ResultsTableContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,55 +2,53 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';

import ResourceRenderer from '../../components/helpers/ResourceRenderer';
import ResultsTable from '../../components/Groups/ResultsTable';
import { fetchBestSubmissions } from '../../redux/modules/groupResults';
import { getBestSubmissionsAssoc } from '../../redux/selectors/groupResults';
import { fetchGroupsStats } from '../../redux/modules/stats';
import { createGroupsStatsSelector } from '../../redux/selectors/stats';

class ResultsTableContainer extends Component {
componentWillMount() {
this.props.loadAsync();
}

componentWillReceiveProps(newProps) {
if (
this.props.users.length !== newProps.users.length ||
this.props.users.some((user, i) => user.id !== newProps.users[i].id)
) {
if (this.props.groupId !== newProps.groupId) {
this.props.loadAsync();
}
}

static loadAsync = ({ users, assignments }, dispatch) => {
assignments.map(assignment =>
dispatch(fetchBestSubmissions(assignment.id))
);
};
static loadAsync = ({ groupId }, dispatch) =>
dispatch(fetchGroupsStats(groupId));

render() {
const { users, assignments, submissions } = this.props;
const { users, assignments, stats } = this.props;
return (
<ResultsTable
users={users}
assignments={assignments}
submissions={submissions}
/>
<ResourceRenderer resource={stats}>
{groupStats =>
<ResultsTable
users={users}
assignments={assignments}
stats={groupStats}
/>}
</ResourceRenderer>
);
}
}

ResultsTableContainer.propTypes = {
users: PropTypes.array.isRequired,
assignments: PropTypes.array.isRequired,
submissions: PropTypes.object,
groupId: PropTypes.string.isRequired,
stats: PropTypes.object,
loadAsync: PropTypes.func
};

export default connect(
(state, { users, assignments }) => ({
submissions: getBestSubmissionsAssoc(assignments, users)(state)
(state, { groupId }) => ({
stats: createGroupsStatsSelector(groupId)(state)
}),
(dispatch, { users, assignments }) => ({
loadAsync: () =>
ResultsTableContainer.loadAsync({ users, assignments }, dispatch)
(dispatch, { groupId }) => ({
loadAsync: () => ResultsTableContainer.loadAsync({ groupId }, dispatch)
})
)(ResultsTableContainer);
Loading

0 comments on commit 1302afc

Please sign in to comment.