Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: hide backdoors for search course #871

Merged
merged 3 commits into from
Nov 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
} 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}`} />;

Check warning on line 44 in src/components/course/routes/CourseAbout.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/course/routes/CourseAbout.jsx#L44

Added line #L44 was not covered by tests
}

return (
<>
<CourseHeader />
Expand All @@ -29,7 +59,8 @@
</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 = {
jajjibhai008 marked this conversation as resolved.
Show resolved Hide resolved
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 @@
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);

Check warning on line 127 in src/components/enterprise-user-subsidy/enterprise-offers/data/utils.js

View check run for this annotation

Codecov / codecov/patch

src/components/enterprise-user-subsidy/enterprise-offers/data/utils.js#L127

Added line #L127 was not covered by tests

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
Loading