Compare commits

...

30 Commits

Author SHA1 Message Date
Simon Chen
0b9fa36fb7 Merge pull request #28 from edx/schen/styles
fix(Styles): Fix the gradebook styles to increase its width
2018-11-30 10:51:03 -05:00
Simon Chen
7bd0c49c14 fix(Styles): Fix the gradebook styles to increase its width 2018-11-30 10:30:59 -05:00
Alex Dusenbery
44f91bb453 Add documentation on platform configuration for local development. 2018-11-30 10:16:08 -05:00
Jansen Kantor
d8f229838f Merge pull request #25 from edx/pagination
fix(pagination) use paragon buttons, hide rather than disable
2018-11-29 16:22:38 -05:00
jkantor
7b5a095898 fix(pagination) use paragon buttons, hide rather than disable 2018-11-29 15:35:13 -05:00
Jansen Kantor
ff7937c2d7 Merge pull request #24 from edx/pagination
feat(pagination) added next and previous buttons to grades page
2018-11-29 14:34:56 -05:00
jkantor
d057497105 feat(pagination) added next and previous buttons to grades page 2018-11-29 13:11:11 -05:00
Richard I Reilly
e1402b0d4f Merge pull request #23 from edx/rir/update-auth
Update auth to the latest version of edx/frontend-auth
2018-11-28 15:41:24 -05:00
Rick Reilly
3ea337e3f8 Update auth to the latest version of edx/frontend-auth 2018-11-28 15:31:19 -05:00
Alex Dusenbery
9c2c16e378 Remove boilerplate README strings, add note about installing into LMS. 2018-11-28 10:30:12 -05:00
Simon Chen
7e9ef204a7 Merge pull request #22 from edx/schen/fix_search
fix(functionality): Fix search so results are rendered with proper heading
2018-11-27 15:02:07 -05:00
Simon Chen
f779e7fd35 fix(functionality): Fix search so results are rendered with proper heading 2018-11-27 14:47:57 -05:00
Richard I Reilly
a5a62922b5 Merge pull request #13 from edx/schen/clean
Clean up not needed example code
2018-11-26 14:41:00 -05:00
Richard I Reilly
2c6aa96f8e Merge pull request #20 from edx/rir/fix-sorting
Fix column sorting in the grade table
2018-11-26 14:29:48 -05:00
Rick Reilly
30e866128f Fix column sorting in the grade table 2018-11-26 14:25:11 -05:00
Simon Chen
ec81eb47d9 Merge pull request #18 from edx/aed/edit-modal
feat(editing): display a status alert after grades have updated.
2018-11-14 21:03:12 -05:00
Alex Dusenbery
cd2a5ae903 feat(editing): display a status alert after grades have updated. 2018-11-14 20:36:41 -05:00
Richard I Reilly
7a02330e9e Merge pull request #17 from edx/rir/frontend-auth-1-1
Return the frontend-auth npm package to 1.1.0
2018-11-14 16:45:51 -05:00
Rick Reilly
a929194a29 Return the frontend-auth npm package to 1.1.0 2018-11-14 16:16:47 -05:00
Simon Chen
febf4d99c6 Merge pull request #14 from edx/schen/add_links
fix(functionality): Add links and subtitles to the UI
2018-11-14 15:52:47 -05:00
Simon Chen
83ed8ab875 Merge pull request #16 from edx/schen/default-wording
fix(ui): update the default wording of the group filters
2018-11-14 15:51:10 -05:00
Simon Chen
6563f54590 fix(functionality): Add links and subtitles to the UI so it helps user navigate 2018-11-14 15:43:48 -05:00
Simon Chen
e1fe31dc94 fix(ui): update the default wording of the group filters 2018-11-14 15:39:56 -05:00
Richard I Reilly
8754263584 Merge pull request #15 from edx/rir/fix-category-filter
Rir/fix category filter
2018-11-14 15:35:34 -05:00
Rick Reilly
c660bd8d15 Fix the category filter 2018-11-14 15:16:40 -05:00
Richard I Reilly
df32123f34 Merge pull request #8 from edx/rir/fix-percent-absolute-filter
Rir/fix percent absolute filter
2018-11-14 14:50:40 -05:00
Rick Reilly
e81db01be2 Fix percent vs absolute radio buttons 2018-11-14 14:32:34 -05:00
Simon Chen
05c3468d93 Merge pull request #12 from edx/schen/fix_download_link
fix(functionality): Update the download report link URL
2018-11-14 14:18:39 -05:00
Simon Chen
1687a6ca1a Clean up not needed example code 2018-11-14 14:02:53 -05:00
Simon Chen
1a88343be9 fix(functionality): Update the download report link URL so it goes to the right place 2018-11-14 13:55:12 -05:00
23 changed files with 517 additions and 1626 deletions

View File

