Added bulk management tab for CSV export/import
The bulk management tab will only be shown for masters courses, i.e. those containing a masters track JIRA:EDUCATOR-4343 JIRA:EDUCATOR-4431
This commit is contained in:
@@ -8,7 +8,11 @@ import {
|
||||
GRADE_UPDATE_FAILURE,
|
||||
TOGGLE_GRADE_FORMAT,
|
||||
FILTER_COLUMNS,
|
||||
UPDATE_BANNER,
|
||||
OPEN_BANNER,
|
||||
CLOSE_BANNER,
|
||||
START_UPLOAD,
|
||||
UPLOAD_COMPLETE,
|
||||
UPLOAD_ERR,
|
||||
} from '../constants/actionTypes/grades';
|
||||
import LmsApiService from '../services/LmsApiService';
|
||||
import { headingMapper, sortAlphaAsc } from './utils';
|
||||
@@ -16,6 +20,10 @@ import apiClient from '../apiClient';
|
||||
|
||||
const defaultAssignmentFilter = 'All';
|
||||
|
||||
const startedCsvUpload = () => ({ type: START_UPLOAD });
|
||||
const finishedCsvUpload = () => ({ type: UPLOAD_COMPLETE });
|
||||
const csvUploadError = data => ({ type: UPLOAD_ERR, data });
|
||||
|
||||
const startedFetchingGrades = () => ({ type: STARTED_FETCHING_GRADES });
|
||||
const finishedFetchingGrades = () => ({ type: FINISHED_FETCHING_GRADES });
|
||||
const errorFetchingGrades = () => ({ type: ERROR_FETCHING_GRADES });
|
||||
@@ -53,12 +61,19 @@ const filterColumns = (filterType, exampleUser) => (
|
||||
})
|
||||
);
|
||||
|
||||
const updateBanner = showSuccess => ({ type: UPDATE_BANNER, showSuccess });
|
||||
const openBanner = () => ({ type: OPEN_BANNER });
|
||||
const closeBanner = () => ({ type: CLOSE_BANNER });
|
||||
|
||||
const fetchGrades = (courseId, cohort, track, assignmentType, showSuccess) => (
|
||||
const fetchGrades = (
|
||||
courseId,
|
||||
cohort,
|
||||
track,
|
||||
assignmentType,
|
||||
options = {},
|
||||
) => (
|
||||
(dispatch) => {
|
||||
dispatch(startedFetchingGrades());
|
||||
return LmsApiService.fetchGradebookData(courseId, null, cohort, track)
|
||||
return LmsApiService.fetchGradebookData(courseId, options.searchText || null, cohort, track)
|
||||
.then(response => response.data)
|
||||
.then((data) => {
|
||||
dispatch(gotGrades(
|
||||
@@ -72,7 +87,9 @@ const fetchGrades = (courseId, cohort, track, assignmentType, showSuccess) => (
|
||||
courseId,
|
||||
));
|
||||
dispatch(finishedFetchingGrades());
|
||||
dispatch(updateBanner(!!showSuccess));
|
||||
if (options.showSuccess) {
|
||||
dispatch(openBanner());
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
dispatch(errorFetchingGrades());
|
||||
@@ -87,30 +104,11 @@ const fetchMatchingUserGrades = (
|
||||
track,
|
||||
assignmentType,
|
||||
showSuccess,
|
||||
) => (
|
||||
(dispatch) => {
|
||||
dispatch(startedFetchingGrades());
|
||||
return LmsApiService.fetchGradebookData(courseId, searchText, cohort, track)
|
||||
.then(response => response.data)
|
||||
.then((data) => {
|
||||
dispatch(gotGrades(
|
||||
data.results.sort(sortAlphaAsc),
|
||||
cohort,
|
||||
track,
|
||||
assignmentType,
|
||||
headingMapper(assignmentType || defaultAssignmentFilter)(data.results[0]),
|
||||
data.previous,
|
||||
data.next,
|
||||
courseId,
|
||||
));
|
||||
dispatch(finishedFetchingGrades());
|
||||
dispatch(updateBanner(showSuccess));
|
||||
})
|
||||
.catch(() => {
|
||||
dispatch(errorFetchingGrades());
|
||||
});
|
||||
}
|
||||
);
|
||||
options = {},
|
||||
) => {
|
||||
const newOptions = { ...options, searchText, showSuccess };
|
||||
return fetchGrades(courseId, cohort, track, assignmentType, newOptions);
|
||||
};
|
||||
|
||||
const fetchPrevNextGrades = (endpoint, courseId, cohort, track, assignmentType) => (
|
||||
(dispatch) => {
|
||||
@@ -150,6 +148,7 @@ const updateGrades = (courseId, updateData, searchText, cohort, track) => (
|
||||
track,
|
||||
defaultAssignmentFilter,
|
||||
true,
|
||||
{ searchText },
|
||||
));
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -158,6 +157,21 @@ const updateGrades = (courseId, updateData, searchText, cohort, track) => (
|
||||
}
|
||||
);
|
||||
|
||||
const submitFileUploadFormData = (courseId, formData) => (
|
||||
(dispatch) => {
|
||||
dispatch(startedCsvUpload());
|
||||
return LmsApiService.uploadGradeCsv(courseId, formData).then(() => (
|
||||
dispatch(finishedCsvUpload())
|
||||
)).catch((err) => {
|
||||
if (err.status === 200 && err.data.error_messages.length) {
|
||||
const { error_messages: errorMessages, saved, total } = err.data;
|
||||
return dispatch(csvUploadError({ errorMessages, saved, total }));
|
||||
}
|
||||
return dispatch(csvUploadError({ errorMessages: ['Unknown error.'] }));
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
export {
|
||||
startedFetchingGrades,
|
||||
finishedFetchingGrades,
|
||||
@@ -172,5 +186,6 @@ export {
|
||||
updateGrades,
|
||||
toggleGradeFormat,
|
||||
filterColumns,
|
||||
updateBanner,
|
||||
closeBanner,
|
||||
submitFileUploadFormData,
|
||||
};
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
FINISHED_FETCHING_GRADES,
|
||||
ERROR_FETCHING_GRADES,
|
||||
GOT_GRADES,
|
||||
UPDATE_BANNER,
|
||||
} from '../constants/actionTypes/grades';
|
||||
import { sortAlphaAsc } from './utils';
|
||||
|
||||
@@ -108,7 +107,6 @@ describe('actions', () => {
|
||||
courseId,
|
||||
},
|
||||
{ type: FINISHED_FETCHING_GRADES },
|
||||
{ type: UPDATE_BANNER, showSuccess: false },
|
||||
];
|
||||
const store = mockStore();
|
||||
|
||||
@@ -167,7 +165,6 @@ describe('actions', () => {
|
||||
courseId,
|
||||
},
|
||||
{ type: FINISHED_FETCHING_GRADES },
|
||||
{ type: UPDATE_BANNER, showSuccess: false },
|
||||
];
|
||||
const store = mockStore();
|
||||
|
||||
|
||||
@@ -9,7 +9,12 @@ const GRADE_UPDATE_FAILURE = 'GRADE_UPDATE_FAILURE';
|
||||
|
||||
const TOGGLE_GRADE_FORMAT = 'TOGGLE_GRADE_FORMAT';
|
||||
const FILTER_COLUMNS = 'FILTER_COLUMNS';
|
||||
const UPDATE_BANNER = 'UPDATE_BANNER';
|
||||
const CLOSE_BANNER = 'CLOSE_BANNER';
|
||||
const OPEN_BANNER = 'OPEN_BANNER';
|
||||
|
||||
const START_UPLOAD = 'START_UPLOAD';
|
||||
const UPLOAD_COMPLETE = 'UPLOAD_COMPLETE';
|
||||
const UPLOAD_ERR = 'UPLOAD_ERR';
|
||||
|
||||
export {
|
||||
STARTED_FETCHING_GRADES,
|
||||
@@ -21,5 +26,9 @@ export {
|
||||
GRADE_UPDATE_FAILURE,
|
||||
TOGGLE_GRADE_FORMAT,
|
||||
FILTER_COLUMNS,
|
||||
UPDATE_BANNER,
|
||||
OPEN_BANNER,
|
||||
CLOSE_BANNER,
|
||||
START_UPLOAD,
|
||||
UPLOAD_COMPLETE,
|
||||
UPLOAD_ERR,
|
||||
};
|
||||
|
||||
@@ -4,7 +4,11 @@ import {
|
||||
GOT_GRADES,
|
||||
TOGGLE_GRADE_FORMAT,
|
||||
FILTER_COLUMNS,
|
||||
UPDATE_BANNER,
|
||||
OPEN_BANNER,
|
||||
CLOSE_BANNER,
|
||||
START_UPLOAD,
|
||||
UPLOAD_COMPLETE,
|
||||
UPLOAD_ERR,
|
||||
} from '../constants/actionTypes/grades';
|
||||
|
||||
const initialState = {
|
||||
@@ -60,10 +64,35 @@ const grades = (state = initialState, action) => {
|
||||
...state,
|
||||
headings: action.headings,
|
||||
};
|
||||
case UPDATE_BANNER:
|
||||
case OPEN_BANNER:
|
||||
return {
|
||||
...state,
|
||||
showSuccess: action.showSuccess,
|
||||
showSuccess: true,
|
||||
};
|
||||
case CLOSE_BANNER:
|
||||
return {
|
||||
...state,
|
||||
showSuccess: false,
|
||||
};
|
||||
case START_UPLOAD:
|
||||
return {
|
||||
...state,
|
||||
showSpinner: true,
|
||||
};
|
||||
case UPLOAD_COMPLETE:
|
||||
return {
|
||||
...state,
|
||||
showSpinner: false,
|
||||
bulkManagement: {},
|
||||
};
|
||||
case UPLOAD_ERR:
|
||||
return {
|
||||
...state,
|
||||
showSpinner: false,
|
||||
bulkManagement: {
|
||||
...(state.bulkManagement || {}),
|
||||
...action.data,
|
||||
},
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
GOT_GRADES,
|
||||
TOGGLE_GRADE_FORMAT,
|
||||
FILTER_COLUMNS,
|
||||
UPDATE_BANNER,
|
||||
OPEN_BANNER,
|
||||
} from '../constants/actionTypes/grades';
|
||||
|
||||
const initialState = {
|
||||
@@ -158,8 +158,7 @@ describe('grades reducer', () => {
|
||||
showSuccess: expectedShowSuccess,
|
||||
};
|
||||
expect(grades(undefined, {
|
||||
type: UPDATE_BANNER,
|
||||
showSuccess: expectedShowSuccess,
|
||||
type: OPEN_BANNER,
|
||||
})).toEqual(expected);
|
||||
});
|
||||
|
||||
|
||||
4
src/data/selectors/tracks.js
Normal file
4
src/data/selectors/tracks.js
Normal file
@@ -0,0 +1,4 @@
|
||||
const getTracks = state => state.tracks.results || [];
|
||||
const hasMastersTrack = state => getTracks(state).some(track => track.slug === 'masters');
|
||||
|
||||
export default hasMastersTrack;
|
||||
@@ -67,6 +67,26 @@ class LmsApiService {
|
||||
const rolesUrl = `${LmsApiService.baseUrl}/api/enrollment/v1/roles/?course_id=${encodeURIComponent(courseId)}`;
|
||||
return apiClient.get(rolesUrl);
|
||||
}
|
||||
|
||||
static getGradeExportCsvUrl(courseId, options = {}) {
|
||||
const trackQueryParam = options.track ? [`track=${options.track}`] : [];
|
||||
const cohortQueryParam = options.cohort ? [`cohort=${options.cohort}`] : [];
|
||||
const queryParams = [...trackQueryParam, ...cohortQueryParam].join('&');
|
||||
const downloadUrl = `${LmsApiService.baseUrl}/api/bulk_grades/course/${courseId}?${queryParams}`;
|
||||
return downloadUrl;
|
||||
}
|
||||
|
||||
static getGradeImportCsvUrl = LmsApiService.getGradeExportCsvUrl;
|
||||
|
||||
static uploadGradeCsv(courseId, formData) {
|
||||
const fileUploadUrl = LmsApiService.getGradeImportCsvUrl(courseId);
|
||||
return apiClient.post(fileUploadUrl, formData).then((result) => {
|
||||
if (result.status === 200 && !result.data.error_messages.length) {
|
||||
return result.data;
|
||||
}
|
||||
return Promise.reject(result);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default LmsApiService;
|
||||
|
||||
Reference in New Issue
Block a user