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: added Beta banner and Beta badge for videos #1169

Merged
merged 1 commit into from
Sep 9, 2024
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
34 changes: 34 additions & 0 deletions src/components/microlearning/BetaBadge.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {
Badge, OverlayTrigger, Tooltip,
} from '@openedx/paragon';
import './styles/VideoDetailPage.scss';
import { FormattedMessage } from '@edx/frontend-platform/i18n';

const BetaBadge = () => (
<OverlayTrigger
placement="top"
overlay={(
<Tooltip id="video-beta-version-badge" className="video-beta-badge-tooltip">
<FormattedMessage
id="enterprise.microlearningVideo.beta.tooltip"
defaultMessage="<b>Beta version of the Videos.</b> Some features may not be fully functional yet. We appreciate your patience as we fine-tune the experience."
description="Tooltip message for the beta badge on the video page."
values={{
// eslint-disable-next-line react/no-unstable-nested-components
b: (msg) => <strong>{msg}</strong>,
}}
/>
</Tooltip>
)}
>
<Badge variant="info" className="ml-2">
<FormattedMessage
id="enterprise.microlearningVideo.betaBadge.text"
defaultMessage="Beta"
description="Beta badge for the video page."
/>
</Badge>
</OverlayTrigger>
);

export default BetaBadge;
73 changes: 73 additions & 0 deletions src/components/microlearning/VideoBanner.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import {
Card, Button,
} from '@openedx/paragon';
import './styles/VideoDetailPage.scss';
import { Link } from 'react-router-dom';
import { sendEnterpriseTrackEvent } from '@edx/frontend-enterprise-utils';
import { AppContext } from '@edx/frontend-platform/react';
import { useContext } from 'react';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import BetaBadge from './BetaBadge';
import { useEnterpriseCustomer } from '../app/data';

const VideoBanner = () => {
const { data: enterpriseCustomer } = useEnterpriseCustomer();
const { authenticatedUser: { userId } } = useContext(AppContext);
const sendPushEvent = () => {
sendEnterpriseTrackEvent(
enterpriseCustomer.uuid,
'edx.ui.enterprise.learner_portal.video_banner.explore_videos_clicked',
{
userId,
},
);
};
return (
<div data-testid="video-banner" className="d-flex justify-content-center">
<Card orientation="horizontal" className="video-banner-class bg-light-300">
<Card.Section className="col-9 text-primary-500">
<span className="d-flex justify-content-center align-items-end">
<h3 className="text-brand-500 pr-1 m-0">
<FormattedMessage
id="enterprise.microlearning.video.banner.new"
defaultMessage="New!"
description="New badge for the video banner on the video page."
/>
</h3>
<h3 className="p-0 m-0">
<FormattedMessage
id="enterprise.microlearning.videoBanner.title"
defaultMessage="Videos Now Available with Your Subscription"
description="Title for the video banner on the video page."
/>
</h3>
<BetaBadge />
</span>
<p className="d-flex justify-content-center">
<FormattedMessage
id="enterprise.microlearning.videoBanner.description"
defaultMessage="Transform your potential into success."
description="Description for the video banner on the video page."
/>
</p>
</Card.Section>
<Card.Footer className="col-3 justify-content-end">
<Button
as={Link}
to="#videos-section"
variant="outline-primary"
onClick={sendPushEvent}
>
<FormattedMessage
id="enterprise.microlearning.videoBanner.exploreVideos"
defaultMessage="Explore Videos"
description="Explore Videos button text for the video banner on the video page."
/>
</Button>
</Card.Footer>
</Card>
</div>
);
};

export default VideoBanner;
2 changes: 1 addition & 1 deletion src/components/microlearning/VideoDetailPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ const VideoDetailPage = () => {
};
const enableVideos = (
features.FEATURE_ENABLE_VIDEO_CATALOG
&& hasActivatedAndCurrentSubscription(subscriptionLicense)
&& hasActivatedAndCurrentSubscription(subscriptionLicense, enterpriseCustomer.enableBrowseAndRequest)
);

useEffect(() => {
Expand Down
11 changes: 11 additions & 0 deletions src/components/microlearning/styles/VideoDetailPage.scss
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,14 @@
.vjs-transcribe-cueline {
color: #00688d;
}

.logo-cutom-style {
max-width: 59px;
max-height: 30px;
box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.15), 0px 0px 4px 0px rgba(0, 0, 0, 0.15);
}
}

