Skip to content
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
22 changes: 11 additions & 11 deletions frontends/api/src/generated/v1/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21272,7 +21272,7 @@ export const TopicsApiAxiosParamCreator = function (
* @param {number} [limit] Number of results to return per page.
* @param {Array<string>} [name] Multiple values may be separated by commas.
* @param {number} [offset] The initial index from which to return the results.
* @param {Array<string>} [parent_topic_name] Multiple values may be separated by commas.
* @param {Array<number>} [parent_topic_id] Multiple values may be separated by commas.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
Expand All @@ -21281,7 +21281,7 @@ export const TopicsApiAxiosParamCreator = function (
limit?: number,
name?: Array<string>,
offset?: number,
parent_topic_name?: Array<string>,
parent_topic_id?: Array<number>,
options: RawAxiosRequestConfig = {},
): Promise<RequestArgs> => {
const localVarPath = `/api/v1/topics/`
Expand Down Expand Up @@ -21316,8 +21316,8 @@ export const TopicsApiAxiosParamCreator = function (
localVarQueryParameter["offset"] = offset
}

if (parent_topic_name) {
localVarQueryParameter["parent_topic_name"] = parent_topic_name.join(
if (parent_topic_id) {
localVarQueryParameter["parent_topic_id"] = parent_topic_id.join(
COLLECTION_FORMATS.csv,
)
}
Expand Down Expand Up @@ -21399,7 +21399,7 @@ export const TopicsApiFp = function (configuration?: Configuration) {
* @param {number} [limit] Number of results to return per page.
* @param {Array<string>} [name] Multiple values may be separated by commas.
* @param {number} [offset] The initial index from which to return the results.
* @param {Array<string>} [parent_topic_name] Multiple values may be separated by commas.
* @param {Array<number>} [parent_topic_id] Multiple values may be separated by commas.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
Expand All @@ -21408,7 +21408,7 @@ export const TopicsApiFp = function (configuration?: Configuration) {
limit?: number,
name?: Array<string>,
offset?: number,
parent_topic_name?: Array<string>,
parent_topic_id?: Array<number>,
options?: RawAxiosRequestConfig,
): Promise<
(
Expand All @@ -21421,7 +21421,7 @@ export const TopicsApiFp = function (configuration?: Configuration) {
limit,
name,
offset,
parent_topic_name,
parent_topic_id,
options,
)
const index = configuration?.serverIndex ?? 0
Expand Down Expand Up @@ -21497,7 +21497,7 @@ export const TopicsApiFactory = function (
requestParameters.limit,
requestParameters.name,
requestParameters.offset,
requestParameters.parent_topic_name,
requestParameters.parent_topic_id,
options,
)
.then((request) => request(axios, basePath))
Expand Down Expand Up @@ -21556,10 +21556,10 @@ export interface TopicsApiTopicsListRequest {

/**
* Multiple values may be separated by commas.
* @type {Array<string>}
* @type {Array<number>}
* @memberof TopicsApiTopicsList
*/
readonly parent_topic_name?: Array<string>
readonly parent_topic_id?: Array<number>
}

/**
Expand Down Expand Up @@ -21601,7 +21601,7 @@ export class TopicsApi extends BaseAPI {
requestParameters.limit,
requestParameters.name,
requestParameters.offset,
requestParameters.parent_topic_name,
requestParameters.parent_topic_id,
options,
)
.then((request) => request(this.axios, this.basePath))
Expand Down
35 changes: 35 additions & 0 deletions frontends/mit-learn/src/pages/ChannelPage/ChannelPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from "../../test-utils"
import ChannelSearch from "./ChannelSearch"
import { assertHeadings } from "ol-test-utilities"
import invariant from "tiny-invariant"

jest.mock("./ChannelSearch", () => {
const actual = jest.requireActual("./ChannelSearch")
Expand Down Expand Up @@ -104,6 +105,21 @@ const setupApis = (
results: [],
})

if (
channel.channel_type === ChannelTypeEnum.Topic &&
channel.topic_detail.topic
) {
const subTopics = factories.learningResources.topics({ count: 5 })
setMockResponse.get(
urls.topics.list({ parent_topic_id: [channel.topic_detail.topic] }),
subTopics,
)
return {
channel,
subTopics,
}
}

return {
channel,
}
Expand Down Expand Up @@ -250,6 +266,25 @@ describe.each(NON_UNIT_CHANNEL_TYPES)(
},
)

describe("Channel Pages, Topic only", () => {
test("Subtopics display", async () => {
const { channel, subTopics } = setupApis({
search_filter: "topic=Physics",
channel_type: ChannelTypeEnum.Topic,
})
renderTestApp({ url: `/c/${channel.channel_type}/${channel.name}` })

invariant(subTopics)
const links = await screen.findAllByRole("link", {
// name arg can be string, regex, or function
name: (name) => subTopics?.results.map((t) => t.name).includes(name),
})
links.forEach((link, i) => {
expect(link).toHaveAttribute("href", subTopics.results[i].channel_url)
})
})
})

describe("Channel Pages, Unit only", () => {
it("Sets the expected meta tags", async () => {
const { channel } = setupApis({
Expand Down
65 changes: 64 additions & 1 deletion frontends/mit-learn/src/pages/ChannelPage/ChannelPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,71 @@ import type {
BooleanFacets,
} from "@mitodl/course-search-utils"
import { ChannelTypeEnum } from "api/v0"
import { useLearningResourceTopics } from "api/hooks/learningResources"
import { ChipLink, Container, styled, Typography } from "ol-components"
import { propsNotNil } from "ol-utilities"

const SubTopicsContainer = styled(Container)(({ theme }) => ({
marginBottom: "60px",
[theme.breakpoints.down("sm")]: {
marginBottom: "24px",
},
}))

const SubTopicsHeader = styled(Typography)(({ theme }) => ({
marginBottom: "10px",
...theme.typography.subtitle1,
}))

const ChipsContainer = styled.div({
display: "flex",
flexWrap: "wrap",
gap: "12px",
})

type RouteParams = {
channelType: ChannelTypeEnum
name: string
}

type SubTopicDisplayProps = {
parentTopicId: number
}

const SubTopicsDisplay: React.FC<SubTopicDisplayProps> = (props) => {
const { parentTopicId } = props
const topicsQuery = useLearningResourceTopics({
parent_topic_id: [parentTopicId],
})
const totalSubtopics = topicsQuery.data?.results.length ?? 0
const subTopics = topicsQuery.data?.results.filter(
propsNotNil(["channel_url"]),
)
return (
totalSubtopics > 0 && (
<SubTopicsContainer>
<SubTopicsHeader>Related Topics</SubTopicsHeader>
<ChipsContainer>
{subTopics?.map((topic) => (
<ChipLink
size="large"
variant="outlinedWhite"
key={topic.id}
href={topic.channel_url}
label={topic.name}
/>
))}
</ChipsContainer>
</SubTopicsContainer>
)
)
}

const ChannelPage: React.FC = () => {
const { channelType, name } = useParams<RouteParams>()
const channelQuery = useChannelDetail(String(channelType), String(name))
const searchParams: Facets & BooleanFacets = {}
const publicDescription = channelQuery.data?.public_description

if (channelQuery.data?.search_filter) {
const urlParams = new URLSearchParams(channelQuery.data.search_filter)
Expand All @@ -37,7 +92,15 @@ const ChannelPage: React.FC = () => {
channelType && (
<>
<ChannelPageTemplate name={name} channelType={channelType}>
<p>{channelQuery.data?.public_description}</p>
{publicDescription && (
<Typography variant="body1">{publicDescription}</Typography>
)}
{channelQuery.data?.channel_type === ChannelTypeEnum.Topic &&
channelQuery.data?.topic_detail?.topic ? (
<SubTopicsDisplay
parentTopicId={channelQuery.data?.topic_detail?.topic}
/>
) : null}
{channelQuery.data?.search_filter && (
<ChannelSearch
channelTitle={channelQuery.data.title}
Expand Down
10 changes: 10 additions & 0 deletions frontends/mit-learn/src/pages/ChannelPage/ChannelSearch.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,16 @@ const setMockApiResponses = ({
results: [],
})

if (
channel.channel_type === ChannelTypeEnum.Topic &&
channel.topic_detail.topic
) {
setMockResponse.get(
urls.topics.list({ parent_topic_id: [channel.topic_detail.topic] }),
factories.learningResources.topics({ count: 5 }),
)
}

return {
channel,
}
Expand Down
2 changes: 0 additions & 2 deletions frontends/mit-learn/src/pages/ChannelPage/ChannelSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,9 @@ import { styled, VisuallyHidden } from "ol-components"

const SearchInputContainer = styled.div`
padding-bottom: 40px;
margin-top: 80px;

${({ theme }) => theme.breakpoints.down("md")} {
padding-bottom: 35px;
margin-top: 40px;
}
`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ import {
} from "./ChannelPageTemplate"
import MetaTags from "@/page-components/MetaTags/MetaTags"

const ChildrenContainer = styled.div(({ theme }) => ({
paddingTop: "40px",
[theme.breakpoints.down("sm")]: {
paddingTop: "24px",
},
}))

const ChannelControlsContainer = styled.div(({ theme }) => ({
display: "flex",
flexDirection: "row",
Expand Down Expand Up @@ -107,7 +114,7 @@ const DefaultChannelTemplate: React.FC<DefaultChannelTemplateProps> = ({
</ChannelControlsContainer>
}
/>
{children}
<ChildrenContainer>{children}</ChildrenContainer>
</>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { factories, urls, setMockResponse } from "api/test-utils"
import { channels as factory } from "api/test-utils/factories"
import { makeChannelViewPath, makeChannelEditPath } from "@/common/urls"
import { makeWidgetListResponse } from "ol-widgets/src/factories"
import type { Channel } from "api/v0"
import { ChannelTypeEnum, type Channel } from "api/v0"

const setupApis = (channelOverrides: Partial<Channel>) => {
const channel = factory.channel({ is_moderator: true, ...channelOverrides })
Expand All @@ -33,6 +33,16 @@ const setupApis = (channelOverrides: Partial<Channel>) => {
results: [],
})

if (
channel.channel_type === ChannelTypeEnum.Topic &&
channel.topic_detail.topic
) {
setMockResponse.get(
urls.topics.list({ parent_topic_id: [channel.topic_detail.topic] }),
factories.learningResources.topics({ count: 5 }),
)
}

return channel
}

Expand Down
24 changes: 6 additions & 18 deletions learning_resources/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,9 +264,9 @@ class TopicFilter(FilterSet):
label="Topic name",
method="filter_name",
)
parent_topic_name = CharInFilter(
label="Parent topic name",
method="filter_parent_topic_name",
parent_topic_id = NumberInFilter(
label="Parent topic ID",
method="filter_parent_topic_id",
)
is_toplevel = BooleanFilter(
label="Filter top-level topics",
Expand All @@ -281,18 +281,6 @@ def filter_toplevel(self, queryset, _, value):
"""Filter by top-level (parent == null)"""
return queryset.filter(parent__isnull=value)

def filter_parent_topic_name(self, queryset, _, values):
"""Filter by parent topic name (up to 2 levels deep)"""

nested_topic_filter = Q()

for topic in values:
nested_topic_filter |= Q(
parent__isnull=False, parent__name__iexact=topic
) | Q(
parent__isnull=False,
parent__parent__isnull=False,
parent__parent__name__iexact=topic,
)

return queryset.filter(nested_topic_filter)
def filter_parent_topic_id(self, queryset, _, values):
"""Get direct children of a topic"""
return queryset.filter(parent_id__in=values)
4 changes: 2 additions & 2 deletions openapi/specs/v1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6308,11 +6308,11 @@ paths:
schema:
type: integer
- in: query
name: parent_topic_name
name: parent_topic_id
schema:
type: array
items:
type: string
type: number
description: Multiple values may be separated by commas.
explode: false
style: form
Expand Down