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,7 +623,10 @@ export class CollectionDetail extends LiteElement {
orgSlug=${this.appState.orgSlug || ""} orgSlug=${this.appState.orgSlug || ""}
.crawl=${item} .crawl=${item}
> >
<sl-menu slot="menu"> ${when(
this.isCrawler,
() =>
html` <sl-menu slot="menu">
<sl-menu-item <sl-menu-item
style="--sl-color-neutral-700: var(--warning)" style="--sl-color-neutral-700: var(--warning)"
@click=${() => this.removeArchivedItem(item.id, idx)} @click=${() => this.removeArchivedItem(item.id, idx)}
@ -631,7 +634,8 @@ export class CollectionDetail extends LiteElement {
<sl-icon name="folder-minus" slot="prefix"></sl-icon> <sl-icon name="folder-minus" slot="prefix"></sl-icon>
${msg("Remove from Collection")} ${msg("Remove from Collection")}
</sl-menu-item> </sl-menu-item>
</sl-menu> </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,14 +102,20 @@ 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(
this.isAdmin,
() =>
html` <sl-icon-button
href=${`${this.orgBasePath}/settings`} href=${`${this.orgBasePath}/settings`}
class="text-lg" class="text-lg"
name="gear" name="gear"
label="Edit org settings" label="Edit org settings"
@click=${this.navLink} @click=${this.navLink}
></sl-icon-button> ></sl-icon-button>`
<sl-dropdown )}
${when(
this.isCrawler,
() => html` <sl-dropdown
distance="4" distance="4"
placement="bottom-end" placement="bottom-end"
@sl-select=${(e: SlSelectEvent) => { @sl-select=${(e: SlSelectEvent) => {
@ -137,7 +149,8 @@ export class Dashboard extends LiteElement {
${msg("Browser Profile")} ${msg("Browser Profile")}
</sl-menu-item> </sl-menu-item>
</sl-menu> </sl-menu>
</sl-dropdown> </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,8 +836,7 @@ 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}
> >
@ -850,7 +849,9 @@ export class WorkflowDetail extends LiteElement {
hour="2-digit" hour="2-digit"
minute="2-digit" minute="2-digit"
></sl-format-date> ></sl-format-date>
<sl-menu slot="menu"> ${when(
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,8 +1038,9 @@ export class WorkflowDetail extends LiteElement {
> >
` `
)} )}
${when(
<sl-tooltip this.isCrawler,
() => html` <sl-tooltip
content=${msg( content=${msg(
"Org Storage Full or Monthly Execution Minutes Reached" "Org Storage Full or Monthly Execution Minutes Reached"
)} )}
@ -1054,7 +1056,8 @@ export class WorkflowDetail extends LiteElement {
<sl-icon name="play" slot="prefix"></sl-icon> <sl-icon name="play" slot="prefix"></sl-icon>
${msg("Run Crawl")} ${msg("Run Crawl")}
</sl-button> </sl-button>
</sl-tooltip> </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(
this.isCrawler,
() => html` <sl-divider></sl-divider>
<sl-menu-item <sl-menu-item
@click=${() => @click=${() =>
this.navTo(`${this.orgBasePath}/workflows/crawl/${workflow.id}?edit`)} this.navTo(
`${this.orgBasePath}/workflows/crawl/${workflow.id}?edit`
)}
> >
<sl-icon name="gear" slot="prefix"></sl-icon> <sl-icon name="gear" slot="prefix"></sl-icon>
${msg("Edit Workflow Settings")} ${msg("Edit Workflow Settings")}
</sl-menu-item> </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(
this.isCrawler,
() => html` <sl-menu-item
@click=${() => this.duplicateConfig(workflow)}
>
<sl-icon name="files" slot="prefix"></sl-icon> <sl-icon name="files" slot="prefix"></sl-icon>
${msg("Duplicate Workflow")} ${msg("Duplicate Workflow")}
</sl-menu-item> </sl-menu-item>`
${when(workflow.isCrawlRunning, () => { )}
const shouldDeactivate = workflow.crawlCount && !workflow.inactive;
return html`
<sl-divider></sl-divider>
<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>
`;
})}
`; `;
} }