Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Update collection sorting & metadata #2324

Merged
merged 18 commits into from
Jan 22, 2025
26 changes: 22 additions & 4 deletions backend/btrixcloud/colls.py
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ async def list_collections(
page = page - 1
skip = page * page_size

match_query: dict[str, object] = {"oid": org.id}
match_query: Dict[str, Union[str, UUID, int, object]] = {"oid": org.id}

if name:
match_query["name"] = name
Expand All @@ -409,15 +409,33 @@ async def list_collections(
elif access:
match_query["access"] = access

aggregate = [{"$match": match_query}]
aggregate: List[Dict[str, Union[str, UUID, int, object]]] = [
{"$match": match_query}
]

if sort_by:
if sort_by not in ("modified", "name", "description", "totalSize"):
if sort_by not in (
"created",
"modified",
"dateLatest",
"name",
"crawlCount",
"pageCount",
"totalSize",
"description",
"caption",
):
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")

aggregate.extend([{"$sort": {sort_by: sort_direction}}])
sort_query = {sort_by: sort_direction}

# add secondary sort keys:
if sort_by == "dateLatest":
sort_query["dateEarliest"] = sort_direction

aggregate.extend([{"$sort": sort_query}])

aggregate.extend(
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,12 +135,13 @@ export class CollectionMetadataDialog extends BtrixElement {
<sl-tooltip>
<span slot="content">
${msg(
"Write a short description that summarizes this collection. If the collection is public, this description will be visible next to the collection name.",
"Write a short description that summarizes this collection. If the collection is shareable, this will appear next to the collection name.",
)}
${this.collection
? nothing
: msg(
"You can write a longer description in the 'About' section after creating the collection.",
html`You can add a longer description in the “About”
section after creating the collection.`,
)}
</span>
<sl-icon
Expand Down
57 changes: 57 additions & 0 deletions frontend/src/layouts/collections/metadataColumn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { msg } from "@lit/localize";
import { html, type TemplateResult } from "lit";
import { when } from "lit/directives/when.js";

import { monthYearDateRange } from "@/strings/utils";
import type { Collection, PublicCollection } from "@/types/collection";
import localize from "@/utils/localize";
import { pluralOf } from "@/utils/pluralize";

export function metadataItemWithCollection(
collection?: Collection | PublicCollection,
) {
return function metadataItem({
label,
render,
}: {
label: string | TemplateResult;
render: (c: PublicCollection) => TemplateResult | string;
}) {
return html`
<btrix-desc-list-item label=${label}>
${when(
collection,
render,
() => html`<sl-skeleton class="w-full"></sl-skeleton>`,
)}
</btrix-desc-list-item>
`;
};
}

export function metadataColumn(collection?: Collection | PublicCollection) {
const metadataItem = metadataItemWithCollection(collection);

return html`
<btrix-desc-list>
${metadataItem({
label: msg("Collection Period"),
render: (col) => html`
<span class="font-sans">
${monthYearDateRange(col.dateEarliest, col.dateLatest)}
</span>
`,
})}
${metadataItem({
label: msg("Pages in Collection"),
render: (col) =>
`${localize.number(col.pageCount)} ${pluralOf("pages", col.pageCount)}`,
})}
${metadataItem({
label: msg("Total Page Snapshots"),
render: (col) =>
`${localize.number(col.snapshotCount)} ${pluralOf("snapshots", col.snapshotCount)}`,
})}
</btrix-desc-list>
`;
}
36 changes: 3 additions & 33 deletions frontend/src/pages/collections/collection.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { localized, msg, str } from "@lit/localize";
import { localized, msg } from "@lit/localize";
import { Task, TaskStatus } from "@lit/task";
import { html, type TemplateResult } from "lit";
import { customElement, property } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { when } from "lit/directives/when.js";

import { BtrixElement } from "@/classes/BtrixElement";
import { metadataColumn } from "@/layouts/collections/metadataColumn";
import { page } from "@/layouts/page";
import { RouteNamespace } from "@/routes";
import type { PublicCollection } from "@/types/collection";
Expand Down Expand Up @@ -211,39 +212,8 @@ export class Collection extends BtrixElement {
`;
}

// TODO Consolidate with collection-detail.ts
private renderAbout(collection: PublicCollection) {
const dateRange = () => {
if (!collection.dateEarliest || !collection.dateLatest) {
return msg("n/a");
}
const format: Intl.DateTimeFormatOptions = {
month: "long",
year: "numeric",
};
const dateEarliest = this.localize.date(collection.dateEarliest, format);
const dateLatest = this.localize.date(collection.dateLatest, format);

if (dateEarliest === dateLatest) return dateLatest;

return msg(str`${dateEarliest} to ${dateLatest}`, {
desc: "Date range formatted to show full month name and year",
});
};

const metadata = html`
<btrix-desc-list>
<btrix-desc-list-item label=${msg("Collection Period")}>
<span class="font-sans">${dateRange()}</span>
</btrix-desc-list-item>
<btrix-desc-list-item label=${msg("Total Pages")}>
${this.localize.number(collection.pageCount)}
</btrix-desc-list-item>
<btrix-desc-list-item label=${msg("Collection Size")}>
${this.localize.bytes(collection.totalSize)}
</btrix-desc-list-item>
</btrix-desc-list>
`;
const metadata = metadataColumn(collection);

if (collection.description) {
return html`
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/pages/org/archived-item-qa/archived-item-qa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import type {
} from "@/types/api";
import type { ArchivedItem, ArchivedItemPageComment } from "@/types/crawler";
import type { ArchivedItemQAPage, QARun } from "@/types/qa";
import { SortDirection as APISortDirection } from "@/types/utils";
import {
isActive,
isSuccessfullyFinished,
Expand Down Expand Up @@ -553,7 +554,8 @@ export class ArchivedItemQA extends BtrixElement {
.pages=${this.pages}
.orderBy=${{
field: this.sortPagesBy.sortBy,
direction: (this.sortPagesBy.sortDirection === -1
direction: (this.sortPagesBy.sortDirection ===
APISortDirection.Descending
? "desc"
: "asc") as SortDirection,
}}
Expand Down
122 changes: 58 additions & 64 deletions frontend/src/pages/org/collection-detail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,21 @@ import type { MarkdownEditor } from "@/components/ui/markdown-editor";
import type { PageChangeEvent } from "@/components/ui/pagination";
import { SelectCollectionAccess } from "@/features/collections/select-collection-access";
import type { ShareCollection } from "@/features/collections/share-collection";
import {
metadataColumn,
metadataItemWithCollection,
} from "@/layouts/collections/metadataColumn";
import { pageHeader, pageNav, type Breadcrumb } from "@/layouts/pageHeader";
import type {
APIPaginatedList,
APIPaginationQuery,
APISortQuery,
} from "@/types/api";
import { CollectionAccess, type Collection } from "@/types/collection";
import {
CollectionAccess,
type Collection,
type PublicCollection,
} from "@/types/collection";
import type { ArchivedItem, Crawl, Upload } from "@/types/crawler";
import type { CrawlState } from "@/types/crawlState";
import { pluralOf } from "@/utils/pluralize";
Expand All @@ -43,7 +51,7 @@ export class CollectionDetail extends BtrixElement {
collectionId!: string;

@property({ type: String })
collectionTab: Tab = Tab.Replay;
collectionTab: Tab | null = Tab.Replay;

@state()
private collection?: Collection;
Expand Down Expand Up @@ -105,6 +113,9 @@ export class CollectionDetail extends BtrixElement {
void this.fetchCollection();
void this.fetchArchivedItems({ page: 1 });
}
if (changedProperties.has("collectionTab") && this.collectionTab === null) {
this.collectionTab = Tab.Replay;
}
}

protected async updated(
Expand Down Expand Up @@ -472,16 +483,6 @@ export class CollectionDetail extends BtrixElement {
(col) =>
`${this.localize.number(col.crawlCount)} ${pluralOf("items", col.crawlCount)}`,
)}
${this.renderDetailItem(msg("Total Size"), (col) =>
this.localize.bytes(col.totalSize || 0, {
unitDisplay: "narrow",
}),
)}
${this.renderDetailItem(
msg("Total Pages"),
(col) =>
`${this.localize.number(col.pageCount)} ${pluralOf("pages", col.pageCount)}`,
)}
${when(this.collection?.created, (created) =>
// Collections created before 49516bc4 is released may not have date in db
created
Expand All @@ -495,12 +496,13 @@ export class CollectionDetail extends BtrixElement {
year="numeric"
hour="numeric"
minute="numeric"
time-zone-name="short"
></btrix-format-date>`,
)
: nothing,
)}
${this.renderDetailItem(
msg("Last Updated"),
msg("Last Modified"),
(col) =>
html`<btrix-format-date
date=${col.modified}
Expand All @@ -509,6 +511,7 @@ export class CollectionDetail extends BtrixElement {
year="numeric"
hour="numeric"
minute="numeric"
time-zone-name="short"
></btrix-format-date>`,
)}
</btrix-desc-list>
Expand All @@ -517,67 +520,58 @@ export class CollectionDetail extends BtrixElement {

private renderDetailItem(
label: string | TemplateResult,
renderContent: (collection: Collection) => TemplateResult | string,
renderContent: (collection: PublicCollection) => TemplateResult | string,
) {
return html`
<btrix-desc-list-item label=${label}>
${when(
this.collection,
() => renderContent(this.collection!),
() => html`<sl-skeleton class="w-full"></sl-skeleton>`,
)}
</btrix-desc-list-item>
`;
return metadataItemWithCollection(this.collection)({
label,
render: renderContent,
});
}

// TODO Consolidate with collection.ts
private renderAbout() {
const dateRange = (collection: Collection) => {
if (!collection.dateEarliest || !collection.dateLatest) {
return msg("n/a");
}
const format: Intl.DateTimeFormatOptions = {
month: "long",
year: "numeric",
};
const dateEarliest = this.localize.date(collection.dateEarliest, format);
const dateLatest = this.localize.date(collection.dateLatest, format);

if (dateEarliest === dateLatest) return dateLatest;

return msg(str`${dateEarliest} to ${dateLatest}`, {
desc: "Date range formatted to show full month name and year",
});
};
const skeleton = html`<sl-skeleton class="w-24"></sl-skeleton>`;

const metadata = html`
<btrix-desc-list>
<btrix-desc-list-item label=${msg("Collection Period")}>
<span class="font-sans"
>${this.collection ? dateRange(this.collection) : skeleton}</span
>
</btrix-desc-list-item>
</btrix-desc-list>
`;
const metadata = metadataColumn(this.collection);

return html`
<div class="flex flex-1 flex-col gap-10 lg:flex-row">
<section class="flex w-full max-w-4xl flex-col leading-relaxed">
<header class="mb-3 flex min-h-8 items-end justify-between">
<h2 class="text-base font-semibold leading-none">
${msg("Description")}
</h2>
<header class="mb-2 flex min-h-8 items-center justify-between">
<div class="flex items-center gap-2">
<h2 class="text-base font-medium">
${msg("About This Collection")}
</h2>
<sl-tooltip>
<div slot="content">
<p class="mb-3">
${msg(
html`Describe your collection in long-form rich text (e.g.
<strong>bold</strong> and <em>italicized</em> text.)`,
)}
</p>
<p>
${msg(
html`If this collection is shareable, this will appear in
the “About This Collection” section of the shared
collection.`,
)}
</p>
</div>
<sl-icon
name="info-circle"
class="size-4 text-base text-neutral-500 [vertical-align:-.175em]"
></sl-icon>
</sl-tooltip>
</div>
${when(
this.collection?.description && !this.isEditingDescription,
() => html`
<sl-button
size="small"
@click=${() => (this.isEditingDescription = true)}
>
<sl-icon name="pencil" slot="prefix"></sl-icon>
${msg("Edit Description")}
</sl-button>
<sl-tooltip content=${msg("Edit description")}>
<sl-icon-button
class="text-base"
name="pencil"
@click=${() => (this.isEditingDescription = true)}
>
</sl-icon-button>
</sl-tooltip>
`,
)}
</header>
Expand All @@ -602,7 +596,7 @@ export class CollectionDetail extends BtrixElement {
`
: html`
<div class="text-center text-neutral-500">
<p class="mb-3">
<p class="mb-3 max-w-prose">
${msg("No description provided.")}
</p>
<sl-button
Expand Down
Loading
Loading