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:
sua yoo 2023-12-07 11:42:20 -08:00 committed by GitHub
parent be41c48c27
commit 8d6375c654
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 63 additions and 23 deletions

View File

@ -1,14 +1,12 @@
import type {
LitElement,
ReactiveController,
ReactiveControllerHost,
} from "lit";
import type { ReactiveController, ReactiveControllerHost } from "lit";
import { msg } from "@lit/localize";
import type { Auth } from "@/utils/AuthService";
import AuthService from "@/utils/AuthService";
import { APIError } from "@/utils/api";
export type QuotaUpdate = { reached: boolean };
/**
* Utilities for interacting with the Browsertrix backend API
*
@ -52,20 +50,22 @@ export class APIController implements ReactiveController {
if (resp.ok) {
const body = await resp.json();
const storageQuotaReached = body.storageQuotaReached;
const executionMinutesQuotaReached = body.executionMinutesQuotaReached;
const executionMinutesQuotaReached = body.execMinutesQuotaReached;
if (typeof storageQuotaReached === "boolean") {
this.host.dispatchEvent(
new CustomEvent("storage-quota-update", {
new CustomEvent<QuotaUpdate>("storage-quota-update", {
detail: { reached: storageQuotaReached },
bubbles: true,
composed: true,
})
);
}
if (typeof executionMinutesQuotaReached === "boolean") {
this.host.dispatchEvent(
new CustomEvent("execution-minutes-quota-update", {
new CustomEvent<QuotaUpdate>("execution-minutes-quota-update", {
detail: { reached: executionMinutesQuotaReached },
bubbles: true,
composed: true,
})
);
}
@ -89,9 +89,10 @@ export class APIController implements ReactiveController {
case 403: {
if (errorDetail === "storage_quota_reached") {
this.host.dispatchEvent(
new CustomEvent("storage-quota-update", {
new CustomEvent<QuotaUpdate>("storage-quota-update", {
detail: { reached: true },
bubbles: true,
composed: true,
})
);
errorMessage = msg("Storage quota reached");
@ -99,9 +100,10 @@ export class APIController implements ReactiveController {
}
if (errorDetail === "exec_minutes_quota_reached") {
this.host.dispatchEvent(
new CustomEvent("execution-minutes-quota-update", {
new CustomEvent<QuotaUpdate>("execution-minutes-quota-update", {
detail: { reached: true },
bubbles: true,
composed: true,
})
);
errorMessage = msg("Monthly execution minutes quota reached");

View File

@ -33,6 +33,7 @@ import type {
} from "./settings";
import type { Tab as CollectionTab } from "./collection-detail";
import type { SelectJobTypeEvent } from "@/features/crawl-workflows/new-workflow-dialog";
import { type QuotaUpdate } from "@/controllers/api";
const RESOURCE_NAMES = ["workflow", "collection", "browser-profile", "upload"];
type ResourceName = (typeof RESOURCE_NAMES)[number];
@ -58,6 +59,20 @@ type Params = {
settingsTab?: "information" | "members";
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 UUID_REGEX =
@ -134,6 +149,25 @@ export class Org extends LiteElement {
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>) {
if (
(changedProperties.has("userInfo") && this.userInfo) ||
@ -469,7 +503,6 @@ export class Org extends LiteElement {
workflowId=${this.params.workflowId || ""}
itemType=${this.params.itemType || "crawl"}
?isCrawler=${this.isCrawler}
@storage-quota-update=${this.onStorageQuotaUpdate}
></btrix-crawl-detail>`;
}
@ -480,7 +513,6 @@ export class Org extends LiteElement {
?orgStorageQuotaReached=${this.orgStorageQuotaReached}
?isCrawler=${this.isCrawler}
itemType=${ifDefined(this.params.itemType || undefined)}
@storage-quota-update=${this.onStorageQuotaUpdate}
@select-new-dialog=${this.onSelectNewDialog}
></btrix-crawls-list>`;
}
@ -504,8 +536,6 @@ export class Org extends LiteElement {
openDialogName=${this.viewStateData?.dialog}
?isEditing=${isEditing}
?isCrawler=${this.isCrawler}
@storage-quota-update=${this.onStorageQuotaUpdate}
@execution-minutes-quota-update=${this.onExecutionMinutesQuotaUpdate}
></btrix-workflow-detail>
`;
}
@ -523,8 +553,6 @@ export class Org extends LiteElement {
jobType=${ifDefined(this.params.jobType)}
?orgStorageQuotaReached=${this.orgStorageQuotaReached}
?orgExecutionMinutesQuotaReached=${this.orgExecutionMinutesQuotaReached}
@storage-quota-update=${this.onStorageQuotaUpdate}
@execution-minutes-quota-update=${this.onExecutionMinutesQuotaUpdate}
@select-new-dialog=${this.onSelectNewDialog}
></btrix-workflows-new>`;
}
@ -536,8 +564,6 @@ export class Org extends LiteElement {
?orgExecutionMinutesQuotaReached=${this.orgExecutionMinutesQuotaReached}
userId=${this.userInfo!.id}
?isCrawler=${this.isCrawler}
@storage-quota-update=${this.onStorageQuotaUpdate}
@execution-minutes-quota-update=${this.onExecutionMinutesQuotaUpdate}
@select-new-dialog=${this.onSelectNewDialog}
></btrix-workflows-list>`;
}
@ -548,7 +574,6 @@ export class Org extends LiteElement {
.authState=${this.authState!}
.orgId=${this.orgId}
profileId=${this.params.browserProfileId}
@storage-quota-update=${this.onStorageQuotaUpdate}
></btrix-browser-profiles-detail>`;
}
@ -557,14 +582,12 @@ export class Org extends LiteElement {
.authState=${this.authState!}
.orgId=${this.orgId}
.browserId=${this.params.browserId}
@storage-quota-update=${this.onStorageQuotaUpdate}
></btrix-browser-profiles-new>`;
}
return html`<btrix-browser-profiles-list
.authState=${this.authState!}
.orgId=${this.orgId}
@storage-quota-update=${this.onStorageQuotaUpdate}
@select-new-dialog=${this.onSelectNewDialog}
></btrix-browser-profiles-list>`;
}
@ -671,7 +694,7 @@ export class Org extends LiteElement {
this.removeMember(e.detail.member);
}
private async onStorageQuotaUpdate(e: CustomEvent) {
private async onStorageQuotaUpdate(e: CustomEvent<QuotaUpdate>) {
e.stopPropagation();
const { reached } = e.detail;
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();
const { reached } = e.detail;
this.orgExecutionMinutesQuotaReached = reached;
@ -789,4 +812,19 @@ export class Org extends LiteElement {
this.orgExecutionMinutesQuotaReached = !!this.org?.execMinutesQuotaReached;
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);
}
}