.preview-enroll-expand-body {
max-height: 120px;
-webkit-transition: max-height .5s ease;
Expand All @@ -107,3 +109,12 @@
padding-left: 1.5rem;
}
}
.video-banner-class {
width: 906px;
height: 100px;
}
.video-beta-badge-tooltip{
.tooltip-inner {
max-width: 310px;
}
}
73 changes: 73 additions & 0 deletions src/components/microlearning/tests/VideoBanner.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { screen, waitFor } from '@testing-library/react';
import { AppContext } from '@edx/frontend-platform/react';
import { sendEnterpriseTrackEvent } from '@edx/frontend-enterprise-utils';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import userEvent from '@testing-library/user-event';
import { useEnterpriseCustomer } from '../../app/data';
import VideoBanner from '../VideoBanner';
import { renderWithRouter } from '../../../utils/tests';
import '@testing-library/jest-dom/extend-expect';

jest.mock('../../app/data', () => ({
useEnterpriseCustomer: jest.fn(),
}));
jest.mock('@edx/frontend-enterprise-utils', () => ({
sendEnterpriseTrackEvent: jest.fn(),
}));

describe('VideoBanner', () => {
const mockEnterpriseCustomer = {
uuid: 'mock-uuid',
};

const mockAuthenticatedUser = {
userId: 'test-user-id',
};
const VideoBannerWrapper = () => (
<IntlProvider locale="en">
<AppContext.Provider value={{ authenticatedUser: mockAuthenticatedUser }}>
<VideoBanner />
</AppContext.Provider>
</IntlProvider>
);

beforeEach(() => {
useEnterpriseCustomer.mockReturnValue({ data: mockEnterpriseCustomer });
});

it('renders the video banner with correct title and description', () => {
renderWithRouter(<VideoBannerWrapper />);

expect(screen.getByText('New!')).toBeInTheDocument();
expect(screen.getByText('Videos Now Available with Your Subscription')).toBeInTheDocument();
expect(screen.getByText('Transform your potential into success.')).toBeInTheDocument();
});

it('renders the explore videos button', () => {
renderWithRouter(<VideoBannerWrapper />);

expect(screen.getByText('Explore Videos')).toBeInTheDocument();
});

it('calls sendEnterpriseTrackEvent when explore videos button is clicked', () => {
renderWithRouter(<VideoBannerWrapper />);

const exploreVideosButton = screen.getByText('Explore Videos');
exploreVideosButton.click();

expect(sendEnterpriseTrackEvent).toHaveBeenCalledWith(
mockEnterpriseCustomer.uuid,
'edx.ui.enterprise.learner_portal.video_banner.explore_videos_clicked',
{
userId: mockAuthenticatedUser.userId,
},
);
});
it('hover on Beta badge', async () => {
renderWithRouter(<VideoBannerWrapper />);
userEvent.hover(screen.getByText('Beta'));
await waitFor(() => {
expect(screen.getByText('Beta version of the Videos.')).toBeVisible();
});
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import React from 'react';
import axios from 'axios';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { AppContext } from '@edx/frontend-platform/react';
Expand Down
8 changes: 5 additions & 3 deletions src/components/search/Search.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { useAlgoliaSearch } from '../../utils/hooks';
import ContentTypeSearchResultsContainer from './ContentTypeSearchResultsContainer';
import SearchVideo from './SearchVideo';
import { hasActivatedAndCurrentSubscription } from './utils';
import VideoBanner from '../microlearning/VideoBanner';

export const sendPushEvent = (isPreQueryEnabled, courseKeyMetadata) => {
if (isPreQueryEnabled) {
Expand Down Expand Up @@ -97,7 +98,7 @@ const Search = () => {
const enableVideos = (
canOnlyViewHighlightSets === false
&& features.FEATURE_ENABLE_VIDEO_CATALOG
&& hasActivatedAndCurrentSubscription(subscriptionLicense)
&& hasActivatedAndCurrentSubscription(subscriptionLicense, enterpriseCustomer.enableBrowseAndRequest)
);

const PAGE_TITLE = intl.formatMessage({
Expand Down Expand Up @@ -179,12 +180,13 @@ const Search = () => {
{/* No content type refinement */}
{(contentType === undefined || contentType.length === 0) && (
<Stack className="my-5" gap={5}>
{enableVideos && <VideoBanner />}
{!hasRefinements && <ContentHighlights />}
{canOnlyViewHighlightSets === false && enterpriseCustomer.enableAcademies && <SearchAcademy />}
{features.ENABLE_PATHWAYS && (canOnlyViewHighlightSets === false) && <SearchPathway filter={filters} />}
{features.ENABLE_PROGRAMS && (canOnlyViewHighlightSets === false) && <SearchProgram filter={filters} />}
{canOnlyViewHighlightSets === false && <SearchCourse filter={filters} /> }
{enableVideos && <SearchVideo filter={filters} /> }
{canOnlyViewHighlightSets === false && <SearchCourse filter={filters} />}
{enableVideos && <SearchVideo filter={filters} />}
</Stack>
)}
{/* render a single contentType if the refinement exist and is either a course, program or learnerpathway */}
Expand Down
5 changes: 3 additions & 2 deletions src/components/search/SearchPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,20 @@ import React from 'react';
import { SearchData } from '@edx/frontend-enterprise-catalog-search';
import { useIntl } from '@edx/frontend-platform/i18n';

import { useSubscriptions } from '../app/data';
import { useEnterpriseCustomer, useSubscriptions } from '../app/data';
import Search from './Search';
import { SEARCH_TRACKING_NAME } from './constants';
import { getSearchFacetFilters, hasActivatedAndCurrentSubscription } from './utils';
import { features } from '../../config';

const SearchPage = () => {
const { data: enterpriseCustomer } = useEnterpriseCustomer();
const intl = useIntl();

const { data: { subscriptionLicense } } = useSubscriptions();
const enableVideos = (
features.FEATURE_ENABLE_VIDEO_CATALOG
&& hasActivatedAndCurrentSubscription(subscriptionLicense)
&& hasActivatedAndCurrentSubscription(subscriptionLicense, enterpriseCustomer.enableBrowseAndRequest)
);

return (
Expand Down
13 changes: 10 additions & 3 deletions src/components/search/SearchResults.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
CARDGRID_COLUMN_SIZES,
} from './constants';
import { getContentTypeFromTitle, getNoOfResultsFromTitle, getSkeletonCardFromTitle } from '../utils/search';
import BetaBadge from '../microlearning/BetaBadge';

const SearchResults = ({
className,
Expand All @@ -32,6 +33,7 @@ const SearchResults = ({
contentType,
translatedTitle,
isPathwaySearchResults,
showBetaBadge,
}) => {
const { refinements, dispatch } = useContext(SearchContext);
const nbHits = useNbHitsFromSearchResults(searchResults);
Expand Down Expand Up @@ -96,11 +98,14 @@ const SearchResults = ({
defaultMessage: 'result',
description: 'Label for the search result count when we have only one result.',
});
// I have added a condition to check if showBetaBadge is true then show the BetaBadge component
// Theses changes are temporary and will be removed once the BetaBadge component is removed from
// the SearchResults component
return (
<>
{translatedTitle || title} ({nbHits} {resultsLabel})
<div className="d-flex align-items-center" id={showBetaBadge ? 'videos-section' : 'some-other-section'}>
{translatedTitle || title} ({nbHits} {resultsLabel}) {showBetaBadge && <BetaBadge />}
{query && <>{' '}for &quot;{query}&quot;</>}
</>
</div>
);
},
// eslint-disable-next-line react-hooks/exhaustive-deps
Expand Down Expand Up @@ -196,6 +201,7 @@ SearchResults.propTypes = {
title: PropTypes.string.isRequired,
translatedTitle: PropTypes.string,
isPathwaySearchResults: PropTypes.bool,
showBetaBadge: PropTypes.bool,
};

SearchResults.defaultProps = {
Expand All @@ -206,6 +212,7 @@ SearchResults.defaultProps = {
contentType: undefined,
translatedTitle: undefined,
isPathwaySearchResults: false,
showBetaBadge: false,
};

export default connectStateResults(SearchResults);
1 change: 1 addition & 0 deletions src/components/search/SearchVideo.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const SearchVideo = ({ filter }) => {
description: 'Translated title for the enterprise search page videos section.',
})
}
showBetaBadge
/>
</Index>
);
Expand Down
Loading
Loading