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 highlight content card to stepper and individual sets #912

Merged
merged 20 commits into from
Dec 19, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
ee6e285
feat: Add highlight content card to stepper and individual sets
brobro10000 Dec 5, 2022
d1e2ec0
feat: Add highlight content card to stepper and individual sets
brobro10000 Dec 7, 2022
8c09af9
feat: Added delete to individual cards
brobro10000 Dec 9, 2022
5de4ae0
Merge branch 'master' into hu/ent-6562
brobro10000 Dec 9, 2022
f699452
chore: Unit testing
brobro10000 Dec 9, 2022
cd6cdae
Merge branch 'hu/ent-6562' of https://github.com/openedx/frontend-app…
brobro10000 Dec 9, 2022
97876cc
Merge branch 'hu/ent-6562' of https://github.com/openedx/frontend-app…
brobro10000 Dec 9, 2022
db03859
Merge branch 'hu/ent-6562' of https://github.com/openedx/frontend-app…
brobro10000 Dec 9, 2022
58e5861
Merge branch 'master' of https://github.com/openedx/frontend-app-admi…
brobro10000 Dec 9, 2022
e7bafe7
Merge branch 'master' of https://github.com/openedx/frontend-app-admi…
brobro10000 Dec 9, 2022
1e9f486
Merge branch 'hu/ent-6562' of https://github.com/openedx/frontend-app…
brobro10000 Dec 13, 2022
edce596
feat: Add hyperlinks on card titles
brobro10000 Dec 13, 2022
d77cd5e
chore: Update test for mapToStore enterpriseSlug
brobro10000 Dec 14, 2022
dc1d591
feat: PR fixes
brobro10000 Dec 16, 2022
60587a7
Merge branch 'master' into hu/ent-6562
brobro10000 Dec 16, 2022
7bc6c4f
chore: tests
brobro10000 Dec 19, 2022
cd1782c
chore:test part 2
brobro10000 Dec 19, 2022
722df03
chore: update tests
adamstankiewicz Dec 19, 2022
17b8baa
chore: update tests... again
adamstankiewicz Dec 19, 2022
d1d51b0
chore: PR feedback
brobro10000 Dec 19, 2022
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 @@ -8,7 +8,6 @@ import HighlightSetSection from './HighlightSetSection';
const ContentHighlightCardContainer = () => {
const { enterpriseCuration: { enterpriseCuration } } = useContext(EnterpriseAppContext);
const highlightSets = useHighlightSetsForCuration(enterpriseCuration);

return (
<Stack gap={4}>
<HighlightSetSection
Expand Down
9 changes: 7 additions & 2 deletions src/components/ContentHighlights/ContentHighlightCardItem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ const ContentHighlightCardItem = ({
contentType,
partners,
cardImageUrl,
extras,
}) => {
const cardLogoSrc = partners?.length === 1 ? partners[0].logoImageUrl : undefined;
const cardLogoAlt = partners?.length === 1 ? `${partners[0].name}'s logo` : undefined;
const cardSubtitle = partners?.map(p => p.name).join(', ');

const cardFooter = extras?.firstEnrollablePaidSeatPrice ? `$${extras.firstEnrollablePaidSeatPrice} · ${FOOTER_TEXT_BY_CONTENT_TYPE[contentType.toLowerCase()]}` : FOOTER_TEXT_BY_CONTENT_TYPE[contentType.toLowerCase()];
brobro10000 marked this conversation as resolved.
Show resolved Hide resolved
return (
<Card>
<Card.ImageCap
Expand All @@ -30,7 +31,7 @@ const ContentHighlightCardItem = ({
<>
<Card.Section />
<Card.Footer
textElement={FOOTER_TEXT_BY_CONTENT_TYPE[contentType.toLowerCase()]}
textElement={cardFooter}
/>
</>
)}
Expand All @@ -47,10 +48,14 @@ ContentHighlightCardItem.propTypes = {
uuid: PropTypes.string,
logoImageUrl: PropTypes.string,
})).isRequired,
extras: PropTypes.shape({
firstEnrollablePaidSeatPrice: PropTypes.number,
}),
};

ContentHighlightCardItem.defaultProps = {
cardImageUrl: undefined,
extras: undefined,
};

export default ContentHighlightCardItem;
20 changes: 14 additions & 6 deletions src/components/ContentHighlights/ContentHighlightSet.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import { Container } from '@edx/paragon';
import { useParams } from 'react-router-dom';
import React from 'react';
import ContentHighlightsCardItemContainer from './ContentHighlightsCardItemsContainer';
import CurrentContentHighlightItemsHeader from './CurrentContentHighlightItemsHeader';
import { useHighlightSetItems } from './data/hooks';

const ContentHighlightSet = () => (
<Container fluid className="mt-5">
<CurrentContentHighlightItemsHeader />
<ContentHighlightsCardItemContainer />
</Container>
);
const ContentHighlightSet = () => {
// const { enterpriseCuration: { enterpriseCuration } } = useContext(EnterpriseAppContext);
brobro10000 marked this conversation as resolved.
Show resolved Hide resolved
const { highlightSetUUID } = useParams();
const { highlightSetItems } = useHighlightSetItems(highlightSetUUID);
brobro10000 marked this conversation as resolved.
Show resolved Hide resolved
return (
<Container fluid className="mt-5">
brobro10000 marked this conversation as resolved.
Show resolved Hide resolved
<CurrentContentHighlightItemsHeader highlightTitle={highlightSetItems?.title} />
<ContentHighlightsCardItemContainer highlightedContent={highlightSetItems?.highlightedContent} />
</Container>
);
};

export default ContentHighlightSet;
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
import React, { useState } from 'react';
import React from 'react';
import { CardGrid } from '@edx/paragon';
import { camelCaseObject } from '@edx/frontend-platform';
import PropTypes from 'prop-types';
import ContentHighlightCardItem from './ContentHighlightCardItem';
import { TEST_COURSE_HIGHLIGHTS_DATA } from './data/constants';

const ContentHighlightsCardItemsContainer = () => {
const [highlightCourses] = useState(
camelCaseObject(TEST_COURSE_HIGHLIGHTS_DATA)[0]?.highlightedContent,
);

if (!highlightCourses) {
const ContentHighlightsCardItemsContainer = ({ highlightedContent }) => {
if (!highlightedContent) {
brobro10000 marked this conversation as resolved.
Show resolved Hide resolved
return null;
}

return (
<CardGrid
columnSizes={{
Expand All @@ -21,7 +15,7 @@ const ContentHighlightsCardItemsContainer = () => {
xl: 4,
brobro10000 marked this conversation as resolved.
Show resolved Hide resolved
}}
>
{highlightCourses.map(({
{highlightedContent.map(({
uuid, title, contentType, authoringOrganizations,
}) => (
<ContentHighlightCardItem
Expand All @@ -36,4 +30,18 @@ const ContentHighlightsCardItemsContainer = () => {
);
};

ContentHighlightsCardItemsContainer.propTypes = {
highlightedContent: PropTypes.arrayOf(PropTypes.shape({
uuid: PropTypes.string,
contentType: PropTypes.oneOf(['course', 'program', 'learnerpathway']),
title: PropTypes.string,
cardImageUrl: PropTypes.string,
authoringOrganizations: PropTypes.arrayOf(PropTypes.shape({
name: PropTypes.string,
logoImageUrl: PropTypes.string,
uuid: PropTypes.string,
})),
})).isRequired,
};

export default ContentHighlightsCardItemsContainer;
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ const ContentHighlightsContextProvider = ({ children }) => {
contentHighlights: [],
searchClient,
});

return (
<ContentHighlightsContext.Provider value={contextValue}>
{children}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,24 @@
import React from 'react';
import { ActionRow } from '@edx/paragon';
import { useParams } from 'react-router-dom';
import PropTypes from 'prop-types';
import ContentHighlightHelmet from './ContentHighlightHelmet';
import DeleteHighlightSet from './DeleteHighlightSet';

const CurrentContentHighlightItemsHeader = () => {
const { highlightSetUUID } = useParams();
const CurrentContentHighlightItemsHeader = ({ highlightTitle }) => (
<>
<ContentHighlightHelmet title={`${highlightTitle} - Highlights`} />
<ActionRow className="mb-4.5">
<h2 className="m-0">
{highlightTitle}
</h2>
<ActionRow.Spacer />
<DeleteHighlightSet />
</ActionRow>
</>
);

const highlightTitle = highlightSetUUID;

const titleName = `${highlightTitle} - Highlights`;

return (
<>
<ContentHighlightHelmet title={titleName} />
<ActionRow className="mb-4.5">
<h2 className="m-0">
{highlightTitle}
</h2>
<ActionRow.Spacer />
<DeleteHighlightSet />
</ActionRow>
</>
);
CurrentContentHighlightItemsHeader.propTypes = {
highlightTitle: PropTypes.string.isRequired,
};

export default CurrentContentHighlightItemsHeader;
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from 'react';
import PropTypes from 'prop-types';

import ContentHighlightCardItem from '../ContentHighlightCardItem';

const ContentConfirmContentCard = ({ original }) => {
const {
title,
contentType,
partners,
cardImageUrl,
originalImageUrl,
extras,
} = original;
return (
<ContentHighlightCardItem
title={title}
contentType={contentType}
partners={partners}
cardImageUrl={cardImageUrl || originalImageUrl}
extras={extras}
/>
);
};

ContentConfirmContentCard.propTypes = {
original: PropTypes.shape({
title: PropTypes.string,
contentType: PropTypes.string,
partners: PropTypes.arrayOf(PropTypes.shape()),
cardImageUrl: PropTypes.string,
originalImageUrl: PropTypes.string,
extras: PropTypes.shape({
firstEnrollablePaidSeatPrice: PropTypes.number,
}),
}).isRequired,
};

export default ContentConfirmContentCard;
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ const ContentHighlightStepper = ({ enterpriseId }) => {
const newHighlightSet = {
title: highlightTitle,
isPublished: true,
// TODO: pass along the selected content keys!
content_key: Object.keys(currentSelectedRowIds).map(key => key.split(':')[1]),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, while this technically should work, it may not be optimal. That is, you're parsing the content_key from the aggregation_key field for the course. While the latter part of the aggregation_key is the format of content_key that enterprise-catalog expects here, it is a bit risky to assume that the content_key can be derived from the aggregation_key in this way, if that makes sense.

For example, content_key for courses in enterprise-catalog is derived from the key field, whereas content_key for programs/pathways content in enterprise-catalog is derived from the uuid field.

I think what you have now should be fine for now because your assumption is currently true, but we may want to add a TODO comment here to suggest finding a solution of extracting the appropriate content_key value from each selected highlighted content item based on its content type (i.e., key for courses, and uuid for programs/pathways).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might try rubber ducking later on (outside of this PR) on finding a more optimal solution here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving Open as reference in the future

};
const response = await EnterpriseCatalogApiService.createHighlightSet(enterpriseId, newHighlightSet);
const result = camelCaseObject(response.data);
Expand Down Expand Up @@ -182,7 +182,7 @@ const ContentHighlightStepper = ({ enterpriseId }) => {
title={STEPPER_STEP_LABELS.CONFIRM_PUBLISH}
index={steps.indexOf(STEPPER_STEP_LABELS.CONFIRM_PUBLISH)}
>
<HighlightStepperConfirmContent />
<HighlightStepperConfirmContent enterpriseId={enterpriseId} />
brobro10000 marked this conversation as resolved.
Show resolved Hide resolved
</Stepper.Step>
</FullscreenModal>
</Stepper>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,16 @@ const ContentSearchResultCard = ({ original }) => {
cardImageUrl,
originalImageUrl,
} = original;

const extras = {
firstEnrollablePaidSeatPrice: original.firstEnrollablePaidSeatPrice,
};
brobro10000 marked this conversation as resolved.
Show resolved Hide resolved
return (
<ContentHighlightCardItem
title={title}
contentType={contentType}
partners={partners}
cardImageUrl={cardImageUrl || originalImageUrl}
extras={extras}
/>
);
};
Expand All @@ -29,6 +32,7 @@ ContentSearchResultCard.propTypes = {
partners: PropTypes.arrayOf(PropTypes.shape()),
cardImageUrl: PropTypes.string,
originalImageUrl: PropTypes.string,
firstEnrollablePaidSeatPrice: PropTypes.number,
}).isRequired,
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useMemo } from 'react';
import React, { useMemo, useState } from 'react';
import PropTypes from 'prop-types';
import { useContextSelector } from 'use-context-selector';
import {
Expand All @@ -13,11 +13,13 @@ import { camelCaseObject } from '@edx/frontend-platform';
import { Configure, InstantSearch, connectStateResults } from 'react-instantsearch-dom';

import { configuration } from '../../../config';
import { STEPPER_STEP_TEXT, MAX_CONTENT_ITEMS_PER_HIGHLIGHT_SET } from '../data/constants';

import {
STEPPER_STEP_TEXT, MAX_CONTENT_ITEMS_PER_HIGHLIGHT_SET,
} from '../data/constants';
import { ContentHighlightsContext } from '../ContentHighlightsContext';
import SkeletonContentCard from '../SkeletonContentCard';

const prodEnterpriseId = 'e783bb19-277f-479e-9c41-8b0ed31b4060';
import ContentConfirmContentCard from './ContentConfirmContentCard';

const BaseReviewContentSelections = ({
searchResults,
Expand All @@ -43,18 +45,40 @@ const BaseReviewContentSelections = ({
}

const { hits } = camelCaseObject(searchResults);
const contentCards = () => {
const cardData = hits.map((highlightedContent) => {
const {
aggregationKey, title, cardImageUrl, contentType, partners, originalImageUrl, firstEnrollablePaidSeatPrice,
} = highlightedContent;
const original = {
aggregationKey,
title,
contentType,
partners,
cardImageUrl,
originalImageUrl,
};
original.extras = {
firstEnrollablePaidSeatPrice,
};
return original;
});
return cardData;
};

// eslint-disable-next-line react-hooks/rules-of-hooks
brobro10000 marked this conversation as resolved.
Show resolved Hide resolved
brobro10000 marked this conversation as resolved.
Show resolved Hide resolved
const [cardData] = useState(contentCards());
return (
<ul>
{hits.map((highlightedContent) => {
const { aggregationKey, title } = highlightedContent;
return (
<li key={aggregationKey}>
{title}
</li>
);
})}
</ul>
<CardGrid
columnSizes={{
xs: 12,
md: 6,
lg: 4,
xl: 3,
}}
>
{cardData.map((original) => (<ContentConfirmContentCard original={original} />))}
</CardGrid>
);
};

Expand All @@ -74,7 +98,7 @@ BaseReviewContentSelections.defaultProps = {

const ReviewContentSelections = connectStateResults(BaseReviewContentSelections);

const SelectedContent = () => {
const SelectedContent = ({ enterpriseId }) => {
const searchClient = useContextSelector(
ContentHighlightsContext,
v => v[0].searchClient,
Expand All @@ -92,7 +116,9 @@ const SelectedContent = () => {
*/
/* eslint-enable max-len */
const algoliaFilters = useMemo(() => {
let filterString = `enterprise_customer_uuids:${prodEnterpriseId}`;
// TODO: replace testEnterpriseId with enterpriseID before push,
// uncomment out import and replace with testEnterpriseId to test
let filterString = `enterprise_customer_uuids:${enterpriseId}`;
brobro10000 marked this conversation as resolved.
Show resolved Hide resolved
if (currentSelectedRowIds.length > 0) {
filterString += ' AND (';
currentSelectedRowIds.forEach((selectedRowId, index) => {
Expand All @@ -104,6 +130,7 @@ const SelectedContent = () => {
filterString += ')';
}
return filterString;
// eslint-disable-next-line react-hooks/exhaustive-deps
brobro10000 marked this conversation as resolved.
Show resolved Hide resolved
}, [currentSelectedRowIds]);

if (currentSelectedRowIds.length === 0) {
Expand All @@ -124,7 +151,11 @@ const SelectedContent = () => {
);
};

const HighlightStepperConfirmContent = () => (
SelectedContent.propTypes = {
enterpriseId: PropTypes.string.isRequired,
};

const HighlightStepperConfirmContent = ({ enterpriseId }) => (
<Container>
<Row>
<Col xs={12} md={8} lg={6}>
Expand All @@ -134,8 +165,12 @@ const HighlightStepperConfirmContent = () => (
</h3>
</Col>
</Row>
<SelectedContent />
<SelectedContent enterpriseId={enterpriseId} />
</Container>
);

HighlightStepperConfirmContent.propTypes = {
enterpriseId: PropTypes.string.isRequired,
};

export default HighlightStepperConfirmContent;
Loading