From ed4423278b6b1155d7dc46969a34bd223150c6ea Mon Sep 17 00:00:00 2001 From: Shankar Ambady Date: Wed, 2 Oct 2024 15:43:13 -0400 Subject: [PATCH 1/6] updating email template with new logo (#1638) * updating email template with new logo * adding copy update for unit * adding trailing slash * adding trailing slash --- .../migrations/0015_unit_page_copy_updates.py | 61 ++++++++++++ .../public/images/mit-block-logo.jpg | Bin 0 -> 3218 bytes .../public/images/mit-learn-logo.jpg | Bin 0 -> 11182 bytes .../public/images/mit-logo-learn.jpg | Bin 3108 -> 0 bytes main/templates/email/email_base.html | 87 ++++++++++++------ .../email/subscribed_channel_digest.html | 2 +- 6 files changed, 119 insertions(+), 31 deletions(-) create mode 100644 data_fixtures/migrations/0015_unit_page_copy_updates.py create mode 100644 frontends/mit-learn/public/images/mit-block-logo.jpg create mode 100644 frontends/mit-learn/public/images/mit-learn-logo.jpg delete mode 100644 frontends/mit-learn/public/images/mit-logo-learn.jpg 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 0000000000000000000000000000000000000000..5e61d900e80ce7cf358c6b4554b7175906451b0c GIT binary patch literal 3218 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 Fe*v$RoR|Or literal 0 HcmV?d00001 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 eb5b0bc61829fab438d1a458b8e7e8af074780c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3108 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 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 @@ >

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/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/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/yarn.lock b/yarn.lock index 6452e2d589..7fa390a739 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15408,7 +15408,6 @@ __metadata: material-ui-popup-state: "npm:^5.1.0" ol-test-utilities: "npm:0.0.0" ol-utilities: "npm:0.0.0" - posthog-js: "npm:^1.165.0" prop-types: "npm:^15.8.1" react: "npm:18.3.1" react-router: "npm:^6.22.2" @@ -16578,17 +16577,6 @@ __metadata: languageName: node linkType: hard -"posthog-js@npm:^1.165.0": - version: 1.165.0 - resolution: "posthog-js@npm:1.165.0" - dependencies: - fflate: "npm:^0.4.8" - preact: "npm:^10.19.3" - web-vitals: "npm:^4.0.1" - checksum: 10/4a640b90af24ffb173b4d20f27aab572437c8641b1ff48ad23e98d593fa7e94e63e660a4ce967a18eaabaf5102ecaff8a258315b47d1916e79a7f1ec7ad3bc7d - languageName: node - linkType: hard - "preact@npm:^10.19.3": version: 10.23.1 resolution: "preact@npm:10.23.1" From 598fac23b98762a4cfa74a5a2d8f42ae10de36c0 Mon Sep 17 00:00:00 2001 From: James Kachel Date: Thu, 3 Oct 2024 12:20:18 -0500 Subject: [PATCH 3/6] Adds base infra for the Unified Ecommerce frontend (#1634) --- .../mit-learn/src/common/feature_flags.ts | 6 ++ frontends/mit-learn/src/common/urls.ts | 2 + .../EcommerceFeature/EcommerceFeature.tsx | 33 ++++++++++ .../pages/EcommercePages/CartPage.test.tsx | 65 +++++++++++++++++++ .../src/pages/EcommercePages/CartPage.tsx | 31 +++++++++ .../src/pages/EcommercePages/README.md | 9 +++ frontends/mit-learn/src/routes.tsx | 11 ++++ 7 files changed, 157 insertions(+) create mode 100644 frontends/mit-learn/src/common/feature_flags.ts create mode 100644 frontends/mit-learn/src/page-components/EcommerceFeature/EcommerceFeature.tsx create mode 100644 frontends/mit-learn/src/pages/EcommercePages/CartPage.test.tsx create mode 100644 frontends/mit-learn/src/pages/EcommercePages/CartPage.tsx create mode 100644 frontends/mit-learn/src/pages/EcommercePages/README.md 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/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/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: , + }, + ], + }, ], }, ] From 6d9ccccd62a6af9bfcb79f6e6c7e39524c00f162 Mon Sep 17 00:00:00 2001 From: Anastasia Beglova Date: Thu, 3 Oct 2024 14:03:44 -0400 Subject: [PATCH 4/6] set default minimum score cutoff (#1642) --- frontends/mit-learn/webpack.config.js | 2 +- main/settings.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/main/settings.py b/main/settings.py index 61a6a9113c..2ceb118546 100644 --- a/main/settings.py +++ b/main/settings.py @@ -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 From 15652f042acd95953eb629f98da7397ef29942be Mon Sep 17 00:00:00 2001 From: Anastasia Beglova Date: Thu, 3 Oct 2024 15:31:54 -0400 Subject: [PATCH 5/6] add is_incomplete_or_stale to default sort (#1641) --- learning_resources_search/api.py | 7 ++++++- learning_resources_search/api_test.py | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) 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"}}, ], ), From 0e1ff4f82b2e1fed59207ed265d03882e8c45324 Mon Sep 17 00:00:00 2001 From: Doof Date: Fri, 4 Oct 2024 15:56:40 +0000 Subject: [PATCH 6/6] Release 0.20.4 --- RELEASE.rst | 9 +++++++++ main/settings.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) 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/main/settings.py b/main/settings.py index 2ceb118546..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()