diff --git a/backend/btrixcloud/models.py b/backend/btrixcloud/models.py index 05c8502daf..e0347c6f29 100644 --- a/backend/btrixcloud/models.py +++ b/backend/btrixcloud/models.py @@ -2539,6 +2539,13 @@ class ProfileBrowserGetUrlResponse(BaseModel): url: AnyHttpUrl +# ============================================================================ +class ProfileSearchValuesResponse(BaseModel): + """Response model for profiles search values""" + + names: List[str] + + # ============================================================================ ### USERS ### diff --git a/backend/btrixcloud/profiles.py b/backend/btrixcloud/profiles.py index 71169df610..be7d4750c1 100644 --- a/backend/btrixcloud/profiles.py +++ b/backend/btrixcloud/profiles.py @@ -46,6 +46,7 @@ ProfileBrowserMetadata, TagsResponse, ListFilterType, + ProfileSearchValuesResponse, ) from .utils import dt_now, str_to_date @@ -444,6 +445,7 @@ async def list_profiles( userid: Optional[UUID] = None, tags: Optional[List[str]] = None, tag_match: Optional[ListFilterType] = ListFilterType.AND, + name: Optional[str] = None, page_size: int = DEFAULT_PAGE_SIZE, page: int = 1, sort_by: str = "modified", @@ -462,6 +464,8 @@ async def list_profiles( if tags: query_type = "$all" if tag_match == ListFilterType.AND else "$in" match_query["tags"] = {query_type: tags} + if name: + match_query["name"] = name aggregate: List[Dict[str, Any]] = [{"$match": match_query}] @@ -651,6 +655,13 @@ async def get_profile_tag_counts( ).to_list() return tags + async def get_profile_search_values(self, org: Organization): + """Return profile names for use in search""" + names = await self.profiles.distinct("name", {"oid": org.id}) + # Remove empty strings + names = [name for name in names if name] + return {"names": names} + def _run_task(self, func) -> None: """add bg tasks to set to avoid premature garbage collection""" task = asyncio.create_task(func) @@ -715,6 +726,7 @@ async def list_profiles( description='Defaults to `"and"` if omitted', ), ] = ListFilterType.AND, + name: Optional[str] = None, pageSize: int = DEFAULT_PAGE_SIZE, page: int = 1, sortBy: str = "modified", @@ -725,6 +737,7 @@ async def list_profiles( userid, tags=tags, tag_match=tag_match, + name=name, page_size=pageSize, page=page, sort_by=sortBy, @@ -748,6 +761,15 @@ async def commit_browser_to_new( async def get_profile_tag_counts(org: Organization = Depends(org_viewer_dep)): return {"tags": await ops.get_profile_tag_counts(org)} + @router.get( + "/search-values", + response_model=ProfileSearchValuesResponse, + ) + async def get_collection_search_values( + org: Organization = Depends(org_viewer_dep), + ): + return await ops.get_profile_search_values(org) + @router.patch("/{profileid}", response_model=UpdatedResponse) async def commit_browser_to_existing( browser_commit: ProfileUpdate, diff --git a/backend/test/test_profiles.py b/backend/test/test_profiles.py index f82771cf96..4020df7a38 100644 --- a/backend/test/test_profiles.py +++ b/backend/test/test_profiles.py @@ -205,6 +205,20 @@ def test_list_profiles_filter_by_tag( assert data["total"] == 2 +def test_list_profiles_filter_by_name(admin_auth_headers, default_org_id, profile_2_id): + r = requests.get( + f"{API_PREFIX}/orgs/{default_org_id}/profiles?name={PROFILE_2_NAME}", + headers=admin_auth_headers, + ) + assert r.status_code == 200 + data = r.json() + assert data["total"] == 1 + + profile = data["items"][0] + assert profile["id"] == profile_2_id + assert profile["name"] == PROFILE_2_NAME + + def test_update_profile_metadata(crawler_auth_headers, default_org_id, profile_id): # Get original created/modified times r = requests.get( @@ -450,6 +464,16 @@ def test_profile_tag_counts(admin_auth_headers, default_org_id): } +def test_profile_search_values(admin_auth_headers, default_org_id): + r = requests.get( + f"{API_PREFIX}/orgs/{default_org_id}/profiles/search-values", + headers=admin_auth_headers, + ) + assert r.status_code == 200 + data = r.json() + assert sorted(data["names"]) == sorted([PROFILE_NAME_UPDATED, PROFILE_2_NAME]) + + def test_delete_profile(admin_auth_headers, default_org_id, profile_2_id): # Delete second profile r = requests.delete(