From 26ab8d1efd21fae2f02b8e646f1ce2957f8d6751 Mon Sep 17 00:00:00 2001 From: Anastasia Beglova Date: Fri, 9 Feb 2024 07:09:10 -0500 Subject: [PATCH 1/2] update search --- .github/workflows/ci.yml | 3 +- .github/workflows/deploy.yml | 6 +- base-theme/assets/types/global.d.ts | 3 +- env.ts | 11 +- jest.config.ts | 5 +- package.json | 3 +- www/assets/js/LearningResources.ts | 167 +--- .../js/components/CourseListRow.test.tsx | 9 +- www/assets/js/components/Facet.tsx | 35 +- .../js/components/FacetDisplay.test.tsx | 41 +- www/assets/js/components/FacetDisplay.tsx | 73 +- www/assets/js/components/FilterableFacet.tsx | 36 +- .../js/components/ResourceCollection.test.tsx | 8 +- www/assets/js/components/SearchFacetItem.tsx | 6 +- www/assets/js/components/SearchFilter.tsx | 2 +- www/assets/js/components/SearchPage.test.tsx | 138 +-- www/assets/js/components/SearchPage.tsx | 125 ++- .../js/components/SearchResult.test.tsx | 90 +- www/assets/js/components/SearchResult.tsx | 4 +- www/assets/js/factories/search.ts | 408 +++----- www/assets/js/lib/api.test.ts | 13 +- www/assets/js/lib/api.ts | 18 +- www/assets/js/lib/constants.ts | 38 +- www/assets/js/lib/search.test.tsx | 83 +- www/assets/js/lib/search.ts | 131 ++- yarn.lock | 946 ++---------------- 26 files changed, 726 insertions(+), 1676 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 99ed9252e..d720ec2c9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,8 @@ jobs: test-e2e: env: OCW_STUDIO_BASE_URL: ${{ secrets.OCW_STUDIO_BASE_URL }} - SEARCH_API_URL: ${{ secrets.SEARCH_API_URL }} + COURSE_SEARCH_API_URL: ${{ secrets.COURSE_SEARCH_API_URL }} + CONTENT_FILE_SEARCH_API_URL: ${{ secrets.CONTENT_FILE_SEARCH_API_URL }} STATIC_API_BASE_URL: ${{ secrets.STATIC_API_BASE_URL }} RESOURCE_BASE_URL: ${{ secrets.RESOURCE_BASE_URL }} COURSE_HUGO_CONFIG_PATH: "$GITHUB_WORKSPACE/ocw-hugo-projects/ocw-course-v2/config.yaml" diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 81c98d7d4..555641701 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -54,7 +54,8 @@ jobs: run: (cd ../ocw-www; hugo --config $GITHUB_WORKSPACE/ocw-hugo-projects/ocw-www/config.yaml --themesDir ../ocw-hugo-themes) env: OCW_STUDIO_BASE_URL: ${{ secrets.OCW_STUDIO_BASE_URL }} - SEARCH_API_URL: ${{ secrets.SEARCH_API_URL }} + COURSE_SEARCH_API_URL: ${{ secrets.COURSE_SEARCH_API_URL }} + CONTENT_FILE_SEARCH_API_URL: ${{ secrets.CONTENT_FILE_SEARCH_API_URL }} OCW_COURSE_STARTER_SLUG: ${{ secrets.OCW_COURSE_STARTER_SLUG }} STATIC_API_BASE_URL: ${{ secrets.STATIC_API_BASE_URL }} RESOURCE_BASE_URL: ${{ secrets.RESOURCE_BASE_URL }} @@ -63,7 +64,8 @@ jobs: run: (cd ../ocw-course; hugo --config $GITHUB_WORKSPACE/ocw-hugo-projects/ocw-course-v2/config.yaml --destination public-v2 --themesDir ../ocw-hugo-themes) env: OCW_STUDIO_BASE_URL: ${{ secrets.OCW_STUDIO_BASE_URL }} - SEARCH_API_URL: ${{ secrets.SEARCH_API_URL }} + COURSE_SEARCH_API_URL: ${{ secrets.COURSE_SEARCH_API_URL }} + CONTENT_FILE_SEARCH_API_URL: ${{ secrets.CONTENT_FILE_SEARCH_API_URL }} OCW_COURSE_STARTER_SLUG: ${{ secrets.OCW_COURSE_STARTER_SLUG }} STATIC_API_BASE_URL: ${{ secrets.STATIC_API_BASE_URL }} RESOURCE_BASE_URL: ${{ secrets.RESOURCE_BASE_URL }} diff --git a/base-theme/assets/types/global.d.ts b/base-theme/assets/types/global.d.ts index 64c0070e0..d2c8a9941 100644 --- a/base-theme/assets/types/global.d.ts +++ b/base-theme/assets/types/global.d.ts @@ -11,7 +11,8 @@ declare global { declare let RELEASE_VERSION: string namespace NodeJS { interface ProcessEnv { - SEARCH_API_URL: string + COURSE_SEARCH_API_URL: string + CONTENT_FILE_SEARCH_API_URL: string } } } diff --git a/env.ts b/env.ts index 4eb0dea67..f3b062c1c 100644 --- a/env.ts +++ b/env.ts @@ -65,9 +65,14 @@ const envSchema = { desc: "Base URL with which to prefix resource paths in Hugo's output.", devDefault: "https://live-qa.ocw.mit.edu/" }), - SEARCH_API_URL: envalid.url({ - desc: "The URL of the search API.", - devDefault: "https://discussions-rc.odl.mit.edu/api/v0/search/" + COURSE_SEARCH_API_URL: envalid.url({ + desc: "The URL of the learning resource search API.", + devDefault: + "https://mit-open-rc.odl.mit.edu/api/v1/learning_resources_search/" + }), + CONTENT_FILE_SEARCH_API_URL: envalid.url({ + desc: "The URL of the contentfile search API.", + devDefault: "https://mit-open-rc.odl.mit.edu/api/v1/content_file_search/" }), SENTRY_ENV: envalid.str({ desc: "The environment for Sentry", diff --git a/jest.config.ts b/jest.config.ts index 7626161f0..846f67358 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -14,7 +14,10 @@ const config: Config.InitialOptions = { // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped testPathIgnorePatterns: ["/node_modules/", "tests-e2e/"], preset: "ts-jest", - testEnvironment: "jsdom" + testEnvironment: "jsdom", + moduleNameMapper: { + axios: "axios/dist/node/axios.cjs" + } } export default config diff --git a/package.json b/package.json index c3ea97e5e..8b9052c18 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ }, "browserslist": "> 0.25%, not dead", "dependencies": { - "@mitodl/course-search-utils": "^2.3.0", + "@mitodl/course-search-utils": "^3.0.0", "@sentry/browser": "^5.19.0", "array-flat-polyfill": "^1.0.1", "bootstrap": "^4.3.1", @@ -70,7 +70,6 @@ "videojs-youtube": "^2.6.1" }, "devDependencies": { - "@mitodl/ocw-to-hugo": "1.36.0", "@playwright/test": "^1.40.0", "@swc/core": "^1.3.14", "@swc/helpers": "^0.4.12", diff --git a/www/assets/js/LearningResources.ts b/www/assets/js/LearningResources.ts index b6786bf9d..6a0b2d819 100644 --- a/www/assets/js/LearningResources.ts +++ b/www/assets/js/LearningResources.ts @@ -1,8 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable camelcase */ -import { LearningResourceType, Facets } from "@mitodl/course-search-utils" +import { Facets } from "@mitodl/course-search-utils" import { - OCW_PLATFORM, COURSE_ARCHIVED, COURSE_CURRENT, PROFESSIONAL, @@ -19,30 +18,6 @@ type Audience = type Certification = [] | [typeof CERTIFICATE] -export interface CourseResult { - id: number - course_id: string - coursenum: string - title: string - url: string - image_src: string - short_description: string - department: string - platform: typeof OCW_PLATFORM - offered_by: [typeof OCW_PLATFORM] - topics: string[] - object_type: LearningResourceType.Course - runs: CourseRun[] - audience: Audience - certification: Certification - course_feature_tags: string[] - content_title: undefined - run_title: undefined - run_slug: undefined - content_type: undefined - short_url: undefined -} - interface Instructor { first_name: string last_name: string @@ -144,135 +119,33 @@ export interface CourseRun { short_url: undefined } -export interface VideoResult { - id: number - video_id: string - title: string - url: string - image_src: string - short_description: string - topics: string[] - object_type: LearningResourceType.Video - offered_by: [typeof OCW_PLATFORM] - runs: any[] - audience: Audience - certification: Certification - department: undefined - content_title: undefined - run_title: undefined - run_slug: undefined - coursenum: undefined - content_type: undefined - short_url: undefined - course_id: undefined - course_feature_tags: undefined -} - -export interface PodcastResult { - id: number - podcast_id: string - title: string - url: string - image_src: string - short_description: string - topics: string[] - object_type: LearningResourceType.Podcast - offered_by: [typeof OCW_PLATFORM] - runs: any[] - audience: Audience - certification: Certification - department: undefined - content_title: undefined - run_title: undefined - run_slug: undefined - coursenum: undefined - content_type: undefined - short_url: undefined - course_id: undefined - course_feature_tags: undefined -} - -export interface PodcastEpisodeResult { - id: number - podcast_id: string - title: string - url: string - image_src: string - short_description: string - topics: string[] - object_type: LearningResourceType.PodcastEpisode - offered_by: [typeof OCW_PLATFORM] - runs: any[] - series_title: string - audience: Audience - certification: Certification - department: undefined - content_title: undefined - run_title: undefined - run_slug: undefined - coursenum: undefined - content_type: undefined - short_url: undefined - course_id: undefined - course_feature_tags: undefined -} - -export interface ResourceFileResult { - id: number - course_id: string - coursenum: string - title: string - url: string - image_src: string - topics: string[] - object_type: LearningResourceType.ResourceFile - content_title: string - run_title: string - run_slug: string - content_type: ContentType - short_url: string - runs: undefined - audience: undefined - certification: undefined - department: undefined - short_description: string - course_feature_tags: undefined -} - -export type LearningResourceResult = - | CourseResult - | VideoResult - | PodcastResult - | PodcastEpisodeResult - | ResourceFileResult - interface Topic { name: string } export interface LearningResource { - id: number | string - title: string + id?: number | string + title: string | null image_src: string - object_type: LearningResourceType - platform: string | null + object_type?: string + platform?: string | null topics: Topic[] - runs: CourseRun[] - level: Level[] | null + runs?: CourseRun[] + level?: Level[] | null instructors: string[] - department: string | undefined - audience: Audience | undefined - certification: Certification | undefined - content_title: string | undefined - run_title: string | null - run_slug: string | null - content_type: ContentType | null - url: string | null - short_url: string | null - course_id: string | null - coursenum: string | null - description: string | null - course_feature_tags: string[] + department?: string | undefined + audience?: Audience | undefined + certification?: Certification | undefined + content_title?: string | undefined | null + run_title?: string | null + run_slug?: string | null + content_type?: ContentType | null + url?: string | null + short_url?: string | null + course_id?: string | null + coursenum?: string | null + description?: string | null + course_feature_tags?: string[] } export type FacetKey = keyof Facets diff --git a/www/assets/js/components/CourseListRow.test.tsx b/www/assets/js/components/CourseListRow.test.tsx index 417900fcf..4b6cffaeb 100644 --- a/www/assets/js/components/CourseListRow.test.tsx +++ b/www/assets/js/components/CourseListRow.test.tsx @@ -1,15 +1,12 @@ import { mount } from "enzyme" import React from "react" -import { LearningResourceType } from "@mitodl/course-search-utils" -import { makeLearningResourceResult } from "../factories/search" -import { searchResultToLearningResource } from "../lib/search" +import { makeCourseSearchResult } from "../factories/search" +import { courseSearchResultToLearningResource } from "../lib/search" import CourseListRow from "./CourseListRow" function setup() { - const course = searchResultToLearningResource( - makeLearningResourceResult(LearningResourceType.Course) - ) + const course = courseSearchResultToLearningResource(makeCourseSearchResult()) const wrapper = mount() diff --git a/www/assets/js/components/Facet.tsx b/www/assets/js/components/Facet.tsx index 2d73a9013..ae6220cf7 100644 --- a/www/assets/js/components/Facet.tsx +++ b/www/assets/js/components/Facet.tsx @@ -1,6 +1,5 @@ import React, { useState } from "react" import { contains } from "ramda" -import has from "lodash.has" import SearchFacetItem from "./SearchFacetItem" import { Aggregation } from "@mitodl/course-search-utils" @@ -15,18 +14,26 @@ interface Props { currentlySelected: string[] onUpdate: React.ChangeEventHandler expandedOnLoad: boolean + labelFunction?: ((value: string) => string | null) | null } function SearchFacet(props: Props) { - const { name, title, results, currentlySelected, onUpdate, expandedOnLoad } = - props + const { + name, + title, + results, + currentlySelected, + onUpdate, + expandedOnLoad, + labelFunction + } = props const [showFacetList, setShowFacetList] = useState(expandedOnLoad) const [showAllFacets, setShowAllFacets] = useState(false) const titleLineIcon = showFacetList ? "arrow_drop_down" : "arrow_right" - return results && results.buckets && results.buckets.length === 0 ? null : ( + return results && results.length === 0 ? null : (
@@ -120,11 +129,4 @@ function FilterableSearchFacet(props: Props) { ) } -const propsAreEqual = (_prevProps: Props, nextProps: Props) => { - // results.buckets is null while the search request is in-flight - // we want to defer rendering in that case because it will cause - // all the facets to briefly disappear before reappearing - return !has(nextProps.results, "buckets") -} - -export default React.memo(FilterableSearchFacet, propsAreEqual) +export default FilterableSearchFacet diff --git a/www/assets/js/components/ResourceCollection.test.tsx b/www/assets/js/components/ResourceCollection.test.tsx index 1875154a7..38395b6ee 100644 --- a/www/assets/js/components/ResourceCollection.test.tsx +++ b/www/assets/js/components/ResourceCollection.test.tsx @@ -1,7 +1,7 @@ import React from "react" import { mount } from "enzyme" -import { makeResourceFileResult } from "../factories/search" -import { searchResultToLearningResource } from "../lib/search" +import { makeContentFileSearchResult } from "../factories/search" +import { resourceSearchResultToLearningResource } from "../lib/search" import * as hugoHooks from "../hooks/hugo_data" import ResourceCollection from "./ResourceCollection" import { LearningResourceDisplay } from "./SearchResult" @@ -11,8 +11,8 @@ jest.mock("../hooks/hugo_data") function setup() { const data = [...Array(10)] - .map(makeResourceFileResult) - .map(searchResultToLearningResource) + .map(makeContentFileSearchResult) + .map(resourceSearchResultToLearningResource) useResourceCollectionData.mockReturnValue(data) diff --git a/www/assets/js/components/SearchFacetItem.tsx b/www/assets/js/components/SearchFacetItem.tsx index 8fae40ced..558bf966a 100644 --- a/www/assets/js/components/SearchFacetItem.tsx +++ b/www/assets/js/components/SearchFacetItem.tsx @@ -11,13 +11,13 @@ interface Props { isChecked: boolean onUpdate: React.ChangeEventHandler name: string + displayKey: string | null } export default function SearchFacetItem(props: Props) { - const { facet, isChecked, onUpdate, name } = props + const { facet, isChecked, onUpdate, name, displayKey } = props const facetId = slugify(`${name}-${facet.key}`) - return (
- {facet.key} + {displayKey || ""}
{facet.doc_count}
diff --git a/www/assets/js/components/SearchFilter.tsx b/www/assets/js/components/SearchFilter.tsx index 8844fa153..901ee7038 100644 --- a/www/assets/js/components/SearchFilter.tsx +++ b/www/assets/js/components/SearchFilter.tsx @@ -2,7 +2,7 @@ import React from "react" interface Props { value: string - labelFunction?: (value: string) => string + labelFunction?: ((value: string) => string | null) | null clearFacet: () => void } diff --git a/www/assets/js/components/SearchPage.test.tsx b/www/assets/js/components/SearchPage.test.tsx index 2f98277f3..0a8810fc7 100644 --- a/www/assets/js/components/SearchPage.test.tsx +++ b/www/assets/js/components/SearchPage.test.tsx @@ -5,8 +5,9 @@ import { search } from "../lib/api" import { times } from "ramda" import { INITIAL_FACET_STATE, - LearningResourceType, - serializeSearchParams + serializeSearchParams, + LEARNING_RESOURCE_ENDPOINT, + CONTENT_FILE_ENDPOINT } from "@mitodl/course-search-utils" import InfiniteScroll from "react-infinite-scroller" @@ -14,14 +15,11 @@ import SearchPage from "./SearchPage" import { DEFAULT_UI_PAGE_SIZE, COMPACT_UI_PAGE_SIZE } from "../lib/constants" -import { makeCourseResult } from "../factories/search" +import { makeCourseSearchResult } from "../factories/search" import { createMemoryHistory, InitialEntry } from "history" import FilterableSearchFacets from "./FilterableFacet" -const mockGetResults = () => - times(makeCourseResult, DEFAULT_UI_PAGE_SIZE).map(result => ({ - _source: result - })) +const mockGetResults = () => times(makeCourseSearchResult, DEFAULT_UI_PAGE_SIZE) // eslint-disable-next-line @typescript-eslint/no-explicit-any let resolver = (_extraData?: any) => { @@ -40,7 +38,9 @@ jest.mock("../lib/api", () => ({ resolver = (extraData = {}) => { const results = mockGetResults() resolve({ - hits: { hits: results, total: results.length }, + count: 10, + results: results, + metadata: {}, ...extraData }) } @@ -51,14 +51,12 @@ const spySearch = jest.mocked(search) const defaultCourseFacets = { ...INITIAL_FACET_STATE, - type: [LearningResourceType.Course], - offered_by: ["OCW"] + offered_by: ["ocw"] } const defaultResourceFacets = { ...INITIAL_FACET_STATE, - type: [LearningResourceType.ResourceFile], - offered_by: ["OCW"] + offered_by: ["ocw"] } describe("SearchPage component", () => { @@ -75,11 +73,11 @@ describe("SearchPage component", () => { { text: "amazing text!", activeFacets: {} }, { text: "great search", - activeFacets: { topics: ["Mathematics"] } + activeFacets: { topic: ["Mathematics"] } }, { text: "", - activeFacets: { topics: ["Science"] } + activeFacets: { topic: ["Science"] } } ].forEach(params => { test(`should search at startup with ${serializeSearchParams( @@ -97,13 +95,9 @@ describe("SearchPage component", () => { ...defaultCourseFacets, ...params.activeFacets }, - aggregations: [ - "department_name", - "level", - "topics", - "course_feature_tags" - ], - sort: null + aggregations: ["department", "level", "topic", "course_feature"], + sort: null, + endpoint: null } ]) }) @@ -129,12 +123,8 @@ describe("SearchPage component", () => { size: DEFAULT_UI_PAGE_SIZE, activeFacets: defaultCourseFacets, sort: null, - aggregations: [ - "department_name", - "level", - "topics", - "course_feature_tags" - ] + aggregations: ["department", "level", "topic", "course_feature"], + endpoint: null } ], [ @@ -144,12 +134,8 @@ describe("SearchPage component", () => { size: DEFAULT_UI_PAGE_SIZE, activeFacets: defaultCourseFacets, sort: null, - aggregations: [ - "department_name", - "level", - "topics", - "course_feature_tags" - ] + aggregations: ["department", "level", "topic", "course_feature"], + endpoint: null } ] ]) @@ -161,8 +147,8 @@ describe("SearchPage component", () => { const parameters = { text: "Math 101", activeFacets: { - topics: ["Mathematics"], - course_feature_tags: ["Exams", "Problem Sets with Solutions"] + topic: ["Mathematics"], + course_feature: ["Exams", "Problem Sets with Solutions"] } } const searchString = serializeSearchParams(parameters) @@ -180,12 +166,8 @@ describe("SearchPage component", () => { size: DEFAULT_UI_PAGE_SIZE, activeFacets: { ...defaultCourseFacets, ...parameters.activeFacets }, sort: null, - aggregations: [ - "department_name", - "level", - "topics", - "course_feature_tags" - ] + aggregations: ["department", "level", "topic", "course_feature"], + endpoint: null } ], [ @@ -195,10 +177,11 @@ describe("SearchPage component", () => { size: DEFAULT_UI_PAGE_SIZE, activeFacets: { ...defaultResourceFacets, - ...{ topics: ["Mathematics"] } + ...{ topic: ["Mathematics"] } }, - sort: null, - aggregations: ["resource_type", "topics"] + sort: "", + aggregations: ["content_feature_type", "topic"], + endpoint: "content_file" } ] ]) @@ -213,7 +196,7 @@ describe("SearchPage component", () => { const searchString = serializeSearchParams(parameters) const { wrapper } = render(searchString) await resolveSearch({ - suggest: ["mathematics"] + metadata: { suggest: ["mathematics"] } }) wrapper.update() expect(wrapper.find(".suggestions").text()).toEqual( @@ -242,7 +225,7 @@ describe("SearchPage component", () => { const sortParam = "-sortablefieldname", differentSortParam = "differentsortparam" const parameters = { - sort: { field: sortParam, option: "asc" } + sort: sortParam } const searchString = serializeSearchParams(parameters) const { wrapper } = render(searchString) @@ -252,10 +235,7 @@ describe("SearchPage component", () => { // @ts-expect-error Not mocking whole event select.prop("onChange")({ target: { value: differentSortParam } }) }) - expect(spySearch.mock.calls[1][0].sort).toEqual({ - field: differentSortParam, - option: "asc" - }) + expect(spySearch.mock.calls[1][0].sort).toEqual(differentSortParam) }) it("should allow the user to toggle the layout", async () => { @@ -281,17 +261,15 @@ describe("SearchPage component", () => { // ;( [ - [LearningResourceType.Course, true], - [LearningResourceType.ResourceFile, false] + [LEARNING_RESOURCE_ENDPOINT, true], + [CONTENT_FILE_ENDPOINT, false] ] as const - ).forEach(([type, sortExists]) => { + ).forEach(([endpoint, sortExists]) => { it(`${ sortExists ? "should" : "shouldn't" - } show the sort option if the user is on the ${type} page`, async () => { + } show the sort option if the endpoint is ${endpoint}`, async () => { const parameters = { - activeFacets: { - type: [type] - } + endpoint: endpoint } const searchString = serializeSearchParams(parameters) const { wrapper } = render(searchString) @@ -337,12 +315,8 @@ describe("SearchPage component", () => { size: DEFAULT_UI_PAGE_SIZE, activeFacets: defaultCourseFacets, sort: null, - aggregations: [ - "department_name", - "level", - "topics", - "course_feature_tags" - ] + aggregations: ["department", "level", "topic", "course_feature"], + endpoint: null } ], [ @@ -352,12 +326,8 @@ describe("SearchPage component", () => { size: DEFAULT_UI_PAGE_SIZE, activeFacets: defaultCourseFacets, sort: null, - aggregations: [ - "department_name", - "level", - "topics", - "course_feature_tags" - ] + aggregations: ["department", "level", "topic", "course_feature"], + endpoint: null } ], [ @@ -367,12 +337,8 @@ describe("SearchPage component", () => { size: DEFAULT_UI_PAGE_SIZE, activeFacets: defaultCourseFacets, sort: null, - aggregations: [ - "department_name", - "level", - "topics", - "course_feature_tags" - ] + aggregations: ["department", "level", "topic", "course_feature"], + endpoint: null } ] ]) @@ -404,12 +370,8 @@ describe("SearchPage component", () => { size: DEFAULT_UI_PAGE_SIZE, activeFacets: defaultCourseFacets, sort: null, - aggregations: [ - "department_name", - "level", - "topics", - "course_feature_tags" - ] + aggregations: ["department", "level", "topic", "course_feature"], + endpoint: null } ], [ @@ -419,12 +381,8 @@ describe("SearchPage component", () => { size: DEFAULT_UI_PAGE_SIZE, activeFacets: defaultCourseFacets, sort: null, - aggregations: [ - "department_name", - "level", - "topics", - "course_feature_tags" - ] + aggregations: ["department", "level", "topic", "course_feature"], + endpoint: null } ] ]) @@ -449,13 +407,13 @@ describe("SearchPage component", () => { const topic = facets.at(1) const features = facets.at(2) - expect(topic.props().name).toEqual("topics") + expect(topic.props().name).toEqual("topic") expect(topic.props().title).toEqual("Topics") expect(topic.props().currentlySelected).toEqual([]) - expect(features.props().name).toEqual("course_feature_tags") + expect(features.props().name).toEqual("course_feature") expect(features.props().title).toEqual("Features") expect(features.props().currentlySelected).toEqual([]) - expect(department.props().name).toEqual("department_name") + expect(department.props().name).toEqual("department") expect(department.props().title).toEqual("Departments") expect(department.props().currentlySelected).toEqual([]) }) diff --git a/www/assets/js/components/SearchPage.tsx b/www/assets/js/components/SearchPage.tsx index 7409c15cc..7d13d5c4e 100644 --- a/www/assets/js/components/SearchPage.tsx +++ b/www/assets/js/components/SearchPage.tsx @@ -1,12 +1,15 @@ import React, { useState, useCallback } from "react" import InfiniteScroll from "react-infinite-scroller" import { - Aggregations, useCourseSearch, - serializeSort, LearningResourceType, - Facets + LEARNING_RESOURCE_ENDPOINT, + CONTENT_FILE_ENDPOINT, + Aggregations, + CourseResource, + ContentFile } from "@mitodl/course-search-utils" + import { without } from "ramda" import { History as RouterHistory } from "history" @@ -16,41 +19,41 @@ import SearchFilterDrawer from "./SearchFilterDrawer" import Loading, { Spinner } from "./Loading" import { search } from "../lib/api" -import { searchResultToLearningResource } from "../lib/search" import { - COURSENUM_SORT_FIELD, + courseSearchResultToLearningResource, + resourceSearchResultToLearningResource +} from "../lib/search" +import { CONTACT_URL, SEARCH_COMPACT_UI, DEFAULT_UI_PAGE_SIZE, COMPACT_UI_PAGE_SIZE, - OCW_PLATFORM + OCW_OFFEROR } from "../lib/constants" import { emptyOrNil, isDoubleQuoted } from "../lib/util" -import { FacetManifest, LearningResourceResult } from "../LearningResources" +import { FacetManifest } from "../LearningResources" const COURSE_FACETS: FacetManifest = [ - ["department_name", "Departments", true, true], + ["department", "Departments", true, true], ["level", "Level", false, false], - ["topics", "Topics", true, false], - ["course_feature_tags", "Features", true, false] + ["topic", "Topics", true, false], + ["course_feature", "Features", true, false] ] const RESOURCE_FACETS: FacetManifest = [ - ["resource_type", "Resource Types", true, false], - ["topics", "Topics", true, false] + ["content_feature_type", "Resource Types", true, false], + ["topic", "Topics", true, false] ] -interface Result { - _source: LearningResourceResult -} - type SearchPageProps = { history: RouterHistory } export default function SearchPage(props: SearchPageProps) { const { history } = props - const [results, setSearchResults] = useState([]) + const [results, setSearchResults] = useState< + CourseResource[] | ContentFile[] + >([]) const [aggregations, setAggregations] = useState(new Map()) const [suggestions, setSuggestions] = useState([]) const [total, setTotal] = useState(0) @@ -58,32 +61,29 @@ export default function SearchPage(props: SearchPageProps) { const [searchApiFailed, setSearchApiFailed] = useState(false) const [requestInFlight, setRequestInFlight] = useState(false) - const isResourceSearch = (activeFacets: Facets) => { - return activeFacets.type?.includes(LearningResourceType.ResourceFile) - } - const runSearch = useCallback( - async (text, activeFacets, from, sort, ui) => { - activeFacets["offered_by"] = [OCW_PLATFORM] - if (activeFacets && activeFacets.type.length > 1) { + async (text, activeFacets, from, sort, ui, endpoint) => { + activeFacets["offered_by"] = [OCW_OFFEROR] + if (activeFacets && activeFacets.type && activeFacets.type.length > 1) { // Default is LR_TYPE_ALL, don't want that here. course or resourcefile only - activeFacets["type"] = [LearningResourceType.Course] + activeFacets["resource_type"] = [LearningResourceType.Course] } const pageSize = getPageSizeFromUIParam(ui) - const relevantFacets = isResourceSearch(activeFacets) ? - RESOURCE_FACETS : - COURSE_FACETS + const relevantFacets = + endpoint === CONTENT_FILE_ENDPOINT ? RESOURCE_FACETS : COURSE_FACETS const allowedAggregations = relevantFacets.map(facet => facet[0]) setRequestInFlight(true) + const newResults = await search({ text, from, activeFacets, size: pageSize, sort: sort, - aggregations: allowedAggregations + aggregations: allowedAggregations, + endpoint: endpoint }) setRequestInFlight(false) @@ -92,7 +92,7 @@ export default function SearchPage(props: SearchPageProps) { return } - const { suggest } = newResults + const { suggest } = newResults.metadata if (!emptyOrNil(suggest) && !emptyOrNil(text)) { setSuggestions( without( @@ -113,14 +113,14 @@ export default function SearchPage(props: SearchPageProps) { setSuggestions([]) } - setAggregations(new Map(Object.entries(newResults.aggregations ?? {}))) + setAggregations( + new Map(Object.entries(newResults.metadata.aggregations ?? {})) + ) setSearchResults(results => - from === 0 ? - newResults.hits.hits : - [...results, ...newResults.hits.hits] + from === 0 ? newResults.results : [...results, ...newResults.results] ) - setTotal(newResults.hits.total) + setTotal(newResults.count) setCompletedInitialLoad(true) }, [ @@ -163,7 +163,9 @@ export default function SearchPage(props: SearchPageProps) { clearAllFilters, acceptSuggestion, updateUI, - ui + ui, + endpoint, + updateEndpoint } = useCourseSearch( runSearch, clearSearch, @@ -177,19 +179,20 @@ export default function SearchPage(props: SearchPageProps) { const toggleResourceSearch = useCallback( nextResourceFilterState => async () => { - if (isResourceSearch(activeFacets) === nextResourceFilterState) { + if (endpoint === nextResourceFilterState) { // Immediately return in case the user clicks and already active facet. // Github issue https://github.com/mitodl/ocw-hugo-themes/issues/105 return } const toggledFacets: [string, string, boolean][] = [ - ["type", LearningResourceType.ResourceFile, nextResourceFilterState], - ["type", LearningResourceType.Course, !nextResourceFilterState] + ["resource_type", LearningResourceType.Course, !nextResourceFilterState] ] // Remove any facets not relevant to the new search type const newFacets: Map = new Map( // @ts-expect-error We should clean this up. It works because Map constructor is ignoring everything except 0th, 1st item in the entries array. - nextResourceFilterState ? RESOURCE_FACETS : COURSE_FACETS + nextResourceFilterState === CONTENT_FILE_ENDPOINT ? + RESOURCE_FACETS : + COURSE_FACETS ) Object.entries(activeFacets).forEach(([key, list]) => { @@ -199,14 +202,18 @@ export default function SearchPage(props: SearchPageProps) { }) } }) + + if (nextResourceFilterState === CONTENT_FILE_ENDPOINT) { + updateSort(null) + } toggleFacets(toggledFacets) + updateEndpoint(nextResourceFilterState) }, - [toggleFacets, activeFacets] + [toggleFacets, activeFacets, endpoint, updateEndpoint, updateSort] ) - const facetMap = isResourceSearch(activeFacets) ? - RESOURCE_FACETS : - COURSE_FACETS + const facetMap = + endpoint === CONTENT_FILE_ENDPOINT ? RESOURCE_FACETS : COURSE_FACETS const pageSize = getPageSizeFromUIParam(ui) return (
@@ -245,7 +252,7 @@ export default function SearchPage(props: SearchPageProps) {
{!emptyOrNil(suggestions) ? ( @@ -280,10 +287,10 @@ export default function SearchPage(props: SearchPageProps) {
  • @@ -291,10 +298,10 @@ export default function SearchPage(props: SearchPageProps) {
  • @@ -311,17 +318,17 @@ export default function SearchPage(props: SearchPageProps) { ) : null}
  • - {!isResourceSearch(activeFacets) ? ( + {!(endpoint === CONTENT_FILE_ENDPOINT) ? (
  • Sort by{" "}
  • ) : null} @@ -386,10 +393,18 @@ export default function SearchPage(props: SearchPageProps) { ) : ( results.map((hit, idx) => ( )) diff --git a/www/assets/js/components/SearchResult.test.tsx b/www/assets/js/components/SearchResult.test.tsx index fbd94f120..7d34bbb48 100644 --- a/www/assets/js/components/SearchResult.test.tsx +++ b/www/assets/js/components/SearchResult.test.tsx @@ -1,15 +1,19 @@ import React from "react" import { mount } from "enzyme" -import { - serializeSearchParams, - LearningResourceType -} from "@mitodl/course-search-utils" +import { serializeSearchParams } from "@mitodl/course-search-utils" import SearchResult from "./SearchResult" -import { makeLearningResourceResult } from "../factories/search" +import { + makeCourseSearchResult, + makeContentFileSearchResult +} from "../factories/search" import { SEARCH_URL } from "../lib/constants" -import { getCoverImageUrl, searchResultToLearningResource } from "../lib/search" +import { + getCoverImageUrl, + courseSearchResultToLearningResource, + resourceSearchResultToLearningResource +} from "../lib/search" import { LearningResource } from "../LearningResources" describe("SearchResult component", () => { @@ -17,8 +21,8 @@ describe("SearchResult component", () => { mount() it("should render the things we expect for a course", () => { - const object = searchResultToLearningResource( - makeLearningResourceResult(LearningResourceType.Course) + const object = courseSearchResultToLearningResource( + makeCourseSearchResult() ) const wrapper = render(object) expect(wrapper.find(".course-title").text()).toBe(object.title) @@ -35,8 +39,8 @@ describe("SearchResult component", () => { }) it("should render the things we expect for a resource", () => { - const object = searchResultToLearningResource( - makeLearningResourceResult(LearningResourceType.ResourceFile) + const object = resourceSearchResultToLearningResource( + makeContentFileSearchResult() ) const wrapper = render(object) expect(wrapper.find(".course-title").text()).toBe(object.content_title) @@ -53,39 +57,38 @@ describe("SearchResult component", () => { expect(wrapper.find("CoverImage").exists()).toBeTruthy() }) - // - ;[(LearningResourceType.ResourceFile, LearningResourceType.Course)].forEach( - objectType => { - it("should not render a course/resource with no url", () => { - const object = searchResultToLearningResource( - makeLearningResourceResult(objectType) - ) - object.url = null - const wrapper = render(object) - expect(wrapper.find("Card").exists()).toBeFalsy() - }) - } - ) + it("should not render a course with no url", () => { + const object = courseSearchResultToLearningResource( + makeCourseSearchResult() + ) + object.url = null + const wrapper = render(object) + expect(wrapper.find("Card").exists()).toBeFalsy() + }) + + it("should not render a resource with no url", () => { + const object = resourceSearchResultToLearningResource( + makeContentFileSearchResult() + ) + object.url = null + const wrapper = render(object) + expect(wrapper.find("Card").exists()).toBeFalsy() + }) // - ;[[], null].forEach(listValue => { - it(`should not render div for instructors, topics if they are ${JSON.stringify( - listValue - )}`, () => { - const result = makeLearningResourceResult(LearningResourceType.Course) - // @ts-expect-error Consider widening type of Course - result.runs[0].instructors = listValue - // @ts-expect-error Consider widening type of Course - result.topics = listValue - const object = searchResultToLearningResource(result) - const wrapper = render(object) - expect(wrapper.find(".subtitles")).toHaveLength(0) - }) + it(`should not render div for instructors, topics if they are empty`, () => { + const result = makeCourseSearchResult() + // @ts-expect-error the default factory makes non-empty runs + result.runs[0].instructors = [] + result.topics = [] + const object = courseSearchResultToLearningResource(result) + const wrapper = render(object) + expect(wrapper.find(".subtitles")).toHaveLength(0) }) it("should link to the course subjects", () => { - const object = searchResultToLearningResource( - makeLearningResourceResult(LearningResourceType.Course) + const object = courseSearchResultToLearningResource( + makeCourseSearchResult() ) const wrapper = render(object) @@ -95,7 +98,7 @@ describe("SearchResult component", () => { `${SEARCH_URL}?${serializeSearchParams({ text: undefined, activeFacets: { - topics: [object.topics[i].name] + topic: [object.topics[i].name] } })}` ) @@ -108,8 +111,8 @@ describe("SearchResult component with compact view", () => { mount() it("should render the things we expect for a course", () => { - const object = searchResultToLearningResource( - makeLearningResourceResult(LearningResourceType.Course) + const object = courseSearchResultToLearningResource( + makeCourseSearchResult() ) const wrapper = render(object) expect(wrapper.find(".course-title").text()).toBe(object.title) @@ -122,10 +125,11 @@ describe("SearchResult component with compact view", () => { }) it("should render the things we expect for a resource", () => { - const object = searchResultToLearningResource( - makeLearningResourceResult(LearningResourceType.ResourceFile) + const object = resourceSearchResultToLearningResource( + makeContentFileSearchResult() ) const wrapper = render(object) + expect(wrapper.find(".resource-title").text()).toBe(object.content_title) expect(wrapper.find(".resource-title").find("a").prop("href")).toBe( object.url diff --git a/www/assets/js/components/SearchResult.tsx b/www/assets/js/components/SearchResult.tsx index 15de1e213..926b4376c 100644 --- a/www/assets/js/components/SearchResult.tsx +++ b/www/assets/js/components/SearchResult.tsx @@ -55,7 +55,7 @@ const Topics = ({ maxTags: number moreUrl?: string | null }) => { - if (!emptyOrNil(object.topics)) { + if (object.topics && !emptyOrNil(object.topics)) { return (
    diff --git a/www/assets/js/factories/search.ts b/www/assets/js/factories/search.ts index ac1c68905..f18cac24b 100644 --- a/www/assets/js/factories/search.ts +++ b/www/assets/js/factories/search.ts @@ -1,32 +1,13 @@ /* eslint-disable camelcase */ -import { LearningResourceType } from "@mitodl/course-search-utils" import casual from "casual-browserify" -import { times } from "ramda" -import { - CourseResult, - CourseRun, - PodcastResult, - PodcastEpisodeResult, - ResourceFileResult, - VideoResult, - LearningResourceResult, - CourseJSON, - ResourceJSON -} from "../LearningResources" +import { CourseJSON, ResourceJSON } from "../LearningResources" -import { - OCW_PLATFORM, - DATE_FORMAT, - COURSE_ARCHIVED, - COURSE_CURRENT, - OPEN_CONTENT, - PROFESSIONAL, - CERTIFICATE, - CONTENT_TYPE_VIDEO, - CONTENT_TYPE_PDF, - CONTENT_TYPE_PAGE, - RESOURCE_TYPE -} from "../lib/constants" +import type { + CourseResource as CourseSearchResult, + ContentFile as ContentFileSearchResult +} from "@mitodl/course-search-utils" + +import { DATE_FORMAT, RESOURCE_TYPE } from "../lib/constants" export function* incrementer() { let int = 1 @@ -48,111 +29,168 @@ export function makeFakeCourseName(): string { return casual.title } -const incrRun = incrementer() +export const makeCourseSearchResult = (): CourseSearchResult => ({ + id: casual.integer(1, 1000), + professional: casual.boolean, + certification: false, + departments: [ + { + department_id: casual.word, + name: casual.word + } + ], + resource_type: "course", + description: casual.description, + image: { + id: casual.integer(1, 1000), + url: casual.url, + description: casual.description, + alt: casual.description + }, + offered_by: { + code: "ocw", + name: "OCW" + }, + platform: { + code: "ocw", + name: "OCW" + }, + prices: null, + readable_id: `course+${String(casual.random)}`, + course_feature: [casual.word, casual.word], + runs: [ + { + id: casual.integer(1, 1000), + instructors: [...Array(4)].map(() => { + const first_name = casual.first_name + const last_name = casual.last_name -export function makeLearningResourceResult( - objectType: LearningResourceType.Course -): CourseResult -export function makeLearningResourceResult( - objectType: LearningResourceType.Video -): VideoResult -export function makeLearningResourceResult( - objectType: LearningResourceType.Podcast -): PodcastResult -export function makeLearningResourceResult( - objectType: LearningResourceType.PodcastEpisode -): PodcastEpisodeResult -export function makeLearningResourceResult( - objectType: LearningResourceType.ResourceFile -): ResourceFileResult -export function makeLearningResourceResult( - objectType: LearningResourceType -): LearningResourceResult -export function makeLearningResourceResult( - objectType: LearningResourceType -): LearningResourceResult { - switch (objectType) { - case LearningResourceType.Course: - return makeCourseResult() - case LearningResourceType.Video: - return makeVideoResult() - case LearningResourceType.Podcast: - return makePodcastResult() - case LearningResourceType.PodcastEpisode: - return makePodcastEpisodeResult() - default: - return makeResourceFileResult() - } -} + return { + id: casual.integer(1, 1000), + first_name, + last_name, + full_name: `Prof. ${first_name}, ${last_name}` + } + }), + image: { + id: casual.integer(1, 1000), + url: casual.url, + description: casual.description, + alt: casual.description + }, + published: true, + slug: `courses/+${String(casual.word)}`, + run_id: casual.word, + title: casual.word, + start_date: casual.date(DATE_FORMAT), + end_date: casual.date(DATE_FORMAT), + level: casual.random_element([ + [ + { + code: "graduate", + name: "Graduate" + } + ], + [ + { + code: "undergraduate", + name: "Undergraduate" + } + ], + [ + { + code: "graduate", + name: "Graduate" + }, + { + code: "undergraduate", + name: "Undergraduate" + } + ], + [], + null + ]) + } + ], + published: true, + title: casual.title, + topics: [{ id: casual.integer(1, 1000), name: casual.word }], + learning_path_parents: [], + user_list_parents: [], + course: { + course_numbers: casual.random_element([ + [ + { + listing_type: "primary", + department: { + department_id: casual.word, + name: casual.word + }, + value: casual.word + } + ], -export const makeRun = (): CourseRun => { - return { - run_id: `courserun_${incrRun.next().value}`, - id: incrRun.next().value!, - url: casual.url, - image_src: "http://image.medium.url", - short_description: casual.description, - language: casual.random_element(["en-US", "fr", null]), - semester: casual.random_element(["Fall", "Spring", null]), - year: casual.year, - level: casual.random_element([ - ["Graduate"], - ["Undergraduate"], - ["Graduate", "Undergraduate"], - [], - null - ]), - start_date: casual.date(DATE_FORMAT), - end_date: casual.date(DATE_FORMAT), - best_start_date: casual.date(DATE_FORMAT), - best_end_date: casual.date(DATE_FORMAT), - enrollment_start: casual.date(DATE_FORMAT), - enrollment_end: casual.date(DATE_FORMAT), - slug: casual.word, - availability: casual.random_element([ - COURSE_ARCHIVED, - COURSE_CURRENT, - "Upcoming" - ]), - instructors: [ - `${casual.name} ${casual.name}`, - `${casual.name} ${casual.name}` - ], - prices: [{ mode: "audit", price: casual.integer(1, 1000) }], - published: true, - // this is here to make typescript happy - short_url: undefined + [ + { + listing_type: "primary", + department: { + department_id: casual.word, + name: casual.word + }, + value: casual.word + }, + { + listing_type: "cross-listed", + department: { + department_id: casual.word, + name: casual.word + }, + value: casual.word + } + ] + ]) } -} +}) -export const makeCourseResult = (): CourseResult => ({ - id: casual.integer(1, 1000), - course_id: `course+${String(casual.random)}`, - coursenum: String(casual.random), - title: casual.title, - url: casual.url, - image_src: "http://image.medium.url", - short_description: casual.description, - platform: OCW_PLATFORM, - offered_by: [OCW_PLATFORM], - topics: [casual.word, casual.word], - object_type: LearningResourceType.Course, - runs: times(makeRun, 3), - audience: casual.random_element([ - [], - [OPEN_CONTENT], - [PROFESSIONAL], - [OPEN_CONTENT, PROFESSIONAL] - ]), - certification: casual.random_element([[], [CERTIFICATE]]), - course_feature_tags: [casual.word, casual.word], - department: casual.word, - // this is here to make typescript happy - content_title: undefined, - run_title: undefined, - run_slug: undefined, - content_type: undefined, - short_url: undefined +export const makeContentFileSearchResult = (): ContentFileSearchResult => ({ + content_author: "", + year: casual.integer(2000, 2024), + description: casual.description, + title: casual.title, + content_feature_type: [casual.word, casual.word], + content: casual.description, + platform: { + code: "ocw", + name: "OCW" + }, + content_language: "", + run_readable_id: `course+${String(casual.random)}`, + content_title: casual.title, + resource_readable_id: `course+${String(casual.random)}`, + uid: null, + content_type: "page", + file_type: null, + id: casual.integer(1, 1000), + departments: [ + { + department_id: casual.word, + name: casual.word + } + ], + key: casual.word, + run_id: casual.integer(1, 1000), + run_title: casual.title, + course_number: `course+${String(casual.random)}`, + topics: [{ id: casual.integer(1, 1000), name: casual.word }], + offered_by: { + code: "mitx", + name: "MITx" + }, + image_src: casual.url, + url: casual.url, + resource_id: casual.word, + semester: "", + run_slug: "" }) export const makeCourseJSON = (): CourseJSON => ({ @@ -216,119 +254,3 @@ export const makeResourceJSON = (): ResourceJSON => ({ transcript_file: casual.string, archive_url: casual.string }) - -export const makeResourceFileResult = (): ResourceFileResult => ({ - id: casual.integer(1, 1000), - course_id: `course_${String(casual.random)}`, - coursenum: String(casual.random), - title: casual.title, - url: casual.url, - image_src: "http://image.medium.url", - topics: [casual.word, casual.word], - object_type: LearningResourceType.ResourceFile, - content_title: casual.title, - run_title: casual.title, - run_slug: `/slug_${String(casual.word)}`, - content_type: casual.random_element([ - CONTENT_TYPE_VIDEO, - CONTENT_TYPE_PDF, - CONTENT_TYPE_PAGE - ]), - short_url: `/short_${String(casual.word)}`, - short_description: casual.short_description, - // this is here to make typescript happy - runs: undefined, - audience: undefined, - certification: undefined, - department: undefined, - course_feature_tags: undefined -}) - -export const makeVideoResult = (): VideoResult => ({ - id: casual.integer(1, 1000), - video_id: `video_${String(casual.random)}`, - title: casual.title, - url: casual.url, - image_src: "http://image.medium.url", - short_description: casual.description, - topics: [casual.word, casual.word], - object_type: LearningResourceType.Video, - offered_by: [OCW_PLATFORM], - runs: [], - audience: casual.random_element([ - [], - [OPEN_CONTENT], - [PROFESSIONAL], - [OPEN_CONTENT, PROFESSIONAL] - ]), - certification: casual.random_element([[], [CERTIFICATE]]), - // typescript! - department: undefined, - content_title: undefined, - run_title: undefined, - run_slug: undefined, - coursenum: undefined, - course_id: undefined, - content_type: undefined, - course_feature_tags: undefined, - short_url: undefined -}) - -export const makePodcastResult = (): PodcastResult => ({ - id: casual.integer(1, 1000), - podcast_id: `podcast_${String(casual.random)}`, - title: casual.title, - url: casual.url, - image_src: "http://image.medium.url", - short_description: casual.description, - topics: [casual.word, casual.word], - object_type: LearningResourceType.Podcast, - offered_by: [OCW_PLATFORM], - runs: [], - audience: casual.random_element([ - [], - [OPEN_CONTENT], - [PROFESSIONAL], - [OPEN_CONTENT, PROFESSIONAL] - ]), - certification: casual.random_element([[], [CERTIFICATE]]), - department: undefined, - content_title: undefined, - run_title: undefined, - run_slug: undefined, - coursenum: undefined, - course_id: undefined, - content_type: undefined, - course_feature_tags: undefined, - short_url: undefined -}) - -export const makePodcastEpisodeResult = (): PodcastEpisodeResult => ({ - id: casual.integer(1, 1000), - podcast_id: `podcastepisode_${String(casual.random)}`, - title: casual.title, - url: casual.url, - image_src: "http://image.medium.url", - short_description: casual.description, - topics: [casual.word, casual.word], - object_type: LearningResourceType.PodcastEpisode, - offered_by: [OCW_PLATFORM], - runs: [], - series_title: `podcast_${String(casual.random)}`, - audience: casual.random_element([ - [], - [OPEN_CONTENT], - [PROFESSIONAL], - [OPEN_CONTENT, PROFESSIONAL] - ]), - certification: casual.random_element([[], [CERTIFICATE]]), - department: undefined, - content_title: undefined, - run_title: undefined, - run_slug: undefined, - coursenum: undefined, - course_id: undefined, - content_type: undefined, - course_feature_tags: undefined, - short_url: undefined -}) diff --git a/www/assets/js/lib/api.test.ts b/www/assets/js/lib/api.test.ts index fb4126a3e..00c15eb02 100644 --- a/www/assets/js/lib/api.test.ts +++ b/www/assets/js/lib/api.test.ts @@ -3,8 +3,8 @@ import { FetchMock } from "jest-fetch-mock" jest.mock("@mitodl/course-search-utils", () => ({ ...jest.requireActual("@mitodl/course-search-utils"), - __esModule: true, - buildSearchQuery: jest.fn(params => ({ searchFor: params })) + __esModule: true, + buildSearchUrl: jest.fn(() => "www.asearchurl.com/with-params") })) const mockFetch = fetch as FetchMock @@ -18,14 +18,9 @@ describe("API module", () => { mockFetch.mockResponse(JSON.stringify({})) search({ text: "my text!" }) expect(mockFetch.mock.calls[0]).toEqual([ - process.env.SEARCH_API_URL, + "www.asearchurl.com/with-params", { - method: "POST", - body: JSON.stringify({ - searchFor: { - text: "my text!" - } - }), + method: "GET", headers: new Headers({ "Content-Type": "application/json", Accept: "application/json" diff --git a/www/assets/js/lib/api.ts b/www/assets/js/lib/api.ts index 627fc7155..92feea94b 100644 --- a/www/assets/js/lib/api.ts +++ b/www/assets/js/lib/api.ts @@ -1,21 +1,23 @@ import { isApiSuccessful } from "./util" import { captureException } from "@sentry/browser" import { - buildSearchQuery, - SearchQueryParams + buildSearchUrl, + SearchQueryParams, + CONTENT_FILE_ENDPOINT } from "@mitodl/course-search-utils" export const search = async (params: SearchQueryParams) => { // for now // eslint-disable-next-line @typescript-eslint/no-explicit-any let results: any = {} - try { - const body = buildSearchQuery(params) - - const response = await fetch(process.env.SEARCH_API_URL, { - method: "POST", - body: JSON.stringify(body), + const baseUrl = + params.endpoint === CONTENT_FILE_ENDPOINT ? + process.env.CONTENT_FILE_SEARCH_API_URL : + process.env.COURSE_SEARCH_API_URL + const url = buildSearchUrl(baseUrl, params) + const response = await fetch(url, { + method: "GET", headers: new Headers({ "Content-Type": "application/json", Accept: "application/json" diff --git a/www/assets/js/lib/constants.ts b/www/assets/js/lib/constants.ts index 6b53ef7b6..8e7e0f558 100644 --- a/www/assets/js/lib/constants.ts +++ b/www/assets/js/lib/constants.ts @@ -1,5 +1,9 @@ -import { LearningResourceType, Facets } from "@mitodl/course-search-utils" -import departments from "../../../../base-theme/data/departments.json" +import { + LearningResourceType, + LEVELS, + DEPARTMENTS +} from "@mitodl/course-search-utils" +import type { Facets } from "@mitodl/course-search-utils" export const CONTENT_TYPE_PDF = "pdf" export const CONTENT_TYPE_PAGE = "page" export const CONTENT_TYPE_VIDEO = LearningResourceType.Video @@ -14,7 +18,7 @@ export type ContentType = | typeof CONTENT_TYPE_PAGE | typeof CONTENT_TYPE_VIDEO -export const OCW_PLATFORM = "OCW" +export const OCW_OFFEROR = "ocw" export const OPEN_CONTENT = "Open Content" export const PROFESSIONAL = "Professional Offerings" @@ -30,23 +34,13 @@ export const COURSE_PRIOR = "Prior" export const DATE_FORMAT = "YYYY-MM-DD[T]HH:mm:ss[Z]" -const ocwPlatform = "ocw" - export const SEARCH_COMPACT_UI = "compact" export const DEFAULT_UI_PAGE_SIZE = 10 export const COMPACT_UI_PAGE_SIZE = 50 export const platforms = { - OCW: ocwPlatform -} - -export const readableLearningResources = { - [LearningResourceType.Course]: "Course", - [LearningResourceType.Video]: "Video", - [LearningResourceType.Podcast]: "Podcast", - [LearningResourceType.PodcastEpisode]: "Podcast Episode", - [LearningResourceType.ResourceFile]: "Course Resource" + OCW: OCW_OFFEROR } export enum RESOURCE_TYPE { @@ -70,8 +64,6 @@ export const PHONE_WIDTH = 599 // Based on desktop-wide breakpoint export const TABLET_WIDTH = 999 -export const COURSENUM_SORT_FIELD = "department_course_numbers.sort_coursenum" - export const STATUS_CODES = { HTTP_200_OK: 200, HTTP_300_MULTIPLE_CHOICES: 300 @@ -129,7 +121,7 @@ const RESOURCE_TYPES = [ ] export const FACET_OPTIONS: Facets = { - topics: [ + topic: [ "Academic Writing", "Accounting", "Aerodynamics", @@ -464,11 +456,9 @@ export const FACET_OPTIONS: Facets = { "Women's Studies", "World History" ], - type: ["resourcefile", "course"], - department_name: Object.values(departments).map( - department => department.title - ), - level: ["Undergraduate", "Graduate", "Non-Credit", "High School"], - course_feature_tags: RESOURCE_TYPES, - resource_type: RESOURCE_TYPES + resource_type: ["course"], + department: Object.keys(DEPARTMENTS), + level: Object.keys(LEVELS), + course_feature: RESOURCE_TYPES, + content_feature_type: RESOURCE_TYPES } diff --git a/www/assets/js/lib/search.test.tsx b/www/assets/js/lib/search.test.tsx index baef5d5da..5807c011a 100644 --- a/www/assets/js/lib/search.test.tsx +++ b/www/assets/js/lib/search.test.tsx @@ -1,4 +1,3 @@ -import { LearningResourceType } from "@mitodl/course-search-utils" import sinon from "sinon" import { @@ -10,12 +9,16 @@ import { import { getCoverImageUrl, getResourceUrl, - getResultUrl, + getCourseUrl, getSectionUrl, courseJSONToLearningResource, - searchResultToLearningResource + courseSearchResultToLearningResource } from "./search" -import { makeLearningResourceResult, makeCourseJSON } from "../factories/search" +import { + makeCourseSearchResult, + makeContentFileSearchResult, + makeCourseJSON +} from "../factories/search" describe("search library", () => { const sandbox = sinon.createSandbox() @@ -45,9 +48,7 @@ describe("search library", () => { }) it("getCoverImageUrl should return RESOURCE_BASE_URL+image_src when set", () => { - const lr = searchResultToLearningResource( - makeLearningResourceResult(LearningResourceType.Course) - ) + const lr = courseSearchResultToLearningResource(makeCourseSearchResult()) expect(getCoverImageUrl({ ...lr, image_src: "/images/foobar.jpeg" })).toBe( "http://resources-galore.example.com/images/foobar.jpeg" ) @@ -56,99 +57,69 @@ describe("search library", () => { // ;( [ + ["", "/courses/18-23/mech_engineering/", null, "/courses/run-slug/"], [ - CONTENT_TYPE_PAGE, - "/courses/18-23/mech_engineering/", null, - "/courses/run-slug/" - ], - [ - CONTENT_TYPE_PAGE, "/courses/18-23/mech_engineering/", "https://cdn.example.com", "/courses/run-slug/" ], [ - CONTENT_TYPE_PDF, + "application/pdf", "https://s3.amazonaws.com/18-23/test.pdf", null, "https://s3.amazonaws.com/18-23/test.pdf" ], [ - CONTENT_TYPE_PDF, + "application/pdf", "https://s3.amazonaws.com/18-23/test.pdf", "https://cdn.example.com", "https://cdn.example.com/18-23/test.pdf" ], [ - CONTENT_TYPE_VIDEO, + "video/mp4", "https://youtube.com/?s=2335", "", "https://youtube.com/?s=2335" ], [ - CONTENT_TYPE_VIDEO, + "video/quicktime", "https://youtube.com/?s=2335", "/coursemedia", "https://youtube.com/?s=2335" ], - [CONTENT_TYPE_VIDEO, "/relative/url", false, "/relative/url"], - [CONTENT_TYPE_VIDEO, "/relative/url", "/coursemedia", "/relative/url"] + ["video/x-msvideo", "/relative/url", false, "/relative/url"], + ["video/x-ms-wmv", "/relative/url", "/coursemedia", "/relative/url"] ] as const - ).forEach(([contentType, url, cdnPrefix, expectedUrl]) => { - it(`should return correct url for content type ${contentType} if the cdn is ${ + ).forEach(([fileType, url, cdnPrefix, expectedUrl]) => { + it(`should return correct url for file type ${fileType} if the cdn is ${ cdnPrefix ? "" : "not " }set`, () => { // @ts-expect-error See note in test-setup process.env["CDN_PREFIX"] = cdnPrefix const result = { - ...makeLearningResourceResult(LearningResourceType.ResourceFile), + ...makeContentFileSearchResult(), url, - run_slug: "run-slug", - content_type: contentType + run_slug: "run-slug", + file_type: fileType } expect(getResourceUrl(result)).toBe(expectedUrl) }) }) - // - ;( - [LearningResourceType.Course, LearningResourceType.ResourceFile] as const - ).forEach(objectType => { - it(`should return correct url for object type ${objectType}`, () => { - const result = makeLearningResourceResult(objectType) - const isCourse = result.object_type === LearningResourceType.Course - if (!isCourse) { - result.content_type = CONTENT_TYPE_PAGE - } else { - result.runs[0].best_start_date = "2001-11-11" - result.runs[1].published = false - result.runs[2].best_start_date = "2002-01-01" - } - const expected = isCourse ? - `/courses/${result.runs[2].slug}/` : - `/courses/${result.run_slug}${getSectionUrl(result)}` - expect(getResultUrl(result)).toBe(expected) - }) - }) - it(`should return a null url for a course without runs`, () => { - const result = makeLearningResourceResult(LearningResourceType.Course) + const result = makeCourseSearchResult() result.runs = [] - expect(getResultUrl(result)).toBe(null) - // @ts-expect-error TODO: Consider updating the types to include this possibility. + expect(getCourseUrl(result)).toBe(null) result.runs = null - expect(getResultUrl(result)).toBe(null) - // @ts-expect-error TODO: Consider updating the types to include this possibility. - delete result.runs - expect(getResultUrl(result)).toBe(null) + expect(getCourseUrl(result)).toBe(null) }) describe("getSectionUrl", () => { it("returns a / for a course site", () => { const result = { - ...makeLearningResourceResult(LearningResourceType.Course), + ...makeContentFileSearchResult(), url: "/courses/course-id/other-course-part/" } expect(getSectionUrl(result)).toBe("/") @@ -156,7 +127,7 @@ describe("search library", () => { it("handles a legacy prefix gracefully", () => { const result = { - ...makeLearningResourceResult(LearningResourceType.Course), + ...makeContentFileSearchResult(), url: "http://ocw.mit.edu/resources/a/resource" } expect(getSectionUrl(result)).toBe("/") @@ -166,7 +137,7 @@ describe("search library", () => { ;["index.htm", "index.html"].forEach(suffix => { it(`removes a ${suffix} from the path`, () => { const result = { - ...makeLearningResourceResult(LearningResourceType.Course), + ...makeContentFileSearchResult(), url: `/courses/course-id/other-piece/${suffix}` } expect(getSectionUrl(result)).toBe("/") @@ -175,7 +146,7 @@ describe("search library", () => { it("adds a /pages if it is pointing to a section within a course url", () => { const result = { - ...makeLearningResourceResult(LearningResourceType.Course), + ...makeContentFileSearchResult(), url: "/courses/course-id/other-part/path/to/a/pdf" } expect(getSectionUrl(result)).toBe("/pages/path/to/a/pdf") diff --git a/www/assets/js/lib/search.ts b/www/assets/js/lib/search.ts index 038b5032f..8e571634a 100644 --- a/www/assets/js/lib/search.ts +++ b/www/assets/js/lib/search.ts @@ -2,21 +2,26 @@ import { emptyOrNil } from "./util" import { - OCW_PLATFORM, + OCW_OFFEROR, CONTENT_TYPE_PAGE, CONTENT_TYPE_PDF, CONTENT_TYPE_VIDEO, ContentType } from "./constants" import { LearningResourceType } from "@mitodl/course-search-utils" -import { - CourseResult, - CourseRun, +import type { CourseJSON, LearningResource, - LearningResourceResult, - ResourceJSON + ResourceJSON, + Level } from "../LearningResources" +import type { + CourseResource as CourseSearchResult, + ContentFile as ContentFileSearchResult, + LearningResourceTopic, + CourseNumber, + LearningResourceRun +} from "@mitodl/course-search-utils" const formatCourseJSONTopics = (courseJSON: CourseJSON) => courseJSON.topics ? @@ -33,7 +38,7 @@ export const courseJSONToLearningResource = ( title: courseData.course_title, image_src: courseData.image_src, object_type: LearningResourceType.Course, - platform: OCW_PLATFORM, + platform: OCW_OFFEROR, topics: formatCourseJSONTopics(courseData), runs: [], level: courseData.level, @@ -82,7 +87,7 @@ export const resourceJSONToLearningResource = ( title: resourceJSON.title, image_src: "", object_type: LearningResourceType.ResourceFile, - platform: OCW_PLATFORM, + platform: OCW_OFFEROR, topics: formatCourseJSONTopics(courseJSON), runs: [], level: null, @@ -103,33 +108,60 @@ export const resourceJSONToLearningResource = ( } } -export const searchResultToLearningResource = ( - result: LearningResourceResult +export const courseSearchResultToLearningResource = ( + result: CourseSearchResult +): LearningResource => ({ + id: result.id, + title: result.title, + image_src: result.image ? result.image.url : "", + object_type: result.resource_type, + platform: result.platform ? result.platform.name : null, + topics: result.topics ? + result.topics.map((topic: LearningResourceTopic) => ({ + name: topic.name as string + })) : + [], + level: !emptyOrNil(result.runs) ? + (result.runs![0]?.level || []).map(level => level.name as Level) : + [], + instructors: !emptyOrNil(result.runs) ? + (result.runs![0]?.instructors || []).flatMap(instructor => + instructor.full_name ? instructor.full_name : [] + ) : + [], + url: getCourseUrl(result) || null, + course_id: result.readable_id || null, + coursenum: + result.course && result.course.course_numbers ? + result.course.course_numbers.find( + (course_number: CourseNumber) => + course_number.listing_type === "primary" + )?.value : + null, + description: result.description || null, + course_feature_tags: result.course_feature ? result.course_feature : [] +}) + +export const resourceSearchResultToLearningResource = ( + result: ContentFileSearchResult ): LearningResource => ({ - id: result.id, - title: result.title, - image_src: result.image_src, - object_type: result.object_type, - platform: "platform" in result ? result.platform : null, - topics: result.topics ? result.topics.map(topic => ({ name: topic })) : [], - runs: "runs" in result ? (result.runs as CourseRun[]) : [], - level: !emptyOrNil(result.runs) ? result.runs![0]?.level : null, - instructors: !emptyOrNil(result.runs) ? result.runs![0]?.instructors : [], - department: result.department, - audience: result.audience, - certification: result.certification, + id: result.id, + title: result.title ? result.title : null, + image_src: result.image_src || "", + object_type: LearningResourceType.ResourceFile, + topics: result.topics ? + result.topics.map(topic => ({ name: topic.name })) : + [], content_title: result.content_title, - run_title: result.run_title || null, - run_slug: result.run_slug || null, - content_type: result.content_type || null, - url: getResultUrl(result) || null, - short_url: result.short_url || null, - course_id: result.course_id || null, - coursenum: result.coursenum || null, - description: result.short_description || null, - course_feature_tags: result.course_feature_tags ? - result.course_feature_tags : - [] + run_title: result.run_title, + content_type: fileTypeToContentType(result.file_type || ""), + url: getResourceUrl(result) || null, + coursenum: result.course_number ? result.course_number[0] : null, + description: result.description || null, + course_feature_tags: result.content_feature_type ? + result.content_feature_type : + [], + instructors: [] }) const IMAGE_URL_PREFIX = process.env["RESOURCE_BASE_URL"] || "" @@ -156,15 +188,18 @@ export const getCoverImageUrl = (result: LearningResource) => { } } -export const getCourseUrl = (result: CourseResult) => { +export const getCourseUrl = (result: CourseSearchResult) => { if (emptyOrNil(result.runs)) { return null } - const publishedRuns = result.runs.filter(run => run.published) + const publishedRuns = (result.runs || []).filter( + (run: LearningResourceRun) => run.published + ) if (!emptyOrNil(publishedRuns)) { - const publishedRun = publishedRuns.sort((a, b) => - a.best_start_date < b.best_start_date ? 1 : -1 - )[0].slug + const publishedRun = publishedRuns[0].slug + if (!publishedRun) { + return null + } // a temporary hack to handle runs possibly including the "courses" prefix return publishedRun.includes("courses/") ? `/${publishedRun}/` : @@ -172,8 +207,11 @@ export const getCourseUrl = (result: CourseResult) => { } else return null } -export const getSectionUrl = (result: LearningResourceResult) => { +export const getSectionUrl = (result: ContentFileSearchResult) => { let url = result.url + if (!url) { + return null + } // a small number of urls still start with the legacy site prefix const legacyPrefix = "http://ocw.mit.edu" if (url.startsWith(legacyPrefix)) { @@ -195,14 +233,11 @@ export const getSectionUrl = (result: LearningResourceResult) => { return `/pages/${urlPieces.join("/")}` } -export const getResourceUrl = (result: LearningResourceResult) => { - if ( - result.object_type === LearningResourceType.ResourceFile && - result.content_type === CONTENT_TYPE_PAGE - ) { +export const getResourceUrl = (result: ContentFileSearchResult) => { + if (fileTypeToContentType(result.file_type || "") === CONTENT_TYPE_PAGE) { // parse the url to get section pieces, then construct a new section url const sectionUrl = getSectionUrl(result) - const runSlug = result.run_slug + const runSlug = result.run_slug || "" // a temporary hack to handle runs possibly including the "courses" prefix return `${ runSlug.includes("courses/") ? "/" : "/courses/" @@ -210,7 +245,7 @@ export const getResourceUrl = (result: LearningResourceResult) => { } else { // Non-page results should have full URLs, convert to CDN if it's an S3 URL try { - const originalUrl = new URL(result.url) + const originalUrl = new URL(result.url || "") const cdnPrefix = process.env["CDN_PREFIX"] const useCDN = originalUrl.hostname.match(/s3\.amazonaws\.com/) && cdnPrefix @@ -220,7 +255,3 @@ export const getResourceUrl = (result: LearningResourceResult) => { } } } -export const getResultUrl = (result: LearningResourceResult) => - result.object_type === LearningResourceType.Course ? - getCourseUrl(result) : - getResourceUrl(result) diff --git a/yarn.lock b/yarn.lock index c81cc721e..af17a090b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -46,7 +46,7 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:^7.1.0, @babel/core@npm:^7.7.5": +"@babel/core@npm:^7.1.0": version: 7.14.0 resolution: "@babel/core@npm:7.14.0" dependencies: @@ -724,17 +724,6 @@ __metadata: languageName: node linkType: hard -"@dabh/diagnostics@npm:^2.0.2": - version: 2.0.2 - resolution: "@dabh/diagnostics@npm:2.0.2" - dependencies: - colorspace: 1.1.x - enabled: 2.0.x - kuler: ^2.0.0 - checksum: 4d95cc31249a840b6cc3dba3dc4345a9295265413456068a0d07b69fa0ec6a5a5bc2c39e56ec04c6509ac1f4d9c17fc80baaaddd5caa1abcdd3aaeffe2b63cec - languageName: node - linkType: hard - "@discoveryjs/json-ext@npm:0.5.7, @discoveryjs/json-ext@npm:^0.5.0": version: 0.5.7 resolution: "@discoveryjs/json-ext@npm:0.5.7" @@ -1238,25 +1227,11 @@ __metadata: languageName: node linkType: hard -"@mitodl/course-search-utils@npm:^1.1.4": - version: 1.8.0 - resolution: "@mitodl/course-search-utils@npm:1.8.0" - dependencies: - bodybuilder: ^2.5.0 - history: ^5.0.0 - query-string: ^6.13.1 - ramda: ^0.27.1 - peerDependencies: - react: ^16.13.1 - checksum: f20a26dc63f5934a11ba77f30624fa7b77f4e6f9bc93fa5640117b166e0016bdc3d3eaa71fad3fde634cd02cb155ef1db81b95091cde7d6478e94905f2e2f233 - languageName: node - linkType: hard - -"@mitodl/course-search-utils@npm:^2.3.0": - version: 2.3.2 - resolution: "@mitodl/course-search-utils@npm:2.3.2" +"@mitodl/course-search-utils@npm:^3.0.0": + version: 3.0.0 + resolution: "@mitodl/course-search-utils@npm:3.0.0" dependencies: - bodybuilder: ^2.5.0 + axios: ^1.6.7 query-string: ^6.13.1 ramda: ^0.27.1 peerDependencies: @@ -1268,34 +1243,7 @@ __metadata: optional: true history: optional: true - checksum: 9d6947520e501b31dd40f4674bec2be08d97423956e105b6334e602dd4e1eea0b075aedd43ebb1e341e6a155d761c8ca15d46722bdcfb581b948fc05b2571ba1 - languageName: node - linkType: hard - -"@mitodl/ocw-to-hugo@npm:1.36.0": - version: 1.36.0 - resolution: "@mitodl/ocw-to-hugo@npm:1.36.0" - dependencies: - "@mitodl/course-search-utils": ^1.1.4 - acorn: ^8.6.0 - acorn-walk: ^8.2.0 - aws-sdk: ^2.671.0 - cli-progress: ^3.4.0 - dotenv: ^8.2.0 - js-yaml: ^3.13.1 - markdown-doc-builder: ^1.3.0 - moment: ^2.29.1 - nyc: ^15.1.0 - string-strip-html: ^6.1.1 - title-case: ^3.0.2 - tmp: ^0.2.1 - turndown: ^7.0.0 - turndown-plugin-gfm: ^1.0.2 - winston: ^3.2.1 - yargs: ^15.0.2 - bin: - ocw-to-hugo: src/bin/index.js - checksum: 7fab9749a2b672e84edd10e05587458f70fc00a6cdf7b71d175cc86b7760d5c95a0552811e7f7a648a635f67f00b2dfa7b519f88a5e29c3b948afd0804f6ec82 + checksum: 14000a21b0b81f3d3023f53f7b2a8e417d7c815f2d2fa627cdc4406a8aff63f30c4fe79f7ecee6425cabd4fe8c5a468c6412682e9e3e021b599a1537972f793a languageName: node linkType: hard @@ -3132,7 +3080,7 @@ __metadata: languageName: node linkType: hard -"acorn-walk@npm:^8.0.0, acorn-walk@npm:^8.1.1, acorn-walk@npm:^8.2.0": +"acorn-walk@npm:^8.0.0, acorn-walk@npm:^8.1.1": version: 8.2.0 resolution: "acorn-walk@npm:8.2.0" checksum: 1715e76c01dd7b2d4ca472f9c58968516a4899378a63ad5b6c2d668bba8da21a71976c14ec5f5b75f887b6317c4ae0b897ab141c831d741dc76024d8745f1ad1 @@ -3202,15 +3150,6 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.6.0": - version: 8.6.0 - resolution: "acorn@npm:8.6.0" - bin: - acorn: bin/acorn - checksum: 9d0de73b73cb6ea8ccd8263a8144d9e2c4b6af90ea0c429997538af0ebbe83c5addecee814b2a7f91f7f615d0bd1547cc7137b3fa236ce058adc64feccee850b - languageName: node - linkType: hard - "acorn@npm:^8.7.1, acorn@npm:^8.8.2": version: 8.10.0 resolution: "acorn@npm:8.10.0" @@ -3501,15 +3440,6 @@ __metadata: languageName: node linkType: hard -"append-transform@npm:^2.0.0": - version: 2.0.0 - resolution: "append-transform@npm:2.0.0" - dependencies: - default-require-extensions: ^3.0.0 - checksum: f26f393bf7a428fd1bb18f2758a819830a582243310c5170edb3f98fdc5a535333d02b952f7c2d9b14522bd8ead5b132a0b15000eca18fa9f49172963ebbc231 - languageName: node - linkType: hard - "aproba@npm:^1.0.3 || ^2.0.0": version: 2.0.0 resolution: "aproba@npm:2.0.0" @@ -3524,13 +3454,6 @@ __metadata: languageName: node linkType: hard -"archy@npm:^1.0.0": - version: 1.0.0 - resolution: "archy@npm:1.0.0" - checksum: 504ae7af655130bab9f471343cfdb054feaec7d8e300e13348bc9fe9e660f83d422e473069584f73233c701ae37d1c8452ff2522f2a20c38849e0f406f1732ac - languageName: node - linkType: hard - "are-we-there-yet@npm:^3.0.0": version: 3.0.1 resolution: "are-we-there-yet@npm:3.0.1" @@ -3737,13 +3660,6 @@ __metadata: languageName: node linkType: hard -"async@npm:^3.1.0": - version: 3.2.0 - resolution: "async@npm:3.2.0" - checksum: 6739fae769e6c9f76b272558f118ef041d45c979c573a8fe93f8cfbc32eb9c92da032e9effe6bbcc9b1131292cde6c4a9e61a442894aa06a262addd8dd3adda1 - languageName: node - linkType: hard - "asynckit@npm:^0.4.0": version: 0.4.0 resolution: "asynckit@npm:0.4.0" @@ -3775,23 +3691,6 @@ __metadata: languageName: node linkType: hard -"aws-sdk@npm:^2.671.0": - version: 2.899.0 - resolution: "aws-sdk@npm:2.899.0" - dependencies: - buffer: 4.9.2 - events: 1.1.1 - ieee754: 1.1.13 - jmespath: 0.15.0 - querystring: 0.2.0 - sax: 1.2.1 - url: 0.10.3 - uuid: 3.3.2 - xml2js: 0.4.19 - checksum: b81ffc4726127ef0e64dd74d006e247d417ccd0f9f2565357be99172f3f4360a282d2e3e4e5a0114ae47b774875c4a8f10e660a4994bb588b0a9949bfa56dcc4 - languageName: node - linkType: hard - "aws-sign2@npm:~0.7.0": version: 0.7.0 resolution: "aws-sign2@npm:0.7.0" @@ -3806,6 +3705,17 @@ __metadata: languageName: node linkType: hard +"axios@npm:^1.6.7": + version: 1.6.7 + resolution: "axios@npm:1.6.7" + dependencies: + follow-redirects: ^1.15.4 + form-data: ^4.0.0 + proxy-from-env: ^1.1.0 + checksum: 87d4d429927d09942771f3b3a6c13580c183e31d7be0ee12f09be6d5655304996bb033d85e54be81606f4e89684df43be7bf52d14becb73a12727bf33298a082 + languageName: node + linkType: hard + "babel-eslint@npm:^10.1.0": version: 10.1.0 resolution: "babel-eslint@npm:10.1.0" @@ -3906,7 +3816,7 @@ __metadata: languageName: node linkType: hard -"base64-js@npm:^1.0.2, base64-js@npm:^1.3.1": +"base64-js@npm:^1.3.1": version: 1.5.1 resolution: "base64-js@npm:1.5.1" checksum: 669632eb3745404c2f822a18fc3a0122d2f9a7a13f7fb8b5823ee19d1d2ff9ee5b52c53367176ea4ad093c332fd5ab4bd0ebae5a8e27917a4105a4cfc86b1005 @@ -3995,20 +3905,6 @@ __metadata: languageName: node linkType: hard -"bodybuilder@npm:^2.5.0": - version: 2.5.0 - resolution: "bodybuilder@npm:2.5.0" - dependencies: - lodash.clonedeep: 4.5.0 - lodash.isobject: 3.0.2 - lodash.isplainobject: 4.0.6 - lodash.merge: 4.6.2 - lodash.set: 4.3.2 - lodash.unset: 4.5.2 - checksum: 1c39eb5d54cbb5f94e9788701416cb2d8bc072d10e8a85f9d112e626e8df100c78f7877993e40cbef9e2cd2bcc79ce6c3b010c41182e6bd2017f7fde6ce2b574 - languageName: node - linkType: hard - "bonjour-service@npm:^1.0.11": version: 1.0.14 resolution: "bonjour-service@npm:1.0.14" @@ -4156,17 +4052,6 @@ __metadata: languageName: node linkType: hard -"buffer@npm:4.9.2": - version: 4.9.2 - resolution: "buffer@npm:4.9.2" - dependencies: - base64-js: ^1.0.2 - ieee754: ^1.1.4 - isarray: ^1.0.0 - checksum: 8801bc1ba08539f3be70eee307a8b9db3d40f6afbfd3cf623ab7ef41dffff1d0a31de0addbe1e66e0ca5f7193eeb667bfb1ecad3647f8f1b0750de07c13295c3 - languageName: node - linkType: hard - "buffer@npm:^5.2.1, buffer@npm:^5.5.0": version: 5.7.1 resolution: "buffer@npm:5.7.1" @@ -4239,18 +4124,6 @@ __metadata: languageName: node linkType: hard -"caching-transform@npm:^4.0.0": - version: 4.0.0 - resolution: "caching-transform@npm:4.0.0" - dependencies: - hasha: ^5.0.0 - make-dir: ^3.0.0 - package-hash: ^4.0.0 - write-file-atomic: ^3.0.0 - checksum: c4db6939533b677866808de67c32f0aaf8bf4fd3e3b8dc957e5d630c007c06b7f11512d44c38a38287fb068e931067e8da9019c34d787259a44121c9a6b87a1f - languageName: node - linkType: hard - "call-bind@npm:^1.0.0, call-bind@npm:^1.0.2": version: 1.0.2 resolution: "call-bind@npm:1.0.2" @@ -4608,16 +4481,6 @@ __metadata: languageName: node linkType: hard -"cli-progress@npm:^3.4.0": - version: 3.9.0 - resolution: "cli-progress@npm:3.9.0" - dependencies: - colors: ^1.1.2 - string-width: ^4.2.0 - checksum: 96f3a8a9caac61dbe05d0fa3cc44fa74b0dccee0da7e4841abc126dae605af82e4da1f3f00b48587cfed3efd45e285a9cc89fcbab92986288ea3605c2d58e16b - languageName: node - linkType: hard - "cli-spinners@npm:^2.5.0": version: 2.7.0 resolution: "cli-spinners@npm:2.7.0" @@ -4663,17 +4526,6 @@ __metadata: languageName: node linkType: hard -"cliui@npm:^6.0.0": - version: 6.0.0 - resolution: "cliui@npm:6.0.0" - dependencies: - string-width: ^4.2.0 - strip-ansi: ^6.0.0 - wrap-ansi: ^6.2.0 - checksum: 4fcfd26d292c9f00238117f39fc797608292ae36bac2168cfee4c85923817d0607fe21b3329a8621e01aedf512c99b7eaa60e363a671ffd378df6649fb48ae42 - languageName: node - linkType: hard - "cliui@npm:^7.0.2": version: 7.0.4 resolution: "cliui@npm:7.0.4" @@ -4733,7 +4585,7 @@ __metadata: languageName: node linkType: hard -"color-convert@npm:^1.9.0, color-convert@npm:^1.9.1": +"color-convert@npm:^1.9.0": version: 1.9.3 resolution: "color-convert@npm:1.9.3" dependencies: @@ -4758,23 +4610,13 @@ __metadata: languageName: node linkType: hard -"color-name@npm:^1.0.0, color-name@npm:~1.1.4": +"color-name@npm:~1.1.4": version: 1.1.4 resolution: "color-name@npm:1.1.4" checksum: b0445859521eb4021cd0fb0cc1a75cecf67fceecae89b63f62b201cca8d345baf8b952c966862a9d9a2632987d4f6581f0ec8d957dfacece86f0a7919316f610 languageName: node linkType: hard -"color-string@npm:^1.5.2": - version: 1.5.5 - resolution: "color-string@npm:1.5.5" - dependencies: - color-name: ^1.0.0 - simple-swizzle: ^0.2.2 - checksum: 4f19c2042c8953973a3c71a53e53da9fa54194cc1e0270bdbe431b14476b3faed054eb1c960910a8c2b631e7c67daccf79f8579eaa2d16dc99c3739c66f98ab1 - languageName: node - linkType: hard - "color-support@npm:^1.1.3": version: 1.1.3 resolution: "color-support@npm:1.1.3" @@ -4784,16 +4626,6 @@ __metadata: languageName: node linkType: hard -"color@npm:3.0.x": - version: 3.0.0 - resolution: "color@npm:3.0.0" - dependencies: - color-convert: ^1.9.1 - color-string: ^1.5.2 - checksum: 273fe5d4c2a322dbc0e184ef6c891cbefefa11af7c6a8ed6001ff6986af747038cf3258bd4f4b715415f287c556efc8d1f0368bc02240877610ae1d427887537 - languageName: node - linkType: hard - "colord@npm:^2.9.1": version: 2.9.2 resolution: "colord@npm:2.9.2" @@ -4815,23 +4647,6 @@ __metadata: languageName: node linkType: hard -"colors@npm:^1.1.2, colors@npm:^1.2.1": - version: 1.4.0 - resolution: "colors@npm:1.4.0" - checksum: 98aa2c2418ad87dedf25d781be69dc5fc5908e279d9d30c34d8b702e586a0474605b3a189511482b9d5ed0d20c867515d22749537f7bc546256c6014f3ebdcec - languageName: node - linkType: hard - -"colorspace@npm:1.1.x": - version: 1.1.2 - resolution: "colorspace@npm:1.1.2" - dependencies: - color: 3.0.x - text-hex: 1.0.x - checksum: a959ec1669176aa72185067b7d04dae1cef2698456e1a452a035ce8adcac95673fbb1547e3240903355bcbaa67e031cca0b8b4f7d42c256b3dd94dcead8e1405 - languageName: node - linkType: hard - "combined-stream@npm:^1.0.6, combined-stream@npm:^1.0.8, combined-stream@npm:~1.0.6": version: 1.0.8 resolution: "combined-stream@npm:1.0.8" @@ -4876,13 +4691,6 @@ __metadata: languageName: node linkType: hard -"commondir@npm:^1.0.1": - version: 1.0.1 - resolution: "commondir@npm:1.0.1" - checksum: 59715f2fc456a73f68826285718503340b9f0dd89bfffc42749906c5cf3d4277ef11ef1cca0350d0e79204f00f1f6d83851ececc9095dc88512a697ac0b9bdcb - languageName: node - linkType: hard - "compressible@npm:~2.0.16": version: 2.0.18 resolution: "compressible@npm:2.0.18" @@ -5101,7 +4909,7 @@ __metadata: languageName: node linkType: hard -"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3": +"cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3": version: 7.0.3 resolution: "cross-spawn@npm:7.0.3" dependencies: @@ -5538,15 +5346,6 @@ __metadata: languageName: node linkType: hard -"default-require-extensions@npm:^3.0.0": - version: 3.0.0 - resolution: "default-require-extensions@npm:3.0.0" - dependencies: - strip-bom: ^4.0.0 - checksum: 0b5bdb6786ebb0ff6ef55386f37c8d221963fbbd3009588fe71032c85ca16da05eff2ad01bfe9bfc8bac5ce95a18f66b38c50d454482e3e9d2de1142424a3e7c - languageName: node - linkType: hard - "defaults@npm:^1.0.3": version: 1.0.4 resolution: "defaults@npm:1.0.4" @@ -5819,13 +5618,6 @@ __metadata: languageName: node linkType: hard -"domino@npm:^2.1.6": - version: 2.1.6 - resolution: "domino@npm:2.1.6" - checksum: 9b1b6d2661efd8bf942b70d5e11ac0de6a63f17e49b7eb227d9a612fa7b7c12b7775520d64f498988a8ee334ea9c59a463c84ea510b0af17dd3e13fdce120410 - languageName: node - linkType: hard - "domutils@npm:^2.5.2, domutils@npm:^2.6.0": version: 2.6.0 resolution: "domutils@npm:2.6.0" @@ -5955,13 +5747,6 @@ __metadata: languageName: node linkType: hard -"enabled@npm:2.0.x": - version: 2.0.0 - resolution: "enabled@npm:2.0.0" - checksum: 9d256d89f4e8a46ff988c6a79b22fa814b4ffd82826c4fdacd9b42e9b9465709d3b748866d0ab4d442dfc6002d81de7f7b384146ccd1681f6a7f868d2acca063 - languageName: node - linkType: hard - "encodeurl@npm:~1.0.2": version: 1.0.2 resolution: "encodeurl@npm:1.0.2" @@ -6016,13 +5801,6 @@ __metadata: languageName: node linkType: hard -"ent@npm:^2.2.0": - version: 2.2.0 - resolution: "ent@npm:2.2.0" - checksum: f588b5707d6fef36011ea10d530645912a69530a1eb0831f8708c498ac028363a7009f45cfadd28ceb4dafd9ac17ec15213f88d09ce239cd033cfe1328dd7d7d - languageName: node - linkType: hard - "entities@npm:^2.0.0": version: 2.2.0 resolution: "entities@npm:2.2.0" @@ -6318,13 +6096,6 @@ __metadata: languageName: node linkType: hard -"es6-error@npm:^4.0.1": - version: 4.1.1 - resolution: "es6-error@npm:4.1.1" - checksum: ae41332a51ec1323da6bbc5d75b7803ccdeddfae17c41b6166ebbafc8e8beb7a7b80b884b7fab1cc80df485860ac3c59d78605e860bb4f8cd816b3d6ade0d010 - languageName: node - linkType: hard - "es6-iterator@npm:^2.0.3, es6-iterator@npm:~2.0.1, es6-iterator@npm:~2.0.3": version: 2.0.3 resolution: "es6-iterator@npm:2.0.3" @@ -6911,13 +6682,6 @@ __metadata: languageName: node linkType: hard -"events@npm:1.1.1": - version: 1.1.1 - resolution: "events@npm:1.1.1" - checksum: 40431eb005cc4c57861b93d44c2981a49e7feb99df84cf551baed299ceea4444edf7744733f6a6667e942af687359b1f4a87ec1ec4f21d5127dac48a782039b9 - languageName: node - linkType: hard - "events@npm:^3.2.0": version: 3.3.0 resolution: "events@npm:3.3.0" @@ -7121,13 +6885,6 @@ __metadata: languageName: node linkType: hard -"fast-safe-stringify@npm:^2.0.4": - version: 2.0.7 - resolution: "fast-safe-stringify@npm:2.0.7" - checksum: e0055e231d1fe0f97863dcfb45f5f285d59e3d23210e1e8a31348829e4a584e04ffe49f5944a0ba2f21d753b67b0ecb6f0ffc49ecd8c7f6f531cbcd45a5f606b - languageName: node - linkType: hard - "fast-url-parser@npm:1.1.3": version: 1.1.3 resolution: "fast-url-parser@npm:1.1.3" @@ -7180,13 +6937,6 @@ __metadata: languageName: node linkType: hard -"fecha@npm:^4.2.0": - version: 4.2.1 - resolution: "fecha@npm:4.2.1" - checksum: 26993474949d94cd2de5eee7dfe283d671d5cd61acdba8819df478cbc86495273363f4a7e98d15ee51563110a38328d268982a6e9048169bce8f15aeba5931f9 - languageName: node - linkType: hard - "figures@npm:^1.3.5": version: 1.7.0 resolution: "figures@npm:1.7.0" @@ -7293,17 +7043,6 @@ __metadata: languageName: node linkType: hard -"find-cache-dir@npm:^3.2.0": - version: 3.3.1 - resolution: "find-cache-dir@npm:3.3.1" - dependencies: - commondir: ^1.0.1 - make-dir: ^3.0.2 - pkg-dir: ^4.1.0 - checksum: 0f7c22b65e07f9b486b4560227d014fab1e79ffbbfbafb87d113a2e878510bd620ef6fdff090e5248bb2846d28851d19e42bfdc7c50687966acc106328e7abf1 - languageName: node - linkType: hard - "find-up@npm:^3.0.0": version: 3.0.0 resolution: "find-up@npm:3.0.0" @@ -7388,13 +7127,6 @@ __metadata: languageName: node linkType: hard -"fn.name@npm:1.x.x": - version: 1.1.0 - resolution: "fn.name@npm:1.1.0" - checksum: e357144f48cfc9a7f52a82bbc6c23df7c8de639fce049cac41d41d62cabb740cdb9f14eddc6485e29c933104455bdd7a69bb14a9012cef9cd4fa252a4d0cf293 - languageName: node - linkType: hard - "follow-redirects@npm:^1.0.0": version: 1.15.2 resolution: "follow-redirects@npm:1.15.2" @@ -7405,6 +7137,16 @@ __metadata: languageName: node linkType: hard +"follow-redirects@npm:^1.15.4": + version: 1.15.5 + resolution: "follow-redirects@npm:1.15.5" + peerDependenciesMeta: + debug: + optional: true + checksum: 5ca49b5ce6f44338cbfc3546823357e7a70813cecc9b7b768158a1d32c1e62e7407c944402a918ea8c38ae2e78266312d617dc68783fac502cbb55e1047b34ec + languageName: node + linkType: hard + "for-each@npm:^0.3.3": version: 0.3.3 resolution: "for-each@npm:0.3.3" @@ -7414,16 +7156,6 @@ __metadata: languageName: node linkType: hard -"foreground-child@npm:^2.0.0": - version: 2.0.0 - resolution: "foreground-child@npm:2.0.0" - dependencies: - cross-spawn: ^7.0.0 - signal-exit: ^3.0.2 - checksum: f77ec9aff621abd6b754cb59e690743e7639328301fbea6ff09df27d2befaf7dd5b77cec51c32323d73a81a7d91caaf9413990d305cbe3d873eec4fe58960956 - languageName: node - linkType: hard - "forever-agent@npm:~0.6.1": version: 0.6.1 resolution: "forever-agent@npm:0.6.1" @@ -7442,6 +7174,17 @@ __metadata: languageName: node linkType: hard +"form-data@npm:^4.0.0": + version: 4.0.0 + resolution: "form-data@npm:4.0.0" + dependencies: + asynckit: ^0.4.0 + combined-stream: ^1.0.8 + mime-types: ^2.1.12 + checksum: 01135bf8675f9d5c61ff18e2e2932f719ca4de964e3be90ef4c36aacfc7b9cb2fceb5eca0b7e0190e3383fe51c5b37f4cb80b62ca06a99aaabfcfd6ac7c9328c + languageName: node + linkType: hard + "form-data@npm:~2.3.2": version: 2.3.3 resolution: "form-data@npm:2.3.3" @@ -7467,13 +7210,6 @@ __metadata: languageName: node linkType: hard -"fromentries@npm:^1.2.0": - version: 1.3.2 - resolution: "fromentries@npm:1.3.2" - checksum: 33729c529ce19f5494f846f0dd4945078f4e37f4e8955f4ae8cc7385c218f600e9d93a7d225d17636c20d1889106fd87061f911550861b7072f53bf891e6b341 - languageName: node - linkType: hard - "front-matter@npm:2.1.2": version: 2.1.2 resolution: "front-matter@npm:2.1.2" @@ -7972,13 +7708,6 @@ __metadata: languageName: node linkType: hard -"graceful-fs@npm:^4.1.15, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.4": - version: 4.2.6 - resolution: "graceful-fs@npm:4.2.6" - checksum: 792e64aafda05a151289f83eaa16aff34ef259658cefd65393883d959409f5a2389b0ec9ebf28f3d21f1b0ddc8f594a1162ae9b18e2b507a6799a70706ec573d - languageName: node - linkType: hard - "graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": version: 4.2.10 resolution: "graceful-fs@npm:4.2.10" @@ -7986,6 +7715,13 @@ __metadata: languageName: node linkType: hard +"graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.4": + version: 4.2.6 + resolution: "graceful-fs@npm:4.2.6" + checksum: 792e64aafda05a151289f83eaa16aff34ef259658cefd65393883d959409f5a2389b0ec9ebf28f3d21f1b0ddc8f594a1162ae9b18e2b507a6799a70706ec573d + languageName: node + linkType: hard + "graceful-fs@npm:^4.2.11": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" @@ -8143,16 +7879,6 @@ __metadata: languageName: node linkType: hard -"hasha@npm:^5.0.0": - version: 5.2.2 - resolution: "hasha@npm:5.2.2" - dependencies: - is-stream: ^2.0.0 - type-fest: ^0.8.0 - checksum: 06cc474bed246761ff61c19d629977eb5f53fa817be4313a255a64ae0f433e831a29e83acb6555e3f4592b348497596f1d1653751008dda4f21c9c21ca60ac5a - languageName: node - linkType: hard - "hashish@npm:>=0.0.2 <0.1": version: 0.0.4 resolution: "hashish@npm:0.0.4" @@ -8171,7 +7897,7 @@ __metadata: languageName: node linkType: hard -"history@npm:^5.0.0, history@npm:^5.3.0": +"history@npm:^5.3.0": version: 5.3.0 resolution: "history@npm:5.3.0" dependencies: @@ -8436,14 +8162,7 @@ __metadata: languageName: node linkType: hard -"ieee754@npm:1.1.13": - version: 1.1.13 - resolution: "ieee754@npm:1.1.13" - checksum: 102df1ba662e316e6160f7ce29c7c7fa3e04f2014c288336c5a9ff40bbcc2a27d209fa2a81ebfb33f28b1941021343d30e9ad8ee85a2d61f79f5936c35edc33d - languageName: node - linkType: hard - -"ieee754@npm:^1.1.13, ieee754@npm:^1.1.4": +"ieee754@npm:^1.1.13": version: 1.2.1 resolution: "ieee754@npm:1.2.1" checksum: 5144c0c9815e54ada181d80a0b810221a253562422e7c6c3a60b1901154184f49326ec239d618c416c1c5945a2e197107aee8d986a3dd836b53dffefd99b5e7e @@ -8726,13 +8445,6 @@ __metadata: languageName: node linkType: hard -"is-arrayish@npm:^0.3.1": - version: 0.3.2 - resolution: "is-arrayish@npm:0.3.2" - checksum: 977e64f54d91c8f169b59afcd80ff19227e9f5c791fa28fa2e5bce355cbaf6c2c356711b734656e80c9dd4a854dd7efcf7894402f1031dfc5de5d620775b4d5f - languageName: node - linkType: hard - "is-bigint@npm:^1.0.1": version: 1.0.1 resolution: "is-bigint@npm:1.0.1" @@ -9145,13 +8857,6 @@ __metadata: languageName: node linkType: hard -"is-windows@npm:^1.0.2": - version: 1.0.2 - resolution: "is-windows@npm:1.0.2" - checksum: 438b7e52656fe3b9b293b180defb4e448088e7023a523ec21a91a80b9ff8cdb3377ddb5b6e60f7c7de4fa8b63ab56e121b6705fe081b3cf1b828b0a380009ad7 - languageName: node - linkType: hard - "is-wsl@npm:^2.2.0": version: 2.2.0 resolution: "is-wsl@npm:2.2.0" @@ -9206,7 +8911,7 @@ __metadata: languageName: node linkType: hard -"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.0.0-alpha.1": +"istanbul-lib-coverage@npm:^3.0.0": version: 3.0.0 resolution: "istanbul-lib-coverage@npm:3.0.0" checksum: ea57c2428858cc5d1e04c0e28b362950bbf6415e8ba1235cdd6f4c8dc3c57cb950db8b4e8a4f7e33abc240aa1eb816dba0d7285bdb8b70bda22bb2082492dbfc @@ -9220,27 +8925,6 @@ __metadata: languageName: node linkType: hard -"istanbul-lib-hook@npm:^3.0.0": - version: 3.0.0 - resolution: "istanbul-lib-hook@npm:3.0.0" - dependencies: - append-transform: ^2.0.0 - checksum: ac4d0a0751e959cfe4c95d817df5f1f573f9b0cf892552e60d81785654291391fac1ceb667f13bb17fcc2ef23b74c89ed8cf1c6148c833c8596a2b920b079101 - languageName: node - linkType: hard - -"istanbul-lib-instrument@npm:^4.0.0": - version: 4.0.3 - resolution: "istanbul-lib-instrument@npm:4.0.3" - dependencies: - "@babel/core": ^7.7.5 - "@istanbuljs/schema": ^0.1.2 - istanbul-lib-coverage: ^3.0.0 - semver: ^6.3.0 - checksum: fa1171d3022b1bb8f6a734042620ac5d9ee7dc80f3065a0bb12863e9f0494d0eefa3d86608fcc0254ab2765d29d7dad8bdc42e5f8df2f9a1fbe85ccc59d76cb9 - languageName: node - linkType: hard - "istanbul-lib-instrument@npm:^5.0.4, istanbul-lib-instrument@npm:^5.1.0": version: 5.1.0 resolution: "istanbul-lib-instrument@npm:5.1.0" @@ -9254,21 +8938,6 @@ __metadata: languageName: node linkType: hard -"istanbul-lib-processinfo@npm:^2.0.2": - version: 2.0.2 - resolution: "istanbul-lib-processinfo@npm:2.0.2" - dependencies: - archy: ^1.0.0 - cross-spawn: ^7.0.0 - istanbul-lib-coverage: ^3.0.0-alpha.1 - make-dir: ^3.0.0 - p-map: ^3.0.0 - rimraf: ^3.0.0 - uuid: ^3.3.3 - checksum: 400bd0b25b623c172e48d37e5bdda7a58b2fe5beeedfeb03099aed3385223d31e4cfa6f9932be07bbf06cfd039023301bce81d3b70b9a20a79a38b0f12cb261a - languageName: node - linkType: hard - "istanbul-lib-report@npm:^3.0.0": version: 3.0.0 resolution: "istanbul-lib-report@npm:3.0.0" @@ -9291,16 +8960,6 @@ __metadata: languageName: node linkType: hard -"istanbul-reports@npm:^3.0.2": - version: 3.0.2 - resolution: "istanbul-reports@npm:3.0.2" - dependencies: - html-escaper: ^2.0.0 - istanbul-lib-report: ^3.0.0 - checksum: c5da63f1f4610f47f3015c525a3bc2fb4c87a8791ae452ee3983546d7a2873f0cf5d5fff7c3735ac52943c5b3506f49c294c92f1837df6ec03312625ccd176d7 - languageName: node - linkType: hard - "istanbul-reports@npm:^3.1.3": version: 3.1.3 resolution: "istanbul-reports@npm:3.1.3" @@ -9891,13 +9550,6 @@ __metadata: languageName: node linkType: hard -"jmespath@npm:0.15.0": - version: 0.15.0 - resolution: "jmespath@npm:0.15.0" - checksum: 353bb9e69cc4c1560be0a4df43cb4020abc246e1c60cb5b55dcc76d8c858383f1633faf22ccaf6a5e09568a2077d0f4f1e989e6fcfd496b5cef87964cc8cb9e7 - languageName: node - linkType: hard - "jquery@npm:>=1.12.4, jquery@npm:^3.5.1": version: 3.6.0 resolution: "jquery@npm:3.6.0" @@ -9919,13 +9571,6 @@ __metadata: languageName: node linkType: hard -"js-utils-lite@npm:^2.0.0": - version: 2.0.0 - resolution: "js-utils-lite@npm:2.0.0" - checksum: aaa986a8425e343411710f7bac20c66e4861aeb90d4ab5ccff84f784318352d55749d95bd1124f95bd1ddb368ca52acfb161c6737727d651aca9658993bf7af4 - languageName: node - linkType: hard - "js-yaml@npm:^3.13.1, js-yaml@npm:^3.4.6, js-yaml@npm:^3.5.1, js-yaml@npm:^3.5.4": version: 3.14.1 resolution: "js-yaml@npm:3.14.1" @@ -10223,13 +9868,6 @@ __metadata: languageName: node linkType: hard -"kuler@npm:^2.0.0": - version: 2.0.0 - resolution: "kuler@npm:2.0.0" - checksum: 9e10b5a1659f9ed8761d38df3c35effabffbd19fc6107324095238e4ef0ff044392cae9ac64a1c2dda26e532426485342226b93806bd97504b174b0dcf04ed81 - languageName: node - linkType: hard - "leven@npm:^3.1.0": version: 3.1.0 resolution: "leven@npm:3.1.0" @@ -10340,13 +9978,6 @@ __metadata: languageName: node linkType: hard -"lodash.clonedeep@npm:4.5.0, lodash.clonedeep@npm:^4.5.0": - version: 4.5.0 - resolution: "lodash.clonedeep@npm:4.5.0" - checksum: 92c46f094b064e876a23c97f57f81fbffd5d760bf2d8a1c61d85db6d1e488c66b0384c943abee4f6af7debf5ad4e4282e74ff83177c9e63d8ff081a4837c3489 - languageName: node - linkType: hard - "lodash.debounce@npm:^4.0.8": version: 4.0.8 resolution: "lodash.debounce@npm:4.0.8" @@ -10403,20 +10034,6 @@ __metadata: languageName: node linkType: hard -"lodash.isobject@npm:3.0.2": - version: 3.0.2 - resolution: "lodash.isobject@npm:3.0.2" - checksum: 6c1667cbc4494d0a13a3617a4b23278d6d02dac520311f2bbb43f16f2cf71d2e6eb9dec8057315b77459df4890c756a256a087d3f4baa44a79ab5d6c968b060e - languageName: node - linkType: hard - -"lodash.isplainobject@npm:4.0.6, lodash.isplainobject@npm:^4.0.6": - version: 4.0.6 - resolution: "lodash.isplainobject@npm:4.0.6" - checksum: 29c6351f281e0d9a1d58f1a4c8f4400924b4c79f18dfc4613624d7d54784df07efaff97c1ff2659f3e085ecf4fff493300adc4837553104cef2634110b0d5337 - languageName: node - linkType: hard - "lodash.kebabcase@npm:^4.0.0": version: 4.1.1 resolution: "lodash.kebabcase@npm:4.1.1" @@ -10431,7 +10048,7 @@ __metadata: languageName: node linkType: hard -"lodash.merge@npm:4.6.2, lodash.merge@npm:^4.6.0, lodash.merge@npm:^4.6.2": +"lodash.merge@npm:^4.6.0, lodash.merge@npm:^4.6.2": version: 4.6.2 resolution: "lodash.merge@npm:4.6.2" checksum: ad580b4bdbb7ca1f7abf7e1bce63a9a0b98e370cf40194b03380a46b4ed799c9573029599caebc1b14e3f24b111aef72b96674a56cfa105e0f5ac70546cdc005 @@ -10445,13 +10062,6 @@ __metadata: languageName: node linkType: hard -"lodash.set@npm:4.3.2": - version: 4.3.2 - resolution: "lodash.set@npm:4.3.2" - checksum: a9122f49eef9f2d0fc9061a33d87f8e5b8c6b23d46e8b9e9ce1529d3588d79741bd1145a3abdfa3b13082703e65af27ff18d8a07bfc22b9be32f3fc36f763f70 - languageName: node - linkType: hard - "lodash.sortby@npm:^4.7.0": version: 4.7.0 resolution: "lodash.sortby@npm:4.7.0" @@ -10459,13 +10069,6 @@ __metadata: languageName: node linkType: hard -"lodash.trim@npm:^4.5.1": - version: 4.5.1 - resolution: "lodash.trim@npm:4.5.1" - checksum: 64b08e97d94d4c7620159371e6fe6cbb706514a41d737db2f189d9ec738305eb08cb772a9bbd2459e90f1c22f96174ec1047ceb8272f2f6040cb5bd63d8f9f2b - languageName: node - linkType: hard - "lodash.truncate@npm:^4.4.2": version: 4.4.2 resolution: "lodash.truncate@npm:4.4.2" @@ -10487,13 +10090,6 @@ __metadata: languageName: node linkType: hard -"lodash.unset@npm:4.5.2": - version: 4.5.2 - resolution: "lodash.unset@npm:4.5.2" - checksum: 53b2a79c20e7c8c604ccfd82c7ad5caa28876171a323c5dc4d6d745d42bc43f4827c24526d877484bf64284104dffa4dd34e0c363d49fd84bd398c259001ac3f - languageName: node - linkType: hard - "lodash.uppercase@npm:^4.3.0": version: 4.3.0 resolution: "lodash.uppercase@npm:4.3.0" @@ -10501,13 +10097,6 @@ __metadata: languageName: node linkType: hard -"lodash.without@npm:^4.4.0": - version: 4.4.0 - resolution: "lodash.without@npm:4.4.0" - checksum: 8cef752edd4ed4065be2a8fd30ea52c0bb27b0cb6c34742f595263c72ee0c3a188572affb477ef18a4dd4d0347fe1a4e580b70d4e36f37323d7924d2e6046bd6 - languageName: node - linkType: hard - "lodash@npm:^4.0.0, lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:^4.3.0, lodash@npm:^4.7.0, lodash@npm:~4.17.10": version: 4.17.21 resolution: "lodash@npm:4.17.21" @@ -10525,19 +10114,6 @@ __metadata: languageName: node linkType: hard -"logform@npm:^2.2.0": - version: 2.2.0 - resolution: "logform@npm:2.2.0" - dependencies: - colors: ^1.2.1 - fast-safe-stringify: ^2.0.4 - fecha: ^4.2.0 - ms: ^2.1.1 - triple-beam: ^1.3.0 - checksum: 07319bfd50dacf69a4a3bc81cd6f5fab2f52d247ba5d2d2df99141f6b62f787f7fbb0353046650da90329d4030f265632d5f995706612ed9cb2c70281866007e - languageName: node - linkType: hard - "loglevel-colored-level-prefix@npm:^1.0.0": version: 1.0.0 resolution: "loglevel-colored-level-prefix@npm:1.0.0" @@ -10609,7 +10185,7 @@ __metadata: languageName: node linkType: hard -"make-dir@npm:^3.0.0, make-dir@npm:^3.0.2, make-dir@npm:^3.1.0": +"make-dir@npm:^3.0.0, make-dir@npm:^3.1.0": version: 3.1.0 resolution: "make-dir@npm:3.1.0" dependencies: @@ -10672,15 +10248,6 @@ __metadata: languageName: node linkType: hard -"markdown-doc-builder@npm:^1.3.0": - version: 1.3.0 - resolution: "markdown-doc-builder@npm:1.3.0" - dependencies: - js-utils-lite: ^2.0.0 - checksum: 09c14081bf95bb278c7ecfbaede4780e143cf43aae3aecac4ca30d4d91aad8ce90b9ddb3ffe31d46df7fe78172ebdba6faae098cb13080e477f9bdec5eaae5ff - languageName: node - linkType: hard - "mathjax@npm:^3.2.2": version: 3.2.2 resolution: "mathjax@npm:3.2.2" @@ -11020,7 +10587,7 @@ __metadata: languageName: node linkType: hard -"moment@npm:^2.15.2, moment@npm:^2.29.1": +"moment@npm:^2.15.2": version: 2.29.1 resolution: "moment@npm:2.29.1" checksum: 1e14d5f422a2687996be11dd2d50c8de3bd577c4a4ca79ba5d02c397242a933e5b941655de6c8cb90ac18f01cc4127e55b4a12ae3c527a6c0a274e455979345e @@ -11074,7 +10641,7 @@ __metadata: languageName: node linkType: hard -"ms@npm:2.1.3, ms@npm:^2.0.0, ms@npm:^2.1.1": +"ms@npm:2.1.3, ms@npm:^2.0.0": version: 2.1.3 resolution: "ms@npm:2.1.3" checksum: aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d @@ -11252,15 +10819,6 @@ __metadata: languageName: node linkType: hard -"node-preload@npm:^0.2.1": - version: 0.2.1 - resolution: "node-preload@npm:0.2.1" - dependencies: - process-on-spawn: ^1.0.0 - checksum: 4586f91ac7417b33accce0ac629fb60f642d0c8d212b3c536dc3dda37fe54f8a3b858273380e1036e41a65d85470332c358315d2288e6584260d620fb4b00fb3 - languageName: node - linkType: hard - "node-releases@npm:^1.1.71": version: 1.1.71 resolution: "node-releases@npm:1.1.71" @@ -11374,43 +10932,6 @@ __metadata: languageName: node linkType: hard -"nyc@npm:^15.1.0": - version: 15.1.0 - resolution: "nyc@npm:15.1.0" - dependencies: - "@istanbuljs/load-nyc-config": ^1.0.0 - "@istanbuljs/schema": ^0.1.2 - caching-transform: ^4.0.0 - convert-source-map: ^1.7.0 - decamelize: ^1.2.0 - find-cache-dir: ^3.2.0 - find-up: ^4.1.0 - foreground-child: ^2.0.0 - get-package-type: ^0.1.0 - glob: ^7.1.6 - istanbul-lib-coverage: ^3.0.0 - istanbul-lib-hook: ^3.0.0 - istanbul-lib-instrument: ^4.0.0 - istanbul-lib-processinfo: ^2.0.2 - istanbul-lib-report: ^3.0.0 - istanbul-lib-source-maps: ^4.0.0 - istanbul-reports: ^3.0.2 - make-dir: ^3.0.0 - node-preload: ^0.2.1 - p-map: ^3.0.0 - process-on-spawn: ^1.0.0 - resolve-from: ^5.0.0 - rimraf: ^3.0.0 - signal-exit: ^3.0.2 - spawn-wrap: ^2.0.0 - test-exclude: ^6.0.0 - yargs: ^15.0.2 - bin: - nyc: bin/nyc.js - checksum: 82a7031982df2fd6ab185c9f1b5d032b6221846268007b45b5773c6582e776ab33e96cd22b4231520345942fcef69b4339bd967675b8483f3fa255b56326faef - languageName: node - linkType: hard - "oauth-sign@npm:~0.9.0": version: 0.9.0 resolution: "oauth-sign@npm:0.9.0" @@ -11608,8 +11129,7 @@ __metadata: version: 0.0.0-use.local resolution: "ocw-hugo-themes@workspace:." dependencies: - "@mitodl/course-search-utils": ^2.3.0 - "@mitodl/ocw-to-hugo": 1.36.0 + "@mitodl/course-search-utils": ^3.0.0 "@playwright/test": ^1.40.0 "@sentry/browser": ^5.19.0 "@swc/core": ^1.3.14 @@ -11745,15 +11265,6 @@ __metadata: languageName: node linkType: hard -"one-time@npm:^1.0.0": - version: 1.0.0 - resolution: "one-time@npm:1.0.0" - dependencies: - fn.name: 1.x.x - checksum: fd008d7e992bdec1c67f53a2f9b46381ee12a9b8c309f88b21f0223546003fb47e8ad7c1fd5843751920a8d276c63bd4b45670ef80c61fb3e07dbccc962b5c7d - languageName: node - linkType: hard - "onetime@npm:^1.0.0": version: 1.1.0 resolution: "onetime@npm:1.1.0" @@ -11944,15 +11455,6 @@ __metadata: languageName: node linkType: hard -"p-map@npm:^3.0.0": - version: 3.0.0 - resolution: "p-map@npm:3.0.0" - dependencies: - aggregate-error: ^3.0.0 - checksum: 49b0fcbc66b1ef9cd379de1b4da07fa7a9f84b41509ea3f461c31903623aaba8a529d22f835e0d77c7cb9fcc16e4fae71e308fd40179aea514ba68f27032b5d5 - languageName: node - linkType: hard - "p-map@npm:^4.0.0": version: 4.0.0 resolution: "p-map@npm:4.0.0" @@ -11986,18 +11488,6 @@ __metadata: languageName: node linkType: hard -"package-hash@npm:^4.0.0": - version: 4.0.0 - resolution: "package-hash@npm:4.0.0" - dependencies: - graceful-fs: ^4.1.15 - hasha: ^5.0.0 - lodash.flattendeep: ^4.4.0 - release-zalgo: ^1.0.0 - checksum: 32c49e3a0e1c4a33b086a04cdd6d6e570aee019cb8402ec16476d9b3564a40e38f91ce1a1f9bc88b08f8ef2917a11e0b786c08140373bdf609ea90749031e6fc - languageName: node - linkType: hard - "parent-module@npm:^1.0.0": version: 1.0.1 resolution: "parent-module@npm:1.0.1" @@ -12238,7 +11728,7 @@ __metadata: languageName: node linkType: hard -"pkg-dir@npm:^4.1.0, pkg-dir@npm:^4.2.0": +"pkg-dir@npm:^4.2.0": version: 4.2.0 resolution: "pkg-dir@npm:4.2.0" dependencies: @@ -13198,15 +12688,6 @@ __metadata: languageName: node linkType: hard -"process-on-spawn@npm:^1.0.0": - version: 1.0.0 - resolution: "process-on-spawn@npm:1.0.0" - dependencies: - fromentries: ^1.2.0 - checksum: 597769e3db6a8e2cb1cd64a952bbc150220588debac31c7cf1a9f620ce981e25583d8d70848d8a14953577608512984a8808c3be77e09af8ebdcdc14ec23a295 - languageName: node - linkType: hard - "process@npm:^0.11.10": version: 0.11.10 resolution: "process@npm:0.11.10" @@ -13331,6 +12812,13 @@ __metadata: languageName: node linkType: hard +"proxy-from-env@npm:^1.1.0": + version: 1.1.0 + resolution: "proxy-from-env@npm:1.1.0" + checksum: ed7fcc2ba0a33404958e34d95d18638249a68c430e30fcb6c478497d72739ba64ce9810a24f53a7d921d0c065e5b78e3822759800698167256b04659366ca4d4 + languageName: node + linkType: hard + "psl@npm:^1.1.28, psl@npm:^1.1.33": version: 1.8.0 resolution: "psl@npm:1.8.0" @@ -13348,13 +12836,6 @@ __metadata: languageName: node linkType: hard -"punycode@npm:1.3.2": - version: 1.3.2 - resolution: "punycode@npm:1.3.2" - checksum: b8807fd594b1db33335692d1f03e8beeddde6fda7fbb4a2e32925d88d20a3aa4cd8dcc0c109ccaccbd2ba761c208dfaaada83007087ea8bfb0129c9ef1b99ed6 - languageName: node - linkType: hard - "punycode@npm:^1.3.2": version: 1.4.1 resolution: "punycode@npm:1.4.1" @@ -13397,13 +12878,6 @@ __metadata: languageName: node linkType: hard -"querystring@npm:0.2.0": - version: 0.2.0 - resolution: "querystring@npm:0.2.0" - checksum: 8258d6734f19be27e93f601758858c299bdebe71147909e367101ba459b95446fbe5b975bf9beb76390156a592b6f4ac3a68b6087cea165c259705b8b4e56a69 - languageName: node - linkType: hard - "queue-microtask@npm:^1.2.2": version: 1.2.3 resolution: "queue-microtask@npm:1.2.3" @@ -13474,42 +12948,6 @@ __metadata: languageName: node linkType: hard -"ranges-apply@npm:^3.2.4": - version: 3.2.4 - resolution: "ranges-apply@npm:3.2.4" - dependencies: - ranges-merge: ^5.0.4 - checksum: f2a9af2cfb626e78971dbc7834b0932393a8a40c2f7ca08b661f483071bb1061f000fd1115a96b178398cfc9d026187cbc4f49509290de2fd8416bc66a394709 - languageName: node - linkType: hard - -"ranges-merge@npm:^5.0.4": - version: 5.0.4 - resolution: "ranges-merge@npm:5.0.4" - dependencies: - ranges-sort: ^3.13.4 - checksum: ca6bc9a8277553bc1c72716d63b92715690fda531d2ce40be2f2d229fea9ecc5548d8fe581e239fed009162dbe13b48f6d32bc90d2c5b951b7e2f31b54d7e904 - languageName: node - linkType: hard - -"ranges-push@npm:^3.7.23": - version: 3.7.23 - resolution: "ranges-push@npm:3.7.23" - dependencies: - ranges-merge: ^5.0.4 - string-collapse-leading-whitespace: ^3.0.3 - string-trim-spaces-only: ^2.8.24 - checksum: bf4da41316fe38e83605972e692f8826e0618072b63b254f554a5ed3f8e4cf0a281955c0d4c42a132b23724e26ae993248d4db75cf39b0220df9958ad7754403 - languageName: node - linkType: hard - -"ranges-sort@npm:^3.13.4": - version: 3.14.0 - resolution: "ranges-sort@npm:3.14.0" - checksum: 7b10d5faad7e2e376d05ab26a2a646157b174b2ec6e8d60825059658061b9bf86287e83cc4d5429746d6bf3c46a24fc228c7de2485d1b896316f8c1b0d9f6aef - languageName: node - linkType: hard - "raw-body@npm:2.5.1": version: 2.5.1 resolution: "raw-body@npm:2.5.1" @@ -13705,7 +13143,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:^2.0.1, readable-stream@npm:^2.2.2, readable-stream@npm:^2.3.7": +"readable-stream@npm:^2.0.1, readable-stream@npm:^2.2.2": version: 2.3.7 resolution: "readable-stream@npm:2.3.7" dependencies: @@ -13821,15 +13259,6 @@ __metadata: languageName: node linkType: hard -"release-zalgo@npm:^1.0.0": - version: 1.0.0 - resolution: "release-zalgo@npm:1.0.0" - dependencies: - es6-error: ^4.0.1 - checksum: b59849dc310f6c426f34e308c48ba83df3d034ddef75189951723bb2aac99d29d15f5e127edad951c4095fc9025aa582053907154d68fe0c5380cd6a75365e53 - languageName: node - linkType: hard - "remove@npm:^0.1.5": version: 0.1.5 resolution: "remove@npm:0.1.5" @@ -14386,20 +13815,6 @@ __metadata: languageName: node linkType: hard -"sax@npm:1.2.1": - version: 1.2.1 - resolution: "sax@npm:1.2.1" - checksum: 8dca7d5e1cd7d612f98ac50bdf0b9f63fbc964b85f0c4e2eb271f8b9b47fd3bf344c4d6a592e69ecf726d1485ca62cd8a52e603bbc332d18a66af25a9a1045ad - languageName: node - linkType: hard - -"sax@npm:>=0.6.0": - version: 1.2.4 - resolution: "sax@npm:1.2.4" - checksum: d3df7d32b897a2c2f28e941f732c71ba90e27c24f62ee918bd4d9a8cfb3553f2f81e5493c7f0be94a11c1911b643a9108f231dd6f60df3fa9586b5d2e3e9e1fe - languageName: node - linkType: hard - "saxes@npm:^3.1.9": version: 3.1.11 resolution: "saxes@npm:3.1.11" @@ -14806,15 +14221,6 @@ __metadata: languageName: node linkType: hard -"simple-swizzle@npm:^0.2.2": - version: 0.2.2 - resolution: "simple-swizzle@npm:0.2.2" - dependencies: - is-arrayish: ^0.3.1 - checksum: a7f3f2ab5c76c4472d5c578df892e857323e452d9f392e1b5cf74b74db66e6294a1e1b8b390b519fa1b96b5b613f2a37db6cffef52c3f1f8f3c5ea64eb2d54c0 - languageName: node - linkType: hard - "sinon@npm:^10.0.0": version: 10.0.1 resolution: "sinon@npm:10.0.1" @@ -15012,20 +14418,6 @@ __metadata: languageName: node linkType: hard -"spawn-wrap@npm:^2.0.0": - version: 2.0.0 - resolution: "spawn-wrap@npm:2.0.0" - dependencies: - foreground-child: ^2.0.0 - is-windows: ^1.0.2 - make-dir: ^3.0.0 - rimraf: ^3.0.0 - signal-exit: ^3.0.2 - which: ^2.0.1 - checksum: 5a518e37620def6d516b86207482a4f76bcf3c37c57d8d886d9fa399b04e5668d11fd12817b178029b02002a5ebbd09010374307effa821ba39594042f0a2d96 - languageName: node - linkType: hard - "spdy-transport@npm:^3.0.0": version: 3.0.0 resolution: "spdy-transport@npm:3.0.0" @@ -15104,13 +14496,6 @@ __metadata: languageName: node linkType: hard -"stack-trace@npm:0.0.x": - version: 0.0.10 - resolution: "stack-trace@npm:0.0.10" - checksum: 473036ad32f8c00e889613153d6454f9be0536d430eb2358ca51cad6b95cea08a3cc33cc0e34de66b0dad221582b08ed2e61ef8e13f4087ab690f388362d6610 - languageName: node - linkType: hard - "stack-utils@npm:^2.0.3": version: 2.0.5 resolution: "stack-utils@npm:2.0.5" @@ -15148,23 +14533,6 @@ __metadata: languageName: node linkType: hard -"string-collapse-leading-whitespace@npm:^3.0.3": - version: 3.0.3 - resolution: "string-collapse-leading-whitespace@npm:3.0.3" - checksum: 889e2e968431fedf8eafa2265abc226a1a459f4f8e37ad9e57fe1a7c27d6b331f3c9a812f56df5dd80e60eef42e53519dfd38420dc523bf73aa5c9aed7b22832 - languageName: node - linkType: hard - -"string-left-right@npm:^2.3.32": - version: 2.3.32 - resolution: "string-left-right@npm:2.3.32" - dependencies: - lodash.clonedeep: ^4.5.0 - lodash.isplainobject: ^4.0.6 - checksum: 31228bd5b4a75a2d579ef9be45b1c43be9c90e343b180a4546bb5d7569a2d5e1dc9a292a8da470deedd0973495baaa8703383bd8ee7e4cde57473a0e6b9e70b7 - languageName: node - linkType: hard - "string-length@npm:^4.0.1": version: 4.0.2 resolution: "string-length@npm:4.0.2" @@ -15185,28 +14553,6 @@ __metadata: languageName: node linkType: hard -"string-strip-html@npm:^6.1.1": - version: 6.3.0 - resolution: "string-strip-html@npm:6.3.0" - dependencies: - ent: ^2.2.0 - lodash.isplainobject: ^4.0.6 - lodash.trim: ^4.5.1 - lodash.without: ^4.4.0 - ranges-apply: ^3.2.4 - ranges-push: ^3.7.23 - string-left-right: ^2.3.32 - checksum: 44cab0bd34583c2df42148db9f68afa11e22e441692be84213f66d725e4bf8c6b2cfd1229bd622339ddec89fc9b7f50d63d1cf2222e503fd9da1dd47655ee932 - languageName: node - linkType: hard - -"string-trim-spaces-only@npm:^2.8.24": - version: 2.9.0 - resolution: "string-trim-spaces-only@npm:2.9.0" - checksum: 39b443d09548b5cca292769f7a7a86ef3ca8bb3c3a054872ce76fb2329cc072cdbacbb79a70c60600aaf31074d210b03b7b464e7218dc8a5df7d69ac4cbf9bd7 - languageName: node - linkType: hard - "string-width@npm:^1.0.1": version: 1.0.2 resolution: "string-width@npm:1.0.2" @@ -15732,13 +15078,6 @@ __metadata: languageName: node linkType: hard -"text-hex@npm:1.0.x": - version: 1.0.0 - resolution: "text-hex@npm:1.0.0" - checksum: 1138f68adc97bf4381a302a24e2352f04992b7b1316c5003767e9b0d3367ffd0dc73d65001ea02b07cd0ecc2a9d186de0cf02f3c2d880b8a522d4ccb9342244a - languageName: node - linkType: hard - "text-table@npm:^0.2.0, text-table@npm:~0.2.0": version: 0.2.0 resolution: "text-table@npm:0.2.0" @@ -15767,15 +15106,6 @@ __metadata: languageName: node linkType: hard -"title-case@npm:^3.0.2": - version: 3.0.3 - resolution: "title-case@npm:3.0.3" - dependencies: - tslib: ^2.0.3 - checksum: e8b7ea006b53cf3208d278455d9f1e22c409459d7f9878da324fa3b18cc0aef8560924c19c744e870394a5d9cddfdbe029ebae9875909ee7f4fc562e7cbfc53e - languageName: node - linkType: hard - "tmp@npm:^0.0.33": version: 0.0.33 resolution: "tmp@npm:0.0.33" @@ -15785,15 +15115,6 @@ __metadata: languageName: node linkType: hard -"tmp@npm:^0.2.1": - version: 0.2.1 - resolution: "tmp@npm:0.2.1" - dependencies: - rimraf: ^3.0.0 - checksum: 8b1214654182575124498c87ca986ac53dc76ff36e8f0e0b67139a8d221eaecfdec108c0e6ec54d76f49f1f72ab9325500b246f562b926f85bcdfca8bf35df9e - languageName: node - linkType: hard - "tmpl@npm:1.0.x": version: 1.0.4 resolution: "tmpl@npm:1.0.4" @@ -15913,13 +15234,6 @@ __metadata: languageName: node linkType: hard -"triple-beam@npm:^1.2.0, triple-beam@npm:^1.3.0": - version: 1.3.0 - resolution: "triple-beam@npm:1.3.0" - checksum: 7d7b77d8625fb252c126c24984a68de462b538a8fcd1de2abd0a26421629cf3527d48e23b3c2264f08f4a6c3bc40a478a722176f4d7b6a1acc154cb70c359f2b - languageName: node - linkType: hard - "ts-jest@npm:^27.1.2": version: 27.1.2 resolution: "ts-jest@npm:27.1.2" @@ -16028,13 +15342,6 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.0.3": - version: 2.2.0 - resolution: "tslib@npm:2.2.0" - checksum: a48c9639f7496fa701ea8ffe0561070fcb44c104a59632f7f845c0af00825c99b6373575ec59b2b5cdbfd7505875086dbe5dc83312304d8979f22ce571218ca3 - languageName: node - linkType: hard - "tslib@npm:^2.1.0, tslib@npm:^2.4.0": version: 2.4.1 resolution: "tslib@npm:2.4.1" @@ -16062,22 +15369,6 @@ __metadata: languageName: node linkType: hard -"turndown-plugin-gfm@npm:^1.0.2": - version: 1.0.2 - resolution: "turndown-plugin-gfm@npm:1.0.2" - checksum: 18191dc18d731ec16bb1f1a477af6471ce2acf152b882a7eccef8e0101e09628fec0f8499f352f0a291ce798f1f30d799b5a72bd5d2cde30a2a28070de24495c - languageName: node - linkType: hard - -"turndown@npm:^7.0.0": - version: 7.0.0 - resolution: "turndown@npm:7.0.0" - dependencies: - domino: ^2.1.6 - checksum: 6c89002073005f687a732651da72764492622f31b5760750dba53f9c83a0211adc2abcc8d44de8a984b4ffc9f64079213edbc7811e90188d01ad27e2cc13a2b5 - languageName: node - linkType: hard - "tweetnacl@npm:^0.14.3, tweetnacl@npm:~0.14.0": version: 0.14.5 resolution: "tweetnacl@npm:0.14.5" @@ -16124,13 +15415,6 @@ __metadata: languageName: node linkType: hard -"type-fest@npm:^0.8.0": - version: 0.8.1 - resolution: "type-fest@npm:0.8.1" - checksum: d61c4b2eba24009033ae4500d7d818a94fd6d1b481a8111612ee141400d5f1db46f199c014766b9fa9b31a6a7374d96fc748c6d688a78a3ce5a33123839becb7 - languageName: node - linkType: hard - "type-fest@npm:^1.2.1": version: 1.4.0 resolution: "type-fest@npm:1.4.0" @@ -16389,16 +15673,6 @@ __metadata: languageName: node linkType: hard -"url@npm:0.10.3": - version: 0.10.3 - resolution: "url@npm:0.10.3" - dependencies: - punycode: 1.3.2 - querystring: 0.2.0 - checksum: 7b83ddb106c27bf9bde8629ccbe8d26e9db789c8cda5aa7db72ca2c6f9b8a88a5adf206f3e10db78e6e2d042b327c45db34c7010c1bf0d9908936a17a2b57d05 - languageName: node - linkType: hard - "user-home@npm:^2.0.0": version: 2.0.0 resolution: "user-home@npm:2.0.0" @@ -16431,16 +15705,7 @@ __metadata: languageName: node linkType: hard -"uuid@npm:3.3.2": - version: 3.3.2 - resolution: "uuid@npm:3.3.2" - bin: - uuid: ./bin/uuid - checksum: 8793629d2799f500aeea9fcd0aec6c4e9fbcc4d62ed42159ad96be345c3fffac1bbf61a23e18e2782600884fee05e6d4012ce4b70d0037c8e987533ae6a77870 - languageName: node - linkType: hard - -"uuid@npm:^3.3.2, uuid@npm:^3.3.3": +"uuid@npm:^3.3.2": version: 3.4.0 resolution: "uuid@npm:3.4.0" bin: @@ -17023,33 +16288,6 @@ __metadata: languageName: node linkType: hard -"winston-transport@npm:^4.4.0": - version: 4.4.0 - resolution: "winston-transport@npm:4.4.0" - dependencies: - readable-stream: ^2.3.7 - triple-beam: ^1.2.0 - checksum: 953d78d152b355962d97697c3ccdc26fda6be017a0e1e555729e218d1269aa32a60e9ff16eb7a72c6403f733e88bab664b259feae3857667b54ff8e2f149fa52 - languageName: node - linkType: hard - -"winston@npm:^3.2.1": - version: 3.3.3 - resolution: "winston@npm:3.3.3" - dependencies: - "@dabh/diagnostics": ^2.0.2 - async: ^3.1.0 - is-stream: ^2.0.0 - logform: ^2.2.0 - one-time: ^1.0.0 - readable-stream: ^3.4.0 - stack-trace: 0.0.x - triple-beam: ^1.3.0 - winston-transport: ^4.4.0 - checksum: 89a0a8db4e577d0df2bee8af67a751663fb80aaa782750b5a0a151a6bf97074dd0eb7c81780e196197735b851c12ea9c176952128fc51fae07a8a5ddba82913a - languageName: node - linkType: hard - "word-wrap@npm:^1.2.3, word-wrap@npm:~1.2.3": version: 1.2.3 resolution: "word-wrap@npm:1.2.3" @@ -17068,7 +16306,7 @@ __metadata: languageName: node linkType: hard -"wrap-ansi@npm:^6.0.1, wrap-ansi@npm:^6.2.0": +"wrap-ansi@npm:^6.0.1": version: 6.2.0 resolution: "wrap-ansi@npm:6.2.0" dependencies: @@ -17185,23 +16423,6 @@ __metadata: languageName: node linkType: hard -"xml2js@npm:0.4.19": - version: 0.4.19 - resolution: "xml2js@npm:0.4.19" - dependencies: - sax: ">=0.6.0" - xmlbuilder: ~9.0.1 - checksum: ca8b2fee430d450a18947786bfd7cd1a353ee00fc6fd550acbc8a8e65f1b4df5e9786fcb2990c1a5514ecd554d445fb74e1d716b3a4fcfffc10554aeb5db482b - languageName: node - linkType: hard - -"xmlbuilder@npm:~9.0.1": - version: 9.0.7 - resolution: "xmlbuilder@npm:9.0.7" - checksum: 8193bb323806a002764f013bea0c6e9ff2dc26fd29109408761b16b59a8ad2214c2abe8e691755fd8b525586e3a0e1efeb92335947d7b0899032b779f1705a53 - languageName: node - linkType: hard - "xmlchars@npm:^2.1.1, xmlchars@npm:^2.2.0": version: 2.2.0 resolution: "xmlchars@npm:2.2.0" @@ -17268,16 +16489,6 @@ __metadata: languageName: node linkType: hard -"yargs-parser@npm:^18.1.2": - version: 18.1.3 - resolution: "yargs-parser@npm:18.1.3" - dependencies: - camelcase: ^5.0.0 - decamelize: ^1.2.0 - checksum: 60e8c7d1b85814594d3719300ecad4e6ae3796748b0926137bfec1f3042581b8646d67e83c6fc80a692ef08b8390f21ddcacb9464476c39bbdf52e34961dd4d9 - languageName: node - linkType: hard - "yargs@npm:^13.1.1": version: 13.3.2 resolution: "yargs@npm:13.3.2" @@ -17296,25 +16507,6 @@ __metadata: languageName: node linkType: hard -"yargs@npm:^15.0.2": - version: 15.4.1 - resolution: "yargs@npm:15.4.1" - dependencies: - cliui: ^6.0.0 - decamelize: ^1.2.0 - find-up: ^4.1.0 - get-caller-file: ^2.0.1 - require-directory: ^2.1.1 - require-main-filename: ^2.0.0 - set-blocking: ^2.0.0 - string-width: ^4.2.0 - which-module: ^2.0.0 - y18n: ^4.0.0 - yargs-parser: ^18.1.2 - checksum: 40b974f508d8aed28598087720e086ecd32a5fd3e945e95ea4457da04ee9bdb8bdd17fd91acff36dc5b7f0595a735929c514c40c402416bbb87c03f6fb782373 - languageName: node - linkType: hard - "yargs@npm:^16.2.0": version: 16.2.0 resolution: "yargs@npm:16.2.0" From 872daa9da88e2cbcbad43550d50efdab90ed886d Mon Sep 17 00:00:00 2001 From: Anastasia Beglova Date: Thu, 14 Mar 2024 09:40:24 -0400 Subject: [PATCH 2/2] move filters to course search util (#1359) * move filters to course search util --- package.json | 2 +- www/assets/css/search-filter.scss | 7 +- www/assets/js/LearningResources.ts | 5 - www/assets/js/components/Facet.tsx | 84 --------- .../js/components/FacetDisplay.test.tsx | 99 ----------- www/assets/js/components/FacetDisplay.tsx | 168 ------------------ www/assets/js/components/FilterableFacet.tsx | 132 -------------- www/assets/js/components/SearchFacetItem.tsx | 46 ----- .../js/components/SearchFilter.test.tsx | 34 ---- www/assets/js/components/SearchFilter.tsx | 27 --- .../js/components/SearchFilterDrawer.test.tsx | 2 +- .../js/components/SearchFilterDrawer.tsx | 12 +- www/assets/js/components/SearchPage.test.tsx | 12 +- www/assets/js/components/SearchPage.tsx | 72 +++++--- yarn.lock | 36 ++-- 15 files changed, 99 insertions(+), 639 deletions(-) delete mode 100644 www/assets/js/components/Facet.tsx delete mode 100644 www/assets/js/components/FacetDisplay.test.tsx delete mode 100644 www/assets/js/components/FacetDisplay.tsx delete mode 100644 www/assets/js/components/FilterableFacet.tsx delete mode 100644 www/assets/js/components/SearchFacetItem.tsx delete mode 100644 www/assets/js/components/SearchFilter.test.tsx delete mode 100644 www/assets/js/components/SearchFilter.tsx diff --git a/package.json b/package.json index 8b9052c18..41769cef2 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ }, "browserslist": "> 0.25%, not dead", "dependencies": { - "@mitodl/course-search-utils": "^3.0.0", + "@mitodl/course-search-utils": "^3.0.2", "@sentry/browser": "^5.19.0", "array-flat-polyfill": "^1.0.1", "bootstrap": "^4.3.1", diff --git a/www/assets/css/search-filter.scss b/www/assets/css/search-filter.scss index fa4479f90..edb935630 100644 --- a/www/assets/css/search-filter.scss +++ b/www/assets/css/search-filter.scss @@ -1,4 +1,5 @@ .filterable-facet { + margin-bottom: 1rem !important; .input-wrapper { position: relative; @@ -46,6 +47,10 @@ background-color: #f6f6f6; width: 100%; border: none; + padding-left: 1rem !important; + padding-top: 0.5rem !important; + padding-bottom: 0.5rem !important; + padding-right: 0 !important; } .filter-section-main-title { @@ -96,7 +101,7 @@ cursor: pointer; } - .facet-label-div { + .facet-label { display: flex; flex-direction: row; justify-content: space-between; diff --git a/www/assets/js/LearningResources.ts b/www/assets/js/LearningResources.ts index 6a0b2d819..3dddb8e47 100644 --- a/www/assets/js/LearningResources.ts +++ b/www/assets/js/LearningResources.ts @@ -1,6 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable camelcase */ -import { Facets } from "@mitodl/course-search-utils" import { COURSE_ARCHIVED, COURSE_CURRENT, @@ -147,7 +146,3 @@ export interface LearningResource { description?: string | null course_feature_tags?: string[] } - -export type FacetKey = keyof Facets - -export type FacetManifest = [FacetKey, string, boolean, boolean][] diff --git a/www/assets/js/components/Facet.tsx b/www/assets/js/components/Facet.tsx deleted file mode 100644 index ae6220cf7..000000000 --- a/www/assets/js/components/Facet.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import React, { useState } from "react" -import { contains } from "ramda" - -import SearchFacetItem from "./SearchFacetItem" -import { Aggregation } from "@mitodl/course-search-utils" - -const MAX_DISPLAY_COUNT = 5 -const FACET_COLLAPSE_THRESHOLD = 15 - -interface Props { - name: string - title: string - results: Aggregation | null - currentlySelected: string[] - onUpdate: React.ChangeEventHandler - expandedOnLoad: boolean - labelFunction?: ((value: string) => string | null) | null -} - -function SearchFacet(props: Props) { - const { - name, - title, - results, - currentlySelected, - onUpdate, - expandedOnLoad, - labelFunction - } = props - - const [showFacetList, setShowFacetList] = useState(expandedOnLoad) - const [showAllFacets, setShowAllFacets] = useState(false) - - const titleLineIcon = showFacetList ? "arrow_drop_down" : "arrow_right" - - return results && results.length === 0 ? null : ( -
    - - {showFacetList ? ( - - {results ? - results.map((facet, i) => - showAllFacets || - i < MAX_DISPLAY_COUNT || - results.length < FACET_COLLAPSE_THRESHOLD ? ( - - ) : null - ) : - null} - {results && results.length >= FACET_COLLAPSE_THRESHOLD ? ( - - ) : null} - - ) : null} -
    - ) -} - -export default SearchFacet diff --git a/www/assets/js/components/FacetDisplay.test.tsx b/www/assets/js/components/FacetDisplay.test.tsx deleted file mode 100644 index 627efb0a0..000000000 --- a/www/assets/js/components/FacetDisplay.test.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import React from "react" -import { shallow } from "enzyme" - -import FacetDisplay from "./FacetDisplay" -import { FacetManifest } from "../LearningResources" -import { Facets } from "@mitodl/course-search-utils" - -describe("FacetDisplay component", () => { - const facetMap: FacetManifest = [ - ["topic", "Topics", false, false], - ["resource_type", "Types", false, false], - ["department", "Departments", false, true], - ["level", "Level", false, true] - ] - - function setup() { - const activeFacets = {} - const facetOptions = jest.fn() - const onUpdateFacets = jest.fn() - const clearAllFilters = jest.fn() - const toggleFacet = jest.fn() - - const render = (props = {}) => - shallow( - - ) - return { render, clearAllFilters } - } - - test("renders a FacetDisplay with expected FilterableFacets", async () => { - const { render } = setup() - const wrapper = render() - const facets = wrapper.children() - expect(facets).toHaveLength(5) - facets.slice(1, 5).map((facet, key) => { - expect(facet.prop("name")).toBe(facetMap[key][0]) - expect(facet.prop("title")).toBe(facetMap[key][1]) - expect(facet.prop("expandedOnLoad")).toBe(facetMap[key][3]) - }) - }) - - test("shows filters which are active excluding invalid facet values", () => { - const activeFacets: Facets = { - topic: [ - "Academic Writing", - "Accounting", - "Aerodynamics", - "Pasta", - "Bread", - "Starch" - ], - resource_type: [], - department: ["1", "2"] - } - - const { render, clearAllFilters } = setup() - const wrapper = render({ - activeFacets - }) - expect( - wrapper - .find(".active-search-filters") - .find("SearchFilter") - .map(el => el.prop("value")) - ).toEqual(["Academic Writing", "Accounting", "Aerodynamics", "1", "2"]) - wrapper.find(".clear-all-filters-button").simulate("click") - expect(clearAllFilters).toHaveBeenCalled() - }) - - test("accepts department and level names and converts them to codes", () => { - const activeFacets: Facets = { - topic: [], - resource_type: [], - department: ["1", "Literature"], - level: ["graduate", "Non-Credit"] - } - - const { render, clearAllFilters } = setup() - const wrapper = render({ - activeFacets - }) - expect( - wrapper - .find(".active-search-filters") - .find("SearchFilter") - .map(el => el.prop("value")) - ).toEqual(["1", "21L", "graduate", "noncredit"]) - wrapper.find(".clear-all-filters-button").simulate("click") - expect(clearAllFilters).toHaveBeenCalled() - }) -}) diff --git a/www/assets/js/components/FacetDisplay.tsx b/www/assets/js/components/FacetDisplay.tsx deleted file mode 100644 index dffdd48e7..000000000 --- a/www/assets/js/components/FacetDisplay.tsx +++ /dev/null @@ -1,168 +0,0 @@ -import React from "react" - -import FilterableFacet from "./FilterableFacet" -import Facet from "./Facet" -import SearchFilter from "./SearchFilter" -import { - Aggregation, - Facets, - LEVELS, - DEPARTMENTS -} from "@mitodl/course-search-utils" -import type { FacetManifest } from "../LearningResources" -import { FACET_OPTIONS } from "../lib/constants" - -interface Props { - facetMap: FacetManifest - facetOptions: (group: string) => Aggregation | null - activeFacets: Facets - onUpdateFacets: React.ChangeEventHandler - clearAllFilters: () => void - toggleFacet: (name: string, value: string, isEnabled: boolean) => void -} - -const reverseObject = ( - stringObject: Record -): Record => { - return Object.fromEntries( - Object.entries(stringObject).map(([key, value]) => [value, key]) - ) -} - -const sanitizeActiveFacets = (activeFacets: Facets): void => { - const reverseLevels = reverseObject(LEVELS) - const reverseDepartments = reverseObject(DEPARTMENTS) - - if (activeFacets) { - Object.entries(activeFacets).forEach(([facet, values]) => { - if (Object.keys(FACET_OPTIONS).indexOf(facet) > -1) { - activeFacets[facet as keyof typeof activeFacets] = values.flatMap( - (facetValue: string) => { - if ( - // @ts-expect-error we checked that facet is also a key of FACET_OPTION - FACET_OPTIONS[facet as keyof typeof FACET_OPTIONS].indexOf( - facetValue - ) > -1 - ) { - return facetValue - } else if (facet === "level" && facetValue in reverseLevels) { - return reverseLevels[facetValue] - } else if ( - facet === "department" && - facetValue in reverseDepartments - ) { - return reverseDepartments[facetValue] - } else { - return [] - } - } - ) - } - }) - } -} - -const departmentName = (departmentId: string): string | null => { - if (departmentId in DEPARTMENTS) { - return DEPARTMENTS[departmentId as keyof typeof DEPARTMENTS] - } else { - return departmentId - } -} - -const levelName = (levelValue: string): string | null => { - if (levelValue in LEVELS) { - return LEVELS[levelValue as keyof typeof LEVELS] - } else { - return levelValue - } -} - -const labels = (name: string): ((value: string) => string | null) | null => { - if (name === "department") { - return departmentName - } else if (name === "level") { - return levelName - } else { - return null - } -} - -const FacetDisplay = React.memo( - function FacetDisplay(props: Props) { - const { - facetMap, - facetOptions, - activeFacets, - onUpdateFacets, - clearAllFilters, - toggleFacet - } = props - - sanitizeActiveFacets(activeFacets) - - return ( - -
    -
    - Filters - -
    - {facetMap.map(([name]) => - (activeFacets[name] || []).map((facet, i) => ( - toggleFacet(name, facet, false)} - labelFunction={labels(name)} - /> - )) - )} -
    - {facetMap.map( - ([name, title, useFilterableFacet, expandedOnLoad], key) => - useFilterableFacet ? ( - - ) : ( - - ) - )} -
    - ) - }, - (prevProps, nextProps) => { - return ( - prevProps.activeFacets === nextProps.activeFacets && - prevProps.clearAllFilters === nextProps.clearAllFilters && - prevProps.toggleFacet === nextProps.toggleFacet && - prevProps.facetOptions === nextProps.facetOptions && - prevProps.onUpdateFacets === nextProps.onUpdateFacets - ) - } -) - -export default FacetDisplay diff --git a/www/assets/js/components/FilterableFacet.tsx b/www/assets/js/components/FilterableFacet.tsx deleted file mode 100644 index 644be026d..000000000 --- a/www/assets/js/components/FilterableFacet.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import React, { useState, useEffect, useCallback } from "react" -import { contains } from "ramda" -import Fuse from "fuse.js" - -import SearchFacetItem from "./SearchFacetItem" -import { Aggregation, Bucket } from "@mitodl/course-search-utils" - -// the `.search method returns records like { item, refindex } -// where item is the facet and refIndex is it's index in the original -// array. this helper just pulls out only the facets themselves -const runSearch = (searcher: Fuse, text: string) => - searcher.search(text).map(({ item }) => item) - -interface Props { - name: string - title: string - results: Aggregation | null - currentlySelected: string[] - onUpdate: React.ChangeEventHandler - expandedOnLoad: boolean - labelFunction?: ((value: string) => string | null) | null -} - -function FilterableSearchFacet(props: Props) { - const { - name, - title, - results, - currentlySelected, - onUpdate, - expandedOnLoad, - labelFunction - } = props - const [showFacetList, setShowFacetList] = useState(expandedOnLoad) - - // null is signal for no input yet or cleared input - const [filteredList, setFilteredList] = useState(null) - const [searcher, setSearcher] = useState>(new Fuse([])) - - const [filterText, setFilterText] = useState("") - - useEffect(() => { - if (results) { - const searcher = new Fuse(results, { - keys: ["key"], - threshold: 0.4 - }) - setSearcher(searcher) - } - }, [results]) - - useEffect(() => { - if (filterText === "") { - setFilteredList(null) - } else { - setFilteredList(runSearch(searcher, filterText)) - } - }, [searcher, filterText]) - - const handleFilterInput = useCallback(e => { - e.preventDefault() - const filterText = e.target.value - setFilterText(filterText) - }, []) - - const titleLineIcon = showFacetList ? "arrow_drop_down" : "arrow_right" - - const facets = (filteredList || results) ?? [] - return results && results.length === 0 ? null : ( -
    - - {showFacetList ? ( - <> -
    - - {filterText === "" ? ( - - ) : ( - - )} -
    -
    - {facets.map((facet, i) => ( - - ))} -
    - - ) : null} -
    - ) -} - -export default FilterableSearchFacet diff --git a/www/assets/js/components/SearchFacetItem.tsx b/www/assets/js/components/SearchFacetItem.tsx deleted file mode 100644 index 558bf966a..000000000 --- a/www/assets/js/components/SearchFacetItem.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import React from "react" -import Dotdotdot from "react-dotdotdot" -import { Bucket } from "@mitodl/course-search-utils" - -import { slugify } from "../lib/util" - -const featuredFacetNames = ["audience", "certification"] - -interface Props { - facet: Bucket - isChecked: boolean - onUpdate: React.ChangeEventHandler - name: string - displayKey: string | null -} - -export default function SearchFacetItem(props: Props) { - const { facet, isChecked, onUpdate, name, displayKey } = props - - const facetId = slugify(`${name}-${facet.key}`) - return ( -
    - -
    - -
    {facet.doc_count}
    -
    -
    - ) -} diff --git a/www/assets/js/components/SearchFilter.test.tsx b/www/assets/js/components/SearchFilter.test.tsx deleted file mode 100644 index e907d8ba4..000000000 --- a/www/assets/js/components/SearchFilter.test.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React from "react" -import upperCase from "lodash.uppercase" -import { shallow } from "enzyme" - -import SearchFilter from "./SearchFilter" - -describe("SearchFilter", () => { - function setup() { - const onClickStub = jest.fn() - - const render = (props = {}) => - shallow() - - return { render, onClickStub } - } - - it("should render a search filter correctly", () => { - const value = "Upcoming" - const { render } = setup() - const wrapper = render({ - value, - labelFunction: upperCase - }) - const label = wrapper.text() - expect(label.includes(upperCase(value))).toBeTruthy() - }) - - it("should trigger clearFacet function on click", async () => { - const { render, onClickStub } = setup() - const wrapper = render({ value: "ocw" }) - wrapper.find(".remove-filter-button").simulate("click") - expect(onClickStub).toHaveBeenCalledTimes(1) - }) -}) diff --git a/www/assets/js/components/SearchFilter.tsx b/www/assets/js/components/SearchFilter.tsx deleted file mode 100644 index 901ee7038..000000000 --- a/www/assets/js/components/SearchFilter.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from "react" - -interface Props { - value: string - labelFunction?: ((value: string) => string | null) | null - clearFacet: () => void -} - -export default function SearchFilter(props: Props) { - const { value, clearFacet, labelFunction } = props - - return ( -
    -
    {labelFunction ? labelFunction(value) : value}
    - -
    - ) -} diff --git a/www/assets/js/components/SearchFilterDrawer.test.tsx b/www/assets/js/components/SearchFilterDrawer.test.tsx index eeff0aa09..1931b7e85 100644 --- a/www/assets/js/components/SearchFilterDrawer.test.tsx +++ b/www/assets/js/components/SearchFilterDrawer.test.tsx @@ -2,7 +2,7 @@ import React from "react" import { shallow } from "enzyme" import SearchFilterDrawer from "./SearchFilterDrawer" -import FacetDisplay from "./FacetDisplay" +import { FacetDisplay } from "@mitodl/course-search-utils" import * as utils from "../lib/util" jest.mock("../lib/util", () => { diff --git a/www/assets/js/components/SearchFilterDrawer.tsx b/www/assets/js/components/SearchFilterDrawer.tsx index 0c9dabfcb..93938bd32 100644 --- a/www/assets/js/components/SearchFilterDrawer.tsx +++ b/www/assets/js/components/SearchFilterDrawer.tsx @@ -1,10 +1,13 @@ import React, { useCallback, useState } from "react" -import FacetDisplay from "./FacetDisplay" import { DESKTOP } from "../lib/constants" import { useDeviceCategory } from "../hooks/util" -import { Aggregation, Facets } from "@mitodl/course-search-utils" -import { FacetManifest } from "../LearningResources" +import { + Aggregation, + Facets, + FacetManifest, + FacetDisplay +} from "@mitodl/course-search-utils" import { SEARCH_COMPACT_UI } from "../lib/constants" import Footer from "./Footer" @@ -12,9 +15,8 @@ interface Props { facetMap: FacetManifest facetOptions: (group: string) => Aggregation | null activeFacets: Facets - onUpdateFacets: React.ChangeEventHandler clearAllFilters: () => void - toggleFacet: (name: string, value: string, isEnabled: boolean) => void + onFacetChange: (name: string, value: string, isEnabled: boolean) => void updateUI: (newUI: string | null) => void } diff --git a/www/assets/js/components/SearchPage.test.tsx b/www/assets/js/components/SearchPage.test.tsx index 0a8810fc7..96e0cac68 100644 --- a/www/assets/js/components/SearchPage.test.tsx +++ b/www/assets/js/components/SearchPage.test.tsx @@ -7,7 +7,8 @@ import { INITIAL_FACET_STATE, serializeSearchParams, LEARNING_RESOURCE_ENDPOINT, - CONTENT_FILE_ENDPOINT + CONTENT_FILE_ENDPOINT, + FilterableFacet } from "@mitodl/course-search-utils" import InfiniteScroll from "react-infinite-scroller" @@ -17,7 +18,6 @@ import { DEFAULT_UI_PAGE_SIZE, COMPACT_UI_PAGE_SIZE } from "../lib/constants" import { makeCourseSearchResult } from "../factories/search" import { createMemoryHistory, InitialEntry } from "history" -import FilterableSearchFacets from "./FilterableFacet" const mockGetResults = () => times(makeCourseSearchResult, DEFAULT_UI_PAGE_SIZE) @@ -402,19 +402,19 @@ describe("SearchPage component", () => { await resolveSearch() wrapper.update() - const facets = wrapper.find(FilterableSearchFacets) + const facets = wrapper.find(FilterableFacet) const department = facets.at(0) const topic = facets.at(1) const features = facets.at(2) expect(topic.props().name).toEqual("topic") expect(topic.props().title).toEqual("Topics") - expect(topic.props().currentlySelected).toEqual([]) + expect(topic.props().selected).toEqual([]) expect(features.props().name).toEqual("course_feature") expect(features.props().title).toEqual("Features") - expect(features.props().currentlySelected).toEqual([]) + expect(features.props().selected).toEqual([]) expect(department.props().name).toEqual("department") expect(department.props().title).toEqual("Departments") - expect(department.props().currentlySelected).toEqual([]) + expect(department.props().selected).toEqual([]) }) }) diff --git a/www/assets/js/components/SearchPage.tsx b/www/assets/js/components/SearchPage.tsx index 7d13d5c4e..57b4ff4c5 100644 --- a/www/assets/js/components/SearchPage.tsx +++ b/www/assets/js/components/SearchPage.tsx @@ -7,7 +7,11 @@ import { CONTENT_FILE_ENDPOINT, Aggregations, CourseResource, - ContentFile + ContentFile, + FacetManifest, + getDepartmentName, + getLevelName, + sanitizeFacets } from "@mitodl/course-search-utils" import { without } from "ramda" @@ -28,21 +32,53 @@ import { SEARCH_COMPACT_UI, DEFAULT_UI_PAGE_SIZE, COMPACT_UI_PAGE_SIZE, - OCW_OFFEROR + OCW_OFFEROR, + FACET_OPTIONS } from "../lib/constants" import { emptyOrNil, isDoubleQuoted } from "../lib/util" -import { FacetManifest } from "../LearningResources" const COURSE_FACETS: FacetManifest = [ - ["department", "Departments", true, true], - ["level", "Level", false, false], - ["topic", "Topics", true, false], - ["course_feature", "Features", true, false] + { + name: "department", + title: "Departments", + useFilterableFacet: true, + expandedOnLoad: true, + labelFunction: getDepartmentName + }, + { + name: "level", + title: "Level", + useFilterableFacet: false, + expandedOnLoad: false, + labelFunction: getLevelName + }, + { + name: "topic", + title: "Topics", + useFilterableFacet: true, + expandedOnLoad: false + }, + { + name: "course_feature", + title: "Features", + useFilterableFacet: true, + expandedOnLoad: false + } ] const RESOURCE_FACETS: FacetManifest = [ - ["content_feature_type", "Resource Types", true, false], - ["topic", "Topics", true, false] + { + name: "content_feature_type", + title: "Resource Types", + useFilterableFacet: true, + expandedOnLoad: false + }, + { + name: "topic", + title: "Topics", + useFilterableFacet: true, + expandedOnLoad: false + } ] type SearchPageProps = { @@ -63,6 +99,7 @@ export default function SearchPage(props: SearchPageProps) { const runSearch = useCallback( async (text, activeFacets, from, sort, ui, endpoint) => { + sanitizeFacets(activeFacets, FACET_OPTIONS) activeFacets["offered_by"] = [OCW_OFFEROR] if (activeFacets && activeFacets.type && activeFacets.type.length > 1) { // Default is LR_TYPE_ALL, don't want that here. course or resourcefile only @@ -72,7 +109,7 @@ export default function SearchPage(props: SearchPageProps) { const relevantFacets = endpoint === CONTENT_FILE_ENDPOINT ? RESOURCE_FACETS : COURSE_FACETS - const allowedAggregations = relevantFacets.map(facet => facet[0]) + const allowedAggregations = relevantFacets.map(facet => facet.name) setRequestInFlight(true) @@ -149,7 +186,6 @@ export default function SearchPage(props: SearchPageProps) { const { facetOptions, - onUpdateFacets, updateText, updateSort, loadMore, @@ -187,16 +223,13 @@ export default function SearchPage(props: SearchPageProps) { const toggledFacets: [string, string, boolean][] = [ ["resource_type", LearningResourceType.Course, !nextResourceFilterState] ] - // Remove any facets not relevant to the new search type - const newFacets: Map = new Map( - // @ts-expect-error We should clean this up. It works because Map constructor is ignoring everything except 0th, 1st item in the entries array. + const newFacets: string[] = nextResourceFilterState === CONTENT_FILE_ENDPOINT ? - RESOURCE_FACETS : - COURSE_FACETS - ) + RESOURCE_FACETS.map(facet => facet.name) : + COURSE_FACETS.map(facet => facet.name) Object.entries(activeFacets).forEach(([key, list]) => { - if (!newFacets.has(key) && !emptyOrNil(list)) { + if (!newFacets.includes(key) && !emptyOrNil(list)) { list.forEach((value: string) => { toggledFacets.push([key, value, false]) }) @@ -244,9 +277,8 @@ export default function SearchPage(props: SearchPageProps) { facetMap={facetMap} facetOptions={facetOptions} activeFacets={activeFacets} - onUpdateFacets={onUpdateFacets} clearAllFilters={clearAllFilters} - toggleFacet={toggleFacet} + onFacetChange={toggleFacet} updateUI={updateUI} />
    diff --git a/yarn.lock b/yarn.lock index af17a090b..74b5fd15e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1227,13 +1227,15 @@ __metadata: languageName: node linkType: hard -"@mitodl/course-search-utils@npm:^3.0.0": - version: 3.0.0 - resolution: "@mitodl/course-search-utils@npm:3.0.0" +"@mitodl/course-search-utils@npm:^3.0.2": + version: 3.0.2 + resolution: "@mitodl/course-search-utils@npm:3.0.2" dependencies: axios: ^1.6.7 + fuse.js: ^7.0.0 query-string: ^6.13.1 ramda: ^0.27.1 + react-dotdotdot: ^1.3.1 peerDependencies: "@types/history": ^4.9 history: ^4.9 || ^5.0.0 @@ -1243,7 +1245,7 @@ __metadata: optional: true history: optional: true - checksum: 14000a21b0b81f3d3023f53f7b2a8e417d7c815f2d2fa627cdc4406a8aff63f30c4fe79f7ecee6425cabd4fe8c5a468c6412682e9e3e021b599a1537972f793a + checksum: ba96edd34aec6592c6f9d6c8921e1a46a90da66afb4c4fa7519a0199e37cc5cfcc3d9b257f37b0ddcd474684a421dbc25d4adaad5fa6999b8db3d6e759953111 languageName: node linkType: hard @@ -5294,9 +5296,9 @@ __metadata: linkType: hard "decode-uri-component@npm:^0.2.0": - version: 0.2.0 - resolution: "decode-uri-component@npm:0.2.0" - checksum: f3749344ab9305ffcfe4bfe300e2dbb61fc6359e2b736812100a3b1b6db0a5668cba31a05e4b45d4d63dbf1a18dfa354cd3ca5bb3ededddabb8cd293f4404f94 + version: 0.2.2 + resolution: "decode-uri-component@npm:0.2.2" + checksum: 95476a7d28f267292ce745eac3524a9079058bbb35767b76e3ee87d42e34cd0275d2eb19d9d08c3e167f97556e8a2872747f5e65cbebcac8b0c98d83e285f139 languageName: node linkType: hard @@ -7357,6 +7359,13 @@ __metadata: languageName: node linkType: hard +"fuse.js@npm:^7.0.0": + version: 7.0.0 + resolution: "fuse.js@npm:7.0.0" + checksum: d15750efec1808370c0cae92ec9473aa7261c59bca1f15f1cf60039ba6f804b8f95340b5cabd83a4ef55839c1034764856e0128e443921f072aa0d8a20e4cacf + languageName: node + linkType: hard + "gauge@npm:^4.0.3": version: 4.0.4 resolution: "gauge@npm:4.0.4" @@ -11129,7 +11138,7 @@ __metadata: version: 0.0.0-use.local resolution: "ocw-hugo-themes@workspace:." dependencies: - "@mitodl/course-search-utils": ^3.0.0 + "@mitodl/course-search-utils": ^3.0.2 "@playwright/test": ^1.40.0 "@sentry/browser": ^5.19.0 "@swc/core": ^1.3.14 @@ -12908,13 +12917,20 @@ __metadata: languageName: node linkType: hard -"ramda@npm:^0.27.0, ramda@npm:^0.27.1": +"ramda@npm:^0.27.0": version: 0.27.1 resolution: "ramda@npm:0.27.1" checksum: 31a0c0ef739b2525d7615f84cbb5d3cb89ee0c795469b711f729ea1d8df0dccc3cd75d3717a1e9742d42315ce86435680b7c87743eb7618111c60c144a5b8059 languageName: node linkType: hard +"ramda@npm:^0.27.1": + version: 0.27.2 + resolution: "ramda@npm:0.27.2" + checksum: 28d6735dd1eea1a796c56cf6111f3673c6105bbd736e521cdd7826c46a18eeff337c2dba4668f6eed990d539b9961fd6db19aa46ccc1530ba67a396c0a9f580d + languageName: node + linkType: hard + "randexp@npm:0.4.6": version: 0.4.6 resolution: "randexp@npm:0.4.6" @@ -13012,7 +13028,7 @@ __metadata: languageName: node linkType: hard -"react-dotdotdot@npm:1.3.1": +"react-dotdotdot@npm:1.3.1, react-dotdotdot@npm:^1.3.1": version: 1.3.1 resolution: "react-dotdotdot@npm:1.3.1" dependencies: