diff --git a/src/containers/GradebookPage/index.jsx b/src/containers/GradebookPage/index.jsx
index 3e4dc4d..b0e437c 100644
--- a/src/containers/GradebookPage/index.jsx
+++ b/src/containers/GradebookPage/index.jsx
@@ -13,6 +13,7 @@ import {
import { fetchCohorts } from '../../data/actions/cohorts';
import { fetchTracks } from '../../data/actions/tracks';
import { fetchAssignmentTypes } from '../../data/actions/assignmentTypes';
+import { getRoles } from '../../data/actions/roles';
const mapStateToProps = state => (
{
@@ -28,10 +29,21 @@ const mapStateToProps = state => (
nextPage: state.grades.nextPage,
assignmnetTypes: state.assignmentTypes.results,
areGradesFrozen: state.assignmentTypes.areGradesFrozen,
- showSpinner: state.grades.showSpinner,
+ showSpinner: shouldShowSpinner(state),
+ canUserViewGradebook: state.roles.canUserViewGradebook
}
);
+function shouldShowSpinner (state) {
+ if (state.roles.canUserViewGradebook === true){
+ return state.grades.showSpinner;
+ } else if (state.roles.canUserViewGradebook === false){
+ return false;
+ } else { // canUserViewGradebook === null
+ return true;
+ }
+}
+
const mapDispatchToProps = dispatch => (
{
getUserGrades: (courseId, cohort, track) => {
@@ -64,6 +76,9 @@ const mapDispatchToProps = dispatch => (
updateBanner: (showSuccess) => {
dispatch(updateBanner(showSuccess));
},
+ getRoles: (matchParams, urlQuery) => {
+ dispatch(getRoles(matchParams, urlQuery));
+ },
}
);
diff --git a/src/data/actions/roles.js b/src/data/actions/roles.js
new file mode 100644
index 0000000..af27ef5
--- /dev/null
+++ b/src/data/actions/roles.js
@@ -0,0 +1,38 @@
+import {
+ 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 gotRoles = canUserViewGradebook => ({ type: GOT_ROLES, canUserViewGradebook });
+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())
+ });
+ });
+
+export {
+ getRoles,
+ errorFetchingRoles,
+};
\ No newline at end of file
diff --git a/src/data/actions/roles.test.js b/src/data/actions/roles.test.js
new file mode 100644
index 0000000..0e5bc5d
--- /dev/null
+++ b/src/data/actions/roles.test.js
@@ -0,0 +1,103 @@
+import configureMockStore from 'redux-mock-store';
+import MockAdapter from 'axios-mock-adapter';
+import thunk from 'redux-thunk';
+
+import apiClient from '../apiClient';
+import { configuration } from '../../config';
+import { getRoles } from './roles';
+import {
+ GOT_ROLES,
+ ERROR_FETCHING_ROLES,
+} from '../constants/actionTypes/roles';
+import { STARTED_FETCHING_GRADES } from '../constants/actionTypes/grades';
+import { STARTED_FETCHING_TRACKS } from '../constants/actionTypes/tracks';
+import { STARTED_FETCHING_COHORTS } from '../constants/actionTypes/cohorts';
+import { STARTED_FETCHING_ASSIGNMENT_TYPES } from '../constants/actionTypes/assignmentTypes';
+
+
+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';
+
+function makeRoleObj(courseId, role) {
+ return {
+ course_id: courseId,
+ role: role,
+ }
+};
+
+const course1StaffRole = makeRoleObj(course1Id, "staff");
+const course1DummyRole = makeRoleObj(course1Id, "dummy");
+const course2StaffRole = makeRoleObj(course2Id, "staff");
+const course2DummyRole = makeRoleObj(course2Id, "dummy");
+const urlParams = { cohort: null, track: null };
+
+describe('actions', () => {
+ afterEach(() => {
+ axiosMock.reset();
+ });
+
+ describe('getRoles', () => {
+ it('dispatches got_roles action and subsequent actions after fetching role that allows gradebook', () => {
+ 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([course1StaffRole, course2DummyRole]));
+
+ return store.dispatch(getRoles(course1Id, urlParams)).then(() => {
+ expect(store.getActions()).toEqual(expectedActions);
+ });
+ });
+
+ it('dispatches got_roles action and no other actions after fetching role that disallows gradebook', () => {
+ const expectedActions = [
+ { type: GOT_ROLES, canUserViewGradebook: false },
+ ];
+ const store = mockStore();
+
+ axiosMock.onGet(rolesUrl)
+ .replyOnce(200, JSON.stringify([course1DummyRole, course2StaffRole]));
+
+ return store.dispatch(getRoles(course1Id, urlParams)).then(() => {
+ expect(store.getActions()).toEqual(expectedActions);
+ });
+ });
+
+ it('dispatches got_roles action and no other actions after fetching empty roles', () => {
+ const expectedActions = [
+ { type: GOT_ROLES, canUserViewGradebook: false },
+ ];
+ const store = mockStore();
+
+ axiosMock.onGet(rolesUrl)
+ .replyOnce(200, JSON.stringify([]));
+
+ return store.dispatch(getRoles(course1Id, urlParams)).then(() => {
+ expect(store.getActions()).toEqual(expectedActions);
+ });
+ });
+
+ it('dispatches error action after getting an error when trying to get roles', () => {
+ const expectedActions = [
+ { type: ERROR_FETCHING_ROLES },
+ ];
+ const store = mockStore();
+
+ axiosMock.onGet(rolesUrl).replyOnce(400);
+
+ return store.dispatch(getRoles(course1Id, urlParams)).then(() => {
+ expect(store.getActions()).toEqual(expectedActions);
+ });
+ });
+ });
+});
diff --git a/src/data/constants/actionTypes/roles.js b/src/data/constants/actionTypes/roles.js
new file mode 100644
index 0000000..2a91200
--- /dev/null
+++ b/src/data/constants/actionTypes/roles.js
@@ -0,0 +1,7 @@
+const GOT_ROLES = 'GOT_ROLES';
+const ERROR_FETCHING_ROLES = 'ERROR_FETCHING_ROLES'
+
+export {
+ GOT_ROLES,
+ ERROR_FETCHING_ROLES,
+};
diff --git a/src/data/reducers/index.js b/src/data/reducers/index.js
index 90ebb3e..392f013 100755
--- a/src/data/reducers/index.js
+++ b/src/data/reducers/index.js
@@ -4,12 +4,14 @@ import cohorts from './cohorts';
import grades from './grades';
import tracks from './tracks';
import assignmentTypes from './assignmentTypes';
+import roles from './roles';
const rootReducer = combineReducers({
grades,
cohorts,
tracks,
assignmentTypes,
+ roles,
});
export default rootReducer;
diff --git a/src/data/reducers/roles.js b/src/data/reducers/roles.js
new file mode 100644
index 0000000..2397dc4
--- /dev/null
+++ b/src/data/reducers/roles.js
@@ -0,0 +1,26 @@
+import {
+ GOT_ROLES,
+ ERROR_FETCHING_ROLES,
+ } from '../constants/actionTypes/roles';
+
+ const initialState = {
+ canUserViewGradebook: null,
+ };
+
+ const roles = (state = initialState, action) => {
+ switch (action.type) {
+ case GOT_ROLES:
+ return {
+ ...state,
+ canUserViewGradebook: action.canUserViewGradebook,
+ };
+ case ERROR_FETCHING_ROLES:
+ return {
+ ...state,
+ canUserViewGradebook: false,
+ };
+ default:
+ return state;
+ }};
+
+ export default roles;
\ No newline at end of file
diff --git a/src/data/reducers/roles.test.js b/src/data/reducers/roles.test.js
new file mode 100644
index 0000000..cb29221
--- /dev/null
+++ b/src/data/reducers/roles.test.js
@@ -0,0 +1,47 @@
+import roles from './roles';
+import {
+ ERROR_FETCHING_ROLES,
+ GOT_ROLES,
+} from '../constants/actionTypes/roles';
+
+const initialState = {
+ canUserViewGradebook: null,
+};
+
+describe('tracks reducer', () => {
+ it('has initial state', () => {
+ expect(roles(undefined, {})).toEqual(initialState);
+ });
+
+ it('updates canUserViewGradebook to true', () => {
+ const expected = {
+ ...initialState,
+ canUserViewGradebook: true
+ };
+ expect(roles(undefined, {
+ type: GOT_ROLES,
+ canUserViewGradebook: true,
+ })).toEqual(expected);
+ });
+
+ it('updates canUserViewGradebook to false', () => {
+ const expected = {
+ ...initialState,
+ canUserViewGradebook: false
+ };
+ expect(roles(undefined, {
+ type: GOT_ROLES,
+ canUserViewGradebook: false,
+ })).toEqual(expected);
+ });
+
+ it('updates fetch roles failure state', () => {
+ const expected = {
+ ...initialState,
+ canUserViewGradebook: false,
+ };
+ expect(roles(undefined, {
+ type: ERROR_FETCHING_ROLES,
+ })).toEqual(expected);
+ });
+});
diff --git a/src/data/services/LmsApiService.js b/src/data/services/LmsApiService.js
index 6124a6f..67cd082 100644
--- a/src/data/services/LmsApiService.js
+++ b/src/data/services/LmsApiService.js
@@ -59,6 +59,11 @@ class LmsApiService {
const assignmentTypesUrl = `${LmsApiService.baseUrl}/api/grades/v1/gradebook/${courseId}/grading-info?graded_only=true`;
return apiClient.get(assignmentTypesUrl);
}
+
+ static fetchUserRoles(){
+ var rolesUrl = `${LmsApiService.baseUrl}/api/enrollment/v1/roles/`;
+ return apiClient.get(rolesUrl)
+ }
}
export default LmsApiService;
diff --git a/src/index.jsx b/src/index.jsx
index 4a27edb..7972f75 100755
--- a/src/index.jsx
+++ b/src/index.jsx
@@ -11,6 +11,8 @@ import Header from './components/Header';
import store from './data/store';
import './App.scss';
+var courseId = window.location.pathname.substring(1);
+
const App = () => (