fix: Sort filtered collection page URLs (#2384)

Fixes https://github.com/webrecorder/browsertrix/issues/2383

- Fixes unpredictable sort order when typing in collection page URL
- Fixes page URL results flickering in and out while typing

---------

Co-authored-by: Tessa Walsh <tessa@bitarchivist.net>
This commit is contained in:
sua yoo 2025-02-12 08:59:20 -08:00 committed by GitHub
parent 5b02d81991
commit f7b9b73a68
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 49 additions and 39 deletions

View File

@ -755,7 +755,7 @@ class CollectionOps:
page_size: int = DEFAULT_PAGE_SIZE,
page: int = 1,
) -> Tuple[List[PageUrlCount], int]:
"""List all URLs in collection sorted desc by snapshot count"""
"""List all URLs in collection sorted desc by snapshot count unless prefix is specified"""
# pylint: disable=duplicate-code, too-many-locals, too-many-branches, too-many-statements
# Zero-index page for query
page = page - 1
@ -764,13 +764,15 @@ class CollectionOps:
crawl_ids = await self.get_collection_crawl_ids(coll_id)
match_query: dict[str, object] = {"oid": oid, "crawl_id": {"$in": crawl_ids}}
sort_query: dict[str, int] = {"count": -1, "_id": 1}
if url_prefix:
url_prefix = urllib.parse.unquote(url_prefix)
regex_pattern = f"^{re.escape(url_prefix)}"
match_query["url"] = {"$regex": regex_pattern, "$options": "i"}
sort_query = {"_id": 1}
aggregate = [{"$match": match_query}]
aggregate: List[Dict[str, Union[int, object]]] = [{"$match": match_query}]
aggregate.extend(
[
@ -781,7 +783,7 @@ class CollectionOps:
"count": {"$sum": 1},
},
},
{"$sort": {"count": -1}},
{"$sort": sort_query},
{"$set": {"url": "$_id"}},
{
"$facet": {

View File

@ -357,45 +357,53 @@ export class SelectCollectionPage extends BtrixElement {
private renderSearchResults() {
return this.searchResults.render({
pending: () => html`
<sl-menu-item slot="menu-item" disabled>
<sl-spinner></sl-spinner>
</sl-menu-item>
`,
complete: ({ items }) => {
if (!items.length) {
return html`
<sl-menu-item slot="menu-item" disabled>
${msg("No matching page found.")}
</sl-menu-item>
`;
}
return html`
${items.map((item: Page) => {
return html`
<sl-menu-item
slot="menu-item"
@click=${async () => {
if (this.input) {
this.input.value = item.url;
}
this.selectedPage = this.formatPage(item);
this.combobox?.hide();
this.selectedSnapshot = this.selectedPage.snapshots[0];
}}
>${item.url}
</sl-menu-item>
`;
})}
`;
},
pending: () =>
this.renderItems(
// Render previous value so that dropdown doesn't shift while typing
this.searchResults.value,
),
complete: this.renderItems,
});
}
private readonly renderItems = (
results: SelectCollectionPage["searchResults"]["value"],
) => {
if (!results) return;
const { items } = results;
if (!items.length) {
return html`
<sl-menu-item slot="menu-item" disabled>
${msg("No matching page found.")}
</sl-menu-item>
`;
}
return html`
${items.map((item: Page) => {
return html`
<sl-menu-item
slot="menu-item"
@click=${async () => {
if (this.input) {
this.input.value = item.url;
}
this.selectedPage = this.formatPage(item);
this.combobox?.hide();
this.selectedSnapshot = this.selectedPage.snapshots[0];
}}
>${item.url}
</sl-menu-item>
`;
})}
`;
};
private readonly onSearchInput = debounce(400)(() => {
const value = this.input?.value;