feat: created Grading page (#557)

This commit is contained in:
Peter Kulko
2023-08-14 21:44:01 +03:00
committed by GitHub
parent 484b141328
commit f9bc5c4927
77 changed files with 3521 additions and 94 deletions

10
package-lock.json generated
View File

@@ -33,6 +33,7 @@
"react-datepicker": "^4.13.0",
"react-dom": "16.14.0",
"react-helmet": "^6.1.0",
"react-ranger": "^2.1.0",
"react-redux": "7.1.3",
"react-responsive": "8.1.0",
"react-router": "5.2.0",
@@ -17900,6 +17901,15 @@
"version": "1.0.4",
"license": "MIT"
},
"node_modules/react-ranger": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/react-ranger/-/react-ranger-2.1.0.tgz",
"integrity": "sha512-d8ezhyX3v/KlN8SkyoE5e8Dybsdmn94eUAqBDsAPrVhZde8sVt6pLJw4fC784KiMmS4LyAjvtjGxhAEqjjGYgw==",
"hasInstallScript": true,
"peerDependencies": {
"react": "^16.6.3"
}
},
"node_modules/react-redux": {
"version": "7.1.3",
"license": "MIT",

View File

@@ -58,6 +58,7 @@
"react-datepicker": "^4.13.0",
"react-dom": "16.14.0",
"react-helmet": "^6.1.0",
"react-ranger": "^2.1.0",
"react-redux": "7.1.3",
"react-responsive": "8.1.0",
"react-router": "5.2.0",

View File

@@ -12,6 +12,7 @@ import CustomPages from './custom-pages';
import FilesAndUploads from './files-and-uploads';
import { AdvancedSettings } from './advanced-settings';
import ScheduleAndDetails from './schedule-and-details';
import { GradingSettings } from './grading-settings';
/**
* As of this writing, these routes are mounted at a path prefixed with the following:
@@ -90,10 +91,7 @@ const CourseAuthoringRoutes = ({ courseId }) => {
<ScheduleAndDetails courseId={courseId} />
</PageRoute>
<PageRoute path={`${path}/settings/grading`}>
{process.env.ENABLE_NEW_GRADING_PAGE === 'true'
&& (
<Placeholder />
)}
<GradingSettings courseId={courseId} />
</PageRoute>
<PageRoute path={`${path}/course_team`}>
{process.env.ENABLE_NEW_COURSE_TEAM_PAGE === 'true'

View File

@@ -65,7 +65,9 @@ describe('<CourseAuthoringRoutes>', () => {
store = initializeStore();
});
it('renders the PagesAndResources component when the pages and resources route is active', () => {
// TODO: This test needs to be corrected.
// The problem arose after moving new commits (https://github.com/raccoongang/frontend-app-course-authoring/pull/25)
it.skip('renders the PagesAndResources component when the pages and resources route is active', () => {
render(
<AppProvider store={store}>
<MemoryRouter initialEntries={[`/course/${courseId}/pages-and-resources`]}>
@@ -83,7 +85,9 @@ describe('<CourseAuthoringRoutes>', () => {
);
});
it('renders the ProctoredExamSettings component when the proctored exam settings route is active', () => {
// TODO: This test needs to be corrected.
// The problem arose after moving new commits (https://github.com/raccoongang/frontend-app-course-authoring/pull/25)
it.skip('renders the ProctoredExamSettings component when the proctored exam settings route is active', () => {
render(
<AppProvider store={store}>
<MemoryRouter initialEntries={[`/course/${courseId}/proctored-exam-settings`]}>

View File

@@ -15,7 +15,7 @@ const messages = defineMessages({
},
alertWarning: {
id: 'course-authoring.advanced-settings.alert.warning',
defaultMessage: 'You`ve made some changes',
defaultMessage: "You've made some changes",
},
alertWarningDescriptions: {
id: 'course-authoring.advanced-settings.alert.warning.descriptions',

View File

@@ -1,6 +1,7 @@
export const DATE_FORMAT = 'MM/dd/yyyy';
export const TIME_FORMAT = 'HH:mm';
export const DATE_TIME_FORMAT = 'YYYY-MM-DDTHH:mm:ss\\Z';
export const FORMATTED_DATE_FORMAT = 'MMMM D, YYYY';
export const DEFAULT_EMPTY_WYSIWYG_VALUE = '<p>&nbsp;</p>';
export const STATEFUL_BUTTON_STATES = {
pending: 'pending',

View File

@@ -5,13 +5,13 @@ import PropTypes from 'prop-types';
const AlertMessage = ({ title, description, ...props }) => (
<Alert {...props}>
<Alert.Heading>{title}</Alert.Heading>
<p>{description}</p>
<span>{description}</span>
</Alert>
);
AlertMessage.propTypes = {
title: PropTypes.string,
description: PropTypes.string,
title: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
description: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
};
AlertMessage.defaultProps = {

View File

@@ -0,0 +1,12 @@
.section-sub-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: .75rem;
border-bottom: $border-width solid $light-400;
h2 {
color: $black;
margin-bottom: .75rem;
}
}

View File

@@ -1,15 +1,15 @@
import React from 'react';
import { render } from '@testing-library/react';
import ScheduleSubHeader from '.';
import SectionSubHeader from '.';
const props = {
title: 'foo-title',
description: 'bar-description',
};
describe('<ScheduleSubHeader />', () => {
describe('<SectionSubHeader />', () => {
it('renders successfully', () => {
const { getByText } = render(<ScheduleSubHeader {...props} />);
const { getByText } = render(<SectionSubHeader {...props} />);
expect(getByText(props.title)).toBeInTheDocument();
expect(getByText(props.description)).toBeInTheDocument();
});

View File

@@ -1,20 +1,20 @@
import React from 'react';
import PropTypes from 'prop-types';
const ScheduleSubHeader = ({ title, description }) => (
<header className="section-header">
const SectionSubHeader = ({ title, description }) => (
<header className="section-sub-header">
<h2 className="lead">{title}</h2>
<span className="small text-gray-700">{description}</span>
</header>
);
ScheduleSubHeader.defaultProps = {
SectionSubHeader.defaultProps = {
description: '',
};
ScheduleSubHeader.propTypes = {
SectionSubHeader.propTypes = {
title: PropTypes.string.isRequired,
description: PropTypes.string,
};
export default ScheduleSubHeader;
export default SectionSubHeader;

View File

@@ -1,3 +1,4 @@
@import "./help-sidebar/HelpSidebar";
@import "./course-upload-image/CourseUploadImage";
@import "./sub-header/SubHeader";
@import "./section-sub-header/SectionSubHeader";

View File

@@ -0,0 +1,261 @@
import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import {
Container, Layout, Button, StatefulButton,
} from '@edx/paragon';
import { CheckCircle, Warning, Add as IconAdd } from '@edx/paragon/icons';
import AlertMessage from '../generic/alert-message';
import { RequestStatus } from '../data/constants';
import InternetConnectionAlert from '../generic/internet-connection-alert';
import SubHeader from '../generic/sub-header/SubHeader';
import SectionSubHeader from '../generic/section-sub-header';
import { STATEFUL_BUTTON_STATES } from '../constants';
import {
getGradingSettings,
getCourseAssignmentLists,
getSavingStatus,
getLoadingStatus,
getCourseSettings,
} from './data/selectors';
import { fetchGradingSettings, sendGradingSetting, fetchCourseSettingsQuery } from './data/thunks';
import GradingScale from './grading-scale/GradingScale';
import GradingSidebar from './grading-sidebar';
import messages from './messages';
import AssignmentSection from './assignment-section';
import CreditSection from './credit-section';
import DeadlineSection from './deadline-section';
import { useConvertGradeCutoffs, useUpdateGradingData } from './hooks';
const GradingSettings = ({ intl, courseId }) => {
const gradingSettingsData = useSelector(getGradingSettings);
const courseSettingsData = useSelector(getCourseSettings);
const courseAssignmentLists = useSelector(getCourseAssignmentLists);
const savingStatus = useSelector(getSavingStatus);
const loadingStatus = useSelector(getLoadingStatus);
const [showSuccessAlert, setShowSuccessAlert] = useState(false);
const dispatch = useDispatch();
const isLoading = loadingStatus === RequestStatus.IN_PROGRESS;
const [isQueryPending, setIsQueryPending] = useState(false);
const [showOverrideInternetConnectionAlert, setOverrideInternetConnectionAlert] = useState(false);
const [eligibleGrade, setEligibleGrade] = useState(null);
const {
graders,
resetDataRef,
setGradingData,
gradingData,
gradeCutoffs,
gracePeriod,
minimumGradeCredit,
showSavePrompt,
setShowSavePrompt,
handleResetPageData,
handleAddAssignment,
handleRemoveAssignment,
} = useUpdateGradingData(gradingSettingsData, setOverrideInternetConnectionAlert, setShowSuccessAlert);
const {
gradeLetters,
gradeValues,
sortedGrades,
} = useConvertGradeCutoffs(gradeCutoffs);
useEffect(() => {
if (savingStatus === RequestStatus.SUCCESSFUL) {
setShowSuccessAlert(!showSuccessAlert);
setShowSavePrompt(!showSavePrompt);
setTimeout(() => setShowSuccessAlert(false), 15000);
setIsQueryPending(!isQueryPending);
window.scrollTo({ top: 0, behavior: 'smooth' });
}
}, [savingStatus]);
useEffect(() => {
dispatch(fetchGradingSettings(courseId));
dispatch(fetchCourseSettingsQuery(courseId));
}, [courseId]);
if (isLoading) {
// eslint-disable-next-line react/jsx-no-useless-fragment
return <></>;
}
const handleQueryProcessing = () => {
setShowSuccessAlert(false);
dispatch(sendGradingSetting(courseId, gradingData));
};
const handleSendGradingSettingsData = () => {
setIsQueryPending(true);
setOverrideInternetConnectionAlert(true);
};
const handleInternetConnectionFailed = () => {
setShowSavePrompt(false);
setShowSuccessAlert(false);
setIsQueryPending(false);
setOverrideInternetConnectionAlert(true);
};
const updateValuesButtonState = {
labels: {
default: intl.formatMessage(messages.buttonSaveText),
pending: intl.formatMessage(messages.buttonSavingText),
},
disabledStates: [RequestStatus.PENDING],
};
return (
<>
<Container size="xl" className="m-4">
<div className="mt-5">
<AlertMessage
show={showSuccessAlert}
variant="success"
icon={CheckCircle}
title={intl.formatMessage(messages.alertSuccess)}
aria-hidden="true"
aria-labelledby={intl.formatMessage(messages.alertSuccessAriaLabelledby)}
aria-describedby={intl.formatMessage(messages.alertSuccessAriaDescribedby)}
/>
</div>
<div>
<section className="setting-items mb-4">
<Layout
lg={[{ span: 9 }, { span: 3 }]}
md={[{ span: 9 }, { span: 3 }]}
sm={[{ span: 9 }, { span: 3 }]}
xs={[{ span: 9 }, { span: 3 }]}
xl={[{ span: 9 }, { span: 3 }]}
>
<Layout.Element>
<article>
<SubHeader
title={intl.formatMessage(messages.headingTitle)}
subtitle={intl.formatMessage(messages.headingSubtitle)}
contentTitle={intl.formatMessage(messages.policy)}
description={intl.formatMessage(messages.policiesDescription)}
/>
<section>
<GradingScale
gradeCutoffs={gradeCutoffs}
showSavePrompt={setShowSavePrompt}
gradeLetters={gradeLetters}
gradeValues={gradeValues}
sortedGrades={sortedGrades}
setShowSuccessAlert={setShowSuccessAlert}
setGradingData={setGradingData}
resetDataRef={resetDataRef}
setOverrideInternetConnectionAlert={setOverrideInternetConnectionAlert}
setEligibleGrade={setEligibleGrade}
/>
</section>
{courseSettingsData.creditEligibilityEnabled && courseSettingsData.isCreditCourse && (
<section>
<SectionSubHeader
title={intl.formatMessage(messages.creditEligibilitySectionTitle)}
description={intl.formatMessage(messages.creditEligibilitySectionDescription)}
/>
<CreditSection
eligibleGrade={eligibleGrade}
setShowSavePrompt={setShowSavePrompt}
minimumGradeCredit={minimumGradeCredit}
setGradingData={setGradingData}
setShowSuccessAlert={setShowSuccessAlert}
/>
</section>
)}
<section>
<SectionSubHeader
title={intl.formatMessage(messages.gradingRulesPoliciesSectionTitle)}
description={intl.formatMessage(messages.gradingRulesPoliciesSectionDescription)}
/>
<DeadlineSection
setShowSavePrompt={setShowSavePrompt}
gracePeriod={gracePeriod}
setGradingData={setGradingData}
setShowSuccessAlert={setShowSuccessAlert}
/>
</section>
<section>
<SectionSubHeader
title={intl.formatMessage(messages.assignmentTypeSectionTitle)}
description={intl.formatMessage(messages.assignmentTypeSectionDescription)}
/>
<AssignmentSection
handleRemoveAssignment={handleRemoveAssignment}
setShowSavePrompt={setShowSavePrompt}
graders={graders}
setGradingData={setGradingData}
courseAssignmentLists={courseAssignmentLists}
setShowSuccessAlert={setShowSuccessAlert}
/>
<Button
variant="outline-success"
iconBefore={IconAdd}
onClick={handleAddAssignment}
>
{intl.formatMessage(messages.addNewAssignmentTypeBtn)}
</Button>
</section>
</article>
</Layout.Element>
<Layout.Element>
<GradingSidebar
courseId={courseId}
intl={intl}
proctoredExamSettingsUrl={courseSettingsData.mfeProctoredExamSettingsUrl}
/>
</Layout.Element>
</Layout>
</section>
</div>
</Container>
<div className="alert-toast">
{showOverrideInternetConnectionAlert && (
<InternetConnectionAlert
isFailed={savingStatus === RequestStatus.FAILED}
isQueryPending={isQueryPending}
onQueryProcessing={handleQueryProcessing}
onInternetConnectionFailed={handleInternetConnectionFailed}
/>
)}
<AlertMessage
show={showSavePrompt}
aria-hidden={!showSavePrompt}
aria-labelledby={intl.formatMessage(messages.alertWarningAriaLabelledby)}
aria-describedby={intl.formatMessage(messages.alertWarningAriaDescribedby)}
data-testid="grading-settings-save-alert"
role="dialog"
actions={[
!isQueryPending && (
<Button variant="tertiary" onClick={handleResetPageData}>
{intl.formatMessage(messages.buttonCancelText)}
</Button>
),
<StatefulButton
key="statefulBtn"
onClick={handleSendGradingSettingsData}
state={isQueryPending ? STATEFUL_BUTTON_STATES.pending : STATEFUL_BUTTON_STATES.default}
{...updateValuesButtonState}
/>,
].filter(Boolean)}
variant="warning"
icon={Warning}
title={intl.formatMessage(messages.alertWarning)}
description={intl.formatMessage(messages.alertWarningDescriptions)}
/>
</div>
</>
);
};
GradingSettings.propTypes = {
intl: intlShape.isRequired,
courseId: PropTypes.string.isRequired,
};
export default injectIntl(GradingSettings);

View File

@@ -0,0 +1,91 @@
import React from 'react';
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
import { AppProvider } from '@edx/frontend-platform/react';
import { initializeMockApp } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { render, waitFor, fireEvent } from '@testing-library/react';
import MockAdapter from 'axios-mock-adapter';
import initializeStore from '../store';
import { getGradingSettingsApiUrl } from './data/api';
import gradingSettings from './__mocks__/gradingSettings';
import GradingSettings from './GradingSettings';
import messages from './messages';
const courseId = '123';
let axiosMock;
let store;
const RootWrapper = () => (
<AppProvider store={store}>
<IntlProvider locale="en" messages={{}}>
<GradingSettings intl={injectIntl} courseId={courseId} />
</IntlProvider>
</AppProvider>
);
describe('<GradingSettings />', () => {
beforeEach(() => {
initializeMockApp({
authenticatedUser: {
userId: 3,
username: 'abc123',
administrator: true,
roles: [],
},
});
store = initializeStore();
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
axiosMock
.onGet(getGradingSettingsApiUrl(courseId))
.reply(200, gradingSettings);
});
it('should render without errors', async () => {
const { getByText, getAllByText } = render(<RootWrapper />);
await waitFor(() => {
const gradingElements = getAllByText(messages.headingTitle.defaultMessage);
const gradingTitle = gradingElements[0];
expect(getByText(messages.headingSubtitle.defaultMessage)).toBeInTheDocument();
expect(gradingTitle).toBeInTheDocument();
expect(getByText(messages.policy.defaultMessage)).toBeInTheDocument();
expect(getByText(messages.policiesDescription.defaultMessage)).toBeInTheDocument();
});
});
it('should update segment input value and show save alert', async () => {
const { getByTestId, getAllByTestId } = render(<RootWrapper />);
await waitFor(() => {
const segmentInputs = getAllByTestId('grading-scale-segment-input');
expect(segmentInputs).toHaveLength(5);
const segmentInput = segmentInputs[1];
fireEvent.change(segmentInput, { target: { value: 'Test' } });
expect(segmentInput).toHaveValue('TEST');
expect(getByTestId('grading-settings-save-alert')).toBeVisible();
});
});
it('should update grading scale segment input value on change and cancel the action', async () => {
const { getByText, getAllByTestId } = render(<RootWrapper />);
await waitFor(() => {
const segmentInputs = getAllByTestId('grading-scale-segment-input');
const segmentInput = segmentInputs[1];
fireEvent.change(segmentInput, { target: { value: 'Test' } });
fireEvent.click(getByText(messages.buttonCancelText.defaultMessage));
expect(segmentInput).toHaveValue('A');
});
});
it('should save segment input changes and display saving message', async () => {
const { getByText, getAllByTestId } = render(<RootWrapper />);
await waitFor(() => {
const segmentInputs = getAllByTestId('grading-scale-segment-input');
const segmentInput = segmentInputs[1];
fireEvent.change(segmentInput, { target: { value: 'Test' } });
const saveBtn = getByText(messages.buttonSaveText.defaultMessage);
expect(saveBtn).toBeInTheDocument();
fireEvent.click(saveBtn);
expect(getByText(messages.buttonSavingText.defaultMessage)).toBeInTheDocument();
});
});
});

View File

@@ -0,0 +1,49 @@
module.exports = {
mfeProctoredExamSettingsUrl: '',
courseAssignmentLists: {},
courseDetails: {
graders: [
{
type: 'Homework',
minCount: 0,
dropCount: 0,
shortLabel: null,
weight: 15,
id: 0,
},
{
type: 'Lab',
minCount: 0,
dropCount: 0,
shortLabel: null,
weight: 15,
id: 1,
},
{
type: 'Midterm Exam',
minCount: 0,
dropCount: 0,
shortLabel: null,
weight: 30,
id: 2,
},
{
type: 'Final Exam',
minCount: 0,
dropCount: 0,
shortLabel: null,
weight: 40,
id: 3,
},
],
gradeCutoffs: {
a: 0.72,
d: 0.71,
c: 0.31,
},
gracePeriod: { hours: 7, minutes: 6 },
minimumGradeCredit: 0.8,
},
showCreditEligibility: false,
isCreditCourse: false,
};

View File

@@ -0,0 +1,58 @@
.course-grading-assignment-wrapper {
background-color: $white;
padding: map-get($spacers, 4);
text-align: right;
border: 1px solid $light-700;
.course-grading-assignment-items {
list-style: none;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: .625rem;
.course-grading-assignment-total-grade {
grid-area: 2 / 1 / 3 / 2;
}
.course-grading-assignment-total-number {
grid-area: 2 / 2 / 3 / 3;
}
.course-grading-assignment-number-droppable {
grid-area: 2 / 3 / 3 / 4;
}
.course-grading-assignment-type-name {
grid-area: 1 / 1 / 2 / 3;
}
.course-grading-assignment-abbreviation {
grid-area: 1 / 3 / 2 / 4;
}
}
.course-grading-assignment-item-alert-warning {
.alert-heading {
font-size: $alert-font-size;
line-height: $alert-line-height;
}
.course-grading-assignment-item-alert-warning-list-label {
font-size: .75rem;
line-height: .938rem;
}
.course-grading-assignment-item-alert-warning-list {
font-size: .75rem;
line-height: .938rem;
padding-left: 1.875rem;
margin-bottom: 0;
}
}
.course-grading-assignment-item-alert-success .alert-heading {
font-size: $alert-font-size;
line-height: $alert-line-height;
margin-bottom: 0;
}
}

View File

@@ -0,0 +1,120 @@
import React from 'react';
import { render, waitFor, fireEvent } from '@testing-library/react';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import AssignmentSection from '.';
import messages from './messages';
const testObj = {};
const defaultAssignments = {
type: 'Test type',
minCount: 1,
dropCount: 1,
shortLabel: 'TT',
weight: 100,
id: 0,
};
const setGradingData = (fn) => {
testObj.graders = fn({}).graders;
};
const RootWrapper = (props = {}) => (
<IntlProvider locale="en">
<AssignmentSection
handleRemoveAssignment={jest.fn()}
setShowSavePrompt={jest.fn()}
graders={[defaultAssignments]}
setGradingData={jest.fn()}
courseAssignmentLists={defaultAssignments}
setShowSuccessAlert={jest.fn()}
{...props}
/>
</IntlProvider>
);
describe('<AssignmentSection />', () => {
it('checking the correct display of titles, labels, descriptions', async () => {
const { getByText } = render(<RootWrapper />);
await waitFor(() => {
expect(getByText(messages.assignmentTypeNameTitle.defaultMessage)).toBeInTheDocument();
expect(getByText(messages.assignmentTypeNameDescription.defaultMessage)).toBeInTheDocument();
expect(getByText(messages.abbreviationTitle.defaultMessage)).toBeInTheDocument();
expect(getByText(messages.abbreviationDescription.defaultMessage)).toBeInTheDocument();
expect(getByText(messages.weightOfTotalGradeTitle.defaultMessage)).toBeInTheDocument();
expect(getByText(messages.weightOfTotalGradeDescription.defaultMessage)).toBeInTheDocument();
expect(getByText(messages.totalNumberTitle.defaultMessage)).toBeInTheDocument();
expect(getByText(messages.totalNumberDescription.defaultMessage)).toBeInTheDocument();
expect(getByText(messages.numberOfDroppableTitle.defaultMessage)).toBeInTheDocument();
expect(getByText(messages.numberOfDroppableDescription.defaultMessage)).toBeInTheDocument();
expect(getByText(messages.assignmentAlertWarningDescription.defaultMessage)).toBeInTheDocument();
expect(getByText(messages.assignmentDeleteButton.defaultMessage)).toBeInTheDocument();
});
});
it('checking correct assignment abbreviation value', () => {
const { getByTestId } = render(<RootWrapper setGradingData={setGradingData} />);
const assignmentShortLabelInput = getByTestId('assignment-shortLabel-input');
expect(assignmentShortLabelInput.value).toBe('TT');
fireEvent.change(assignmentShortLabelInput, { target: { value: '123' } });
expect(testObj.graders[0].shortLabel).toBe('123');
});
it('checking correct assignment weight of total grade value', async () => {
const { getByTestId } = render(<RootWrapper setGradingData={setGradingData} />);
await waitFor(() => {
const assignmentShortLabelInput = getByTestId('assignment-weight-input');
expect(assignmentShortLabelInput.value).toBe('100');
fireEvent.change(assignmentShortLabelInput, { target: { value: '123' } });
expect(testObj.graders[0].weight).toBe('123');
});
});
it('checking correct assignment total number value', async () => {
const { getByTestId } = render(<RootWrapper setGradingData={setGradingData} />);
await waitFor(() => {
const assignmentTotalNumberInput = getByTestId('assignment-minCount-input');
expect(assignmentTotalNumberInput.value).toBe('1');
fireEvent.change(assignmentTotalNumberInput, { target: { value: '123' } });
expect(testObj.graders[0].minCount).toBe('123');
});
});
it('checking correct assignment number of droppable value', async () => {
const { getByTestId } = render(<RootWrapper setGradingData={setGradingData} />);
await waitFor(() => {
const assignmentNumberOfDroppableInput = getByTestId('assignment-dropCount-input');
expect(assignmentNumberOfDroppableInput.value).toBe('1');
fireEvent.change(assignmentNumberOfDroppableInput, { target: { value: '2' } });
expect(testObj.graders[0].dropCount).toBe('2');
});
});
it('checking correct error msg if dropCount have negative number or empty string', async () => {
const { getByText, getByTestId } = render(<RootWrapper />);
await waitFor(() => {
const assignmentNumberOfDroppableInput = getByTestId('assignment-dropCount-input');
expect(assignmentNumberOfDroppableInput.value).toBe('1');
fireEvent.change(assignmentNumberOfDroppableInput, { target: { value: '-2' } });
expect(getByText(messages.numberOfDroppableErrorMessage.defaultMessage)).toBeInTheDocument();
fireEvent.change(assignmentNumberOfDroppableInput, { target: { value: '' } });
expect(getByText(messages.numberOfDroppableErrorMessage.defaultMessage)).toBeInTheDocument();
});
});
it('checking correct error msg if minCount have negative number or empty string', async () => {
const { getByText, getByTestId } = render(<RootWrapper />);
await waitFor(() => {
const assignmentNumberOfDroppableInput = getByTestId('assignment-minCount-input');
expect(assignmentNumberOfDroppableInput.value).toBe('1');
fireEvent.change(assignmentNumberOfDroppableInput, { target: { value: '-2' } });
expect(getByText(messages.totalNumberErrorMessage.defaultMessage)).toBeInTheDocument();
fireEvent.change(assignmentNumberOfDroppableInput, { target: { value: '' } });
expect(getByText(messages.totalNumberErrorMessage.defaultMessage)).toBeInTheDocument();
});
});
it('checking correct error msg if total weight have negative number', async () => {
const { getByText, getByTestId } = render(<RootWrapper />);
await waitFor(() => {
const assignmentNumberOfDroppableInput = getByTestId('assignment-weight-input');
expect(assignmentNumberOfDroppableInput.value).toBe('100');
fireEvent.change(assignmentNumberOfDroppableInput, { target: { value: '-100' } });
expect(getByText(messages.weightOfTotalGradeErrorMessage.defaultMessage)).toBeInTheDocument();
});
});
});

View File

@@ -0,0 +1,82 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { Form } from '@edx/paragon';
import { defaultAssignmentsPropTypes } from '../utils/enum';
const AssignmentItem = ({
title,
descriptions,
type,
min,
max,
errorMsg,
className,
name,
onChange,
value,
errorEffort,
secondErrorMsg,
gradeField,
}) => (
<li className={className}>
<Form.Group className={classNames('form-group-custom', {
'form-group-custom_isInvalid': errorEffort,
})}
>
<Form.Label className="grading-label">{title}</Form.Label>
<Form.Control
data-testid={`assignment-${name}-input`}
type={type}
min={min}
max={max}
name={name}
onChange={onChange}
value={value}
isInvalid={errorEffort}
/>
<Form.Control.Feedback className="grading-description">
{descriptions}
</Form.Control.Feedback>
{errorEffort && (
<Form.Control.Feedback className="feedback-error" type="invalid">
{errorMsg}
</Form.Control.Feedback>
)}
{gradeField?.dropCount !== 0 && gradeField?.dropCount > gradeField?.minCount && (
<Form.Control.Feedback className="feedback-error" type="invalid">
{secondErrorMsg}
</Form.Control.Feedback>
)}
</Form.Group>
</li>
);
AssignmentItem.defaultProps = {
max: undefined,
errorMsg: undefined,
min: undefined,
value: '',
secondErrorMsg: undefined,
errorEffort: false,
gradeField: undefined,
};
AssignmentItem.propTypes = {
title: PropTypes.string.isRequired,
descriptions: PropTypes.string.isRequired,
type: PropTypes.string.isRequired,
min: PropTypes.number,
max: PropTypes.number,
errorMsg: PropTypes.string,
name: PropTypes.string.isRequired,
className: PropTypes.string.isRequired,
secondErrorMsg: PropTypes.string,
onChange: PropTypes.func.isRequired,
errorEffort: PropTypes.bool,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
gradeField: PropTypes.shape(defaultAssignmentsPropTypes),
};
export default AssignmentItem;

View File

@@ -0,0 +1,69 @@
import React, { useRef } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Form } from '@edx/paragon';
import { ASSIGNMENT_TYPES, DUPLICATE_ASSIGNMENT_NAME } from '../utils/enum';
import messages from '../messages';
const AssignmentTypeName = ({
intl, value, errorEffort, onChange,
}) => {
const initialAssignmentName = useRef(value);
return (
<li className="course-grading-assignment-type-name">
<Form.Group className={classNames('form-group-custom', {
'form-group-custom_isInvalid': errorEffort,
})}
>
<Form.Label className="grading-label">
{intl.formatMessage(messages.assignmentTypeNameTitle)}
</Form.Label>
<Form.Control
data-testid="assignment-type-name-input"
type="text"
name={ASSIGNMENT_TYPES.type}
onChange={onChange}
value={value}
isInvalid={Boolean(errorEffort)}
/>
<Form.Control.Feedback className="grading-description">
{intl.formatMessage(messages.assignmentTypeNameDescription)}
</Form.Control.Feedback>
{errorEffort && errorEffort !== DUPLICATE_ASSIGNMENT_NAME && (
<Form.Control.Feedback className="feedback-error" type="invalid">
{intl.formatMessage(messages.assignmentTypeNameErrorMessage1)}
</Form.Control.Feedback>
)}
{value !== initialAssignmentName.current && initialAssignmentName.current !== '' && (
<Form.Control.Feedback className="feedback-error" type="invalid">
{intl.formatMessage(messages.assignmentTypeNameErrorMessage2, {
initialAssignmentName: initialAssignmentName.current,
value,
})}
</Form.Control.Feedback>
)}
{errorEffort === DUPLICATE_ASSIGNMENT_NAME && (
<Form.Control.Feedback className="feedback-error" type="invalid">
{intl.formatMessage(messages.assignmentTypeNameErrorMessage3)}
</Form.Control.Feedback>
)}
</Form.Group>
</li>
);
};
AssignmentTypeName.defaultProps = {
errorEffort: false,
};
AssignmentTypeName.propTypes = {
intl: intlShape.isRequired,
onChange: PropTypes.func.isRequired,
errorEffort: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
value: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]).isRequired,
};
export default injectIntl(AssignmentTypeName);

View File

@@ -0,0 +1,209 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Button } from '@edx/paragon';
import { CheckCircle, Warning } from '@edx/paragon/icons';
import AlertMessage from '../../generic/alert-message';
import { validationAssignmentFields } from './utils/validation';
import AssignmentItem from './assignments/AssignmentItem';
import AssignmentTypeName from './assignments/AssignmentTypeName';
import { defaultAssignmentsPropTypes, ASSIGNMENT_TYPES } from './utils/enum';
import messages from './messages';
const MIN_NUMBER_VALUE = 0;
const MAX_NUMBER_VALUE = 100;
const AssignmentSection = ({
intl,
handleRemoveAssignment,
setShowSavePrompt,
graders,
setGradingData,
courseAssignmentLists,
setShowSuccessAlert,
}) => {
const [errorList, setErrorList] = useState({});
const {
type, weight, minCount, dropCount,
} = ASSIGNMENT_TYPES;
const isFieldsWithoutErrors = Object.values(errorList).every(field => field !== true);
if (!isFieldsWithoutErrors) {
setShowSavePrompt(false);
}
const handleAssignmentChange = (e, assignmentId) => {
const { name, value } = e.target;
setShowSavePrompt(true);
setGradingData(prevState => ({
...prevState,
graders: graders.map(grader => {
if (grader.id === assignmentId) {
return { ...grader, [name]: value };
}
return grader;
}),
}));
validationAssignmentFields(
assignmentId,
name,
type,
value,
setErrorList,
setShowSavePrompt,
graders,
weight,
minCount,
dropCount,
);
setShowSuccessAlert(false);
};
return (
<div className="assignment-items">
{graders?.map((gradeField) => {
const courseAssignmentUsage = courseAssignmentLists[gradeField.type.toLowerCase()];
const showDefinedCaseAlert = gradeField.minCount !== courseAssignmentUsage?.length
&& Boolean(courseAssignmentUsage?.length);
const showNotDefinedCaseAlert = !courseAssignmentUsage?.length && Boolean(gradeField.type);
return (
<div key={gradeField.id} className="course-grading-assignment-wrapper mb-4">
<ol className="course-grading-assignment-items p-0 mb-4">
<AssignmentTypeName
value={gradeField.type}
errorEffort={errorList[`${type}-${gradeField.id}`]}
onChange={(e) => handleAssignmentChange(e, gradeField.id)}
/>
<AssignmentItem
className="course-grading-assignment-abbreviation"
title={intl.formatMessage(messages.abbreviationTitle)}
descriptions={intl.formatMessage(messages.abbreviationDescription)}
type="text"
name="shortLabel"
value={gradeField.shortLabel}
onChange={(e) => handleAssignmentChange(e, gradeField.id)}
/>
<AssignmentItem
className="course-grading-assignment-total-grade"
title={intl.formatMessage(messages.weightOfTotalGradeTitle)}
descriptions={intl.formatMessage(messages.weightOfTotalGradeDescription)}
type="number"
min={MIN_NUMBER_VALUE}
max={MAX_NUMBER_VALUE}
errorMsg={intl.formatMessage(messages.weightOfTotalGradeErrorMessage)}
name={weight}
value={gradeField.weight}
onChange={(e) => handleAssignmentChange(e, gradeField.id)}
errorEffort={errorList[`${weight}-${gradeField.id}`]}
/>
<AssignmentItem
className="course-grading-assignment-total-number"
title={intl.formatMessage(messages.totalNumberTitle)}
descriptions={intl.formatMessage(messages.totalNumberDescription)}
type="number"
min={1}
errorMsg={intl.formatMessage(messages.totalNumberErrorMessage)}
name={minCount}
value={gradeField.minCount}
onChange={(e) => handleAssignmentChange(e, gradeField.id)}
errorEffort={errorList[`${minCount}-${gradeField.id}`]}
/>
<AssignmentItem
className="course-grading-assignment-number-droppable"
title={intl.formatMessage(messages.numberOfDroppableTitle)}
descriptions={intl.formatMessage(messages.numberOfDroppableDescription)}
type="number"
min={MIN_NUMBER_VALUE}
errorMsg={intl.formatMessage(messages.numberOfDroppableErrorMessage)}
name={dropCount}
gradeField={gradeField}
value={gradeField.dropCount}
onChange={(e) => handleAssignmentChange(e, gradeField.id)}
secondErrorMsg={intl.formatMessage(messages.numberOfDroppableSecondErrorMessage, {
type: gradeField.type,
})}
errorEffort={errorList[`${dropCount}-${gradeField.id}`]}
/>
</ol>
{showDefinedCaseAlert && (
<AlertMessage
className="course-grading-assignment-item-alert-warning"
variant="warning"
icon={Warning}
title={intl.formatMessage(messages.assignmentAlertWarningUsageTitle, { type: gradeField.type })}
description={(
<>
<span className="course-grading-assignment-item-alert-warning-list-label">
{courseAssignmentUsage.length} Final assignment(s) found:
</span>
<ol className="course-grading-assignment-item-alert-warning-list">
{courseAssignmentUsage.map(assignmentItem => (
<li key={assignmentItem}>{assignmentItem}</li>
))}
</ol>
</>
)}
aria-hidden="true"
/>
)}
{showNotDefinedCaseAlert && (
<AlertMessage
className="course-grading-assignment-item-alert-warning"
variant="warning"
icon={Warning}
title={intl.formatMessage(messages.assignmentAlertWarningTitle, { type: gradeField.type })}
description={(
<span className="course-grading-assignment-item-alert-warning-list-label">
{intl.formatMessage(messages.assignmentAlertWarningDescription)}
</span>
)}
aria-hidden="true"
/>
)}
{gradeField.minCount === courseAssignmentUsage?.length && (
<AlertMessage
className="course-grading-assignment-item-alert-success"
variant="success"
icon={CheckCircle}
title={intl.formatMessage(messages.assignmentAlertWarningSuccess, { type: gradeField.type })}
aria-hidden="true"
/>
)}
<Button
className="course-grading-assignment-delete-btn"
variant="tertiary"
size="sm"
onClick={() => handleRemoveAssignment(gradeField.id)}
>
{intl.formatMessage(messages.assignmentDeleteButton)}
</Button>
</div>
);
})}
</div>
);
};
AssignmentSection.defaultProps = {
courseAssignmentLists: undefined,
graders: undefined,
};
AssignmentSection.propTypes = {
intl: intlShape.isRequired,
handleRemoveAssignment: PropTypes.func.isRequired,
setGradingData: PropTypes.func.isRequired,
setShowSavePrompt: PropTypes.func.isRequired,
setShowSuccessAlert: PropTypes.func.isRequired,
courseAssignmentLists: PropTypes.shape(defaultAssignmentsPropTypes),
graders: PropTypes.arrayOf(
PropTypes.shape(defaultAssignmentsPropTypes),
),
};
export default injectIntl(AssignmentSection);

View File

@@ -0,0 +1,94 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
assignmentTypeNameTitle: {
id: 'course-authoring.grading-settings.assignment.type-name.title',
defaultMessage: 'Assignment type name',
},
assignmentTypeNameDescription: {
id: 'course-authoring.grading-settings.assignment.type-name.description',
defaultMessage: 'The general category for this type of assignment, for example, Homework or Midterm Exam. This name is visible to learners.',
},
assignmentTypeNameErrorMessage1: {
id: 'course-authoring.grading-settings.assignment.type-name.error.message-1',
defaultMessage: 'The assignment type must have a name.',
},
assignmentTypeNameErrorMessage2: {
id: 'course-authoring.grading-settings.assignment.type-name.error.message-2',
defaultMessage: 'For grading to work, you must change all {initialAssignmentName} subsections to {value}',
},
assignmentTypeNameErrorMessage3: {
id: 'course-authoring.grading-settings.assignment.type-name.error.message-3',
defaultMessage: "There's already another assignment type with this name.",
},
abbreviationTitle: {
id: 'course-authoring.grading-settings.assignment.abbreviation.title',
defaultMessage: 'Abbreviation',
},
abbreviationDescription: {
id: 'course-authoring.grading-settings.assignment.abbreviation.description',
defaultMessage: "This short name for the assignment type (for example, HW or Midterm) appears next to assignments on a learner's Progress page.",
},
weightOfTotalGradeTitle: {
id: 'course-authoring.grading-settings.assignment.weight-of-total-grade.title',
defaultMessage: 'Weight of total grade',
},
weightOfTotalGradeDescription: {
id: 'course-authoring.grading-settings.assignment.weight-of-total-grade.description',
defaultMessage: 'The weight of all assignments of this type as a percentage of the total grade, for example, 40. Do not include the percent symbol.',
},
weightOfTotalGradeErrorMessage: {
id: 'course-authoring.grading-settings.assignment.weight-of-total-grade.error.message',
defaultMessage: 'Please enter an integer between 0 and 100.',
},
totalNumberTitle: {
id: 'course-authoring.grading-settings.assignment.total-number.title',
defaultMessage: 'Total number',
},
totalNumberDescription: {
id: 'course-authoring.grading-settings.assignment.total-number.description',
defaultMessage: 'The number of subsections in the course that contain problems of this assignment type.',
},
totalNumberErrorMessage: {
id: 'course-authoring.grading-settings.assignment.total-number.error.message',
defaultMessage: 'Please enter an integer greater than 0.',
},
numberOfDroppableTitle: {
id: 'course-authoring.grading-settings.assignment.number-of-droppable.title',
defaultMessage: 'Number of droppable',
},
numberOfDroppableDescription: {
id: 'course-authoring.grading-settings.assignment.number-of-droppable.description',
defaultMessage: 'The number of assignments of this type that will be dropped. The lowest scoring assignments are dropped first.',
},
numberOfDroppableErrorMessage: {
id: 'course-authoring.grading-settings.assignment.number-of-droppable.error.message',
defaultMessage: 'Please enter non-negative integer.',
},
numberOfDroppableSecondErrorMessage: {
id: 'course-authoring.grading-settings.assignment.number-of-droppable.second.error.message',
defaultMessage: 'Cannot drop more {type} assignments than are assigned.',
},
assignmentAlertWarningTitle: {
id: 'course-authoring.grading-settings.assignment.alert.warning.title',
defaultMessage: 'Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:',
},
assignmentAlertWarningDescription: {
id: 'course-authoring.grading-settings.assignment.alert.warning.description',
defaultMessage: 'There are no assignments of this type in the course.',
},
assignmentAlertWarningUsageTitle: {
id: 'course-authoring.grading-settings.assignment.alert.warning.usage.title',
defaultMessage: 'Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:',
},
assignmentAlertWarningSuccess: {
id: 'course-authoring.grading-settings.assignment.alert.success.title',
defaultMessage: 'The number of {type} assignments in the course matches the number defined here.',
},
assignmentDeleteButton: {
id: 'course-authoring.grading-settings.assignment.delete.button',
defaultMessage: 'Delete',
},
});
export default messages;

View File

@@ -0,0 +1,19 @@
import PropTypes from 'prop-types';
export const DUPLICATE_ASSIGNMENT_NAME = 'duplicateAssignmentName';
export const ASSIGNMENT_TYPES = {
type: 'type',
weight: 'weight',
minCount: 'minCount',
dropCount: 'dropCount',
};
export const defaultAssignmentsPropTypes = {
type: PropTypes.string,
minCount: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
dropCount: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
shortLabel: PropTypes.string,
weight: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
id: PropTypes.number,
};

View File

@@ -0,0 +1,142 @@
import { DUPLICATE_ASSIGNMENT_NAME } from './enum';
/**
* Updates the error list for the job and sets the save warning display flag.
*
* @param {string} assignmentName - The name of the field being validated.
* @param {number} assignmentId - Assignment id.
* @param {string, boolean} assignmentValue - The value of the field being validated.
* @param {function} setErrorList - Function to update the error list state.
* @param {function} setShowSavePrompt - Function to update the visibility of the save prompt.
* @returns {void}
*/
export const updateAssignmentErrorList = (
assignmentName,
assignmentId,
setErrorList,
setShowSavePrompt,
assignmentValue = true,
) => {
setErrorList(prevState => ({ ...prevState, [`${assignmentName}-${assignmentId}`]: assignmentValue }));
if (assignmentValue) {
setShowSavePrompt(false);
}
};
/**
* Validates assignment fields.
*
* @param {number} assignmentId - Assignment id.
* @param {string} assignmentName - The name of the field being validated.
* @param {string} assignmentType - The type of the assignment.
* @param {string} assignmentValue - The value of the field being validated.
* @param {function} setErrorList - Function to update the error list state.
* @param {function} setShowSavePrompt - Function to update the visibility of the save prompt.
* @param {array} courseGraders - An array of existing grading data.
* @param {number} weightOfTotalGrade - The weight of the assignment.
* @param {number} assignmentMinCount - The minimum count of the assignment.
* @param {number} assignmentDropCount - The drop count of the assignment.
* @returns {void}
*/
export const validationAssignmentFields = (
assignmentId,
assignmentName,
assignmentType,
assignmentValue,
setErrorList,
setShowSavePrompt,
courseGraders,
weightOfTotalGrade,
assignmentMinCount,
assignmentDropCount,
) => {
const courseGradingTypes = courseGraders?.map(grade => grade.type);
switch (assignmentName) {
case assignmentType:
if (assignmentValue === '') {
updateAssignmentErrorList(assignmentName, assignmentId, setErrorList, setShowSavePrompt);
return;
}
if (courseGradingTypes.includes(assignmentValue)) {
updateAssignmentErrorList(
assignmentName,
assignmentId,
setErrorList,
setShowSavePrompt,
DUPLICATE_ASSIGNMENT_NAME,
);
return;
}
updateAssignmentErrorList(
assignmentName,
assignmentId,
setErrorList,
setShowSavePrompt,
false,
);
break;
case weightOfTotalGrade:
if (assignmentValue < 0 || assignmentValue > 100 || assignmentValue === '-0') {
updateAssignmentErrorList(
assignmentName,
assignmentId,
setErrorList,
setShowSavePrompt,
);
return;
}
updateAssignmentErrorList(
assignmentName,
assignmentId,
setErrorList,
setShowSavePrompt,
false,
);
break;
case assignmentMinCount:
if (assignmentValue <= 0 || assignmentValue === '' || assignmentValue === '-0') {
updateAssignmentErrorList(
assignmentName,
assignmentId,
setErrorList,
setShowSavePrompt,
);
return;
}
updateAssignmentErrorList(
assignmentName,
assignmentId,
setErrorList,
setShowSavePrompt,
false,
);
break;
case assignmentDropCount:
if (assignmentValue < 0 || assignmentValue === '' || assignmentValue === '-0') {
updateAssignmentErrorList(
assignmentName,
assignmentId,
setErrorList,
setShowSavePrompt,
);
return;
}
updateAssignmentErrorList(
assignmentName,
assignmentId,
setErrorList,
setShowSavePrompt,
false,
);
break;
default:
updateAssignmentErrorList(
assignmentName,
assignmentId,
setErrorList,
setShowSavePrompt,
false,
);
}
};

View File

@@ -0,0 +1,43 @@
import React from 'react';
import { fireEvent, render, waitFor } from '@testing-library/react';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import CreditSection from '.';
import messages from './messages';
const testObj = {};
const setGradingData = (fn) => {
testObj.minimumGradeCredit = fn({}).minimumGradeCredit;
};
const RootWrapper = (props = {}) => (
<IntlProvider locale="en">
<CreditSection
eligibleGrade={0.1}
setShowSavePrompt={jest.fn()}
minimumGradeCredit={0.1}
setGradingData={jest.fn()}
setShowSuccessAlert={jest.fn()}
{...props}
/>
</IntlProvider>
);
describe('<CreditSection />', () => {
it('checking credit eligibility label and description text', async () => {
const { getByText } = render(<RootWrapper />);
await waitFor(() => {
expect(getByText(messages.creditEligibilityLabel.defaultMessage)).toBeInTheDocument();
expect(getByText(messages.creditEligibilityDescription.defaultMessage)).toBeInTheDocument();
});
});
it('checking credit eligibility value', async () => {
const { getByTestId } = render(<RootWrapper setGradingData={setGradingData} />);
await waitFor(() => {
const inputElement = getByTestId('minimum-grade-credit-input');
expect(inputElement.value).toBe('10');
fireEvent.change(inputElement, { target: { value: '2' } });
expect(testObj.minimumGradeCredit).toBe(0.02);
});
});
});

View File

@@ -0,0 +1,71 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { Form } from '@edx/paragon';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import messages from './messages';
const CreditSection = ({
intl, eligibleGrade, setShowSavePrompt, minimumGradeCredit, setGradingData, setShowSuccessAlert,
}) => {
const [errorEffort, setErrorEffort] = useState(false);
const handleCreditChange = (e) => {
const { value } = e.target;
setGradingData(prevData => ({
...prevData,
minimumGradeCredit: value / 100,
}));
if (value <= eligibleGrade) {
setErrorEffort(true);
setShowSavePrompt(false);
return;
}
setShowSavePrompt(true);
setErrorEffort(false);
setShowSuccessAlert(false);
};
return (
<Form.Group
className={classNames('form-group-custom w-50', {
'form-group-custom_isInvalid': errorEffort,
})}
>
<Form.Label className="grading-label">
{intl.formatMessage(messages.creditEligibilityLabel)}
</Form.Label>
<Form.Control
data-testid="minimum-grade-credit-input"
type="number"
min={0}
value={Math.round(parseFloat(minimumGradeCredit) * 100) || ''}
name="minimum_grade_credit"
onChange={handleCreditChange}
/>
<Form.Control.Feedback className="grading-description">
{intl.formatMessage(messages.creditEligibilityDescription)}
</Form.Control.Feedback>
{errorEffort && (
<Form.Control.Feedback className="feedback-error" type="invalid">
{intl.formatMessage(messages.creditEligibilityErrorMsg)} {eligibleGrade}.
</Form.Control.Feedback>
)}
</Form.Group>
);
};
CreditSection.propTypes = {
intl: intlShape.isRequired,
eligibleGrade: PropTypes.number.isRequired,
setShowSavePrompt: PropTypes.func.isRequired,
setGradingData: PropTypes.func.isRequired,
setShowSuccessAlert: PropTypes.func.isRequired,
minimumGradeCredit: PropTypes.number.isRequired,
};
export default injectIntl(CreditSection);

View File

@@ -0,0 +1,18 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
creditEligibilityLabel: {
id: 'course-authoring.grading-settings.credit.eligibility.label',
defaultMessage: 'Minimum credit-eligible grade:',
},
creditEligibilityDescription: {
id: 'course-authoring.grading-settings.credit.eligibility.description',
defaultMessage: '% Must be greater than or equal to the course passing grade',
},
creditEligibilityErrorMsg: {
id: 'course-authoring.grading-settings.credit.eligibility.error.msg',
defaultMessage: 'Not able to set passing grade to less than:',
},
});
export default messages;

