Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Issue #3464] Better agency display/sort in search #3738

Open
wants to merge 7 commits into
base: chouinar/3577-multisort
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 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
2 changes: 2 additions & 0 deletions api/openapi.generated.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1521,6 +1521,8 @@ components:
- post_date
- close_date
- agency_code
- agency_name
- top_level_agency_name
description: The field to sort the response by
sort_direction:
description: Whether to sort the response ascending or descending
Expand Down
2 changes: 2 additions & 0 deletions api/src/api/opportunities_v1/opportunity_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,8 @@ class OpportunitySearchRequestV1Schema(Schema):
"post_date",
"close_date",
"agency_code",
"agency_name",
"top_level_agency_name",
],
default_sort_order=[{"order_by": "opportunity_id", "sort_direction": "descending"}],
),
Expand Down
2 changes: 2 additions & 0 deletions api/src/services/opportunities_v1/search_opportunities.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
"close_date": "summary.close_date",
"agency_code": "agency_code.keyword",
"agency": "agency_code.keyword",
"agency_name": "agency_name.keyword",
"top_level_agency_name": "top_level_agency_name.keyword",
"opportunity_status": "opportunity_status.keyword",
"funding_instrument": "summary.funding_instruments.keyword",
"funding_category": "summary.funding_categories.keyword",
Expand Down
20 changes: 12 additions & 8 deletions frontend/src/components/search/SearchResultsListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,17 +97,21 @@ export default function SearchResultsListItem({
: "--"}
</span>
</div>
<div className="grid-col tablet:order-3 overflow-hidden font-body-xs">
<div className="grid-col tablet:order-2 overflow-hidden font-body-xs">
<span className={metadataBorderClasses}>
<strong>{t("resultsListItem.summary.agency")}</strong>
{opportunity?.agency ||
(opportunity?.summary?.agency_name &&
opportunity?.summary?.agency_code &&
agencyNameLookup
? // Use same exact label we're using for the agency filter list
agencyNameLookup[opportunity?.summary?.agency_code]
: "--")}
{opportunity?.top_level_agency_name &&
opportunity?.agency_name &&
opportunity?.top_level_agency_name !== opportunity?.agency_name
? `${opportunity?.top_level_agency_name} - ${opportunity?.agency_name}`
: opportunity?.agency_name ||
(agencyNameLookup && opportunity?.summary?.agency_code
? // Use same exact label we're using for the agency filter list
agencyNameLookup[opportunity?.summary?.agency_code]
: "--")}
</span>
</div>
<div className="grid-col tablet:order-3 overflow-hidden font-body-xs">
<span className={metadataBorderClasses}>
<strong>{t("resultsListItem.opportunity_number")}</strong>
{opportunity?.opportunity_number}
Expand Down
45 changes: 30 additions & 15 deletions frontend/src/services/fetch/fetchers/searchFetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,20 @@ import { fetchOpportunitySearch } from "src/services/fetch/fetchers/fetchers";
import {
PaginationOrderBy,
PaginationRequestBody,
PaginationSortOrder,
QueryParamData,
SearchFetcherActionType,
SearchFilterRequestBody,
SearchRequestBody,
} from "src/types/search/searchRequestTypes";

const orderByFieldLookup = {
relevancy: "relevancy",
opportunityNumber: "opportunity_number",
opportunityTitle: "opportunity_title",
agency: "agency_code",
postedDate: "post_date",
closeDate: "close_date",
relevancy: ["relevancy"],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this also have post_date? I see you had a default pagination sort order below, but if someone intentionally chose relevancy (not sure how that links / if that's possible) you'd lose the post_date bit, right?

Suggested change
relevancy: ["relevancy"],
relevancy: ["relevancy", "post_date"],

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I'm torn on how to best handle the default and the post_date backup. Part of me thinks we should always have a post_date fallback, so even if sorting by agency, we show the ones closing sooner earlier amongst the peers from that same agency. But I think In the implementation you'll see that if it's hitting on relevancy it's adding the post_date backup in the pagination builder. But agree, I'm not sure if that's the right way, except to note, anything we default here, doesn't affect the "no sort" route which is possible and does happen (primarily if you've just never touched sort upon landing on the page).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[tldr: I'm good with this change as is] it seems like based on the changes to the fetcher, the default is being set the way we want with the secondary sort. Beyond that, I feel like if users want to update the sort away from the default I think it's best to not implement a hidden secondary sort behind the scenes at the moment.

opportunityNumber: ["opportunity_number"],
opportunityTitle: ["opportunity_title"],
agency: ["top_level_agency_name", "agency_name"],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To make this fully work, we'd also need the backend to always populate top level agency name, as long as you're making the API changes, could add the fallback to the property on the ORM model

Copy link
Collaborator Author

@mdragon mdragon Feb 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chouinar Is this what you mean by the fallback? I think this is saying to not return the top level if the agency is set, so this change should make it always populate when top level is available?
image

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't modify that line, you'll hit a null pointer error if you do. That full check needs to be there to check if the agency record exists first, and then if the top level agency exists off of that.

Add a new if statement below it that is:

if self.agency_record is not None:
     return self.agency_record.agency_name

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, thanks. I realized I was thinking about the Not None backwards after I asked you here.

postedDate: ["post_date"],
closeDate: ["close_date"],
};

type FrontendFilterNames =
Expand Down Expand Up @@ -98,24 +99,38 @@ export const buildPagination = (
? 1
: page;

let order_by: PaginationOrderBy = "relevancy";
let sort_order: PaginationSortOrder = [
{ order_by: "relevancy", sort_direction: "descending" },
{ order_by: "post_date", sort_direction: "descending" },
];

if (sortby) {
sort_order = [];
// this will need to change in a future where we allow the user to set an ordered set of sort columns.
// for now we're just using the multiple internally behind a single column picker drop down so this is fine.
for (const [key, value] of Object.entries(orderByFieldLookup)) {
if (sortby.startsWith(key)) {
order_by = value as PaginationOrderBy;
break; // Stop searching after the first match is found
const sort_direction =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like I'm missing why this was moved into the body of the for loop

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was originally going to go bigger and try to support multiple arguments via the URL argument so we'd need to be parsing each argument for it's direction. I stopped short of that since we have no plans for how the UI for that would work, and it would have required a more fundamental overhaul of what this was already doing.

sortby && sortby.endsWith("Asc") ? "ascending" : "descending";
value.forEach((item) => {
sort_order.push({
order_by: <PaginationOrderBy>item,
sort_direction,
});
if (item === "relevancy") {
sort_order.push({
order_by: "post_date",
sort_direction,
});
}
});
}
}
}

// sort relevancy descending without suffix
const sort_direction =
sortby && sortby.endsWith("Asc") ? "ascending" : "descending";

return {
order_by,
page_offset,
page_size: 25,
sort_direction,
sort_order,
};
};
10 changes: 7 additions & 3 deletions frontend/src/types/search/searchRequestTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,19 @@ export type PaginationOrderBy =
| "opportunity_id"
| "opportunity_number"
| "opportunity_title"
| "agency_code"
| "agency_name"
| "top_level_agency_code"
| "post_date"
| "close_date";
export type PaginationSortDirection = "ascending" | "descending";
export interface PaginationRequestBody {
export type PaginationSortOrder = {
order_by: PaginationOrderBy;
sort_direction: PaginationSortDirection;
}[];
export interface PaginationRequestBody {
page_offset: number;
page_size: number;
sort_direction: PaginationSortDirection;
sort_order: PaginationSortOrder;
}

export type SearchRequestBody = {
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/types/search/searchResponseTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export interface Summary {

export interface Opportunity {
agency: string | null;
agency_name: string | null;
category: string | null;
category_explanation: string | null;
created_at: string;
Expand All @@ -52,6 +53,7 @@ export interface Opportunity {
opportunity_status: OpportunityStatus;
opportunity_title: string;
summary: Summary;
top_level_agency_name: string | null;
updated_at: string;
}

Expand Down