From f132221a8d74f7cb87cc67b286377805d08e6935 Mon Sep 17 00:00:00 2001 From: sua yoo Date: Tue, 25 Nov 2025 17:11:41 -0800 Subject: [PATCH 1/9] add search bar --- .../src/pages/org/browser-profiles-list.ts | 107 +++++++++++++++--- frontend/src/utils/searchValues.ts | 14 +++ 2 files changed, 107 insertions(+), 14 deletions(-) create mode 100644 frontend/src/utils/searchValues.ts diff --git a/frontend/src/pages/org/browser-profiles-list.ts b/frontend/src/pages/org/browser-profiles-list.ts index 4ace718c76..5a67385111 100644 --- a/frontend/src/pages/org/browser-profiles-list.ts +++ b/frontend/src/pages/org/browser-profiles-list.ts @@ -3,6 +3,7 @@ import { Task } from "@lit/task"; import { html, type PropertyValues } from "lit"; import { customElement, state } from "lit/decorators.js"; import { when } from "lit/directives/when.js"; +import omit from "lodash/fp/omit"; import queryString from "query-string"; import type { Profile } from "./types"; @@ -20,7 +21,7 @@ import { ClipboardController } from "@/controllers/clipboard"; import { SearchParamsValue } from "@/controllers/searchParamsValue"; import { originsWithRemainder } from "@/features/browser-profiles/templates/origins-with-remainder"; import { emptyMessage } from "@/layouts/emptyMessage"; -import { page } from "@/layouts/page"; +import { pageHeader } from "@/layouts/pageHeader"; import { OrgTab } from "@/routes"; import type { APIPaginatedList, @@ -30,6 +31,7 @@ import type { import { SortDirection as SortDirectionEnum } from "@/types/utils"; import { isApiError } from "@/utils/api"; import { isArchivingDisabled } from "@/utils/orgs"; +import { toSearchItem, type SearchValues } from "@/utils/searchValues"; const SORT_DIRECTIONS = ["asc", "desc"] as const; type SortDirection = (typeof SORT_DIRECTIONS)[number]; @@ -67,6 +69,11 @@ const DEFAULT_SORT_BY = { } as const satisfies SortBy; const INITIAL_PAGE_SIZE = 20; const FILTER_BY_CURRENT_USER_STORAGE_KEY = "btrix.filterByCurrentUser.crawls"; +const SEARCH_KEYS = ["name"] as const; + +type FilterBy = { + name?: string; +}; const columnsCss = [ "min-content", // Status @@ -169,6 +176,32 @@ export class BrowserProfilesList extends BtrixElement { }, ); + private readonly filterBy = new SearchParamsValue( + this, + (value, params) => { + const keys = ["name", "firstSeed", "state"] as (keyof FilterBy)[]; + keys.forEach((key) => { + if (value[key] == null) { + params.delete(key); + } else { + switch (key) { + case "name": + params.set(key, value[key]); + break; + default: + break; + } + } + }); + return params; + }, + (params) => { + return { + name: params.get("name") ?? undefined, + }; + }, + ); + private get hasFiltersSet() { return [ this.filterByCurrentUser.value || undefined, @@ -221,6 +254,15 @@ export class BrowserProfilesList extends BtrixElement { ] as const, }); + private readonly searchOptionsTask = new Task(this, { + task: async (_args, { signal }) => { + const data = await this.getSearchValues(signal); + + return [...data.names.map(toSearchItem("name"))]; + }, + args: () => [] as const, + }); + protected willUpdate(changedProperties: PropertyValues): void { if ( changedProperties.has("orderBy.internalValue") || @@ -243,8 +285,8 @@ export class BrowserProfilesList extends BtrixElement { } render() { - return page( - { + return html` + ${pageHeader({ title: msg("Browser Profiles"), border: false, actions: this.isCrawler @@ -266,9 +308,9 @@ export class BrowserProfilesList extends BtrixElement { ` : undefined, - }, - this.renderPage, - ); + })} + ${this.renderPage()} + `; } private readonly renderPage = () => { @@ -377,24 +419,52 @@ export class BrowserProfilesList extends BtrixElement { private renderControls() { return html` -
+
+
${this.renderSearch()}
+ +
+ + ${this.renderSortControl()} +
+
${msg("Filter by:")} ${this.renderFilterControls()}
- -
- - ${this.renderSortControl()} -
`; } + private renderSearch() { + return html` + { + const { key, value } = e.detail; + this.filterBy.setValue({ + ...this.filterBy.value, + [key]: value, + }); + }} + @btrix-clear=${() => { + const otherFilters = omit(SEARCH_KEYS, this.filterBy.value); + this.filterBy.setValue(otherFilters); + }} + > + + `; + } + private renderFilterControls() { return html` ( + `/orgs/${this.orgId}/profiles/search-values`, + { + signal, + }, + ); + } } diff --git a/frontend/src/utils/searchValues.ts b/frontend/src/utils/searchValues.ts new file mode 100644 index 0000000000..267eb5c80a --- /dev/null +++ b/frontend/src/utils/searchValues.ts @@ -0,0 +1,14 @@ +export type SearchValues = { + names: string[]; +}; + +export type SearchField = "name"; + +/** + * Convert API search values to a format compatible with Fuse collections + */ +export function toSearchItem(key: T) { + return (value: string) => ({ + [key as string]: value, + }); +} From d204eee035473278901707b2cd8c1a46a5732b15 Mon Sep 17 00:00:00 2001 From: sua yoo Date: Tue, 25 Nov 2025 17:47:10 -0800 Subject: [PATCH 2/9] switch to single filter --- .../src/pages/org/browser-profiles-list.ts | 170 ++++++++---------- 1 file changed, 70 insertions(+), 100 deletions(-) diff --git a/frontend/src/pages/org/browser-profiles-list.ts b/frontend/src/pages/org/browser-profiles-list.ts index 5a67385111..0d55eee336 100644 --- a/frontend/src/pages/org/browser-profiles-list.ts +++ b/frontend/src/pages/org/browser-profiles-list.ts @@ -68,11 +68,16 @@ const DEFAULT_SORT_BY = { direction: sortableFields.modified.defaultDirection || "desc", } as const satisfies SortBy; const INITIAL_PAGE_SIZE = 20; -const FILTER_BY_CURRENT_USER_STORAGE_KEY = "btrix.filterByCurrentUser.crawls"; +const FILTER_BY_CURRENT_USER_STORAGE_KEY = + "btrix.filterByCurrentUser.browserProfiles"; const SEARCH_KEYS = ["name"] as const; +const DEFAULT_TAGS_TYPE = "or"; type FilterBy = { - name?: string; + name?: string | null; + tags?: string[]; + tagsType?: "and" | "or"; + mine?: boolean; }; const columnsCss = [ @@ -131,82 +136,64 @@ export class BrowserProfilesList extends BtrixElement { }, ); - private readonly filterByTags = new SearchParamsValue( - this, - (value, params) => { - params.delete("tags"); - value?.forEach((v) => { - params.append("tags", v); - }); - return params; - }, - (params) => params.getAll("tags"), - ); - - private readonly filterByTagsType = new SearchParamsValue<"and" | "or">( + private readonly filterBy = new SearchParamsValue( this, (value, params) => { - if (value === "and") { - params.set("tagsType", value); + if ("name" in value && value["name"]) { + params.set("name", value["name"]); + } else { + params.delete("name"); + } + if ("tags" in value) { + params.delete("tags"); + value["tags"]?.forEach((v) => { + params.append("tags", v); + }); + } else { + params.delete("tags"); + } + if ("tagsType" in value && value["tagsType"] === "and") { + params.set("tagsType", value["tagsType"]); } else { params.delete("tagsType"); } - return params; - }, - (params) => (params.get("tagsType") === "and" ? "and" : "or"), - ); - - private readonly filterByCurrentUser = new SearchParamsValue( - this, - (value, params) => { - if (value) { + if ("mine" in value && value["mine"]) { params.set("mine", "true"); + window.sessionStorage.setItem( + FILTER_BY_CURRENT_USER_STORAGE_KEY, + "true", + ); } else { params.delete("mine"); + window.sessionStorage.removeItem(FILTER_BY_CURRENT_USER_STORAGE_KEY); } return params; }, - (params) => params.get("mine") === "true", + (params) => ({ + name: params.get("name") ?? undefined, + tags: params.getAll("tags"), + tagsType: params.get("tagsType") === "and" ? "and" : "or", + mine: params.get("mine") === "true", + }), { - initial: (initialValue) => - window.sessionStorage.getItem(FILTER_BY_CURRENT_USER_STORAGE_KEY) === - "true" || - initialValue || - false, - }, - ); - - private readonly filterBy = new SearchParamsValue( - this, - (value, params) => { - const keys = ["name", "firstSeed", "state"] as (keyof FilterBy)[]; - keys.forEach((key) => { - if (value[key] == null) { - params.delete(key); - } else { - switch (key) { - case "name": - params.set(key, value[key]); - break; - default: - break; - } - } - }); - return params; - }, - (params) => { - return { - name: params.get("name") ?? undefined, - }; + initial: (initialValue) => ({ + ...initialValue, + mine: + window.sessionStorage.getItem(FILTER_BY_CURRENT_USER_STORAGE_KEY) === + "true" || + initialValue?.["mine"] || + false, + }), }, ); private get hasFiltersSet() { - return [ - this.filterByCurrentUser.value || undefined, - this.filterByTags.value?.length || undefined, - ].some((v) => v !== undefined); + const filterBy = this.filterBy.value; + return ( + filterBy.tags?.length || + filterBy.tagsType !== DEFAULT_TAGS_TYPE || + filterBy.mine !== undefined + ); } get isCrawler() { @@ -214,27 +201,17 @@ export class BrowserProfilesList extends BtrixElement { } private clearFilters() { - this.filterByCurrentUser.setValue(false); - this.filterByTags.setValue([]); + this.filterBy.setValue({}); } private readonly profilesTask = new Task(this, { - task: async ( - [ - pagination, - orderBy, - filterByCurrentUser, - filterByTags, - filterByTagsType, - ], - { signal }, - ) => { + task: async ([pagination, orderBy, filterBy], { signal }) => { return this.getProfiles( { ...pagination, - userid: filterByCurrentUser ? this.userInfo?.id : undefined, - tags: filterByTags, - tagMatch: filterByTagsType, + userid: filterBy.mine ? this.userInfo?.id : undefined, + tags: filterBy.tags, + tagMatch: filterBy.tagsType, sortBy: orderBy.field, sortDirection: orderBy.direction === "desc" @@ -245,13 +222,7 @@ export class BrowserProfilesList extends BtrixElement { ); }, args: () => - [ - this.pagination, - this.orderBy.value, - this.filterByCurrentUser.value, - this.filterByTags.value, - this.filterByTagsType.value, - ] as const, + [this.pagination, this.orderBy.value, this.filterBy.value] as const, }); private readonly searchOptionsTask = new Task(this, { @@ -266,22 +237,13 @@ export class BrowserProfilesList extends BtrixElement { protected willUpdate(changedProperties: PropertyValues): void { if ( changedProperties.has("orderBy.internalValue") || - changedProperties.has("filterByCurrentUser.internalValue") || - changedProperties.has("filterByTags.internalValue") || - changedProperties.has("filterByTagsType.internalValue") + changedProperties.has("filterBy.internalValue") ) { this.pagination = { ...this.pagination, page: 1, }; } - - if (changedProperties.has("filterByCurrentUser.internalValue")) { - window.sessionStorage.setItem( - FILTER_BY_CURRENT_USER_STORAGE_KEY, - this.filterByCurrentUser.value.toString(), - ); - } } render() { @@ -466,22 +428,30 @@ export class BrowserProfilesList extends BtrixElement { } private renderFilterControls() { + const filterBy = this.filterBy.value; + return html` { - this.filterByTags.setValue(e.detail.value?.tags || []); - this.filterByTagsType.setValue(e.detail.value?.type || "or"); + this.filterBy.setValue({ + ...this.filterBy.value, + tags: e.detail.value?.tags || [], + tagsType: e.detail.value?.type || DEFAULT_TAGS_TYPE, + }); }} > { const { checked } = e.target as FilterChip; - this.filterByCurrentUser.setValue(Boolean(checked)); + this.filterBy.setValue({ + ...this.filterBy.value, + mine: checked, + }); }} > ${msg("Mine")} From b51162765eb19b96fbd7b1cc02c6818ca84979db Mon Sep 17 00:00:00 2001 From: sua yoo Date: Tue, 25 Nov 2025 17:53:24 -0800 Subject: [PATCH 3/9] filter by name --- frontend/src/pages/org/browser-profiles-list.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/src/pages/org/browser-profiles-list.ts b/frontend/src/pages/org/browser-profiles-list.ts index 0d55eee336..41a39bc7df 100644 --- a/frontend/src/pages/org/browser-profiles-list.ts +++ b/frontend/src/pages/org/browser-profiles-list.ts @@ -210,6 +210,7 @@ export class BrowserProfilesList extends BtrixElement { { ...pagination, userid: filterBy.mine ? this.userInfo?.id : undefined, + name: filterBy.name || undefined, tags: filterBy.tags, tagMatch: filterBy.tagsType, sortBy: orderBy.field, @@ -689,6 +690,7 @@ export class BrowserProfilesList extends BtrixElement { private async getProfiles( params: { userid?: string; + name?: string; tags?: string[]; tagMatch?: string; } & APIPaginationQuery & From 56159a5ead37b23ade1d0f0015185fc3475bfb13 Mon Sep 17 00:00:00 2001 From: sua yoo Date: Tue, 25 Nov 2025 18:02:19 -0800 Subject: [PATCH 4/9] fix inconsistencies --- frontend/src/layouts/pageHeader.ts | 4 ++-- frontend/src/pages/org/browser-profiles-list.ts | 2 +- frontend/src/pages/org/collections-list.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/layouts/pageHeader.ts b/frontend/src/layouts/pageHeader.ts index 3b6f7478e2..5bd908cb2d 100644 --- a/frontend/src/layouts/pageHeader.ts +++ b/frontend/src/layouts/pageHeader.ts @@ -123,8 +123,8 @@ export function pageHeader({ return html`
diff --git a/frontend/src/pages/org/browser-profiles-list.ts b/frontend/src/pages/org/browser-profiles-list.ts index 41a39bc7df..a1c1e6990e 100644 --- a/frontend/src/pages/org/browser-profiles-list.ts +++ b/frontend/src/pages/org/browser-profiles-list.ts @@ -411,7 +411,7 @@ export class BrowserProfilesList extends BtrixElement { .searchKeys=${SEARCH_KEYS} .searchOptions=${this.searchOptionsTask.value || []} .keyLabels=${{}} - placeholder=${msg("Search browser profiles by name")} + placeholder=${msg("Search by name")} @btrix-select=${(e: CustomEvent) => { const { key, value } = e.detail; this.filterBy.setValue({ diff --git a/frontend/src/pages/org/collections-list.ts b/frontend/src/pages/org/collections-list.ts index 336eeeb88f..bedc7b7430 100644 --- a/frontend/src/pages/org/collections-list.ts +++ b/frontend/src/pages/org/collections-list.ts @@ -150,6 +150,7 @@ export class CollectionsList extends WithSearchOrgContext(BtrixElement) {
${pageHeader({ title: msg("Collections"), + border: false, actions: this.isCrawler ? html` ` : nothing, - classNames: tw`border-b-transparent`, })}
@@ -384,7 +384,7 @@ export class CollectionsList extends WithSearchOrgContext(BtrixElement) { > { this.searchResultsOpen = false; From f594173dfa49356034a53ce530db5a6917a054c4 Mon Sep 17 00:00:00 2001 From: sua yoo Date: Tue, 25 Nov 2025 18:03:11 -0800 Subject: [PATCH 5/9] fix filter set --- frontend/src/pages/org/browser-profiles-list.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/org/browser-profiles-list.ts b/frontend/src/pages/org/browser-profiles-list.ts index a1c1e6990e..045cacf200 100644 --- a/frontend/src/pages/org/browser-profiles-list.ts +++ b/frontend/src/pages/org/browser-profiles-list.ts @@ -192,7 +192,7 @@ export class BrowserProfilesList extends BtrixElement { return ( filterBy.tags?.length || filterBy.tagsType !== DEFAULT_TAGS_TYPE || - filterBy.mine !== undefined + filterBy.mine ); } From 9862eb6397b77b340c63b18d2d2e5fb16bb28893 Mon Sep 17 00:00:00 2001 From: sua yoo Date: Tue, 25 Nov 2025 18:06:04 -0800 Subject: [PATCH 6/9] fix name sort direction --- frontend/src/pages/org/browser-profiles-list.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frontend/src/pages/org/browser-profiles-list.ts b/frontend/src/pages/org/browser-profiles-list.ts index 045cacf200..f47f269916 100644 --- a/frontend/src/pages/org/browser-profiles-list.ts +++ b/frontend/src/pages/org/browser-profiles-list.ts @@ -47,7 +47,7 @@ const sortableFields: Record< > = { name: { label: msg("Name"), - defaultDirection: "desc", + defaultDirection: "asc", }, url: { label: msg("Primary Site"), @@ -410,7 +410,6 @@ export class BrowserProfilesList extends BtrixElement { { const { key, value } = e.detail; From 426d4e67a81ba99d2b40fd254c21c83646ad57a9 Mon Sep 17 00:00:00 2001 From: sua yoo Date: Wed, 26 Nov 2025 08:03:22 -0800 Subject: [PATCH 7/9] pass initial value to combobox --- frontend/src/pages/org/browser-profiles-list.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/org/browser-profiles-list.ts b/frontend/src/pages/org/browser-profiles-list.ts index f47f269916..d7b68621df 100644 --- a/frontend/src/pages/org/browser-profiles-list.ts +++ b/frontend/src/pages/org/browser-profiles-list.ts @@ -74,7 +74,7 @@ const SEARCH_KEYS = ["name"] as const; const DEFAULT_TAGS_TYPE = "or"; type FilterBy = { - name?: string | null; + name?: string; tags?: string[]; tagsType?: "and" | "or"; mine?: boolean; @@ -170,7 +170,7 @@ export class BrowserProfilesList extends BtrixElement { return params; }, (params) => ({ - name: params.get("name") ?? undefined, + name: params.get("name") || undefined, tags: params.getAll("tags"), tagsType: params.get("tagsType") === "and" ? "and" : "or", mine: params.get("mine") === "true", @@ -410,6 +410,7 @@ export class BrowserProfilesList extends BtrixElement { { const { key, value } = e.detail; From 26cc3f7181532c040922fed8abd7961fbc1b70ec Mon Sep 17 00:00:00 2001 From: sua yoo Date: Wed, 26 Nov 2025 13:40:10 -0800 Subject: [PATCH 8/9] account for name in filter set --- frontend/src/pages/org/browser-profiles-list.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/pages/org/browser-profiles-list.ts b/frontend/src/pages/org/browser-profiles-list.ts index d7b68621df..744a1e3e6f 100644 --- a/frontend/src/pages/org/browser-profiles-list.ts +++ b/frontend/src/pages/org/browser-profiles-list.ts @@ -190,6 +190,7 @@ export class BrowserProfilesList extends BtrixElement { private get hasFiltersSet() { const filterBy = this.filterBy.value; return ( + filterBy.name || filterBy.tags?.length || filterBy.tagsType !== DEFAULT_TAGS_TYPE || filterBy.mine From f3b04777330c258b4048cc5d78b6fc341a9299ad Mon Sep 17 00:00:00 2001 From: sua yoo Date: Wed, 26 Nov 2025 18:03:14 -0800 Subject: [PATCH 9/9] Update frontend/src/pages/org/browser-profiles-list.ts Co-authored-by: Emma Segal-Grossman --- frontend/src/pages/org/browser-profiles-list.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/org/browser-profiles-list.ts b/frontend/src/pages/org/browser-profiles-list.ts index 744a1e3e6f..2b12e6a852 100644 --- a/frontend/src/pages/org/browser-profiles-list.ts +++ b/frontend/src/pages/org/browser-profiles-list.ts @@ -192,7 +192,7 @@ export class BrowserProfilesList extends BtrixElement { return ( filterBy.name || filterBy.tags?.length || - filterBy.tagsType !== DEFAULT_TAGS_TYPE || + (filterBy.tagsType ?? DEFAULT_TAGS_TYPE) !== DEFAULT_TAGS_TYPE || filterBy.mine ); }