Compare commits
10 Commits
v1.4.5
...
rir/header
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5b025aef5 | ||
|
|
0b507c558a | ||
|
|
86092f22b3 | ||
|
|
c8cb07228f | ||
|
|
a1946e7bc4 | ||
|
|
01d80e0fff | ||
|
|
e6da087e83 | ||
|
|
ac5eaed5cb | ||
|
|
88997ca242 | ||
|
|
d5daf9086f |
@@ -5,6 +5,8 @@ $fa-font-path: "~font-awesome/fonts";
|
|||||||
@import "~font-awesome/scss/font-awesome";
|
@import "~font-awesome/scss/font-awesome";
|
||||||
|
|
||||||
@import "~@edx/paragon/src/SearchField/SearchField";
|
@import "~@edx/paragon/src/SearchField/SearchField";
|
||||||
|
@import "./components/Gradebook/gradebook";
|
||||||
|
|
||||||
@import "./components/Gradebook/gradebook";
|
@import "./components/Gradebook/gradebook";
|
||||||
@import "./components/Gradebook/footer";
|
@import "./components/Gradebook/footer";
|
||||||
|
@import "./components/Header/header";
|
||||||
|
|||||||
@@ -299,29 +299,25 @@ export default class Gradebook extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
{(this.props.tracks.length > 0 || this.props.cohorts.length > 0) &&
|
<div className="student-filters">
|
||||||
<div className="student-filters">
|
<span className="label">
|
||||||
<span className="label">
|
Student Groups:
|
||||||
Student Groups:
|
</span>
|
||||||
</span>
|
<InputSelect
|
||||||
{this.props.tracks.length > 0 &&
|
name="Tracks"
|
||||||
<InputSelect
|
disabled={this.props.tracks.length === 0}
|
||||||
name="Tracks"
|
value={this.mapSelectedTrackEntry(this.props.selectedTrack)}
|
||||||
value={this.mapSelectedTrackEntry(this.props.selectedTrack)}
|
options={this.mapTracksEntries(this.props.tracks)}
|
||||||
options={this.mapTracksEntries(this.props.tracks)}
|
onChange={this.updateTracks}
|
||||||
onChange={this.updateTracks}
|
/>
|
||||||
/>
|
<InputSelect
|
||||||
}
|
name="Cohorts"
|
||||||
{this.props.cohorts.length > 0 &&
|
disabled={this.props.cohorts.length === 0}
|
||||||
<InputSelect
|
value={this.mapSelectedCohortEntry(this.props.selectedCohort)}
|
||||||
name="Cohorts"
|
options={this.mapCohortsEntries(this.props.cohorts)}
|
||||||
value={this.mapSelectedCohortEntry(this.props.selectedCohort)}
|
onChange={this.updateCohorts}
|
||||||
options={this.mapCohortsEntries(this.props.cohorts)}
|
/>
|
||||||
onChange={this.updateCohorts}
|
</div>
|
||||||
/>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div style={{ marginLeft: '10px', marginBottom: '10px' }}>
|
<div style={{ marginLeft: '10px', marginBottom: '10px' }}>
|
||||||
@@ -369,6 +365,7 @@ export default class Gradebook extends React.Component {
|
|||||||
<Modal
|
<Modal
|
||||||
open={this.state.modalOpen}
|
open={this.state.modalOpen}
|
||||||
title="Edit Grades"
|
title="Edit Grades"
|
||||||
|
closeText="Cancel"
|
||||||
body={(
|
body={(
|
||||||
<div>
|
<div>
|
||||||
<h3>{this.state.modalModel[0].assignmentName}</h3>
|
<h3>{this.state.modalModel[0].assignmentName}</h3>
|
||||||
@@ -376,11 +373,12 @@ export default class Gradebook extends React.Component {
|
|||||||
columns={[{ label: 'Username', key: 'username' }, { label: 'Current grade', key: 'currentGrade' }, { label: 'Adjusted grade', key: 'adjustedGrade' }]}
|
columns={[{ label: 'Username', key: 'username' }, { label: 'Current grade', key: 'currentGrade' }, { label: 'Adjusted grade', key: 'adjustedGrade' }]}
|
||||||
data={this.state.modalModel}
|
data={this.state.modalModel}
|
||||||
/>
|
/>
|
||||||
|
<div>Note: Once you save, your changes will be visible to students.</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
buttons={[
|
buttons={[
|
||||||
<Button
|
<Button
|
||||||
label="Edit Grade"
|
label="Save Grade"
|
||||||
buttonType="primary"
|
buttonType="primary"
|
||||||
onClick={this.handleAdjustedGradeClick}
|
onClick={this.handleAdjustedGradeClick}
|
||||||
/>,
|
/>,
|
||||||
|
|||||||
24
src/components/Header/_header.scss
Normal file
24
src/components/Header/_header.scss
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
.color-gray-dark {
|
||||||
|
color: #767676;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weight-bold {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-16 {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-bottom-blue {
|
||||||
|
border-bottom: 1px solid #0075b4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-bottom-gray {
|
||||||
|
border-bottom: 1px solid #e7e7e7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link::after {
|
||||||
|
content: '\00BB';
|
||||||
|
padding-left: 4px;
|
||||||
|
}
|
||||||
0
src/components/Header/header.scss
Normal file
0
src/components/Header/header.scss
Normal file
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Hyperlink } from '@edx/paragon';
|
import { Hyperlink, Icon } from '@edx/paragon';
|
||||||
|
import { configuration } from '../../config';
|
||||||
|
|
||||||
import EdxLogo from '../../../assets/edx-sm.png';
|
import EdxLogo from '../../../assets/edx-sm.png';
|
||||||
|
|
||||||
@@ -24,6 +25,15 @@ export default class Header extends React.Component {
|
|||||||
<Hyperlink content={this.renderLogo()} destination="https://www.edx.org" />
|
<Hyperlink content={this.renderLogo()} destination="https://www.edx.org" />
|
||||||
<div />
|
<div />
|
||||||
</header>
|
</header>
|
||||||
|
{this.state.mobileNavOpen &&
|
||||||
|
<nav className="d-flex flex-column weight-bold size-16">
|
||||||
|
<a href="https://www.google.com" className="nav-link border-bottom-gray">Rick</a>
|
||||||
|
<a href="https://www.google.com" className="nav-link border-bottom-gray">Alex</a>
|
||||||
|
<a href="https://www.google.com" className="nav-link border-bottom-gray">Jasen</a>
|
||||||
|
<a href="https://www.google.com" className="nav-link border-bottom-gray">Doug</a>
|
||||||
|
<a href="https://www.google.com" className="nav-link border-bottom-gray">Simon</a>
|
||||||
|
</nav>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,36 @@
|
|||||||
import {
|
import {
|
||||||
GOT_ROLES,
|
GOT_ROLES,
|
||||||
ERROR_FETCHING_ROLES
|
ERROR_FETCHING_ROLES,
|
||||||
} from '../constants/actionTypes/roles';
|
} from '../constants/actionTypes/roles';
|
||||||
import { fetchGrades } from './grades';
|
import { fetchGrades } from './grades';
|
||||||
import { fetchTracks } from './tracks';
|
import { fetchTracks } from './tracks';
|
||||||
import { fetchCohorts } from './cohorts';
|
import { fetchCohorts } from './cohorts';
|
||||||
import { fetchAssignmentTypes } from './assignmentTypes';
|
import { fetchAssignmentTypes } from './assignmentTypes';
|
||||||
import LmsApiService from '../services/LmsApiService';
|
import LmsApiService from '../services/LmsApiService';
|
||||||
|
|
||||||
const allowed_roles = ['staff', 'instructor', 'support'];
|
const allowedRoles = ['staff', 'instructor', 'support'];
|
||||||
|
|
||||||
const gotRoles = canUserViewGradebook => ({ type: GOT_ROLES, canUserViewGradebook });
|
const gotRoles = canUserViewGradebook => ({ type: GOT_ROLES, canUserViewGradebook });
|
||||||
const errorFetchingRoles = () => ({type: ERROR_FETCHING_ROLES });
|
const errorFetchingRoles = () => ({ type: ERROR_FETCHING_ROLES });
|
||||||
|
|
||||||
const getRoles = (courseId, urlQuery) => (
|
const getRoles = (courseId, urlQuery) => (
|
||||||
(dispatch) => {
|
dispatch => LmsApiService.fetchUserRoles(courseId)
|
||||||
return LmsApiService.fetchUserRoles(courseId)
|
.then(response => response.data)
|
||||||
.then(response => response.data)
|
.then((response) => {
|
||||||
.then(roles => {
|
const canUserViewGradebook = response.is_staff
|
||||||
var canUserViewGradebook = roles.some(role => (role.course_id === courseId) && allowed_roles.includes(role.role));
|
|| (response.roles.some(role => (role.course_id === courseId)
|
||||||
dispatch(gotRoles(canUserViewGradebook));
|
&& allowedRoles.includes(role.role)));
|
||||||
if(canUserViewGradebook){
|
dispatch(gotRoles(canUserViewGradebook));
|
||||||
dispatch(fetchGrades(courseId, urlQuery.cohort, urlQuery.track));
|
if (canUserViewGradebook) {
|
||||||
dispatch(fetchTracks(courseId));
|
dispatch(fetchGrades(courseId, urlQuery.cohort, urlQuery.track));
|
||||||
dispatch(fetchCohorts(courseId));
|
dispatch(fetchTracks(courseId));
|
||||||
dispatch(fetchAssignmentTypes(courseId));
|
dispatch(fetchCohorts(courseId));
|
||||||
}
|
dispatch(fetchAssignmentTypes(courseId));
|
||||||
})
|
}
|
||||||
.catch(() => {
|
})
|
||||||
dispatch(errorFetchingRoles())
|
.catch(() => {
|
||||||
});
|
dispatch(errorFetchingRoles());
|
||||||
});
|
}));
|
||||||
|
|
||||||
export {
|
export {
|
||||||
getRoles,
|
getRoles,
|
||||||
|
|||||||
@@ -18,11 +18,16 @@ import { STARTED_FETCHING_ASSIGNMENT_TYPES } from '../constants/actionTypes/assi
|
|||||||
const mockStore = configureMockStore([thunk]);
|
const mockStore = configureMockStore([thunk]);
|
||||||
const axiosMock = new MockAdapter(apiClient);
|
const axiosMock = new MockAdapter(apiClient);
|
||||||
|
|
||||||
const rolesUrl = `${configuration.LMS_BASE_URL}/api/enrollment/v1/roles/`;
|
|
||||||
|
|
||||||
const course1Id = 'course-v1:edX+DemoX+Demo_Course';
|
const course1Id = 'course-v1:edX+DemoX+Demo_Course';
|
||||||
const course2Id = 'course-v1:edX+DemoX+Demo_Course_2';
|
const course2Id = 'course-v1:edX+DemoX+Demo_Course_2';
|
||||||
|
const rolesUrl = `${configuration.LMS_BASE_URL}/api/enrollment/v1/roles/?course_id=${encodeURIComponent(course1Id)}`;
|
||||||
|
|
||||||
|
function makeRoleListObj(roles, isGlobalStaff){
|
||||||
|
return {
|
||||||
|
roles: roles,
|
||||||
|
is_staff: isGlobalStaff,
|
||||||
|
}
|
||||||
|
}
|
||||||
function makeRoleObj(courseId, role) {
|
function makeRoleObj(courseId, role) {
|
||||||
return {
|
return {
|
||||||
course_id: courseId,
|
course_id: courseId,
|
||||||
@@ -52,7 +57,25 @@ describe('actions', () => {
|
|||||||
];
|
];
|
||||||
const store = mockStore();
|
const store = mockStore();
|
||||||
axiosMock.onGet(rolesUrl)
|
axiosMock.onGet(rolesUrl)
|
||||||
.replyOnce(200, JSON.stringify([course1StaffRole, course2DummyRole]));
|
.replyOnce(200, JSON.stringify(makeRoleListObj([course1StaffRole, course2DummyRole], false)));
|
||||||
|
|
||||||
|
return store.dispatch(getRoles(course1Id, urlParams)).then(() => {
|
||||||
|
expect(store.getActions()).toEqual(expectedActions);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('dispatches got_roles action and other actions after fetching irrelevent roles but user is global staff', () => {
|
||||||
|
const expectedActions = [
|
||||||
|
{ type: GOT_ROLES, canUserViewGradebook: true },
|
||||||
|
{ type: STARTED_FETCHING_GRADES },
|
||||||
|
{ type: STARTED_FETCHING_TRACKS },
|
||||||
|
{ type: STARTED_FETCHING_COHORTS },
|
||||||
|
{ type: STARTED_FETCHING_ASSIGNMENT_TYPES },
|
||||||
|
];
|
||||||
|
const store = mockStore();
|
||||||
|
|
||||||
|
axiosMock.onGet(rolesUrl)
|
||||||
|
.replyOnce(200, JSON.stringify(makeRoleListObj([course1DummyRole, course2DummyRole], true)));
|
||||||
|
|
||||||
return store.dispatch(getRoles(course1Id, urlParams)).then(() => {
|
return store.dispatch(getRoles(course1Id, urlParams)).then(() => {
|
||||||
expect(store.getActions()).toEqual(expectedActions);
|
expect(store.getActions()).toEqual(expectedActions);
|
||||||
@@ -66,7 +89,7 @@ describe('actions', () => {
|
|||||||
const store = mockStore();
|
const store = mockStore();
|
||||||
|
|
||||||
axiosMock.onGet(rolesUrl)
|
axiosMock.onGet(rolesUrl)
|
||||||
.replyOnce(200, JSON.stringify([course1DummyRole, course2StaffRole]));
|
.replyOnce(200, JSON.stringify(makeRoleListObj([course1DummyRole, course2StaffRole], false)));
|
||||||
|
|
||||||
return store.dispatch(getRoles(course1Id, urlParams)).then(() => {
|
return store.dispatch(getRoles(course1Id, urlParams)).then(() => {
|
||||||
expect(store.getActions()).toEqual(expectedActions);
|
expect(store.getActions()).toEqual(expectedActions);
|
||||||
@@ -80,7 +103,25 @@ describe('actions', () => {
|
|||||||
const store = mockStore();
|
const store = mockStore();
|
||||||
|
|
||||||
axiosMock.onGet(rolesUrl)
|
axiosMock.onGet(rolesUrl)
|
||||||
.replyOnce(200, JSON.stringify([]));
|
.replyOnce(200, JSON.stringify(makeRoleListObj([], false)));
|
||||||
|
|
||||||
|
return store.dispatch(getRoles(course1Id, urlParams)).then(() => {
|
||||||
|
expect(store.getActions()).toEqual(expectedActions);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('dispatches got_roles action and other actions after fetching empty roles but user is global staff', () => {
|
||||||
|
const expectedActions = [
|
||||||
|
{ type: GOT_ROLES, canUserViewGradebook: true },
|
||||||
|
{ type: STARTED_FETCHING_GRADES },
|
||||||
|
{ type: STARTED_FETCHING_TRACKS },
|
||||||
|
{ type: STARTED_FETCHING_COHORTS },
|
||||||
|
{ type: STARTED_FETCHING_ASSIGNMENT_TYPES },
|
||||||
|
];
|
||||||
|
const store = mockStore();
|
||||||
|
|
||||||
|
axiosMock.onGet(rolesUrl)
|
||||||
|
.replyOnce(200, JSON.stringify(makeRoleListObj([], true)));
|
||||||
|
|
||||||
return store.dispatch(getRoles(course1Id, urlParams)).then(() => {
|
return store.dispatch(getRoles(course1Id, urlParams)).then(() => {
|
||||||
expect(store.getActions()).toEqual(expectedActions);
|
expect(store.getActions()).toEqual(expectedActions);
|
||||||
|
|||||||
@@ -60,9 +60,9 @@ class LmsApiService {
|
|||||||
return apiClient.get(assignmentTypesUrl);
|
return apiClient.get(assignmentTypesUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
static fetchUserRoles(){
|
static fetchUserRoles(courseId) {
|
||||||
var rolesUrl = `${LmsApiService.baseUrl}/api/enrollment/v1/roles/`;
|
const rolesUrl = `${LmsApiService.baseUrl}/api/enrollment/v1/roles/?course_id=${encodeURIComponent(courseId)}`;
|
||||||
return apiClient.get(rolesUrl)
|
return apiClient.get(rolesUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user