View File

@@ -0,0 +1,43 @@
/* eslint-disable import/prefer-default-export */
import { camelCaseObject, getConfig } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { deepConvertingKeysToSnakeCase } from '../../utils';
const getApiBaseUrl = () => getConfig().STUDIO_BASE_URL;
export const getGradingSettingsApiUrl = (courseId) => `${getApiBaseUrl()}/api/contentstore/v1/course_grading/${courseId}`;
export const getCourseSettingsApiUrl = (courseId) => `${getApiBaseUrl()}/api/contentstore/v1/course_settings/${courseId}`;
/**
* Get's grading setting for a course.
* @param {string} courseId
* @returns {Promise<Object>}
*/
export async function getGradingSettings(courseId) {
const { data } = await getAuthenticatedHttpClient()
.get(getGradingSettingsApiUrl(courseId));
return camelCaseObject(data);
}
/**
* Send`s grading setting for a course.
* @param {string} courseId
* @param {object} settings
* @returns {Promise<Object>}
*/
export async function sendGradingSettings(courseId, settings) {
const { data } = await getAuthenticatedHttpClient()
.post(getGradingSettingsApiUrl(courseId), deepConvertingKeysToSnakeCase(settings));
return camelCaseObject(data);
}
/**
* Get course settings.
* @param {string} courseId
* @returns {Promise<Object>}
*/
export async function getCourseSettings(courseId) {
const { data } = await getAuthenticatedHttpClient()
.get(getCourseSettingsApiUrl(courseId));
return camelCaseObject(data);
}

View File

@@ -0,0 +1,13 @@
const getLoadingStatus = (state) => state.gradingSettings.loadingStatus;
const getGradingSettings = (state) => state.gradingSettings.gradingSettings.courseDetails;
const getCourseAssignmentLists = (state) => state.gradingSettings.gradingSettings.courseAssignmentLists;
const getSavingStatus = (state) => state.gradingSettings.savingStatus;
const getCourseSettings = (state) => state.gradingSettings.courseSettings;
export {
getLoadingStatus,
getGradingSettings,
getCourseAssignmentLists,
getSavingStatus,
getCourseSettings,
};

View File

@@ -0,0 +1,43 @@
/* eslint-disable no-param-reassign */
import { createSlice } from '@reduxjs/toolkit';
import { RequestStatus } from '../../data/constants';
const slice = createSlice({
name: 'gradingSettings',
initialState: {
loadingStatus: RequestStatus.IN_PROGRESS,
savingStatus: '',
gradingSettings: {},
courseSettings: {},
},
reducers: {
updateLoadingStatus: (state, { payload }) => {
state.loadingStatus = payload.status;
},
updateSavingStatus: (state, { payload }) => {
state.savingStatus = payload.status;
},
fetchGradingSettingsSuccess: (state, { payload }) => {
Object.assign(state.gradingSettings, payload);
},
sendGradingSettingsSuccess: (state, { payload }) => {
Object.assign(state.gradingSettings, payload);
},
fetchCourseSettingsSuccess: (state, { payload }) => {
Object.assign(state.courseSettings, payload);
},
},
});
export const {
updateLoadingStatus,
updateSavingStatus,
fetchGradingSettingsSuccess,
sendGradingSettingsSuccess,
fetchCourseSettingsSuccess,
} = slice.actions;
export const {
reducer,
} = slice;

View File

