Fix UI not updating after quotas reached status changes (#1425)
Fixes #1426 - Update expected org field for execMinutesQuotaReached - Move event handlers from child components to org index connectedCallback - Add composed to events - Improve typing Co-authored-by: Tessa Walsh <tessa@bitarchivist.net> Co-authored-by: Emma Segal-Grossman <hi@emma.cafe>
This commit is contained in:
parent
be41c48c27
commit
8d6375c654
@ -1,14 +1,12 @@
|
|||||||
import type {
|
import type { ReactiveController, ReactiveControllerHost } from "lit";
|
||||||
LitElement,
|
|
||||||
ReactiveController,
|
|
||||||
ReactiveControllerHost,
|
|
||||||
} from "lit";
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
|
|
||||||
import type { Auth } from "@/utils/AuthService";
|
import type { Auth } from "@/utils/AuthService";
|
||||||
import AuthService from "@/utils/AuthService";
|
import AuthService from "@/utils/AuthService";
|
||||||
import { APIError } from "@/utils/api";
|
import { APIError } from "@/utils/api";
|
||||||
|
|
||||||
|
export type QuotaUpdate = { reached: boolean };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utilities for interacting with the Browsertrix backend API
|
* Utilities for interacting with the Browsertrix backend API
|
||||||
*
|
*
|
||||||
@ -52,20 +50,22 @@ export class APIController implements ReactiveController {
|
|||||||
if (resp.ok) {
|
if (resp.ok) {
|
||||||
const body = await resp.json();
|
const body = await resp.json();
|
||||||
const storageQuotaReached = body.storageQuotaReached;
|
const storageQuotaReached = body.storageQuotaReached;
|
||||||
const executionMinutesQuotaReached = body.executionMinutesQuotaReached;
|
const executionMinutesQuotaReached = body.execMinutesQuotaReached;
|
||||||
if (typeof storageQuotaReached === "boolean") {
|
if (typeof storageQuotaReached === "boolean") {
|
||||||
this.host.dispatchEvent(
|
this.host.dispatchEvent(
|
||||||
new CustomEvent("storage-quota-update", {
|
new CustomEvent<QuotaUpdate>("storage-quota-update", {
|
||||||
detail: { reached: storageQuotaReached },
|
detail: { reached: storageQuotaReached },
|
||||||
bubbles: true,
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (typeof executionMinutesQuotaReached === "boolean") {
|
if (typeof executionMinutesQuotaReached === "boolean") {
|
||||||
this.host.dispatchEvent(
|
this.host.dispatchEvent(
|
||||||
new CustomEvent("execution-minutes-quota-update", {
|
new CustomEvent<QuotaUpdate>("execution-minutes-quota-update", {
|
||||||
detail: { reached: executionMinutesQuotaReached },
|
detail: { reached: executionMinutesQuotaReached },
|
||||||
bubbles: true,
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -89,9 +89,10 @@ export class APIController implements ReactiveController {
|
|||||||
case 403: {
|
case 403: {
|
||||||
if (errorDetail === "storage_quota_reached") {
|
if (errorDetail === "storage_quota_reached") {
|
||||||
this.host.dispatchEvent(
|
this.host.dispatchEvent(
|
||||||
new CustomEvent("storage-quota-update", {
|
new CustomEvent<QuotaUpdate>("storage-quota-update", {
|
||||||
detail: { reached: true },
|
detail: { reached: true },
|
||||||
bubbles: true,
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
errorMessage = msg("Storage quota reached");
|
errorMessage = msg("Storage quota reached");
|
||||||
@ -99,9 +100,10 @@ export class APIController implements ReactiveController {
|
|||||||
}
|
}
|
||||||
if (errorDetail === "exec_minutes_quota_reached") {
|
if (errorDetail === "exec_minutes_quota_reached") {
|
||||||
this.host.dispatchEvent(
|
this.host.dispatchEvent(
|
||||||
new CustomEvent("execution-minutes-quota-update", {
|
new CustomEvent<QuotaUpdate>("execution-minutes-quota-update", {
|
||||||
detail: { reached: true },
|
detail: { reached: true },
|
||||||
bubbles: true,
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
errorMessage = msg("Monthly execution minutes quota reached");
|
errorMessage = msg("Monthly execution minutes quota reached");
|
||||||
|
@ -33,6 +33,7 @@ import type {
|
|||||||
} from "./settings";
|
} from "./settings";
|
||||||
import type { Tab as CollectionTab } from "./collection-detail";
|
import type { Tab as CollectionTab } from "./collection-detail";
|
||||||
import type { SelectJobTypeEvent } from "@/features/crawl-workflows/new-workflow-dialog";
|
import type { SelectJobTypeEvent } from "@/features/crawl-workflows/new-workflow-dialog";
|
||||||
|
import { type QuotaUpdate } from "@/controllers/api";
|
||||||
|
|
||||||
const RESOURCE_NAMES = ["workflow", "collection", "browser-profile", "upload"];
|
const RESOURCE_NAMES = ["workflow", "collection", "browser-profile", "upload"];
|
||||||
type ResourceName = (typeof RESOURCE_NAMES)[number];
|
type ResourceName = (typeof RESOURCE_NAMES)[number];
|
||||||
@ -58,6 +59,20 @@ type Params = {
|
|||||||
settingsTab?: "information" | "members";
|
settingsTab?: "information" | "members";
|
||||||
new?: ResourceName;
|
new?: ResourceName;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type OrgEventMap = {
|
||||||
|
"execution-minutes-quota-update": QuotaUpdate;
|
||||||
|
"storage-quota-update": QuotaUpdate;
|
||||||
|
};
|
||||||
|
|
||||||
|
type OrgEventListener<T extends string> = T extends keyof OrgEventMap
|
||||||
|
? (this: Org, ev: CustomEvent<OrgEventMap[T]>) => unknown
|
||||||
|
: EventListenerOrEventListenerObject;
|
||||||
|
|
||||||
|
// `string & {}` is resolved to `string`, but not by intellisense, so this gives us string suggestions in vscode from OrgEventMap but still allows arbitrary strings
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
|
type EventType = keyof OrgEventMap | (string & {});
|
||||||
|
|
||||||
const defaultTab = "home";
|
const defaultTab = "home";
|
||||||
|
|
||||||
const UUID_REGEX =
|
const UUID_REGEX =
|
||||||
@ -134,6 +149,25 @@ export class Org extends LiteElement {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
this.addEventListener(
|
||||||
|
"execution-minutes-quota-update",
|
||||||
|
this.onExecutionMinutesQuotaUpdate
|
||||||
|
);
|
||||||
|
this.addEventListener("storage-quota-update", this.onStorageQuotaUpdate);
|
||||||
|
this.addEventListener("", () => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
this.removeEventListener(
|
||||||
|
"execution-minutes-quota-update",
|
||||||
|
this.onExecutionMinutesQuotaUpdate
|
||||||
|
);
|
||||||
|
this.removeEventListener("storage-quota-update", this.onStorageQuotaUpdate);
|
||||||
|
this.disconnectedCallback();
|
||||||
|
}
|
||||||
|
|
||||||
async willUpdate(changedProperties: Map<string, any>) {
|
async willUpdate(changedProperties: Map<string, any>) {
|
||||||
if (
|
if (
|
||||||
(changedProperties.has("userInfo") && this.userInfo) ||
|
(changedProperties.has("userInfo") && this.userInfo) ||
|
||||||
@ -469,7 +503,6 @@ export class Org extends LiteElement {
|
|||||||
workflowId=${this.params.workflowId || ""}
|
workflowId=${this.params.workflowId || ""}
|
||||||
itemType=${this.params.itemType || "crawl"}
|
itemType=${this.params.itemType || "crawl"}
|
||||||
?isCrawler=${this.isCrawler}
|
?isCrawler=${this.isCrawler}
|
||||||
@storage-quota-update=${this.onStorageQuotaUpdate}
|
|
||||||
></btrix-crawl-detail>`;
|
></btrix-crawl-detail>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -480,7 +513,6 @@ export class Org extends LiteElement {
|
|||||||
?orgStorageQuotaReached=${this.orgStorageQuotaReached}
|
?orgStorageQuotaReached=${this.orgStorageQuotaReached}
|
||||||
?isCrawler=${this.isCrawler}
|
?isCrawler=${this.isCrawler}
|
||||||
itemType=${ifDefined(this.params.itemType || undefined)}
|
itemType=${ifDefined(this.params.itemType || undefined)}
|
||||||
@storage-quota-update=${this.onStorageQuotaUpdate}
|
|
||||||
@select-new-dialog=${this.onSelectNewDialog}
|
@select-new-dialog=${this.onSelectNewDialog}
|
||||||
></btrix-crawls-list>`;
|
></btrix-crawls-list>`;
|
||||||
}
|
}
|
||||||
@ -504,8 +536,6 @@ export class Org extends LiteElement {
|
|||||||
openDialogName=${this.viewStateData?.dialog}
|
openDialogName=${this.viewStateData?.dialog}
|
||||||
?isEditing=${isEditing}
|
?isEditing=${isEditing}
|
||||||
?isCrawler=${this.isCrawler}
|
?isCrawler=${this.isCrawler}
|
||||||
@storage-quota-update=${this.onStorageQuotaUpdate}
|
|
||||||
@execution-minutes-quota-update=${this.onExecutionMinutesQuotaUpdate}
|
|
||||||
></btrix-workflow-detail>
|
></btrix-workflow-detail>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -523,8 +553,6 @@ export class Org extends LiteElement {
|
|||||||
jobType=${ifDefined(this.params.jobType)}
|
jobType=${ifDefined(this.params.jobType)}
|
||||||
?orgStorageQuotaReached=${this.orgStorageQuotaReached}
|
?orgStorageQuotaReached=${this.orgStorageQuotaReached}
|
||||||
?orgExecutionMinutesQuotaReached=${this.orgExecutionMinutesQuotaReached}
|
?orgExecutionMinutesQuotaReached=${this.orgExecutionMinutesQuotaReached}
|
||||||
@storage-quota-update=${this.onStorageQuotaUpdate}
|
|
||||||
@execution-minutes-quota-update=${this.onExecutionMinutesQuotaUpdate}
|
|
||||||
@select-new-dialog=${this.onSelectNewDialog}
|
@select-new-dialog=${this.onSelectNewDialog}
|
||||||
></btrix-workflows-new>`;
|
></btrix-workflows-new>`;
|
||||||
}
|
}
|
||||||
@ -536,8 +564,6 @@ export class Org extends LiteElement {
|
|||||||
?orgExecutionMinutesQuotaReached=${this.orgExecutionMinutesQuotaReached}
|
?orgExecutionMinutesQuotaReached=${this.orgExecutionMinutesQuotaReached}
|
||||||
userId=${this.userInfo!.id}
|
userId=${this.userInfo!.id}
|
||||||
?isCrawler=${this.isCrawler}
|
?isCrawler=${this.isCrawler}
|
||||||
@storage-quota-update=${this.onStorageQuotaUpdate}
|
|
||||||
@execution-minutes-quota-update=${this.onExecutionMinutesQuotaUpdate}
|
|
||||||
@select-new-dialog=${this.onSelectNewDialog}
|
@select-new-dialog=${this.onSelectNewDialog}
|
||||||
></btrix-workflows-list>`;
|
></btrix-workflows-list>`;
|
||||||
}
|
}
|
||||||
@ -548,7 +574,6 @@ export class Org extends LiteElement {
|
|||||||
.authState=${this.authState!}
|
.authState=${this.authState!}
|
||||||
.orgId=${this.orgId}
|
.orgId=${this.orgId}
|
||||||
profileId=${this.params.browserProfileId}
|
profileId=${this.params.browserProfileId}
|
||||||
@storage-quota-update=${this.onStorageQuotaUpdate}
|
|
||||||
></btrix-browser-profiles-detail>`;
|
></btrix-browser-profiles-detail>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -557,14 +582,12 @@ export class Org extends LiteElement {
|
|||||||
.authState=${this.authState!}
|
.authState=${this.authState!}
|
||||||
.orgId=${this.orgId}
|
.orgId=${this.orgId}
|
||||||
.browserId=${this.params.browserId}
|
.browserId=${this.params.browserId}
|
||||||
@storage-quota-update=${this.onStorageQuotaUpdate}
|
|
||||||
></btrix-browser-profiles-new>`;
|
></btrix-browser-profiles-new>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return html`<btrix-browser-profiles-list
|
return html`<btrix-browser-profiles-list
|
||||||
.authState=${this.authState!}
|
.authState=${this.authState!}
|
||||||
.orgId=${this.orgId}
|
.orgId=${this.orgId}
|
||||||
@storage-quota-update=${this.onStorageQuotaUpdate}
|
|
||||||
@select-new-dialog=${this.onSelectNewDialog}
|
@select-new-dialog=${this.onSelectNewDialog}
|
||||||
></btrix-browser-profiles-list>`;
|
></btrix-browser-profiles-list>`;
|
||||||
}
|
}
|
||||||
@ -671,7 +694,7 @@ export class Org extends LiteElement {
|
|||||||
this.removeMember(e.detail.member);
|
this.removeMember(e.detail.member);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async onStorageQuotaUpdate(e: CustomEvent) {
|
private async onStorageQuotaUpdate(e: CustomEvent<QuotaUpdate>) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const { reached } = e.detail;
|
const { reached } = e.detail;
|
||||||
this.orgStorageQuotaReached = reached;
|
this.orgStorageQuotaReached = reached;
|
||||||
@ -680,7 +703,7 @@ export class Org extends LiteElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async onExecutionMinutesQuotaUpdate(e: CustomEvent) {
|
private async onExecutionMinutesQuotaUpdate(e: CustomEvent<QuotaUpdate>) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const { reached } = e.detail;
|
const { reached } = e.detail;
|
||||||
this.orgExecutionMinutesQuotaReached = reached;
|
this.orgExecutionMinutesQuotaReached = reached;
|
||||||
@ -789,4 +812,19 @@ export class Org extends LiteElement {
|
|||||||
this.orgExecutionMinutesQuotaReached = !!this.org?.execMinutesQuotaReached;
|
this.orgExecutionMinutesQuotaReached = !!this.org?.execMinutesQuotaReached;
|
||||||
this.showExecutionMinutesQuotaAlert = this.orgExecutionMinutesQuotaReached;
|
this.showExecutionMinutesQuotaAlert = this.orgExecutionMinutesQuotaReached;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addEventListener<T extends EventType>(
|
||||||
|
type: T,
|
||||||
|
listener: OrgEventListener<T>,
|
||||||
|
options?: boolean | AddEventListenerOptions
|
||||||
|
): void {
|
||||||
|
super.addEventListener(type, listener as EventListener, options);
|
||||||
|
}
|
||||||
|
removeEventListener<T extends EventType>(
|
||||||
|
type: T,
|
||||||
|
listener: OrgEventListener<T>,
|
||||||
|
options?: boolean | AddEventListenerOptions
|
||||||
|
): void {
|
||||||
|
super.removeEventListener(type, listener as EventListener, options);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user