Skip to content

Commit

Permalink
feat: hide backdoors for search course (#871)
Browse files Browse the repository at this point in the history
* feat: hide backdoors for search course

* fix: failing test cases

* fix: incorporate PR comments
  • Loading branch information
jajjibhai008 authored Nov 22, 2023
1 parent 4b08e4c commit 9dec77f
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 12 deletions.
35 changes: 33 additions & 2 deletions src/components/course/routes/CourseAbout.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,46 @@ import {
} from '@edx/paragon';

import { AppContext } from '@edx/frontend-platform/react';
import { Redirect } from 'react-router-dom';
import { MainContent, Sidebar } from '../../layout';
import CourseHeader from '../course-header/CourseHeader';
import CourseMainContent from '../CourseMainContent';
import CourseSidebar from '../CourseSidebar';
import CourseRecommendations from '../CourseRecommendations';
import { CourseContext } from '../CourseContextProvider';
import { UserSubsidyContext } from '../../enterprise-user-subsidy';
import { isDisableCourseSearch } from '../../enterprise-user-subsidy/enterprise-offers/data/utils';
import { useIsCourseAssigned } from '../data/hooks';
import { features } from '../../../config';

const CourseAbout = () => {
const { canOnlyViewHighlightSets } = useContext(CourseContext);
const {
canOnlyViewHighlightSets,
state: {
course,
},
} = useContext(CourseContext);
const { enterpriseConfig } = useContext(AppContext);
const {
redeemableLearnerCreditPolicies,
enterpriseOffers,
subscriptionPlan,
subscriptionLicense,
} = useContext(UserSubsidyContext);

const isCourseAssigned = useIsCourseAssigned(redeemableLearnerCreditPolicies, course?.key);
const hideCourseSearch = isDisableCourseSearch(
redeemableLearnerCreditPolicies,
enterpriseOffers,
subscriptionPlan,
subscriptionLicense,
);

const featuredHideCourseSearch = features.FEATURE_ENABLE_TOP_DOWN_ASSIGNMENT && hideCourseSearch;
if (!isCourseAssigned && featuredHideCourseSearch) {
return <Redirect to={`/${enterpriseConfig.slug}`} />;
}

return (
<>
<CourseHeader />
Expand All @@ -29,7 +59,8 @@ const CourseAbout = () => {
</Sidebar>
)}
</MediaQuery>
{(canOnlyViewHighlightSets === false && !enterpriseConfig.disableSearch) && <CourseRecommendations />}
{(canOnlyViewHighlightSets === false
&& !enterpriseConfig.disableSearch && !featuredHideCourseSearch) && <CourseRecommendations />}
</Row>
</Container>
</>
Expand Down
50 changes: 41 additions & 9 deletions src/components/course/routes/tests/CourseAbout.test.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { render, screen } from '@testing-library/react';
import { screen } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import { ResponsiveContext, breakpoints } from '@edx/paragon';

import { AppContext } from '@edx/frontend-platform/react';
import CourseAbout from '../CourseAbout';
import { CourseContext } from '../../CourseContextProvider';
import { UserSubsidyContext } from '../../../enterprise-user-subsidy';
import { renderWithRouter } from '../../../../utils/tests';

jest.mock('../../course-header/CourseHeader', () => jest.fn(() => (
<div data-testid="course-header" />
Expand Down Expand Up @@ -32,13 +34,31 @@ jest.mock('../../CourseRecommendations', () => jest.fn(() => (
<div data-testid="course-recommendations" />
)));

const baseCourseContextValue = { canOnlyViewHighlightSets: false };
const baseCourseContextValue = {
canOnlyViewHighlightSets: false,
state: {
courseEntitlementProductSku: 'test-sku',
course: {
key: 'demo-course',
organizationShortCodeOverride: 'Test Org',
organizationLogoOverrideUrl: 'https://test.org/logo.png',
},
},
};

const appContextValues = {
enterpriseConfig: {
disableSearch: false,
},
};

const initialUserSubsidyState = {
redeemableLearnerCreditPolicies: [],
enterpriseOffers: [],
subscriptionPlan: {},
subscriptionLicense: {},
};

const CourseAboutWrapper = ({
responsiveContextValue = { width: breakpoints.extraLarge.minWidth },
courseContextValue = baseCourseContextValue,
Expand All @@ -47,16 +67,18 @@ const CourseAboutWrapper = ({
}) => (
<ResponsiveContext.Provider value={responsiveContextValue}>
<AppContext.Provider value={initialAppState}>
<CourseContext.Provider value={courseContextValue}>
<CourseAbout />
</CourseContext.Provider>
<UserSubsidyContext.Provider value={initialUserSubsidyState}>
<CourseContext.Provider value={courseContextValue}>
<CourseAbout />
</CourseContext.Provider>
</UserSubsidyContext.Provider>
</AppContext.Provider>
</ResponsiveContext.Provider>
);

describe('CourseAbout', () => {
it('renders', () => {
render(<CourseAboutWrapper />);
renderWithRouter(<CourseAboutWrapper />);
expect(screen.getByTestId('course-header')).toBeInTheDocument();
expect(screen.getByTestId('main-content')).toBeInTheDocument();
expect(screen.getByTestId('course-main-content')).toBeInTheDocument();
Expand All @@ -66,8 +88,18 @@ describe('CourseAbout', () => {
});

it('renders with canOnlyViewHighlightSets=true', () => {
const courseContextValue = { canOnlyViewHighlightSets: true };
render(<CourseAboutWrapper courseContextValue={courseContextValue} />);
const courseContextValue = {
canOnlyViewHighlightSets: true,
state: {
courseEntitlementProductSku: 'test-sku',
course: {
key: 'demo-course',
organizationShortCodeOverride: 'Test Org',
organizationLogoOverrideUrl: 'https://test.org/logo.png',
},
},
};
renderWithRouter(<CourseAboutWrapper courseContextValue={courseContextValue} />);
expect(screen.getByTestId('course-header')).toBeInTheDocument();
expect(screen.getByTestId('main-content')).toBeInTheDocument();
expect(screen.getByTestId('course-main-content')).toBeInTheDocument();
Expand All @@ -77,7 +109,7 @@ describe('CourseAbout', () => {
});

it('renders without sidebar is screen is below breakpointslarge.minWidth', () => {
render(<CourseAboutWrapper responsiveContextValue={{ width: breakpoints.small.minWidth }} />);
renderWithRouter(<CourseAboutWrapper responsiveContextValue={{ width: breakpoints.small.minWidth }} />);
expect(screen.queryByTestId('sidebar')).not.toBeInTheDocument();
expect(screen.queryByTestId('course-sidebar')).not.toBeInTheDocument();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,9 @@ export const NO_BALANCE_ALERT_TEXT = 'Your learner credit balance has run out, a
export const NO_BALANCE_CONTACT_ADMIN_TEXT = 'Contact administrator';

export const OFFER_BALANCE_CLICK_EVENT = 'edx.ui.enterprise.learner_portal.offer_balance_alert.clicked';

export const ASSIGNMENT_TYPES = {
ACCEPTED: 'accepted',
ALLOCATED: 'allocated',
CANCELLED: 'cancelled',
};
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import isNil from 'lodash.isnil';
import {
ASSIGNMENT_TYPES,
ENTERPRISE_OFFER_LOW_BALANCE_THRESHOLD_RATIO,
ENTERPRISE_OFFER_LOW_BALANCE_USER_THRESHOLD_DOLLARS,
ENTERPRISE_OFFER_NO_BALANCE_THRESHOLD_DOLLARS,
ENTERPRISE_OFFER_NO_BALANCE_USER_THRESHOLD_DOLLARS,
ENTERPRISE_OFFER_TYPE,
} from './constants';

import { LICENSE_STATUS } from '../../data/constants';

export const offerHasBookingsLimit = offer => (
!isNil(offer.maxDiscount) || !isNil(offer.maxUserDiscount)
);
Expand Down Expand Up @@ -99,3 +102,36 @@ export const transformEnterpriseOffer = (offer) => {
isOutOfBalance: isOfferOutOfBalance(transformedOffer),
};
};

/**
* Determines whether course search should be disabled based on the provided criteria.
* Criteria:
* -> Is assigned a course,
* -> And has no other subsidy,If they had a subscription,
* but the license is no longer relevant, we would not want to count that.
* @param {Array} redeemableLearnerCreditPolicies - Array of redeemable learner credit policies.
* @param {Array} enterpriseOffers - Array of enterprise offers.
* @param {Object} subscriptionPlan - Subscription plan object.
* @param {Object} subscriptionLicense - Subscription license object.
*
* @returns {boolean} Returns true if course search should be disabled, otherwise false.
*/
export const isDisableCourseSearch = (
redeemableLearnerCreditPolicies,
enterpriseOffers,
subscriptionPlan,
subscriptionLicense,
) => {
const assignments = redeemableLearnerCreditPolicies?.flatMap(item => item?.learnerContentAssignments || []);
const allocatedAndAcceptedAssignments = assignments?.filter(item => item?.state === ASSIGNMENT_TYPES.ALLOCATED
|| item?.state === ASSIGNMENT_TYPES.ACCEPTED);

if (allocatedAndAcceptedAssignments?.length === 0) {
return false;
}

const activeOffers = enterpriseOffers?.filter(item => item?.isCurrent);
const hasActiveSubPlan = subscriptionPlan?.isActive && subscriptionLicense?.status === LICENSE_STATUS.ACTIVATED;

return (activeOffers?.length === 1 && !hasActiveSubPlan) || (activeOffers?.length === 0 && hasActiveSubPlan);
};
19 changes: 18 additions & 1 deletion src/components/my-career/CategoryCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import algoliasearch from 'algoliasearch/lite';
import { AppContext } from '@edx/frontend-platform/react';
import LevelBars from './LevelBars';
import SkillsRecommendationCourses from './SkillsRecommendationCourses';
import { UserSubsidyContext } from '../enterprise-user-subsidy';
import { isDisableCourseSearch } from '../enterprise-user-subsidy/enterprise-offers/data/utils';
import { features } from '../../config';

const CategoryCard = ({ topCategory }) => {
const { skillsSubcategories } = topCategory;
Expand All @@ -23,6 +26,20 @@ const CategoryCard = ({ topCategory }) => {
const [showSkills, setShowSkillsOn, , toggleShowSkills] = useToggle(false);
const [showAll, setShowAllOn, setShowAllOff, toggleShowAll] = useToggle(false);
const [showLess, , setShowLessOff, toggleShowLess] = useToggle(false);
const {
redeemableLearnerCreditPolicies,
enterpriseOffers,
subscriptionPlan,
subscriptionLicense,
} = useContext(UserSubsidyContext);
const hideCourseSearch = isDisableCourseSearch(
redeemableLearnerCreditPolicies,
enterpriseOffers,
subscriptionPlan,
subscriptionLicense,
);

const featuredHideCourseSearch = features.FEATURE_ENABLE_TOP_DOWN_ASSIGNMENT && hideCourseSearch;

const config = getConfig();
const { enterpriseConfig } = useContext(AppContext);
Expand Down Expand Up @@ -152,7 +169,7 @@ const CategoryCard = ({ topCategory }) => {
}
</Button>
)}
{!enterpriseConfig.disableSearch && (
{(!enterpriseConfig.disableSearch && !featuredHideCourseSearch) && (
<Card.Section>
{showSkills && subcategorySkills && (
<div className="skill-details-recommended-courses">
Expand Down

0 comments on commit 9dec77f

Please sign in to comment.