Add and apply backend sorting for org list
The default org will always be sorted first, regardless of sort options. Orgs after the first will be sorted by name ascending by default. Sorting currently supported on name, slug, and readOnly.
This commit is contained in:
parent
ed0d489cda
commit
d3fb33a78a
@ -134,6 +134,8 @@ class OrgOps:
|
|||||||
role: UserRole = UserRole.VIEWER,
|
role: UserRole = UserRole.VIEWER,
|
||||||
page_size: int = DEFAULT_PAGE_SIZE,
|
page_size: int = DEFAULT_PAGE_SIZE,
|
||||||
page: int = 1,
|
page: int = 1,
|
||||||
|
sort_by: Optional[str] = "name",
|
||||||
|
sort_direction: int = 1,
|
||||||
calculate_total=True,
|
calculate_total=True,
|
||||||
):
|
):
|
||||||
"""Get all orgs a user is a member of"""
|
"""Get all orgs a user is a member of"""
|
||||||
@ -142,19 +144,52 @@ class OrgOps:
|
|||||||
skip = page_size * page
|
skip = page_size * page
|
||||||
|
|
||||||
if user.is_superuser:
|
if user.is_superuser:
|
||||||
query = {}
|
query: Dict[str, object] = {}
|
||||||
else:
|
else:
|
||||||
query = {f"users.{user.id}": {"$gte": role.value}}
|
query: Dict[str, object] = {f"users.{user.id}": {"$gte": role.value}}
|
||||||
|
|
||||||
total = 0
|
aggregate = [{"$match": query}]
|
||||||
if calculate_total:
|
|
||||||
total = await self.orgs.count_documents(query)
|
|
||||||
|
|
||||||
cursor = self.orgs.find(query, skip=skip, limit=page_size)
|
# Ensure default org is always first, then sort on sort_by if set
|
||||||
results = await cursor.to_list(length=page_size)
|
sort_query = {"default": -1}
|
||||||
orgs = [Organization.from_dict(res) for res in results]
|
|
||||||
|
|
||||||
return orgs, total
|
if sort_by:
|
||||||
|
sort_fields = ("name", "slug", "readOnly")
|
||||||
|
if sort_by not in sort_fields:
|
||||||
|
raise HTTPException(status_code=400, detail="invalid_sort_by")
|
||||||
|
if sort_direction not in (1, -1):
|
||||||
|
raise HTTPException(status_code=400, detail="invalid_sort_direction")
|
||||||
|
|
||||||
|
sort_query[sort_by] = sort_direction
|
||||||
|
|
||||||
|
aggregate.extend([{"$sort": sort_query}])
|
||||||
|
|
||||||
|
aggregate.extend(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"$facet": {
|
||||||
|
"items": [
|
||||||
|
{"$skip": skip},
|
||||||
|
{"$limit": page_size},
|
||||||
|
],
|
||||||
|
"total": [{"$count": "count"}],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get total
|
||||||
|
cursor = self.orgs.aggregate(aggregate)
|
||||||
|
results = await cursor.to_list(length=1)
|
||||||
|
result = results[0]
|
||||||
|
items = result["items"]
|
||||||
|
|
||||||
|
try:
|
||||||
|
total = int(result["total"][0]["count"])
|
||||||
|
except (IndexError, ValueError):
|
||||||
|
total = 0
|
||||||
|
|
||||||
|
return [Organization.from_dict(data) for data in items], total
|
||||||
|
|
||||||
async def get_org_for_user_by_id(
|
async def get_org_for_user_by_id(
|
||||||
self, oid: UUID, user: User, role: UserRole = UserRole.VIEWER
|
self, oid: UUID, user: User, role: UserRole = UserRole.VIEWER
|
||||||
@ -788,9 +823,15 @@ def init_orgs_api(app, mdb, user_manager, invites, user_dep, user_or_shared_secr
|
|||||||
user: User = Depends(user_dep),
|
user: User = Depends(user_dep),
|
||||||
pageSize: int = DEFAULT_PAGE_SIZE,
|
pageSize: int = DEFAULT_PAGE_SIZE,
|
||||||
page: int = 1,
|
page: int = 1,
|
||||||
|
sortBy: Optional[str] = "name",
|
||||||
|
sortDirection: Optional[int] = 1,
|
||||||
):
|
):
|
||||||
results, total = await ops.get_orgs_for_user(
|
results, total = await ops.get_orgs_for_user(
|
||||||
user, page_size=pageSize, page=page
|
user,
|
||||||
|
page_size=pageSize,
|
||||||
|
page=page,
|
||||||
|
sort_by=sortBy,
|
||||||
|
sort_direction=sortDirection,
|
||||||
)
|
)
|
||||||
serialized_results = [
|
serialized_results = [
|
||||||
await res.serialize_for_user(user, user_manager) for res in results
|
await res.serialize_for_user(user, user_manager) for res in results
|
||||||
|
|||||||
@ -703,3 +703,60 @@ def test_create_org_and_invite_existing_user(admin_auth_headers):
|
|||||||
"giftedExecMinutes": 0,
|
"giftedExecMinutes": 0,
|
||||||
}
|
}
|
||||||
assert "subData" not in org
|
assert "subData" not in org
|
||||||
|
|
||||||
|
|
||||||
|
def test_sort_orgs(admin_auth_headers):
|
||||||
|
# Create a few new orgs for testing
|
||||||
|
r = requests.post(
|
||||||
|
f"{API_PREFIX}/orgs/create",
|
||||||
|
headers=admin_auth_headers,
|
||||||
|
json={"name": "abc", "slug": "abc"},
|
||||||
|
)
|
||||||
|
assert r.status_code == 200
|
||||||
|
|
||||||
|
r = requests.post(
|
||||||
|
f"{API_PREFIX}/orgs/create",
|
||||||
|
headers=admin_auth_headers,
|
||||||
|
json={"name": "mno", "slug": "mno"},
|
||||||
|
)
|
||||||
|
assert r.status_code == 200
|
||||||
|
|
||||||
|
r = requests.post(
|
||||||
|
f"{API_PREFIX}/orgs/create",
|
||||||
|
headers=admin_auth_headers,
|
||||||
|
json={"name": "xyz", "slug": "xyz"},
|
||||||
|
)
|
||||||
|
assert r.status_code == 200
|
||||||
|
|
||||||
|
# Check default sorting
|
||||||
|
# Default org should come first, followed by alphabetical sorting ascending
|
||||||
|
r = requests.get(f"{API_PREFIX}/orgs", headers=admin_auth_headers)
|
||||||
|
data = r.json()
|
||||||
|
orgs = data["items"]
|
||||||
|
|
||||||
|
assert orgs[0]["default"]
|
||||||
|
|
||||||
|
other_orgs = orgs[1:]
|
||||||
|
last_name = None
|
||||||
|
for org in other_orgs:
|
||||||
|
org_name = org["name"]
|
||||||
|
if last_name:
|
||||||
|
assert org_name > last_name
|
||||||
|
last_name = org_name
|
||||||
|
|
||||||
|
# Sort by name descending, ensure default org still first
|
||||||
|
r = requests.get(
|
||||||
|
f"{API_PREFIX}/orgs?sortBy=name&sortDirection=-1", headers=admin_auth_headers
|
||||||
|
)
|
||||||
|
data = r.json()
|
||||||
|
orgs = data["items"]
|
||||||
|
|
||||||
|
assert orgs[0]["default"]
|
||||||
|
|
||||||
|
other_orgs = orgs[1:]
|
||||||
|
last_name = None
|
||||||
|
for org in other_orgs:
|
||||||
|
org_name = org["name"]
|
||||||
|
if last_name:
|
||||||
|
assert org_name < last_name
|
||||||
|
last_name = org_name
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import { localized, msg, str } from "@lit/localize";
|
|||||||
import { serialize } from "@shoelace-style/shoelace/dist/utilities/form.js";
|
import { serialize } from "@shoelace-style/shoelace/dist/utilities/form.js";
|
||||||
import { type PropertyValues, type TemplateResult } from "lit";
|
import { type PropertyValues, type TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators.js";
|
import { customElement, property, state } from "lit/decorators.js";
|
||||||
import sortBy from "lodash/fp/sortBy";
|
|
||||||
|
|
||||||
import type { InviteSuccessDetail } from "@/features/accounts/invite-form";
|
import type { InviteSuccessDetail } from "@/features/accounts/invite-form";
|
||||||
import type { APIPaginatedList } from "@/types/api";
|
import type { APIPaginatedList } from "@/types/api";
|
||||||
@ -278,12 +277,12 @@ export class Home extends LiteElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async fetchOrgs() {
|
private async fetchOrgs() {
|
||||||
this.orgList = sortBy<OrgData>("name")(await this.getOrgs());
|
this.orgList = await this.getOrgs();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getOrgs() {
|
private async getOrgs() {
|
||||||
const data = await this.apiFetch<APIPaginatedList<OrgData>>(
|
const data = await this.apiFetch<APIPaginatedList<OrgData>>(
|
||||||
"/orgs",
|
"/orgs?sortBy=name",
|
||||||
this.authState!,
|
this.authState!,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user