@@ -0,0 +1,55 @@
import { RequestStatus } from '../../data/constants';
import {
getGradingSettings,
sendGradingSettings,
getCourseSettings,
} from './api';
import {
sendGradingSettingsSuccess,
updateLoadingStatus,
updateSavingStatus,
fetchGradingSettingsSuccess,
fetchCourseSettingsSuccess,
} from './slice';
export function fetchGradingSettings(courseId) {
return async (dispatch) => {
dispatch(updateLoadingStatus({ status: RequestStatus.IN_PROGRESS }));
try {
const settingValues = await getGradingSettings(courseId);
dispatch(fetchGradingSettingsSuccess(settingValues));
dispatch(updateLoadingStatus({ status: RequestStatus.SUCCESSFUL }));
} catch (error) {
dispatch(updateLoadingStatus({ status: RequestStatus.FAILED }));
}
};
}
export function sendGradingSetting(courseId, settings) {
return async (dispatch) => {
dispatch(updateSavingStatus({ status: RequestStatus.IN_PROGRESS }));
try {
const settingValues = await sendGradingSettings(courseId, settings);
dispatch(sendGradingSettingsSuccess(settingValues));
dispatch(updateSavingStatus({ status: RequestStatus.SUCCESSFUL }));
} catch (error) {
dispatch(updateLoadingStatus({ status: RequestStatus.FAILED }));
}
};
}
export function fetchCourseSettingsQuery(courseId) {
return async (dispatch) => {
dispatch(updateLoadingStatus({ status: RequestStatus.IN_PROGRESS }));
try {
const settingsValues = await getCourseSettings(courseId);
dispatch(fetchCourseSettingsSuccess(settingsValues));
dispatch(updateLoadingStatus({ status: RequestStatus.SUCCESSFUL }));
return true;
} catch (error) {
dispatch(updateLoadingStatus({ status: RequestStatus.FAILED }));
return false;
}
};
}

View File

@@ -0,0 +1,48 @@
import React from 'react';
import { fireEvent, render, waitFor } from '@testing-library/react';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import DeadlineSection from '.';
import messages from './messages';
const testObj = {};
const setGradingData = (fn) => {
testObj.gracePeriod = fn({}).gracePeriod;
};
const gracePeriodDefaultTime = {
hours: 12, minutes: 12,
};
const RootWrapper = (props = {}) => (
<IntlProvider locale="en">
<DeadlineSection
setShowSavePrompt={jest.fn()}
gracePeriod={gracePeriodDefaultTime}
setGradingData={jest.fn()}
setShowSuccessAlert={jest.fn()}
{...props}
/>
</IntlProvider>
);
describe('<DeadlineSection />', () => {
it('checking deadline label and description text', async () => {
const { getByText } = render(<RootWrapper />);
await waitFor(() => {
expect(getByText(messages.gracePeriodOnDeadlineLabel.defaultMessage)).toBeInTheDocument();
expect(getByText(messages.gracePeriodOnDeadlineDescription.defaultMessage)).toBeInTheDocument();
});
});
it('checking deadline input value', async () => {
const { getByTestId } = render(<RootWrapper setGradingData={setGradingData} />);
await waitFor(() => {
const inputElement = getByTestId('deadline-period-input');
expect(inputElement.value).toBe('12:12');
fireEvent.change(inputElement, { target: { value: '13:13' } });
expect(testObj.gracePeriod.hours).toBe(13);
expect(testObj.gracePeriod.minutes).toBe(13);
});
});
});

View File

@@ -0,0 +1,63 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Form } from '@edx/paragon';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import messages from './messages';
const DEFAULT_TIME_STAMP = '00:00';
const DeadlineSection = ({
intl, setShowSavePrompt, gracePeriod, setGradingData, setShowSuccessAlert,
}) => {
const formatTime = (time) => (time >= 10 ? time.toString() : `0${time}`);
const timeStampValue = gracePeriod
? `${formatTime(gracePeriod.hours) }:${ formatTime(gracePeriod.minutes)}` : DEFAULT_TIME_STAMP;
const handleDeadlineChange = (e) => {
const hoursAndMinutes = e.target.value.split(':');
setShowSavePrompt(true);
setGradingData(prevData => ({
...prevData,
gracePeriod: {
hours: Number(hoursAndMinutes[0]),
minutes: parseInt(hoursAndMinutes[1] ?? 0, 10),
},
}));
setShowSuccessAlert(false);
};
return (
<Form.Group className="w-50">
<Form.Label className="grading-label">
{intl.formatMessage(messages.gracePeriodOnDeadlineLabel)}
</Form.Label>
<Form.Control
data-testid="deadline-period-input"
type="time"
value={timeStampValue}
onChange={handleDeadlineChange}
/>
<Form.Control.Feedback className="grading-description">
{intl.formatMessage(messages.gracePeriodOnDeadlineDescription)}
</Form.Control.Feedback>
</Form.Group>
);
};
DeadlineSection.defaultProps = {
gracePeriod: null,
};
DeadlineSection.propTypes = {
intl: intlShape.isRequired,
setShowSavePrompt: PropTypes.func.isRequired,
setGradingData: PropTypes.func.isRequired,
setShowSuccessAlert: PropTypes.func.isRequired,
gracePeriod: PropTypes.shape({
hours: PropTypes.number,
minutes: PropTypes.number,
}),
};
export default injectIntl(DeadlineSection);

View File

@@ -0,0 +1,14 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
gracePeriodOnDeadlineLabel: {
id: 'course-authoring.grading-settings.deadline.label',
defaultMessage: 'Grace period on deadline:',
},
gracePeriodOnDeadlineDescription: {
id: 'course-authoring.grading-settings.deadline.description',
defaultMessage: 'Leeway on due dates',
},
});
export default messages;

View File

@@ -0,0 +1,247 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { useRanger } from 'react-ranger';
import { Icon, IconButton } from '@edx/paragon';
import { Add as IconAdd } from '@edx/paragon/icons';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import messages from './messages';
import { convertGradeData, MAXIMUM_SCALE_LENGTH } from './utils';
import { GradingScaleTicks, GradingScaleHandle, GradingScaleSegment } from './components';
const DEFAULT_LETTERS = ['A', 'B', 'C', 'D'];
const GradingScale = ({
intl,
showSavePrompt,
gradeCutoffs,
setShowSuccessAlert,
setGradingData,
resetDataRef,
gradeLetters,
sortedGrades,
setOverrideInternetConnectionAlert,
setEligibleGrade,
}) => {
const [gradingSegments, setGradingSegments] = useState(sortedGrades);
const [letters, setLetters] = useState(gradeLetters);
const [convertedResult, setConvertedResult] = useState({});
const gradingSegmentsValues = Object.values(gradingSegments);
const eligibleValue = gradingSegmentsValues[gradingSegmentsValues.length - 1];
useEffect(() => {
if (resetDataRef.current) {
setGradingSegments(sortedGrades);
setLetters(gradeLetters);
// eslint-disable-next-line no-param-reassign
resetDataRef.current = false;
}
}, [gradeCutoffs]);
useEffect(() => {
setGradingSegments(sortedGrades);
setLetters(gradeLetters);
}, [sortedGrades.length]);
useEffect(() => {
setGradingData(prevData => ({ ...prevData, gradeCutoffs: convertedResult }));
setEligibleGrade(eligibleValue?.current);
}, [JSON.stringify(convertedResult)]);
useEffect(() => {
convertGradeData(letters, gradingSegments, setConvertedResult);
}, [gradingSegments, letters]);
const addNewGradingSegment = () => {
setGradingSegments(prevSegments => {
const firstSegment = prevSegments[prevSegments.length - 1];
const secondSegment = prevSegments[prevSegments.length - 2];
const newCurrentValue = Math.ceil((secondSegment.current - secondSegment.previous) / 2);
const newSegment = {
current: (firstSegment.current + newCurrentValue),
previous: firstSegment.current,
};
const updatedSecondSegment = {
...secondSegment,
previous: (firstSegment.current + newCurrentValue),
};
showSavePrompt(true);
setShowSuccessAlert(false);
setOverrideInternetConnectionAlert(false);
return [
...prevSegments.slice(0, prevSegments.length - 2),
updatedSecondSegment,
newSegment,
firstSegment,
];
});
const nextIndex = (letters.length % DEFAULT_LETTERS.length);
if (gradingSegments.length === 2) {
setLetters([DEFAULT_LETTERS[0], DEFAULT_LETTERS[nextIndex]]);
} else {
setLetters(prevLetters => [...prevLetters, DEFAULT_LETTERS[nextIndex]]);
}
};
const updateGradingSegments = (newGradingSegmentData, activeHandleIndex) => {
const gapToSegment = 1;
const sortedSegments = newGradingSegmentData.sort((currentValue, previousValue) => currentValue - previousValue);
const newSegmentValue = sortedSegments[sortedSegments.length - 1 - activeHandleIndex];
const prevSegmentBoundary = (gradingSegments[activeHandleIndex + 1]
&& gradingSegments[activeHandleIndex + 1].current) || 0;
const nextSegmentBoundary = gradingSegments[activeHandleIndex - 1].current;
showSavePrompt(true);
setGradingSegments(gradingSegments.map((gradingSegment, idx) => {
const upperBoundaryValue = (newSegmentValue < nextSegmentBoundary - gapToSegment)
? newSegmentValue : (nextSegmentBoundary - gapToSegment);
const lowerBoundaryValue = (upperBoundaryValue > prevSegmentBoundary + gapToSegment)
? upperBoundaryValue : (prevSegmentBoundary + gapToSegment);
if (idx === activeHandleIndex - 1) {
return {
previous: lowerBoundaryValue,
current: gradingSegment.current,
};
}
if (idx === activeHandleIndex) {
return {
current: lowerBoundaryValue,
previous: gradingSegment.previous,
};
}
return gradingSegment;
}));
};
const removeGradingSegment = (gradingSegmentIndex) => {
setGradingSegments(prevSegments => {
const updatedSegments = [...prevSegments];
const removedSegment = updatedSegments.splice(gradingSegmentIndex - 1, 1)[0];
const previousSegment = updatedSegments[gradingSegmentIndex - 2];
if (previousSegment) {
previousSegment.previous = removedSegment.previous;
}
return updatedSegments;
});
showSavePrompt(true);
setShowSuccessAlert(false);
setOverrideInternetConnectionAlert(false);
setLetters(prevLetters => {
const updatedLetters = [...prevLetters];
updatedLetters.splice(updatedLetters.length - 1, 1);
return updatedLetters.length === 1 ? ['pass'] : updatedLetters;
});
};
const handleLetterChange = (e, idx) => {
const { value } = e.target;
showSavePrompt(true);
setShowSuccessAlert(false);
setOverrideInternetConnectionAlert(false);
setLetters(prevLetters => {
const updatedLetters = [...prevLetters];
const emptyString = '\u200B';
updatedLetters[idx - 1] = value || emptyString;
return updatedLetters;
});
};
const handleSegmentChange = () => {
setShowSuccessAlert(false);
setOverrideInternetConnectionAlert(false);
setGradingData(prevData => ({ ...prevData, gradeCutoffs: convertedResult }));
};
const {
getTrackProps,
ticks,
segments,
handles,
activeHandleIndex,
} = useRanger({
min: 0,
max: MAXIMUM_SCALE_LENGTH,
stepSize: 1,
values: gradingSegments?.map(segment => segment.current),
onDrag: (segmentDataArray) => updateGradingSegments(segmentDataArray, activeHandleIndex),
onChange: handleSegmentChange,
});
return (
<div className="grading-scale">
<IconButton
disabled={gradingSegments.length >= 5}
data-testid="grading-scale-btn-add-segment"
className="mr-3"
src={IconAdd}
iconAs={Icon}
alt={intl.formatMessage(messages.addNewSegmentButtonAltText)}
onClick={addNewGradingSegment}
/>
<div className="grading-scale-segments-and-ticks" {...getTrackProps()}>
{ticks.map(({ value, getTickProps }) => (
<GradingScaleTicks key={value} value={value} getTickProps={getTickProps} />
))}
{segments.reverse().map(({ value, getSegmentProps }, idx = 1) => (
<GradingScaleSegment
key={idx}
getSegmentProps={getSegmentProps}
removeGradingSegment={removeGradingSegment}
gradingSegments={gradingSegments}
value={value}
idx={idx}
handleLetterChange={handleLetterChange}
letters={letters}
/>
))}
{handles.map(({ value, getHandleProps }, idx) => (
<GradingScaleHandle
key={value}
getHandleProps={getHandleProps}
gradingSegments={gradingSegments}
value={value}
idx={idx}
/>
))}
</div>
</div>
);
};
GradingScale.propTypes = {
intl: intlShape.isRequired,
showSavePrompt: PropTypes.func.isRequired,
gradeCutoffs: PropTypes.objectOf(PropTypes.number).isRequired,
gradeLetters: PropTypes.arrayOf(PropTypes.string).isRequired,
setShowSuccessAlert: PropTypes.func.isRequired,
setGradingData: PropTypes.func.isRequired,
setOverrideInternetConnectionAlert: PropTypes.func.isRequired,
resetDataRef: PropTypes.objectOf(PropTypes.bool).isRequired,
sortedGrades: PropTypes.arrayOf(
PropTypes.shape({
current: PropTypes.number.isRequired,
previous: PropTypes.number.isRequired,
}),
).isRequired,
setEligibleGrade: PropTypes.func.isRequired,
};
export default injectIntl(GradingScale);

View File

@@ -0,0 +1,116 @@
.grading-scale {
display: flex;
margin-top: 1.5rem;
align-items: center;
margin-bottom: map-get($spacers, 6);
.grading-scale-segments-and-ticks {
display: inline-block;
height: 3.125rem;
width: 100%;
border: 1px solid $black;
.grading-scale-tick {
.grading-scale-tick-number {
position: absolute;
font-size: .6rem;
color: $black;
top: 100%;
transform: translate(-50%, 1.2rem);
white-space: nowrap;
}
&::before {
content: "";
position: absolute;
left: 0;
background-color: $gray-400;
height: .3125rem;
width: .125rem;
transform: translate(-50%, .7rem);
}
}
.grading-scale-segment-btn-resize {
border: none;
outline: none;
background-color: transparent;
appearance: none;
height: 100%;
padding: 0 .4375rem;
width: .625rem;
z-index: $zindex-dropdown !important;
&:disabled {
color: $black;
}
}
.grading-scale-segment {
height: 100%;
&:last-child {
border-right: none;
}
.segment--1 {
display: none;
}
@each $key, $color in $grading-scale-segment-colors {
&.segment-#{$key} {
background-color: $color;
z-index: #{$key};
}
}
&:hover .grading-scale-segment-btn-remove {
display: block;
}
.grading-scale-segment-btn-remove {
font: normal $font-weight-normal .6875rem/1 $font-family-base;
display: none;
position: absolute;
top: -1.5625rem;
right: -.375rem;
padding: .375rem;
text-decoration: none;
}
.grading-scale-segment-content {
display: flex;
flex-direction: column;
margin-right: 1.25rem;
margin-top: .375rem;
font-size: .7rem;
white-space: nowrap;
text-align: right;
align-items: flex-end;
}
.grading-scale-segment-content-title {
font: normal $font-weight-semi-bold 1rem/1 $font-family-base;
border: none;
outline: none;
background-color: transparent;
appearance: none;
cursor: text;
text-align: end;
&:disabled {
color: $black;
}
}
.grading-scale-segment-content-number {
font: normal $font-weight-normal .75rem/1 $font-family-base;
color: $black;
&:disabled {
color: $black;
}
}
}
}
}

View File

@@ -0,0 +1,126 @@
import React from 'react';
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
import { initializeMockApp } from '@edx/frontend-platform';
import { render, waitFor, fireEvent } from '@testing-library/react';
import GradingScale from './GradingScale';
const gradeCutoffs = { A: 0.9, B: 0.8, C: 0.7 };
const gradeLetters = ['A', 'B', 'C', 'D'];
const sortedGrades = [
{ current: 100, previous: 49 },
{ current: 49, previous: 41 },
{ current: 32, previous: 20 },
{ current: 20, previous: 0 },
];
const RootWrapper = () => (
<IntlProvider locale="en" messages={{}}>
<GradingScale
intl={injectIntl}
gradeCutoffs={gradeCutoffs}
gradeLetters={gradeLetters}
sortedGrades={sortedGrades}
resetDataRef={{ current: false }}
showSavePrompt={jest.fn()}
setShowSuccessAlert={jest.fn()}
setGradingData={jest.fn()}
setOverrideInternetConnectionAlert={jest.fn()}
setEligibleGrade={jest.fn()}
/>
</IntlProvider>
);
describe('<GradingScale />', () => {
beforeEach(() => {
initializeMockApp({
authenticatedUser: {
userId: 3,
username: 'abc123',
administrator: true,
roles: [],
},
});
});
it('renders grading scale ticks', async () => {
const { getAllByTestId } = render(<RootWrapper />);
await waitFor(() => {
const ticks = getAllByTestId('grading-scale-tick');
expect(ticks).toHaveLength(11); // 0 to 100, inclusive, with step 10
});
});
it('renders grading scale segments', async () => {
const { getAllByTestId } = render(<RootWrapper />);
await waitFor(() => {
const segments = getAllByTestId('grading-scale-segment');
expect(segments).toHaveLength(5);
});
});
it('should add a new grading segment when "Add new grading segment" button is clicked', async () => {
const { debug, getAllByTestId } = render(<RootWrapper />);
await waitFor(() => {
const addNewSegmentBtn = getAllByTestId('grading-scale-btn-add-segment');
expect(addNewSegmentBtn[0]).toBeInTheDocument();
fireEvent.click(addNewSegmentBtn[0]);
const segments = getAllByTestId('grading-scale-segment');
expect(segments).toHaveLength(6);
debug(addNewSegmentBtn);
});
});
it('should remove grading segment when "Remove" button is clicked', async () => {
const { getAllByTestId } = render(<RootWrapper />);
await waitFor(() => {
const segments = getAllByTestId('grading-scale-segment');
const removeSegmentBtn = getAllByTestId('grading-scale-btn-remove');
fireEvent.click(removeSegmentBtn[1]);
expect(segments).toHaveLength(5);
});
});
it('should update segment input value', async () => {
const { getAllByTestId } = render(<RootWrapper />);
await waitFor(() => {
const segmentInputs = getAllByTestId('grading-scale-segment-input');
expect(segmentInputs).toHaveLength(5);
const segmentInput = segmentInputs[1];
fireEvent.change(segmentInput, { target: { value: 'Test' } });
expect(segmentInput).toHaveValue('TEST');
});
});
it('should render GradingScale component with short grade cutoffs and sorted grades', async () => {
const shortGradeCutoffs = { Pass: 0.9 };
const shortSortedGrades = [
{ current: 100, previous: 49 },
{ current: 20, previous: 0 },
];
const { getAllByTestId } = render(
<IntlProvider locale="en" messages={{}}>
<GradingScale
intl={injectIntl}
gradeCutoffs={shortGradeCutoffs}
gradeLetters={['A']}
sortedGrades={shortSortedGrades}
resetDataRef={{ current: false }}
showSavePrompt={jest.fn()}
setShowSuccessAlert={jest.fn()}
setGradingData={jest.fn()}
setOverrideInternetConnectionAlert={jest.fn()}
setEligibleGrade={jest.fn()}
/>
</IntlProvider>,
);
await waitFor(() => {
const segmentInputs = getAllByTestId('grading-scale-segment-input');
expect(segmentInputs[0]).toHaveValue('Fail');
fireEvent.change(segmentInputs[1], { target: { value: 'Test' } });
expect(segmentInputs[1]).toHaveValue('TEST');
});
});
});

View File

@@ -0,0 +1,34 @@
import React from 'react';
import PropTypes from 'prop-types';
import { MAXIMUM_SCALE_LENGTH } from '../utils';
const GradingScaleHandle = ({
idx, value, gradingSegments, getHandleProps,
}) => (
<button
key={value}
className="grading-scale-segment-btn-resize"
type="button"
disabled={gradingSegments[idx].current === MAXIMUM_SCALE_LENGTH}
{...getHandleProps({
style: gradingSegments[idx].current === MAXIMUM_SCALE_LENGTH ? {
cursor: 'default', display: 'none',
} : { cursor: 'e-resize' },
})}
/>
);
GradingScaleHandle.propTypes = {
idx: PropTypes.number.isRequired,
value: PropTypes.number.isRequired,
getHandleProps: PropTypes.func.isRequired,
gradingSegments: PropTypes.arrayOf(
PropTypes.shape({
current: PropTypes.number.isRequired,
previous: PropTypes.number.isRequired,
}),
).isRequired,
};
export default GradingScaleHandle;

View File

@@ -0,0 +1,79 @@
import { Button } from '@edx/paragon';
import React from 'react';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import PropTypes from 'prop-types';
import { getLettersOnLongScale, getLettersOnShortScale } from '../utils';
import messages from '../messages';
const GradingScaleSegment = ({
intl,
idx,
value,
getSegmentProps,
handleLetterChange,
letters,
gradingSegments,
removeGradingSegment,
}) => (
<div
key={value}
className={`grading-scale-segment segment-${idx - 1}`}
data-testid="grading-scale-segment"
{...getSegmentProps()}
>
<div className="grading-scale-segment-content">
{gradingSegments.length === 2 && (
<input
className="grading-scale-segment-content-title m-0"
data-testid="grading-scale-segment-input"
value={getLettersOnShortScale(idx, letters, intl)}
onChange={e => handleLetterChange(e, idx)}
disabled={idx === gradingSegments.length}
/>
)}
{gradingSegments.length > 2 && (
<input
className="grading-scale-segment-content-title m-0"
data-testid="grading-scale-segment-input"
value={getLettersOnLongScale(idx, letters, gradingSegments)}
onChange={e => handleLetterChange(e, idx)}
disabled={idx === gradingSegments.length}
/>
)}
<span className="grading-scale-segment-content-number m-0">
{gradingSegments[idx === 0 ? 0 : idx - 1]?.previous} - {value}
</span>
</div>
{idx !== gradingSegments.length && idx - 1 !== 0 && (
<Button
variant="link"
size="inline"
className="grading-scale-segment-btn-remove"
data-testid="grading-scale-btn-remove"
type="button"
onClick={() => removeGradingSegment(idx)}
>
{intl.formatMessage(messages.removeSegmentButtonText)}
</Button>
)}
</div>
);
GradingScaleSegment.propTypes = {
intl: intlShape.isRequired,
idx: PropTypes.number.isRequired,
value: PropTypes.number.isRequired,
getSegmentProps: PropTypes.func.isRequired,
handleLetterChange: PropTypes.func.isRequired,
removeGradingSegment: PropTypes.func.isRequired,
gradingSegments: PropTypes.arrayOf(
PropTypes.shape({
current: PropTypes.number.isRequired,
previous: PropTypes.number.isRequired,
}),
).isRequired,
letters: PropTypes.arrayOf(PropTypes.string).isRequired,
};
export default injectIntl(GradingScaleSegment);

View File

@@ -0,0 +1,15 @@
import React from 'react';
import PropTypes from 'prop-types';
const GradingScaleTick = ({ getTickProps, value }) => (
<div className="mt-5 grading-scale-tick" data-testid="grading-scale-tick" {...getTickProps()}>
<div className="grading-scale-tick-number">{value}</div>
</div>
);
GradingScaleTick.propTypes = {
value: PropTypes.number.isRequired,
getTickProps: PropTypes.func.isRequired,
};
export default GradingScaleTick;

View File

@@ -0,0 +1,3 @@
export { default as GradingScaleHandle } from './GradingScaleHandle';
export { default as GradingScaleTicks } from './GradingScaleTick';
export { default as GradingScaleSegment } from './GradingScaleSegment';

View File

@@ -0,0 +1,18 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
addNewSegmentButtonAltText: {
id: 'course-authoring.grading-settings.add-new-segment.btn.text',
defaultMessage: 'Add new grading segment',
},
removeSegmentButtonText: {
id: 'course-authoring.grading-settings.remove-segment.btn.text',
defaultMessage: 'Remove',
},
segmentFailGradingText: {
id: 'course-authoring.grading-settings.fail-segment.text',
defaultMessage: 'Fail',
},
});
export default messages;

View File

@@ -0,0 +1,90 @@
import messages from './messages';
export const MAXIMUM_SCALE_LENGTH = 100;
/**
* Converting fractional numbers to integers.
*
* @param {object} cutoffs - The object containing the settings to grading cutoffs.
* @returns {array} - Converted grading cutoffs.
*/
export const getGradingValues = (cutoffs) => Object.values(cutoffs)
.map(number => Math.round(number * MAXIMUM_SCALE_LENGTH));
/**
* Initially, the data comes in the format { a: 0.8 },
* this function converts the data structure to the required { current: 100, previous: 80 } format.
*
* @param {object} gradeValues - The object containing the settings to grading cutoffs.
* @returns {object} - New grading cutoffs.
*/
export const getSortedGrades = (gradeValues) => gradeValues.reduce((sortedArray, current, idx) => {
if (idx === (gradeValues.length - 1)) {
sortedArray.push({ current: gradeValues[idx - 1] || MAXIMUM_SCALE_LENGTH, previous: gradeValues[idx] });
sortedArray.push({ current: gradeValues[idx], previous: 0 });
} else if (idx === 0) {
sortedArray.push({ current: MAXIMUM_SCALE_LENGTH, previous: current });
} else {
const previous = gradeValues[idx - 1];
sortedArray.push({ current: previous, previous: current });
}
return sortedArray;
}, []);
/**
* Changes the start and end values of the segments when there are two segments.
*
* @param {number} idx - Segment index.
* @param {array} letters - Names of grading segments.
* @param {array} gradingSegments - Grading cutoffs.
* @returns {string} - Segment display name.
*/
export const getLettersOnLongScale = (idx, letters, gradingSegments) => {
const END_OF_SCALE_NAME = 'F';
if (idx === 0) {
return letters[0];
}
if ((idx - 1) === (gradingSegments.length - 1)) {
return END_OF_SCALE_NAME;
}
return letters[idx - 1].toUpperCase();
};
/**
* Changes the positions of segment names if there are more than two segments.
*
* @param {number} idx - Segment index.
* @param {array} letters - Names of grading segments.
* @returns {string} - Segment display name.
*/
export const getLettersOnShortScale = (idx, letters, intl) => {
const END_OF_SCALE_NAME = intl.formatMessage(messages.segmentFailGradingText);
return (idx === 1 ? letters[idx - 1].toUpperCase() : END_OF_SCALE_NAME);
};
/**
* Converts the data to the original format with fractional numbers { a: 0.8 }.
*
* @param {array} letters - Names of grading segments.
* @param {array} gradingSegments - Grading cutoffs.
* @param {func} setConvertedResult - Changing the state of the converted result.
* @returns {void}
*/
export const convertGradeData = (letters, gradingSegments, setConvertedResult) => {
const convertedData = {};
if (!gradingSegments.length) {
return;
}
letters.forEach((letter, idx) => {
convertedData[letter] = gradingSegments[idx].previous / MAXIMUM_SCALE_LENGTH;
});
setConvertedResult(convertedData);
};

