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 "~@edx/paragon/src/SearchField/SearchField";
|
||||
@import "./components/Gradebook/gradebook";
|
||||
|
||||
@import "./components/Gradebook/gradebook";
|
||||
@import "./components/Gradebook/footer";
|
||||
@import "./components/Header/header";
|
||||
|
||||
@@ -299,29 +299,25 @@ export default class Gradebook extends React.Component {
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
{(this.props.tracks.length > 0 || this.props.cohorts.length > 0) &&
|
||||
<div className="student-filters">
|
||||
<span className="label">
|
||||
Student Groups:
|
||||
</span>
|
||||
{this.props.tracks.length > 0 &&
|
||||
<InputSelect
|
||||
name="Tracks"
|
||||
value={this.mapSelectedTrackEntry(this.props.selectedTrack)}
|
||||
options={this.mapTracksEntries(this.props.tracks)}
|
||||
onChange={this.updateTracks}
|
||||
/>
|
||||
}
|
||||
{this.props.cohorts.length > 0 &&
|
||||
<InputSelect
|
||||
name="Cohorts"
|
||||
value={this.mapSelectedCohortEntry(this.props.selectedCohort)}
|
||||
options={this.mapCohortsEntries(this.props.cohorts)}
|
||||
onChange={this.updateCohorts}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<div className="student-filters">
|
||||
<span className="label">
|
||||
Student Groups:
|
||||
</span>
|
||||
<InputSelect
|
||||
name="Tracks"
|
||||
disabled={this.props.tracks.length === 0}
|
||||
value={this.mapSelectedTrackEntry(this.props.selectedTrack)}
|
||||
options={this.mapTracksEntries(this.props.tracks)}
|
||||
onChange={this.updateTracks}
|
||||
/>
|
||||
<InputSelect
|
||||
name="Cohorts"
|
||||
disabled={this.props.cohorts.length === 0}
|
||||
value={this.mapSelectedCohortEntry(this.props.selectedCohort)}
|
||||
options={this.mapCohortsEntries(this.props.cohorts)}
|
||||
onChange={this.updateCohorts}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ marginLeft: '10px', marginBottom: '10px' }}>
|
||||
@@ -369,6 +365,7 @@ export default class Gradebook extends React.Component {
|
||||
<Modal
|
||||
open={this.state.modalOpen}
|
||||
title="Edit Grades"
|
||||
closeText="Cancel"
|
||||
body={(
|
||||
<div>
|
||||
<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' }]}
|
||||
data={this.state.modalModel}
|
||||
/>
|
||||
<div>Note: Once you save, your changes will be visible to students.</div>
|
||||
</div>
|
||||
)}
|
||||
buttons={[
|
||||
<Button
|
||||
label="Edit Grade"
|
||||
label="Save Grade"
|
||||
buttonType="primary"
|
||||
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 { Hyperlink } from '@edx/paragon';
|
||||
import { Hyperlink, Icon } from '@edx/paragon';
|
||||
import { configuration } from '../../config';
|
||||
|
||||
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" />
|
||||
<div />
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
import {
|
||||
GOT_ROLES,
|
||||
ERROR_FETCHING_ROLES
|
||||
} from '../constants/actionTypes/roles';
|
||||
GOT_ROLES,
|
||||
ERROR_FETCHING_ROLES,
|
||||
} from '../constants/actionTypes/roles';
|
||||
import { fetchGrades } from './grades';
|
||||
import { fetchTracks } from './tracks';
|
||||
import { fetchCohorts } from './cohorts';
|
||||
import { fetchAssignmentTypes } from './assignmentTypes';
|
||||
import LmsApiService from '../services/LmsApiService';
|
||||
|
||||
const allowed_roles = ['staff', 'instructor', 'support'];
|
||||
const allowedRoles = ['staff', 'instructor', 'support'];
|
||||
|
||||
const gotRoles = canUserViewGradebook => ({ type: GOT_ROLES, canUserViewGradebook });
|
||||
const errorFetchingRoles = () => ({type: ERROR_FETCHING_ROLES });
|
||||
const errorFetchingRoles = () => ({ type: ERROR_FETCHING_ROLES });
|
||||
|
||||
const getRoles = (courseId, urlQuery) => (
|
||||
(dispatch) => {
|
||||
return LmsApiService.fetchUserRoles(courseId)
|
||||
.then(response => response.data)
|
||||
.then(roles => {
|
||||
var canUserViewGradebook = roles.some(role => (role.course_id === courseId) && allowed_roles.includes(role.role));
|
||||
dispatch(gotRoles(canUserViewGradebook));
|
||||
if(canUserViewGradebook){
|
||||
dispatch(fetchGrades(courseId, urlQuery.cohort, urlQuery.track));
|
||||
dispatch(fetchTracks(courseId));
|
||||
dispatch(fetchCohorts(courseId));
|
||||
dispatch(fetchAssignmentTypes(courseId));
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
dispatch(errorFetchingRoles())
|
||||
});
|
||||
});
|
||||
dispatch => LmsApiService.fetchUserRoles(courseId)
|
||||
.then(response => response.data)
|
||||
.then((response) => {
|
||||
const canUserViewGradebook = response.is_staff
|
||||
|| (response.roles.some(role => (role.course_id === courseId)
|
||||
&& allowedRoles.includes(role.role)));
|
||||
dispatch(gotRoles(canUserViewGradebook));
|
||||
if (canUserViewGradebook) {
|
||||
dispatch(fetchGrades(courseId, urlQuery.cohort, urlQuery.track));
|
||||
dispatch(fetchTracks(courseId));
|
||||
dispatch(fetchCohorts(courseId));
|
||||
dispatch(fetchAssignmentTypes(courseId));
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
dispatch(errorFetchingRoles());
|
||||
}));
|
||||
|
||||
export {
|
||||
getRoles,
|
||||
errorFetchingRoles,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -18,11 +18,16 @@ import { STARTED_FETCHING_ASSIGNMENT_TYPES } from '../constants/actionTypes/assi
|
||||
const mockStore = configureMockStore([thunk]);
|
||||
const axiosMock = new MockAdapter(apiClient);
|
||||
|
||||
const rolesUrl = `${configuration.LMS_BASE_URL}/api/enrollment/v1/roles/`;
|
||||
|
||||
const course1Id = 'course-v1:edX+DemoX+Demo_Course';
|
||||
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) {
|
||||
return {
|
||||
course_id: courseId,
|
||||
@@ -52,7 +57,25 @@ describe('actions', () => {
|
||||
];
|
||||
const store = mockStore();
|
||||
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(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
@@ -66,7 +89,7 @@ describe('actions', () => {
|
||||
const store = mockStore();
|
||||
|
||||
axiosMock.onGet(rolesUrl)
|
||||
.replyOnce(200, JSON.stringify([course1DummyRole, course2StaffRole]));
|
||||
.replyOnce(200, JSON.stringify(makeRoleListObj([course1DummyRole, course2StaffRole], false)));
|
||||
|
||||
return store.dispatch(getRoles(course1Id, urlParams)).then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
@@ -80,7 +103,25 @@ describe('actions', () => {
|
||||
const store = mockStore();
|
||||
|
||||
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(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
|
||||
@@ -60,9 +60,9 @@ class LmsApiService {
|
||||
return apiClient.get(assignmentTypesUrl);
|
||||
}
|
||||
|
||||
static fetchUserRoles(){
|
||||
var rolesUrl = `${LmsApiService.baseUrl}/api/enrollment/v1/roles/`;
|
||||
return apiClient.get(rolesUrl)
|
||||
static fetchUserRoles(courseId) {
|
||||
const rolesUrl = `${LmsApiService.baseUrl}/api/enrollment/v1/roles/?course_id=${encodeURIComponent(courseId)}`;
|
||||
return apiClient.get(rolesUrl);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user