Skip to content

Commit

Permalink
feat: add execEd slug format validations (#894)
Browse files Browse the repository at this point in the history
  • Loading branch information
AfaqShuaib09 authored Aug 1, 2023
1 parent b3e41f5 commit 983a602
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 30 deletions.
1 change: 1 addition & 0 deletions .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ MFE_CONFIG_API_URL=''
ADDITIONAL_METADATA_REQUIRED_FIELDS='{}'
IS_NEW_SLUG_FORMAT_ENABLED='false'
MARKETING_SITE_PREVIEW_URL_ROOT=''
COURSE_URL_SLUGS_PATTERN = '{}'
1 change: 1 addition & 0 deletions .env.test
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ SITE_NAME='edX'
ADDITIONAL_METADATA_REQUIRED_FIELDS='{}'
IS_NEW_SLUG_FORMAT_ENABLED='false'
MARKETING_SITE_PREVIEW_URL_ROOT=''
COURSE_URL_SLUGS_PATTERN = `{}`;
8 changes: 4 additions & 4 deletions src/components/EditCoursePage/EditCourseForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import Collapsible from '../Collapsible';
import PriceList from '../PriceList';

import {
PUBLISHED, REVIEWED, EXECUTIVE_EDUCATION_SLUG, COURSE_URL_SLUG_VALIDATION_MESSAGE, REVIEW_BY_INTERNAL,
PUBLISHED, REVIEWED, EXECUTIVE_EDUCATION_SLUG, REVIEW_BY_INTERNAL,
} from '../../data/constants';
import {
titleHelp, typeHelp, getUrlSlugHelp, productSourceHelp,
Expand Down Expand Up @@ -314,7 +314,7 @@ export class BaseEditCourseForm extends React.Component {

const IS_NEW_SLUG_FORMAT_ENABLED = Boolean(process.env.IS_NEW_SLUG_FORMAT_ENABLED === 'true');
// eslint-disable-next-line max-len
const COURSE_URL_SLUG_PATTERN = getCourseUrlSlugPattern(IS_NEW_SLUG_FORMAT_ENABLED, courseInfo.data.course_run_statuses, productSource?.slug);
const COURSE_URL_SLUG_PATTERN = getCourseUrlSlugPattern(IS_NEW_SLUG_FORMAT_ENABLED, productSource?.slug, courseInfo.data.course_type);
const urlSlugHelp = getUrlSlugHelp(process.env.IS_NEW_SLUG_FORMAT_ENABLED);

return (
Expand Down Expand Up @@ -361,14 +361,14 @@ export class BaseEditCourseForm extends React.Component {
onInvalid: (e) => {
this.openCollapsible();
e.target.setCustomValidity(
`Please enter a valid URL slug. ${COURSE_URL_SLUG_VALIDATION_MESSAGE[COURSE_URL_SLUG_PATTERN] || ''}`,
`Please enter a valid URL slug. ${COURSE_URL_SLUG_PATTERN.error_msg || ''}`,
);
},
onInput: (e) => {
e.target.setCustomValidity('');
},
}}
pattern={COURSE_URL_SLUG_PATTERN}
pattern={COURSE_URL_SLUG_PATTERN.slug_format}
disabled={disabled || !administrator}
optional
/>
Expand Down
2 changes: 1 addition & 1 deletion src/data/constants/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const COURSE_URL_SLUG_PATTERN = `${COURSE_URL_SLUG_PATTERN_NEW}|${COURSE_URL_SLU
const COURSE_URL_SLUG_VALIDATION_MESSAGE = {
[COURSE_URL_SLUG_PATTERN_OLD]: 'Course URL slug contains lowercase letters, numbers, underscores, and dashes only.',
[COURSE_URL_SLUG_PATTERN_NEW]: 'Course URL slug contains lowercase letters, numbers, underscores, and dashes only and must be in the format learn/<primary_subject>/<org-slug>-<course_slug>',
[COURSE_URL_SLUG_PATTERN]: 'Course URL slug contains lowercase letters, numbers, underscores, and dashes only and must in the format <custom-url-slug> or learn/<primary_subject>/<org-slug>-<course_slug>.',
[COURSE_URL_SLUG_PATTERN]: 'Course URL slug contains lowercase letters, numbers, underscores, and dashes only and must be in the format <custom-url-slug> or learn/<primary_subject>/<org-slug>-<course_slug>.',
};

export {
Expand Down
19 changes: 19 additions & 0 deletions src/setupTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,25 @@ jest.mock('@edx/frontend-platform/auth');
getAuthenticatedHttpClient.mockReturnValue(axios);
getAuthenticatedUser.mockReturnValue({ administrator: false });

process.env.COURSE_URL_SLUGS_PATTERN = `{
"edx": {
"default": {
"slug_format": "^learn/[a-z0-9_]+(?:-?[a-z0-9_]+)*/[a-z0-9_]+(?:-?[a-z0-9_]+)*$|^[a-z0-9_]+(?:-[a-z0-9_]+)*$",
"error_msg": "Course URL slug contains lowercase letters, numbers, underscores, and dashes only and must be in the format <custom-url-slug> or learn/<primary_subject>/<org-slug>-<course_slug>."
}
},
"external-source": {
"default": {
"slug_format": "^[a-z0-9_]+(?:-[a-z0-9_]+)*$",
"error_msg": "Course URL slug contains lowercase letters, numbers, underscores, and dashes only."
},
"executive-education-2u": {
"slug_format": "^executive-education/[a-z0-9_]+(?:-?[a-z0-9_]+)*$|^[a-z0-9_]+(?:-[a-z0-9_]+)*$",
"error_msg": "Course URL slug contains lowercase letters, numbers, underscores, and dashes only and must be in the format <custom-url-slug> or executive-education/<org-slug>-<course_slug>."
}
}
}`;

// We need this here because tinymce uses a method(s) which JSDOM has not
// implemented yet. To fix this, the following mocks matchMedia so that all tests
// execute properly. More info here:
Expand Down
32 changes: 20 additions & 12 deletions src/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@ import qs from 'query-string';

import history from '../data/history';
import {
COURSE_EXEMPT_FIELDS, COURSE_RUN_NON_EXEMPT_FIELDS, COURSE_URL_SLUG_PATTERN,
COURSE_URL_SLUG_PATTERN_OLD, MASTERS_TRACK, POST_REVIEW_STATUSES, IN_REVIEW_STATUS,
COURSE_EXEMPT_FIELDS, COURSE_RUN_NON_EXEMPT_FIELDS, COURSE_URL_SLUG_PATTERN_OLD,
MASTERS_TRACK, COURSE_URL_SLUG_VALIDATION_MESSAGE,
} from '../data/constants';
import DiscoveryDataApiService from '../data/services/DiscoveryDataApiService';
import { PAGE_SIZE } from '../data/constants/table';
import { DEFAULT_PRODUCT_SOURCE } from '../data/constants/productSourceOptions';

const getDateWithDashes = date => (date ? moment(date).format('YYYY-MM-DD') : '');
const getDateWithSlashes = date => (date ? moment(date).format('YYYY/MM/DD') : '');
Expand All @@ -32,18 +31,27 @@ const isValidDate = (dateStr) => {
return moment(dateStr) && date.isValid();
};

const getCourseUrlSlugPattern = (updatedSlugFlag, courseRunStatuses, productSource) => {
const getCourseUrlSlugPattern = (updatedSlugFlag, productSource, courseType) => {
/**
* This function returns the course url slug pattern based on the new slug enabled flag and courseRunStatuses
* This function returns the course url slug pattern based on the subidrectly slug
* format flag, product source and course type.
*/
if (updatedSlugFlag && productSource === DEFAULT_PRODUCT_SOURCE && courseRunStatuses.some((status) =>
// eslint-disable-next-line implicit-arrow-linebreak
(IN_REVIEW_STATUS.includes(status) || POST_REVIEW_STATUSES.includes(status)))) {
// change to COURSE_URL_SLUG_PATTERN_NEW when rollout is complete
return COURSE_URL_SLUG_PATTERN;
const COURSE_URL_SLUGS_PATTERN = JSON.parse(process.env.COURSE_URL_SLUGS_PATTERN || '{}');

let slugPattern = null;
const DEFAULT_SLUG_PATTERN = {
slug_format: COURSE_URL_SLUG_PATTERN_OLD,
error_msg: COURSE_URL_SLUG_VALIDATION_MESSAGE[COURSE_URL_SLUG_PATTERN_OLD],
};

if (updatedSlugFlag) {
const urlSlugsDictWrtProductSource = COURSE_URL_SLUGS_PATTERN[productSource] || {};
slugPattern = urlSlugsDictWrtProductSource[courseType]
|| urlSlugsDictWrtProductSource.default || DEFAULT_SLUG_PATTERN;
} else {
slugPattern = DEFAULT_SLUG_PATTERN;
}
// eslint-disable-next-line max-len
return updatedSlugFlag && productSource === DEFAULT_PRODUCT_SOURCE ? COURSE_URL_SLUG_PATTERN : COURSE_URL_SLUG_PATTERN_OLD;
return slugPattern;
};

const updateUrl = (queryOptions) => {
Expand Down
58 changes: 45 additions & 13 deletions src/utils/utils.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as utils from '.';
import { COURSE_URL_SLUG_PATTERN, COURSE_URL_SLUG_PATTERN_OLD } from '../data/constants';
import { COURSE_URL_SLUG_PATTERN_OLD, EXECUTIVE_EDUCATION_SLUG } from '../data/constants';
import { DEFAULT_PRODUCT_SOURCE } from '../data/constants/productSourceOptions';

const initialRuns = [
Expand Down Expand Up @@ -57,46 +57,78 @@ describe('getCourseNumber', () => {
});

describe('getCourseUrlSlugPattern', () => {
const { COURSE_URL_SLUGS_PATTERN } = process.env;
it(
'returns the new course url slug pattern when updatedSlugFlag is true and courseRunStatuses are in review',
() => {
const updatedSlugFlag = true;
const courseRunStatuses = ['review_by_legal', 'review_by_internal'];
const courseType = 'audit';
expect(
utils.getCourseUrlSlugPattern(updatedSlugFlag, courseRunStatuses, DEFAULT_PRODUCT_SOURCE),
).toEqual(COURSE_URL_SLUG_PATTERN);
utils.getCourseUrlSlugPattern(updatedSlugFlag, DEFAULT_PRODUCT_SOURCE, courseType),
).toEqual(
JSON.parse(COURSE_URL_SLUGS_PATTERN)[DEFAULT_PRODUCT_SOURCE].default,
);
},
);

it(
'returns the new course url slug pattern when updatedSlugFlag is true and courseRunStatuses are post review',
() => {
const updatedSlugFlag = true;
const courseRunStatuses = ['published', 'reviewed'];
const courseType = 'audit';
expect(
utils.getCourseUrlSlugPattern(updatedSlugFlag, courseRunStatuses, DEFAULT_PRODUCT_SOURCE),
).toEqual(COURSE_URL_SLUG_PATTERN);
utils.getCourseUrlSlugPattern(updatedSlugFlag, DEFAULT_PRODUCT_SOURCE, courseType),
).toEqual(JSON.parse(COURSE_URL_SLUGS_PATTERN)[DEFAULT_PRODUCT_SOURCE].default);
},
);

it(
'returns the both old & new pattern when updatedSlugFlag is true and courseRunStatuses are not in review or post review',
() => {
const updatedSlugFlag = true;
const courseRunStatuses = ['archived'];
const courseType = 'audit';
expect(
utils.getCourseUrlSlugPattern(updatedSlugFlag, courseRunStatuses, DEFAULT_PRODUCT_SOURCE),
).toEqual(COURSE_URL_SLUG_PATTERN);
utils.getCourseUrlSlugPattern(updatedSlugFlag, DEFAULT_PRODUCT_SOURCE, courseType),
).toEqual(JSON.parse(COURSE_URL_SLUGS_PATTERN)[DEFAULT_PRODUCT_SOURCE].default);
},
);

it('returns the old course url slug pattern when updatedSlugFlag is false', () => {
const updatedSlugFlag = false;
const courseRunStatuses = ['archived'];
const courseType = 'audit';
expect(
utils.getCourseUrlSlugPattern(updatedSlugFlag, courseRunStatuses, DEFAULT_PRODUCT_SOURCE),
).toEqual(COURSE_URL_SLUG_PATTERN_OLD);
utils.getCourseUrlSlugPattern(updatedSlugFlag, DEFAULT_PRODUCT_SOURCE, courseType),
).toEqual({
slug_format: COURSE_URL_SLUG_PATTERN_OLD,
error_msg: 'Course URL slug contains lowercase letters, numbers, underscores, and dashes only.',
});
});

it(
'returns the exec_ed subdirectory slug pattern when courseType is executive education and updatedSlugFlag is true',
() => {
const updatedSlugFlag = true;
const courseType = EXECUTIVE_EDUCATION_SLUG;
expect(
utils.getCourseUrlSlugPattern(updatedSlugFlag, 'external-source', courseType),
).toEqual(JSON.parse(COURSE_URL_SLUGS_PATTERN)['external-source'][EXECUTIVE_EDUCATION_SLUG]);
},
);

it(
'returns the old course url slug pattern when courseType is executive education and updatedSlugFlag is false',
() => {
const updatedSlugFlag = false;
const courseType = EXECUTIVE_EDUCATION_SLUG;

expect(
utils.getCourseUrlSlugPattern(updatedSlugFlag, 'external-source', courseType),
).toEqual({
slug_format: COURSE_URL_SLUG_PATTERN_OLD,
error_msg: 'Course URL slug contains lowercase letters, numbers, underscores, and dashes only.',
});
},
);
});

describe('getCourseError', () => {
Expand Down

0 comments on commit 983a602

Please sign in to comment.