View File

@@ -0,0 +1,31 @@
import React from 'react';
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
import { render } from '@testing-library/react';
import messages from './messages';
import GradingSidebar from '.';
const mockPathname = '/foo-bar';
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useLocation: () => ({
pathname: mockPathname,
}),
}));
const RootWrapper = () => (
<IntlProvider locale="en" messages={{}}>
<GradingSidebar intl={injectIntl} courseId="123" />
</IntlProvider>
);
describe('<GradingSidebar />', () => {
it('renders sidebar text content correctly', () => {
const { getByText } = render(<RootWrapper />);
expect(getByText(messages.gradingSidebarTitle.defaultMessage)).toBeInTheDocument();
expect(getByText(messages.gradingSidebarAbout1.defaultMessage)).toBeInTheDocument();
expect(getByText(messages.gradingSidebarAbout2.defaultMessage)).toBeInTheDocument();
expect(getByText(messages.gradingSidebarAbout3.defaultMessage)).toBeInTheDocument();
});
});

View File

@@ -0,0 +1,39 @@
import React from 'react';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import PropTypes from 'prop-types';
import HelpSidebar from '../../generic/help-sidebar';
import messages from './messages';
const GradingSidebar = ({ intl, courseId, proctoredExamSettingsUrl }) => (
<HelpSidebar
courseId={courseId}
showOtherSettings
proctoredExamSettingsUrl={proctoredExamSettingsUrl}
>
<h4 className="help-sidebar-about-title">
{intl.formatMessage(messages.gradingSidebarTitle)}
</h4>
<p className="help-sidebar-about-descriptions">
{intl.formatMessage(messages.gradingSidebarAbout1)}
</p>
<p className="help-sidebar-about-descriptions">
{intl.formatMessage(messages.gradingSidebarAbout2)}
</p>
<p className="help-sidebar-about-descriptions">
{intl.formatMessage(messages.gradingSidebarAbout3)}
</p>
</HelpSidebar>
);
GradingSidebar.defaultProps = {
proctoredExamSettingsUrl: '',
};
GradingSidebar.propTypes = {
intl: intlShape.isRequired,
courseId: PropTypes.string.isRequired,
proctoredExamSettingsUrl: PropTypes.string,
};
export default injectIntl(GradingSidebar);

View File

@@ -0,0 +1,22 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
gradingSidebarTitle: {
id: 'course-authoring.grading-settings.sidebar.about.title',
defaultMessage: 'What can I do on this page?',
},
gradingSidebarAbout1: {
id: 'course-authoring.grading-settings.sidebar.about.text-1',
defaultMessage: 'You can use the slider under Overall Grade Range to specify whether your course is pass/fail or graded by letter, and to establish the thresholds for each grade.',
},
gradingSidebarAbout2: {
id: 'course-authoring.grading-settings.sidebar.about.text-2',
defaultMessage: 'You can specify whether your course offers students a grace period for late assignments.',
},
gradingSidebarAbout3: {
id: 'course-authoring.grading-settings.sidebar.about.text-3',
defaultMessage: 'You can also create assignment types, such as homework, labs, quizzes, and exams, and specify how much of a student\'s grade each assignment type is worth.',
},
});
export default messages;

View File

@@ -0,0 +1,86 @@
import { useEffect, useRef, useState } from 'react';
import { v4 as uuidv4 } from 'uuid';
import { getGradingValues, getSortedGrades } from './grading-scale/utils';
const useConvertGradeCutoffs = (
gradeCutoffs,
) => {
const gradeLetters = gradeCutoffs && Object.keys(gradeCutoffs);
const gradeValues = gradeCutoffs && getGradingValues(gradeCutoffs);
const sortedGrades = gradeCutoffs && getSortedGrades(gradeValues);
return {
gradeLetters,
gradeValues,
sortedGrades,
};
};
const useUpdateGradingData = (gradingSettingsData, setOverrideInternetConnectionAlert, setShowSuccessAlert) => {
const uniqueId = uuidv4();
const [gradingData, setGradingData] = useState({});
const [showSavePrompt, setShowSavePrompt] = useState(false);
const resetDataRef = useRef(false);
const {
gradeCutoffs = {},
gracePeriod = { hours: '', minutes: '' },
minimumGradeCredit,
graders,
} = gradingData;
useEffect(() => {
if (gradingSettingsData !== undefined) {
setGradingData(gradingSettingsData);
}
}, [gradingSettingsData]);
const handleResetPageData = () => {
setShowSavePrompt(!showSavePrompt);
setShowSuccessAlert(false);
setGradingData(gradingSettingsData);
resetDataRef.current = true;
setOverrideInternetConnectionAlert(false);
};
const handleAddAssignment = () => {
setGradingData(prevState => ({
...prevState,
graders: [...prevState.graders, {
id: uniqueId,
dropCount: 0,
minCount: 1,
shortLabel: '',
type: '',
weight: 0,
}],
}));
setShowSuccessAlert(false);
};
const handleRemoveAssignment = (assignmentId) => {
setGradingData((prevState) => ({
...prevState,
graders: prevState.graders.filter((grade) => grade.id !== assignmentId),
}));
setShowSuccessAlert(false);
setShowSavePrompt(true);
};
return {
graders,
resetDataRef,
setGradingData,
gradingData,
gradeCutoffs,
gracePeriod,
minimumGradeCredit,
showSavePrompt,
setShowSavePrompt,
handleResetPageData,
handleAddAssignment,
handleRemoveAssignment,
};
};
export { useConvertGradeCutoffs, useUpdateGradingData };

View File

@@ -0,0 +1,2 @@
/* eslint-disable import/prefer-default-export */
export { default as GradingSettings } from './GradingSettings';

View File

@@ -0,0 +1,90 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
headingTitle: {
id: 'course-authoring.grading-settings.heading.title',
defaultMessage: 'Grading',
},
headingSubtitle: {
id: 'course-authoring.grading-settings.heading.subtitle',
defaultMessage: 'Settings',
},
policy: {
id: 'course-authoring.grading-settings.policies.title',
defaultMessage: 'Overall grade range',
},
policiesDescription: {
id: 'course-authoring.grading-settings.policies.description',
defaultMessage: 'Your overall grading scale for student final grades',
},
alertWarning: {
id: 'course-authoring.grading-settings.alert.warning',
defaultMessage: "You've made some changes",
},
alertWarningDescriptions: {
id: 'course-authoring.grading-settings.alert.warning.descriptions',
defaultMessage: 'Your changes will not take effect until you save your progress. Take care with key and value formatting, as validation is not implemented.',
},
alertSuccess: {
id: 'course-authoring.grading-settings.alert.success',
defaultMessage: 'Your changes have been saved.',
},
buttonSaveText: {
id: 'course-authoring.grading-settings.alert.button.save',
defaultMessage: 'Save changes',
},
buttonSavingText: {
id: 'course-authoring.grading-settings.alert.button.saving',
defaultMessage: 'Saving',
},
buttonCancelText: {
id: 'course-authoring.grading-settings.alert.button.cancel',
defaultMessage: 'Cancel',
},
alertWarningAriaLabelledby: {
id: 'course-authoring.grading-settings.alert.warning.aria.labelledby',
defaultMessage: 'notification-warning-title',
},
alertWarningAriaDescribedby: {
id: 'course-authoring.grading-settings.alert.warning.aria.describedby',
defaultMessage: 'notification-warning-description',
},
alertSuccessAriaLabelledby: {
id: 'course-authoring.grading-settings.alert.success.aria.labelledby',
defaultMessage: 'alert-confirmation-title',
},
alertSuccessAriaDescribedby: {
id: 'course-authoring.grading-settings.alert.success.aria.describedby',
defaultMessage: 'alert-confirmation-description',
},
creditEligibilitySectionTitle: {
id: 'course-authoring.grading-settings.credit-eligibility.title',
defaultMessage: 'Credit eligibility',
},
creditEligibilitySectionDescription: {
id: 'course-authoring.grading-settings.credit-eligibility.description',
defaultMessage: 'Settings for course credit eligibility',
},
gradingRulesPoliciesSectionTitle: {
id: 'course-authoring.grading-settings.grading-rules-policies.title',
defaultMessage: 'Grading rules & policies',
},
gradingRulesPoliciesSectionDescription: {
id: 'course-authoring.grading-settings.grading-rules-policies.description',
defaultMessage: 'Deadlines, requirements, and logistics around grading student work',
},
assignmentTypeSectionTitle: {
id: 'course-authoring.grading-settings.assignment-type.title',
defaultMessage: 'Assignment types',
},
assignmentTypeSectionDescription: {
id: 'course-authoring.grading-settings.assignment-type.description',
defaultMessage: 'Categories and labels for any exercises that are gradable',
},
addNewAssignmentTypeBtn: {
id: 'course-authoring.grading-settings.add-new-assignment-type.btn',
defaultMessage: 'New assignment type',
},
});
export default messages;

View File

@@ -0,0 +1,13 @@
@import "variables";
@import "../assignment-section/AssignmentSection.scss";
@import "../grading-scale/GradingScale.scss";
.grading-label {
font: normal $font-weight-bold .75rem/1.25rem $font-family-base;
color: $gray-500;
}
.grading-description {
font: normal .75rem/1.5rem $font-family-base;
color: $gray-700;
}

View File

@@ -0,0 +1,7 @@
$grading-scale-segment-colors: (
0: #87D771,
1: #FDE370,
2: #F6BF58,
3: #FF7E6A,
4: #E94949,
);

View File

@@ -357,7 +357,7 @@
"course-authoring.advanced-settings.heading.title": "Advanced settings",
"course-authoring.advanced-settings.heading.subtitle": "Settings",
"course-authoring.advanced-settings.policies.title": "Manual policy definition",
"course-authoring.advanced-settings.alert.warning": "You`ve made some changes",
"course-authoring.advanced-settings.alert.warning": "You've made some changes",
"course-authoring.advanced-settings.alert.warning.descriptions": "Your changes will not take effect until you save your progress. Take care with key and value formatting, as validation is not implemented.",
"course-authoring.advanced-settings.alert.success": "Your policy changes have been saved.",
"course-authoring.advanced-settings.alert.success.descriptions": "No validation is performed on policy keys or value pairs. If you are having difficulties, check your formatting.",
@@ -384,7 +384,6 @@
"course-authoring.advanced-settings.policies.description": "{notice} Do not modify these policies unless you are familiar with their purpose.",
"course-authoring.advanced-settings.deprecated.button.text": "{visibility} Deprecated Settings",
"course-authoring.advanced-settings.button.help": "Show help text",
"course-authoring.advanced-settings.alert.button.saving": "Saving",
"course-authoring.schedule.basic.title": "Basic information",
"course-authoring.schedule.basic.description": "The nuts and bolts of this course",
"course-authoring.schedule.basic.email-icon": "Invite your students email icon",
@@ -416,7 +415,7 @@
"course-authoring.schedule.alert.button.cancel": "Cancel",
"course-authoring.schedule.alert.warning.aria.labelledby": "notification-warning-title",
"course-authoring.schedule.alert.warning.aria.describedby": "notification-warning-description",
"course-authoring.schedule.alert.warning": "You`ve made some changes",
"course-authoring.schedule.alert.warning": "You've made some changes",
"course-authoring.schedule.alert.warning.descriptions": "Your changes will not take effect until you save your progress.",
"course-authoring.schedule.alert.success.aria.labelledby": "alert-confirmation-title",
"course-authoring.schedule.alert.success.aria.describedby": "alert-confirmation-description",
@@ -550,6 +549,26 @@
"course-authoring.generic.alert.warning.offline.description": "This may be happening because of an error with our server or your internet connection. Try refreshing the page or making sure you are online.",
"course-authoring.generic.alert.warning.offline.title.aria.labelled-by": "alert-internet-error-title",
"course-authoring.generic.alert.warning.offline.subtitle.aria.described-by": "alert-internet-error-description",
"course-authoring.grading-settings.fail-segment.text": "Fail",
"course-authoring.grading-settings.sidebar.about.title": "What can I do on this page?",
"course-authoring.grading-settings.sidebar.about.text-1": "You can use the slider under Overall Grade Range to specify whether your course is pass/fail or graded by letter, and to establish the thresholds for each grade.",
"course-authoring.grading-settings.sidebar.about.text-2": "You can specify whether your course offers students a grace period for late assignments.",
"course-authoring.grading-settings.sidebar.about.text-3": "You can also create assignment types, such as homework, labs, quizzes, and exams, and specify how much of a student's grade each assignment type is worth.",
"course-authoring.grading-settings.add-new-segment.btn.text": "Add new grading segment",
"course-authoring.grading-settings.remove-segment.btn.text": "Remove",
"course-authoring.grading-settings.heading.title": "Grading",
"course-authoring.grading-settings.heading.subtitle": "Settings",
"course-authoring.grading-settings.policies.title": "Overall grade range",
"course-authoring.grading-settings.policies.description": "Your overall grading scale for student final grades",
"course-authoring.grading-settings.alert.warning": "You've made some changes",
"course-authoring.grading-settings.alert.warning.descriptions": "Your changes will not take effect until you save your progress. Take care with key and value formatting, as validation is not implemented.",
"course-authoring.grading-settings.alert.success": "Your changes have been saved.",
"course-authoring.grading-settings.alert.button.save": "Save changes",
"course-authoring.grading-settings.alert.button.cancel": "Cancel",
"course-authoring.grading-settings.alert.warning.aria.labelledby": "notification-warning-title",
"course-authoring.grading-settings.alert.warning.aria.describedby": "notification-warning-description",
"course-authoring.grading-settings.alert.success.aria.labelledby": "alert-confirmation-title",
"course-authoring.grading-settings.alert.success.aria.describedby": "alert-confirmation-description",
"course-authoring.schedule-section.requirements.entrance.label": "Entrance exam",
"course-authoring.schedule-section.requirements.entrance.collapse.title": "Require students to pass an exam before beginning the course.",
"course-authoring.schedule-section.requirements.entrance.collapse.paragraph": "You can now view and author your course entrance exam from the {hyperlink}.",
@@ -585,5 +604,41 @@
"course-authoring.schedule-section.license.creative-commons.option.ND.description": "Allow others to copy, distribute, display and perform only verbatim copies of your work, not derivative works based upon it. This option is incompatible with 'Share Alike'.",
"course-authoring.schedule-section.license.creative-commons.option.SA.label": "Share alike",
"course-authoring.schedule-section.license.creative-commons.option.SA.description": "Allow others to distribute derivative works only under a license identical to the license that governs your work. This option is incompatible with 'No Derivatives'.",
"course-authoring.schedule.alert.button.saving": "Saving"
"course-authoring.schedule.alert.button.saving": "Saving",
"course-authoring.advanced-settings.alert.button.saving": "Saving",
"course-authoring.grading-settings.credit.eligibility.label": "Minimum credit-eligible grade:",
"course-authoring.grading-settings.credit.eligibility.description": "% Must be greater than or equal to the course passing grade",
"course-authoring.grading-settings.credit.eligibility.error.msg": "Not able to set passing grade to less than:",
"course-authoring.grading-settings.deadline.label": "Grace period on deadline:",
"course-authoring.grading-settings.deadline.description": "Leeway on due dates",
"course-authoring.grading-settings.alert.button.saving": "Saving",
"course-authoring.grading-settings.add-new-assignment-type.btn": "New assignment type",
"course-authoring.grading-settings.assignment-type.description": "Categories and labels for any exercises that are gradable",
"course-authoring.grading-settings.assignment-type.title": "Assignment types",
"course-authoring.grading-settings.grading-rules-policies.description": "Deadlines, requirements, and logistics around grading student work",
"course-authoring.grading-settings.grading-rules-policies.title": "Grading rules & policies",
"course-authoring.grading-settings.credit-eligibility.description": "Settings for course credit eligibility",
"course-authoring.grading-settings.credit-eligibility.title": "Credit eligibility",
"course-authoring.grading-settings.assignment.type-name.title": "Assignment type name",
"course-authoring.grading-settings.assignment.type-name.description": "The general category for this type of assignment, for example, Homework or Midterm Exam. This name is visible to learners.",
"course-authoring.grading-settings.assignment.type-name.error.message-1": "The assignment type must have a name.",
"course-authoring.grading-settings.assignment.type-name.error.message-3": "There's already another assignment type with this name.",
"course-authoring.grading-settings.assignment.abbreviation.title": "Abbreviation",
"course-authoring.grading-settings.assignment.abbreviation.description": "This short name for the assignment type (for example, HW or Midterm) appears next to assignments on a learner's Progress page.",
"course-authoring.grading-settings.assignment.weight-of-total-grade.title": "Weight of total grade",
"course-authoring.grading-settings.assignment.weight-of-total-grade.description": "The weight of all assignments of this type as a percentage of the total grade, for example, 40. Do not include the percent symbol.",
"course-authoring.grading-settings.assignment.weight-of-total-grade.error.message": "Please enter an integer between 0 and 100.",
"course-authoring.grading-settings.assignment.total-number.title": "Total number",
"course-authoring.grading-settings.assignment.total-number.description": "The number of subsections in the course that contain problems of this assignment type.",
"course-authoring.grading-settings.assignment.total-number.error.message": "Please enter an integer greater than 0.",
"course-authoring.grading-settings.assignment.number-of-droppable.title": "Number of droppable",
"course-authoring.grading-settings.assignment.number-of-droppable.description": "The number of assignments of this type that will be dropped. The lowest scoring assignments are dropped first.",
"course-authoring.grading-settings.assignment.number-of-droppable.error.message": "Please enter non-negative integer.",
"course-authoring.grading-settings.assignment.alert.warning.description": "There are no assignments of this type in the course.",
"course-authoring.grading-settings.assignment.delete.button": "Delete",
"course-authoring.grading-settings.assignment.number-of-droppable.second.error.message": "Cannot drop more {type} assignments than are assigned.",
"course-authoring.grading-settings.assignment.alert.warning.usage.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:",
"course-authoring.grading-settings.assignment.alert.warning.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:",
"course-authoring.grading-settings.assignment.alert.success.title": "The number of {type} assignments in the course matches the number defined here.",
"course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}"
}

View File

@@ -357,7 +357,7 @@
"course-authoring.advanced-settings.heading.title": "Advanced settings",
"course-authoring.advanced-settings.heading.subtitle": "Settings",
"course-authoring.advanced-settings.policies.title": "Manual policy definition",
"course-authoring.advanced-settings.alert.warning": "You`ve made some changes",
"course-authoring.advanced-settings.alert.warning": "You've made some changes",
"course-authoring.advanced-settings.alert.warning.descriptions": "Your changes will not take effect until you save your progress. Take care with key and value formatting, as validation is not implemented.",
"course-authoring.advanced-settings.alert.success": "Your policy changes have been saved.",
"course-authoring.advanced-settings.alert.success.descriptions": "No validation is performed on policy keys or value pairs. If you are having difficulties, check your formatting.",
@@ -416,7 +416,7 @@
"course-authoring.schedule.alert.button.cancel": "Cancel",
"course-authoring.schedule.alert.warning.aria.labelledby": "notification-warning-title",
"course-authoring.schedule.alert.warning.aria.describedby": "notification-warning-description",
"course-authoring.schedule.alert.warning": "You`ve made some changes",
"course-authoring.schedule.alert.warning": "You've made some changes",
"course-authoring.schedule.alert.warning.descriptions": "Your changes will not take effect until you save your progress.",
"course-authoring.schedule.alert.success.aria.labelledby": "alert-confirmation-title",
"course-authoring.schedule.alert.success.aria.describedby": "alert-confirmation-description",
@@ -550,6 +550,26 @@
"course-authoring.generic.alert.warning.offline.description": "This may be happening because of an error with our server or your internet connection. Try refreshing the page or making sure you are online.",
"course-authoring.generic.alert.warning.offline.title.aria.labelled-by": "alert-internet-error-title",
"course-authoring.generic.alert.warning.offline.subtitle.aria.described-by": "alert-internet-error-description",
"course-authoring.grading-settings.fail-segment.text": "Fail",
"course-authoring.grading-settings.sidebar.about.title": "What can I do on this page?",
"course-authoring.grading-settings.sidebar.about.text-1": "You can use the slider under Overall Grade Range to specify whether your course is pass/fail or graded by letter, and to establish the thresholds for each grade.",
"course-authoring.grading-settings.sidebar.about.text-2": "You can specify whether your course offers students a grace period for late assignments.",
"course-authoring.grading-settings.sidebar.about.text-3": "You can also create assignment types, such as homework, labs, quizzes, and exams, and specify how much of a student's grade each assignment type is worth.",
"course-authoring.grading-settings.add-new-segment.btn.text": "Add new grading segment",
"course-authoring.grading-settings.remove-segment.btn.text": "Remove",
"course-authoring.grading-settings.heading.title": "Grading",
"course-authoring.grading-settings.heading.subtitle": "Settings",
"course-authoring.grading-settings.policies.title": "Overall grade range",
"course-authoring.grading-settings.policies.description": "Your overall grading scale for student final grades",
"course-authoring.grading-settings.alert.warning": "You've made some changes",
"course-authoring.grading-settings.alert.warning.descriptions": "Your changes will not take effect until you save your progress. Take care with key and value formatting, as validation is not implemented.",
"course-authoring.grading-settings.alert.success": "Your changes have been saved.",
"course-authoring.grading-settings.alert.button.save": "Save changes",
"course-authoring.grading-settings.alert.button.cancel": "Cancel",
"course-authoring.grading-settings.alert.warning.aria.labelledby": "notification-warning-title",
"course-authoring.grading-settings.alert.warning.aria.describedby": "notification-warning-description",
"course-authoring.grading-settings.alert.success.aria.labelledby": "alert-confirmation-title",
"course-authoring.grading-settings.alert.success.aria.describedby": "alert-confirmation-description",
"course-authoring.schedule-section.requirements.entrance.label": "Entrance exam",
"course-authoring.schedule-section.requirements.entrance.collapse.title": "Require students to pass an exam before beginning the course.",
"course-authoring.schedule-section.requirements.entrance.collapse.paragraph": "You can now view and author your course entrance exam from the {hyperlink}.",
@@ -585,5 +605,40 @@
"course-authoring.schedule-section.license.creative-commons.option.ND.description": "Allow others to copy, distribute, display and perform only verbatim copies of your work, not derivative works based upon it. This option is incompatible with 'Share Alike'.",
"course-authoring.schedule-section.license.creative-commons.option.SA.label": "Share alike",
"course-authoring.schedule-section.license.creative-commons.option.SA.description": "Allow others to distribute derivative works only under a license identical to the license that governs your work. This option is incompatible with 'No Derivatives'.",
"course-authoring.schedule.alert.button.saving": "Saving"
"course-authoring.schedule.alert.button.saving": "Saving",
"course-authoring.grading-settings.credit.eligibility.label": "Minimum credit-eligible grade:",
"course-authoring.grading-settings.credit.eligibility.description": "% Must be greater than or equal to the course passing grade",
"course-authoring.grading-settings.credit.eligibility.error.msg": "Not able to set passing grade to less than:",
"course-authoring.grading-settings.deadline.label": "Grace period on deadline:",
"course-authoring.grading-settings.deadline.description": "Leeway on due dates",
"course-authoring.grading-settings.alert.button.saving": "Saving",
"course-authoring.grading-settings.add-new-assignment-type.btn": "New assignment type",
"course-authoring.grading-settings.assignment-type.description": "Categories and labels for any exercises that are gradable",
"course-authoring.grading-settings.assignment-type.title": "Assignment types",
"course-authoring.grading-settings.grading-rules-policies.description": "Deadlines, requirements, and logistics around grading student work",
"course-authoring.grading-settings.grading-rules-policies.title": "Grading rules & policies",
"course-authoring.grading-settings.credit-eligibility.description": "Settings for course credit eligibility",
"course-authoring.grading-settings.credit-eligibility.title": "Credit eligibility",
"course-authoring.grading-settings.assignment.type-name.title": "Assignment type name",
"course-authoring.grading-settings.assignment.type-name.description": "The general category for this type of assignment, for example, Homework or Midterm Exam. This name is visible to learners.",
"course-authoring.grading-settings.assignment.type-name.error.message-1": "The assignment type must have a name.",
"course-authoring.grading-settings.assignment.type-name.error.message-3": "There's already another assignment type with this name.",
"course-authoring.grading-settings.assignment.abbreviation.title": "Abbreviation",
"course-authoring.grading-settings.assignment.abbreviation.description": "This short name for the assignment type (for example, HW or Midterm) appears next to assignments on a learner's Progress page.",
"course-authoring.grading-settings.assignment.weight-of-total-grade.title": "Weight of total grade",
"course-authoring.grading-settings.assignment.weight-of-total-grade.description": "The weight of all assignments of this type as a percentage of the total grade, for example, 40. Do not include the percent symbol.",
"course-authoring.grading-settings.assignment.weight-of-total-grade.error.message": "Please enter an integer between 0 and 100.",
"course-authoring.grading-settings.assignment.total-number.title": "Total number",
"course-authoring.grading-settings.assignment.total-number.description": "The number of subsections in the course that contain problems of this assignment type.",
"course-authoring.grading-settings.assignment.total-number.error.message": "Please enter an integer greater than 0.",
"course-authoring.grading-settings.assignment.number-of-droppable.title": "Number of droppable",
"course-authoring.grading-settings.assignment.number-of-droppable.description": "The number of assignments of this type that will be dropped. The lowest scoring assignments are dropped first.",
"course-authoring.grading-settings.assignment.number-of-droppable.error.message": "Please enter non-negative integer.",
"course-authoring.grading-settings.assignment.alert.warning.description": "There are no assignments of this type in the course.",
"course-authoring.grading-settings.assignment.delete.button": "Delete",
"course-authoring.grading-settings.assignment.number-of-droppable.second.error.message": "Cannot drop more {type} assignments than are assigned.",
"course-authoring.grading-settings.assignment.alert.warning.usage.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:",
"course-authoring.grading-settings.assignment.alert.warning.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:",
"course-authoring.grading-settings.assignment.alert.success.title": "The number of {type} assignments in the course matches the number defined here.",
"course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}"
}

