Skip to content

Commit

Permalink
chore: add feature flag for subs catalog consolidation (#356)
Browse files Browse the repository at this point in the history
* chore: add feature flag for subs catalog consolidation
  • Loading branch information
bcitro committed Sep 26, 2023
1 parent 1c23eeb commit 6519c3d
Show file tree
Hide file tree
Showing 23 changed files with 213 additions and 10 deletions.
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ SEGMENT_KEY=''
SITE_NAME=null
USER_INFO_COOKIE_NAME=null
EDX_FOR_BUSINESS_TITLE=''
EDX_FOR_ONLINE_EDU_TITLE=''
EDX_ENTERPRISE_ALACARTE_TITLE=''
1 change: 1 addition & 0 deletions .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ HOTJAR_VERSION=6
HOTJAR_DEBUG=true
HUBSPOT_MARKETING_URL='http://example.com'
EXEC_ED_INCLUSION=true
CONSOLIDATE_SUBS_CATALOG='true'
1 change: 1 addition & 0 deletions .env.test
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ SEGMENT_KEY=''
SITE_NAME='edX'
USER_INFO_COOKIE_NAME='edx-user-info'
CATALOG_SERVICE_BASE_URL='foobar.com'
CONSOLIDATE_SUBS_CATALOG='true'
24 changes: 24 additions & 0 deletions src/components/catalogInfoModal/CatalogInfoModal.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import '@testing-library/jest-dom/extend-expect';

import { IntlProvider } from '@edx/frontend-platform/i18n';
import CatalogInfoModal from './CatalogInfoModal';
import features from '../../config';

const descriptionText = 'this is a description';
// course descriptions are injected into the DOM with dangerouslySetInnerHTML
Expand Down Expand Up @@ -38,6 +39,9 @@ const courseTypeModalProps = {
};

describe('Course info modal works as expected', () => {
afterEach(() => {
features.CONSOLIDATE_SUBS_CATALOG = true;
});
const OLD_ENV = process.env;
const { selectedCourse } = courseTypeModalProps;
beforeEach(() => {
Expand Down Expand Up @@ -100,6 +104,26 @@ describe('Course info modal works as expected', () => {
businessQueryTitle,
];

render(
<IntlProvider locale="en">
<CatalogInfoModal {...defaultPropsCopy} />
</IntlProvider>,
);
expect(
screen.queryByText('Included with subscription'),
).toBeInTheDocument();
});
test('Renders Course info modal with correct catalogs including business subs', () => {
features.CONSOLIDATE_SUBS_CATALOG = false;
const defaultPropsCopy = {};
Object.assign(defaultPropsCopy, courseTypeModalProps);

const businessQueryTitle = 'test-business-query-title';
process.env.EDX_FOR_BUSINESS_TITLE = businessQueryTitle;
defaultPropsCopy.selectedCourse.courseAssociatedCatalogs = [
businessQueryTitle,
];

render(
<IntlProvider locale="en">
<CatalogInfoModal {...defaultPropsCopy} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
checkAvailability,
checkSubscriptions,
} from '../../utils/catalogUtils';
import features from '../../config';

const nowDate = new Date(Date.now());

Expand Down Expand Up @@ -67,9 +68,11 @@ const CatalogCourseModalBanner = ({
messages['CatalogCourseModalBanner.bannerCatalogText'],
)}
</div>
{!features.CONSOLIDATE_SUBS_CATALOG && (
<div className="banner-subtitle small">
{checkSubscriptions(courseAssociatedCatalogs)}
</div>
)}
</div>
<div className="banner-section slash">/</div>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Assignment, BookOpen, MoneyOutline } from '@edx/paragon/icons';
import messages from './CatalogCourseModalBanner.messages';
import { checkSubscriptions } from '../../utils/catalogUtils';
import features from '../../config';

const CatalogProgramModalBanner = ({
intl,
Expand Down Expand Up @@ -54,9 +55,11 @@ const CatalogProgramModalBanner = ({
messages['CatalogCourseModalBanner.bannerCatalogText'],
)}
</div>
{!features.CONSOLIDATE_SUBS_CATALOG && (
<div className="banner-subtitle small">
{checkSubscriptions(courseAssociatedCatalogs)}
</div>
)}
</div>
)}
</div>
Expand Down
12 changes: 12 additions & 0 deletions src/components/catalogPage/CatalogPage.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { mockWindowLocations, renderWithRouter } from '../tests/testUtils';
import CatalogPage from './CatalogPage';
import selectionCardMessage from '../catalogSelectionDeck/CatalogSelectionDeck.messages';
import { LEARNING_TYPE_REFINEMENT } from '../../constants';
import features from '../../config';

// all we are testing is routes, we don't need InstantSearch to work here
jest.mock('react-instantsearch-dom', () => ({
Expand Down Expand Up @@ -36,6 +37,7 @@ describe('CatalogPage', () => {
});
afterAll(() => {
process.env = OLD_ENV; // Restore old environment
features.CONSOLIDATE_SUBS_CATALOG = true;
});
it('renders a catalog page component', () => {
const { container } = renderWithRouter(<CatalogPage />);
Expand All @@ -46,6 +48,16 @@ describe('CatalogPage', () => {
expect(screen.getByText('SEARCH')).toBeInTheDocument();
});
it('renders with catalog selection cards', () => {
renderWithRouter(<CatalogPage />);
expect(
screen.getByText(
selectionCardMessage['catalogSelectionDeck.edxSubscription.label']
.defaultMessage,
),
).toBeInTheDocument();
});
it('renders with catalog selection cards including business catalog', () => {
features.CONSOLIDATE_SUBS_CATALOG = false;
renderWithRouter(<CatalogPage />);
expect(
screen.getByText(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ const messages = defineMessages({
defaultMessage: 'Business',
description: 'Badge text for the `Business` catalog badge.',
},
'catalogSearchResults.educationBadge': {
id: 'catalogSearchResults.educationBadge',
defaultMessage: 'Education',
description: 'Badge text for the `Education` catalog badge.',
},
'catalogSearchResults.popularCourses': {
id: 'catalogSearchResults.popularCourses',
defaultMessage: 'Popular Courses',
Expand Down
22 changes: 22 additions & 0 deletions src/components/catalogSearchResults/CatalogSearchResults.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
EXEC_ED_TITLE,
HIDE_PRICE_REFINEMENT,
} from '../../constants';
import features from '../../config';

// Mocking this connected component so as not to have to mock the algolia Api
const PAGINATE_ME = 'PAGINATE ME :)';
Expand Down Expand Up @@ -232,6 +233,7 @@ describe('Main Catalogs view works as expected', () => {
});
afterEach(() => {
process.env = OLD_ENV; // Restore old environment
features.CONSOLIDATE_SUBS_CATALOG = true;
});

test('all courses rendered when search results available', async () => {
Expand Down Expand Up @@ -259,6 +261,26 @@ describe('Main Catalogs view works as expected', () => {
expect(screen.queryByText(TEST_PARTNER_2)).toBeInTheDocument();

expect(screen.queryAllByText('A la carte').length === 2);
await act(() => screen.findByText('Subscription'));
expect(screen.queryByText('Subscription')).toBeInTheDocument();
});
test('all courses rendered when search results available', async () => {
process.env.EDX_FOR_BUSINESS_TITLE = 'ayylmao';
process.env.EDX_ENTERPRISE_ALACARTE_TITLE = 'baz';
features.CONSOLIDATE_SUBS_CATALOG = false;
render(
<SearchDataWrapper>
<IntlProvider locale="en">
<BaseCatalogSearchResults {...defaultProps} />
</IntlProvider>
,
</SearchDataWrapper>,
);

// Card view should be default
const listViewToggleButton = screen.getByLabelText('Card');
userEvent.click(listViewToggleButton);

await act(() => screen.findByText('Business'));
expect(screen.queryByText('Business')).toBeInTheDocument();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import PropTypes from 'prop-types';

import React from 'react';
import messages from '../../CatalogSearchResults.messages';
import features from '../../../../config';

const CatalogBadges = ({ row }) => {
const intl = useIntl();
Expand All @@ -23,6 +24,14 @@ const CatalogBadges = ({ row }) => {
{intl.formatMessage(messages['catalogSearchResults.businessBadge'])}
</Badge>
)}
{!features.CONSOLIDATE_SUBS_CATALOG
&& row.original.enterprise_catalog_query_titles.includes(
process.env.EDX_FOR_ONLINE_EDU_TITLE,
) && (
<Badge variant="light" className="padded-catalog">
{intl.formatMessage(messages['catalogSearchResults.educationBadge'])}
</Badge>
)}
</div>
);
};
Expand Down
29 changes: 26 additions & 3 deletions src/components/catalogSelectionDeck/CatalogSelectionDeck.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';

import messages from './CatalogSelectionDeck.messages';
import { QUERY_TITLE_REFINEMENT } from '../../constants';
import features from '../../config';

const CatalogSelectionDeck = ({ intl, title, hide }) => {
const { refinements, dispatch } = useContext(SearchContext);
Expand All @@ -24,6 +25,14 @@ const CatalogSelectionDeck = ({ intl, title, hide }) => {
setValue(e.target.value);
};
const isExtraSmall = useMediaQuery({ maxWidth: breakpoints.extraSmall.maxWidth });
const columnCount = features.CONSOLIDATE_SUBS_CATALOG ? 2 : 3;

let businessBadgeMessageKey = 'catalogSelectionDeck.edxForBusiness.badge';
let businessLabelMessageKey = 'catalogSelectionDeck.edxForBusiness.label';
if (features.CONSOLIDATE_SUBS_CATALOG) {
businessBadgeMessageKey = 'catalogSelectionDeck.edxSubscription.badge';
businessLabelMessageKey = 'catalogSelectionDeck.edxSubscription.label';
}

useEffect(() => {
if (refinements[QUERY_TITLE_REFINEMENT]) {
Expand All @@ -39,7 +48,7 @@ const CatalogSelectionDeck = ({ intl, title, hide }) => {
type="radio"
value={value}
onChange={handleChange}
columns={isExtraSmall ? 1 : 3}
columns={isExtraSmall ? 1 : columnCount}
className="py-4"
>
<SelectableBox value={config.EDX_ENTERPRISE_ALACARTE_TITLE} inputHidden={false} type="radio" aria-label="a la carte select">
Expand All @@ -57,16 +66,30 @@ const CatalogSelectionDeck = ({ intl, title, hide }) => {
</SelectableBox>
<SelectableBox value={config.EDX_FOR_BUSINESS_TITLE} inputHidden={false} type="radio" aria-label="business select">
<Badge variant="secondary">
{intl.formatMessage(messages['catalogSelectionDeck.edxForBusiness.badge'])}
{intl.formatMessage(messages[businessBadgeMessageKey])}
</Badge>
<h3>{intl.formatMessage(messages[businessLabelMessageKey])}</h3>
<p>{intl.formatMessage(messages['catalogSelectionDeck.labelDetail'])}</p>
<ul className="catalog-list">
<li>{intl.formatMessage(messages['catalogSelectionDeck.bullet1'])}</li>
<li>{intl.formatMessage(messages['catalogSelectionDeck.bullet2'])}</li>
<li>{intl.formatMessage(messages['catalogSelectionDeck.bullet3'])}</li>
</ul>
</SelectableBox>
{!features.CONSOLIDATE_SUBS_CATALOG && (
<SelectableBox value={config.EDX_FOR_ONLINE_EDU_TITLE} inputHidden={false} type="radio" aria-label="education select">
<Badge variant="light">
{intl.formatMessage(messages['catalogSelectionDeck.edxForOnlineEdu.badge'])}
</Badge>
<h3>{intl.formatMessage(messages['catalogSelectionDeck.edxForBusiness.label'])}</h3>
<h3>{intl.formatMessage(messages['catalogSelectionDeck.edxForOnlineEdu.label'])}</h3>
<p>{intl.formatMessage(messages['catalogSelectionDeck.labelDetail'])}</p>
<ul className="catalog-list">
<li>{intl.formatMessage(messages['catalogSelectionDeck.bullet1'])}</li>
<li>{intl.formatMessage(messages['catalogSelectionDeck.bullet2'])}</li>
<li>{intl.formatMessage(messages['catalogSelectionDeck.bullet3'])}</li>
</ul>
</SelectableBox>
)}
</SelectableBox.Set>
</Container>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,26 @@ const messages = defineMessages({
defaultMessage: 'Business subscription',
description: 'label for checkbox for filtering results',
},
'catalogSelectionDeck.edxSubscription.badge': {
id: 'catalogSelectionDeck.edxSubscription.badge',
defaultMessage: 'For all organizations',
description: 'badge for catalog',
},
'catalogSelectionDeck.edxSubscription.label': {
id: 'catalogSelectionDeck.edxSubscription.label',
defaultMessage: 'Subscription',
description: 'label for checkbox for filtering results',
},
'catalogSelectionDeck.edxForOnlineEdu.badge': {
id: 'catalogSelectionDeck.edxForOnlineEdu.badge',
defaultMessage: 'For educational institutions',
description: 'badge for catalog',
},
'catalogSelectionDeck.edxForOnlineEdu.label': {
id: 'catalogSelectionDeck.edxForOnlineEdu.label',
defaultMessage: 'Education subscription',
description: 'label for checkbox for filtering results',
},
'catalogSelectionDeck.labelDetail': {
id: 'catalogSelectionDeck.edxForOnlineEdu.labelDetail',
defaultMessage: 'Single, per learner price',
Expand All @@ -19,7 +39,7 @@ const messages = defineMessages({
'catalogSelectionDeck.bullet1': {
id: 'catalogSelectionDeck.edxForBusiness.bullet1',
defaultMessage:
'Unlimited access to 1,000+ courses',
'Unlimited access to 2,000+ courses',
description: 'description of filter',
},
'catalogSelectionDeck.bullet2': {
Expand Down
10 changes: 9 additions & 1 deletion src/components/catalogSelectionDeck/CatalogSelectionDeck.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
.catalog-list {
li {
list-style-type: '';
list-style-type: '';
}
}
.pgn__selectable_box {
.pgn__form-radio-input {
inset-inline-end: 1rem;
position: absolute;
top: 1rem;
margin-inline-end: 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { IntlProvider } from '@edx/frontend-platform/i18n';
import CatalogSelectionDeck from './CatalogSelectionDeck';
import { renderWithRouter } from '../tests/testUtils';
import QUERY_TITLE_REFINEMENT from '../../constants';
import messages from './CatalogSelectionDeck.messages';

const BUSINESS_SEARCH_CONTEXT_VALUE = {
refinements: { enterprise_catalog_query_titles: ['ayylmao'] },
Expand Down Expand Up @@ -48,10 +47,12 @@ describe('CatalogSelectionDeck', () => {
</SearchDataWrapper>,
);
expect(screen.getByText(label)).toBeInTheDocument();
Object.keys(messages).forEach((key) => {
const terms = ['For all organizations', 'A la carte', 'Subscription'];

terms.forEach((term) => {
// Note: we just pick out the first match for this basic test because some messages appear more than once
expect(
screen.getAllByText(messages[key].defaultMessage, {
screen.getAllByText(term, {
normalizer: getDefaultNormalizer({
trim: false,
collapseWhitespace: false,
Expand Down
12 changes: 11 additions & 1 deletion src/components/courseCard/CourseCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import messages from './CourseCard.messages';
import { CONTENT_TYPE_COURSE } from '../../constants';
import defaultCardHeader from '../../static/default-card-header-light.png';
import features from '../../config';

const CourseCard = ({
intl, onClick, original, learningType,
Expand Down Expand Up @@ -39,6 +40,7 @@ const CourseCard = ({

const imageSrc = card_image_url || defaultCardHeader;
const altText = `${title} course image`;
const businessBadgeMessageKey = features.CONSOLIDATE_SUBS_CATALOG ? 'courseCard.subscriptionBadge' : 'courseCard.businessBadge';

return (
<Card
Expand Down Expand Up @@ -74,7 +76,15 @@ const CourseCard = ({
variant="secondary"
className="business-catalog padded-catalog"
>
{intl.formatMessage(messages['courseCard.businessBadge'])}
{intl.formatMessage(messages[businessBadgeMessageKey])}
</Badge>
)}
{!features.CONSOLIDATE_SUBS_CATALOG
&& enterprise_catalog_query_titles?.includes(
process.env.EDX_FOR_ONLINE_EDU_TITLE,
) && (
<Badge variant="light" className="padded-catalog">
{intl.formatMessage(messages['courseCard.educationBadge'])}
</Badge>
)}
</div>
Expand Down

0 comments on commit 6519c3d

Please sign in to comment.