Ensure lexical sort for org names (#1958)

Fixes #1955 

Orgs list endpoint sorting now works as follows:
- Default org is always sorted first
- Name sorting now works on a lowercased version of the org names to
ensure lexical sorting

The lodash `sortBy` resorting of orgs in the "All Organizations"
dropdown list in the nav bar has also been removed so that the backend
sorting is applied instead.

Tests have been updated accordingly.
This commit is contained in:
Tessa Walsh 2024-07-23 16:13:04 -04:00 committed by GitHub
parent 8c0321bdea
commit a02f7a6826
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 18 additions and 11 deletions

View File

@ -204,7 +204,10 @@ class OrgOps:
if not user.is_superuser:
query[f"users.{user.id}"] = {"$gte": role.value}
aggregate: List[Dict[str, Any]] = [{"$match": query}]
aggregate: List[Dict[str, Any]] = [
{"$match": query},
{"$set": {"nameLower": {"$toLower": "$name"}}},
]
# Ensure default org is always first, then sort on sort_by if set
sort_query = {"default": -1}
@ -216,9 +219,13 @@ class OrgOps:
if sort_direction not in (1, -1):
raise HTTPException(status_code=400, detail="invalid_sort_direction")
# Do lexical sort of names
if sort_by == "name":
sort_by = "nameLower"
sort_query[sort_by] = sort_direction
aggregate.extend([{"$sort": sort_query}])
aggregate.extend([{"$sort": sort_query}, {"$unset": ["nameLower"]}])
aggregate.extend(
[

View File

@ -688,7 +688,7 @@ def test_sort_orgs(admin_auth_headers):
r = requests.post(
f"{API_PREFIX}/orgs/create",
headers=admin_auth_headers,
json={"name": "mno", "slug": "mno"},
json={"name": "Mno", "slug": "mno"},
)
assert r.status_code == 200
@ -701,6 +701,7 @@ def test_sort_orgs(admin_auth_headers):
# Check default sorting
# Default org should come first, followed by alphabetical sorting ascending
# Ensure org names are sorted lexically, not by character code
r = requests.get(f"{API_PREFIX}/orgs", headers=admin_auth_headers)
data = r.json()
orgs = data["items"]
@ -711,9 +712,10 @@ def test_sort_orgs(admin_auth_headers):
last_name = None
for org in other_orgs:
org_name = org["name"]
org_name_lower = org_name.lower()
if last_name:
assert org_name > last_name
last_name = org_name
assert org_name_lower > last_name
last_name = org_name_lower
# Sort by name descending, ensure default org still first
r = requests.get(
@ -728,6 +730,7 @@ def test_sort_orgs(admin_auth_headers):
last_name = None
for org in other_orgs:
org_name = org["name"]
org_name_lower = org_name.lower()
if last_name:
assert org_name < last_name
last_name = org_name
assert org_name_lower < last_name
last_name = org_name_lower

View File

@ -4,7 +4,6 @@ import { nothing, render, type TemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { when } from "lit/directives/when.js";
import sortBy from "lodash/fp/sortBy";
import "broadcastchannel-polyfill";
import "./utils/polyfills";
@ -355,8 +354,6 @@ export class App extends LiteElement {
const orgs = this.appState.userInfo?.orgs;
if (!orgs || orgs.length < 2 || !this.appState.userInfo) return;
const sortedOrgs = sortBy<UserOrg>("name")(orgs);
const selectedOption = this.appState.orgSlug
? orgs.find(({ slug }) => slug === this.appState.orgSlug)
: { slug: "", name: msg("All Organizations") };
@ -401,7 +398,7 @@ export class App extends LiteElement {
<sl-divider></sl-divider>
`,
)}
${sortedOrgs.map(
${orgs.map(
(org) => html`
<sl-menu-item
type="checkbox"