Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions RELEASE.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
Release Notes
=============

Version 0.19.3
--------------

- Make search mode defaults settable env variables (#1590)
- follow/unfollow popover (#1589)
- Fix extract_openedx_data and backpopulate_mit_edx_data commands to work with course/program datafiles (#1587)

Version 0.19.2 (Released September 23, 2024)
--------------

Expand Down
20 changes: 20 additions & 0 deletions app.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,26 @@
"description": "The domain to set the CSRF cookie on",
"required": false
},
"DEFAULT_SEARCH_MODE": {
"description": "Default search mode for the search API and frontend",
"required": false
},
"DEFAULT_SEARCH_SLOP": {
"description": "Default slop value for the search API and frontend. Only used for phrase queries.",
"required": false
},
"DEFAULT_SEARCH_STALENESS_PENALTY": {
"description": "Default staleness penalty value for the search API and frontend",
"required": false
},
"DEFAULT_SEARCH_MINIMUM_SCORE_CUTOFF": {
"description": "Default minimum score cutoff value for the search API and frontend",
"required": false
},
"DEFAULT_SEARCH_MAX_INCOMPLETENESS_PENALTY": {
"description": "Default max incompleteness penalty value for the search API and frontend",
"required": false
},
"EDX_API_ACCESS_TOKEN_URL": {
"description": "URL to retrieve a MITx access token",
"required": false
Expand Down
5 changes: 5 additions & 0 deletions frontends/mit-learn/jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ const config: Config.InitialOptions = {
MITOL_API_BASE_URL: "https://api.test.learn.mit.edu",
PUBLIC_URL: "",
SITE_NAME: "MIT Learn",
DEFAULT_SEARCH_MODE: "phrase",
DEFAULT_SEARCH_SLOP: 6,
DEFAULT_SEARCH_STALENESS_PENALTY: 2.5,
DEFAULT_SEARCH_MINIMUM_SCORE_CUTOFF: 0,
DEFAULT_SEARCH_MAX_INCOMPLETENESS_PENALTY: 90,
},
},
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import React, { useMemo } from "react"
import { Popover, Typography, styled, Button } from "ol-components"
import type { PopoverProps } from "ol-components"
import { getSearchParamMap } from "@/common/utils"

import { SignupPopover } from "../SignupPopover/SignupPopover"

import { useUserMe } from "api/hooks/user"
import { SourceTypeEnum } from "api"
import {
useSearchSubscriptionCreate,
useSearchSubscriptionDelete,
useSearchSubscriptionList,
} from "api/hooks/searchSubscription"

const StyledPopover = styled(Popover)({
width: "300px",
maxWidth: "100vw",
})
const HeaderText = styled(Typography)(({ theme }) => ({
color: theme.custom.colors.darkGray2,
marginBottom: "8px",
...theme.typography.subtitle2,
}))
const BodyText = styled(Typography)(({ theme }) => ({
...theme.typography.body2,
color: theme.custom.colors.silverGrayDark,
marginBottom: "16px",
}))

const Footer = styled.div({
display: "flex",
justifyContent: "end",
gap: "16px",
})

interface FollowPopoverProps
extends Pick<PopoverProps, "anchorEl" | "onClose" | "placement"> {
itemName?: string
searchParams: URLSearchParams
sourceType: SourceTypeEnum
}

const FollowPopover: React.FC<FollowPopoverProps> = ({
itemName,
searchParams,
sourceType,
...props
}) => {
const { data: user } = useUserMe()
const subscribeParams: Record<string, string[] | string> = useMemo(() => {
return { source_type: sourceType, ...getSearchParamMap(searchParams) }
}, [searchParams, sourceType])

const subscriptionDelete = useSearchSubscriptionDelete()
const subscriptionCreate = useSearchSubscriptionCreate()
const subscriptionList = useSearchSubscriptionList(subscribeParams, {
enabled: !!user?.is_authenticated,
})
const unsubscribe = subscriptionDelete.mutate
const subscriptionId = subscriptionList.data?.[0]?.id

const isSubscribed = !!subscriptionId
const handleFollowAction = async (): Promise<void> => {
props.onClose()
if (!isSubscribed) {
await subscriptionCreate.mutateAsync({
PercolateQuerySubscriptionRequestRequest: subscribeParams,
})
} else {
unsubscribe(subscriptionId)
}
}

if (user?.is_authenticated && subscriptionList.isLoading) return null
if (!user) return null
if (!user?.is_authenticated) {
return <SignupPopover {...props}></SignupPopover>
}

if (isSubscribed) {
return (
<StyledPopover {...props} open={!!props.anchorEl}>
<HeaderText variant="subtitle2">
You are following {itemName}
</HeaderText>
<BodyText variant="body2">
Unfollow to stop getting emails for new {itemName} courses.
</BodyText>
<Footer>
<Button
size="medium"
responsive={true}
variant="inverted"
edge="rounded"
data-testid="action-unfollow"
onClick={handleFollowAction}
>
Unfollow
</Button>
<Button
responsive={true}
size="medium"
edge="rounded"
onClick={() => props.onClose()}
>
Close
</Button>
</Footer>
</StyledPopover>
)
}
return (
<StyledPopover {...props} open={!!props.anchorEl}>
<HeaderText variant="subtitle2">Follow {itemName}?</HeaderText>
<BodyText variant="body2">
You will get an email when new courses are available.
</BodyText>
<Footer>
<Button
responsive={true}
size="medium"
edge="rounded"
variant="inverted"
onClick={() => props.onClose()}
>
Close
</Button>
<Button
responsive={true}
size="medium"
edge="rounded"
data-testid="action-follow"
onClick={handleFollowAction}
>
Follow
</Button>
</Footer>
</StyledPopover>
)
}

export { FollowPopover }
export type { FollowPopoverProps }
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,11 @@ const FacetStyles = styled.div`

&.facets-expanded {
max-height: 600px;

&.admin-facet {
max-height: fit-content;
}

transition: max-height 0.4s ease-in;
}

Expand Down Expand Up @@ -515,6 +520,14 @@ interface SearchDisplayProps {
filterHeadingEl: React.ElementType
}

const {
DEFAULT_SEARCH_MODE,
DEFAULT_SEARCH_SLOP,
DEFAULT_SEARCH_STALENESS_PENALTY,
DEFAULT_SEARCH_MINIMUM_SCORE_CUTOFF,
DEFAULT_SEARCH_MAX_INCOMPLETENESS_PENALTY,
} = APP_SETTINGS

const SearchDisplay: React.FC<SearchDisplayProps> = ({
page,
setPage,
Expand Down Expand Up @@ -585,7 +598,7 @@ const SearchDisplay: React.FC<SearchDisplayProps> = ({
const searchModeDropdown = (
<StyledSelect
size="small"
value={searchParams.get("search_mode") || "best_fields"}
value={searchParams.get("search_mode") || DEFAULT_SEARCH_MODE}
onChange={(e) =>
setSearchParams((prev) => {
const next = new URLSearchParams(prev)
Expand Down Expand Up @@ -623,7 +636,7 @@ const SearchDisplay: React.FC<SearchDisplayProps> = ({

return (
<div
className={`facets base-facet${expandAdminOptions ? " facets-expanded" : ""}`}
className={`facets admin-facet base-facet${expandAdminOptions ? " facets-expanded" : ""}`}
>
<button
className="filter-section-button"
Expand All @@ -645,7 +658,7 @@ const SearchDisplay: React.FC<SearchDisplayProps> = ({
currentValue={
searchParams.get("yearly_decay_percent")
? Number(searchParams.get("yearly_decay_percent"))
: 2.5
: DEFAULT_SEARCH_STALENESS_PENALTY
}
setSearchParams={setSearchParams}
urlParam="yearly_decay_percent"
Expand All @@ -666,15 +679,17 @@ const SearchDisplay: React.FC<SearchDisplayProps> = ({
OpenSearch search multi-match query type.
</ExplanationContainer>
</div>
{searchParams.get("search_mode") === "phrase" ? (
{(!searchParams.get("search_mode") &&
DEFAULT_SEARCH_MODE === "phrase") ||
searchParams.get("search_mode") === "phrase" ? (
<div>
<AdminTitleContainer>Slop</AdminTitleContainer>

<SliderInput
currentValue={
searchParams.get("slop")
? Number(searchParams.get("slop"))
: 0
: DEFAULT_SEARCH_SLOP
}
setSearchParams={setSearchParams}
urlParam="slop"
Expand All @@ -694,7 +709,7 @@ const SearchDisplay: React.FC<SearchDisplayProps> = ({
currentValue={
searchParams.get("min_score")
? Number(searchParams.get("min_score"))
: 0
: DEFAULT_SEARCH_MINIMUM_SCORE_CUTOFF
}
setSearchParams={setSearchParams}
urlParam="min_score"
Expand All @@ -713,7 +728,7 @@ const SearchDisplay: React.FC<SearchDisplayProps> = ({
currentValue={
searchParams.get("max_incompleteness_penalty")
? Number(searchParams.get("max_incompleteness_penalty"))
: 0
: DEFAULT_SEARCH_MAX_INCOMPLETENESS_PENALTY
}
setSearchParams={setSearchParams}
urlParam="max_incompleteness_penalty"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ test.each(Object.values(SourceTypeEnum))(
})
await user.click(subscribeButton)

const followButton = screen.getByTestId("action-follow")
await user.click(followButton)
expect(makeRequest).toHaveBeenCalledWith("post", subscribeUrl, {
source_type: sourceType,
offered_by: ["ocw"],
Expand Down Expand Up @@ -111,11 +113,8 @@ test.each(Object.values(SourceTypeEnum))(
})

await user.click(subscribedButton)
const unsubscribeButton = screen.getByTestId("action-unfollow")

const menu = screen.getByRole("menu")
const unsubscribeButton = within(menu).getByRole("menuitem", {
name: "Unfollow",
})
await user.click(unsubscribeButton)

expect(makeRequest).toHaveBeenCalledWith(
Expand Down
Loading