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: add course/program detail to 'View Course' button #1065

Merged
merged 29 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
cfc8972
feat: display search result cards in catalog tab
katrinan029 Oct 12, 2023
853811c
fix: failing test in BudgetDetailPage
katrinan029 Oct 13, 2023
2a94621
fix: replace word register with enroll
katrinan029 Oct 13, 2023
3689f23
fix: implemented reviewer comments
katrinan029 Oct 16, 2023
c5f3d6e
fix: lint error
katrinan029 Oct 16, 2023
4695894
chore: rebase
katrinan029 Oct 19, 2023
af78b0d
fix: lint error
katrinan029 Oct 19, 2023
db847ca
feat: added policy's catalog uuid to search filter
katrinan029 Oct 19, 2023
c11a6d3
feat: implement view course button to learn more
katrinan029 Oct 19, 2023
2c7bd80
fix: failing test
katrinan029 Oct 19, 2023
4328754
chore: rebase
katrinan029 Oct 19, 2023
a7158f6
fix: added test
katrinan029 Oct 19, 2023
602cdfa
fix: added test coverage
katrinan029 Oct 19, 2023
08e54d4
fix: refactored based on reviewer feedback
katrinan029 Oct 25, 2023
f95d489
fix: lint error
katrinan029 Oct 25, 2023
94bf5ea
fix: refactored code to include new api field and updated test
katrinan029 Oct 26, 2023
284eade
fix: removing unused prop in test
katrinan029 Oct 26, 2023
491fc21
Merge remote-tracking branch 'origin' into knguyen2/ENT-7591
katrinan029 Oct 26, 2023
f621880
fix: refactored
katrinan029 Oct 26, 2023
5a56945
fix: search filters
katrinan029 Oct 26, 2023
dc7109d
chore: rebase
katrinan029 Oct 27, 2023
b2543ac
chore: rebase
katrinan029 Oct 27, 2023
4736de4
chore: rebase
katrinan029 Oct 27, 2023
74e91df
chore: refactored
katrinan029 Oct 27, 2023
2676168
chore: fix lint error
katrinan029 Oct 27, 2023
2555222
chore: refactored
katrinan029 Oct 30, 2023
9373a68
Merge remote-tracking branch 'origin' into knguyen/ENT-7594
katrinan029 Oct 30, 2023
7fd32e3
fix: updated failing test
katrinan029 Oct 30, 2023
1ac7055
Merge remote-tracking branch 'origin' into knguyen/ENT-7594
katrinan029 Oct 30, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,18 @@ import React from 'react';
import { InstantSearch } from 'react-instantsearch-dom';
import algoliasearch from 'algoliasearch/lite';
import { Row, Col } from '@edx/paragon';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';

import { SearchData, SEARCH_FACET_FILTERS } from '@edx/frontend-enterprise-catalog-search';
import CatalogSearch from './search/CatalogSearch';
import {
LANGUAGE_REFINEMENT,
LEARNING_TYPE_REFINEMENT,
useBudgetId,
useSubsidyAccessPolicy,
} from './data';
import { configuration } from '../../config';

