Compare commits
9 Commits
master
...
open-relea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4592c0d06a | ||
|
|
91922874fc | ||
|
|
0020597d97 | ||
|
|
74ce163bec | ||
|
|
2b8edfd761 | ||
|
|
d0c4765698 | ||
|
|
d48d6ffa5d | ||
|
|
14f035435c | ||
|
|
fa25150e3d |
@@ -4,7 +4,7 @@
|
||||
<title>Course Authoring | <%= process.env.SITE_NAME %></title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="shortcut icon" href="<%= process.env.FAVICON_URL %>" type="image/x-icon" />
|
||||
<link rel="shortcut icon" href="<%=htmlWebpackPlugin.options.FAVICON_URL%>" type="image/x-icon" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -41,6 +41,7 @@ import messages from './messages';
|
||||
import CustomPagesProvider from './CustomPagesProvider';
|
||||
import EditModal from './EditModal';
|
||||
import getPageHeadTitle from '../generic/utils';
|
||||
import { getPagePath } from '../utils';
|
||||
|
||||
const CustomPages = ({
|
||||
courseId,
|
||||
@@ -121,7 +122,7 @@ const CustomPages = ({
|
||||
ariaLabel="Custom Page breadcrumbs"
|
||||
links={[
|
||||
{ label: 'Content', href: `${config.STUDIO_BASE_URL}/course/${courseId}` },
|
||||
{ label: 'Pages and Resources', href: `/course/${courseId}/pages-and-resources` },
|
||||
{ label: 'Pages and Resources', href: getPagePath(courseId, 'true', 'tabs') },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@import "./export-stepper/ExportStepper";
|
||||
@import "./export-footer/ExportFooter";
|
||||
|
||||
.export {
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
.pgn__stepper-header-step-list {
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -153,7 +153,7 @@ describe('<CreateOrRerunCourseForm />', () => {
|
||||
userEvent.type(runInput, '1');
|
||||
userEvent.click(createBtn);
|
||||
});
|
||||
await axiosMock.onPost(getCreateOrRerunCourseUrl).reply(200, { url });
|
||||
await axiosMock.onPost(getCreateOrRerunCourseUrl()).reply(200, { url });
|
||||
await executeThunk(updateCreateOrRerunCourseQuery({ org: 'testX', run: 'some' }), store.dispatch);
|
||||
|
||||
expect(window.location.assign).toHaveBeenCalledWith(`${process.env.STUDIO_BASE_URL}${url}`);
|
||||
@@ -168,7 +168,7 @@ describe('<CreateOrRerunCourseForm />', () => {
|
||||
const numberInput = screen.getByPlaceholderText(messages.courseNumberPlaceholder.defaultMessage);
|
||||
const runInput = screen.getByPlaceholderText(messages.courseRunPlaceholder.defaultMessage);
|
||||
const createBtn = screen.getByRole('button', { name: messages.createButton.defaultMessage });
|
||||
await axiosMock.onPost(getCreateOrRerunCourseUrl).reply(200, { url, destinationCourseKey });
|
||||
await axiosMock.onPost(getCreateOrRerunCourseUrl()).reply(200, { url, destinationCourseKey });
|
||||
|
||||
await act(async () => {
|
||||
userEvent.type(displayNameInput, 'foo course name');
|
||||
@@ -250,7 +250,7 @@ describe('<CreateOrRerunCourseForm />', () => {
|
||||
it('shows alert error if postErrors presents', async () => {
|
||||
render(<RootWrapper {...props} />);
|
||||
await mockStore();
|
||||
await axiosMock.onPost(getCreateOrRerunCourseUrl).reply(200, { errMsg: 'aaa' });
|
||||
await axiosMock.onPost(getCreateOrRerunCourseUrl()).reply(200, { errMsg: 'aaa' });
|
||||
await executeThunk(updateCreateOrRerunCourseQuery({ org: 'testX', run: 'some' }), store.dispatch);
|
||||
|
||||
expect(screen.getByText('aaa')).toBeInTheDocument();
|
||||
|
||||
@@ -4,9 +4,9 @@ import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
import { convertObjectToSnakeCase } from '../../utils';
|
||||
|
||||
export const getApiBaseUrl = () => getConfig().STUDIO_BASE_URL;
|
||||
export const getCreateOrRerunCourseUrl = new URL('course/', getApiBaseUrl()).href;
|
||||
export const getCreateOrRerunCourseUrl = () => new URL('course/', getApiBaseUrl()).href;
|
||||
export const getCourseRerunUrl = (courseId) => new URL(`/api/contentstore/v1/course_rerun/${courseId}`, getApiBaseUrl()).href;
|
||||
export const getOrganizationsUrl = new URL('organizations', getApiBaseUrl()).href;
|
||||
export const getOrganizationsUrl = () => new URL('organizations', getApiBaseUrl()).href;
|
||||
|
||||
/**
|
||||
* Get's organizations data.
|
||||
@@ -14,7 +14,7 @@ export const getOrganizationsUrl = new URL('organizations', getApiBaseUrl()).hre
|
||||
*/
|
||||
export async function getOrganizations() {
|
||||
const { data } = await getAuthenticatedHttpClient().get(
|
||||
getOrganizationsUrl,
|
||||
getOrganizationsUrl(),
|
||||
);
|
||||
return camelCaseObject(data);
|
||||
}
|
||||
@@ -37,7 +37,7 @@ export async function getCourseRerun(courseId) {
|
||||
*/
|
||||
export async function createOrRerunCourse(courseData) {
|
||||
const { data } = await getAuthenticatedHttpClient().post(
|
||||
getCreateOrRerunCourseUrl,
|
||||
getCreateOrRerunCourseUrl(),
|
||||
convertObjectToSnakeCase(courseData, true),
|
||||
);
|
||||
return camelCaseObject(data);
|
||||
|
||||
@@ -66,10 +66,10 @@ describe('generic api calls', () => {
|
||||
org: 'edX',
|
||||
run: 'Demo_Course',
|
||||
};
|
||||
axiosMock.onPost(getCreateOrRerunCourseUrl).reply(200, courseRerunData);
|
||||
axiosMock.onPost(getCreateOrRerunCourseUrl()).reply(200, courseRerunData);
|
||||
const result = await createOrRerunCourse(courseRerunData);
|
||||
|
||||
expect(axiosMock.history.post[0].url).toEqual(getCreateOrRerunCourseUrl);
|
||||
expect(axiosMock.history.post[0].url).toEqual(getCreateOrRerunCourseUrl());
|
||||
expect(result).toEqual(courseRerunData);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -62,10 +62,10 @@ describe('<AssignmentSection />', () => {
|
||||
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');
|
||||
const assignmentWeightInput = getByTestId('assignment-weight-input');
|
||||
expect(assignmentWeightInput.value).toBe('100');
|
||||
fireEvent.change(assignmentWeightInput, { target: { value: '123' } });
|
||||
expect(testObj.graders[0].weight).toBe(123);
|
||||
});
|
||||
});
|
||||
it('checking correct assignment total number value', async () => {
|
||||
@@ -74,7 +74,7 @@ describe('<AssignmentSection />', () => {
|
||||
const assignmentTotalNumberInput = getByTestId('assignment-minCount-input');
|
||||
expect(assignmentTotalNumberInput.value).toBe('1');
|
||||
fireEvent.change(assignmentTotalNumberInput, { target: { value: '123' } });
|
||||
expect(testObj.graders[0].minCount).toBe('123');
|
||||
expect(testObj.graders[0].minCount).toBe(123);
|
||||
});
|
||||
});
|
||||
it('checking correct assignment number of droppable value', async () => {
|
||||
@@ -83,7 +83,7 @@ describe('<AssignmentSection />', () => {
|
||||
const assignmentNumberOfDroppableInput = getByTestId('assignment-dropCount-input');
|
||||
expect(assignmentNumberOfDroppableInput.value).toBe('1');
|
||||
fireEvent.change(assignmentNumberOfDroppableInput, { target: { value: '2' } });
|
||||
expect(testObj.graders[0].dropCount).toBe('2');
|
||||
expect(testObj.graders[0].dropCount).toBe(2);
|
||||
});
|
||||
});
|
||||
it('checking correct error msg if dropCount have negative number or empty string', async () => {
|
||||
@@ -100,20 +100,20 @@ describe('<AssignmentSection />', () => {
|
||||
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' } });
|
||||
const assignmentMinCountInput = getByTestId('assignment-minCount-input');
|
||||
expect(assignmentMinCountInput.value).toBe('1');
|
||||
fireEvent.change(assignmentMinCountInput, { target: { value: '-2' } });
|
||||
expect(getByText(messages.totalNumberErrorMessage.defaultMessage)).toBeInTheDocument();
|
||||
fireEvent.change(assignmentNumberOfDroppableInput, { target: { value: '' } });
|
||||
fireEvent.change(assignmentMinCountInput, { 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' } });
|
||||
const assignmentWeightInput = getByTestId('assignment-weight-input');
|
||||
expect(assignmentWeightInput.value).toBe('100');
|
||||
fireEvent.change(assignmentWeightInput, { target: { value: '-100' } });
|
||||
expect(getByText(messages.weightOfTotalGradeErrorMessage.defaultMessage)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -34,7 +34,12 @@ const AssignmentSection = ({
|
||||
}
|
||||
|
||||
const handleAssignmentChange = (e, assignmentId) => {
|
||||
const { name, value } = e.target;
|
||||
const { name, value, type: inputType } = e.target;
|
||||
|
||||
let inputValue = value;
|
||||
if (inputType === 'number') {
|
||||
inputValue = parseInt(value, 10);
|
||||
}
|
||||
|
||||
setShowSavePrompt(true);
|
||||
|
||||
@@ -42,7 +47,7 @@ const AssignmentSection = ({
|
||||
...prevState,
|
||||
graders: graders.map(grader => {
|
||||
if (grader.id === assignmentId) {
|
||||
return { ...grader, [name]: value };
|
||||
return { ...grader, [name]: inputValue };
|
||||
}
|
||||
return grader;
|
||||
}),
|
||||
@@ -66,7 +71,7 @@ const AssignmentSection = ({
|
||||
return (
|
||||
<div className="assignment-items">
|
||||
{graders?.map((gradeField) => {
|
||||
const courseAssignmentUsage = courseAssignmentLists[gradeField.type.toLowerCase()];
|
||||
const courseAssignmentUsage = courseAssignmentLists[gradeField.type];
|
||||
const showDefinedCaseAlert = gradeField.minCount !== courseAssignmentUsage?.length
|
||||
&& Boolean(courseAssignmentUsage?.length);
|
||||
const showNotDefinedCaseAlert = !courseAssignmentUsage?.length && Boolean(gradeField.type);
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
// This SCSS was partly copied from edx/frontend-app-support-tools/src/support-header/index.scss.
|
||||
$spacer: 1rem;
|
||||
$white: #FFFFFF;
|
||||
|
||||
.btn-tertiary:hover {
|
||||
color: white;
|
||||
background-color: #00262B;
|
||||
}
|
||||
|
||||
.course-title-lockup {
|
||||
@media only screen and (max-width: 768px) {
|
||||
padding-left: .5rem;
|
||||
max-width: 70%;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 769px) {
|
||||
padding: .5rem;
|
||||
padding-right: $spacer;
|
||||
border-right: 1px solid #E5E5E5;
|
||||
min-width: 70%;
|
||||
}
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
span {
|
||||
color: #333333;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 1.375rem;
|
||||
}
|
||||
}
|
||||
|
||||
.site-header-mobile,
|
||||
.site-header-desktop {
|
||||
position: relative;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.site-header-mobile {
|
||||
img {
|
||||
height: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.site-header-desktop {
|
||||
height: 3.75rem;
|
||||
box-shadow: 0 1px 0 0 rgb(0 0 0 / .1);
|
||||
background: $white;
|
||||
|
||||
.logo {
|
||||
display: block;
|
||||
box-sizing: content-box;
|
||||
position: relative;
|
||||
top: -.05em;
|
||||
height: 1.75rem;
|
||||
padding: $spacer 0;
|
||||
margin-right: $spacer;
|
||||
|
||||
img {
|
||||
display: block;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@ export const getSettingMenuItems = ({ studioBaseUrl, courseId, intl }) => ([
|
||||
title: intl.formatMessage(messages['header.links.courseTeam']),
|
||||
},
|
||||
{
|
||||
href: `${studioBaseUrl}/group_configurations/course-v1:${courseId}`,
|
||||
href: `${studioBaseUrl}/group_configurations/${courseId}`,
|
||||
title: intl.formatMessage(messages['header.links.groupConfigurations']),
|
||||
},
|
||||
{
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
@import "~@edx/brand/paragon/variables";
|
||||
@import "~@edx/paragon/scss/core/core";
|
||||
@import "~@edx/brand/paragon/overrides";
|
||||
@import "header/header";
|
||||
@import "~@edx/frontend-component-header/dist/index";
|
||||
@import "assets/scss/variables";
|
||||
@import "assets/scss/form";
|
||||
@import "assets/scss/utilities";
|
||||
|
||||
@@ -42,7 +42,6 @@ const StudioHome = ({ intl }) => {
|
||||
studioRequestEmail,
|
||||
libraryAuthoringMfeUrl,
|
||||
redirectToLibraryAuthoringMfe,
|
||||
splitStudioHome,
|
||||
} = studioHomeData;
|
||||
|
||||
function getHeaderButtons() {
|
||||
@@ -68,26 +67,24 @@ const StudioHome = ({ intl }) => {
|
||||
);
|
||||
}
|
||||
|
||||
let libraryHref = `${getConfig().STUDIO_BASE_URL}/home#libraries-tab`;
|
||||
if (splitStudioHome) {
|
||||
libraryHref = `${getConfig().STUDIO_BASE_URL}/home_library`;
|
||||
}
|
||||
let libraryHref = `${getConfig().STUDIO_BASE_URL}/home_library`;
|
||||
if (redirectToLibraryAuthoringMfe) {
|
||||
libraryHref = `${libraryAuthoringMfeUrl}/create`;
|
||||
headerButtons.push(
|
||||
<Button
|
||||
variant="outline-primary"
|
||||
iconBefore={AddIcon}
|
||||
size="sm"
|
||||
disabled={showNewCourseContainer}
|
||||
href={libraryHref}
|
||||
data-testid="new-library-button"
|
||||
>
|
||||
{intl.formatMessage(messages.addNewLibraryBtnText)}
|
||||
</Button>,
|
||||
);
|
||||
}
|
||||
|
||||
headerButtons.push(
|
||||
<Button
|
||||
variant="outline-primary"
|
||||
iconBefore={AddIcon}
|
||||
size="sm"
|
||||
disabled={showNewCourseContainer}
|
||||
href={libraryHref}
|
||||
data-testid="new-library-button"
|
||||
>
|
||||
{intl.formatMessage(messages.addNewLibraryBtnText)}
|
||||
</Button>,
|
||||
);
|
||||
|
||||
return headerButtons;
|
||||
}
|
||||
|
||||
|
||||
@@ -116,15 +116,16 @@ describe('<StudioHome />', async () => {
|
||||
});
|
||||
|
||||
describe('render new library button', () => {
|
||||
it('should not show button', async () => {
|
||||
it('href should include home_library', async () => {
|
||||
useSelector.mockReturnValue({
|
||||
...studioHomeMock,
|
||||
courseCreatorStatus: COURSE_CREATOR_STATES.granted,
|
||||
});
|
||||
const studioBaseUrl = 'http://localhost:18010';
|
||||
|
||||
const { queryByTestId } = render(<RootWrapper />);
|
||||
const createNewLibraryButton = queryByTestId('new-library-button');
|
||||
expect(createNewLibraryButton).toBeNull();
|
||||
const { getByTestId } = render(<RootWrapper />);
|
||||
const createNewLibraryButton = getByTestId('new-library-button');
|
||||
expect(createNewLibraryButton.getAttribute('href')).toBe(`${studioBaseUrl}/home_library`);
|
||||
});
|
||||
it('href should include create', async () => {
|
||||
useSelector.mockReturnValue({
|
||||
|
||||
@@ -42,7 +42,7 @@ describe('<OrganizationSection />', async () => {
|
||||
});
|
||||
store = initializeStore();
|
||||
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
||||
axiosMock.onDelete(getOrganizationsUrl).reply(200);
|
||||
axiosMock.onDelete(getOrganizationsUrl()).reply(200);
|
||||
await executeThunk(fetchOrganizationsQuery(), store.dispatch);
|
||||
useSelector.mockReturnValue(['edX', 'org']);
|
||||
});
|
||||
|
||||
23
src/utils.js
23
src/utils.js
@@ -95,12 +95,31 @@ export function parseArrayOrObjectValues(obj) {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a correct inner path depend on config PUBLIC_PATH.
|
||||
* @param {string} checkPath - the internal route path that is validated
|
||||
* @returns {string} - the correct internal route path
|
||||
*/
|
||||
export const createCorrectInternalRoute = (checkPath) => {
|
||||
let basePath = getConfig().PUBLIC_PATH;
|
||||
|
||||
if (basePath.endsWith('/')) {
|
||||
basePath = basePath.slice(0, -1);
|
||||
}
|
||||
|
||||
if (!checkPath.startsWith(basePath)) {
|
||||
return `${basePath}${checkPath}`;
|
||||
}
|
||||
|
||||
return checkPath;
|
||||
};
|
||||
|
||||
export function getPagePath(courseId, isMfePageEnabled, urlParameter) {
|
||||
if (isMfePageEnabled === 'true') {
|
||||
if (urlParameter === 'tabs') {
|
||||
return `${getConfig().BASE_URL}/course/${courseId}/pages-and-resources`;
|
||||
return createCorrectInternalRoute(`/course/${courseId}/pages-and-resources`);
|
||||
}
|
||||
return `${getConfig().BASE_URL}/course/${courseId}/${urlParameter}`;
|
||||
return createCorrectInternalRoute(`/course/${courseId}/${urlParameter}`);
|
||||
}
|
||||
return `${getConfig().STUDIO_BASE_URL}/${urlParameter}/${courseId}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user