@@ -29,6 +29,26 @@ If you don't, you can see the log messages for the docker container by executing
Note that `make up-detached` executes the `npm run start` script which will hot-reload JavaScript and Sass files changes, so you should (:crossed_fingers:) not need to do anything (other than wait) when making changes.
## Configuring for local use in edx-platform
Assuming you've got the UI running at `http://localhost:1991`, you can configure the LMS in edx-platform
to point to your local gradebook from the instructor dashboard by putting this settings in `lms/env/private.py`:
```
WRITABLE_GRADEBOOK_URL = 'http://localhost:1991'
```
There are also several edx-platform waffle and feature flags you'll have to enable from the Django admin:
1. Grades > Persistent grades enabled flag. Add this flag if it doesn't exist,
check the ``enabled`` and ``enabled for all courses`` boxes.
2. Waffle > Switches. Add the ``grades.assume_zero_grade_if_absent`` switch and make it active.
3. Waffle_utils > Waffle flag course overrides. You want to activate this flag for any course
in which you'd like to enable the gradebook. Add a course override flag using a course id and the flag name
``grades.writable_gradebook``. Make sure to check the ``enabled`` box. Alternatively, you could add this as a
regular waffle flag to enable the gradebook for all courses.
## Directory Structure
* `config`
@@ -46,61 +66,6 @@ Note that `make up-detached` executes the `npm run start` script which will hot-
* `constants`
* `reducers`
* Directory for `Redux` reducers
* [`.babelrc`](#babelrc)
* [`.dockerignore`](#dockerignore)
* [`.eslintignore`](#eslintignore)
* [`.eslintrc.js`](#eslintrcjs)
* `.gitignore`
* [`npmignore`](#npmignore)
* [`.travis.yml`](#travisyml)
* `docker-compose.yml`
* `Dockerfile`
* `LICENSE`
* `Makefile`
* `package-lock.json`
* [`package.json`](#packagejson)
### `.babelrc`
We use [`Babel`](https://babeljs.io/) to transpile `ES2015+` JavaScript to `ES5` JavaScript. `ES5` JavaScript has [greater browser compatibility](http://kangax.github.io/compat-table/es5/) than [`ES2015+`](http://kangax.github.io/compat-table/es6/).
The `.babelrc` file is used to specify a particular configuration - for example, we use the [`babel-preset-react`](https://babeljs.io/docs/plugins/preset-react/), which, among other things, allows `babel` to parse `JSX`.
### `.dockerignore`
The important thing to remember is to add the `node_modules` directory to `.dockerignore` - for more information [see the Docker documentation](https://docs.docker.com/engine/reference/builder/#dockerignore-file).
### `.eslintignore`
We use [`eslint`](https://eslint.org/) for our `JavaScript` linting needs. The `.eslintignore` file is used to [specify files or directories to, well, ignore](https://eslint.org/docs/user-guide/configuring#ignoring-files-and-directories).
While `eslint` automatically ignores `node_modules`, we like to add it to the `.eslintignore` just for the added explicitness. In addition, you probably want to add the directory for your compiled files (in our case, `./dist`) and your coverage directory (in our case, `./coverage`).
### `.eslintrc`
This is where the actual `eslint` configuration is specified. All `edX` JavaScript projects should extend either the [`eslint-config-edx`](https://github.com/edx/eslint-config-edx/blob/master/packages/eslint-config-edx/README.md) or [`eslint-config-edx-es5`](https://github.com/edx/eslint-config-edx/blob/master/packages/eslint-config-edx-es5/README.md) configurations (for `ES2015+` and `ES5` JavaScript, respectively). Both configurations can be found in [the `eslint-config-edx` repository](https://github.com/edx/eslint-config-edx).
### `.npmignore`
We are not currently publishing this package to [`npm`](https://www.npmjs.com/). If we did, we would want to exclude certain files from getting uploaded to `npm` (like our coverage files, for example). For more information, see [the `npm` documentation](https://docs.npmjs.com/misc/developers#keeping-files-out-of-your-package).
### `.travis.yml`
We use [`Travis CI`](https://travis-ci.org/) to build (and deploy) our application. The `.travis.yml` file specifies the configuration for `Travis` builds. For more information, see [the `Travis` documentation](https://docs.travis-ci.com/user/customizing-the-build/).
### `package.json`
Arguably, one of the **most important files in an `npm`-based application**, the `package.json` file specifies everything from the `name` of the application, were it to be published to `npm`, to it's `dependencies`.
For more information, see [the `npm` documentation](https://docs.npmjs.com/files/package.json).
## Helpful Applications
### [`Greenkeeper`](https://greenkeeper.io/)
[`Greenkeeper`](https://greenkeeper.io/) is basically a `GitHub` application that handles `npm` dependencies. It will automatically open PRs with `package.json` updates when new versions of your `npm` dependencies get published. There are ways to also automatically keep the `package-lock.json` in-line, in the same PR, using [`greenkeeper-lockfile`].
For more information, see [the `Greenkeeper` documentation](https://greenkeeper.io/docs.html#what-greenkeeper-does).
## Authentication with backend API services

View File

@@ -79,6 +79,7 @@ module.exports = Merge.smart(commonConfig, {
LMS_BASE_URL: 'http://localhost:18000',
LOGIN_URL: 'http://localhost:18000/login',
LOGOUT_URL: 'http://localhost:18000/login',
CSRF_TOKEN_API_PATH: '/csrf/api/v1/token',
REFRESH_ACCESS_TOKEN_ENDPOINT: 'http://localhost:18000/login',
DATA_API_BASE_URL: 'http://localhost:8000',
// LMS_CLIENT_ID should match the lms DOT client application id your LMS container

View File

@@ -98,6 +98,7 @@ module.exports = Merge.smart(commonConfig, {
LMS_BASE_URL: null,
LOGIN_URL: null,
LOGOUT_URL: null,
CSRF_TOKEN_API_PATH: null,
REFRESH_ACCESS_TOKEN_ENDPOINT: null,
DATA_API_BASE_URL: null,
SEGMENT_KEY: null,

73
package-lock.json generated
View File

@@ -2965,18 +2965,20 @@
}
},
"@edx/frontend-auth": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@edx/frontend-auth/-/frontend-auth-1.1.0.tgz",
"integrity": "sha512-Pl6CUgwRPHcKx3REX1CXJIGl/WqSCg20IPGNUbpsyQ4ZuIaOi/bxI6dYfGhmOWt8LPLeBiEmGerofEWLNydZ9w==",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@edx/frontend-auth/-/frontend-auth-1.2.1.tgz",
"integrity": "sha512-NEc+rAJq5HJ9UACPirezpwTp5yhX9G2AQDPfk1hW4ceEF7BFRMIfzb+nJqmTJkEX/37yRVYUfQFLW+z2j1ZRcw==",
"requires": {
"axios": "^0.18.0",
"jwt-decode": "^2.2.0",
"prop-types": "^15.5.10",
"pubsub-js": "^1.7.0",
"react": "^16.4.2",
"react-redux": "^5.0.7",
"react-router-dom": "^4.3.1",
"redux": "^4.0.0",
"universal-cookie": "^3.0.4"
"universal-cookie": "^3.0.4",
"url-parse": "^1.4.3"
},
"dependencies": {
"invariant": {
@@ -2987,6 +2989,11 @@
"loose-envify": "^1.0.0"
}
},
"querystringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.0.tgz",
"integrity": "sha512-sluvZZ1YiTLD5jsqZcDmFyV2EwToyXZBfpoVOmktMmW+VEnhgakFHnasVph65fOjGPTWN0Nw3+XQaSeMayr0kg=="
},
"react": {
"version": "16.6.3",
"resolved": "https://registry.npmjs.org/react/-/react-16.6.3.tgz",
@@ -3055,13 +3062,13 @@
}
}
},
"scheduler": {
"version": "0.11.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.11.2.tgz",
"integrity": "sha512-+WCP3s3wOaW4S7C1tl3TEXp4l9lJn0ZK8G3W3WKRWmw77Z2cIFUW2MiNTMHn5sCjxN+t7N43HAOOgMjyAg5hlg==",
"url-parse": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.4.tgz",
"integrity": "sha512-/92DTTorg4JjktLNLe6GPS2/RvAd/RGr6LuktmWSMLEOa6rjnlrFXNgSbSmkNvCoL2T028A0a1JaJLzRMlFoHg==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
"querystringify": "^2.0.0",
"requires-port": "^1.0.0"
}
},
"warning": {
@@ -3075,9 +3082,9 @@
}
},
"@edx/paragon": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/@edx/paragon/-/paragon-3.7.0.tgz",
"integrity": "sha512-BFzPA03CjzSMEwz4utSu5Nzh9tz5kShrWHuBNZvTtwD529ObYK52C7occ9Eid2jVHtAkbwkBNQdLzz6KscCniw==",
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/@edx/paragon/-/paragon-3.7.2.tgz",
"integrity": "sha512-M1l/0yVc2291jqyVpOHZkaq3Hz3A2ua87P7WrxM4vk6I8HAqNJyWRasa8rPYEGJ6hz0wAhA2Eemuq7YlMHaBVA==",
"requires": {
"@edx/edx-bootstrap": "^1.0.0",
"@sambego/storybook-styles": "^1.0.0",
@@ -3112,14 +3119,14 @@
"integrity": "sha512-gulJE5dGFo6Q61V/whS6VM4WIyrlydXfCgkE+Gxe5hjrJ8rXLLZlALq7zq2RPhOc45PSwQpJkrTnc2KgD6cvmA=="
},
"react": {
"version": "16.6.1",
"resolved": "https://registry.npmjs.org/react/-/react-16.6.1.tgz",
"integrity": "sha512-OtawJThYlvRgm9BXK+xTL7BIlDx8vv21j+fbQDjRRUyok6y7NyjlweGorielTahLZHYIdKUoK2Dp9ByVWuMqxw==",
"version": "16.6.3",
"resolved": "https://registry.npmjs.org/react/-/react-16.6.3.tgz",
"integrity": "sha512-zCvmH2vbEolgKxtqXL2wmGCUxUyNheYn/C+PD1YAjfxHC54+MhdruyhO7QieQrYsYeTxrn93PM2y0jRH1zEExw==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"prop-types": "^15.6.2",
"scheduler": "^0.11.0"
"scheduler": "^0.11.2"
},
"dependencies": {
"prop-types": {
@@ -3533,9 +3540,9 @@
}
},
"@types/cookie": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.1.tgz",
"integrity": "sha512-64Uv+8bTRVZHlbB8eXQgMP9HguxPgnOOIYrQpwHWrtLDrtcG/lILKhUl7bV65NSOIJ9dXGYD7skQFXzhL8tk1A=="
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.2.tgz",
"integrity": "sha512-aHQA072E10/8iUQsPH7mQU/KUyQBZAGzTVRCUvnSz8mSvbrYsP4xEO2RSA0Pjltolzi0j8+8ixrm//Hr4umPzw=="
},
"@types/node": {
"version": "9.4.7",
@@ -9162,9 +9169,9 @@
}
},
"follow-redirects": {
"version": "1.5.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.9.tgz",
"integrity": "sha512-Bh65EZI/RU8nx0wbYF9shkFZlqLP+6WT/5FnA3cE/djNSuKNHJEinGGZgu/cQEkeeb2GdFOgenAmn8qaqYke2w==",
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
"integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
"requires": {
"debug": "=3.1.0"
},
@@ -19165,6 +19172,11 @@
"randombytes": "^2.0.1"
}
},
"pubsub-js": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/pubsub-js/-/pubsub-js-1.7.0.tgz",
"integrity": "sha512-Pb68P9qFZxnvDipHMuj9oT1FoIgBcXJ9C9eWdHCLZAnulaUoJ3+Y87RhGMYilWpun6DMWVmvK70T4RP4drZMSA=="
},
"pump": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
@@ -20102,8 +20114,7 @@
"requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
"dev": true
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
},
"resolve": {
"version": "1.5.0",
@@ -20506,9 +20517,9 @@
"dev": true
},
"scheduler": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.11.0.tgz",
"integrity": "sha512-MAYbBfmiEHxF0W+c4CxMpEqMYK+rYF584VP/qMKSiHM6lTkBKKYOJaDiSILpJHla6hBOsVd6GucPL46o2Uq3sg==",
"version": "0.11.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.11.2.tgz",
"integrity": "sha512-+WCP3s3wOaW4S7C1tl3TEXp4l9lJn0ZK8G3W3WKRWmw77Z2cIFUW2MiNTMHn5sCjxN+t7N43HAOOgMjyAg5hlg==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
@@ -22987,9 +22998,9 @@
}
},
"universal-cookie": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-3.0.4.tgz",
"integrity": "sha512-3rhx6RAIuRmCWJttnbgzMrp2TbHhUmgQ2GrpY/US03Siv5T28iXr2qYw1m3YqmluBxEyrvZaloVemkLSId+Oyg==",
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-3.0.6.tgz",
"integrity": "sha512-VxVnwj1bWVLuYKAbaeQ6PL4NlIEWB6r4PUjwKp76nFnrLyqQtnOKAHe9dOjESpcJ4gPoc/Zkxb/6ZK+FMuEioA==",
"requires": {
"@types/cookie": "^0.3.1",
"@types/object-assign": "^4.0.30",

View File

@@ -25,8 +25,8 @@
},
"dependencies": {
"@edx/edx-bootstrap": "^0.4.3",
"@edx/frontend-auth": "1.1.0",
"@edx/paragon": "^3.7.0",
"@edx/frontend-auth": "^1.2.1",
"@edx/paragon": "^3.7.2",
"babel-polyfill": "^6.26.0",
"classnames": "^2.2.5",
"email-prop-type": "^1.1.5",

View File

@@ -1,3 +1,18 @@
.gradebook-container{
width: 500px;
@media only screen and (min-width: 640px) {
width: 630px;
}
@media only screen and (min-width: 992px) {
width: 900px;
}
@media only screen and (min-width: 1200px) {
width: 1024px;
}
}
.back-link{
float:right;
}
.student-filters{
display: flex;
.label{
@@ -29,10 +44,6 @@
display: block;
background-color: #fff;
}
.table tr td:not(:first-child) {
//not real sylz. plz kill before prod
min-width: 250px;
}
.table tr td:nth-child(2) {
box-sizing: content-box;
padding-left: 170px;

View File

@@ -1,18 +1,13 @@
import React from 'react';
import PropTypes from 'prop-types';
import emailPropType from 'email-prop-type';
import { Button, Modal, SearchField, Table, InputSelect } from '@edx/paragon';
import { Button, InputSelect, Modal, SearchField, StatusAlert, Table } from '@edx/paragon';
import queryString from 'query-string';
import { configuration } from '../../config';
export default class Gradebook extends React.Component {
constructor(props) {
super(props);
this.state = {
grades: [], // this.mapUserEntriesPercent(this.props.grades).sort(this.sortAlphaDesc),
headings: [], // this.mapHeadings(this.props.grades[0]),
filterValue: '',
modalContent: (<h1>Hello, World!</h1>),
modalOpen: false,
modalModel: [{}],
updateVal: 0,
@@ -32,134 +27,6 @@ export default class Gradebook extends React.Component {
this.props.getCohorts(this.props.match.params.courseId);
}
sortAlphaDesc = (gradeRowA, gradeRowB) => {
const a = gradeRowA.username.toUpperCase();
const b = gradeRowB.username.toUpperCase();
if (a < b) {
return -1;
}
if (a > b) {
return 1;
}
return 0;
};
sortAlphaAsc = (gradeRowA, gradeRowB) => {
const a = gradeRowA.username.toUpperCase();
const b = gradeRowB.username.toUpperCase();
if (a < b) {
return 1;
}
if (a > b) {
return -1;
}
return 0;
};
sortNumerically = (colKey, direction) => {
function sortNumAsc(gradeRowA, gradeRowB) {
if (gradeRowA[colKey] < gradeRowB[colKey]) {
return -1;
}
if (gradeRowA[colKey] > gradeRowB[colKey]) {
return 1;
}
return 0;
}
function sortNumDesc(gradeRowA, gradeRowB) {
if (gradeRowA[colKey] < gradeRowB[colKey]) {
return 1;
}
if (gradeRowA[colKey] > gradeRowB[colKey]) {
return -1;
}
return 0;
}
this.setState({ grades: [...this.state.grades].sort(direction === 'desc' ? sortNumDesc : sortNumAsc) });
}
mapHeadings = (entry) => {
if (entry) {
const results = [{
label: 'Username',
key: 'username',
columnSortable: true,
onSort: (direction) => {
this.setState({
grades: [...this.state.grades].sort(direction === 'desc' ? this.sortAlphaDesc : this.sortAlphaAsc),
});
},
}];
const assignmentHeadings = entry.section_breakdown
.filter(section => section.is_graded && section.label)
.map(s => ({
label: s.label,
key: s.label,
columnSortable: true,
onSort: (direction) => { this.sortNumerically(s.label, direction); },
}));
const totals = [{
label: 'Total',
key: 'total',
columnSortable: true,
onSort: (direction) => { this.sortNumerically('total', direction); },
}];
return results.concat(assignmentHeadings).concat(totals);
}
return [];
};
mapHeadingsHw = (entry) => {
const results = [{
label: 'Username',
key: 'username',
columnSortable: true,
onSort: (direction) => {
this.setState({
grades: [...this.state.grades].sort(direction === 'desc' ? this.sortAlphaDesc : this.sortAlphaAsc),
});
},
}];
const assignmentHeadings = entry.section_breakdown
.filter(section => section.is_graded && section.label && section.category == 'Homework')
.map(s => ({
label: s.label,
key: s.label,
columnSortable: true,
onSort: (direction) => { this.sortNumerically(s.label, direction); },
}));
return results.concat(assignmentHeadings);
};
mapHeadingsExam = (entry) => {
const results = [{
label: 'Username',
key: 'username',
columnSortable: true,
onSort: (direction) => {
this.setState({
grades: [...this.state.grades].sort(direction === 'desc' ? this.sortAlphaDesc : this.sortAlphaAsc),
});
},
}];
const assignmentHeadings = entry.section_breakdown
.filter(section => section.is_graded && section.label && section.category == 'Exam')
.map(s => ({
label: s.label,
key: s.label,
columnSortable: true,
onSort: (direction) => { this.sortNumerically(s.label, direction); },
}));
return results.concat(assignmentHeadings);
};
setNewModalState = (userEntry, subsection) => {
this.setState({
modalModel: [{
@@ -170,7 +37,7 @@ export default class Gradebook extends React.Component {
<input
style={{ width: '25px' }}
type="text"
onChange={(event) => this.setState({updateVal: event.target.value})}
onChange={event => this.setState({ updateVal: event.target.value })}
/> / {subsection.score_possible}
</span>
),
@@ -179,58 +46,28 @@ export default class Gradebook extends React.Component {
modalOpen: true,
updateModuleId: subsection.module_id,
updateUserId: userEntry.user_id,
})
});
}
mapUserEntriesPercent = entries => entries.map((entry) => {
const results = { username: entry.username };
const assignments = entry.section_breakdown
.filter(section => section.is_graded)
.reduce((acc, subsection) => {
acc[subsection.label] = (
<button
className="btn btn-header link-style"
onClick={() => this.setNewModalState(entry, subsection)}
>
{subsection.percent}
</button>);
return acc;
}, {});
const totals = { total: entry.percent * 100 };
return Object.assign(results, assignments, totals);
});
mapUserEntriesAbsolute = entries => entries.map((entry) => {
const results = { username: entry.username };
const assignments = entry.section_breakdown
.filter(section => section.is_graded)
.reduce((acc, subsection) => {
acc[subsection.label] = (
<button
className="btn btn-header link-style"
onClick={() => this.setNewModalState(entry, subsection)}
>
{subsection.score_earned}/{subsection.score_possible}
</button>);
return acc;
}, {});
const totals = { total: entry.percent * 100 };
return Object.assign(results, assignments, totals);
});
handleAdjustedGradeClick = () => {
this.props.updateGrades(this.props.match.params.courseId, [
{
'user_id': this.state.updateUserId,
'usage_id': this.state.updateModuleId,
'grade': {
'earned_graded_override': this.state.updateVal,
user_id: this.state.updateUserId,
usage_id: this.state.updateModuleId,
grade: {
earned_graded_override: this.state.updateVal,
},
},
]);
this.setState({
modalModel: [{}],
modalOpen: false,
updateModuleId: null,
updateUserId: null,
});
}
updateQueryParams = (queryKey, queryValue) => {
const parsed = queryString.parse(this.props.location.search);
parsed[queryKey] = queryValue;
@@ -238,27 +75,27 @@ export default class Gradebook extends React.Component {
};
mapCohortsEntries = (entries) => {
let mapped = entries.map(entry => ({
const mapped = entries.map(entry => ({
id: entry.id,
label: entry.name,
}));
mapped.unshift({id:0, label:'Cohorts'});
mapped.unshift({ id: 0, label: 'Cohort-All' });
return mapped;
};
mapTracksEntries = (entries) => {
let mapped = entries.map(entry => ({
const mapped = entries.map(entry => ({
id: entry.slug,
label: entry.name,
}));
mapped.unshift({ label:'Tracks' });
mapped.unshift({ label: 'Track-All' });
return mapped;
};
updateTracks = (event) => {
const selectedTrackItem = this.props.tracks.find(x=>x.name===event);
const selectedTrackItem = this.props.tracks.find(x => x.name === event);
let selectedTrackSlug = null;
if(selectedTrackItem) {
if (selectedTrackItem) {
selectedTrackSlug = selectedTrackItem.slug;
}
this.props.getUserGrades(
@@ -266,14 +103,14 @@ export default class Gradebook extends React.Component {
this.props.selectedCohort,
selectedTrackSlug,
);
const updatedQueryStrings = this.updateQueryParams('track', selectedTrackSlug)
const updatedQueryStrings = this.updateQueryParams('track', selectedTrackSlug);
this.props.history.push(updatedQueryStrings);
};
updateCohorts = (event) => {
const selectedCohortItem = this.props.cohorts.find(x=>x.name===event);
const selectedCohortItem = this.props.cohorts.find(x => x.name === event);
let selectedCohortId = null;
if(selectedCohortItem) {
if (selectedCohortItem) {
selectedCohortId = selectedCohortItem.id;
}
this.props.getUserGrades(
@@ -281,7 +118,7 @@ export default class Gradebook extends React.Component {
selectedCohortId,
this.props.selectedTrack,
);
const updatedQueryStrings = this.updateQueryParams('cohort', selectedCohortId)
const updatedQueryStrings = this.updateQueryParams('cohort', selectedCohortId);
this.props.history.push(updatedQueryStrings);
};
@@ -301,12 +138,60 @@ export default class Gradebook extends React.Component {
return 'Tracks';
};
formatter = {
percent: entries => entries.map((entry) => {
const results = { username: entry.username };
const assignments = entry.section_breakdown
.filter(section => section.is_graded)
.reduce((acc, subsection) => {
acc[subsection.label] = (
<button
className="btn btn-header link-style"
onClick={() => this.setNewModalState(entry, subsection)}
>
{subsection.percent}
</button>);
return acc;
}, {});
const totals = { total: entry.percent * 100 };
return Object.assign(results, assignments, totals);
}),
absolute: entries => entries.map((entry) => {
const results = { username: entry.username };
const assignments = entry.section_breakdown
.filter(section => section.is_graded)
.reduce((acc, subsection) => {
acc[subsection.label] = (
<button
className="btn btn-header link-style"
onClick={() => this.setNewModalState(entry, subsection)}
>
{subsection.score_earned}/{subsection.score_possible}
</button>);
return acc;
}, {});
const totals = { total: entry.percent * 100 };
return Object.assign(results, assignments, totals);
}),
};
lmsInstructorDashboardUrl = courseId => `${configuration.LMS_BASE_URL}/courses/${courseId}/instructor`;
render() {
return (
<div className="d-flex justify-content-center">
<div className="card" style={{ width: '50rem' }}>
<div className="card gradebook-container">
<div className="card-body">
<a
href={this.lmsInstructorDashboardUrl(this.props.match.params.courseId)}
className="back-link"
>
Back to Dashboard
</a>
<h1>Gradebook</h1>
<h3> {this.props.match.params.courseId}</h3>
<hr />
<div className="d-flex justify-content-between" >
<div>
@@ -315,13 +200,13 @@ export default class Gradebook extends React.Component {
<span>
<input
id="score-view-percent"
className="ml-2"
className="ml-2 mr-1"
type="radio"
name="score-view"
value="percent"
onClick={() => this.setState({ grades: this.mapUserEntriesPercent(this.props.results).sort(this.sortAlphaDesc) })}
onClick={() => this.props.toggleFormat('percent')}
/>
<label className="ml-2 mr-2" htmlFor="score-view-percent">Percent</label>
<label className="mr-2" htmlFor="score-view-percent">Percent</label>
</span>
<span>
<input
@@ -329,50 +214,48 @@ export default class Gradebook extends React.Component {
type="radio"
name="score-view"
value="absolute"
onClick={() => this.setState({ grades: this.mapUserEntriesAbsolute(this.props.results).sort(this.sortAlphaDesc) })}
className="mr-1"
onClick={() => this.props.toggleFormat('absolute')}
/>
<label className="ml-2 mr-2" htmlFor="score-view-absolute">Absolute</label>
<label htmlFor="score-view-absolute">Absolute</label>
</span>
</div>
<div>
Category:
<span>
<label className="ml-2 mr-2" htmlFor="category-all">
<input
id="category-all"
className="ml-2"
type="radio"
name="category"
value="all"
onClick={() =>
this.setState({ headings: this.mapHeadings(this.props.results[0]) })}
/>
<input
id="category-all"
className="ml-2 mr-1"
type="radio"
name="category"
value="all"
onClick={() => this.props.filterColumns('all', this.props.grades[0])}
/>
<label className="mr-2" htmlFor="category-all">
All
</label>
</span>
<span>
<input
id="category-homework"
className="ml-2"
className="mr-1"
type="radio"
name="category"
value="homework"
onClick={() =>
this.setState({
headings: this.mapHeadingsHw(this.props.results[0]),
})}
onClick={() => this.props.filterColumns('hw', this.props.grades[0])}
/>
<label className="ml-2 mr-2" htmlFor="category-homework">Homework</label>
<label className="mr-2" htmlFor="category-homework">Homework</label>
</span>
<span>
<label className="ml-2 mr-2" htmlFor="Exam">
<input
id="category-exam"
type="radio"
name="category"
value="exam"
onClick={() => this.setState({ headings: this.mapHeadingsExam(this.props.results[0]) })}
/>
<input
id="category-exam"
type="radio"
name="category"
value="exam"
className="ml-2 mr-1"
onClick={() => this.props.filterColumns('exam', this.props.grades[0])}
/>
<label htmlFor="category-exam">
Exam
</label>
</span>
@@ -384,7 +267,7 @@ export default class Gradebook extends React.Component {
</span>
{this.props.tracks.length > 0 &&
<InputSelect
name='Tracks'
name="Tracks"
value={this.mapSelectedTrackEntry(this.props.selectedTrack)}
options={this.mapTracksEntries(this.props.tracks)}
onChange={this.updateTracks}
@@ -392,7 +275,7 @@ export default class Gradebook extends React.Component {
}
{this.props.cohorts.length > 0 &&
<InputSelect
name='Cohorts'
name="Cohorts"
value={this.mapSelectedCohortEntry(this.props.selectedCohort)}
options={this.mapCohortsEntries(this.props.cohorts)}
onChange={this.updateCohorts}
@@ -403,7 +286,7 @@ export default class Gradebook extends React.Component {
</div>
<div>
<div style={{ marginLeft: '10px', marginBottom: '10px' }}>
<a href="https://www.google./com">Download Grade Report</a>
<a href={`${this.lmsInstructorDashboardUrl(this.props.match.params.courseId)}#view-data_download`}>Download Grade Report</a>
</div>
<SearchField
onSubmit={value => this.props.searchForUser(this.props.match.params.courseId, value, this.props.selectedCohort, this.props.selectedTrack)}
@@ -411,15 +294,36 @@ export default class Gradebook extends React.Component {
onClear={() => this.props.getUserGrades(this.props.match.params.courseId, this.props.selectedCohort, this.props.selectedTrack)}
value={this.state.filterValue}
/>
<div className="d-flex justify-content-end" style={{ marginTop : '20px'}}>
<Button
label="Previous"
buttonType="primary"
style={{ visibility: (!this.props.prevPage ? 'hidden' : 'visible') }}
onClick={() => this.props.getPrevNextGrades(this.props.prevPage, this.props.selectedCohort, this.props.selectedTrack)}
/>
<div style={{width: '10px'}} />
<Button
label="Next"
buttonType="primary"
style={{ visibility: (!this.props.nextPage ? 'hidden' : 'visible') }}
onClick={() => this.props.getPrevNextGrades(this.props.nextPage, this.props.selectedCohort, this.props.selectedTrack)}
/>
</div>
</div>
</div>
<br />
<StatusAlert
alertType="success"
dialog="The grade has been successfully edited."
onClose={() => this.props.updateBanner(false)}
open={this.props.showSuccess}
/>
<div className="gbook">
<Table
columns={this.mapHeadings(this.props.grades[0])}
data={this.mapUserEntriesPercent(this.props.grades)}
columns={this.props.headings}
data={this.formatter[this.props.format](this.props.grades)}
tableSortable
defaultSortDirection="desc"
defaultSortDirection="asc"
defaultSortedColumn="username"
/>
</div>
@@ -430,12 +334,9 @@ export default class Gradebook extends React.Component {
<div>
<h3>{this.state.modalModel[0].assignmentName}</h3>
<Table
columns={[{ label: 'Username', key: 'username' }, { label: 'Current grade', key: 'currentGrade' }, { label: 'Adjusted grade', key: 'adjustedGrade' }]}
data={this.state.modalModel}
tableSortable
defaultSortDirection="desc"
defaultSortedColumn="username"
/>
columns={[{ label: 'Username', key: 'username' }, { label: 'Current grade', key: 'currentGrade' }, { label: 'Adjusted grade', key: 'adjustedGrade' }]}
data={this.state.modalModel}
/>
</div>
)}
buttons={[
@@ -443,7 +344,7 @@ export default class Gradebook extends React.Component {
label="Edit Grade"
buttonType="primary"
onClick={this.handleAdjustedGradeClick}
/>
/>,
]}
onClose={() => this.setState({
modalOpen: false,

View File

@@ -3,6 +3,7 @@ const configuration = {
LMS_BASE_URL: process.env.LMS_BASE_URL,
LOGIN_URL: process.env.LOGIN_URL,
LOGOUT_URL: process.env.LOGOUT_URL,
CSRF_TOKEN_API_PATH: process.env.CSRF_TOKEN_API_PATH,
REFRESH_ACCESS_TOKEN_ENDPOINT: process.env.REFRESH_ACCESS_TOKEN_ENDPOINT,
DATA_API_BASE_URL: process.env.DATA_API_BASE_URL,
SECURE_COOKIES: process.env.NODE_ENV !== 'development',

View File

@@ -1,17 +1,30 @@
import { connect } from 'react-redux';
import Gradebook from '../../components/Gradebook';
import { fetchGrades, fetchMatchingUserGrades, updateGrades } from '../../data/actions/grades';
import {
fetchGrades,
fetchMatchingUserGrades,
fetchPrevNextGrades,
updateGrades,
toggleGradeFormat,
filterColumns,
updateBanner,
} from '../../data/actions/grades';
import { fetchCohorts } from '../../data/actions/cohorts';
import { fetchTracks } from '../../data/actions/tracks';
const mapStateToProps = state => (
{
grades: state.grades.results,
headings: state.grades.headings,
tracks: state.tracks.results,
cohorts: state.cohorts.results,
selectedTrack: state.grades.selectedTrack,
selectedCohort: state.grades.selectedCohort,
format: state.grades.gradeFormat,
showSuccess: state.grades.showSuccess,
prevPage: state.grades.prevPage,
nextPage: state.grades.nextPage,
}
);
@@ -23,6 +36,9 @@ const mapDispatchToProps = dispatch => (
searchForUser: (courseId, searchText, cohort, track) => {
dispatch(fetchMatchingUserGrades(courseId, searchText, cohort, track));
},
getPrevNextGrades : (endpoint, cohort, track) => {
dispatch(fetchPrevNextGrades(endpoint, cohort, track));
},
getCohorts: (courseId) => {
dispatch(fetchCohorts(courseId));
},
@@ -32,6 +48,15 @@ const mapDispatchToProps = dispatch => (
updateGrades: (courseId, updateData) => {
dispatch(updateGrades(courseId, updateData));
},
toggleFormat: (formatType) => {
dispatch(toggleGradeFormat(formatType));
},
filterColumns: (filterType, exampleUser) => {
dispatch(filterColumns(filterType, exampleUser));
},
updateBanner: (showSuccess) => {
dispatch(updateBanner(showSuccess));
},
}
);

View File

@@ -1,38 +0,0 @@
import 'whatwg-fetch';
import {
STARTED_FETCHING_COMMENT,
FINISHED_FETCHING_COMMENT,
ERROR_FETCHING_COMMENT,
GET_COMMENT,
} from '../constants/actionTypes/comment';
const startedFetchingComment = () => ({ type: STARTED_FETCHING_COMMENT });
const finishedFetchingComment = () => ({ type: FINISHED_FETCHING_COMMENT });
const errorFetchingComment = () => ({ type: ERROR_FETCHING_COMMENT });
const getComment = comment => ({ type: GET_COMMENT, comment });
const fetchComment = commentId => (
(dispatch) => {
dispatch(startedFetchingComment());
return fetch(`https://jsonplaceholder.typicode.com/comments/${commentId}`)
.then((response) => {
if (response.ok) {
return response.json();
}
throw new Error();
})
.then((data) => {
dispatch(getComment(data));
dispatch(finishedFetchingComment());
})
.catch(() => dispatch(errorFetchingComment()));
}
);
export {
startedFetchingComment,
finishedFetchingComment,
errorFetchingComment,
getComment,
fetchComment,
};

View File

@@ -6,39 +6,80 @@ import {
GRADE_UPDATE_REQUEST,
GRADE_UPDATE_SUCCESS,
GRADE_UPDATE_FAILURE,
TOGGLE_GRADE_FORMAT,
SORT_GRADES,
FILTER_COLUMNS,
UPDATE_BANNER,
} from '../constants/actionTypes/grades';
import LmsApiService from '../services/LmsApiService';
import store from '../store';
import { headingMapper, gradeSortMap, sortAlphaAsc } from './utils';
import apiClient from "../apiClient";
const sortGrades = (columnName, direction) => {
const sortFn = gradeSortMap(columnName, direction);
const { results } = store.getState().grades;
results.sort(sortFn);
/* have to make a copy of results or React wont know there was
* a change and wont trigger a re-render
*/
return ({ type: SORT_GRADES, results: [...results] });
};
const startedFetchingGrades = () => ({ type: STARTED_FETCHING_GRADES });
const finishedFetchingGrades = () => ({ type: FINISHED_FETCHING_GRADES });
const errorFetchingGrades = () => ({ type: ERROR_FETCHING_GRADES });
const gotGrades = (grades, cohort, track) => ({
const gotGrades = (grades, cohort, track, headings, prev, next) => ({
type: GOT_GRADES,
grades,
cohort,
track,
headings,
prev,
next,
});
const gradeUpdateRequest = () => ({ type: GRADE_UPDATE_REQUEST });
const gradeUpdateSuccess = responseData => ({
type: GRADE_UPDATE_SUCCESS,
payload: { responseData },
})
});
const gradeUpdateFailure = error => ({
type: GRADE_UPDATE_FAILURE,
payload: { error },
});
const fetchGrades = (courseId, cohort, track) => (
const toggleGradeFormat = formatType => ({ type: TOGGLE_GRADE_FORMAT, formatType });
const filterColumns = (filterType, exampleUser) => (
dispatch => ({
type: FILTER_COLUMNS,
headings: headingMapper[filterType](dispatch, exampleUser),
}));
const updateBanner = showSuccess => ({ type: UPDATE_BANNER, showSuccess });
const fetchGrades = (courseId, cohort, track, showSuccess) => (
(dispatch) => {
dispatch(startedFetchingGrades());
return LmsApiService.fetchGradebookData(courseId, null, cohort, track)
.then(response => response.data)
.then((data) => {
dispatch(gotGrades(data.results, cohort, track));
dispatch(gotGrades(
data.results.sort(sortAlphaAsc),
cohort,
track,
headingMapper.all(dispatch, data.results[0]),
data.previous,
data.next,
));
dispatch(finishedFetchingGrades());
dispatch(updateBanner(!!showSuccess));
})
.catch((error) => {
.catch(() => {
dispatch(errorFetchingGrades());
});
}
@@ -50,22 +91,53 @@ const fetchMatchingUserGrades = (courseId, searchText, cohort, track) => (
return LmsApiService.fetchGradebookData(courseId, searchText, cohort, track)
.then(response => response.data)
.then((data) => {
dispatch(gotGrades(data.results, cohort, track));
dispatch(gotGrades(
data.results.sort(sortAlphaAsc),
cohort,
track,
headingMapper.all(dispatch, data.results[0]),
data.previous,
data.next,
));
dispatch(finishedFetchingGrades());
})
.catch((error) => {
.catch(() => {
dispatch(errorFetchingGrades());
});
}
);
const fetchPrevNextGrades = (endpoint, cohort, track) => (
(dispatch) => {
dispatch(startedFetchingGrades());
return apiClient.get(endpoint)
.then(response => response.data)
.then((data) => {
dispatch(gotGrades(
data.results.sort(sortAlphaAsc),
cohort,
track,
headingMapper.all(dispatch, data.results[0]),
data.previous,
data.next,
));
dispatch(finishedFetchingGrades());
})
.catch(() => {
dispatch(errorFetchingGrades());
});
}
);
const updateGrades = (courseId, updateData) => (
(dispatch) => {
dispatch(gradeUpdateRequest());
return LmsApiService.updateGradebookData(courseId, updateData)
.then(response => response.data)
.then((data) => {
dispatch(gradeUpdateSuccess(data))
dispatch(gradeUpdateSuccess(data));
dispatch(fetchGrades(courseId, null, null, true));
})
.catch((error) => {
dispatch(gradeUpdateFailure(error));
@@ -80,8 +152,13 @@ export {
gotGrades,
fetchGrades,
fetchMatchingUserGrades,
fetchPrevNextGrades,
gradeUpdateRequest,
gradeUpdateSuccess,
gradeUpdateFailure,
updateGrades,
toggleGradeFormat,
sortGrades,
filterColumns,
updateBanner,
};

View File

@@ -1,46 +0,0 @@
import 'whatwg-fetch';
import {
STARTED_FETCHING_POSTS,
FINISHED_FETCHING_POSTS,
GET_POSTS,
} from '../constants/actionTypes/posts';
const startedFetchingPosts = () => (
{
type: STARTED_FETCHING_POSTS,
}
);
const finishedFetchingPosts = () => (
{
type: FINISHED_FETCHING_POSTS,
}
);
const getPosts = posts => (
{
type: GET_POSTS,
posts,
}
);
const fetchPosts = () => (
(dispatch) => {
dispatch(startedFetchingPosts());
return fetch('https://jsonplaceholder.typicode.com/posts')
// TODO: handle response error
.then(response => response.json())
.then((data) => {
dispatch(getPosts(data));
dispatch(finishedFetchingPosts());
});
}
);
export {
startedFetchingPosts,
finishedFetchingPosts,
getPosts,
fetchPosts,
};

View File

@@ -1,65 +0,0 @@
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import fetchMock from 'fetch-mock';
import {
startedFetchingPosts,
finishedFetchingPosts,
getPosts,
fetchPosts,
} from './posts';
import {
STARTED_FETCHING_POSTS,
GET_POSTS,
FINISHED_FETCHING_POSTS,
} from '../constants/actionTypes/posts';
const mockStore = configureMockStore([thunk]);
describe('actions', () => {
afterEach(() => {
fetchMock.reset();
fetchMock.restore();
});
it('sends started fetching post action', () => {
const expected = { type: STARTED_FETCHING_POSTS };
expect(startedFetchingPosts()).toEqual(expected);
});
it('sends finished fetching posts', () => {
const expected = { type: FINISHED_FETCHING_POSTS };
expect(finishedFetchingPosts()).toEqual(expected);
});
it('sends posts', () => {
const data = 'data';
const expected = { type: GET_POSTS, posts: data };
expect(getPosts(data)).toEqual(expected);
});
it('fetches posts', () => {
const posts = [
{
id: 1,
title: 'title',
body: 'body',
},
];
fetchMock.getOnce('https://jsonplaceholder.typicode.com/posts', {
body: JSON.stringify({ posts }),
headers: { 'content-type': 'application/json' },
});
const store = mockStore({ posts: [] });
const expectedActions = [
{ type: STARTED_FETCHING_POSTS },
{ type: GET_POSTS, posts: { posts } },
{ type: FINISHED_FETCHING_POSTS },
];
return store.dispatch(fetchPosts()).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
});
});

142
src/data/actions/utils.js Normal file
View File

@@ -0,0 +1,142 @@
import { sortGrades } from './grades';
const sortAlphaAsc = (gradeRowA, gradeRowB) => {
const a = gradeRowA.username.toUpperCase();
const b = gradeRowB.username.toUpperCase();
if (a < b) {
return -1;
}
if (a > b) {
return 1;
}
return 0;
};
const sortAlphaDesc = (gradeRowA, gradeRowB) => {
const a = gradeRowA.username.toUpperCase();
const b = gradeRowB.username.toUpperCase();
if (a < b) {
return 1;
}
if (a > b) {
return -1;
}
return 0;
};
const sortNumerically = (colKey, direction) => {
function getPercents(gradeRowA, gradeRowB) {
if (colKey !== 'total') {
return {
a: gradeRowA.section_breakdown.find(x => x.label === colKey).percent,
b: gradeRowB.section_breakdown.find(x => x.label === colKey).percent,
};
}
return {
a: gradeRowA.percent,
b: gradeRowB.percent,
};
}
function sortNumAsc(gradeRowA, gradeRowB) {
const { a, b } = getPercents(gradeRowA, gradeRowB);
return a - b;
}
function sortNumDesc(gradeRowA, gradeRowB) {
const { a, b } = getPercents(gradeRowA, gradeRowB);
return b - a;
}
return direction === 'desc' ? sortNumDesc : sortNumAsc;
};
function gradeSortMap(columnName, direction) {
if (columnName === 'username' && direction === 'desc') {
return sortAlphaDesc;
} else if (columnName === 'username') {
return sortAlphaAsc;
}
return sortNumerically(columnName, direction);
}
const headingMapper = {
all: (dispatch, entry) => {
if (entry) {
const results = [{
label: 'Username',
key: 'username',
columnSortable: true,
onSort: (direction) => { dispatch(sortGrades('username', direction)); },
}];
const assignmentHeadings = entry.section_breakdown
.filter(section => section.is_graded && section.label)
.map(s => ({
label: s.label,
key: s.label,
columnSortable: true,
onSort: direction => dispatch(sortGrades(s.label, direction)),
}));
const totals = [{
label: 'Total',
key: 'total',
columnSortable: true,
onSort: direction => dispatch(sortGrades('total', direction)),
}];
return results.concat(assignmentHeadings).concat(totals);
}
return [];
},
hw: (dispatch, entry) => {
const results = [{
label: 'Username',
key: 'username',
columnSortable: true,
onSort: (direction) => {
this.setState({
grades: [...this.state.grades].sort(direction === 'desc' ? this.sortAlphaDesc : this.sortAlphaAsc),
});
},
}];
const assignmentHeadings = entry.section_breakdown
.filter(section => section.is_graded && section.label && section.category == 'Homework')
.map(s => ({
label: s.label,
key: s.label,
columnSortable: false,
onSort: (direction) => { this.sortNumerically(s.label, direction); },
}));
return results.concat(assignmentHeadings);
},
exam: (dispatch, entry) => {
const results = [{
label: 'Username',
key: 'username',
columnSortable: false,
onSort: (direction) => {
this.setState({
grades: [...this.state.grades].sort(direction === 'desc' ? this.sortAlphaDesc : this.sortAlphaAsc),
});
},
}];
const assignmentHeadings = entry.section_breakdown
.filter(section => section.is_graded && section.label && section.category == 'Exam')
.map(s => ({
label: s.label,
key: s.label,
columnSortable: false,
onSort: (direction) => { this.sortNumerically(s.label, direction); },
}));
return results.concat(assignmentHeadings);
},
};
export { headingMapper, gradeSortMap, sortAlphaAsc };

View File

@@ -1,10 +1,12 @@
import { configuration } from '../config';
import { getAuthenticatedAPIClient } from '@edx/frontend-auth';
import { configuration } from '../config';
const apiClient = getAuthenticatedAPIClient({
appBaseUrl: configuration.BASE_URL,
loginUrl: configuration.LOGIN_URL,
logoutUrl: configuration.LOGOUT_URL,
csrfTokenApiPath: process.env.CSRF_TOKEN_API_PATH,
refreshAccessTokenEndpoint: configuration.REFRESH_ACCESS_TOKEN_ENDPOINT,
accessTokenCookieName: configuration.ACCESS_TOKEN_COOKIE_NAME,
csrfCookieName: configuration.CSRF_COOKIE_NAME,

View File

@@ -1,11 +0,0 @@
const STARTED_FETCHING_COMMENT = 'STARTED_FETCHING_COMMENT';
const FINISHED_FETCHING_COMMENT = 'FINISHED_FETCHING_COMMENT';
const ERROR_FETCHING_COMMENT = 'ERROR_FETCHING_COMMENT';
const GET_COMMENT = 'GET_COMMENT';
export {
STARTED_FETCHING_COMMENT,
FINISHED_FETCHING_COMMENT,
ERROR_FETCHING_COMMENT,
GET_COMMENT,
};

View File

@@ -7,6 +7,11 @@ const GRADE_UPDATE_REQUEST = 'GRADE_UPDATE_REQUEST';
const GRADE_UPDATE_SUCCESS = 'GRADE_UPDATE_SUCCESS';
const GRADE_UPDATE_FAILURE = 'GRADE_UPDATE_FAILURE';
const TOGGLE_GRADE_FORMAT = 'TOGGLE_GRADE_FORMAT';
const SORT_GRADES = 'SORT_GRADES';
const FILTER_COLUMNS = 'FILTER_COLUMNS';
const UPDATE_BANNER = 'UPDATE_BANNER';
export {
STARTED_FETCHING_GRADES,
FINISHED_FETCHING_GRADES,
@@ -15,4 +20,8 @@ export {
GRADE_UPDATE_REQUEST,
GRADE_UPDATE_SUCCESS,
GRADE_UPDATE_FAILURE,
TOGGLE_GRADE_FORMAT,
SORT_GRADES,
FILTER_COLUMNS,
UPDATE_BANNER,
};

View File

@@ -1,9 +0,0 @@
const STARTED_FETCHING_POSTS = 'STARTED_FETCHING_POSTS';
const GET_POSTS = 'GET_POSTS';
const FINISHED_FETCHING_POSTS = 'FINISHED_FETCHING_POSTS';
export {
STARTED_FETCHING_POSTS,
GET_POSTS,
FINISHED_FETCHING_POSTS,
};

View File

@@ -1,46 +0,0 @@
import {
STARTED_FETCHING_COMMENT,
ERROR_FETCHING_COMMENT,
GET_COMMENT,
} from '../constants/actionTypes/comment';
const initialState = {
details: {
id: null,
postId: null,
name: '',
email: 'example@example.com',
body: '',
},
startedFetching: false,
finishedFetching: false,
errorFetching: false,
};
const comment = (state = initialState, action) => {
switch (action.type) {
case GET_COMMENT:
return {
...state,
details: { ...action.comment },
finishedFetching: true,
errorFetching: false,
};
case STARTED_FETCHING_COMMENT:
return {
...state,
startedFetching: true,
finishedFetching: false,
};
case ERROR_FETCHING_COMMENT:
return {
...state,
finishedFetching: true,
errorFetching: true,
};
default:
return state;
}
};
export default comment;

View File

@@ -2,13 +2,23 @@ import {
STARTED_FETCHING_GRADES,
ERROR_FETCHING_GRADES,
GOT_GRADES,
TOGGLE_GRADE_FORMAT,
FILTER_COLUMNS,
GRADE_UPDATE_SUCCESS,
UPDATE_BANNER,
SORT_GRADES,
} from '../constants/actionTypes/grades';
const initialState = {
results: [],
headings: [],
startedFetching: false,
finishedFetching: false,
errorFetching: false,
gradeFormat: 'percent',
showSuccess: false,
prevPage: null,
nextPage: null,
};
const grades = (state = initialState, action) => {
@@ -17,10 +27,13 @@ const grades = (state = initialState, action) => {
return {
...state,
results: action.grades,
headings: action.headings,
finishedFetching: true,
errorFetching: false,
selectedTrack: action.track,
selectedCohort: action.cohort,
prevPage: action.prev,
nextPage: action.next,
};
case STARTED_FETCHING_GRADES:
return {
@@ -34,1009 +47,29 @@ const grades = (state = initialState, action) => {
finishedFetching: true,
errorFetching: true,
};
case TOGGLE_GRADE_FORMAT:
return {
...state,
gradeFormat: action.formatType,
};
case FILTER_COLUMNS:
return {
...state,
headings: action.headings,
};
case UPDATE_BANNER:
return {
...state,
showSuccess: action.showSuccess,
};
case SORT_GRADES:
return {
...state,
results: action.results,
};
default:
return state;
}
};
export default grades;
// Gradebook.defaultProps = {
// "results": [
// {
// "course_id": "course-v1:edX+DemoX+Demo_Course",
// "email": "honor@example.com",
// "user_id": 6,
// "username": "honor",
// "full_name": "",
// "passed": false,
// "percent": 0,
// "letter_grade": null,
// "progress_page_url": "/courses/course-v1:edX+DemoX+Demo_Course/progress/6/",
// "section_breakdown": [
// {
// "are_grades_published": true,
// "auto_grade": false,
// "category": null,
// "chapter_name": "Introduction",
// "comment": "",
// "detail": "",
// "displayed_value": "0.00",
// "is_graded": false,
// "grade_description": "(0.00/0.00)",
// "is_ag": false,
// "is_average": false,
// "is_manually_graded": false,
// "label": null,
// "letter_grade": null,
// "module_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@edx_introduction",
// "percent": 0,
// "score_earned": 0,
// "score_possible": 0,
// "section_block_id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@d8a6192ade314473a78242dfeedfbf5b",
// "subsection_name": "Demo Course Overview"
// },
// {
// "are_grades_published": true,
// "auto_grade": false,
// "category": null,
// "chapter_name": "Example Week 1: Getting Started",
// "comment": "",
// "detail": "",
// "displayed_value": "0.00",
// "is_graded": false,
// "grade_description": "(0.00/3.00)",
// "is_ag": false,
// "is_average": false,
// "is_manually_graded": false,
// "label": null,
// "letter_grade": null,
// "module_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@19a30717eff543078a5d94ae9d6c18a5",
// "percent": 0,
// "score_earned": 0,
// "score_possible": 3,
// "section_block_id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@interactive_demonstrations",
// "subsection_name": "Lesson 1 - Getting Started"
// },
// {
// "are_grades_published": true,
// "auto_grade": false,
// "category": "Homework",
// "chapter_name": "Example Week 1: Getting Started",
// "comment": "",
// "detail": "",
// "displayed_value": "0.00",
// "is_graded": true,
// "grade_description": "(0.00/11.00)",
// "is_ag": false,
// "is_average": false,
// "is_manually_graded": false,
// "label": "Ex 01",
// "letter_grade": null,
// "module_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@basic_questions",
// "percent": 0,
// "score_earned": 0,
// "score_possible": 11,
// "section_block_id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@interactive_demonstrations",
// "subsection_name": "Homework - Question Styles"
// },
// {
// "are_grades_published": true,
// "auto_grade": false,
// "category": null,
// "chapter_name": "Example Week 2: Get Interactive",
// "comment": "",
// "detail": "",
// "displayed_value": "0.00",
// "is_graded": false,
// "grade_description": "(0.00/0.00)",
// "is_ag": false,
// "is_average": false,
// "is_manually_graded": false,
// "label": null,
// "letter_grade": null,
// "module_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@simulations",
// "percent": 0,
// "score_earned": 0,
// "score_possible": 0,
// "section_block_id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@graded_interactions",
// "subsection_name": "Lesson 2 - Let's Get Interactive!"
// },
// {
// "are_grades_published": true,
// "auto_grade": false,
// "category": "Homework",
// "chapter_name": "Example Week 2: Get Interactive",
// "comment": "",
// "detail": "",
// "displayed_value": "0.00",
// "is_graded": true,
// "grade_description": "(0.00/5.00)",
// "is_ag": false,
// "is_average": false,
// "is_manually_graded": false,
// "label": "Ex 02",
// "letter_grade": null,
// "module_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@graded_simulations",
// "percent": 0,
// "score_earned": 0,
// "score_possible": 5,
// "section_block_id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@graded_interactions",
// "subsection_name": "Homework - Labs and Demos"
// },
// {
// "are_grades_published": true,
// "auto_grade": false,
// "category": null,
// "chapter_name": "Example Week 2: Get Interactive",
// "comment": "",
// "detail": "",
// "displayed_value": "0.00",
// "is_graded": false,
// "grade_description": "(0.00/19.00)",
// "is_ag": false,
// "is_average": false,
// "is_manually_graded": false,
// "label": null,
// "letter_grade": null,
// "module_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@175e76c4951144a29d46211361266e0e",
// "percent": 0,
// "score_earned": 0,
// "score_possible": 19,
// "section_block_id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@graded_interactions",
// "subsection_name": "Homework - Essays"
// },
// {
// "are_grades_published": true,
// "auto_grade": false,
// "category": null,
// "chapter_name": "Example Week 3: Be Social",
// "comment": "",
// "detail": "",
// "displayed_value": "0.00",
// "is_graded": false,
// "grade_description": "(0.00/0.00)",
// "is_ag": false,
// "is_average": false,
// "is_manually_graded": false,
// "label": null,
// "letter_grade": null,
// "module_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@48ecb924d7fe4b66a230137626bfa93e",
// "percent": 0,
// "score_earned": 0,
// "score_possible": 0,
// "section_block_id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@social_integration",
// "subsection_name": "Lesson 3 - Be Social"
// },
// {
// "are_grades_published": true,
// "auto_grade": false,
// "category": null,
// "chapter_name": "Example Week 3: Be Social",
// "comment": "",
// "detail": "",
// "displayed_value": "0.00",
// "is_graded": false,
// "grade_description": "(0.00/0.00)",
// "is_ag": false,
// "is_average": false,
// "is_manually_graded": false,
// "label": null,
// "letter_grade": null,
// "module_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@dbe8fc027bcb4fe9afb744d2e8415855",
// "percent": 0,
// "score_earned": 0,
// "score_possible": 0,
// "section_block_id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@social_integration",
// "subsection_name": "Homework - Find Your Study Buddy"
// },
// {
// "are_grades_published": true,
// "auto_grade": false,
// "category": null,
// "chapter_name": "Example Week 3: Be Social",
// "comment": "",
// "detail": "",
// "displayed_value": "0.00",
// "is_graded": false,
// "grade_description": "(0.00/0.00)",
// "is_ag": false,
// "is_average": false,
// "is_manually_graded": false,
// "label": null,
// "letter_grade": null,
// "module_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@6ab9c442501d472c8ed200e367b4edfa",
// "percent": 0,
// "score_earned": 0,
// "score_possible": 0,
// "section_block_id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@social_integration",
// "subsection_name": "More Ways to Connect"
// },
// {
// "are_grades_published": true,
// "auto_grade": false,
// "category": "Exam",
// "chapter_name": "About Exams and Certificates",
// "comment": "",
// "detail": "",
// "displayed_value": "0.00",
// "is_graded": true,
// "grade_description": "(0.00/6.00)",
// "is_ag": false,
// "is_average": false,
// "is_manually_graded": false,
// "label": null,
// "letter_grade": null,
// "module_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@workflow",
// "percent": 0,
// "score_earned": 0,
// "score_possible": 6,
// "section_block_id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@1414ffd5143b4b508f739b563ab468b7",
// "subsection_name": "edX Exams"
// }
// ],
// "aggregates": {
// "Exam": {
// "score_possible": 6,
// "score_earned": 0
// },
// "Homework": {
// "score_possible": 16,
// "score_earned": 0
// }
// }
// },
// {
// "course_id": "course-v1:edX+DemoX+Demo_Course",
// "email": "audit@example.com",
// "user_id": 7,
// "username": "audit",
// "full_name": "",
// "passed": false,
// "percent": 0.17,
// "letter_grade": null,
// "progress_page_url": "/courses/course-v1:edX+DemoX+Demo_Course/progress/7/",
// "section_breakdown": [
// {
// "are_grades_published": true,
// "auto_grade": false,
// "category": null,
// "chapter_name": "Introduction",
// "comment": "",
// "detail": "",
// "displayed_value": "0.00",
// "is_graded": false,
// "grade_description": "(0.00/0.00)",
// "is_ag": false,
// "is_average": false,
// "is_manually_graded": false,
// "label": null,
// "letter_grade": null,
// "module_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@edx_introduction",
// "percent": 0,
// "score_earned": 0,
// "score_possible": 0,
// "section_block_id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@d8a6192ade314473a78242dfeedfbf5b",
// "subsection_name": "Demo Course Overview"
// },
// {
// "are_grades_published": true,
// "auto_grade": false,
// "category": null,
// "chapter_name": "Example Week 1: Getting Started",
// "comment": "",
// "detail": "",
// "displayed_value": "0.00",
// "is_graded": false,
// "grade_description": "(0.00/3.00)",
// "is_ag": false,
// "is_average": false,
// "is_manually_graded": false,
// "label": null,
// "letter_grade": null,
// "module_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@19a30717eff543078a5d94ae9d6c18a5",
// "percent": 0,
// "score_earned": 0,
// "score_possible": 3,
// "section_block_id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@interactive_demonstrations",
// "subsection_name": "Lesson 1 - Getting Started"
// },
// {
// "are_grades_published": true,
// "auto_grade": false,
// "category": "Homework",
// "chapter_name": "Example Week 1: Getting Started",
// "comment": "",
// "detail": "",
// "displayed_value": "0.45",
// "is_graded": true,
// "grade_description": "(5.00/11.00)",
// "is_ag": false,
// "is_average": false,
// "is_manually_graded": false,
// "label": "Ex 01",
// "letter_grade": null,
// "module_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@basic_questions",
// "percent": 0.45,
// "score_earned": 5,
// "score_possible": 11,
// "section_block_id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@interactive_demonstrations",
// "subsection_name": "Homework - Question Styles"
// },
// {
// "are_grades_published": true,
// "auto_grade": false,
// "category": null,
// "chapter_name": "Example Week 2: Get Interactive",
// "comment": "",
// "detail": "",
// "displayed_value": "0.00",
// "is_graded": false,
// "grade_description": "(0.00/0.00)",
// "is_ag": false,
// "is_average": false,
// "is_manually_graded": false,
// "label": null,
// "letter_grade": null,
// "module_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@simulations",
// "percent": 0,
// "score_earned": 0,
// "score_possible": 0,
// "section_block_id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@graded_interactions",
// "subsection_name": "Lesson 2 - Let's Get Interactive!"
// },
// {
// "are_grades_published": true,
// "auto_grade": false,
// "category": "Homework",
// "chapter_name": "Example Week 2: Get Interactive",
// "comment": "",
// "detail": "",
// "displayed_value": "0.00",
// "is_graded": true,
// "grade_description": "(0.00/5.00)",
// "is_ag": false,
// "is_average": false,
// "is_manually_graded": false,
// "label": "Ex 02",
// "letter_grade": null,
// "module_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@graded_simulations",
// "percent": 0,
// "score_earned": 0,
// "score_possible": 5,
// "section_block_id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@graded_interactions",
// "subsection_name": "Homework - Labs and Demos"
// },
// {
// "are_grades_published": true,
// "auto_grade": false,
// "category": null,
// "chapter_name": "Example Week 2: Get Interactive",
// "comment": "",
// "detail": "",
// "displayed_value": "0.00",
// "is_graded": false,
// "grade_description": "(0.00/19.00)",
// "is_ag": false,
// "is_average": false,
// "is_manually_graded": false,
// "label": null,
// "letter_grade": null,
// "module_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@175e76c4951144a29d46211361266e0e",
// "percent": 0,
// "score_earned": 0,
// "score_possible": 19,
// "section_block_id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@graded_interactions",
// "subsection_name": "Homework - Essays"
// },
// {
// "are_grades_published": true,
// "auto_grade": false,
// "category": null,
// "chapter_name": "Example Week 3: Be Social",
// "comment": "",
// "detail": "",
// "displayed_value": "0.00",
// "is_graded": false,
// "grade_description": "(0.00/0.00)",
// "is_ag": false,
// "is_average": false,
// "is_manually_graded": false,
// "label": null,
// "letter_grade": null,
// "module_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@48ecb924d7fe4b66a230137626bfa93e",
// "percent": 0,
// "score_earned": 0,
// "score_possible": 0,
// "section_block_id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@social_integration",
// "subsection_name": "Lesson 3 - Be Social"
// },
// {
// "are_grades_published": true,
// "auto_grade": false,
// "category": null,
// "chapter_name": "Example Week 3: Be Social",
// "comment": "",
// "detail": "",
// "displayed_value": "0.00",
// "is_graded": false,
// "grade_description": "(0.00/0.00)",
// "is_ag": false,
// "is_average": false,
// "is_manually_graded": false,
// "label": null,
// "letter_grade": null,
// "module_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@dbe8fc027bcb4fe9afb744d2e8415855",
// "percent": 0,
// "score_earned": 0,
// "score_possible": 0,
// "section_block_id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@social_integration",
// "subsection_name": "Homework - Find Your Study Buddy"
// },
// {
// "are_grades_published": true,
// "auto_grade": false,
// "category": null,
// "chapter_name": "Example Week 3: Be Social",
// "comment": "",
// "detail": "",
// "displayed_value": "0.00",
// "is_graded": false,
// "grade_description": "(0.00/0.00)",
// "is_ag": false,
// "is_average": false,
// "is_manually_graded": false,
// "label": null,
// "letter_grade": null,
// "module_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@6ab9c442501d472c8ed200e367b4edfa",
// "percent": 0,
// "score_earned": 0,
// "score_possible": 0,
// "section_block_id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@social_integration",
// "subsection_name": "More Ways to Connect"
// },
// {
// "are_grades_published": true,
// "auto_grade": false,
// "category": "Exam",
// "chapter_name": "About Exams and Certificates",
// "comment": "",
// "detail": "",
// "displayed_value": "0.00",
// "is_graded": true,
// "grade_description": "(0.00/6.00)",
// "is_ag": false,
// "is_average": false,
// "is_manually_graded": false,
// "label": null,
// "letter_grade": null,
// "module_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@workflow",
// "percent": 0,
// "score_earned": 0,
// "score_possible": 6,
// "section_block_id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@1414ffd5143b4b508f739b563ab468b7",
// "subsection_name": "edX Exams"
// }
// ],
// "aggregates": {
// "Exam": {
// "score_possible": 6,
// "score_earned": 0
// },
// "Homework": {
// "score_possible": 16,
// "score_earned": 5
// }
// }
// },
// {
// "course_id": "course-v1:edX+DemoX+Demo_Course",
// "email": "verified@example.com",
// "user_id": 8,
// "username": "verified",
// "full_name": "",
// "passed": false,
// "percent": 0,
// "letter_grade": null,
// "progress_page_url": "/courses/course-v1:edX+DemoX+Demo_Course/progress/8/",
// "section_breakdown": [
// {
// "are_grades_published": true,
// "auto_grade": false,
// "category": null,
// "chapter_name": "Introduction",
// "comment": "",
// "detail": "",
// "displayed_value": "0.00",
// "is_graded": false,
// "grade_description": "(0.00/0.00)",
// "is_ag": false,
// "is_average": false,
// "is_manually_graded": false,
// "label": null,
// "letter_grade": null,
// "module_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@edx_introduction",
// "percent": 0,
// "score_earned": 0,
// "score_possible": 0,
// "section_block_id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@d8a6192ade314473a78242dfeedfbf5b",
// "subsection_name": "Demo Course Overview"
// },
// {
// "are_grades_published": true,
// "auto_grade": false,
// "category": null,
// "chapter_name": "Example Week 1: Getting Started",
// "comment": "",
// "detail": "",
// "displayed_value": "0.00",
// "is_graded": false,
// "grade_description": "(0.00/3.00)",
// "is_ag": false,
// "is_average": false,
// "is_manually_graded": false,
// "label": null,
// "letter_grade": null,
// "module_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@19a30717eff543078a5d94ae9d6c18a5",
// "percent": 0,
// "score_earned": 0,
// "score_possible": 3,
// "section_block_id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@interactive_demonstrations",
// "subsection_name": "Lesson 1 - Getting Started"
// },
// {
// "are_grades_published": true,
// "auto_grade": false,
// "category": "Homework",
// "chapter_name": "Example Week 1: Getting Started",
// "comment": "",
// "detail": "",
// "displayed_value": "0.00",
// "is_graded": true,
// "grade_description": "(0.00/11.00)",
// "is_ag": false,
// "is_average": false,
// "is_manually_graded": false,
// "label": "Ex 01",
// "letter_grade": null,
// "module_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@basic_questions",
// "percent": 0,
// "score_earned": 0,
// "score_possible": 11,
// "section_block_id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@interactive_demonstrations",
// "subsection_name": "Homework - Question Styles"
// },
// {
// "are_grades_published": true,
// "auto_grade": false,
// "category": null,
// "chapter_name": "Example Week 2: Get Interactive",
// "comment": "",
// "detail": "",
// "displayed_value": "0.00",
// "is_graded": false,
// "grade_description": "(0.00/0.00)",
// "is_ag": false,
// "is_average": false,
// "is_manually_graded": false,
// "label": null,
// "letter_grade": null,
// "module_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@simulations",
// "percent": 0,
// "score_earned": 0,
// "score_possible": 0,
// "section_block_id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@graded_interactions",
// "subsection_name": "Lesson 2 - Let's Get Interactive!"
// },
// {
// "are_grades_published": true,
// "auto_grade": false,
// "category": "Homework",
// "chapter_name": "Example Week 2: Get Interactive",
// "comment": "",
// "detail": "",
// "displayed_value": "0.00",
// "is_graded": true,
// "grade_description": "(0.00/5.00)",
// "is_ag": false,
// "is_average": false,
// "is_manually_graded": false,
// "label": "Ex 02",
// "letter_grade": null,
// "module_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@graded_simulations",
// "percent": 0,
// "score_earned": 0,
// "score_possible": 5,
// "section_block_id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@graded_interactions",
// "subsection_name": "Homework - Labs and Demos"
// },
// {
// "are_grades_published": true,
// "auto_grade": false,
// "category": null,
// "chapter_name": "Example Week 2: Get Interactive",
// "comment": "",
// "detail": "",
// "displayed_value": "0.00",
// "is_graded": false,
// "grade_description": "(0.00/19.00)",
// "is_ag": false,
// "is_average": false,
// "is_manually_graded": false,
// "label": null,
// "letter_grade": null,
// "module_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@175e76c4951144a29d46211361266e0e",
// "percent": 0,
// "score_earned": 0,
// "score_possible": 19,
// "section_block_id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@graded_interactions",
// "subsection_name": "Homework - Essays"
// },
// {
// "are_grades_published": true,
// "auto_grade": false,
// "category": null,
// "chapter_name": "Example Week 3: Be Social",
// "comment": "",
// "detail": "",
// "displayed_value": "0.00",
// "is_graded": false,
// "grade_description": "(0.00/0.00)",
// "is_ag": false,
// "is_average": false,
// "is_manually_graded": false,
// "label": null,
// "letter_grade": null,
// "module_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@48ecb924d7fe4b66a230137626bfa93e",
// "percent": 0,
// "score_earned": 0,
// "score_possible": 0,
// "section_block_id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@social_integration",
// "subsection_name": "Lesson 3 - Be Social"
// },
// {
// "are_grades_published": true,
// "auto_grade": false,
// "category": null,
// "chapter_name": "Example Week 3: Be Social",
// "comment": "",
// "detail": "",
// "displayed_value": "0.00",
// "is_graded": false,
// "grade_description": "(0.00/0.00)",
// "is_ag": false,
// "is_average": false,
// "is_manually_graded": false,
// "label": null,
// "letter_grade": null,
// "module_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@dbe8fc027bcb4fe9afb744d2e8415855",
// "percent": 0,
// "score_earned": 0,
// "score_possible": 0,
// "section_block_id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@social_integration",
// "subsection_name": "Homework - Find Your Study Buddy"
// },
// {
// "are_grades_published": true,
// "auto_grade": false,
// "category": null,
// "chapter_name": "Example Week 3: Be Social",
// "comment": "",
// "detail": "",
// "displayed_value": "0.00",
// "is_graded": false,
// "grade_description": "(0.00/0.00)",
// "is_ag": false,
// "is_average": false,
// "is_manually_graded": false,
// "label": null,
// "letter_grade": null,
// "module_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@6ab9c442501d472c8ed200e367b4edfa",
// "percent": 0,
// "score_earned": 0,
// "score_possible": 0,
// "section_block_id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@social_integration",
// "subsection_name": "More Ways to Connect"
// },
// {
// "are_grades_published": true,
// "auto_grade": false,
// "category": "Exam",
// "chapter_name": "About Exams and Certificates",
// "comment": "",
// "detail": "",
// "displayed_value": "0.00",
// "is_graded": true,
// "grade_description": "(0.00/6.00)",
// "is_ag": false,
// "is_average": false,
// "is_manually_graded": false,
// "label": null,
// "letter_grade": null,
// "module_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@workflow",
// "percent": 0,
// "score_earned": 0,
// "score_possible": 6,
// "section_block_id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@1414ffd5143b4b508f739b563ab468b7",
// "subsection_name": "edX Exams"
// }
// ],
// "aggregates": {
// "Exam": {
// "score_possible": 6,
// "score_earned": 0
// },
// "Homework": {
// "score_possible": 16,
// "score_earned": 0
// }
// }
// },
// {
// "course_id": "course-v1:edX+DemoX+Demo_Course",
// "email": "staff@example.com",
// "user_id": 9,
// "username": "staff",
// "full_name": "",
// "passed": false,
// "percent": 0,
// "letter_grade": null,
// "progress_page_url": "/courses/course-v1:edX+DemoX+Demo_Course/progress/9/",
// "section_breakdown": [
// {
// "are_grades_published": true,
// "auto_grade": false,
// "category": null,
// "chapter_name": "Introduction",
// "comment": "",
// "detail": "",
// "displayed_value": "0.00",
// "is_graded": false,
// "grade_description": "(0.00/0.00)",
// "is_ag": false,
// "is_average": false,
// "is_manually_graded": false,
// "label": null,
// "letter_grade": null,
// "module_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@edx_introduction",
// "percent": 0,
// "score_earned": 0,
// "score_possible": 0,
// "section_block_id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@d8a6192ade314473a78242dfeedfbf5b",
// "subsection_name": "Demo Course Overview"
// },
// {
// "are_grades_published": true,
// "auto_grade": false,
// "category": null,
// "chapter_name": "Example Week 1: Getting Started",
// "comment": "",
// "detail": "",
// "displayed_value": "0.00",
// "is_graded": false,
// "grade_description": "(0.00/3.00)",
// "is_ag": false,
// "is_average": false,
// "is_manually_graded": false,
// "label": null,
// "letter_grade": null,
// "module_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@19a30717eff543078a5d94ae9d6c18a5",
// "percent": 0,
// "score_earned": 0,
// "score_possible": 3,
// "section_block_id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@interactive_demonstrations",
// "subsection_name": "Lesson 1 - Getting Started"
// },
// {
// "are_grades_published": true,
// "auto_grade": false,
// "category": "Homework",
// "chapter_name": "Example Week 1: Getting Started",
// "comment": "",
// "detail": "",
// "displayed_value": "0.00",
// "is_graded": true,
// "grade_description": "(0.00/11.00)",
// "is_ag": false,
// "is_average": false,
// "is_manually_graded": false,
// "label": "Ex 01",
// "letter_grade": null,
// "module_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@basic_questions",
// "percent": 0,
// "score_earned": 0,
// "score_possible": 11,
// "section_block_id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@interactive_demonstrations",
// "subsection_name": "Homework - Question Styles"
// },
// {
// "are_grades_published": true,
// "auto_grade": false,
// "category": null,
// "chapter_name": "Example Week 2: Get Interactive",
// "comment": "",
// "detail": "",
// "displayed_value": "0.00",
// "is_graded": false,
// "grade_description": "(0.00/0.00)",
// "is_ag": false,
// "is_average": false,
// "is_manually_graded": false,
// "label": null,
// "letter_grade": null,
// "module_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@simulations",
// "percent": 0,
// "score_earned": 0,
// "score_possible": 0,
// "section_block_id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@graded_interactions",
// "subsection_name": "Lesson 2 - Let's Get Interactive!"
// },
// {
// "are_grades_published": true,
// "auto_grade": false,
// "category": "Homework",
// "chapter_name": "Example Week 2: Get Interactive",
// "comment": "",
// "detail": "",
// "displayed_value": "0.00",
// "is_graded": true,
// "grade_description": "(0.00/5.00)",
// "is_ag": false,
// "is_average": false,
// "is_manually_graded": false,
// "label": "Ex 02",
// "letter_grade": null,
// "module_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@graded_simulations",
// "percent": 0,
// "score_earned": 0,
// "score_possible": 5,
// "section_block_id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@graded_interactions",
// "subsection_name": "Homework - Labs and Demos"
// },
// {
// "are_grades_published": true,
// "auto_grade": false,
// "category": null,
// "chapter_name": "Example Week 2: Get Interactive",
// "comment": "",
// "detail": "",
// "displayed_value": "0.00",
// "is_graded": false,
// "grade_description": "(0.00/19.00)",
// "is_ag": false,
// "is_average": false,
// "is_manually_graded": false,
// "label": null,
// "letter_grade": null,
// "module_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@175e76c4951144a29d46211361266e0e",
// "percent": 0,
// "score_earned": 0,
// "score_possible": 19,
// "section_block_id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@graded_interactions",
// "subsection_name": "Homework - Essays"
// },
// {
// "are_grades_published": true,
// "auto_grade": false,
// "category": null,
// "chapter_name": "Example Week 3: Be Social",
// "comment": "",
// "detail": "",
// "displayed_value": "0.00",
// "is_graded": false,
// "grade_description": "(0.00/0.00)",
// "is_ag": false,
// "is_average": false,
// "is_manually_graded": false,
// "label": null,
// "letter_grade": null,
// "module_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@48ecb924d7fe4b66a230137626bfa93e",
// "percent": 0,
// "score_earned": 0,
// "score_possible": 0,
// "section_block_id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@social_integration",
// "subsection_name": "Lesson 3 - Be Social"
// },
// {
// "are_grades_published": true,
// "auto_grade": false,
// "category": null,
// "chapter_name": "Example Week 3: Be Social",
// "comment": "",
// "detail": "",
// "displayed_value": "0.00",
// "is_graded": false,
// "grade_description": "(0.00/0.00)",
// "is_ag": false,
// "is_average": false,
// "is_manually_graded": false,
// "label": null,
// "letter_grade": null,
// "module_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@dbe8fc027bcb4fe9afb744d2e8415855",
// "percent": 0,
// "score_earned": 0,
// "score_possible": 0,
// "section_block_id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@social_integration",
// "subsection_name": "Homework - Find Your Study Buddy"
// },
// {
// "are_grades_published": true,
// "auto_grade": false,
// "category": null,
// "chapter_name": "Example Week 3: Be Social",
// "comment": "",
// "detail": "",
// "displayed_value": "0.00",
// "is_graded": false,
// "grade_description": "(0.00/0.00)",
// "is_ag": false,
// "is_average": false,
// "is_manually_graded": false,
// "label": null,
// "letter_grade": null,
// "module_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@6ab9c442501d472c8ed200e367b4edfa",
// "percent": 0,
// "score_earned": 0,
// "score_possible": 0,
// "section_block_id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@social_integration",
// "subsection_name": "More Ways to Connect"
// },
// {
// "are_grades_published": true,
// "auto_grade": false,
// "category": "Exam",
// "chapter_name": "About Exams and Certificates",
// "comment": "",
// "detail": "",
// "displayed_value": "0.00",
// "is_graded": true,
// "grade_description": "(0.00/6.00)",
// "is_ag": false,
// "is_average": false,
// "is_manually_graded": false,
// "label": null,
// "letter_grade": null,
// "module_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@workflow",
// "percent": 0,
// "score_earned": 0,
// "score_possible": 6,
// "section_block_id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@1414ffd5143b4b508f739b563ab468b7",
// "subsection_name": "edX Exams"
// },
// {
// "are_grades_published": true,
// "auto_grade": false,
// "category": null,
// "chapter_name": "holding section",
// "comment": "",
// "detail": "",
// "displayed_value": "0.00",
// "is_graded": false,
// "grade_description": "(0.00/0.00)",
// "is_ag": false,
// "is_average": false,
// "is_manually_graded": false,
// "label": null,
// "letter_grade": null,
// "module_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@07bc32474380492cb34f76e5f9d9a135",
// "percent": 0,
// "score_earned": 0,
// "score_possible": 0,
// "section_block_id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@9fca584977d04885bc911ea76a9ef29e",
// "subsection_name": "New Subsection"
// }
// ],
// "aggregates": {
// "Exam": {
// "score_possible": 6,
// "score_earned": 0
// },
// "Homework": {
// "score_possible": 16,
// "score_earned": 0
// }
// }
// }
// ]
// };

View File

@@ -1,31 +0,0 @@
import {
GET_POSTS,
STARTED_FETCHING_POSTS,
FINISHED_FETCHING_POSTS,
} from '../constants/actionTypes/posts';
const posts = (state = { posts: [], startedFetching: false, finishedFetching: false }, action) => {
switch (action.type) {
case GET_POSTS:
return {
...state,
posts: action.posts,
};
case STARTED_FETCHING_POSTS:
return {
...state,
startedFetching: true,
finishedFetching: false,
};
case FINISHED_FETCHING_POSTS:
return {
...state,
startedFetching: false,
finishedFetching: true,
};
default:
return state;
}
};
export default posts;

View File

@@ -1,45 +0,0 @@
import posts from './posts';
import {
GET_POSTS,
STARTED_FETCHING_POSTS,
FINISHED_FETCHING_POSTS,
} from '../constants/actionTypes/posts';
const initialState = {
posts: [],
startedFetching: false,
finishedFetching: false,
};
describe('posts reducer', () => {
it('has initial state', () => {
expect(posts(undefined, {})).toEqual(initialState);
});
it('adds posts', () => {
const fetchedPosts = [1, 2, 3];
const expected = {
...initialState,
posts: fetchedPosts,
};
expect(posts(undefined, { type: GET_POSTS, posts: fetchedPosts })).toEqual(expected);
});
it('updates started fetching posts state', () => {
const expected = {
...initialState,
startedFetching: true,
finishedFetching: false,
};
expect(posts(undefined, { type: STARTED_FETCHING_POSTS })).toEqual(expected);
});
it('updates finished fetching posts state', () => {
const expected = {
...initialState,
startedFetching: false,
finishedFetching: true,
};
expect(posts(undefined, { type: FINISHED_FETCHING_POSTS })).toEqual(expected);
});
});

View File

@@ -4,6 +4,7 @@ import ReactDOM from 'react-dom';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import { Provider } from 'react-redux';
import apiClient from './data/apiClient';
import GradebookPage from './containers/GradebookPage';
import store from './data/store';
import './App.scss';
@@ -20,4 +21,6 @@ const App = () => (
</Provider>
);
ReactDOM.render(<App />, document.getElementById('root'));
if (apiClient.ensurePublicOrAuthencationAndCookies(window.location.pathname)) {
ReactDOM.render(<App />, document.getElementById('root'));
}