const BudgetDetailCatalogTabContents = () => {
const { subsidyAccessPolicyId } = useBudgetId();
const {
data: subsidyAccessPolicy,
} = useSubsidyAccessPolicy(subsidyAccessPolicyId);

const BudgetDetailCatalogTabContents = ({ enterpriseSlug }) => {
const language = {
attribute: LANGUAGE_REFINEMENT,
title: 'Language',
Expand Down Expand Up @@ -48,12 +43,20 @@ const BudgetDetailCatalogTabContents = () => {
indexName={configuration.ALGOLIA.INDEX_NAME}
searchClient={searchClient}
>
<CatalogSearch catalogUuid={subsidyAccessPolicy?.catalogUuid} />
<CatalogSearch enterpriseSlug={enterpriseSlug} />
</InstantSearch>
</SearchData>
</Col>
</Row>
);
};

export default BudgetDetailCatalogTabContents;
BudgetDetailCatalogTabContents.propTypes = {
enterpriseSlug: PropTypes.string.isRequired,
};

const mapStateToProps = state => ({
enterpriseSlug: state.portalConfiguration.enterpriseSlug,
});

export default connect(mapStateToProps)(BudgetDetailCatalogTabContents);
katrinan029 marked this conversation as resolved.
Show resolved Hide resolved
14 changes: 10 additions & 4 deletions src/components/learner-credit-management/cards/CourseCard.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/* eslint-disable @typescript-eslint/naming-convention */
// variables taken from algolia not in camelcase
import React from 'react';
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import { AppContext } from '@edx/frontend-platform/react';

import {
Badge,
Expand All @@ -21,17 +22,19 @@ import { formatPrice, formatDate, getEnrollmentDeadline } from '../data/utils';
import CARD_TEXT from '../constants';

const CourseCard = ({
original,
original, enterpriseSlug,
}) => {
const {
availability,
cardImageUrl,
courseType,
key,
normalizedMetadata,
partners,
title,
} = camelCaseObject(original);

const { config: { ENTERPRISE_LEARNER_PORTAL_URL } } = useContext(AppContext);
const isSmall = useMediaQuery({ maxWidth: breakpoints.small.maxWidth });
const isExtraSmall = useMediaQuery({ maxWidth: breakpoints.extraSmall.maxWidth });

Expand Down Expand Up @@ -72,6 +75,8 @@ const CourseCard = ({

const isExecEd = courseType === EXEC_ED_COURSE_TYPE;

const linkToCourse = `${ENTERPRISE_LEARNER_PORTAL_URL}/${enterpriseSlug}/course/${key}`;
katrinan029 marked this conversation as resolved.
Show resolved Hide resolved

return (
<Card
orientation={isSmall ? 'vertical' : 'horizontal'}
Expand Down Expand Up @@ -104,9 +109,9 @@ const CourseCard = ({
textElement={isExecEd ? execEdEnrollmentInfo : courseEnrollmentInfo}
>
<Button
// TODO: Implementation to follow in ENT-7594
as={Hyperlink}
destination="https://enterprise.stage.edx.org"
data-testid="hyperlink-view-course"
destination={linkToCourse}
target="_blank"
variant="outline-primary"
>
Expand All @@ -120,6 +125,7 @@ const CourseCard = ({
};

CourseCard.propTypes = {
enterpriseSlug: PropTypes.string.isRequired,
original: PropTypes.shape({
availability: PropTypes.arrayOf(PropTypes.string),
cardImageUrl: PropTypes.string,
Expand Down
48 changes: 45 additions & 3 deletions src/components/learner-credit-management/cards/CourseCard.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import React from 'react';
import { fireEvent, render, screen } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';

import { AppContext } from '@edx/frontend-platform/react';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import CourseCard from './CourseCard';

const originalData = {
availability: ['Upcoming'],
card_image_url: undefined,
course_type: 'course',
key: 'course-123x',
normalized_metadata: {
enroll_by_date: '2016-02-18T04:00:00Z',
start_date: '2016-04-18T04:00:00Z',
Expand All @@ -21,8 +23,11 @@ const originalData = {

const defaultProps = {
original: originalData,
enterpriseSlug: 'test-enterprise-slug',
};

const mockLearnerPortal = 'https://enterprise.stage.edx.org';

const execEdData = {
availability: ['Upcoming'],
card_image_url: undefined,
Expand All @@ -46,7 +51,13 @@ describe('Course card works as expected', () => {
test('course card renders', () => {
render(
<IntlProvider locale="en">
<CourseCard {...defaultProps} />
katrinan029 marked this conversation as resolved.
Show resolved Hide resolved
<AppContext.Provider
value={{
config: { ENTERPRISE_LEARNER_PORTAL_URL: mockLearnerPortal },
}}
>
<CourseCard {...defaultProps} />
</AppContext.Provider>
</IntlProvider>,
);
expect(screen.queryByText(defaultProps.original.title)).toBeInTheDocument();
Expand All @@ -64,7 +75,13 @@ describe('Course card works as expected', () => {
test('test card renders default image', async () => {
render(
<IntlProvider locale="en">
<CourseCard {...defaultProps} />
<AppContext.Provider
value={{
config: { ENTERPRISE_LEARNER_PORTAL_URL: mockLearnerPortal },
}}
>
<CourseCard {...defaultProps} />
</AppContext.Provider>
</IntlProvider>,
);
const imageAltText = `${originalData.title} course image`;
Expand All @@ -75,11 +92,36 @@ describe('Course card works as expected', () => {
test('exec ed card renders', async () => {
render(
<IntlProvider locale="en">
<CourseCard {...execEdProps} />
<AppContext.Provider
value={{
config: { ENTERPRISE_LEARNER_PORTAL_URL: mockLearnerPortal },
}}
>
<CourseCard {...execEdProps} />
</AppContext.Provider>
</IntlProvider>,
);
expect(screen.queryByText('$999')).toBeInTheDocument();
expect(screen.queryByText('Starts Apr 18, 2016 • Learner must enroll by Feb 18, 2016')).toBeInTheDocument();
expect(screen.queryByText('Executive Education')).toBeInTheDocument();
});

test('sets correct course hyperlink for course', async () => {
render(
<IntlProvider locale="en">
<AppContext.Provider
value={{
config: { ENTERPRISE_LEARNER_PORTAL_URL: mockLearnerPortal },
}}
>
<CourseCard {...defaultProps} />
</AppContext.Provider>
</IntlProvider>,
);

const hyperlink = screen.getByRole('link', {
name: 'View course Opens in a new tab',
});
expect(hyperlink.href).toContain('https://enterprise.stage.edx.org/test-enterprise-slug/course/course-123x');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ import { SearchHeader } from '@edx/frontend-enterprise-catalog-search';

import { configuration } from '../../../config';
import CatalogSearchResults from './CatalogSearchResults';
import { SEARCH_RESULT_PAGE_SIZE } from '../data';
import { SEARCH_RESULT_PAGE_SIZE, useBudgetId, useSubsidyAccessPolicy } from '../data';

const CatalogSearch = ({ catalogUuid }) => {
const CatalogSearch = ({ enterpriseSlug }) => {
const searchClient = algoliasearch(configuration.ALGOLIA.APP_ID, configuration.ALGOLIA.SEARCH_API_KEY);
const searchFilters = `enterprise_catalog_uuids:${catalogUuid} AND content_type:course`;
const { subsidyAccessPolicyId } = useBudgetId();
const {
data: subsidyAccessPolicy,
} = useSubsidyAccessPolicy(subsidyAccessPolicyId);
const searchFilters = `enterprise_catalog_uuids:${subsidyAccessPolicy.catalogUuid} AND content_type:course`;

return (
<section>
Expand All @@ -35,14 +39,14 @@ const CatalogSearch = ({ catalogUuid }) => {
disableSuggestionRedirect
/>
</div>
<CatalogSearchResults />
<CatalogSearchResults enterpriseSlug={enterpriseSlug} />
</InstantSearch>
</section>
);
};

CatalogSearch.propTypes = {
catalogUuid: PropTypes.string.isRequired,
enterpriseSlug: PropTypes.string.isRequired,
};

export default CatalogSearch;
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const ERROR_MESSAGE = 'An error occurred while retrieving data';
*/

export const BaseCatalogSearchResults = ({
enterpriseSlug,
searchResults,
searchState,
// algolia recommends this prop instead of searching
Expand Down Expand Up @@ -62,6 +63,13 @@ export const BaseCatalogSearchResults = ({
const { refinements } = useContext(SearchContext);
const page = refinements.page || (searchState.page || 0);

const renderCardComponent = (props) => (
<CourseCard
{...props}
enterpriseSlug={enterpriseSlug}
/>
);

useEffect(() => {
setNoContent(searchResults === null || searchResults?.nbHits === 0);
}, [searchResults, setNoContent]);
Expand Down Expand Up @@ -99,7 +107,7 @@ export const BaseCatalogSearchResults = ({
<DataTable.TableControlBar />
<CardView
columnSizes={{ xs: 12 }}
CardComponent={CourseCard}
CardComponent={(props) => renderCardComponent(props)}
/>
<DataTable.EmptyTable content="No results found" />
<DataTable.TableFooter className="justify-content-center">
Expand All @@ -118,6 +126,7 @@ BaseCatalogSearchResults.defaultProps = {
};

BaseCatalogSearchResults.propTypes = {
enterpriseSlug: PropTypes.string.isRequired,
katrinan029 marked this conversation as resolved.
Show resolved Hide resolved
// from Algolia
searchResults: PropTypes.shape({
_state: PropTypes.shape({
Expand Down
Loading