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
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
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
39 changes: 24 additions & 15 deletions frontends/mit-learn/src/pages/SearchPage/SearchPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,11 @@ describe("SearchPage", () => {
setMockResponse.get(urls.userMe.get(), {
is_learning_path_editor: true,
})
APP_SETTINGS.DEFAULT_SEARCH_MODE = "phrase"
APP_SETTINGS.DEFAULT_SEARCH_SLOP = 6
APP_SETTINGS.DEFAULT_SEARCH_STALENESS_PENALTY = 2.5
APP_SETTINGS.DEFAULT_SEARCH_MINIMUM_SCORE_CUTOFF = 0
APP_SETTINGS.DEFAULT_SEARCH_MAX_INCOMPLETENESS_PENALTY = 90
renderWithProviders(<SearchPage />)
await waitFor(() => {
const adminFacetContainer = screen.getByText("Admin Options")
Expand All @@ -321,6 +326,11 @@ describe("SearchPage", () => {
})

test("admin users can set the search mode and slop", async () => {
APP_SETTINGS.DEFAULT_SEARCH_MODE = "phrase"
APP_SETTINGS.DEFAULT_SEARCH_SLOP = 6
APP_SETTINGS.DEFAULT_SEARCH_STALENESS_PENALTY = 2.5
APP_SETTINGS.DEFAULT_SEARCH_MINIMUM_SCORE_CUTOFF = 0
APP_SETTINGS.DEFAULT_SEARCH_MAX_INCOMPLETENESS_PENALTY = 90
setMockApiResponses({
search: {
count: 700,
Expand Down Expand Up @@ -349,14 +359,24 @@ test("admin users can set the search mode and slop", async () => {
user.click(adminFacetContainer)
})

let slopSlider = screen.queryByText("Slop")
expect(slopSlider).toBeNull()

const searchModeDropdowns = await screen.findAllByText("best_fields")
const searchModeDropdowns = await screen.findAllByText("phrase")
const searchModeDropdown = searchModeDropdowns[0]

await user.click(searchModeDropdown)

const mostFieldsSelect = await screen.findByRole("option", {
name: "most_fields",
})

await user.click(mostFieldsSelect)

expect(location.current.search).toBe("?search_mode=most_fields")

const slopSlider = screen.queryByText("Slop")
expect(slopSlider).toBeNull()

await user.click(searchModeDropdown)

const phraseSelect = await screen.findByRole("option", {
name: "phrase",
})
Expand All @@ -370,17 +390,6 @@ test("admin users can set the search mode and slop", async () => {
})

await user.click(searchModeDropdown)

const mostFieldsSelect = await screen.findByRole("option", {
name: "most_fields",
})

await user.click(mostFieldsSelect)

expect(location.current.search).toBe("?search_mode=most_fields")

slopSlider = screen.queryByText("Slop")
expect(slopSlider).toBeNull()
})

describe("Search Page Tabs", () => {
Expand Down
30 changes: 30 additions & 0 deletions frontends/mit-learn/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ const {
CSRF_COOKIE_NAME,
APPZI_URL,
MITOL_NOINDEX,
DEFAULT_SEARCH_MODE,
DEFAULT_SEARCH_SLOP,
DEFAULT_SEARCH_STALENESS_PENALTY,
DEFAULT_SEARCH_MINIMUM_SCORE_CUTOFF,
DEFAULT_SEARCH_MAX_INCOMPLETENESS_PENALTY,
} = cleanEnv(process.env, {
NODE_ENV: str({
choices: ["development", "production", "test"],
Expand Down Expand Up @@ -124,6 +129,26 @@ const {
desc: "Whether to include a noindex meta tag",
default: true,
}),
DEFAULT_SEARCH_SLOP: num({
desc: "The default search slop",
default: 6,
}),
DEFAULT_SEARCH_STALENESS_PENALTY: num({
desc: "The default search staleness penalty",
default: 2.5,
}),
DEFAULT_SEARCH_MINIMUM_SCORE_CUTOFF: num({
desc: "The default search minimum score cutoff",
default: 0,
}),
DEFAULT_SEARCH_MAX_INCOMPLETENESS_PENALTY: num({
desc: "The default search max incompleteness penalty",
default: 90,
}),
DEFAULT_SEARCH_MODE: str({
desc: "The default search mode",
default: "phrase",
}),
})

const MITOL_FEATURES_PREFIX = "FEATURE_"
Expand Down Expand Up @@ -265,6 +290,11 @@ module.exports = (env, argv) => {
MITOL_SUPPORT_EMAIL: JSON.stringify(MITOL_SUPPORT_EMAIL),
PUBLIC_URL: JSON.stringify(PUBLIC_URL),
CSRF_COOKIE_NAME: JSON.stringify(CSRF_COOKIE_NAME),
DEFAULT_SEARCH_MODE: JSON.stringify(DEFAULT_SEARCH_MODE),
DEFAULT_SEARCH_MAX_INCOMPLETENESS_PENALTY,
DEFAULT_SEARCH_MINIMUM_SCORE_CUTOFF,
DEFAULT_SEARCH_SLOP,
DEFAULT_SEARCH_STALENESS_PENALTY,
},
}),
]
Expand Down
5 changes: 5 additions & 0 deletions frontends/ol-utilities/src/types/settings.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,10 @@ export declare global {
SITE_NAME: string
MITOL_SUPPORT_EMAIL: string
PUBLIC_URL: string
DEFAULT_SEARCH_MODE: string
DEFAULT_SEARCH_SLOP: number
DEFAULT_SEARCH_STALENESS_PENALTY: number
DEFAULT_SEARCH_MINIMUM_SCORE_CUTOFF: number
DEFAULT_SEARCH_MAX_INCOMPLETENESS_PENALTY: number
}
}
30 changes: 29 additions & 1 deletion learning_resources_search/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from collections import Counter
from datetime import UTC, datetime

from django.conf import settings
from opensearch_dsl import Search
from opensearch_dsl.query import MoreLikeThis, Percolate
from opensearchpy.exceptions import NotFoundError
Expand Down Expand Up @@ -507,7 +508,19 @@ def adjust_original_query_for_percolate(query):
Remove keys that are irrelevent when storing original queries
for percolate uniqueness such as "limit" and "offset"
"""
for key in ["limit", "offset", "sortby", "yearly_decay_percent", "dev_mode"]:
for key in [
"limit",
"offset",
"sortby",
"yearly_decay_percent",
"dev_mode",
"use_dfs_query_then_fetch",
"max_incompleteness_penalty",
"min_score",
"search_mode",
"slop",
"use_dfs_query_then_fetch",
]:
query.pop(key, None)
return order_params(query)

Expand Down Expand Up @@ -684,6 +697,21 @@ def execute_learn_search(search_params):
Returns:
dict: The opensearch response dict
"""
if search_params.get("endpoint") != CONTENT_FILE_TYPE:
if search_params.get("yearly_decay_percent") is None:
search_params["yearly_decay_percent"] = (
settings.DEFAULT_SEARCH_STALENESS_PENALTY
)
if search_params.get("search_mode") is None:
search_params["search_mode"] = settings.DEFAULT_SEARCH_MODE
if search_params.get("slop") is None:
search_params["slop"] = settings.DEFAULT_SEARCH_SLOP
if search_params.get("min_score") is None:
search_params["min_score"] = settings.DEFAULT_SEARCH_MINIMUM_SCORE_CUTOFF
if search_params.get("max_incompleteness_penalty") is None:
search_params["max_incompleteness_penalty"] = (
settings.DEFAULT_SEARCH_MAX_INCOMPLETENESS_PENALTY
)
search = construct_search(search_params)
return search.execute().to_dict()

Expand Down
Loading