Files
frontend-app-gradebook/src/data/selectors/grades.js
Omar Al-Ithawi ae1702d182 feat: use atlas in make pull_translations (#325)
Changes
-------
 - Bump frontend-platform to bring `intl-imports.js` script
 - Move all i18n imports into `src/i18n/index.js` so `intl-imports.js` can
   override it with latest translations
 - Add `atlas` into `make pull_translations` when `OPENEDX_ATLAS_PULL`
   environment variable is set.
 - Refactored i18n utils into own file to avoid overwriting them by
   atlas

Refs: [FC-0012 project](https://openedx.atlassian.net/l/cp/XGS0iCcQ) implementing Translation Infrastructure OEP-58.
2023-05-09 10:32:40 -04:00

295 lines
9.0 KiB
JavaScript

/* eslint-disable import/no-self-import */
import { StrictDict } from 'utils';
import { Headings, GradeFormats } from 'data/constants/grades';
import { formatDateForDisplay } from 'data/actions/utils';
import { getLocalizedSlash } from 'i18n/utils';
import simpleSelectorFactory from '../utils';
import * as module from './grades';
/**
* getRowsProcessed(historyEntryData)
* @param {object} historyEntryData - the data param from a bulk management history entry
* @return {string} - user-facing summary of total/skipped/failed rows.
*/
export const getRowsProcessed = ({
processed_rows: processed,
saved_rows: saved,
total_rows: total,
}) => {
const failed = processed - saved;
const skipped = total - processed;
const summaryEntries = [`${total} Students: ${saved} processed`];
if (skipped > 0) { summaryEntries.push(`${skipped} skipped`); }
if (failed > 0) { summaryEntries.push(`${failed} failed`); }
return summaryEntries.join(', ');
};
/**
* formatGradeOverrideForDisplay(historyArray)
* returns the grade override history results in display format.
* @param {object[]} historyArray - array of gradeOverrideHistoryResults
* @return {object[]} - display-formatted history results ({ date, grader, reason, adjustedGrade })
*/
export const formatGradeOverrideForDisplay = historyArray => historyArray.map(item => ({
date: formatDateForDisplay(new Date(item.history_date)),
grader: item.history_user,
reason: item.override_reason,
adjustedGrade: item.earned_graded_override,
}));
export const minGrade = '0';
export const maxGrade = '100';
/**
* formatMaxCourseGrade(percentGrade)
* Takes a percent grade and returns it unless it is equal to the max grade
* @param {string} percentGrade - grade percentage
* @return {string} percent grade or null
*/
export const formatMaxCourseGrade = (percentGrade) => (
(percentGrade === maxGrade) ? null : percentGrade
);
/**
* formatMinCourseGrade(percentGrade)
* Takes a percent grade and returns it unless it is equal to the min grade
* @param {string} percentGrade - grade percentage
* @return {string} percent grade or null
*/
export const formatMinCourseGrade = (percentGrade) => (
(percentGrade === minGrade) ? null : percentGrade
);
/**
* formatMaxAssignmentGrade(percentGrade, options)
* Takes a percent grade and returns it unless it is equal to the max grade or
* the assignment id is set.
* @param {string} percentGrade - grade percentage
* @param {object} options - options object ({ assignmentId });
* @return {string} percent grade or null
*/
export const formatMaxAssignmentGrade = (percentGrade, options) => (
(percentGrade === maxGrade || !options.assignmentId) ? null : percentGrade
);
/**
* formatMinAssignmentGrade(percentGrade, options)
* Takes a percent grade and returns it unless it is equal to the min grade or
* the assignment id is set.
* @param {string} percentGrade - grade percentage
* @param {object} options - options object ({ assignmentId });
* @return {string} percent grade or null
*/
export const formatMinAssignmentGrade = (percentGrade, options) => (
(percentGrade === minGrade || !options.assignmentId) ? null : percentGrade
);
/**
* headingMapper(category, label='All')
* Takes category and label filters and returns a method that will take a section breakdown
* and return the appropriate table headings.
* @param {string} category - assignment filter type
* @param {string} label - assignment filter label
* @return {string[]} - list of table headers
*/
export const headingMapper = (category, label = 'All') => {
const filters = {
all: section => section.label,
byCategory: section => section.label && section.category === category,
byLabel: section => section.label && section.label === label,
};
let filter;
if (label === 'All') {
filter = category === 'All' ? filters.all : filters.byCategory;
} else {
filter = filters.byLabel;
}
const {
username,
fullName,
email,
totalGrade,
} = Headings;
const filteredLabels = (entry) => entry.filter(filter).map(s => s.label);
return (entry) => (
entry
? [username, fullName, email, ...filteredLabels(entry), totalGrade]
: []
);
};
/**
* transformHistoryEntry(rawEntry)
* Takes a raw bulkManagementHistory entry and formats it for consumption
* @param {object} rawEntry - raw history entry to transform
* @return {object} - transformed history entry
* ({ timeUploaded, originalFilename, resultsSummary, ... })
*/
export const transformHistoryEntry = ({
modified,
original_filename: originalFilename,
data,
unique_id: courseId,
id,
...rest
}) => ({
timeUploaded: formatDateForDisplay(new Date(modified)),
originalFilename,
resultsSummary: {
rowId: id,
text: module.getRowsProcessed(data),
},
...rest,
});
/**
* roundGrade(val)
* Takes a number and rounds it to 2 decimal places
* defaults to 0
* @param {number=0} val - value to round.
* @return {number} - rounded number
*/
export const roundGrade = val => parseFloat((val || 0).toFixed(2));
export const subsectionGrade = StrictDict({
/**
* subsectionGrade.absolute(subsection)
* returns rounded {earned}/{possible} if attempted, else ${earned}
* @param {object} subsection - grade subsection entry
* @return {string} - absolute-formatted subsection grade string
*/
[GradeFormats.absolute]: (subsection) => {
const earned = module.roundGrade(subsection.score_earned);
const possible = module.roundGrade(subsection.score_possible);
return subsection.attempted ? `${earned}${getLocalizedSlash()}${possible}` : `${earned}`;
},
/**
* subsectionGrade.percent(subsection)
* returns rounded percent times 100
* @param {object} subsection - grade subsection entry
* @return {string} - percent-formatted subsection grade string
*/
[GradeFormats.percent]: (subsection) => (
module.roundGrade(subsection.percent * 100)
),
});
// Selectors
/**
* allGrades(state)
* returns the top-level redux grades state.
* @param {object} state - redux state
* @return {object} - redux grades state
*/
export const allGrades = ({ grades: { results } }) => results;
/**
* bulkImportError(state)
* returns the stringified bulkManagement import error messages.
* @param {object} state - redux state
* @return {string} - bulk import error messages joined into a display form
* (or empty string if there are none)
*/
export const bulkImportError = ({ grades: { bulkManagement } }) => (
(!!bulkManagement && bulkManagement.errorMessages)
? `Errors while processing: ${bulkManagement.errorMessages.join('; ')};`
: ''
);
/**
* bulkManagementHistory(state)
* returns the bulkManagement history entries from the grades state
* @param {object} state - redux state
* @return {object[]} - list of bulkManagement history entries
*/
export const bulkManagementHistory = ({ grades: { bulkManagement } }) => (
(bulkManagement.history || [])
);
/**
* bulkManagementHistoryEntries(state)
* returns transformed history entries from bulkManagement grades data.
* @param {object} state - redux state
* @return {object[]} - list of transformed bulkManagement history entries
*/
export const bulkManagementHistoryEntries = (state) => (
module.bulkManagementHistory(state).map(module.transformHistoryEntry)
);
/**
* getExampleSectionBreakdown(state)
* returns section breakdown of first grades result.
* @param {object} state - redux state
* @return {object[]} - section breakdown of first grades result.
*/
export const getExampleSectionBreakdown = ({ grades }) => (
(grades.results[0] || {}).section_breakdown || []
);
/**
* gradeOverrides(state)
* returns the gradeOverride history results
* @param {object} state - redux state
* @return {object[]} - grade override history result entries
*/
export const gradeOverrides = ({ grades }) => grades.gradeOverrideHistoryResults;
/**
* hasOverrideErrors(state)
* returns true iff gradeOverrideHistoryErrors are present
* @param {object} state - redux state
* @return {bool} - are there grade override history errors?
*/
export const hasOverrideErrors = ({ grades }) => !!grades.gradeOverrideHistoryError;
/**
* uploadSuccess(state)
* @param {object} state - redux state
* @return {bool} - is bulkManagement.uploadSuccess true?
*/
export const uploadSuccess = ({ grades: { bulkManagement } }) => (
!!bulkManagement && bulkManagement.uploadSuccess
);
const simpleSelectors = simpleSelectorFactory(
({ grades }) => grades,
[
'courseId',
'filteredUsersCount',
'totalUsersCount',
'gradeFormat',
'nextPage',
'prevPage',
'showSpinner',
'gradeOverrideCurrentEarnedGradedOverride',
'gradeOverrideHistoryError',
'gradeOriginalEarnedGraded',
'gradeOriginalPossibleGraded',
'nextPage',
'prevPage',
'showSuccess',
],
);
export default StrictDict({
bulkImportError,
formatGradeOverrideForDisplay,
formatMinAssignmentGrade,
formatMaxAssignmentGrade,
formatMaxCourseGrade,
formatMinCourseGrade,
hasOverrideErrors,
headingMapper,
roundGrade,
subsectionGrade,
...simpleSelectors,
allGrades,
bulkManagementHistoryEntries,
getExampleSectionBreakdown,
gradeOverrides,
uploadSuccess,
});