diff --git a/RELEASE.rst b/RELEASE.rst index 296f6daec5..f5420d58f2 100644 --- a/RELEASE.rst +++ b/RELEASE.rst @@ -1,6 +1,15 @@ Release Notes ============= +Version 0.20.4 +-------------- + +- add is_incomplete_or_stale to default sort (#1641) +- set default minimum score cutoff (#1642) +- Adds base infra for the Unified Ecommerce frontend (#1634) +- reset search page in SearchField (#1647) +- updating email template with new logo (#1638) + Version 0.20.3 (Released October 03, 2024) -------------- diff --git a/data_fixtures/migrations/0015_unit_page_copy_updates.py b/data_fixtures/migrations/0015_unit_page_copy_updates.py new file mode 100644 index 0000000000..769ad6cc9d --- /dev/null +++ b/data_fixtures/migrations/0015_unit_page_copy_updates.py @@ -0,0 +1,61 @@ +# Generated by Django 4.2.14 on 2024-07-16 17:30 + +from django.db import migrations + +fixtures = [ + { + "name": "mitpe", + "offeror_configuration": { + "value_prop": ( + "MIT Professional Education is a leader in technology and " + "engineering education for working professionals pursuing " + "career advancement, and organizations seeking to meet modern-day " + "challenges by expanding the knowledge and skills of their employees. " + "Courses are delivered in a range of formats—in-person (on-campus " + "and live online), online, and through hybrid approaches—to " + "meet the needs of today's learners." + ), + }, + "channel_configuration": { + "sub_heading": ( + "MIT Professional Education is a leader in technology and " + "engineering education for working professionals pursuing " + "career advancement, and organizations seeking to meet modern-day " + "challenges by expanding the knowledge and skills of their employees. " + "Courses are delivered in a range of formats—in-person (on-campus " + "and live online), online, and through hybrid approaches—to " + "meet the needs of today's learners." + ), + }, + }, +] + + +def update_copy(apps, schema_editor): + Channel = apps.get_model("channels", "Channel") + LearningResourceOfferor = apps.get_model( + "learning_resources", "LearningResourceOfferor" + ) + for fixture in fixtures: + channel_configuration_updates = fixture["channel_configuration"] + offeror_configuration_updates = fixture["offeror_configuration"] + channel = Channel.objects.get(name=fixture["name"]) + if Channel.objects.filter(name=fixture["name"]).exists(): + for key, val in channel_configuration_updates.items(): + channel.configuration[key] = val + channel.save() + if LearningResourceOfferor.objects.filter(code=fixture["name"]).exists(): + offeror = LearningResourceOfferor.objects.get(code=fixture["name"]) + for key, val in offeror_configuration_updates.items(): + setattr(offeror, key, val) + offeror.save() + + +class Migration(migrations.Migration): + dependencies = [ + ("data_fixtures", "0014_add_department_SP"), + ] + + operations = [ + migrations.RunPython(update_copy, migrations.RunPython.noop), + ] diff --git a/frontends/mit-learn/public/images/mit-block-logo.jpg b/frontends/mit-learn/public/images/mit-block-logo.jpg new file mode 100644 index 0000000000..5e61d900e8 Binary files /dev/null and b/frontends/mit-learn/public/images/mit-block-logo.jpg differ diff --git a/frontends/mit-learn/public/images/mit-learn-logo.jpg b/frontends/mit-learn/public/images/mit-learn-logo.jpg new file mode 100644 index 0000000000..488eae1e1b Binary files /dev/null and b/frontends/mit-learn/public/images/mit-learn-logo.jpg differ diff --git a/frontends/mit-learn/public/images/mit-logo-learn.jpg b/frontends/mit-learn/public/images/mit-logo-learn.jpg deleted file mode 100644 index eb5b0bc618..0000000000 Binary files a/frontends/mit-learn/public/images/mit-logo-learn.jpg and /dev/null differ diff --git a/frontends/mit-learn/src/common/feature_flags.ts b/frontends/mit-learn/src/common/feature_flags.ts new file mode 100644 index 0000000000..8d6e479d1e --- /dev/null +++ b/frontends/mit-learn/src/common/feature_flags.ts @@ -0,0 +1,6 @@ +// Feature flags for the app. These should correspond to the flag that's set up +// in PostHog. + +export enum FeatureFlags { + EnableEcommerce = "enable-ecommerce", +} diff --git a/frontends/mit-learn/src/common/urls.ts b/frontends/mit-learn/src/common/urls.ts index 8dad6174e8..363bced253 100644 --- a/frontends/mit-learn/src/common/urls.ts +++ b/frontends/mit-learn/src/common/urls.ts @@ -127,3 +127,5 @@ export const SEARCH_PROGRAM = querifiedSearchUrl({ export const SEARCH_LEARNING_MATERIAL = querifiedSearchUrl({ resource_category: "learning_material", }) + +export const ECOMMERCE_CART = "/cart/" as const diff --git a/frontends/mit-learn/src/page-components/EcommerceFeature/EcommerceFeature.tsx b/frontends/mit-learn/src/page-components/EcommerceFeature/EcommerceFeature.tsx new file mode 100644 index 0000000000..0cbb0a2edf --- /dev/null +++ b/frontends/mit-learn/src/page-components/EcommerceFeature/EcommerceFeature.tsx @@ -0,0 +1,33 @@ +import React from "react" +import { useFeatureFlagEnabled } from "posthog-js/react" +import { ForbiddenError } from "@/common/permissions" +import { FeatureFlags } from "@/common/feature_flags" + +type EcommerceFeatureProps = { + children: React.ReactNode +} + +/** + * Simple wrapper to standardize the feature flag check for ecommerce UI pages. + * If the flag is enabled, display the children; if not, throw a ForbiddenError + * like you'd get for an unauthenticated route. + * + * There's a PostHogFeature component that is provided but went this route + * because it seemed to be inconsistent - sometimes having the flag enabled + * resulted in it tossing to the error page. + * + * Set the feature flag here using the enum, and then make sure it's also + * defined in commmon/feature_flags too. + */ + +const EcommerceFeature: React.FC = ({ children }) => { + const ecommFlag = useFeatureFlagEnabled(FeatureFlags.EnableEcommerce) + + if (ecommFlag === false) { + throw new ForbiddenError("Not enabled.") + } + + return ecommFlag ? children : null +} + +export default EcommerceFeature diff --git a/frontends/mit-learn/src/page-components/SearchField/SearchField.tsx b/frontends/mit-learn/src/page-components/SearchField/SearchField.tsx new file mode 100644 index 0000000000..5de4ede3d4 --- /dev/null +++ b/frontends/mit-learn/src/page-components/SearchField/SearchField.tsx @@ -0,0 +1,38 @@ +import React from "react" +import { SearchInput } from "ol-components" +import type { SearchInputProps, SearchSubmissionEvent } from "ol-components" +import { usePostHog } from "posthog-js/react" + +type SearchFieldProps = SearchInputProps & { + onSubmit: (event: SearchSubmissionEvent) => void + setPage: (page: number) => void +} + +const { POSTHOG } = APP_SETTINGS + +/** + * A wrapper around SearchInput that handles a little application logic like + * - resetting search page to 1 on submission + * - firing tracking events + */ +const SearchField: React.FC = ({ + onSubmit, + setPage, + ...others +}) => { + const posthog = usePostHog() + const handleSubmit: SearchInputProps["onSubmit"] = ( + event, + { isEnter } = {}, + ) => { + onSubmit(event) + setPage(1) + if (POSTHOG?.api_key) { + posthog.capture("search_update", { isEnter: isEnter }) + } + } + + return +} + +export { SearchField } diff --git a/frontends/mit-learn/src/pages/ChannelPage/ChannelSearch.test.tsx b/frontends/mit-learn/src/pages/ChannelPage/ChannelSearch.test.tsx index 8063466e3e..0e77812442 100644 --- a/frontends/mit-learn/src/pages/ChannelPage/ChannelSearch.test.tsx +++ b/frontends/mit-learn/src/pages/ChannelPage/ChannelSearch.test.tsx @@ -1,4 +1,4 @@ -import { screen, within, waitFor, renderTestApp } from "@/test-utils" +import { screen, within, waitFor, renderTestApp, user } from "@/test-utils" import { setMockResponse, urls, factories, makeRequest } from "api/test-utils" import type { LearningResourcesSearchResponse } from "api" import invariant from "tiny-invariant" @@ -264,4 +264,45 @@ describe("ChannelSearch", () => { } }, ) + + test("Submitting search text updates URL correctly", async () => { + const resources = factories.learningResources.resources({ + count: 10, + }).results + const { channel } = setMockApiResponses({ + search: { + count: 1000, + metadata: { + aggregations: { + resource_type: [ + { key: "course", doc_count: 100 }, + { key: "podcast", doc_count: 200 }, + { key: "program", doc_count: 300 }, + { key: "irrelevant", doc_count: 400 }, + ], + }, + suggestions: [], + }, + results: resources, + }, + }) + setMockResponse.get(urls.userMe.get(), {}) + + const initialSearch = "?q=meow&page=2" + const finalSearch = "?q=woof" + + const { location } = renderTestApp({ + url: `/c/${channel.channel_type}/${channel.name}${initialSearch}`, + }) + + const queryInput = await screen.findByRole("textbox", { + name: "Search for", + }) + expect(queryInput.value).toBe("meow") + await user.clear(queryInput) + await user.paste("woof") + expect(location.current.search).toBe(initialSearch) + await user.click(screen.getByRole("button", { name: "Search" })) + expect(location.current.search).toBe(finalSearch) + }) }) diff --git a/frontends/mit-learn/src/pages/ChannelPage/ChannelSearch.tsx b/frontends/mit-learn/src/pages/ChannelPage/ChannelSearch.tsx index 4b32b3c7ee..2e88ed9bac 100644 --- a/frontends/mit-learn/src/pages/ChannelPage/ChannelSearch.tsx +++ b/frontends/mit-learn/src/pages/ChannelPage/ChannelSearch.tsx @@ -14,7 +14,8 @@ import type { } from "@mitodl/course-search-utils" import { useSearchParams } from "@mitodl/course-search-utils/react-router" import SearchDisplay from "@/page-components/SearchDisplay/SearchDisplay" -import { Container, SearchInput, styled, VisuallyHidden } from "ol-components" +import { Container, styled, VisuallyHidden } from "ol-components" +import { SearchField } from "@/page-components/SearchField/SearchField" import { getFacetManifest } from "@/pages/SearchPage/SearchPage" @@ -30,7 +31,7 @@ const SearchInputContainer = styled(Container)(({ theme }) => ({ }, })) -const StyledSearchInput = styled(SearchInput)({ +const StyledSearchField = styled(SearchField)({ width: "624px", }) @@ -172,7 +173,7 @@ const ChannelSearch: React.FC = ({
Search within {channelTitle} - setCurrentText(e.target.value)} @@ -182,6 +183,7 @@ const ChannelSearch: React.FC = ({ onClear={() => { setCurrentTextAndQuery("") }} + setPage={setPage} /> diff --git a/frontends/mit-learn/src/pages/EcommercePages/CartPage.test.tsx b/frontends/mit-learn/src/pages/EcommercePages/CartPage.test.tsx new file mode 100644 index 0000000000..1a5eb127cc --- /dev/null +++ b/frontends/mit-learn/src/pages/EcommercePages/CartPage.test.tsx @@ -0,0 +1,65 @@ +import { renderTestApp, waitFor, setMockResponse } from "../../test-utils" +import { urls } from "api/test-utils" +import * as commonUrls from "@/common/urls" +import { Permissions } from "@/common/permissions" +import { login } from "@/common/urls" +import { useFeatureFlagEnabled } from "posthog-js/react" + +jest.mock("posthog-js/react") +const mockedUseFeatureFlagEnabled = jest.mocked(useFeatureFlagEnabled) + +const oldWindowLocation = window.location + +beforeAll(() => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + delete (window as any).location + + window.location = Object.defineProperties({} as Location, { + ...Object.getOwnPropertyDescriptors(oldWindowLocation), + assign: { + configurable: true, + value: jest.fn(), + }, + }) +}) + +afterAll(() => { + window.location = oldWindowLocation +}) + +describe("CartPage", () => { + ;["on", "off"].forEach((testCase: string) => { + test(`Renders when logged in and feature flag is ${testCase}`, async () => { + setMockResponse.get(urls.userMe.get(), { + [Permissions.Authenticated]: true, + }) + mockedUseFeatureFlagEnabled.mockReturnValue(testCase === "on") + + renderTestApp({ + url: commonUrls.ECOMMERCE_CART, + }) + await waitFor(() => { + testCase === "on" + ? expect(document.title).toBe("Shopping Cart | MIT Learn") + : expect(document.title).not.toBe("Shopping Cart | MIT Learn") + }) + }) + }) + + test("Sends to login page when logged out", async () => { + setMockResponse.get(urls.userMe.get(), { + [Permissions.Authenticated]: false, + }) + const expectedUrl = login({ + pathname: "/cart/", + }) + + renderTestApp({ + url: commonUrls.ECOMMERCE_CART, + }) + + await waitFor(() => { + expect(window.location.assign).toHaveBeenCalledWith(expectedUrl) + }) + }) +}) diff --git a/frontends/mit-learn/src/pages/EcommercePages/CartPage.tsx b/frontends/mit-learn/src/pages/EcommercePages/CartPage.tsx new file mode 100644 index 0000000000..59f9b94dd4 --- /dev/null +++ b/frontends/mit-learn/src/pages/EcommercePages/CartPage.tsx @@ -0,0 +1,31 @@ +import React from "react" +import { Breadcrumbs, Container, Typography } from "ol-components" +import EcommerceFeature from "@/page-components/EcommerceFeature/EcommerceFeature" +import MetaTags from "@/page-components/MetaTags/MetaTags" +import * as urls from "@/common/urls" + +const CartPage: React.FC = () => { + return ( + + + + + + + Shopping Cart + + + + The shopping cart layout should go here, if you're allowed to see + this. + + + + ) +} + +export default CartPage diff --git a/frontends/mit-learn/src/pages/EcommercePages/README.md b/frontends/mit-learn/src/pages/EcommercePages/README.md new file mode 100644 index 0000000000..878d3f0887 --- /dev/null +++ b/frontends/mit-learn/src/pages/EcommercePages/README.md @@ -0,0 +1,9 @@ +# Unified Ecommerce in MIT Learn + +The front end for the Unified Ecommerce system lives in MIT Learn. So, pages that exist here are designed to talk to Unified Ecommerce rather than to the Learn system. + +There's a few functional pieces here: + +- **Cart** - Displays the user's cart, and provides some additional functionality for that (item management, discount application, etc.) +- **Receipts** - Allows the user to display their order history and view receipts from their purchases, including historical ones from other systems. +- **Financial Assistance** - For learning resources that support it, the learner side of the financial assistance request system lives here. (Approvals do not.) diff --git a/frontends/mit-learn/src/pages/SearchPage/SearchPage.test.tsx b/frontends/mit-learn/src/pages/SearchPage/SearchPage.test.tsx index 697d3f9ae5..6370ad254a 100644 --- a/frontends/mit-learn/src/pages/SearchPage/SearchPage.test.tsx +++ b/frontends/mit-learn/src/pages/SearchPage/SearchPage.test.tsx @@ -201,25 +201,24 @@ describe("SearchPage", () => { await within(facetsContainer).findByText("Resource Type") }) - test.each([{ withPage: false }, { withPage: true }])( - "Submitting text updates URL", - async ({ withPage }) => { - setMockApiResponses({}) - const urlQueryString = withPage ? "?q=meow&page=2" : "?q=meow" - const { location } = renderWithProviders(, { - url: urlQueryString, - }) - const queryInput = await screen.findByRole("textbox", { - name: "Search for", - }) - expect(queryInput.value).toBe("meow") - await user.clear(queryInput) - await user.paste("woof") - expect(location.current.search).toBe(urlQueryString) - await user.click(screen.getByRole("button", { name: "Search" })) - expect(location.current.search).toBe("?q=woof") - }, - ) + test.each([ + { initialQuery: "?q=meow&page=2", finalQuery: "?q=woof" }, + { initialQuery: "?q=meow", finalQuery: "?q=woof" }, + ])("Submitting text updates URL", async ({ initialQuery, finalQuery }) => { + setMockApiResponses({}) + const { location } = renderWithProviders(, { + url: initialQuery, + }) + const queryInput = await screen.findByRole("textbox", { + name: "Search for", + }) + expect(queryInput.value).toBe("meow") + await user.clear(queryInput) + await user.paste("woof") + expect(location.current.search).toBe(initialQuery) + await user.click(screen.getByRole("button", { name: "Search" })) + expect(location.current.search).toBe(finalQuery) + }) test("unathenticated users do not see admin options", async () => { setMockApiResponses({ diff --git a/frontends/mit-learn/src/pages/SearchPage/SearchPage.tsx b/frontends/mit-learn/src/pages/SearchPage/SearchPage.tsx index 476945824c..1b3cfd56a7 100644 --- a/frontends/mit-learn/src/pages/SearchPage/SearchPage.tsx +++ b/frontends/mit-learn/src/pages/SearchPage/SearchPage.tsx @@ -9,13 +9,8 @@ import { getDepartmentName, } from "@mitodl/course-search-utils" import SearchDisplay from "@/page-components/SearchDisplay/SearchDisplay" -import { - SearchInput, - styled, - Container, - theme, - VisuallyHidden, -} from "ol-components" +import { styled, Container, theme, VisuallyHidden } from "ol-components" +import { SearchField } from "@/page-components/SearchField/SearchField" import type { LearningResourceOfferor } from "api" import { useOfferorsList } from "api/hooks/learningResources" import { capitalize } from "ol-utilities" @@ -56,7 +51,7 @@ const SearchFieldContainer = styled(Container)({ justifyContent: "center", }) -const SearchField = styled(SearchInput)(({ theme }) => ({ +const StyledSearchField = styled(SearchField)(({ theme }) => ({ [theme.breakpoints.down("sm")]: { width: "100%", }, @@ -216,14 +211,6 @@ const SearchPage: React.FC = () => { onFacetsChange, }) - const onSearchTermSubmit = useCallback( - (term: string) => { - setCurrentTextAndQuery(term) - setPage(1) - }, - [setPage, setCurrentTextAndQuery], - ) - const page = +(searchParams.get("page") ?? "1") return ( @@ -234,16 +221,17 @@ const SearchPage: React.FC = () => {
- setCurrentText(e.target.value)} onSubmit={(e) => { - onSearchTermSubmit(e.target.value) + setCurrentTextAndQuery(e.target.value) }} onClear={() => { - onSearchTermSubmit("") + setCurrentTextAndQuery("") }} + setPage={setPage} />
diff --git a/frontends/mit-learn/src/routes.tsx b/frontends/mit-learn/src/routes.tsx index fb5b338066..0cef84e264 100644 --- a/frontends/mit-learn/src/routes.tsx +++ b/frontends/mit-learn/src/routes.tsx @@ -1,6 +1,7 @@ import React from "react" import { RouteObject, Outlet } from "react-router" import { ScrollRestoration } from "react-router-dom" + import HomePage from "@/pages/HomePage/HomePage" import RestrictedRoute from "@/components/RestrictedRoute/RestrictedRoute" import LearningPathListingPage from "@/pages/LearningPathListingPage/LearningPathListingPage" @@ -26,6 +27,7 @@ import DepartmentListingPage from "./pages/DepartmentListingPage/DepartmentListi import TopicsListingPage from "./pages/TopicListingPage/TopicsListingPage" import UnitsListingPage from "./pages/UnitsListingPage/UnitsListingPage" import OnboardingPage from "./pages/OnboardingPage/OnboardingPage" +import CartPage from "./pages/EcommercePages/CartPage" import { styled } from "ol-components" @@ -190,6 +192,15 @@ const routes: RouteObject[] = [ }, ], }, + { + element: , + children: [ + { + path: urls.ECOMMERCE_CART, + element: , + }, + ], + }, ], }, ] diff --git a/frontends/mit-learn/webpack.config.js b/frontends/mit-learn/webpack.config.js index 9d48b30bd0..5adf71258b 100644 --- a/frontends/mit-learn/webpack.config.js +++ b/frontends/mit-learn/webpack.config.js @@ -139,7 +139,7 @@ const { }), DEFAULT_SEARCH_MINIMUM_SCORE_CUTOFF: num({ desc: "The default search minimum score cutoff", - default: 0, + default: 5, }), DEFAULT_SEARCH_MAX_INCOMPLETENESS_PENALTY: num({ desc: "The default search max incompleteness penalty", diff --git a/frontends/ol-components/package.json b/frontends/ol-components/package.json index 87b9df5a3c..8942839161 100644 --- a/frontends/ol-components/package.json +++ b/frontends/ol-components/package.json @@ -26,7 +26,6 @@ "material-ui-popup-state": "^5.1.0", "ol-test-utilities": "0.0.0", "ol-utilities": "0.0.0", - "posthog-js": "^1.165.0", "react": "18.3.1", "react-router": "^6.22.2", "react-router-dom": "^6.22.2", diff --git a/frontends/ol-components/src/components/SearchInput/SearchInput.test.tsx b/frontends/ol-components/src/components/SearchInput/SearchInput.test.tsx index bac9a47464..6e00b8a3ec 100644 --- a/frontends/ol-components/src/components/SearchInput/SearchInput.test.tsx +++ b/frontends/ol-components/src/components/SearchInput/SearchInput.test.tsx @@ -66,7 +66,17 @@ describe("SearchInput", () => { it("Calls onSubmit when search is clicked", async () => { const { user, spies } = renderSearchInput({ value: "chemistry" }) await user.click(getSearchButton()) - expect(spies.onSubmit).toHaveBeenCalledWith(searchEvent("chemistry")) + expect(spies.onSubmit).toHaveBeenCalledWith(searchEvent("chemistry"), { + isEnter: false, + }) + }) + + it("Calls onSubmit when 'Enter' is pressed", async () => { + const { user, spies } = renderSearchInput({ value: "chemistry" }) + await user.type(getSearchInput(), "{enter}") + expect(spies.onSubmit).toHaveBeenCalledWith(searchEvent("chemistry"), { + isEnter: true, + }) }) it("Calls onClear clear is clicked", async () => { diff --git a/frontends/ol-components/src/components/SearchInput/SearchInput.tsx b/frontends/ol-components/src/components/SearchInput/SearchInput.tsx index 09e711f40e..3391e299dd 100644 --- a/frontends/ol-components/src/components/SearchInput/SearchInput.tsx +++ b/frontends/ol-components/src/components/SearchInput/SearchInput.tsx @@ -1,9 +1,8 @@ -import React, { useCallback } from "react" +import React from "react" import { RiSearch2Line, RiCloseLine } from "@remixicon/react" import { Input, AdornmentButton } from "../Input/Input" import type { InputProps } from "../Input/Input" import styled from "@emotion/styled" -import { usePostHog } from "posthog-js/react" const StyledInput = styled(Input)(({ theme }) => ({ boxShadow: "0px 8px 20px 0px rgba(120, 147, 172, 0.10)", @@ -34,7 +33,10 @@ export interface SearchSubmissionEvent { preventDefault: () => void } -type SearchSubmitHandler = (event: SearchSubmissionEvent) => void +type SearchSubmitHandler = ( + event: SearchSubmissionEvent, + opts?: { isEnter?: boolean }, +) => void interface SearchInputProps { className?: string @@ -54,35 +56,15 @@ const muiInputProps = { "aria-label": "Search for" } const SearchInput: React.FC = (props) => { const { onSubmit, value } = props - const posthog = usePostHog() - const { POSTHOG } = APP_SETTINGS + const event = { + target: { value }, + preventDefault: () => null, + } - const handleSubmit = useCallback( - ( - ev: - | React.SyntheticEvent - | React.SyntheticEvent, - isEnter: boolean = false, - ) => { - const event = { - target: { value }, - preventDefault: () => null, - } - if (!(!POSTHOG?.api_key || POSTHOG.api_key.length < 1)) { - posthog.capture("search_update", { isEnter: isEnter }) - } - onSubmit(event) - }, - [onSubmit, value, posthog, POSTHOG], - ) - const onInputKeyDown: React.KeyboardEventHandler = - useCallback( - (e) => { - if (e.key !== "Enter") return - handleSubmit(e, true) - }, - [handleSubmit], - ) + const onInputKeyDown: React.KeyboardEventHandler = (e) => { + if (e.key !== "Enter") return + onSubmit(event, { isEnter: true }) + } return ( = (props) => { onSubmit(event, { isEnter: false })} > diff --git a/frontends/ol-components/src/index.ts b/frontends/ol-components/src/index.ts index 9c988a0768..eb81aa4375 100644 --- a/frontends/ol-components/src/index.ts +++ b/frontends/ol-components/src/index.ts @@ -200,7 +200,10 @@ export * from "./constants/imgConfigs" export { Input, AdornmentButton } from "./components/Input/Input" export type { InputProps, AdornmentButtonProps } from "./components/Input/Input" export { SearchInput } from "./components/SearchInput/SearchInput" -export type { SearchInputProps } from "./components/SearchInput/SearchInput" +export type { + SearchInputProps, + SearchSubmissionEvent, +} from "./components/SearchInput/SearchInput" export { TextField } from "./components/TextField/TextField" export { SimpleSelect, diff --git a/learning_resources_search/api.py b/learning_resources_search/api.py index f6e08a07d1..9ebc5a0a67 100644 --- a/learning_resources_search/api.py +++ b/learning_resources_search/api.py @@ -40,7 +40,12 @@ LEARN_SUGGEST_FIELDS = ["title.trigram", "description.trigram"] COURSENUM_SORT_FIELD = "course.course_numbers.sort_coursenum" -DEFAULT_SORT = ["featured_rank", "is_learning_material", "-created_on"] +DEFAULT_SORT = [ + "featured_rank", + "is_learning_material", + "is_incomplete_or_stale", + "-created_on", +] def gen_content_file_id(content_file_id): diff --git a/learning_resources_search/api_test.py b/learning_resources_search/api_test.py index 50952da6c7..f9e3165b54 100644 --- a/learning_resources_search/api_test.py +++ b/learning_resources_search/api_test.py @@ -2700,6 +2700,7 @@ def test_document_percolation(opensearch, mocker): [ "featured_rank", "is_learning_material", + "is_incomplete_or_stale", {"created_on": {"order": "desc"}}, ], ), diff --git a/main/settings.py b/main/settings.py index 61a6a9113c..a17b5c57ec 100644 --- a/main/settings.py +++ b/main/settings.py @@ -33,7 +33,7 @@ from main.settings_pluggy import * # noqa: F403 from openapi.settings_spectacular import open_spectacular_settings -VERSION = "0.20.3" +VERSION = "0.20.4" log = logging.getLogger() @@ -774,7 +774,7 @@ def get_all_config_keys(): name="DEFAULT_SEARCH_STALENESS_PENALTY", default=2.5 ) DEFAULT_SEARCH_MINIMUM_SCORE_CUTOFF = get_float( - name="DEFAULT_SEARCH_MINIMUM_SCORE_CUTOFF", default=0 + name="DEFAULT_SEARCH_MINIMUM_SCORE_CUTOFF", default=5 ) DEFAULT_SEARCH_MAX_INCOMPLETENESS_PENALTY = get_float( name="DEFAULT_SEARCH_MAX_INCOMPLETENESS_PENALTY", default=90 diff --git a/main/templates/email/email_base.html b/main/templates/email/email_base.html index c2646e8720..5ee577f21f 100644 --- a/main/templates/email/email_base.html +++ b/main/templates/email/email_base.html @@ -249,49 +249,76 @@ - - + - - - + + + +
- {% block logo %} -

- MIT-Learn -

- {% endblock %} +
+ MIT-Learn + + MIT-Learn
+ + + + - {% block content %}{% endblock %} @@ -332,7 +359,7 @@

If you don't want to receive these emails in the future, you can unsubscribe.

diff --git a/main/templates/email/subscribed_channel_digest.html b/main/templates/email/subscribed_channel_digest.html index f0f805e4a4..cf2ac84f78 100644 --- a/main/templates/email/subscribed_channel_digest.html +++ b/main/templates/email/subscribed_channel_digest.html @@ -17,7 +17,7 @@ >