View File

@@ -357,7 +357,7 @@
"course-authoring.advanced-settings.heading.title": "Advanced settings",
"course-authoring.advanced-settings.heading.subtitle": "Settings",
"course-authoring.advanced-settings.policies.title": "Manual policy definition",
"course-authoring.advanced-settings.alert.warning": "You`ve made some changes",
"course-authoring.advanced-settings.alert.warning": "You've made some changes",
"course-authoring.advanced-settings.alert.warning.descriptions": "Your changes will not take effect until you save your progress. Take care with key and value formatting, as validation is not implemented.",
"course-authoring.advanced-settings.alert.success": "Your policy changes have been saved.",
"course-authoring.advanced-settings.alert.success.descriptions": "No validation is performed on policy keys or value pairs. If you are having difficulties, check your formatting.",
@@ -416,7 +416,7 @@
"course-authoring.schedule.alert.button.cancel": "Cancel",
"course-authoring.schedule.alert.warning.aria.labelledby": "notification-warning-title",
"course-authoring.schedule.alert.warning.aria.describedby": "notification-warning-description",
"course-authoring.schedule.alert.warning": "You`ve made some changes",
"course-authoring.schedule.alert.warning": "You've made some changes",
"course-authoring.schedule.alert.warning.descriptions": "Your changes will not take effect until you save your progress.",
"course-authoring.schedule.alert.success.aria.labelledby": "alert-confirmation-title",
"course-authoring.schedule.alert.success.aria.describedby": "alert-confirmation-description",
@@ -550,6 +550,26 @@
"course-authoring.generic.alert.warning.offline.description": "This may be happening because of an error with our server or your internet connection. Try refreshing the page or making sure you are online.",
"course-authoring.generic.alert.warning.offline.title.aria.labelled-by": "alert-internet-error-title",
"course-authoring.generic.alert.warning.offline.subtitle.aria.described-by": "alert-internet-error-description",
"course-authoring.grading-settings.fail-segment.text": "Fail",
"course-authoring.grading-settings.sidebar.about.title": "What can I do on this page?",
"course-authoring.grading-settings.sidebar.about.text-1": "You can use the slider under Overall Grade Range to specify whether your course is pass/fail or graded by letter, and to establish the thresholds for each grade.",
"course-authoring.grading-settings.sidebar.about.text-2": "You can specify whether your course offers students a grace period for late assignments.",
"course-authoring.grading-settings.sidebar.about.text-3": "You can also create assignment types, such as homework, labs, quizzes, and exams, and specify how much of a student's grade each assignment type is worth.",
"course-authoring.grading-settings.add-new-segment.btn.text": "Add new grading segment",
"course-authoring.grading-settings.remove-segment.btn.text": "Remove",
"course-authoring.grading-settings.heading.title": "Grading",
"course-authoring.grading-settings.heading.subtitle": "Settings",
"course-authoring.grading-settings.policies.title": "Overall grade range",
"course-authoring.grading-settings.policies.description": "Your overall grading scale for student final grades",
"course-authoring.grading-settings.alert.warning": "You've made some changes",
"course-authoring.grading-settings.alert.warning.descriptions": "Your changes will not take effect until you save your progress. Take care with key and value formatting, as validation is not implemented.",
"course-authoring.grading-settings.alert.success": "Your changes have been saved.",
"course-authoring.grading-settings.alert.button.save": "Save changes",
"course-authoring.grading-settings.alert.button.cancel": "Cancel",
"course-authoring.grading-settings.alert.warning.aria.labelledby": "notification-warning-title",
"course-authoring.grading-settings.alert.warning.aria.describedby": "notification-warning-description",
"course-authoring.grading-settings.alert.success.aria.labelledby": "alert-confirmation-title",
"course-authoring.grading-settings.alert.success.aria.describedby": "alert-confirmation-description",
"course-authoring.schedule-section.requirements.entrance.label": "Entrance exam",
"course-authoring.schedule-section.requirements.entrance.collapse.title": "Require students to pass an exam before beginning the course.",
"course-authoring.schedule-section.requirements.entrance.collapse.paragraph": "You can now view and author your course entrance exam from the {hyperlink}.",
@@ -585,5 +605,40 @@
"course-authoring.schedule-section.license.creative-commons.option.ND.description": "Allow others to copy, distribute, display and perform only verbatim copies of your work, not derivative works based upon it. This option is incompatible with 'Share Alike'.",
"course-authoring.schedule-section.license.creative-commons.option.SA.label": "Share alike",
"course-authoring.schedule-section.license.creative-commons.option.SA.description": "Allow others to distribute derivative works only under a license identical to the license that governs your work. This option is incompatible with 'No Derivatives'.",
"course-authoring.schedule.alert.button.saving": "Saving"
"course-authoring.schedule.alert.button.saving": "Saving",
"course-authoring.grading-settings.credit.eligibility.label": "Minimum credit-eligible grade:",
"course-authoring.grading-settings.credit.eligibility.description": "% Must be greater than or equal to the course passing grade",
"course-authoring.grading-settings.credit.eligibility.error.msg": "Not able to set passing grade to less than:",
"course-authoring.grading-settings.deadline.label": "Grace period on deadline:",
"course-authoring.grading-settings.deadline.description": "Leeway on due dates",
"course-authoring.grading-settings.alert.button.saving": "Saving",
"course-authoring.grading-settings.add-new-assignment-type.btn": "New assignment type",
"course-authoring.grading-settings.assignment-type.description": "Categories and labels for any exercises that are gradable",
"course-authoring.grading-settings.assignment-type.title": "Assignment types",
"course-authoring.grading-settings.grading-rules-policies.description": "Deadlines, requirements, and logistics around grading student work",
"course-authoring.grading-settings.grading-rules-policies.title": "Grading rules & policies",
"course-authoring.grading-settings.credit-eligibility.description": "Settings for course credit eligibility",
"course-authoring.grading-settings.credit-eligibility.title": "Credit eligibility",
"course-authoring.grading-settings.assignment.type-name.title": "Assignment type name",
"course-authoring.grading-settings.assignment.type-name.description": "The general category for this type of assignment, for example, Homework or Midterm Exam. This name is visible to learners.",
"course-authoring.grading-settings.assignment.type-name.error.message-1": "The assignment type must have a name.",
"course-authoring.grading-settings.assignment.type-name.error.message-3": "There's already another assignment type with this name.",
"course-authoring.grading-settings.assignment.abbreviation.title": "Abbreviation",
"course-authoring.grading-settings.assignment.abbreviation.description": "This short name for the assignment type (for example, HW or Midterm) appears next to assignments on a learner's Progress page.",
"course-authoring.grading-settings.assignment.weight-of-total-grade.title": "Weight of total grade",
"course-authoring.grading-settings.assignment.weight-of-total-grade.description": "The weight of all assignments of this type as a percentage of the total grade, for example, 40. Do not include the percent symbol.",
"course-authoring.grading-settings.assignment.weight-of-total-grade.error.message": "Please enter an integer between 0 and 100.",
"course-authoring.grading-settings.assignment.total-number.title": "Total number",
"course-authoring.grading-settings.assignment.total-number.description": "The number of subsections in the course that contain problems of this assignment type.",
"course-authoring.grading-settings.assignment.total-number.error.message": "Please enter an integer greater than 0.",
"course-authoring.grading-settings.assignment.number-of-droppable.title": "Number of droppable",
"course-authoring.grading-settings.assignment.number-of-droppable.description": "The number of assignments of this type that will be dropped. The lowest scoring assignments are dropped first.",
"course-authoring.grading-settings.assignment.number-of-droppable.error.message": "Please enter non-negative integer.",
"course-authoring.grading-settings.assignment.alert.warning.description": "There are no assignments of this type in the course.",
"course-authoring.grading-settings.assignment.delete.button": "Delete",
"course-authoring.grading-settings.assignment.number-of-droppable.second.error.message": "Cannot drop more {type} assignments than are assigned.",
"course-authoring.grading-settings.assignment.alert.warning.usage.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:",
"course-authoring.grading-settings.assignment.alert.warning.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:",
"course-authoring.grading-settings.assignment.alert.success.title": "The number of {type} assignments in the course matches the number defined here.",
"course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}"
}

View File

@@ -357,7 +357,7 @@
"course-authoring.advanced-settings.heading.title": "Advanced settings",
"course-authoring.advanced-settings.heading.subtitle": "Settings",
"course-authoring.advanced-settings.policies.title": "Manual policy definition",
"course-authoring.advanced-settings.alert.warning": "You`ve made some changes",
"course-authoring.advanced-settings.alert.warning": "You've made some changes",
"course-authoring.advanced-settings.alert.warning.descriptions": "Your changes will not take effect until you save your progress. Take care with key and value formatting, as validation is not implemented.",
"course-authoring.advanced-settings.alert.success": "Your policy changes have been saved.",
"course-authoring.advanced-settings.alert.success.descriptions": "No validation is performed on policy keys or value pairs. If you are having difficulties, check your formatting.",
@@ -416,7 +416,7 @@
"course-authoring.schedule.alert.button.cancel": "Cancel",
"course-authoring.schedule.alert.warning.aria.labelledby": "notification-warning-title",
"course-authoring.schedule.alert.warning.aria.describedby": "notification-warning-description",
"course-authoring.schedule.alert.warning": "You`ve made some changes",
"course-authoring.schedule.alert.warning": "You've made some changes",
"course-authoring.schedule.alert.warning.descriptions": "Your changes will not take effect until you save your progress.",
"course-authoring.schedule.alert.success.aria.labelledby": "alert-confirmation-title",
"course-authoring.schedule.alert.success.aria.describedby": "alert-confirmation-description",
@@ -550,6 +550,26 @@
"course-authoring.generic.alert.warning.offline.description": "This may be happening because of an error with our server or your internet connection. Try refreshing the page or making sure you are online.",
"course-authoring.generic.alert.warning.offline.title.aria.labelled-by": "alert-internet-error-title",
"course-authoring.generic.alert.warning.offline.subtitle.aria.described-by": "alert-internet-error-description",
"course-authoring.grading-settings.fail-segment.text": "Fail",
"course-authoring.grading-settings.sidebar.about.title": "What can I do on this page?",
"course-authoring.grading-settings.sidebar.about.text-1": "You can use the slider under Overall Grade Range to specify whether your course is pass/fail or graded by letter, and to establish the thresholds for each grade.",
"course-authoring.grading-settings.sidebar.about.text-2": "You can specify whether your course offers students a grace period for late assignments.",
"course-authoring.grading-settings.sidebar.about.text-3": "You can also create assignment types, such as homework, labs, quizzes, and exams, and specify how much of a student's grade each assignment type is worth.",
"course-authoring.grading-settings.add-new-segment.btn.text": "Add new grading segment",
"course-authoring.grading-settings.remove-segment.btn.text": "Remove",
"course-authoring.grading-settings.heading.title": "Grading",
"course-authoring.grading-settings.heading.subtitle": "Settings",
"course-authoring.grading-settings.policies.title": "Overall grade range",
"course-authoring.grading-settings.policies.description": "Your overall grading scale for student final grades",
"course-authoring.grading-settings.alert.warning": "You've made some changes",
"course-authoring.grading-settings.alert.warning.descriptions": "Your changes will not take effect until you save your progress. Take care with key and value formatting, as validation is not implemented.",
"course-authoring.grading-settings.alert.success": "Your changes have been saved.",
"course-authoring.grading-settings.alert.button.save": "Save changes",
"course-authoring.grading-settings.alert.button.cancel": "Cancel",
"course-authoring.grading-settings.alert.warning.aria.labelledby": "notification-warning-title",
"course-authoring.grading-settings.alert.warning.aria.describedby": "notification-warning-description",
"course-authoring.grading-settings.alert.success.aria.labelledby": "alert-confirmation-title",
"course-authoring.grading-settings.alert.success.aria.describedby": "alert-confirmation-description",
"course-authoring.schedule-section.requirements.entrance.label": "Entrance exam",
"course-authoring.schedule-section.requirements.entrance.collapse.title": "Require students to pass an exam before beginning the course.",
"course-authoring.schedule-section.requirements.entrance.collapse.paragraph": "You can now view and author your course entrance exam from the {hyperlink}.",
@@ -585,5 +605,40 @@
"course-authoring.schedule-section.license.creative-commons.option.ND.description": "Allow others to copy, distribute, display and perform only verbatim copies of your work, not derivative works based upon it. This option is incompatible with 'Share Alike'.",
"course-authoring.schedule-section.license.creative-commons.option.SA.label": "Share alike",
"course-authoring.schedule-section.license.creative-commons.option.SA.description": "Allow others to distribute derivative works only under a license identical to the license that governs your work. This option is incompatible with 'No Derivatives'.",
"course-authoring.schedule.alert.button.saving": "Saving"
"course-authoring.schedule.alert.button.saving": "Saving",
"course-authoring.grading-settings.credit.eligibility.label": "Minimum credit-eligible grade:",
"course-authoring.grading-settings.credit.eligibility.description": "% Must be greater than or equal to the course passing grade",
"course-authoring.grading-settings.credit.eligibility.error.msg": "Not able to set passing grade to less than:",
"course-authoring.grading-settings.deadline.label": "Grace period on deadline:",
"course-authoring.grading-settings.deadline.description": "Leeway on due dates",
"course-authoring.grading-settings.alert.button.saving": "Saving",
"course-authoring.grading-settings.add-new-assignment-type.btn": "New assignment type",
"course-authoring.grading-settings.assignment-type.description": "Categories and labels for any exercises that are gradable",
"course-authoring.grading-settings.assignment-type.title": "Assignment types",
"course-authoring.grading-settings.grading-rules-policies.description": "Deadlines, requirements, and logistics around grading student work",
"course-authoring.grading-settings.grading-rules-policies.title": "Grading rules & policies",
"course-authoring.grading-settings.credit-eligibility.description": "Settings for course credit eligibility",
"course-authoring.grading-settings.credit-eligibility.title": "Credit eligibility",
"course-authoring.grading-settings.assignment.type-name.title": "Assignment type name",
"course-authoring.grading-settings.assignment.type-name.description": "The general category for this type of assignment, for example, Homework or Midterm Exam. This name is visible to learners.",
"course-authoring.grading-settings.assignment.type-name.error.message-1": "The assignment type must have a name.",
"course-authoring.grading-settings.assignment.type-name.error.message-3": "There's already another assignment type with this name.",
"course-authoring.grading-settings.assignment.abbreviation.title": "Abbreviation",
"course-authoring.grading-settings.assignment.abbreviation.description": "This short name for the assignment type (for example, HW or Midterm) appears next to assignments on a learner's Progress page.",
"course-authoring.grading-settings.assignment.weight-of-total-grade.title": "Weight of total grade",
"course-authoring.grading-settings.assignment.weight-of-total-grade.description": "The weight of all assignments of this type as a percentage of the total grade, for example, 40. Do not include the percent symbol.",
"course-authoring.grading-settings.assignment.weight-of-total-grade.error.message": "Please enter an integer between 0 and 100.",
"course-authoring.grading-settings.assignment.total-number.title": "Total number",
"course-authoring.grading-settings.assignment.total-number.description": "The number of subsections in the course that contain problems of this assignment type.",
"course-authoring.grading-settings.assignment.total-number.error.message": "Please enter an integer greater than 0.",
"course-authoring.grading-settings.assignment.number-of-droppable.title": "Number of droppable",
"course-authoring.grading-settings.assignment.number-of-droppable.description": "The number of assignments of this type that will be dropped. The lowest scoring assignments are dropped first.",
"course-authoring.grading-settings.assignment.number-of-droppable.error.message": "Please enter non-negative integer.",
"course-authoring.grading-settings.assignment.alert.warning.description": "There are no assignments of this type in the course.",
"course-authoring.grading-settings.assignment.delete.button": "Delete",
"course-authoring.grading-settings.assignment.number-of-droppable.second.error.message": "Cannot drop more {type} assignments than are assigned.",
"course-authoring.grading-settings.assignment.alert.warning.usage.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:",
"course-authoring.grading-settings.assignment.alert.warning.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:",
"course-authoring.grading-settings.assignment.alert.success.title": "The number of {type} assignments in the course matches the number defined here.",
"course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}"
}

View File

@@ -357,7 +357,7 @@
"course-authoring.advanced-settings.heading.title": "Advanced settings",
"course-authoring.advanced-settings.heading.subtitle": "Settings",
"course-authoring.advanced-settings.policies.title": "Manual policy definition",
"course-authoring.advanced-settings.alert.warning": "You`ve made some changes",
"course-authoring.advanced-settings.alert.warning": "You've made some changes",
"course-authoring.advanced-settings.alert.warning.descriptions": "Your changes will not take effect until you save your progress. Take care with key and value formatting, as validation is not implemented.",
"course-authoring.advanced-settings.alert.success": "Your policy changes have been saved.",
"course-authoring.advanced-settings.alert.success.descriptions": "No validation is performed on policy keys or value pairs. If you are having difficulties, check your formatting.",
@@ -416,7 +416,7 @@
"course-authoring.schedule.alert.button.cancel": "Cancel",
"course-authoring.schedule.alert.warning.aria.labelledby": "notification-warning-title",
"course-authoring.schedule.alert.warning.aria.describedby": "notification-warning-description",
"course-authoring.schedule.alert.warning": "You`ve made some changes",
"course-authoring.schedule.alert.warning": "You've made some changes",
"course-authoring.schedule.alert.warning.descriptions": "Your changes will not take effect until you save your progress.",
"course-authoring.schedule.alert.success.aria.labelledby": "alert-confirmation-title",
"course-authoring.schedule.alert.success.aria.describedby": "alert-confirmation-description",
@@ -550,6 +550,26 @@
"course-authoring.generic.alert.warning.offline.description": "This may be happening because of an error with our server or your internet connection. Try refreshing the page or making sure you are online.",
"course-authoring.generic.alert.warning.offline.title.aria.labelled-by": "alert-internet-error-title",
"course-authoring.generic.alert.warning.offline.subtitle.aria.described-by": "alert-internet-error-description",
"course-authoring.grading-settings.fail-segment.text": "Fail",
"course-authoring.grading-settings.sidebar.about.title": "What can I do on this page?",
"course-authoring.grading-settings.sidebar.about.text-1": "You can use the slider under Overall Grade Range to specify whether your course is pass/fail or graded by letter, and to establish the thresholds for each grade.",
"course-authoring.grading-settings.sidebar.about.text-2": "You can specify whether your course offers students a grace period for late assignments.",
"course-authoring.grading-settings.sidebar.about.text-3": "You can also create assignment types, such as homework, labs, quizzes, and exams, and specify how much of a student's grade each assignment type is worth.",
"course-authoring.grading-settings.add-new-segment.btn.text": "Add new grading segment",
"course-authoring.grading-settings.remove-segment.btn.text": "Remove",
"course-authoring.grading-settings.heading.title": "Grading",
"course-authoring.grading-settings.heading.subtitle": "Settings",
"course-authoring.grading-settings.policies.title": "Overall grade range",
"course-authoring.grading-settings.policies.description": "Your overall grading scale for student final grades",
"course-authoring.grading-settings.alert.warning": "You've made some changes",
"course-authoring.grading-settings.alert.warning.descriptions": "Your changes will not take effect until you save your progress. Take care with key and value formatting, as validation is not implemented.",
"course-authoring.grading-settings.alert.success": "Your changes have been saved.",
"course-authoring.grading-settings.alert.button.save": "Save changes",
"course-authoring.grading-settings.alert.button.cancel": "Cancel",
"course-authoring.grading-settings.alert.warning.aria.labelledby": "notification-warning-title",
"course-authoring.grading-settings.alert.warning.aria.describedby": "notification-warning-description",
"course-authoring.grading-settings.alert.success.aria.labelledby": "alert-confirmation-title",
"course-authoring.grading-settings.alert.success.aria.describedby": "alert-confirmation-description",
"course-authoring.schedule-section.requirements.entrance.label": "Entrance exam",
"course-authoring.schedule-section.requirements.entrance.collapse.title": "Require students to pass an exam before beginning the course.",
"course-authoring.schedule-section.requirements.entrance.collapse.paragraph": "You can now view and author your course entrance exam from the {hyperlink}.",
@@ -585,5 +605,40 @@
"course-authoring.schedule-section.license.creative-commons.option.ND.description": "Allow others to copy, distribute, display and perform only verbatim copies of your work, not derivative works based upon it. This option is incompatible with 'Share Alike'.",
"course-authoring.schedule-section.license.creative-commons.option.SA.label": "Share alike",
"course-authoring.schedule-section.license.creative-commons.option.SA.description": "Allow others to distribute derivative works only under a license identical to the license that governs your work. This option is incompatible with 'No Derivatives'.",
"course-authoring.schedule.alert.button.saving": "Saving"
"course-authoring.schedule.alert.button.saving": "Saving",
"course-authoring.grading-settings.credit.eligibility.label": "Minimum credit-eligible grade:",
"course-authoring.grading-settings.credit.eligibility.description": "% Must be greater than or equal to the course passing grade",
"course-authoring.grading-settings.credit.eligibility.error.msg": "Not able to set passing grade to less than:",
"course-authoring.grading-settings.deadline.label": "Grace period on deadline:",
"course-authoring.grading-settings.deadline.description": "Leeway on due dates",
"course-authoring.grading-settings.alert.button.saving": "Saving",
"course-authoring.grading-settings.add-new-assignment-type.btn": "New assignment type",
"course-authoring.grading-settings.assignment-type.description": "Categories and labels for any exercises that are gradable",
"course-authoring.grading-settings.assignment-type.title": "Assignment types",
"course-authoring.grading-settings.grading-rules-policies.description": "Deadlines, requirements, and logistics around grading student work",
"course-authoring.grading-settings.grading-rules-policies.title": "Grading rules & policies",
"course-authoring.grading-settings.credit-eligibility.description": "Settings for course credit eligibility",
"course-authoring.grading-settings.credit-eligibility.title": "Credit eligibility",
"course-authoring.grading-settings.assignment.type-name.title": "Assignment type name",
"course-authoring.grading-settings.assignment.type-name.description": "The general category for this type of assignment, for example, Homework or Midterm Exam. This name is visible to learners.",
"course-authoring.grading-settings.assignment.type-name.error.message-1": "The assignment type must have a name.",
"course-authoring.grading-settings.assignment.type-name.error.message-3": "There's already another assignment type with this name.",
"course-authoring.grading-settings.assignment.abbreviation.title": "Abbreviation",
"course-authoring.grading-settings.assignment.abbreviation.description": "This short name for the assignment type (for example, HW or Midterm) appears next to assignments on a learner's Progress page.",
"course-authoring.grading-settings.assignment.weight-of-total-grade.title": "Weight of total grade",
"course-authoring.grading-settings.assignment.weight-of-total-grade.description": "The weight of all assignments of this type as a percentage of the total grade, for example, 40. Do not include the percent symbol.",
"course-authoring.grading-settings.assignment.weight-of-total-grade.error.message": "Please enter an integer between 0 and 100.",
"course-authoring.grading-settings.assignment.total-number.title": "Total number",
"course-authoring.grading-settings.assignment.total-number.description": "The number of subsections in the course that contain problems of this assignment type.",
"course-authoring.grading-settings.assignment.total-number.error.message": "Please enter an integer greater than 0.",
"course-authoring.grading-settings.assignment.number-of-droppable.title": "Number of droppable",
"course-authoring.grading-settings.assignment.number-of-droppable.description": "The number of assignments of this type that will be dropped. The lowest scoring assignments are dropped first.",
"course-authoring.grading-settings.assignment.number-of-droppable.error.message": "Please enter non-negative integer.",
"course-authoring.grading-settings.assignment.alert.warning.description": "There are no assignments of this type in the course.",
"course-authoring.grading-settings.assignment.delete.button": "Delete",
"course-authoring.grading-settings.assignment.number-of-droppable.second.error.message": "Cannot drop more {type} assignments than are assigned.",
"course-authoring.grading-settings.assignment.alert.warning.usage.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:",
"course-authoring.grading-settings.assignment.alert.warning.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:",
"course-authoring.grading-settings.assignment.alert.success.title": "The number of {type} assignments in the course matches the number defined here.",
"course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}"
}

View File

