Fixes allowed actions for viewers and crawlers throughout the app (#1326)

Closes #1294

### Changes
- `crawl-list` component
- Adds a check if there are any items in the actions menu. If not, skip
rendering the actions menu.
- This allows us to give the component no actions! Currently required to
remove them for viewers!
- Collection Details
  - Hides "Remove from Collection" option for viewers
- Crawls List
- Removes the single "View Crawl Details" option from archived items for
viewers
- All the other actions were already set up correctly to be used by all
roles!
- Dashboard
  - Hides org settings gear icon button unless the user is an admin
  - Hides "Create New" dropdown for viewers
- Workflow Details
  - Hides workflow edit icon button for viewers
  - Hides the "Delete Crawl" option in archived items for viewers
  - Hides the "Run Crawl" option for viewers
- Workflow List
- Hides all edit-related options for viewers, the only option now is
copying tags
- Removes the deactivate / delete options (were only visible when
running a crawl) in the workflow list actions

---------
Co-authored-by: Ilya Kreymer <ikreymer@gmail.com>
Co-authored-by: sua yoo <sua@suayoo.com>
This commit is contained in:
Henry Wilkinson 2023-11-17 17:41:21 -05:00 committed by GitHub
parent 1218d6e767
commit f507f1d2ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 182 additions and 170 deletions

View File

@ -833,7 +833,7 @@ def init_crawls_api(app, user_dep, *args):
crawl_id: str, crawl_id: str,
pageSize: int = DEFAULT_PAGE_SIZE, pageSize: int = DEFAULT_PAGE_SIZE,
page: int = 1, page: int = 1,
org: Organization = Depends(org_crawl_dep), org: Organization = Depends(org_viewer_dep),
): ):
crawl_raw = await ops.get_crawl_raw(crawl_id, org) crawl_raw = await ops.get_crawl_raw(crawl_id, org)
crawl = Crawl.from_dict(crawl_raw) crawl = Crawl.from_dict(crawl_raw)

View File

