Skip to content

Commit 4db1e9d

Browse files
committed
feat: Add tags to profile + consolidate tag filter (#2976)
- Allows users to set tags on browser profiles - Allows users to filter browser profiles by tag - Refactors `workflow-tag-filter` and `archived-item-tag-filter` into single `tag-filter`
1 parent 8413a04 commit 4db1e9d

File tree

20 files changed

+297
-433
lines changed

20 files changed

+297
-433
lines changed

frontend/src/components/ui/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ import("./select-crawler-proxy");
4141
import("./select-crawler");
4242
import("./syntax-input");
4343
import("./table");
44-
import("./tag-input");
4544
import("./tag");
45+
import("./tag-filter");
46+
import("./tag-input");
4647
import("./time-input");
4748
import("./user-language-select");
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import "./tag-filter";

frontend/src/features/archived-items/archived-item-tag-filter.ts renamed to frontend/src/components/ui/tag-filter/tag-filter.ts

Lines changed: 44 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -20,37 +20,39 @@ import { repeat } from "lit/directives/repeat.js";
2020
import queryString from "query-string";
2121
import { isFocusable } from "tabbable";
2222

23+
import type {
24+
BtrixChangeTagFilterEvent,
25+
TagCount,
26+
TagCounts,
27+
TagType,
28+
} from "./types";
29+
2330
import { BtrixElement } from "@/classes/BtrixElement";
24-
import type { BtrixChangeEvent } from "@/events/btrix-change";
25-
import type { ArchivedItem } from "@/types/crawler";
26-
import { type WorkflowTag, type WorkflowTags } from "@/types/workflow";
2731
import { stopProp } from "@/utils/events";
2832
import { isNotEqual } from "@/utils/is-not-equal";
2933
import { tw } from "@/utils/tailwind";
3034

3135
const MAX_TAGS_IN_LABEL = 5;
32-
33-
type ChangeArchivedItemTagEventDetails =
34-
| { tags: string[]; type: "and" | "or" }
35-
| undefined;
36-
37-
export type BtrixChangeArchivedItemTagFilterEvent =
38-
BtrixChangeEvent<ChangeArchivedItemTagEventDetails>;
36+
const apiPathForTagType: Record<TagType, string> = {
37+
workflow: "crawlconfigs",
38+
"workflow-crawl": "crawls",
39+
"archived-item": "all-crawls",
40+
"archived-item-crawl": "crawls",
41+
upload: "uploads",
42+
profile: "profiles",
43+
};
3944

4045
/**
4146
* @fires btrix-change
4247
*/
43-
@customElement("btrix-archived-item-tag-filter")
48+
@customElement("btrix-tag-filter")
4449
@localized()
45-
export class ArchivedItemTagFilter extends BtrixElement {
46-
@property({ type: Array })
47-
tags?: string[];
48-
50+
export class TagFilter extends BtrixElement {
4951
@property({ type: String })
50-
itemType?: ArchivedItem["type"];
52+
tagType?: TagType;
5153

52-
@property({ type: Boolean })
53-
includeNotSuccessful = false;
54+
@property({ type: Array })
55+
tags?: string[];
5456

5557
@state()
5658
private searchString = "";
@@ -61,7 +63,7 @@ export class ArchivedItemTagFilter extends BtrixElement {
6163
@queryAll("sl-checkbox")
6264
private readonly checkboxes!: NodeListOf<SlCheckbox>;
6365

64-
private readonly fuse = new Fuse<WorkflowTag>([], {
66+
private readonly fuse = new Fuse<TagCount>([], {
6567
keys: ["tag"],
6668
});
6769

@@ -82,14 +84,22 @@ export class ArchivedItemTagFilter extends BtrixElement {
8284
}
8385

8486
private readonly orgTagsTask = new Task(this, {
85-
task: async ([itemType], { signal }) => {
86-
const query = queryString.stringify({
87-
onlySuccessful: !this.includeNotSuccessful,
88-
crawlType: itemType,
89-
});
90-
91-
const { tags } = await this.api.fetch<WorkflowTags>(
92-
`/orgs/${this.orgId}/all-crawls/tagCounts?${query}`,
87+
task: async ([tagType], { signal }) => {
88+
if (!tagType) {
89+
console.debug("no tagType");
90+
return;
91+
}
92+
93+
let query = "";
94+
95+
if (tagType === "workflow-crawl") {
96+
query = queryString.stringify({
97+
onlySuccessful: false,
98+
});
99+
}
100+
101+
const { tags } = await this.api.fetch<TagCounts>(
102+
`/orgs/${this.orgId}/${apiPathForTagType[tagType]}/tagCounts${query && `?${query}`}`,
93103
{ signal },
94104
);
95105

@@ -98,7 +108,7 @@ export class ArchivedItemTagFilter extends BtrixElement {
98108
// Match fuse shape
99109
return tags.map((item) => ({ item }));
100110
},
101-
args: () => [this.itemType] as const,
111+
args: () => [this.tagType] as const,
102112
});
103113

104114
render() {
@@ -149,9 +159,8 @@ export class ArchivedItemTagFilter extends BtrixElement {
149159
this.checkboxes.forEach((checkbox) => {
150160
checkbox.checked = false;
151161
});
152-
162+
this.selected = new Map();
153163
this.type = "or";
154-
155164
void this.dispatchChange();
156165
}}
157166
>${msg("Clear")}</sl-button
@@ -190,6 +199,8 @@ export class ArchivedItemTagFilter extends BtrixElement {
190199
191200
${this.orgTagsTask.render({
192201
complete: (tags) => {
202+
if (!tags) return;
203+
193204
let options = tags;
194205
195206
if (tags.length && this.searchString) {
@@ -267,8 +278,8 @@ export class ArchivedItemTagFilter extends BtrixElement {
267278
`;
268279
}
269280

270-
private renderList(opts: { item: WorkflowTag }[]) {
271-
const tag = (tag: WorkflowTag) => {
281+
private renderList(opts: { item: TagCount }[]) {
282+
const tag = (tag: TagCount) => {
272283
const checked = this.selected.get(tag.tag) === true;
273284

274285
return html`
@@ -314,9 +325,7 @@ export class ArchivedItemTagFilter extends BtrixElement {
314325
.filter(([_tag, selected]) => selected)
315326
.map(([tag]) => tag);
316327
this.dispatchEvent(
317-
new CustomEvent<
318-
BtrixChangeEvent<ChangeArchivedItemTagEventDetails>["detail"]
319-
>("btrix-change", {
328+
new CustomEvent<BtrixChangeTagFilterEvent["detail"]>("btrix-change", {
320329
detail: {
321330
value: selectedTags.length
322331
? { tags: selectedTags, type: this.type }
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import type { BtrixChangeEvent } from "@/events/btrix-change";
2+
3+
export type TagType =
4+
| "workflow"
5+
| "workflow-crawl"
6+
| "archived-item"
7+
| "archived-item-crawl"
8+
| "upload"
9+
| "profile";
10+
11+
export type TagCount = {
12+
tag: string;
13+
count: number;
14+
};
15+
16+
export type TagCounts = {
17+
tags: TagCount[];
18+
};
19+
20+
export type ChangeTagEventDetails =
21+
| { tags: string[]; type: "and" | "or" }
22+
| undefined;
23+
24+
export type BtrixChangeTagFilterEvent = BtrixChangeEvent<ChangeTagEventDetails>;

frontend/src/components/ui/tag-input.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,10 @@ export class TagInput extends LitElement {
150150
@query("sl-popup")
151151
private readonly combobox!: SlPopup;
152152

153+
public getTags() {
154+
return this.tags;
155+
}
156+
153157
connectedCallback() {
154158
if (this.initialTags) {
155159
this.tags = this.initialTags;

frontend/src/features/archived-items/file-uploader.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ import queryString from "query-string";
1111
import { BtrixElement } from "@/classes/BtrixElement";
1212
import type { FileRemoveEvent } from "@/components/ui/file-list";
1313
import type { BtrixFileChangeEvent } from "@/components/ui/file-list/events";
14+
import type { TagCount, TagCounts } from "@/components/ui/tag-filter/types";
1415
import type {
1516
TagInputEvent,
1617
Tags,
1718
TagsChangeEvent,
1819
} from "@/components/ui/tag-input";
1920
import { type CollectionsChangeEvent } from "@/features/collections/collections-add";
20-
import { type WorkflowTag, type WorkflowTags } from "@/types/workflow";
2121
import { APIError } from "@/utils/api";
2222
import { maxLengthValidator } from "@/utils/form";
2323

@@ -71,7 +71,7 @@ export class FileUploader extends BtrixElement {
7171
private collectionIds: string[] = [];
7272

7373
@state()
74-
private tagOptions: WorkflowTag[] = [];
74+
private tagOptions: TagCount[] = [];
7575

7676
@state()
7777
private tagsToSave: Tags = [];
@@ -86,7 +86,7 @@ export class FileUploader extends BtrixElement {
8686
private readonly form!: Promise<HTMLFormElement>;
8787

8888
// For fuzzy search:
89-
private readonly fuse = new Fuse<WorkflowTag>([], {
89+
private readonly fuse = new Fuse<TagCount>([], {
9090
keys: ["tag"],
9191
shouldSort: false,
9292
threshold: 0.2, // stricter; default is 0.6
@@ -362,7 +362,7 @@ export class FileUploader extends BtrixElement {
362362

363363
private async fetchTags() {
364364
try {
365-
const { tags } = await this.api.fetch<WorkflowTags>(
365+
const { tags } = await this.api.fetch<TagCounts>(
366366
`/orgs/${this.orgId}/crawlconfigs/tagCounts`,
367367
);
368368

frontend/src/features/archived-items/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import("./archived-item-list");
22
import("./archived-item-state-filter");
3-
import("./archived-item-tag-filter");
43
import("./crawl-log-table");
54
import("./crawl-logs");
65
import("./delete-item-dialog");

frontend/src/features/archived-items/item-metadata-editor.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { customElement, property, query, state } from "lit/decorators.js";
77
import { when } from "lit/directives/when.js";
88

99
import { BtrixElement } from "@/classes/BtrixElement";
10+
import type { TagCount, TagCounts } from "@/components/ui/tag-filter/types";
1011
import type {
1112
TagInputEvent,
1213
Tags,
@@ -17,7 +18,6 @@ import type {
1718
CollectionsChangeEvent,
1819
} from "@/features/collections/collections-add";
1920
import type { ArchivedItem } from "@/types/crawler";
20-
import { type WorkflowTag, type WorkflowTags } from "@/types/workflow";
2121
import { isSuccessfullyFinished } from "@/utils/crawler";
2222
import { maxLengthValidator } from "@/utils/form";
2323

@@ -54,7 +54,7 @@ export class CrawlMetadataEditor extends BtrixElement {
5454
private includeName = false;
5555

5656
@state()
57-
private tagOptions: WorkflowTag[] = [];
57+
private tagOptions: TagCount[] = [];
5858

5959
@state()
6060
private tagsToSave: Tags = [];
@@ -69,7 +69,7 @@ export class CrawlMetadataEditor extends BtrixElement {
6969
public readonly collectionInput?: CollectionsAdd | null;
7070

7171
// For fuzzy search:
72-
private readonly fuse = new Fuse<WorkflowTag>([], {
72+
private readonly fuse = new Fuse<TagCount>([], {
7373
keys: ["tag"],
7474
shouldSort: false,
7575
threshold: 0.2, // stricter; default is 0.6
@@ -191,7 +191,7 @@ export class CrawlMetadataEditor extends BtrixElement {
191191
private async fetchTags() {
192192
if (!this.crawl) return;
193193
try {
194-
const { tags } = await this.api.fetch<WorkflowTags>(
194+
const { tags } = await this.api.fetch<TagCounts>(
195195
`/orgs/${this.crawl.oid}/crawlconfigs/tagCounts`,
196196
);
197197

frontend/src/features/browser-profiles/profile-metadata-dialog.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { customElement, property, query, state } from "lit/decorators.js";
77

88
import { BtrixElement } from "@/classes/BtrixElement";
99
import type { Dialog } from "@/components/ui/dialog";
10+
import type { TagInput } from "@/components/ui/tag-input";
1011
import type { ProfileUpdatedEvent } from "@/features/browser-profiles/types";
1112
import type { Profile } from "@/types/crawler";
1213
import { isApiError } from "@/utils/api";
@@ -42,6 +43,9 @@ export class ProfileMetadataDialog extends BtrixElement {
4243
@query(`sl-textarea[name="description"]`)
4344
private readonly descriptionInput?: SlTextarea | null;
4445

46+
@query(`btrix-tag-input`)
47+
private readonly tagInput?: TagInput | null;
48+
4549
private readonly validateNameMax = maxLengthValidator(50);
4650
private readonly validateDescriptionMax = maxLengthValidator(500);
4751

@@ -53,11 +57,15 @@ export class ProfileMetadataDialog extends BtrixElement {
5357
return;
5458
}
5559

56-
const params = serialize(this.form) as {
60+
const formValues = serialize(this.form) as {
5761
name: string;
5862
description: string;
5963
};
6064

65+
const tags = this.tagInput?.getTags();
66+
67+
const params = { ...formValues, tags };
68+
6169
try {
6270
await this.api.fetch<{ updated: boolean }>(
6371
`/orgs/${this.orgId}/profiles/${profile.id}`,
@@ -170,16 +178,10 @@ export class ProfileMetadataDialog extends BtrixElement {
170178
@sl-input=${this.validateDescriptionMax.validate}
171179
></sl-textarea>
172180
173-
${
174-
// <btrix-tag-input
175-
// name="tags"
176-
// .initialTags=${[]}
177-
// .tagOptions=${[]}
178-
// @tag-input=${console.log}
179-
// @tags-change=${console.log}
180-
// ></btrix-tag-input>
181-
undefined
182-
}
181+
<btrix-tag-input
182+
name="tags"
183+
.initialTags=${this.profile.tags}
184+
></btrix-tag-input>
183185
</form>
184186
<div slot="footer" class="flex justify-between">
185187
<sl-button form="crawlDetailsForm" type="reset" size="small"

frontend/src/features/crawl-workflows/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,5 @@ import("./workflow-editor");
1010
import("./workflow-list");
1111
import("./workflow-schedule-filter");
1212
import("./workflow-search");
13-
import("./workflow-tag-filter");
1413
import("./workflow-profile-filter");
1514
import("./workflow-last-crawl-state-filter");

0 commit comments

Comments
 (0)