@@ -357,7 +357,7 @@
"course-authoring.advanced-settings.heading.title": "Advanced settings",
"course-authoring.advanced-settings.heading.subtitle": "Settings",
"course-authoring.advanced-settings.policies.title": "Manual policy definition",
"course-authoring.advanced-settings.alert.warning": "You`ve made some changes",
"course-authoring.advanced-settings.alert.warning": "You've made some changes",
"course-authoring.advanced-settings.alert.warning.descriptions": "Your changes will not take effect until you save your progress. Take care with key and value formatting, as validation is not implemented.",
"course-authoring.advanced-settings.alert.success": "Your policy changes have been saved.",
"course-authoring.advanced-settings.alert.success.descriptions": "No validation is performed on policy keys or value pairs. If you are having difficulties, check your formatting.",
@@ -416,7 +416,7 @@
"course-authoring.schedule.alert.button.cancel": "Cancel",
"course-authoring.schedule.alert.warning.aria.labelledby": "notification-warning-title",
"course-authoring.schedule.alert.warning.aria.describedby": "notification-warning-description",
"course-authoring.schedule.alert.warning": "You`ve made some changes",
"course-authoring.schedule.alert.warning": "You've made some changes",
"course-authoring.schedule.alert.warning.descriptions": "Your changes will not take effect until you save your progress.",
"course-authoring.schedule.alert.success.aria.labelledby": "alert-confirmation-title",
"course-authoring.schedule.alert.success.aria.describedby": "alert-confirmation-description",
@@ -550,6 +550,26 @@
"course-authoring.generic.alert.warning.offline.description": "This may be happening because of an error with our server or your internet connection. Try refreshing the page or making sure you are online.",
"course-authoring.generic.alert.warning.offline.title.aria.labelled-by": "alert-internet-error-title",
"course-authoring.generic.alert.warning.offline.subtitle.aria.described-by": "alert-internet-error-description",
"course-authoring.grading-settings.fail-segment.text": "Fail",
"course-authoring.grading-settings.sidebar.about.title": "What can I do on this page?",
"course-authoring.grading-settings.sidebar.about.text-1": "You can use the slider under Overall Grade Range to specify whether your course is pass/fail or graded by letter, and to establish the thresholds for each grade.",
"course-authoring.grading-settings.sidebar.about.text-2": "You can specify whether your course offers students a grace period for late assignments.",
"course-authoring.grading-settings.sidebar.about.text-3": "You can also create assignment types, such as homework, labs, quizzes, and exams, and specify how much of a student's grade each assignment type is worth.",
"course-authoring.grading-settings.add-new-segment.btn.text": "Add new grading segment",
"course-authoring.grading-settings.remove-segment.btn.text": "Remove",
"course-authoring.grading-settings.heading.title": "Grading",
"course-authoring.grading-settings.heading.subtitle": "Settings",
"course-authoring.grading-settings.policies.title": "Overall grade range",
"course-authoring.grading-settings.policies.description": "Your overall grading scale for student final grades",
"course-authoring.grading-settings.alert.warning": "You've made some changes",
"course-authoring.grading-settings.alert.warning.descriptions": "Your changes will not take effect until you save your progress. Take care with key and value formatting, as validation is not implemented.",
"course-authoring.grading-settings.alert.success": "Your changes have been saved.",
"course-authoring.grading-settings.alert.button.save": "Save changes",
"course-authoring.grading-settings.alert.button.cancel": "Cancel",
"course-authoring.grading-settings.alert.warning.aria.labelledby": "notification-warning-title",
"course-authoring.grading-settings.alert.warning.aria.describedby": "notification-warning-description",
"course-authoring.grading-settings.alert.success.aria.labelledby": "alert-confirmation-title",
"course-authoring.grading-settings.alert.success.aria.describedby": "alert-confirmation-description",
"course-authoring.schedule-section.requirements.entrance.label": "Entrance exam",
"course-authoring.schedule-section.requirements.entrance.collapse.title": "Require students to pass an exam before beginning the course.",
"course-authoring.schedule-section.requirements.entrance.collapse.paragraph": "You can now view and author your course entrance exam from the {hyperlink}.",
@@ -585,5 +605,40 @@
"course-authoring.schedule-section.license.creative-commons.option.ND.description": "Allow others to copy, distribute, display and perform only verbatim copies of your work, not derivative works based upon it. This option is incompatible with 'Share Alike'.",
"course-authoring.schedule-section.license.creative-commons.option.SA.label": "Share alike",
"course-authoring.schedule-section.license.creative-commons.option.SA.description": "Allow others to distribute derivative works only under a license identical to the license that governs your work. This option is incompatible with 'No Derivatives'.",
"course-authoring.schedule.alert.button.saving": "Saving"
"course-authoring.schedule.alert.button.saving": "Saving",
"course-authoring.grading-settings.credit.eligibility.label": "Minimum credit-eligible grade:",
"course-authoring.grading-settings.credit.eligibility.description": "% Must be greater than or equal to the course passing grade",
"course-authoring.grading-settings.credit.eligibility.error.msg": "Not able to set passing grade to less than:",
"course-authoring.grading-settings.deadline.label": "Grace period on deadline:",
"course-authoring.grading-settings.deadline.description": "Leeway on due dates",
"course-authoring.grading-settings.alert.button.saving": "Saving",
"course-authoring.grading-settings.add-new-assignment-type.btn": "New assignment type",
"course-authoring.grading-settings.assignment-type.description": "Categories and labels for any exercises that are gradable",
"course-authoring.grading-settings.assignment-type.title": "Assignment types",
"course-authoring.grading-settings.grading-rules-policies.description": "Deadlines, requirements, and logistics around grading student work",
"course-authoring.grading-settings.grading-rules-policies.title": "Grading rules & policies",
"course-authoring.grading-settings.credit-eligibility.description": "Settings for course credit eligibility",
"course-authoring.grading-settings.credit-eligibility.title": "Credit eligibility",
"course-authoring.grading-settings.assignment.type-name.title": "Assignment type name",
"course-authoring.grading-settings.assignment.type-name.description": "The general category for this type of assignment, for example, Homework or Midterm Exam. This name is visible to learners.",
"course-authoring.grading-settings.assignment.type-name.error.message-1": "The assignment type must have a name.",
"course-authoring.grading-settings.assignment.type-name.error.message-3": "There's already another assignment type with this name.",
"course-authoring.grading-settings.assignment.abbreviation.title": "Abbreviation",
"course-authoring.grading-settings.assignment.abbreviation.description": "This short name for the assignment type (for example, HW or Midterm) appears next to assignments on a learner's Progress page.",
"course-authoring.grading-settings.assignment.weight-of-total-grade.title": "Weight of total grade",
"course-authoring.grading-settings.assignment.weight-of-total-grade.description": "The weight of all assignments of this type as a percentage of the total grade, for example, 40. Do not include the percent symbol.",
"course-authoring.grading-settings.assignment.weight-of-total-grade.error.message": "Please enter an integer between 0 and 100.",
"course-authoring.grading-settings.assignment.total-number.title": "Total number",
"course-authoring.grading-settings.assignment.total-number.description": "The number of subsections in the course that contain problems of this assignment type.",
"course-authoring.grading-settings.assignment.total-number.error.message": "Please enter an integer greater than 0.",
"course-authoring.grading-settings.assignment.number-of-droppable.title": "Number of droppable",
"course-authoring.grading-settings.assignment.number-of-droppable.description": "The number of assignments of this type that will be dropped. The lowest scoring assignments are dropped first.",
"course-authoring.grading-settings.assignment.number-of-droppable.error.message": "Please enter non-negative integer.",
"course-authoring.grading-settings.assignment.alert.warning.description": "There are no assignments of this type in the course.",
"course-authoring.grading-settings.assignment.delete.button": "Delete",
"course-authoring.grading-settings.assignment.number-of-droppable.second.error.message": "Cannot drop more {type} assignments than are assigned.",
"course-authoring.grading-settings.assignment.alert.warning.usage.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:",
"course-authoring.grading-settings.assignment.alert.warning.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:",
"course-authoring.grading-settings.assignment.alert.success.title": "The number of {type} assignments in the course matches the number defined here.",
"course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}"
}

View File

@@ -357,7 +357,7 @@
"course-authoring.advanced-settings.heading.title": "Advanced settings",
"course-authoring.advanced-settings.heading.subtitle": "Settings",
"course-authoring.advanced-settings.policies.title": "Manual policy definition",
"course-authoring.advanced-settings.alert.warning": "You`ve made some changes",
"course-authoring.advanced-settings.alert.warning": "You've made some changes",
"course-authoring.advanced-settings.alert.warning.descriptions": "Your changes will not take effect until you save your progress. Take care with key and value formatting, as validation is not implemented.",
"course-authoring.advanced-settings.alert.success": "Your policy changes have been saved.",
"course-authoring.advanced-settings.alert.success.descriptions": "No validation is performed on policy keys or value pairs. If you are having difficulties, check your formatting.",
@@ -416,7 +416,7 @@
"course-authoring.schedule.alert.button.cancel": "Cancel",
"course-authoring.schedule.alert.warning.aria.labelledby": "notification-warning-title",
"course-authoring.schedule.alert.warning.aria.describedby": "notification-warning-description",
"course-authoring.schedule.alert.warning": "You`ve made some changes",
"course-authoring.schedule.alert.warning": "You've made some changes",
"course-authoring.schedule.alert.warning.descriptions": "Your changes will not take effect until you save your progress.",
"course-authoring.schedule.alert.success.aria.labelledby": "alert-confirmation-title",
"course-authoring.schedule.alert.success.aria.describedby": "alert-confirmation-description",
@@ -550,6 +550,26 @@
"course-authoring.generic.alert.warning.offline.description": "This may be happening because of an error with our server or your internet connection. Try refreshing the page or making sure you are online.",
"course-authoring.generic.alert.warning.offline.title.aria.labelled-by": "alert-internet-error-title",
"course-authoring.generic.alert.warning.offline.subtitle.aria.described-by": "alert-internet-error-description",
"course-authoring.grading-settings.fail-segment.text": "Fail",
"course-authoring.grading-settings.sidebar.about.title": "What can I do on this page?",
"course-authoring.grading-settings.sidebar.about.text-1": "You can use the slider under Overall Grade Range to specify whether your course is pass/fail or graded by letter, and to establish the thresholds for each grade.",
"course-authoring.grading-settings.sidebar.about.text-2": "You can specify whether your course offers students a grace period for late assignments.",
"course-authoring.grading-settings.sidebar.about.text-3": "You can also create assignment types, such as homework, labs, quizzes, and exams, and specify how much of a student's grade each assignment type is worth.",
"course-authoring.grading-settings.add-new-segment.btn.text": "Add new grading segment",
"course-authoring.grading-settings.remove-segment.btn.text": "Remove",
"course-authoring.grading-settings.heading.title": "Grading",
"course-authoring.grading-settings.heading.subtitle": "Settings",
"course-authoring.grading-settings.policies.title": "Overall grade range",
"course-authoring.grading-settings.policies.description": "Your overall grading scale for student final grades",
"course-authoring.grading-settings.alert.warning": "You've made some changes",
"course-authoring.grading-settings.alert.warning.descriptions": "Your changes will not take effect until you save your progress. Take care with key and value formatting, as validation is not implemented.",
"course-authoring.grading-settings.alert.success": "Your changes have been saved.",
"course-authoring.grading-settings.alert.button.save": "Save changes",
"course-authoring.grading-settings.alert.button.cancel": "Cancel",
"course-authoring.grading-settings.alert.warning.aria.labelledby": "notification-warning-title",
"course-authoring.grading-settings.alert.warning.aria.describedby": "notification-warning-description",
"course-authoring.grading-settings.alert.success.aria.labelledby": "alert-confirmation-title",
"course-authoring.grading-settings.alert.success.aria.describedby": "alert-confirmation-description",
"course-authoring.schedule-section.requirements.entrance.label": "Entrance exam",
"course-authoring.schedule-section.requirements.entrance.collapse.title": "Require students to pass an exam before beginning the course.",
"course-authoring.schedule-section.requirements.entrance.collapse.paragraph": "You can now view and author your course entrance exam from the {hyperlink}.",
@@ -585,5 +605,40 @@
"course-authoring.schedule-section.license.creative-commons.option.ND.description": "Allow others to copy, distribute, display and perform only verbatim copies of your work, not derivative works based upon it. This option is incompatible with 'Share Alike'.",
"course-authoring.schedule-section.license.creative-commons.option.SA.label": "Share alike",
"course-authoring.schedule-section.license.creative-commons.option.SA.description": "Allow others to distribute derivative works only under a license identical to the license that governs your work. This option is incompatible with 'No Derivatives'.",
"course-authoring.schedule.alert.button.saving": "Saving"
"course-authoring.schedule.alert.button.saving": "Saving",
"course-authoring.grading-settings.credit.eligibility.label": "Minimum credit-eligible grade:",
"course-authoring.grading-settings.credit.eligibility.description": "% Must be greater than or equal to the course passing grade",
"course-authoring.grading-settings.credit.eligibility.error.msg": "Not able to set passing grade to less than:",
"course-authoring.grading-settings.deadline.label": "Grace period on deadline:",
"course-authoring.grading-settings.deadline.description": "Leeway on due dates",
"course-authoring.grading-settings.alert.button.saving": "Saving",
"course-authoring.grading-settings.add-new-assignment-type.btn": "New assignment type",
"course-authoring.grading-settings.assignment-type.description": "Categories and labels for any exercises that are gradable",
"course-authoring.grading-settings.assignment-type.title": "Assignment types",
"course-authoring.grading-settings.grading-rules-policies.description": "Deadlines, requirements, and logistics around grading student work",
"course-authoring.grading-settings.grading-rules-policies.title": "Grading rules & policies",
"course-authoring.grading-settings.credit-eligibility.description": "Settings for course credit eligibility",
"course-authoring.grading-settings.credit-eligibility.title": "Credit eligibility",
"course-authoring.grading-settings.assignment.type-name.title": "Assignment type name",
"course-authoring.grading-settings.assignment.type-name.description": "The general category for this type of assignment, for example, Homework or Midterm Exam. This name is visible to learners.",
"course-authoring.grading-settings.assignment.type-name.error.message-1": "The assignment type must have a name.",
"course-authoring.grading-settings.assignment.type-name.error.message-3": "There's already another assignment type with this name.",
"course-authoring.grading-settings.assignment.abbreviation.title": "Abbreviation",
"course-authoring.grading-settings.assignment.abbreviation.description": "This short name for the assignment type (for example, HW or Midterm) appears next to assignments on a learner's Progress page.",
"course-authoring.grading-settings.assignment.weight-of-total-grade.title": "Weight of total grade",
"course-authoring.grading-settings.assignment.weight-of-total-grade.description": "The weight of all assignments of this type as a percentage of the total grade, for example, 40. Do not include the percent symbol.",
"course-authoring.grading-settings.assignment.weight-of-total-grade.error.message": "Please enter an integer between 0 and 100.",
"course-authoring.grading-settings.assignment.total-number.title": "Total number",
"course-authoring.grading-settings.assignment.total-number.description": "The number of subsections in the course that contain problems of this assignment type.",
"course-authoring.grading-settings.assignment.total-number.error.message": "Please enter an integer greater than 0.",
"course-authoring.grading-settings.assignment.number-of-droppable.title": "Number of droppable",
"course-authoring.grading-settings.assignment.number-of-droppable.description": "The number of assignments of this type that will be dropped. The lowest scoring assignments are dropped first.",
"course-authoring.grading-settings.assignment.number-of-droppable.error.message": "Please enter non-negative integer.",
"course-authoring.grading-settings.assignment.alert.warning.description": "There are no assignments of this type in the course.",
"course-authoring.grading-settings.assignment.delete.button": "Delete",
"course-authoring.grading-settings.assignment.number-of-droppable.second.error.message": "Cannot drop more {type} assignments than are assigned.",
"course-authoring.grading-settings.assignment.alert.warning.usage.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:",
"course-authoring.grading-settings.assignment.alert.warning.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:",
"course-authoring.grading-settings.assignment.alert.success.title": "The number of {type} assignments in the course matches the number defined here.",
"course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}"
}

View File

@@ -357,7 +357,7 @@
"course-authoring.advanced-settings.heading.title": "Advanced settings",
"course-authoring.advanced-settings.heading.subtitle": "Settings",
"course-authoring.advanced-settings.policies.title": "Manual policy definition",
"course-authoring.advanced-settings.alert.warning": "You`ve made some changes",
"course-authoring.advanced-settings.alert.warning": "You've made some changes",
"course-authoring.advanced-settings.alert.warning.descriptions": "Your changes will not take effect until you save your progress. Take care with key and value formatting, as validation is not implemented.",
"course-authoring.advanced-settings.alert.success": "Your policy changes have been saved.",
"course-authoring.advanced-settings.alert.success.descriptions": "No validation is performed on policy keys or value pairs. If you are having difficulties, check your formatting.",
@@ -416,7 +416,7 @@
"course-authoring.schedule.alert.button.cancel": "Cancel",
"course-authoring.schedule.alert.warning.aria.labelledby": "notification-warning-title",
"course-authoring.schedule.alert.warning.aria.describedby": "notification-warning-description",
"course-authoring.schedule.alert.warning": "You`ve made some changes",
"course-authoring.schedule.alert.warning": "You've made some changes",
"course-authoring.schedule.alert.warning.descriptions": "Your changes will not take effect until you save your progress.",
"course-authoring.schedule.alert.success.aria.labelledby": "alert-confirmation-title",
"course-authoring.schedule.alert.success.aria.describedby": "alert-confirmation-description",
@@ -550,6 +550,26 @@
"course-authoring.generic.alert.warning.offline.description": "This may be happening because of an error with our server or your internet connection. Try refreshing the page or making sure you are online.",
"course-authoring.generic.alert.warning.offline.title.aria.labelled-by": "alert-internet-error-title",
"course-authoring.generic.alert.warning.offline.subtitle.aria.described-by": "alert-internet-error-description",
"course-authoring.grading-settings.fail-segment.text": "Fail",
"course-authoring.grading-settings.sidebar.about.title": "What can I do on this page?",
"course-authoring.grading-settings.sidebar.about.text-1": "You can use the slider under Overall Grade Range to specify whether your course is pass/fail or graded by letter, and to establish the thresholds for each grade.",
"course-authoring.grading-settings.sidebar.about.text-2": "You can specify whether your course offers students a grace period for late assignments.",
"course-authoring.grading-settings.sidebar.about.text-3": "You can also create assignment types, such as homework, labs, quizzes, and exams, and specify how much of a student's grade each assignment type is worth.",
"course-authoring.grading-settings.add-new-segment.btn.text": "Add new grading segment",
"course-authoring.grading-settings.remove-segment.btn.text": "Remove",
"course-authoring.grading-settings.heading.title": "Grading",
"course-authoring.grading-settings.heading.subtitle": "Settings",
"course-authoring.grading-settings.policies.title": "Overall grade range",
"course-authoring.grading-settings.policies.description": "Your overall grading scale for student final grades",
"course-authoring.grading-settings.alert.warning": "You've made some changes",
"course-authoring.grading-settings.alert.warning.descriptions": "Your changes will not take effect until you save your progress. Take care with key and value formatting, as validation is not implemented.",
"course-authoring.grading-settings.alert.success": "Your changes have been saved.",
"course-authoring.grading-settings.alert.button.save": "Save changes",
"course-authoring.grading-settings.alert.button.cancel": "Cancel",
"course-authoring.grading-settings.alert.warning.aria.labelledby": "notification-warning-title",
"course-authoring.grading-settings.alert.warning.aria.describedby": "notification-warning-description",
"course-authoring.grading-settings.alert.success.aria.labelledby": "alert-confirmation-title",
"course-authoring.grading-settings.alert.success.aria.describedby": "alert-confirmation-description",
"course-authoring.schedule-section.requirements.entrance.label": "Entrance exam",
"course-authoring.schedule-section.requirements.entrance.collapse.title": "Require students to pass an exam before beginning the course.",
"course-authoring.schedule-section.requirements.entrance.collapse.paragraph": "You can now view and author your course entrance exam from the {hyperlink}.",
@@ -585,5 +605,40 @@
"course-authoring.schedule-section.license.creative-commons.option.ND.description": "Allow others to copy, distribute, display and perform only verbatim copies of your work, not derivative works based upon it. This option is incompatible with 'Share Alike'.",
"course-authoring.schedule-section.license.creative-commons.option.SA.label": "Share alike",
"course-authoring.schedule-section.license.creative-commons.option.SA.description": "Allow others to distribute derivative works only under a license identical to the license that governs your work. This option is incompatible with 'No Derivatives'.",
"course-authoring.schedule.alert.button.saving": "Saving"
"course-authoring.schedule.alert.button.saving": "Saving",
"course-authoring.grading-settings.credit.eligibility.label": "Minimum credit-eligible grade:",
"course-authoring.grading-settings.credit.eligibility.description": "% Must be greater than or equal to the course passing grade",
"course-authoring.grading-settings.credit.eligibility.error.msg": "Not able to set passing grade to less than:",
"course-authoring.grading-settings.deadline.label": "Grace period on deadline:",
"course-authoring.grading-settings.deadline.description": "Leeway on due dates",
"course-authoring.grading-settings.alert.button.saving": "Saving",
"course-authoring.grading-settings.add-new-assignment-type.btn": "New assignment type",
"course-authoring.grading-settings.assignment-type.description": "Categories and labels for any exercises that are gradable",
"course-authoring.grading-settings.assignment-type.title": "Assignment types",
"course-authoring.grading-settings.grading-rules-policies.description": "Deadlines, requirements, and logistics around grading student work",
"course-authoring.grading-settings.grading-rules-policies.title": "Grading rules & policies",
"course-authoring.grading-settings.credit-eligibility.description": "Settings for course credit eligibility",
"course-authoring.grading-settings.credit-eligibility.title": "Credit eligibility",
"course-authoring.grading-settings.assignment.type-name.title": "Assignment type name",
"course-authoring.grading-settings.assignment.type-name.description": "The general category for this type of assignment, for example, Homework or Midterm Exam. This name is visible to learners.",
"course-authoring.grading-settings.assignment.type-name.error.message-1": "The assignment type must have a name.",
"course-authoring.grading-settings.assignment.type-name.error.message-3": "There's already another assignment type with this name.",
"course-authoring.grading-settings.assignment.abbreviation.title": "Abbreviation",
"course-authoring.grading-settings.assignment.abbreviation.description": "This short name for the assignment type (for example, HW or Midterm) appears next to assignments on a learner's Progress page.",
"course-authoring.grading-settings.assignment.weight-of-total-grade.title": "Weight of total grade",
"course-authoring.grading-settings.assignment.weight-of-total-grade.description": "The weight of all assignments of this type as a percentage of the total grade, for example, 40. Do not include the percent symbol.",
"course-authoring.grading-settings.assignment.weight-of-total-grade.error.message": "Please enter an integer between 0 and 100.",
"course-authoring.grading-settings.assignment.total-number.title": "Total number",
"course-authoring.grading-settings.assignment.total-number.description": "The number of subsections in the course that contain problems of this assignment type.",
"course-authoring.grading-settings.assignment.total-number.error.message": "Please enter an integer greater than 0.",
"course-authoring.grading-settings.assignment.number-of-droppable.title": "Number of droppable",
"course-authoring.grading-settings.assignment.number-of-droppable.description": "The number of assignments of this type that will be dropped. The lowest scoring assignments are dropped first.",
"course-authoring.grading-settings.assignment.number-of-droppable.error.message": "Please enter non-negative integer.",
"course-authoring.grading-settings.assignment.alert.warning.description": "There are no assignments of this type in the course.",
"course-authoring.grading-settings.assignment.delete.button": "Delete",
"course-authoring.grading-settings.assignment.number-of-droppable.second.error.message": "Cannot drop more {type} assignments than are assigned.",
"course-authoring.grading-settings.assignment.alert.warning.usage.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:",
"course-authoring.grading-settings.assignment.alert.warning.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:",
"course-authoring.grading-settings.assignment.alert.success.title": "The number of {type} assignments in the course matches the number defined here.",
"course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}"
}

View File

@@ -357,7 +357,7 @@
"course-authoring.advanced-settings.heading.title": "Advanced settings",
"course-authoring.advanced-settings.heading.subtitle": "Settings",
"course-authoring.advanced-settings.policies.title": "Manual policy definition",
"course-authoring.advanced-settings.alert.warning": "You`ve made some changes",
"course-authoring.advanced-settings.alert.warning": "You've made some changes",
"course-authoring.advanced-settings.alert.warning.descriptions": "Your changes will not take effect until you save your progress. Take care with key and value formatting, as validation is not implemented.",
"course-authoring.advanced-settings.alert.success": "Your policy changes have been saved.",
"course-authoring.advanced-settings.alert.success.descriptions": "No validation is performed on policy keys or value pairs. If you are having difficulties, check your formatting.",
@@ -416,7 +416,7 @@
"course-authoring.schedule.alert.button.cancel": "Cancel",
"course-authoring.schedule.alert.warning.aria.labelledby": "notification-warning-title",
"course-authoring.schedule.alert.warning.aria.describedby": "notification-warning-description",
"course-authoring.schedule.alert.warning": "You`ve made some changes",
"course-authoring.schedule.alert.warning": "You've made some changes",
"course-authoring.schedule.alert.warning.descriptions": "Your changes will not take effect until you save your progress.",
"course-authoring.schedule.alert.success.aria.labelledby": "alert-confirmation-title",
"course-authoring.schedule.alert.success.aria.describedby": "alert-confirmation-description",
@@ -550,6 +550,26 @@
"course-authoring.generic.alert.warning.offline.description": "This may be happening because of an error with our server or your internet connection. Try refreshing the page or making sure you are online.",
"course-authoring.generic.alert.warning.offline.title.aria.labelled-by": "alert-internet-error-title",
"course-authoring.generic.alert.warning.offline.subtitle.aria.described-by": "alert-internet-error-description",
"course-authoring.grading-settings.fail-segment.text": "Fail",
"course-authoring.grading-settings.sidebar.about.title": "What can I do on this page?",
"course-authoring.grading-settings.sidebar.about.text-1": "You can use the slider under Overall Grade Range to specify whether your course is pass/fail or graded by letter, and to establish the thresholds for each grade.",
"course-authoring.grading-settings.sidebar.about.text-2": "You can specify whether your course offers students a grace period for late assignments.",
"course-authoring.grading-settings.sidebar.about.text-3": "You can also create assignment types, such as homework, labs, quizzes, and exams, and specify how much of a student's grade each assignment type is worth.",
"course-authoring.grading-settings.add-new-segment.btn.text": "Add new grading segment",
"course-authoring.grading-settings.remove-segment.btn.text": "Remove",
"course-authoring.grading-settings.heading.title": "Grading",
"course-authoring.grading-settings.heading.subtitle": "Settings",
"course-authoring.grading-settings.policies.title": "Overall grade range",
"course-authoring.grading-settings.policies.description": "Your overall grading scale for student final grades",
"course-authoring.grading-settings.alert.warning": "You've made some changes",
"course-authoring.grading-settings.alert.warning.descriptions": "Your changes will not take effect until you save your progress. Take care with key and value formatting, as validation is not implemented.",
"course-authoring.grading-settings.alert.success": "Your changes have been saved.",
"course-authoring.grading-settings.alert.button.save": "Save changes",
"course-authoring.grading-settings.alert.button.cancel": "Cancel",
"course-authoring.grading-settings.alert.warning.aria.labelledby": "notification-warning-title",
"course-authoring.grading-settings.alert.warning.aria.describedby": "notification-warning-description",
"course-authoring.grading-settings.alert.success.aria.labelledby": "alert-confirmation-title",
"course-authoring.grading-settings.alert.success.aria.describedby": "alert-confirmation-description",
"course-authoring.schedule-section.requirements.entrance.label": "Entrance exam",
"course-authoring.schedule-section.requirements.entrance.collapse.title": "Require students to pass an exam before beginning the course.",
"course-authoring.schedule-section.requirements.entrance.collapse.paragraph": "You can now view and author your course entrance exam from the {hyperlink}.",
@@ -585,5 +605,40 @@
"course-authoring.schedule-section.license.creative-commons.option.ND.description": "Allow others to copy, distribute, display and perform only verbatim copies of your work, not derivative works based upon it. This option is incompatible with 'Share Alike'.",
"course-authoring.schedule-section.license.creative-commons.option.SA.label": "Share alike",
"course-authoring.schedule-section.license.creative-commons.option.SA.description": "Allow others to distribute derivative works only under a license identical to the license that governs your work. This option is incompatible with 'No Derivatives'.",
"course-authoring.schedule.alert.button.saving": "Saving"
"course-authoring.schedule.alert.button.saving": "Saving",
"course-authoring.grading-settings.credit.eligibility.label": "Minimum credit-eligible grade:",
"course-authoring.grading-settings.credit.eligibility.description": "% Must be greater than or equal to the course passing grade",
"course-authoring.grading-settings.credit.eligibility.error.msg": "Not able to set passing grade to less than:",
"course-authoring.grading-settings.deadline.label": "Grace period on deadline:",
"course-authoring.grading-settings.deadline.description": "Leeway on due dates",
"course-authoring.grading-settings.alert.button.saving": "Saving",
"course-authoring.grading-settings.add-new-assignment-type.btn": "New assignment type",
"course-authoring.grading-settings.assignment-type.description": "Categories and labels for any exercises that are gradable",
"course-authoring.grading-settings.assignment-type.title": "Assignment types",
"course-authoring.grading-settings.grading-rules-policies.description": "Deadlines, requirements, and logistics around grading student work",
"course-authoring.grading-settings.grading-rules-policies.title": "Grading rules & policies",
"course-authoring.grading-settings.credit-eligibility.description": "Settings for course credit eligibility",
"course-authoring.grading-settings.credit-eligibility.title": "Credit eligibility",
"course-authoring.grading-settings.assignment.type-name.title": "Assignment type name",
"course-authoring.grading-settings.assignment.type-name.description": "The general category for this type of assignment, for example, Homework or Midterm Exam. This name is visible to learners.",
"course-authoring.grading-settings.assignment.type-name.error.message-1": "The assignment type must have a name.",
"course-authoring.grading-settings.assignment.type-name.error.message-3": "There's already another assignment type with this name.",
"course-authoring.grading-settings.assignment.abbreviation.title": "Abbreviation",
"course-authoring.grading-settings.assignment.abbreviation.description": "This short name for the assignment type (for example, HW or Midterm) appears next to assignments on a learner's Progress page.",
"course-authoring.grading-settings.assignment.weight-of-total-grade.title": "Weight of total grade",
"course-authoring.grading-settings.assignment.weight-of-total-grade.description": "The weight of all assignments of this type as a percentage of the total grade, for example, 40. Do not include the percent symbol.",
"course-authoring.grading-settings.assignment.weight-of-total-grade.error.message": "Please enter an integer between 0 and 100.",
"course-authoring.grading-settings.assignment.total-number.title": "Total number",
"course-authoring.grading-settings.assignment.total-number.description": "The number of subsections in the course that contain problems of this assignment type.",
"course-authoring.grading-settings.assignment.total-number.error.message": "Please enter an integer greater than 0.",
"course-authoring.grading-settings.assignment.number-of-droppable.title": "Number of droppable",
"course-authoring.grading-settings.assignment.number-of-droppable.description": "The number of assignments of this type that will be dropped. The lowest scoring assignments are dropped first.",
"course-authoring.grading-settings.assignment.number-of-droppable.error.message": "Please enter non-negative integer.",
"course-authoring.grading-settings.assignment.alert.warning.description": "There are no assignments of this type in the course.",
"course-authoring.grading-settings.assignment.delete.button": "Delete",
"course-authoring.grading-settings.assignment.number-of-droppable.second.error.message": "Cannot drop more {type} assignments than are assigned.",
"course-authoring.grading-settings.assignment.alert.warning.usage.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:",
"course-authoring.grading-settings.assignment.alert.warning.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:",
"course-authoring.grading-settings.assignment.alert.success.title": "The number of {type} assignments in the course matches the number defined here.",
"course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}"
}