@ -207,6 +207,9 @@ export class CrawlListItem extends LitElement {
@state() @state()
private dropdownIsOpen?: boolean; private dropdownIsOpen?: boolean;
@state()
private hasMenuItems?: boolean;
// TODO localize // TODO localize
private numberFormatter = new Intl.NumberFormat(undefined, { private numberFormatter = new Intl.NumberFormat(undefined, {
notation: "compact", notation: "compact",
@ -357,35 +360,7 @@ export class CrawlListItem extends LitElement {
)} )}
</div> </div>
</div> </div>
<div class="col action"> ${this.renderActions()}
<sl-icon-button
class="dropdownTrigger"
label=${msg("Actions")}
name="three-dots-vertical"
@click=${(e: MouseEvent) => {
// Prevent anchor link default behavior
e.preventDefault();
// Stop prop to anchor link
e.stopPropagation();
this.dropdownIsOpen = !this.dropdownIsOpen;
}}
@focusout=${(e: FocusEvent) => {
const relatedTarget = e.relatedTarget as HTMLElement;
if (relatedTarget) {
if (this.menuArr[0]?.contains(relatedTarget)) {
// Keep dropdown open if moving to menu selection
return;
}
if (this.row?.isEqualNode(relatedTarget)) {
// Handle with click event
return;
}
}
this.dropdownIsOpen = false;
}}
>
</sl-icon-button>
</div>
</a>`; </a>`;
} }
@ -406,6 +381,7 @@ export class CrawlListItem extends LitElement {
> >
<slot <slot
name="menu" name="menu"
@slotchange=${() => (this.hasMenuItems = this.menuArr.length > 0)}
@sl-select=${() => (this.dropdownIsOpen = false)} @sl-select=${() => (this.dropdownIsOpen = false)}
></slot> ></slot>
</div> `; </div> `;
@ -440,6 +416,42 @@ export class CrawlListItem extends LitElement {
`; `;
} }
private renderActions() {
if (!this.hasMenuItems) {
return;
}
return html` <div class="col action">
<sl-icon-button
class="dropdownTrigger"
label=${msg("Actions")}
name="three-dots-vertical"
@click=${(e: MouseEvent) => {
// Prevent anchor link default behavior
e.preventDefault();
// Stop prop to anchor link
e.stopPropagation();
this.dropdownIsOpen = !this.dropdownIsOpen;
}}
@focusout=${(e: FocusEvent) => {
const relatedTarget = e.relatedTarget as HTMLElement;
if (relatedTarget) {
if (this.menuArr[0]?.contains(relatedTarget)) {
// Keep dropdown open if moving to menu selection
return;
}
if (this.row?.isEqualNode(relatedTarget)) {
// Handle with click event
return;
}
}
this.dropdownIsOpen = false;
}}
>
</sl-icon-button>
</div>`;
}
private repositionDropdown() { private repositionDropdown() {
const { x, y } = this.dropdownTrigger.getBoundingClientRect(); const { x, y } = this.dropdownTrigger.getBoundingClientRect();
this.dropdown.style.left = `${x + window.scrollX}px`; this.dropdown.style.left = `${x + window.scrollX}px`;

View File

@ -623,15 +623,19 @@ export class CollectionDetail extends LiteElement {
orgSlug=${this.appState.orgSlug || ""} orgSlug=${this.appState.orgSlug || ""}
.crawl=${item} .crawl=${item}
> >
<sl-menu slot="menu"> ${when(
<sl-menu-item this.isCrawler,
style="--sl-color-neutral-700: var(--warning)" () =>
@click=${() => this.removeArchivedItem(item.id, idx)} html` <sl-menu slot="menu">
> <sl-menu-item
<sl-icon name="folder-minus" slot="prefix"></sl-icon> style="--sl-color-neutral-700: var(--warning)"
${msg("Remove from Collection")} @click=${() => this.removeArchivedItem(item.id, idx)}
</sl-menu-item> >
</sl-menu> <sl-icon name="folder-minus" slot="prefix"></sl-icon>
${msg("Remove from Collection")}
</sl-menu-item>
</sl-menu>`
)}
</btrix-crawl-list-item> </btrix-crawl-list-item>
`; `;

View File

@ -447,28 +447,11 @@ export class CrawlsList extends LiteElement {
orgSlug=${this.appState.orgSlug || ""} orgSlug=${this.appState.orgSlug || ""}
.crawl=${item} .crawl=${item}
> >
<sl-menu slot="menu"> <sl-menu slot="menu"> ${this.crawlerMenuItemsRenderer(item)} </sl-menu>
${when(
this.isCrawler,
this.crawlerMenuItemsRenderer(item),
() => html`
<sl-menu-item
@click=${() =>
this.navTo(
`${this.orgBasePath}/items/${
item.type === "upload" ? "upload" : "crawl"
}/${item.id}`
)}
>
${msg("View Crawl Details")}
</sl-menu-item>
`
)}
</sl-menu>
</btrix-crawl-list-item> </btrix-crawl-list-item>
`; `;
private crawlerMenuItemsRenderer = (item: Crawl) => () => private crawlerMenuItemsRenderer = (item: Crawl) =>
// HACK shoelace doesn't current have a way to override non-hover // HACK shoelace doesn't current have a way to override non-hover
// color without resetting the --sl-color-neutral-700 variable // color without resetting the --sl-color-neutral-700 variable
html` html`

View File

@ -36,6 +36,12 @@ export class Dashboard extends LiteElement {
@property({ type: Object }) @property({ type: Object })
authState!: AuthState; authState!: AuthState;
@property({ type: Boolean })
isCrawler?: boolean;
@property({ type: Boolean })
isAdmin?: boolean;
@property({ type: String }) @property({ type: String })
orgId!: string; orgId!: string;
@ -96,48 +102,55 @@ export class Dashboard extends LiteElement {
<h1 class="min-w-0 text-xl font-semibold leading-8 mr-auto"> <h1 class="min-w-0 text-xl font-semibold leading-8 mr-auto">
${this.org?.name} ${this.org?.name}
</h1> </h1>
<sl-icon-button ${when(
href=${`${this.orgBasePath}/settings`} this.isAdmin,
class="text-lg" () =>
name="gear" html` <sl-icon-button
label="Edit org settings" href=${`${this.orgBasePath}/settings`}
@click=${this.navLink} class="text-lg"
></sl-icon-button> name="gear"
<sl-dropdown label="Edit org settings"
distance="4" @click=${this.navLink}
placement="bottom-end" ></sl-icon-button>`
@sl-select=${(e: SlSelectEvent) => { )}
this.dispatchEvent( ${when(
<SelectNewDialogEvent>new CustomEvent("select-new-dialog", { this.isCrawler,
detail: e.detail.item.value, () => html` <sl-dropdown
}) distance="4"
); placement="bottom-end"
}} @sl-select=${(e: SlSelectEvent) => {
> this.dispatchEvent(
<sl-button slot="trigger" size="small" caret> <SelectNewDialogEvent>new CustomEvent("select-new-dialog", {
<sl-icon slot="prefix" name="plus-lg"></sl-icon> detail: e.detail.item.value,
${msg("Create New...")} })
</sl-button> );
<sl-menu> }}
<sl-menu-item value="workflow" >
>${msg("Crawl Workflow")}</sl-menu-item <sl-button slot="trigger" size="small" caret>
> <sl-icon slot="prefix" name="plus-lg"></sl-icon>
<sl-menu-item ${msg("Create New...")}
value="upload" </sl-button>
?disabled=${!this.metrics || quotaReached} <sl-menu>
>${msg("Upload")}</sl-menu-item <sl-menu-item value="workflow"
> >${msg("Crawl Workflow")}</sl-menu-item
<sl-menu-item value="collection"> >
${msg("Collection")} <sl-menu-item
</sl-menu-item> value="upload"
<sl-menu-item ?disabled=${!this.metrics || quotaReached}
value="browser-profile" >${msg("Upload")}</sl-menu-item
?disabled=${!this.metrics || quotaReached} >
> <sl-menu-item value="collection">
${msg("Browser Profile")} ${msg("Collection")}
</sl-menu-item> </sl-menu-item>
</sl-menu> <sl-menu-item
</sl-dropdown> value="browser-profile"
?disabled=${!this.metrics || quotaReached}
>
${msg("Browser Profile")}
</sl-menu-item>
</sl-menu>
</sl-dropdown>`
)}
</header> </header>
<main> <main>
<div class="flex flex-col md:flex-row gap-6"> <div class="flex flex-col md:flex-row gap-6">

View File

@ -455,6 +455,8 @@ export class Org extends LiteElement {
.authState=${this.authState!} .authState=${this.authState!}
orgId=${this.orgId} orgId=${this.orgId}
.org=${this.org || null} .org=${this.org || null}
?isCrawler=${this.isCrawler}
?isAdmin=${this.isAdmin}
@select-new-dialog=${this.onSelectNewDialog} @select-new-dialog=${this.onSelectNewDialog}
></btrix-dashboard> ></btrix-dashboard>
`; `;

View File

@ -448,7 +448,7 @@ export class WorkflowDetail extends LiteElement {
)} )}
</h3>`; </h3>`;
} }
if (this.activePanel === "settings") { if (this.activePanel === "settings" && this.isCrawler == true) {
return html` <h3>${this.tabLabels[this.activePanel]}</h3> return html` <h3>${this.tabLabels[this.activePanel]}</h3>
<sl-icon-button <sl-icon-button
name="gear" name="gear"
@ -460,7 +460,7 @@ export class WorkflowDetail extends LiteElement {
> >
</sl-icon-button>`; </sl-icon-button>`;
} }
if (this.activePanel === "watch") { if (this.activePanel === "watch" && this.isCrawler == true) {
return html` <h3>${this.tabLabels[this.activePanel]}</h3> return html` <h3>${this.tabLabels[this.activePanel]}</h3>
<sl-button <sl-button
size="small" size="small"
@ -836,21 +836,22 @@ export class WorkflowDetail extends LiteElement {
this.crawls, this.crawls,
() => () =>
this.crawls!.items.map( this.crawls!.items.map(
(crawl: Crawl) => html` (crawl: Crawl) => html` <btrix-crawl-list-item
<btrix-crawl-list-item orgSlug=${this.appState.orgSlug || ""}
orgSlug=${this.appState.orgSlug || ""} .crawl=${crawl}
.crawl=${crawl} >
> <sl-format-date
<sl-format-date slot="id"
slot="id" date=${`${crawl.started}Z`}
date=${`${crawl.started}Z`} month="2-digit"
month="2-digit" day="2-digit"
day="2-digit" year="2-digit"
year="2-digit" hour="2-digit"
hour="2-digit" minute="2-digit"
minute="2-digit" ></sl-format-date>
></sl-format-date> ${when(
<sl-menu slot="menu"> this.isCrawler,
() => html` <sl-menu slot="menu">
<sl-menu-item <sl-menu-item
style="--sl-color-neutral-700: var(--danger)" style="--sl-color-neutral-700: var(--danger)"
@click=${() => this.deleteCrawl(crawl)} @click=${() => this.deleteCrawl(crawl)}
@ -858,9 +859,9 @@ export class WorkflowDetail extends LiteElement {
<sl-icon name="trash3" slot="prefix"></sl-icon> <sl-icon name="trash3" slot="prefix"></sl-icon>
${msg("Delete Crawl")} ${msg("Delete Crawl")}
</sl-menu-item> </sl-menu-item>
</sl-menu> </sl-menu>`
</btrix-crawl-list-item> )}</btrix-crawl-list-item
` >`
), ),
() => html`<div () => html`<div
class="w-full flex items-center justify-center my-24 text-3xl" class="w-full flex items-center justify-center my-24 text-3xl"
@ -1037,24 +1038,26 @@ export class WorkflowDetail extends LiteElement {
> >
` `
)} )}
${when(
<sl-tooltip this.isCrawler,
content=${msg( () => html` <sl-tooltip
"Org Storage Full or Monthly Execution Minutes Reached" content=${msg(
)} "Org Storage Full or Monthly Execution Minutes Reached"
?disabled=${!this.orgStorageQuotaReached && )}
!this.orgExecutionMinutesQuotaReached} ?disabled=${!this.orgStorageQuotaReached &&
> !this.orgExecutionMinutesQuotaReached}
<sl-button
size="small"
?disabled=${this.orgStorageQuotaReached ||
this.orgExecutionMinutesQuotaReached}
@click=${() => this.runNow()}
> >
<sl-icon name="play" slot="prefix"></sl-icon> <sl-button
${msg("Run Crawl")} size="small"
</sl-button> ?disabled=${this.orgStorageQuotaReached ||
</sl-tooltip> this.orgExecutionMinutesQuotaReached}
@click=${() => this.runNow()}
>
<sl-icon name="play" slot="prefix"></sl-icon>
${msg("Run Crawl")}
</sl-button>
</sl-tooltip>`
)}
</div> </div>
</section> </section>
`; `;

View File

@ -421,7 +421,7 @@ export class WorkflowsList extends LiteElement {
private renderMenuItems(workflow: ListWorkflow) { private renderMenuItems(workflow: ListWorkflow) {
return html` return html`
${when( ${when(
workflow.isCrawlRunning, workflow.isCrawlRunning && this.isCrawler,
// HACK shoelace doesn't current have a way to override non-hover // HACK shoelace doesn't current have a way to override non-hover
// color without resetting the --sl-color-neutral-700 variable // color without resetting the --sl-color-neutral-700 variable
() => html` () => html`
@ -439,7 +439,10 @@ export class WorkflowsList extends LiteElement {
<sl-icon name="x-octagon" slot="prefix"></sl-icon> <sl-icon name="x-octagon" slot="prefix"></sl-icon>
${msg("Cancel & Discard Crawl")} ${msg("Cancel & Discard Crawl")}
</sl-menu-item> </sl-menu-item>
`, `
)}
${when(
this.isCrawler && !workflow.isCrawlRunning,
() => html` () => html`
<sl-menu-item <sl-menu-item
style="--sl-color-neutral-700: var(--success)" style="--sl-color-neutral-700: var(--success)"
@ -453,7 +456,7 @@ export class WorkflowsList extends LiteElement {
` `
)} )}
${when( ${when(
workflow.isCrawlRunning, workflow.isCrawlRunning && this.isCrawler,
// HACK shoelace doesn't current have a way to override non-hover // HACK shoelace doesn't current have a way to override non-hover
// color without resetting the --sl-color-neutral-700 variable // color without resetting the --sl-color-neutral-700 variable
() => html` () => html`
@ -485,14 +488,19 @@ export class WorkflowsList extends LiteElement {
<sl-divider></sl-divider> <sl-divider></sl-divider>
` `
)} )}
<sl-divider></sl-divider> ${when(
<sl-menu-item this.isCrawler,
@click=${() => () => html` <sl-divider></sl-divider>
this.navTo(`${this.orgBasePath}/workflows/crawl/${workflow.id}?edit`)} <sl-menu-item
> @click=${() =>
<sl-icon name="gear" slot="prefix"></sl-icon> this.navTo(
${msg("Edit Workflow Settings")} `${this.orgBasePath}/workflows/crawl/${workflow.id}?edit`
</sl-menu-item> )}
>
<sl-icon name="gear" slot="prefix"></sl-icon>
${msg("Edit Workflow Settings")}
</sl-menu-item>`
)}
<sl-menu-item <sl-menu-item
@click=${() => CopyButton.copyToClipboard(workflow.tags.join(", "))} @click=${() => CopyButton.copyToClipboard(workflow.tags.join(", "))}
?disabled=${!workflow.tags.length} ?disabled=${!workflow.tags.length}
@ -500,28 +508,15 @@ export class WorkflowsList extends LiteElement {
<sl-icon name="tags" slot="prefix"></sl-icon> <sl-icon name="tags" slot="prefix"></sl-icon>
${msg("Copy Tags")} ${msg("Copy Tags")}
</sl-menu-item> </sl-menu-item>
<sl-menu-item @click=${() => this.duplicateConfig(workflow)}> ${when(
<sl-icon name="files" slot="prefix"></sl-icon> this.isCrawler,
${msg("Duplicate Workflow")} () => html` <sl-menu-item
</sl-menu-item> @click=${() => this.duplicateConfig(workflow)}
${when(workflow.isCrawlRunning, () => { >
const shouldDeactivate = workflow.crawlCount && !workflow.inactive; <sl-icon name="files" slot="prefix"></sl-icon>
return html` ${msg("Duplicate Workflow")}
<sl-divider></sl-divider> </sl-menu-item>`
<sl-menu-item )}
style="--sl-color-neutral-700: var(--danger)"
@click=${() =>
shouldDeactivate
? this.deactivate(workflow)
: this.delete(workflow)}
>
<sl-icon name="trash3" slot="prefix"></sl-icon>
${shouldDeactivate
? msg("Deactivate Workflow")
: msg("Delete Workflow")}
</sl-menu-item>
`;
})}
`; `;
} }