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: tag sections, subsections, and the whole course (FC-0053) #879

Merged
11 changes: 10 additions & 1 deletion src/content-tags-drawer/ContentTagsDrawer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ const ContentTagsDrawer = ({ id, onClose }) => {
} = useContentTaxonomyTagsData(contentId);
const { taxonomyListData, isTaxonomyListLoaded } = useTaxonomyListData();

let contentName = '';
if (isContentDataLoaded) {
if ('displayName' in contentData) {
contentName = contentData.displayName;
} else {
contentName = contentData.name;
}
}

let onCloseDrawer = onClose;
if (onCloseDrawer === undefined) {
onCloseDrawer = () => {
Expand Down Expand Up @@ -106,7 +115,7 @@ const ContentTagsDrawer = ({ id, onClose }) => {
<CloseButton onClick={() => onCloseDrawer()} data-testid="drawer-close-button" />
<span>{intl.formatMessage(messages.headerSubtitle)}</span>
{ isContentDataLoaded
? <h3>{ contentData.displayName }</h3>
? <h3>{ contentName }</h3>
: (
<div className="d-flex justify-content-center align-items-center flex-column">
<Spinner
Expand Down
13 changes: 10 additions & 3 deletions src/content-tags-drawer/data/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';

const getApiBaseUrl = () => getConfig().STUDIO_BASE_URL;
const getLmsApiBaseUrl = () => getConfig().LMS_BASE_URL;

/**
* Get the URL used to fetch tags data from the "taxonomy tags" REST API
Expand Down Expand Up @@ -30,6 +31,7 @@
};
export const getContentTaxonomyTagsApiUrl = (contentId) => new URL(`api/content_tagging/v1/object_tags/${contentId}/`, getApiBaseUrl()).href;
export const getXBlockContentDataApiURL = (contentId) => new URL(`/xblock/outline/${contentId}`, getApiBaseUrl()).href;
export const getCourseContentDataApiURL = (contentId) => new URL(`/api/courses/v1/courses/${contentId}/`, getLmsApiBaseUrl()).href;
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a Studio API you can use for this? It's better not to use LMS APIs for the "Course Authoring" frontend if we don't have to.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

OK I guess you can leave it for now then, but I've asked on Slack if there's a better way. I'll let you know if I hear anything better.

Copy link
Contributor

Choose a reason for hiding this comment

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

You can use /api/contentstore/v1/course_settings/:course_id which is a CMS API returns the display_name . But I guess either way is fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nice! Fixed here: 1f14240

export const getLibraryContentDataApiUrl = (contentId) => new URL(`/api/libraries/v2/blocks/${contentId}/`, getApiBaseUrl()).href;

/**
Expand Down Expand Up @@ -60,9 +62,14 @@
* @returns {Promise<import("./types.mjs").ContentData>}
*/
export async function getContentData(contentId) {
const url = contentId.startsWith('lb:')
? getLibraryContentDataApiUrl(contentId)
: getXBlockContentDataApiURL(contentId);
let url;
if (contentId.startsWith('lb:')) {
url = getLibraryContentDataApiUrl(contentId);
} else if (contentId.startsWith('course-v1:')) {
url = getCourseContentDataApiURL(contentId);

Check warning on line 69 in src/content-tags-drawer/data/api.js

View check run for this annotation

Codecov / codecov/patch

src/content-tags-drawer/data/api.js#L69

Added line #L69 was not covered by tests
} else {
url = getXBlockContentDataApiURL(contentId);
}
const { data } = await getAuthenticatedHttpClient().get(url);
return camelCaseObject(data);
}
Expand Down
8 changes: 7 additions & 1 deletion src/content-tags-drawer/data/apiHooks.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,13 @@ export const useContentTaxonomyTagsUpdater = (contentId, taxonomyId) => {
onSettled: /* istanbul ignore next */ () => {
queryClient.invalidateQueries({ queryKey: ['contentTaxonomyTags', contentId] });
/// Invalidate query with pattern on course outline
queryClient.invalidateQueries({ queryKey: ['unitTagsCount'] });
let contentPattern;
if (contentId.includes('course-v1')) {
contentPattern = contentId;
} else {
contentPattern = contentId.replace(/\+type@.*$/, '*');
}
queryClient.invalidateQueries({ queryKey: ['contentTagsCount', contentPattern] });
},
});
};
11 changes: 10 additions & 1 deletion src/content-tags-drawer/data/types.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
*/

/**
* @typedef {Object} ContentData
* @typedef {Object} XBlockData
* @property {string} id
* @property {string} displayName
* @property {string} category
Expand Down Expand Up @@ -58,3 +58,12 @@
* @property {boolean} staffOnlyMessage
* @property {boolean} hasPartitionGroupComponents
*/

/**
* @typedef {Object} CourseData
* @property {string} name
*/

/**
* @typedef {XBlockData | CourseData} ContentData
*/
31 changes: 4 additions & 27 deletions src/course-outline/CourseOutline.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useState, useEffect, useMemo } from 'react';
// @ts-check
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { useIntl } from '@edx/frontend-platform/i18n';
import {
Expand Down Expand Up @@ -43,7 +44,6 @@ import ConfigureModal from './configure-modal/ConfigureModal';
import PageAlerts from './page-alerts/PageAlerts';
import { useCourseOutline } from './hooks';
import messages from './messages';
import useUnitTagsCount from './data/apiHooks';

const CourseOutline = ({ courseId }) => {
const intl = useIntl();
Expand Down Expand Up @@ -163,35 +163,14 @@ const CourseOutline = ({ courseId }) => {
});
};

const unitsIdPattern = useMemo(() => {
let pattern = '';
sections.forEach((section) => {
section.childInfo.children.forEach((subsection) => {
subsection.childInfo.children.forEach((unit) => {
if (pattern !== '') {
pattern += `,${unit.id}`;
} else {
pattern += unit.id;
}
});
});
});
return pattern;
}, [sections]);

const {
data: unitsTagCounts,
isSuccess: isUnitsTagCountsLoaded,
} = useUnitTagsCount(unitsIdPattern);

/**
* Check if item can be moved by given step.
* Inner function returns false if the new index after moving by given step
* is out of bounds of item length.
* If it is within bounds, returns draggable flag of the item in the new index.
* This helps us avoid moving the item to a position of unmovable item.
* @param {Array} items
* @returns {(id, step) => bool}
* @returns {(id, step) => boolean}
*/
const canMoveItem = (items) => (id, step) => {
const newId = id + step;
Expand Down Expand Up @@ -312,7 +291,6 @@ const CourseOutline = ({ courseId }) => {
) : null}
</TransitionReplace>
<SubHeader
className="mt-5"
title={intl.formatMessage(messages.headingTitle)}
subtitle={intl.formatMessage(messages.headingSubtitle)}
headerActions={(
Expand Down Expand Up @@ -350,7 +328,6 @@ const CourseOutline = ({ courseId }) => {
<DraggableList itemList={sections} setState={setSections} updateOrder={finalizeSectionOrder}>
{sections.map((section, sectionIndex) => (
<SectionCard
id={section.id}
key={section.id}
section={section}
index={sectionIndex}
Expand Down Expand Up @@ -427,7 +404,6 @@ const CourseOutline = ({ courseId }) => {
)}
onCopyToClipboardClick={handleCopyToClipboardClick}
discussionsSettings={discussionsSettings}
tagsCount={isUnitsTagCountsLoaded ? unitsTagCounts[unit.id] : 0}
/>
))}
</DraggableList>
Expand Down Expand Up @@ -510,6 +486,7 @@ const CourseOutline = ({ courseId }) => {
variant="danger"
icon={WarningIcon}
title={intl.formatMessage(messages.alertErrorTitle)}
description=""
aria-hidden="true"
/>
)}
Expand Down
17 changes: 11 additions & 6 deletions src/course-outline/CourseOutline.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { AppProvider } from '@edx/frontend-platform/react';
import { initializeMockApp } from '@edx/frontend-platform';
import MockAdapter from 'axios-mock-adapter';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { cloneDeep } from 'lodash';

import {
Expand Down Expand Up @@ -78,16 +79,20 @@ jest.mock('@edx/frontend-platform/i18n', () => ({
}),
}));

jest.mock('./data/apiHooks', () => () => ({
data: {},
isSuccess: true,
jest.mock('./data/api', () => ({
...jest.requireActual('./data/api'),
getTagsCount: () => jest.fn().mockResolvedValue({}),
}));

const queryClient = new QueryClient();

const RootWrapper = () => (
<AppProvider store={store}>
<IntlProvider locale="en">
<CourseOutline courseId={courseId} />
</IntlProvider>
<QueryClientProvider client={queryClient}>
<IntlProvider locale="en">
<CourseOutline courseId={courseId} />
</IntlProvider>
</QueryClientProvider>
</AppProvider>
);

Expand Down
Loading
Loading