View File

@@ -357,7 +357,7 @@
"course-authoring.advanced-settings.heading.title": "Advanced settings",
"course-authoring.advanced-settings.heading.subtitle": "Settings",
"course-authoring.advanced-settings.policies.title": "Manual policy definition",
"course-authoring.advanced-settings.alert.warning": "You`ve made some changes",
"course-authoring.advanced-settings.alert.warning": "You've made some changes",
"course-authoring.advanced-settings.alert.warning.descriptions": "Your changes will not take effect until you save your progress. Take care with key and value formatting, as validation is not implemented.",
"course-authoring.advanced-settings.alert.success": "Your policy changes have been saved.",
"course-authoring.advanced-settings.alert.success.descriptions": "No validation is performed on policy keys or value pairs. If you are having difficulties, check your formatting.",
@@ -416,7 +416,7 @@
"course-authoring.schedule.alert.button.cancel": "Cancel",
"course-authoring.schedule.alert.warning.aria.labelledby": "notification-warning-title",
"course-authoring.schedule.alert.warning.aria.describedby": "notification-warning-description",
"course-authoring.schedule.alert.warning": "You`ve made some changes",
"course-authoring.schedule.alert.warning": "You've made some changes",
"course-authoring.schedule.alert.warning.descriptions": "Your changes will not take effect until you save your progress.",
"course-authoring.schedule.alert.success.aria.labelledby": "alert-confirmation-title",
"course-authoring.schedule.alert.success.aria.describedby": "alert-confirmation-description",
@@ -550,6 +550,26 @@
"course-authoring.generic.alert.warning.offline.description": "This may be happening because of an error with our server or your internet connection. Try refreshing the page or making sure you are online.",
"course-authoring.generic.alert.warning.offline.title.aria.labelled-by": "alert-internet-error-title",
"course-authoring.generic.alert.warning.offline.subtitle.aria.described-by": "alert-internet-error-description",
"course-authoring.grading-settings.fail-segment.text": "Fail",
"course-authoring.grading-settings.sidebar.about.title": "What can I do on this page?",
"course-authoring.grading-settings.sidebar.about.text-1": "You can use the slider under Overall Grade Range to specify whether your course is pass/fail or graded by letter, and to establish the thresholds for each grade.",
"course-authoring.grading-settings.sidebar.about.text-2": "You can specify whether your course offers students a grace period for late assignments.",
"course-authoring.grading-settings.sidebar.about.text-3": "You can also create assignment types, such as homework, labs, quizzes, and exams, and specify how much of a student's grade each assignment type is worth.",
"course-authoring.grading-settings.add-new-segment.btn.text": "Add new grading segment",
"course-authoring.grading-settings.remove-segment.btn.text": "Remove",
"course-authoring.grading-settings.heading.title": "Grading",
"course-authoring.grading-settings.heading.subtitle": "Settings",
"course-authoring.grading-settings.policies.title": "Overall grade range",
"course-authoring.grading-settings.policies.description": "Your overall grading scale for student final grades",
"course-authoring.grading-settings.alert.warning": "You've made some changes",
"course-authoring.grading-settings.alert.warning.descriptions": "Your changes will not take effect until you save your progress. Take care with key and value formatting, as validation is not implemented.",
"course-authoring.grading-settings.alert.success": "Your changes have been saved.",
"course-authoring.grading-settings.alert.button.save": "Save changes",
"course-authoring.grading-settings.alert.button.cancel": "Cancel",
"course-authoring.grading-settings.alert.warning.aria.labelledby": "notification-warning-title",
"course-authoring.grading-settings.alert.warning.aria.describedby": "notification-warning-description",
"course-authoring.grading-settings.alert.success.aria.labelledby": "alert-confirmation-title",
"course-authoring.grading-settings.alert.success.aria.describedby": "alert-confirmation-description",
"course-authoring.schedule-section.requirements.entrance.label": "Entrance exam",
"course-authoring.schedule-section.requirements.entrance.collapse.title": "Require students to pass an exam before beginning the course.",
"course-authoring.schedule-section.requirements.entrance.collapse.paragraph": "You can now view and author your course entrance exam from the {hyperlink}.",
@@ -585,5 +605,40 @@
"course-authoring.schedule-section.license.creative-commons.option.ND.description": "Allow others to copy, distribute, display and perform only verbatim copies of your work, not derivative works based upon it. This option is incompatible with 'Share Alike'.",
"course-authoring.schedule-section.license.creative-commons.option.SA.label": "Share alike",
"course-authoring.schedule-section.license.creative-commons.option.SA.description": "Allow others to distribute derivative works only under a license identical to the license that governs your work. This option is incompatible with 'No Derivatives'.",
"course-authoring.schedule.alert.button.saving": "Saving"
"course-authoring.schedule.alert.button.saving": "Saving",
"course-authoring.grading-settings.credit.eligibility.label": "Minimum credit-eligible grade:",
"course-authoring.grading-settings.credit.eligibility.description": "% Must be greater than or equal to the course passing grade",
"course-authoring.grading-settings.credit.eligibility.error.msg": "Not able to set passing grade to less than:",
"course-authoring.grading-settings.deadline.label": "Grace period on deadline:",
"course-authoring.grading-settings.deadline.description": "Leeway on due dates",
"course-authoring.grading-settings.alert.button.saving": "Saving",
"course-authoring.grading-settings.add-new-assignment-type.btn": "New assignment type",
"course-authoring.grading-settings.assignment-type.description": "Categories and labels for any exercises that are gradable",
"course-authoring.grading-settings.assignment-type.title": "Assignment types",
"course-authoring.grading-settings.grading-rules-policies.description": "Deadlines, requirements, and logistics around grading student work",
"course-authoring.grading-settings.grading-rules-policies.title": "Grading rules & policies",
"course-authoring.grading-settings.credit-eligibility.description": "Settings for course credit eligibility",
"course-authoring.grading-settings.credit-eligibility.title": "Credit eligibility",
"course-authoring.grading-settings.assignment.type-name.title": "Assignment type name",
"course-authoring.grading-settings.assignment.type-name.description": "The general category for this type of assignment, for example, Homework or Midterm Exam. This name is visible to learners.",
"course-authoring.grading-settings.assignment.type-name.error.message-1": "The assignment type must have a name.",
"course-authoring.grading-settings.assignment.type-name.error.message-3": "There's already another assignment type with this name.",
"course-authoring.grading-settings.assignment.abbreviation.title": "Abbreviation",
"course-authoring.grading-settings.assignment.abbreviation.description": "This short name for the assignment type (for example, HW or Midterm) appears next to assignments on a learner's Progress page.",
"course-authoring.grading-settings.assignment.weight-of-total-grade.title": "Weight of total grade",
"course-authoring.grading-settings.assignment.weight-of-total-grade.description": "The weight of all assignments of this type as a percentage of the total grade, for example, 40. Do not include the percent symbol.",
"course-authoring.grading-settings.assignment.weight-of-total-grade.error.message": "Please enter an integer between 0 and 100.",
"course-authoring.grading-settings.assignment.total-number.title": "Total number",
"course-authoring.grading-settings.assignment.total-number.description": "The number of subsections in the course that contain problems of this assignment type.",
"course-authoring.grading-settings.assignment.total-number.error.message": "Please enter an integer greater than 0.",
"course-authoring.grading-settings.assignment.number-of-droppable.title": "Number of droppable",
"course-authoring.grading-settings.assignment.number-of-droppable.description": "The number of assignments of this type that will be dropped. The lowest scoring assignments are dropped first.",
"course-authoring.grading-settings.assignment.number-of-droppable.error.message": "Please enter non-negative integer.",
"course-authoring.grading-settings.assignment.alert.warning.description": "There are no assignments of this type in the course.",
"course-authoring.grading-settings.assignment.delete.button": "Delete",
"course-authoring.grading-settings.assignment.number-of-droppable.second.error.message": "Cannot drop more {type} assignments than are assigned.",
"course-authoring.grading-settings.assignment.alert.warning.usage.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:",
"course-authoring.grading-settings.assignment.alert.warning.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:",
"course-authoring.grading-settings.assignment.alert.success.title": "The number of {type} assignments in the course matches the number defined here.",
"course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}"
}

View File

@@ -357,7 +357,7 @@
"course-authoring.advanced-settings.heading.title": "Advanced settings",
"course-authoring.advanced-settings.heading.subtitle": "Settings",
"course-authoring.advanced-settings.policies.title": "Manual policy definition",
"course-authoring.advanced-settings.alert.warning": "You`ve made some changes",
"course-authoring.advanced-settings.alert.warning": "You've made some changes",
"course-authoring.advanced-settings.alert.warning.descriptions": "Your changes will not take effect until you save your progress. Take care with key and value formatting, as validation is not implemented.",
"course-authoring.advanced-settings.alert.success": "Your policy changes have been saved.",
"course-authoring.advanced-settings.alert.success.descriptions": "No validation is performed on policy keys or value pairs. If you are having difficulties, check your formatting.",
@@ -416,7 +416,7 @@
"course-authoring.schedule.alert.button.cancel": "Cancel",
"course-authoring.schedule.alert.warning.aria.labelledby": "notification-warning-title",
"course-authoring.schedule.alert.warning.aria.describedby": "notification-warning-description",
"course-authoring.schedule.alert.warning": "You`ve made some changes",
"course-authoring.schedule.alert.warning": "You've made some changes",
"course-authoring.schedule.alert.warning.descriptions": "Your changes will not take effect until you save your progress.",
"course-authoring.schedule.alert.success.aria.labelledby": "alert-confirmation-title",
"course-authoring.schedule.alert.success.aria.describedby": "alert-confirmation-description",
@@ -550,6 +550,26 @@
"course-authoring.generic.alert.warning.offline.description": "This may be happening because of an error with our server or your internet connection. Try refreshing the page or making sure you are online.",
"course-authoring.generic.alert.warning.offline.title.aria.labelled-by": "alert-internet-error-title",
"course-authoring.generic.alert.warning.offline.subtitle.aria.described-by": "alert-internet-error-description",
"course-authoring.grading-settings.fail-segment.text": "Fail",
"course-authoring.grading-settings.sidebar.about.title": "What can I do on this page?",
"course-authoring.grading-settings.sidebar.about.text-1": "You can use the slider under Overall Grade Range to specify whether your course is pass/fail or graded by letter, and to establish the thresholds for each grade.",
"course-authoring.grading-settings.sidebar.about.text-2": "You can specify whether your course offers students a grace period for late assignments.",
"course-authoring.grading-settings.sidebar.about.text-3": "You can also create assignment types, such as homework, labs, quizzes, and exams, and specify how much of a student's grade each assignment type is worth.",
"course-authoring.grading-settings.add-new-segment.btn.text": "Add new grading segment",
"course-authoring.grading-settings.remove-segment.btn.text": "Remove",
"course-authoring.grading-settings.heading.title": "Grading",
"course-authoring.grading-settings.heading.subtitle": "Settings",
"course-authoring.grading-settings.policies.title": "Overall grade range",
"course-authoring.grading-settings.policies.description": "Your overall grading scale for student final grades",
"course-authoring.grading-settings.alert.warning": "You've made some changes",
"course-authoring.grading-settings.alert.warning.descriptions": "Your changes will not take effect until you save your progress. Take care with key and value formatting, as validation is not implemented.",
"course-authoring.grading-settings.alert.success": "Your changes have been saved.",
"course-authoring.grading-settings.alert.button.save": "Save changes",
"course-authoring.grading-settings.alert.button.cancel": "Cancel",
"course-authoring.grading-settings.alert.warning.aria.labelledby": "notification-warning-title",
"course-authoring.grading-settings.alert.warning.aria.describedby": "notification-warning-description",
"course-authoring.grading-settings.alert.success.aria.labelledby": "alert-confirmation-title",
"course-authoring.grading-settings.alert.success.aria.describedby": "alert-confirmation-description",
"course-authoring.schedule-section.requirements.entrance.label": "Entrance exam",
"course-authoring.schedule-section.requirements.entrance.collapse.title": "Require students to pass an exam before beginning the course.",
"course-authoring.schedule-section.requirements.entrance.collapse.paragraph": "You can now view and author your course entrance exam from the {hyperlink}.",
@@ -585,5 +605,40 @@
"course-authoring.schedule-section.license.creative-commons.option.ND.description": "Allow others to copy, distribute, display and perform only verbatim copies of your work, not derivative works based upon it. This option is incompatible with 'Share Alike'.",
"course-authoring.schedule-section.license.creative-commons.option.SA.label": "Share alike",
"course-authoring.schedule-section.license.creative-commons.option.SA.description": "Allow others to distribute derivative works only under a license identical to the license that governs your work. This option is incompatible with 'No Derivatives'.",
"course-authoring.schedule.alert.button.saving": "Saving"
"course-authoring.schedule.alert.button.saving": "Saving",
"course-authoring.grading-settings.credit.eligibility.label": "Minimum credit-eligible grade:",
"course-authoring.grading-settings.credit.eligibility.description": "% Must be greater than or equal to the course passing grade",
"course-authoring.grading-settings.credit.eligibility.error.msg": "Not able to set passing grade to less than:",
"course-authoring.grading-settings.deadline.label": "Grace period on deadline:",
"course-authoring.grading-settings.deadline.description": "Leeway on due dates",
"course-authoring.grading-settings.alert.button.saving": "Saving",
"course-authoring.grading-settings.add-new-assignment-type.btn": "New assignment type",
"course-authoring.grading-settings.assignment-type.description": "Categories and labels for any exercises that are gradable",
"course-authoring.grading-settings.assignment-type.title": "Assignment types",
"course-authoring.grading-settings.grading-rules-policies.description": "Deadlines, requirements, and logistics around grading student work",
"course-authoring.grading-settings.grading-rules-policies.title": "Grading rules & policies",
"course-authoring.grading-settings.credit-eligibility.description": "Settings for course credit eligibility",
"course-authoring.grading-settings.credit-eligibility.title": "Credit eligibility",
"course-authoring.grading-settings.assignment.type-name.title": "Assignment type name",
"course-authoring.grading-settings.assignment.type-name.description": "The general category for this type of assignment, for example, Homework or Midterm Exam. This name is visible to learners.",
"course-authoring.grading-settings.assignment.type-name.error.message-1": "The assignment type must have a name.",
"course-authoring.grading-settings.assignment.type-name.error.message-3": "There's already another assignment type with this name.",
"course-authoring.grading-settings.assignment.abbreviation.title": "Abbreviation",
"course-authoring.grading-settings.assignment.abbreviation.description": "This short name for the assignment type (for example, HW or Midterm) appears next to assignments on a learner's Progress page.",
"course-authoring.grading-settings.assignment.weight-of-total-grade.title": "Weight of total grade",
"course-authoring.grading-settings.assignment.weight-of-total-grade.description": "The weight of all assignments of this type as a percentage of the total grade, for example, 40. Do not include the percent symbol.",
"course-authoring.grading-settings.assignment.weight-of-total-grade.error.message": "Please enter an integer between 0 and 100.",
"course-authoring.grading-settings.assignment.total-number.title": "Total number",
"course-authoring.grading-settings.assignment.total-number.description": "The number of subsections in the course that contain problems of this assignment type.",
"course-authoring.grading-settings.assignment.total-number.error.message": "Please enter an integer greater than 0.",
"course-authoring.grading-settings.assignment.number-of-droppable.title": "Number of droppable",
"course-authoring.grading-settings.assignment.number-of-droppable.description": "The number of assignments of this type that will be dropped. The lowest scoring assignments are dropped first.",
"course-authoring.grading-settings.assignment.number-of-droppable.error.message": "Please enter non-negative integer.",
"course-authoring.grading-settings.assignment.alert.warning.description": "There are no assignments of this type in the course.",
"course-authoring.grading-settings.assignment.delete.button": "Delete",
"course-authoring.grading-settings.assignment.number-of-droppable.second.error.message": "Cannot drop more {type} assignments than are assigned.",
"course-authoring.grading-settings.assignment.alert.warning.usage.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:",
"course-authoring.grading-settings.assignment.alert.warning.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:",
"course-authoring.grading-settings.assignment.alert.success.title": "The number of {type} assignments in the course matches the number defined here.",
"course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}"
}

View File

@@ -357,7 +357,7 @@
"course-authoring.advanced-settings.heading.title": "Advanced settings",
"course-authoring.advanced-settings.heading.subtitle": "Settings",
"course-authoring.advanced-settings.policies.title": "Manual policy definition",
"course-authoring.advanced-settings.alert.warning": "You`ve made some changes",
"course-authoring.advanced-settings.alert.warning": "You've made some changes",
"course-authoring.advanced-settings.alert.warning.descriptions": "Your changes will not take effect until you save your progress. Take care with key and value formatting, as validation is not implemented.",
"course-authoring.advanced-settings.alert.success": "Your policy changes have been saved.",
"course-authoring.advanced-settings.alert.success.descriptions": "No validation is performed on policy keys or value pairs. If you are having difficulties, check your formatting.",
@@ -416,7 +416,7 @@
"course-authoring.schedule.alert.button.cancel": "Cancel",
"course-authoring.schedule.alert.warning.aria.labelledby": "notification-warning-title",
"course-authoring.schedule.alert.warning.aria.describedby": "notification-warning-description",
"course-authoring.schedule.alert.warning": "You`ve made some changes",
"course-authoring.schedule.alert.warning": "You've made some changes",
"course-authoring.schedule.alert.warning.descriptions": "Your changes will not take effect until you save your progress.",
"course-authoring.schedule.alert.success.aria.labelledby": "alert-confirmation-title",
"course-authoring.schedule.alert.success.aria.describedby": "alert-confirmation-description",
@@ -550,6 +550,26 @@
"course-authoring.generic.alert.warning.offline.description": "This may be happening because of an error with our server or your internet connection. Try refreshing the page or making sure you are online.",
"course-authoring.generic.alert.warning.offline.title.aria.labelled-by": "alert-internet-error-title",
"course-authoring.generic.alert.warning.offline.subtitle.aria.described-by": "alert-internet-error-description",
"course-authoring.grading-settings.fail-segment.text": "Fail",
"course-authoring.grading-settings.sidebar.about.title": "What can I do on this page?",
"course-authoring.grading-settings.sidebar.about.text-1": "You can use the slider under Overall Grade Range to specify whether your course is pass/fail or graded by letter, and to establish the thresholds for each grade.",
"course-authoring.grading-settings.sidebar.about.text-2": "You can specify whether your course offers students a grace period for late assignments.",
"course-authoring.grading-settings.sidebar.about.text-3": "You can also create assignment types, such as homework, labs, quizzes, and exams, and specify how much of a student's grade each assignment type is worth.",
"course-authoring.grading-settings.add-new-segment.btn.text": "Add new grading segment",
"course-authoring.grading-settings.remove-segment.btn.text": "Remove",
"course-authoring.grading-settings.heading.title": "Grading",
"course-authoring.grading-settings.heading.subtitle": "Settings",
"course-authoring.grading-settings.policies.title": "Overall grade range",
"course-authoring.grading-settings.policies.description": "Your overall grading scale for student final grades",
"course-authoring.grading-settings.alert.warning": "You've made some changes",
"course-authoring.grading-settings.alert.warning.descriptions": "Your changes will not take effect until you save your progress. Take care with key and value formatting, as validation is not implemented.",
"course-authoring.grading-settings.alert.success": "Your changes have been saved.",
"course-authoring.grading-settings.alert.button.save": "Save changes",
"course-authoring.grading-settings.alert.button.cancel": "Cancel",
"course-authoring.grading-settings.alert.warning.aria.labelledby": "notification-warning-title",
"course-authoring.grading-settings.alert.warning.aria.describedby": "notification-warning-description",
"course-authoring.grading-settings.alert.success.aria.labelledby": "alert-confirmation-title",
"course-authoring.grading-settings.alert.success.aria.describedby": "alert-confirmation-description",
"course-authoring.schedule-section.requirements.entrance.label": "Entrance exam",
"course-authoring.schedule-section.requirements.entrance.collapse.title": "Require students to pass an exam before beginning the course.",
"course-authoring.schedule-section.requirements.entrance.collapse.paragraph": "You can now view and author your course entrance exam from the {hyperlink}.",
@@ -585,5 +605,40 @@
"course-authoring.schedule-section.license.creative-commons.option.ND.description": "Allow others to copy, distribute, display and perform only verbatim copies of your work, not derivative works based upon it. This option is incompatible with 'Share Alike'.",
"course-authoring.schedule-section.license.creative-commons.option.SA.label": "Share alike",
"course-authoring.schedule-section.license.creative-commons.option.SA.description": "Allow others to distribute derivative works only under a license identical to the license that governs your work. This option is incompatible with 'No Derivatives'.",
"course-authoring.schedule.alert.button.saving": "Saving"
"course-authoring.schedule.alert.button.saving": "Saving",
"course-authoring.grading-settings.credit.eligibility.label": "Minimum credit-eligible grade:",
"course-authoring.grading-settings.credit.eligibility.description": "% Must be greater than or equal to the course passing grade",
"course-authoring.grading-settings.credit.eligibility.error.msg": "Not able to set passing grade to less than:",
"course-authoring.grading-settings.deadline.label": "Grace period on deadline:",
"course-authoring.grading-settings.deadline.description": "Leeway on due dates",
"course-authoring.grading-settings.alert.button.saving": "Saving",
"course-authoring.grading-settings.add-new-assignment-type.btn": "New assignment type",
"course-authoring.grading-settings.assignment-type.description": "Categories and labels for any exercises that are gradable",
"course-authoring.grading-settings.assignment-type.title": "Assignment types",
"course-authoring.grading-settings.grading-rules-policies.description": "Deadlines, requirements, and logistics around grading student work",
"course-authoring.grading-settings.grading-rules-policies.title": "Grading rules & policies",
"course-authoring.grading-settings.credit-eligibility.description": "Settings for course credit eligibility",
"course-authoring.grading-settings.credit-eligibility.title": "Credit eligibility",
"course-authoring.grading-settings.assignment.type-name.title": "Assignment type name",
"course-authoring.grading-settings.assignment.type-name.description": "The general category for this type of assignment, for example, Homework or Midterm Exam. This name is visible to learners.",
"course-authoring.grading-settings.assignment.type-name.error.message-1": "The assignment type must have a name.",
"course-authoring.grading-settings.assignment.type-name.error.message-3": "There's already another assignment type with this name.",
"course-authoring.grading-settings.assignment.abbreviation.title": "Abbreviation",
"course-authoring.grading-settings.assignment.abbreviation.description": "This short name for the assignment type (for example, HW or Midterm) appears next to assignments on a learner's Progress page.",
"course-authoring.grading-settings.assignment.weight-of-total-grade.title": "Weight of total grade",
"course-authoring.grading-settings.assignment.weight-of-total-grade.description": "The weight of all assignments of this type as a percentage of the total grade, for example, 40. Do not include the percent symbol.",
"course-authoring.grading-settings.assignment.weight-of-total-grade.error.message": "Please enter an integer between 0 and 100.",
"course-authoring.grading-settings.assignment.total-number.title": "Total number",
"course-authoring.grading-settings.assignment.total-number.description": "The number of subsections in the course that contain problems of this assignment type.",
"course-authoring.grading-settings.assignment.total-number.error.message": "Please enter an integer greater than 0.",
"course-authoring.grading-settings.assignment.number-of-droppable.title": "Number of droppable",
"course-authoring.grading-settings.assignment.number-of-droppable.description": "The number of assignments of this type that will be dropped. The lowest scoring assignments are dropped first.",
"course-authoring.grading-settings.assignment.number-of-droppable.error.message": "Please enter non-negative integer.",
"course-authoring.grading-settings.assignment.alert.warning.description": "There are no assignments of this type in the course.",
"course-authoring.grading-settings.assignment.delete.button": "Delete",
"course-authoring.grading-settings.assignment.number-of-droppable.second.error.message": "Cannot drop more {type} assignments than are assigned.",
"course-authoring.grading-settings.assignment.alert.warning.usage.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:",
"course-authoring.grading-settings.assignment.alert.warning.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:",
"course-authoring.grading-settings.assignment.alert.success.title": "The number of {type} assignments in the course matches the number defined here.",
"course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}"
}

View File

@@ -357,7 +357,7 @@
"course-authoring.advanced-settings.heading.title": "Advanced settings",
"course-authoring.advanced-settings.heading.subtitle": "Settings",
"course-authoring.advanced-settings.policies.title": "Manual policy definition",
"course-authoring.advanced-settings.alert.warning": "You`ve made some changes",
"course-authoring.advanced-settings.alert.warning": "You've made some changes",
"course-authoring.advanced-settings.alert.warning.descriptions": "Your changes will not take effect until you save your progress. Take care with key and value formatting, as validation is not implemented.",
"course-authoring.advanced-settings.alert.success": "Your policy changes have been saved.",
"course-authoring.advanced-settings.alert.success.descriptions": "No validation is performed on policy keys or value pairs. If you are having difficulties, check your formatting.",
@@ -416,7 +416,7 @@
"course-authoring.schedule.alert.button.cancel": "Cancel",
"course-authoring.schedule.alert.warning.aria.labelledby": "notification-warning-title",
"course-authoring.schedule.alert.warning.aria.describedby": "notification-warning-description",
"course-authoring.schedule.alert.warning": "You`ve made some changes",
"course-authoring.schedule.alert.warning": "You've made some changes",
"course-authoring.schedule.alert.warning.descriptions": "Your changes will not take effect until you save your progress.",
"course-authoring.schedule.alert.success.aria.labelledby": "alert-confirmation-title",
"course-authoring.schedule.alert.success.aria.describedby": "alert-confirmation-description",
@@ -550,6 +550,26 @@
"course-authoring.generic.alert.warning.offline.description": "This may be happening because of an error with our server or your internet connection. Try refreshing the page or making sure you are online.",
"course-authoring.generic.alert.warning.offline.title.aria.labelled-by": "alert-internet-error-title",
"course-authoring.generic.alert.warning.offline.subtitle.aria.described-by": "alert-internet-error-description",
"course-authoring.grading-settings.fail-segment.text": "Fail",
"course-authoring.grading-settings.sidebar.about.title": "What can I do on this page?",
"course-authoring.grading-settings.sidebar.about.text-1": "You can use the slider under Overall Grade Range to specify whether your course is pass/fail or graded by letter, and to establish the thresholds for each grade.",
"course-authoring.grading-settings.sidebar.about.text-2": "You can specify whether your course offers students a grace period for late assignments.",
"course-authoring.grading-settings.sidebar.about.text-3": "You can also create assignment types, such as homework, labs, quizzes, and exams, and specify how much of a student's grade each assignment type is worth.",
"course-authoring.grading-settings.add-new-segment.btn.text": "Add new grading segment",
"course-authoring.grading-settings.remove-segment.btn.text": "Remove",
"course-authoring.grading-settings.heading.title": "Grading",
"course-authoring.grading-settings.heading.subtitle": "Settings",
"course-authoring.grading-settings.policies.title": "Overall grade range",
"course-authoring.grading-settings.policies.description": "Your overall grading scale for student final grades",
"course-authoring.grading-settings.alert.warning": "You've made some changes",
"course-authoring.grading-settings.alert.warning.descriptions": "Your changes will not take effect until you save your progress. Take care with key and value formatting, as validation is not implemented.",
"course-authoring.grading-settings.alert.success": "Your changes have been saved.",
"course-authoring.grading-settings.alert.button.save": "Save changes",
"course-authoring.grading-settings.alert.button.cancel": "Cancel",
"course-authoring.grading-settings.alert.warning.aria.labelledby": "notification-warning-title",
"course-authoring.grading-settings.alert.warning.aria.describedby": "notification-warning-description",
"course-authoring.grading-settings.alert.success.aria.labelledby": "alert-confirmation-title",
"course-authoring.grading-settings.alert.success.aria.describedby": "alert-confirmation-description",
"course-authoring.schedule-section.requirements.entrance.label": "Entrance exam",
"course-authoring.schedule-section.requirements.entrance.collapse.title": "Require students to pass an exam before beginning the course.",
"course-authoring.schedule-section.requirements.entrance.collapse.paragraph": "You can now view and author your course entrance exam from the {hyperlink}.",
@@ -585,5 +605,40 @@
"course-authoring.schedule-section.license.creative-commons.option.ND.description": "Allow others to copy, distribute, display and perform only verbatim copies of your work, not derivative works based upon it. This option is incompatible with 'Share Alike'.",
"course-authoring.schedule-section.license.creative-commons.option.SA.label": "Share alike",
"course-authoring.schedule-section.license.creative-commons.option.SA.description": "Allow others to distribute derivative works only under a license identical to the license that governs your work. This option is incompatible with 'No Derivatives'.",
"course-authoring.schedule.alert.button.saving": "Saving"
"course-authoring.schedule.alert.button.saving": "Saving",
"course-authoring.grading-settings.credit.eligibility.label": "Minimum credit-eligible grade:",
"course-authoring.grading-settings.credit.eligibility.description": "% Must be greater than or equal to the course passing grade",
"course-authoring.grading-settings.credit.eligibility.error.msg": "Not able to set passing grade to less than:",
"course-authoring.grading-settings.deadline.label": "Grace period on deadline:",
"course-authoring.grading-settings.deadline.description": "Leeway on due dates",
"course-authoring.grading-settings.alert.button.saving": "Saving",
"course-authoring.grading-settings.add-new-assignment-type.btn": "New assignment type",
"course-authoring.grading-settings.assignment-type.description": "Categories and labels for any exercises that are gradable",
"course-authoring.grading-settings.assignment-type.title": "Assignment types",
"course-authoring.grading-settings.grading-rules-policies.description": "Deadlines, requirements, and logistics around grading student work",
"course-authoring.grading-settings.grading-rules-policies.title": "Grading rules & policies",
"course-authoring.grading-settings.credit-eligibility.description": "Settings for course credit eligibility",
"course-authoring.grading-settings.credit-eligibility.title": "Credit eligibility",
"course-authoring.grading-settings.assignment.type-name.title": "Assignment type name",
"course-authoring.grading-settings.assignment.type-name.description": "The general category for this type of assignment, for example, Homework or Midterm Exam. This name is visible to learners.",
"course-authoring.grading-settings.assignment.type-name.error.message-1": "The assignment type must have a name.",
"course-authoring.grading-settings.assignment.type-name.error.message-3": "There's already another assignment type with this name.",
"course-authoring.grading-settings.assignment.abbreviation.title": "Abbreviation",
"course-authoring.grading-settings.assignment.abbreviation.description": "This short name for the assignment type (for example, HW or Midterm) appears next to assignments on a learner's Progress page.",
"course-authoring.grading-settings.assignment.weight-of-total-grade.title": "Weight of total grade",
"course-authoring.grading-settings.assignment.weight-of-total-grade.description": "The weight of all assignments of this type as a percentage of the total grade, for example, 40. Do not include the percent symbol.",
"course-authoring.grading-settings.assignment.weight-of-total-grade.error.message": "Please enter an integer between 0 and 100.",
"course-authoring.grading-settings.assignment.total-number.title": "Total number",
"course-authoring.grading-settings.assignment.total-number.description": "The number of subsections in the course that contain problems of this assignment type.",
"course-authoring.grading-settings.assignment.total-number.error.message": "Please enter an integer greater than 0.",
"course-authoring.grading-settings.assignment.number-of-droppable.title": "Number of droppable",
"course-authoring.grading-settings.assignment.number-of-droppable.description": "The number of assignments of this type that will be dropped. The lowest scoring assignments are dropped first.",
"course-authoring.grading-settings.assignment.number-of-droppable.error.message": "Please enter non-negative integer.",
"course-authoring.grading-settings.assignment.alert.warning.description": "There are no assignments of this type in the course.",
"course-authoring.grading-settings.assignment.delete.button": "Delete",
"course-authoring.grading-settings.assignment.number-of-droppable.second.error.message": "Cannot drop more {type} assignments than are assigned.",
"course-authoring.grading-settings.assignment.alert.warning.usage.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:",
"course-authoring.grading-settings.assignment.alert.warning.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:",
"course-authoring.grading-settings.assignment.alert.success.title": "The number of {type} assignments in the course matches the number defined here.",
"course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}"
}

View File

@@ -357,7 +357,7 @@
"course-authoring.advanced-settings.heading.title": "Advanced settings",
"course-authoring.advanced-settings.heading.subtitle": "Settings",
"course-authoring.advanced-settings.policies.title": "Manual policy definition",
"course-authoring.advanced-settings.alert.warning": "You`ve made some changes",
"course-authoring.advanced-settings.alert.warning": "You've made some changes",
"course-authoring.advanced-settings.alert.warning.descriptions": "Your changes will not take effect until you save your progress. Take care with key and value formatting, as validation is not implemented.",
"course-authoring.advanced-settings.alert.success": "Your policy changes have been saved.",
"course-authoring.advanced-settings.alert.success.descriptions": "No validation is performed on policy keys or value pairs. If you are having difficulties, check your formatting.",
@@ -416,7 +416,7 @@
"course-authoring.schedule.alert.button.cancel": "Cancel",
"course-authoring.schedule.alert.warning.aria.labelledby": "notification-warning-title",
"course-authoring.schedule.alert.warning.aria.describedby": "notification-warning-description",
"course-authoring.schedule.alert.warning": "You`ve made some changes",
"course-authoring.schedule.alert.warning": "You've made some changes",
"course-authoring.schedule.alert.warning.descriptions": "Your changes will not take effect until you save your progress.",
"course-authoring.schedule.alert.success.aria.labelledby": "alert-confirmation-title",
"course-authoring.schedule.alert.success.aria.describedby": "alert-confirmation-description",
@@ -550,6 +550,26 @@
"course-authoring.generic.alert.warning.offline.description": "This may be happening because of an error with our server or your internet connection. Try refreshing the page or making sure you are online.",
"course-authoring.generic.alert.warning.offline.title.aria.labelled-by": "alert-internet-error-title",
"course-authoring.generic.alert.warning.offline.subtitle.aria.described-by": "alert-internet-error-description",
"course-authoring.grading-settings.fail-segment.text": "Fail",
"course-authoring.grading-settings.sidebar.about.title": "What can I do on this page?",
"course-authoring.grading-settings.sidebar.about.text-1": "You can use the slider under Overall Grade Range to specify whether your course is pass/fail or graded by letter, and to establish the thresholds for each grade.",
"course-authoring.grading-settings.sidebar.about.text-2": "You can specify whether your course offers students a grace period for late assignments.",
"course-authoring.grading-settings.sidebar.about.text-3": "You can also create assignment types, such as homework, labs, quizzes, and exams, and specify how much of a student's grade each assignment type is worth.",
"course-authoring.grading-settings.add-new-segment.btn.text": "Add new grading segment",
"course-authoring.grading-settings.remove-segment.btn.text": "Remove",
"course-authoring.grading-settings.heading.title": "Grading",
"course-authoring.grading-settings.heading.subtitle": "Settings",
"course-authoring.grading-settings.policies.title": "Overall grade range",
"course-authoring.grading-settings.policies.description": "Your overall grading scale for student final grades",
"course-authoring.grading-settings.alert.warning": "You've made some changes",
"course-authoring.grading-settings.alert.warning.descriptions": "Your changes will not take effect until you save your progress. Take care with key and value formatting, as validation is not implemented.",
"course-authoring.grading-settings.alert.success": "Your changes have been saved.",
"course-authoring.grading-settings.alert.button.save": "Save changes",
"course-authoring.grading-settings.alert.button.cancel": "Cancel",
"course-authoring.grading-settings.alert.warning.aria.labelledby": "notification-warning-title",
"course-authoring.grading-settings.alert.warning.aria.describedby": "notification-warning-description",
"course-authoring.grading-settings.alert.success.aria.labelledby": "alert-confirmation-title",
"course-authoring.grading-settings.alert.success.aria.describedby": "alert-confirmation-description",
"course-authoring.schedule-section.requirements.entrance.label": "Entrance exam",
"course-authoring.schedule-section.requirements.entrance.collapse.title": "Require students to pass an exam before beginning the course.",
"course-authoring.schedule-section.requirements.entrance.collapse.paragraph": "You can now view and author your course entrance exam from the {hyperlink}.",
@@ -585,5 +605,40 @@
"course-authoring.schedule-section.license.creative-commons.option.ND.description": "Allow others to copy, distribute, display and perform only verbatim copies of your work, not derivative works based upon it. This option is incompatible with 'Share Alike'.",
"course-authoring.schedule-section.license.creative-commons.option.SA.label": "Share alike",
"course-authoring.schedule-section.license.creative-commons.option.SA.description": "Allow others to distribute derivative works only under a license identical to the license that governs your work. This option is incompatible with 'No Derivatives'.",
"course-authoring.schedule.alert.button.saving": "Saving"
"course-authoring.schedule.alert.button.saving": "Saving",
"course-authoring.grading-settings.credit.eligibility.label": "Minimum credit-eligible grade:",
"course-authoring.grading-settings.credit.eligibility.description": "% Must be greater than or equal to the course passing grade",
"course-authoring.grading-settings.credit.eligibility.error.msg": "Not able to set passing grade to less than:",
"course-authoring.grading-settings.deadline.label": "Grace period on deadline:",
"course-authoring.grading-settings.deadline.description": "Leeway on due dates",
"course-authoring.grading-settings.alert.button.saving": "Saving",
"course-authoring.grading-settings.add-new-assignment-type.btn": "New assignment type",
"course-authoring.grading-settings.assignment-type.description": "Categories and labels for any exercises that are gradable",
"course-authoring.grading-settings.assignment-type.title": "Assignment types",
"course-authoring.grading-settings.grading-rules-policies.description": "Deadlines, requirements, and logistics around grading student work",
"course-authoring.grading-settings.grading-rules-policies.title": "Grading rules & policies",
"course-authoring.grading-settings.credit-eligibility.description": "Settings for course credit eligibility",
"course-authoring.grading-settings.credit-eligibility.title": "Credit eligibility",
"course-authoring.grading-settings.assignment.type-name.title": "Assignment type name",
"course-authoring.grading-settings.assignment.type-name.description": "The general category for this type of assignment, for example, Homework or Midterm Exam. This name is visible to learners.",
"course-authoring.grading-settings.assignment.type-name.error.message-1": "The assignment type must have a name.",
"course-authoring.grading-settings.assignment.type-name.error.message-3": "There's already another assignment type with this name.",
"course-authoring.grading-settings.assignment.abbreviation.title": "Abbreviation",
"course-authoring.grading-settings.assignment.abbreviation.description": "This short name for the assignment type (for example, HW or Midterm) appears next to assignments on a learner's Progress page.",
"course-authoring.grading-settings.assignment.weight-of-total-grade.title": "Weight of total grade",
"course-authoring.grading-settings.assignment.weight-of-total-grade.description": "The weight of all assignments of this type as a percentage of the total grade, for example, 40. Do not include the percent symbol.",
"course-authoring.grading-settings.assignment.weight-of-total-grade.error.message": "Please enter an integer between 0 and 100.",
"course-authoring.grading-settings.assignment.total-number.title": "Total number",
"course-authoring.grading-settings.assignment.total-number.description": "The number of subsections in the course that contain problems of this assignment type.",
"course-authoring.grading-settings.assignment.total-number.error.message": "Please enter an integer greater than 0.",
"course-authoring.grading-settings.assignment.number-of-droppable.title": "Number of droppable",
"course-authoring.grading-settings.assignment.number-of-droppable.description": "The number of assignments of this type that will be dropped. The lowest scoring assignments are dropped first.",
"course-authoring.grading-settings.assignment.number-of-droppable.error.message": "Please enter non-negative integer.",
"course-authoring.grading-settings.assignment.alert.warning.description": "There are no assignments of this type in the course.",
"course-authoring.grading-settings.assignment.delete.button": "Delete",
"course-authoring.grading-settings.assignment.number-of-droppable.second.error.message": "Cannot drop more {type} assignments than are assigned.",
"course-authoring.grading-settings.assignment.alert.warning.usage.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:",
"course-authoring.grading-settings.assignment.alert.warning.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:",
"course-authoring.grading-settings.assignment.alert.success.title": "The number of {type} assignments in the course matches the number defined here.",
"course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}"
}

View File

@@ -10,5 +10,6 @@
@import "proctored-exam-settings/proctoredExamSettings";
@import "pages-and-resources/discussions/app-list/AppList";
@import "advanced-settings/scss/AdvancedSettings";
@import "grading-settings/scss/GradingSettings";
@import "generic/styles";
@import "schedule-and-details/ScheduleAndDetails";

View File

@@ -14,17 +14,4 @@
&:not(:last-child) {
margin-bottom: 1.75rem;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: .75rem;
border-bottom: $border-width solid $light-400;
h2 {
color: $black;
margin-bottom: .75rem;
}
}
}

View File

@@ -6,7 +6,7 @@ import {
} from '@edx/paragon';
import { Email as EmailIcon } from '@edx/paragon/icons';
import ScheduleSubHeader from '../schedule-sub-header';
import SectionSubHeader from '../../generic/section-sub-header';
import { INVITE_STUDENTS_LINK_ID } from './constants';
import messages from './messages';
@@ -126,7 +126,7 @@ const BasicSection = ({
return (
<section className="section-container basic-section">
<ScheduleSubHeader
<SectionSubHeader
title={intl.formatMessage(messages.basicTitle)}
description={intl.formatMessage(messages.basicDescription)}
/>

View File

@@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { useIntl } from '@edx/frontend-platform/i18n';
import ScheduleSubHeader from '../schedule-sub-header';
import SectionSubHeader from '../../generic/section-sub-header';
import messages from './messages';
const CreditSection = ({ creditRequirements }) => {
@@ -49,7 +49,7 @@ const CreditSection = ({ creditRequirements }) => {
return (
<section className="section-container credit-section">
<ScheduleSubHeader
<SectionSubHeader
title={intl.formatMessage(messages.creditTitle)}
description={intl.formatMessage(messages.creditDescription)}
/>

View File

@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Form, Dropdown } from '@edx/paragon';
import ScheduleSubHeader from '../schedule-sub-header';
import SectionSubHeader from '../../generic/section-sub-header';
import messages from './messages';
const DetailsSection = ({
@@ -17,7 +17,7 @@ const DetailsSection = ({
return (
<section className="section-container details-section">
<ScheduleSubHeader
<SectionSubHeader
title={intl.formatMessage(messages.detailsTitle)}
description={intl.formatMessage(messages.detailsDescription)}
/>

View File

@@ -6,7 +6,7 @@ import { Button } from '@edx/paragon';
import { Add as AddIcon } from '@edx/paragon/icons';
import InstructorContainer from './instructor-container';
import ScheduleSubHeader from '../schedule-sub-header';
import SectionSubHeader from '../../generic/section-sub-header';
import messages from './messages';
const InstructorsSection = ({ instructors, onChange }) => {
@@ -54,7 +54,7 @@ const InstructorsSection = ({ instructors, onChange }) => {
return (
<section className="section-container instructors-section">
<ScheduleSubHeader
<SectionSubHeader
title={intl.formatMessage(messages.instructorsTitle)}
description={intl.formatMessage(messages.instructorsDescription)}
/>

View File

@@ -9,7 +9,7 @@ import { Form, Hyperlink } from '@edx/paragon';
import CourseUploadImage from '../../generic/course-upload-image';
import { WysiwygEditor } from '../../generic/WysiwygEditor';
import ScheduleSubHeader from '../schedule-sub-header';
import SectionSubHeader from '../../generic/section-sub-header';
import IntroductionVideo from './introduction-video';
import ExtendedCourseDetails from './extended-course-details';
import messages from './messages';
@@ -73,7 +73,7 @@ const IntroducingSection = ({
return (
<section className="section-container introducing-section">
{aboutPageEditable && (
<ScheduleSubHeader
<SectionSubHeader
title={intl.formatMessage(messages.introducingTitle)}
description={intl.formatMessage(messages.introducingDescription)}
/>

View File

@@ -4,7 +4,7 @@ import { useIntl } from '@edx/frontend-platform/i18n';
import { Form, Button } from '@edx/paragon';
import { Add as AddIcon } from '@edx/paragon/icons';
import ScheduleSubHeader from '../schedule-sub-header';
import SectionSubHeader from '../../generic/section-sub-header';
import messages from './messages';
const LearningOutcomesSection = ({ learningInfo, onChange }) => {
@@ -51,7 +51,7 @@ const LearningOutcomesSection = ({ learningInfo, onChange }) => {
return (
<section className="section-container learning-outcomes-section">
<ScheduleSubHeader
<SectionSubHeader
title={intl.formatMessage(messages.outcomesTitle)}
description={intl.formatMessage(messages.outcomesDescription)}
/>

View File

@@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { useIntl } from '@edx/frontend-platform/i18n';
import ScheduleSubHeader from '../schedule-sub-header';
import SectionSubHeader from '../../generic/section-sub-header';
import LicenseDisplay from './license-display';
import LicenseSelector from './license-selector';
import LicenseCommonsOptions from './license-commons-options';
@@ -22,7 +22,7 @@ const LicenseSection = ({ license, onChange }) => {
return (
<section className="section-container license-section">
<ScheduleSubHeader
<SectionSubHeader
title={intl.formatMessage(messages.licenseTitle)}
description={intl.formatMessage(messages.licenseDescription)}
/>

View File

@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Form } from '@edx/paragon';
import ScheduleSubHeader from '../schedule-sub-header';
import SectionSubHeader from '../../generic/section-sub-header';
import messages from './messages';
const PacingSection = ({
@@ -14,7 +14,7 @@ const PacingSection = ({
return (
<section className="section-container pacing-section">
<ScheduleSubHeader
<SectionSubHeader
title={intl.formatMessage(messages.pacingTitle)}
description={intl.formatMessage(messages.pacingDescription)}
/>

View File

@@ -4,7 +4,7 @@ import { useIntl } from '@edx/frontend-platform/i18n';
import { Form, Dropdown } from '@edx/paragon';
import { TIME_FORMAT } from '../../constants';
import ScheduleSubHeader from '../schedule-sub-header';
import SectionSubHeader from '../../generic/section-sub-header';
import EntranceExam from './entrance-exam';
import messages from './messages';
@@ -61,7 +61,7 @@ const RequirementsSection = ({
return (
<section className="section-container requirements-section">
<ScheduleSubHeader
<SectionSubHeader
title={intl.formatMessage(messages.requirementsTitle)}
description={intl.formatMessage(messages.requirementsDescription)}
/>

View File

@@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { useIntl } from '@edx/frontend-platform/i18n';
import ScheduleSubHeader from '../schedule-sub-header';
import SectionSubHeader from '../../generic/section-sub-header';
import { ScheduleRow, SCHEDULE_ROW_TYPES } from './schedule-row';
import { CertificateDisplayRow } from './certificate-display-row';
import messages from './messages';
@@ -110,7 +110,7 @@ const ScheduleSection = ({
return (
<section className="section-container schedule-section">
<ScheduleSubHeader
<SectionSubHeader
title={intl.formatMessage(messages.scheduleTitle)}
description={intl.formatMessage(messages.scheduleDescription)}
/>

View File

@@ -6,6 +6,7 @@ import { reducer as discussionsReducer } from './pages-and-resources/discussions
import { reducer as pagesAndResourcesReducer } from './pages-and-resources/data/slice';
import { reducer as customPagesReducer } from './custom-pages/data/slice';
import { reducer as advancedSettingsReducer } from './advanced-settings/data/slice';
import { reducer as gradingSettingsReducer } from './grading-settings/data/slice';
import { reducer as scheduleAndDetailsReducer } from './schedule-and-details/data/slice';
import { reducer as liveReducer } from './pages-and-resources/live/data/slice';
import { reducer as filesReducer } from './files-and-uploads/data/slice';
@@ -20,6 +21,7 @@ export default function initializeStore(preloadedState = undefined) {
pagesAndResources: pagesAndResourcesReducer,
scheduleAndDetails: scheduleAndDetailsReducer,
advancedSettings: advancedSettingsReducer,
gradingSettings: gradingSettingsReducer,
models: modelsReducer,
live: liveReducer,
},

View File

@@ -39,6 +39,23 @@ export function convertObjectToSnakeCase(obj, unpacked = false) {
}, {});
}
export function deepConvertingKeysToSnakeCase(obj) {
if (typeof obj !== 'object' || obj === null) {
return obj;
}
if (Array.isArray(obj)) {
return obj.map((item) => deepConvertingKeysToSnakeCase(item));
}
const snakeCaseObj = {};
Object.keys(obj).forEach((key) => {
const snakeCaseKey = snakeCase(key);
snakeCaseObj[snakeCaseKey] = deepConvertingKeysToSnakeCase(obj[key]);
});
return snakeCaseObj;
}
export function transformKeysToCamelCase(obj) {
return obj.key.replace(/_([a-z])/g, (match, letter) => letter.toUpperCase());
}