From 0de84198eda99938bcfc9cdcbbb3204d799415bf Mon Sep 17 00:00:00 2001 From: emma Date: Tue, 1 Apr 2025 18:16:41 -0400 Subject: [PATCH 1/3] allow filtering crawls by running or not running --- backend/btrixcloud/crawlconfigs.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backend/btrixcloud/crawlconfigs.py b/backend/btrixcloud/crawlconfigs.py index 264ec05079..d02d9a4693 100644 --- a/backend/btrixcloud/crawlconfigs.py +++ b/backend/btrixcloud/crawlconfigs.py @@ -543,6 +543,7 @@ async def get_crawl_configs( description: Optional[str] = None, tags: Optional[List[str]] = None, schedule: Optional[bool] = None, + running: Optional[bool] = None, sort_by: str = "lastRun", sort_direction: int = -1, ) -> tuple[list[CrawlConfigOut], int]: @@ -569,6 +570,9 @@ async def get_crawl_configs( if description: match_query["description"] = description + if running is not None: + match_query["isCrawlRunning"] = running + if schedule is not None: if schedule: match_query["schedule"] = {"$nin": ["", None]} @@ -1294,6 +1298,7 @@ async def get_crawl_configs( schedule: Optional[bool] = None, sortBy: str = "", sortDirection: int = -1, + running: Optional[bool] = None, ): # pylint: disable=duplicate-code if firstSeed: @@ -1314,6 +1319,7 @@ async def get_crawl_configs( description=description, tags=tag, schedule=schedule, + running=running, page_size=pageSize, page=page, sort_by=sortBy, From 68820f95c20a6837154238cea383e57ca8bf0230 Mon Sep 17 00:00:00 2001 From: emma Date: Tue, 1 Apr 2025 19:39:14 -0400 Subject: [PATCH 2/3] wip --- .../org/workflows-list-group-controller.ts | 80 ++++ frontend/src/pages/org/workflows-list.ts | 410 +++++++++++++----- 2 files changed, 385 insertions(+), 105 deletions(-) create mode 100644 frontend/src/pages/org/workflows-list-group-controller.ts diff --git a/frontend/src/pages/org/workflows-list-group-controller.ts b/frontend/src/pages/org/workflows-list-group-controller.ts new file mode 100644 index 0000000000..80938932ae --- /dev/null +++ b/frontend/src/pages/org/workflows-list-group-controller.ts @@ -0,0 +1,80 @@ +import { Task } from "@lit/task"; +import { type ReactiveController } from "lit"; + +import { type WorkflowsList } from "@/pages/org/workflows-list"; + +export class ClockController implements ReactiveController { + host: WorkflowsList; + + timeout: number; + private _timerID?: number; + + readonly task; + + constructor(host: WorkflowsList, timeout = 1000, INITIAL_PAGE_SIZE = 10) { + (this.host = host).addController(this); + this.timeout = timeout; + this.task = new Task(this.host, { + task: async ( + [showRunningFirst, filterBy, orderBy, page, filterByCurrentUser], + { signal }, + ) => { + if (!showRunningFirst) { + return; + } + const queryParams = { + ...filterBy, + page: page || 1, + pageSize: this.task.value?.pageSize || INITIAL_PAGE_SIZE, + userid: filterByCurrentUser ? this.host.userInfo?.id : undefined, + sortBy: orderBy.field, + sortDirection: orderBy.direction === "desc" ? -1 : 1, + running: true, + }; + + const query = stringifyQuery(queryParams); + + const workflows = await this.api.fetch>( + `/orgs/${this.orgId}/crawlconfigs?${query}`, + { + signal: signal, + }, + ); + + signal.addEventListener("abort", () => { + clearTimeout(this.runningIntervalId); + this.runningIntervalId = undefined; + }); + + clearTimeout(this.allIntervalId); + + this.runningIntervalId = window.setTimeout(() => { + void this.runningWorkflowsTask.run(); + }, 1000 * POLL_INTERVAL_SECONDS); + + return workflows; + }, + args: () => + [ + this.showRunningFirst, + this.filterBy, + this.orderBy, + this.page[WorkflowGroup.RUNNING], + this.filterByCurrentUser, + ] as const, + }); + } + hostConnected() { + // Start a timer when the host is connected + this._timerID = setInterval(() => { + this.value = new Date(); + // Update the host with new value + this.host.requestUpdate(); + }, this.timeout); + } + hostDisconnected() { + // Clear the timer when the host is disconnected + clearInterval(this._timerID); + this._timerID = undefined; + } +} diff --git a/frontend/src/pages/org/workflows-list.ts b/frontend/src/pages/org/workflows-list.ts index 2892707bcc..930be38b06 100644 --- a/frontend/src/pages/org/workflows-list.ts +++ b/frontend/src/pages/org/workflows-list.ts @@ -1,4 +1,5 @@ import { localized, msg, str } from "@lit/localize"; +import { Task, TaskStatus } from "@lit/task"; import type { SlCheckbox, SlDialog, @@ -26,7 +27,7 @@ import type { SelectJobTypeEvent } from "@/features/crawl-workflows/new-workflow import { pageHeader } from "@/layouts/pageHeader"; import scopeTypeLabels from "@/strings/crawl-workflows/scopeType"; import { deleteConfirmation } from "@/strings/ui"; -import type { APIPaginatedList, APIPaginationQuery } from "@/types/api"; +import type { APIPaginatedList } from "@/types/api"; import { NewWorkflowOnlyScopeType } from "@/types/workflow"; import { isApiError } from "@/utils/api"; import { isArchivingDisabled } from "@/utils/orgs"; @@ -38,12 +39,22 @@ type SortDirection = "asc" | "desc"; const FILTER_BY_CURRENT_USER_STORAGE_KEY = "btrix.filterByCurrentUser.crawlConfigs"; -const INITIAL_PAGE_SIZE = 10; -const POLL_INTERVAL_SECONDS = 10; -const ABORT_REASON_THROTTLE = "throttled"; +const INITIAL_PAGE_SIZE = 1; +const POLL_INTERVAL_SECONDS = 1; +// const ABORT_REASON_THROTTLE = "throttled"; // NOTE Backend pagination max is 1000 const SEEDS_MAX = 1000; +enum WorkflowGroup { + ALL = "all", + RUNNING = "running", +} + +const stringifyQuery = (query: {}) => + queryString.stringify(query, { + arrayFormat: "comma", + }); + const sortableFields: Record< SortField, { label: string; defaultDirection?: SortDirection } @@ -84,17 +95,17 @@ export class WorkflowsList extends BtrixElement { firstSeed: msg("Crawl Start URL"), }; - @state() - private workflows?: APIPaginatedList; + // @state() + // private workflows?: APIPaginatedList; @state() private searchOptions: { [x: string]: string }[] = []; - @state() - private isFetching = false; + // @state() + // private isFetching = false; - @state() - private fetchErrorStatusCode?: number; + // @state() + // private fetchErrorStatusCode?: number; @state() private workflowToDelete?: ListWorkflow; @@ -114,15 +125,145 @@ export class WorkflowsList extends BtrixElement { @state() private filterByCurrentUser = false; + @state() + private showRunningFirst = true; + + @state() + private page = { + [WorkflowGroup.ALL]: 1, + [WorkflowGroup.RUNNING]: 1, + }; + @query("#deleteDialog") private readonly deleteDialog?: SlDialog | null; // For fuzzy search: private readonly searchKeys = ["name", "firstSeed"]; - // Use to cancel requests - private getWorkflowsController: AbortController | null = null; - private timerId?: number; + // // Use to cancel requests + // private getWorkflowsController: AbortController | null = null; + private runningIntervalId?: number; + private allIntervalId?: number; + + private readonly runningWorkflowsTask = new Task(this, { + task: async ( + [showRunningFirst, filterBy, orderBy, page, filterByCurrentUser], + { signal }, + ) => { + if (!showRunningFirst) { + return; + } + const queryParams = { + ...filterBy, + page: page || 1, + pageSize: + this.runningWorkflowsTask.value?.pageSize || INITIAL_PAGE_SIZE, + userid: filterByCurrentUser ? this.userInfo?.id : undefined, + sortBy: orderBy.field, + sortDirection: orderBy.direction === "desc" ? -1 : 1, + running: true, + }; + + const query = stringifyQuery(queryParams); + + const workflows = await this.api.fetch>( + `/orgs/${this.orgId}/crawlconfigs?${query}`, + { + signal: signal, + }, + ); + + signal.addEventListener("abort", () => { + clearTimeout(this.runningIntervalId); + this.runningIntervalId = undefined; + }); + + clearTimeout(this.allIntervalId); + + this.runningIntervalId = window.setTimeout(() => { + void this.runningWorkflowsTask.run(); + }, 1000 * POLL_INTERVAL_SECONDS); + + return workflows; + }, + args: () => + [ + this.showRunningFirst, + this.filterBy, + this.orderBy, + this.page[WorkflowGroup.RUNNING], + this.filterByCurrentUser, + ] as const, + }); + + private readonly allWorkflowsTask = new Task(this, { + task: async ( + [showRunningFirst, filterBy, orderBy, page, filterByCurrentUser], + { signal }, + ) => { + const queryParams = { + ...filterBy, + page: page || 1, + pageSize: this.allWorkflowsTask.value?.pageSize || INITIAL_PAGE_SIZE, + userid: filterByCurrentUser ? this.userInfo?.id : undefined, + sortBy: orderBy.field, + sortDirection: orderBy.direction === "desc" ? -1 : 1, + running: showRunningFirst ? false : undefined, + }; + + const query = stringifyQuery(queryParams); + + const workflows = await this.api.fetch>( + `/orgs/${this.orgId}/crawlconfigs?${query}`, + { + signal: signal, + }, + ); + + // When refreshing, sometimes enough workflows might move from one group to another that the current page might not exist anymore, so we go back enough pages to see content again + console.log({ page, workflows }); + if (page * workflows.pageSize > workflows.total) { + this.page[WorkflowGroup.ALL] = Math.ceil( + workflows.total / workflows.pageSize, + ); + } + + // await new Promise((resolve) => setTimeout(resolve, 500)); + + signal.addEventListener("abort", () => { + clearInterval(this.allIntervalId); + this.allIntervalId = undefined; + }); + + clearTimeout(this.allIntervalId); + + this.allIntervalId ||= window.setInterval(() => { + void this.allWorkflowsTask.run(); + }, 1000 * POLL_INTERVAL_SECONDS); + + return workflows; + }, + args: () => + [ + this.showRunningFirst, + this.filterBy, + this.orderBy, + this.page[WorkflowGroup.ALL], + this.filterByCurrentUser, + ] as const, + }); + + private taskForGroup(group: WorkflowGroup) { + switch (group) { + case WorkflowGroup.ALL: + return this.allWorkflowsTask; + case WorkflowGroup.RUNNING: + return this.runningWorkflowsTask; + default: + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + throw new Error(`Unknown workflow group: ${group}`); + } + } private get selectedSearchFilterKey() { return Object.keys(WorkflowsList.FieldLabels).find((key) => @@ -146,9 +287,10 @@ export class WorkflowsList extends BtrixElement { changedProperties.has("filterByScheduled") || changedProperties.has("filterBy") ) { - void this.fetchWorkflows({ - page: 1, - }); + this.page = { + [WorkflowGroup.ALL]: 1, + [WorkflowGroup.RUNNING]: 1, + }; } if (changedProperties.has("filterByCurrentUser")) { window.sessionStorage.setItem( @@ -162,48 +304,48 @@ export class WorkflowsList extends BtrixElement { void this.fetchConfigSearchValues(); } - disconnectedCallback(): void { - this.cancelInProgressGetWorkflows(); - super.disconnectedCallback(); - } + // disconnectedCallback(): void { + // this.cancelInProgressGetWorkflows(); + // super.disconnectedCallback(); + // } - private async fetchWorkflows(params?: APIPaginationQuery) { - this.fetchErrorStatusCode = undefined; + // private async fetchWorkflows(params?: APIPaginationQuery) { + // this.fetchErrorStatusCode = undefined; - this.cancelInProgressGetWorkflows(); - this.isFetching = true; - try { - const workflows = await this.getWorkflows(params); - this.workflows = workflows; - } catch (e) { - if (isApiError(e)) { - this.fetchErrorStatusCode = e.statusCode; - } else if ((e as Error).name === "AbortError") { - console.debug("Fetch archived items aborted to throttle"); - } else { - this.notify.toast({ - message: msg("Sorry, couldn't retrieve Workflows at this time."), - variant: "danger", - icon: "exclamation-octagon", - id: "workflow-retrieve-error", - }); - } - } - this.isFetching = false; + // this.cancelInProgressGetWorkflows(); + // this.isFetching = true; + // try { + // const workflows = await this.getWorkflows(params); + // this.workflows = workflows; + // } catch (e) { + // if (isApiError(e)) { + // this.fetchErrorStatusCode = e.statusCode; + // } else if ((e as Error).name === "AbortError") { + // console.debug("Fetch archived items aborted to throttle"); + // } else { + // this.notify.toast({ + // message: msg("Sorry, couldn't retrieve Workflows at this time."), + // variant: "danger", + // icon: "exclamation-octagon", + // id: "workflow-retrieve-error", + // }); + // } + // } + // this.isFetching = false; - // Restart timer for next poll - this.timerId = window.setTimeout(() => { - void this.fetchWorkflows(); - }, 1000 * POLL_INTERVAL_SECONDS); - } + // // Restart timer for next poll + // this.timerId = window.setTimeout(() => { + // void this.runningWorkflowsTask.run(); + // }, 1000 * POLL_INTERVAL_SECONDS); + // } - private cancelInProgressGetWorkflows() { - window.clearTimeout(this.timerId); - if (this.getWorkflowsController) { - this.getWorkflowsController.abort(ABORT_REASON_THROTTLE); - this.getWorkflowsController = null; - } - } + // private cancelInProgressGetWorkflows() { + // window.clearTimeout(this.timerId); + // if (this.getWorkflowsController) { + // this.getWorkflowsController.abort(ABORT_REASON_THROTTLE); + // this.getWorkflowsController = null; + // } + // } render() { return html` @@ -311,8 +453,38 @@ export class WorkflowsList extends BtrixElement { + ${when(this.showRunningFirst, () => + when( + this.runningWorkflowsTask.error, + () => html` +
+ + ${msg( + `Something unexpected went wrong while retrieving running Workflows.`, + )} + +
+ `, + () => html` +

+ ${msg("Running Workflows")} + ${when( + this.runningWorkflowsTask.status === TaskStatus.PENDING, + () => html``, + )} +

+
+ ${this.runningWorkflowsTask.value + ? this.runningWorkflowsTask.value.total + ? this.renderWorkflowList(WorkflowGroup.RUNNING) + : this.renderEmptyState(WorkflowGroup.RUNNING) + : this.renderLoading()} +
+ `, + ), + )} ${when( - this.fetchErrorStatusCode, + this.allWorkflowsTask.error, () => html`
@@ -323,11 +495,22 @@ export class WorkflowsList extends BtrixElement {
`, () => html` + ${when( + this.showRunningFirst, + () => + html`

+ ${msg("All Workflows")} + ${when( + this.allWorkflowsTask.status === TaskStatus.PENDING, + () => html``, + )} +

`, + )}
- ${this.workflows - ? this.workflows.total - ? this.renderWorkflowList() - : this.renderEmptyState() + ${this.allWorkflowsTask.value + ? this.allWorkflowsTask.value.total + ? this.renderWorkflowList(WorkflowGroup.ALL) + : this.renderEmptyState(WorkflowGroup.ALL) : this.renderLoading()}
`, @@ -468,6 +651,16 @@ export class WorkflowsList extends BtrixElement { ?checked=${this.filterByCurrentUser} > + `; @@ -501,12 +694,13 @@ export class WorkflowsList extends BtrixElement { `; } - private renderWorkflowList() { - if (!this.workflows) return; - const { page, total, pageSize } = this.workflows; + private renderWorkflowList(group: WorkflowGroup) { + const task = this.taskForGroup(group); + if (!task.value) return; + const { page, total, pageSize } = task.value; return html` - ${this.workflows.items.map(this.renderWorkflowItem)} + ${task.value.items.map(this.renderWorkflowItem)} ${when( total > pageSize, @@ -517,12 +711,13 @@ export class WorkflowsList extends BtrixElement { totalCount=${total} size=${pageSize} @page-change=${async (e: PageChangeEvent) => { - await this.fetchWorkflows({ - page: e.detail.page, - }); + this.page[group] = e.detail.page; + void task.run(); // Scroll to top of list // TODO once deep-linking is implemented, scroll to top of pushstate + await this.updateComplete; + await task.taskComplete; this.scrollIntoView({ behavior: "smooth" }); }} > @@ -689,7 +884,8 @@ export class WorkflowsList extends BtrixElement { ); } - private renderEmptyState() { + private renderEmptyState(group: WorkflowGroup) { + const task = this.taskForGroup(group); if (Object.keys(this.filterBy).length) { return html`
@@ -710,7 +906,7 @@ export class WorkflowsList extends BtrixElement { `; } - if (this.workflows?.page && this.workflows.page > 1) { + if (task.value?.page && task.value.page > 1) { return html`

@@ -720,7 +916,7 @@ export class WorkflowsList extends BtrixElement { `; } - if (this.isFetching) { + if (!task.value && task.status === TaskStatus.PENDING) { return this.renderLoading(); } @@ -739,40 +935,40 @@ export class WorkflowsList extends BtrixElement {

`; } - /** - * Fetch Workflows and update state - **/ - private async getWorkflows( - queryParams?: APIPaginationQuery & Record, - ) { - const query = queryString.stringify( - { - ...this.filterBy, - page: queryParams?.page || this.workflows?.page || 1, - pageSize: - queryParams?.pageSize || - this.workflows?.pageSize || - INITIAL_PAGE_SIZE, - userid: this.filterByCurrentUser ? this.userInfo?.id : undefined, - sortBy: this.orderBy.field, - sortDirection: this.orderBy.direction === "desc" ? -1 : 1, - }, - { - arrayFormat: "comma", - }, - ); + // /** + // * Fetch Workflows and update state + // **/ + // private async getWorkflows( + // queryParams?: APIPaginationQuery & Record, + // ) { + // const query = queryString.stringify( + // { + // ...this.filterBy, + // page: queryParams?.page || this.workflows?.page || 1, + // pageSize: + // queryParams?.pageSize || + // this.workflows?.pageSize || + // INITIAL_PAGE_SIZE, + // userid: this.filterByCurrentUser ? this.userInfo?.id : undefined, + // sortBy: this.orderBy.field, + // sortDirection: this.orderBy.direction === "desc" ? -1 : 1, + // }, + // { + // arrayFormat: "comma", + // }, + // ); - this.getWorkflowsController = new AbortController(); - const data = await this.api.fetch>( - `/orgs/${this.orgId}/crawlconfigs?${query}`, - { - signal: this.getWorkflowsController.signal, - }, - ); - this.getWorkflowsController = null; + // this.getWorkflowsController = new AbortController(); + // const data = await this.api.fetch>( + // `/orgs/${this.orgId}/crawlconfigs?${query}`, + // { + // signal: this.getWorkflowsController.signal, + // }, + // ); + // this.getWorkflowsController = null; - return data; - } + // return data; + // } /** * Create a new template using existing template data @@ -819,7 +1015,8 @@ export class WorkflowsList extends BtrixElement { method: "DELETE", }); - void this.fetchWorkflows(); + void this.allWorkflowsTask.run(); + void this.runningWorkflowsTask.run(); this.notify.toast({ message: msg( html`Deleted ${this.renderName(workflow)} Workflow.`, @@ -848,7 +1045,8 @@ export class WorkflowsList extends BtrixElement { }, ); if (data.success) { - void this.fetchWorkflows(); + void this.allWorkflowsTask.run(); + void this.runningWorkflowsTask.run(); } else { this.notify.toast({ message: msg("Something went wrong, couldn't cancel crawl."), @@ -870,7 +1068,8 @@ export class WorkflowsList extends BtrixElement { }, ); if (data.success) { - void this.fetchWorkflows(); + void this.allWorkflowsTask.run(); + void this.runningWorkflowsTask.run(); } else { this.notify.toast({ message: msg("Something went wrong, couldn't stop crawl."), @@ -907,7 +1106,8 @@ export class WorkflowsList extends BtrixElement { duration: 8000, }); - await this.fetchWorkflows(); + void this.allWorkflowsTask.run(); + void this.runningWorkflowsTask.run(); // Scroll to top of list this.scrollIntoView({ behavior: "smooth" }); } catch (e) { From 9a6060bf84607452dfcc7776d7ffe8719834f07f Mon Sep 17 00:00:00 2001 From: emma Date: Mon, 7 Apr 2025 19:10:27 -0400 Subject: [PATCH 3/3] wip --- .../org/workflows-list-group-controller.ts | 59 ++++++++++--------- frontend/src/pages/org/workflows-list.ts | 4 +- 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/frontend/src/pages/org/workflows-list-group-controller.ts b/frontend/src/pages/org/workflows-list-group-controller.ts index 80938932ae..76fdb8b3a3 100644 --- a/frontend/src/pages/org/workflows-list-group-controller.ts +++ b/frontend/src/pages/org/workflows-list-group-controller.ts @@ -1,19 +1,35 @@ import { Task } from "@lit/task"; import { type ReactiveController } from "lit"; +import queryString from "query-string"; + +import { type Workflow } from "./types"; import { type WorkflowsList } from "@/pages/org/workflows-list"; +import { type APIPaginatedList } from "@/types/api"; +import { type UserInfo } from "@/types/user"; + +const stringifyQuery = (query: {}) => + queryString.stringify(query, { + arrayFormat: "comma", + }); export class ClockController implements ReactiveController { host: WorkflowsList; - timeout: number; - private _timerID?: number; + private readonly POLL_INTERVAL_SECONDS; + private runningIntervalId?: number; + private allIntervalId?: number; readonly task; - constructor(host: WorkflowsList, timeout = 1000, INITIAL_PAGE_SIZE = 10) { + constructor( + host: WorkflowsList, + INITIAL_PAGE_SIZE = 10, + POLL_INTERVAL_SECONDS = 10, + userInfo: () => UserInfo | undefined, + ) { (this.host = host).addController(this); - this.timeout = timeout; + this.POLL_INTERVAL_SECONDS = POLL_INTERVAL_SECONDS; this.task = new Task(this.host, { task: async ( [showRunningFirst, filterBy, orderBy, page, filterByCurrentUser], @@ -26,16 +42,16 @@ export class ClockController implements ReactiveController { ...filterBy, page: page || 1, pageSize: this.task.value?.pageSize || INITIAL_PAGE_SIZE, - userid: filterByCurrentUser ? this.host.userInfo?.id : undefined, + userid: filterByCurrentUser ? userInfo()?.id : undefined, sortBy: orderBy.field, sortDirection: orderBy.direction === "desc" ? -1 : 1, running: true, - }; + } as const; const query = stringifyQuery(queryParams); - const workflows = await this.api.fetch>( - `/orgs/${this.orgId}/crawlconfigs?${query}`, + const workflows = await this.host.api.fetch>( + `/orgs/${this.host.orgId}/crawlconfigs?${query}`, { signal: signal, }, @@ -49,32 +65,21 @@ export class ClockController implements ReactiveController { clearTimeout(this.allIntervalId); this.runningIntervalId = window.setTimeout(() => { - void this.runningWorkflowsTask.run(); + void this.task.run(); }, 1000 * POLL_INTERVAL_SECONDS); return workflows; }, args: () => [ - this.showRunningFirst, - this.filterBy, - this.orderBy, - this.page[WorkflowGroup.RUNNING], - this.filterByCurrentUser, + this.host.showRunningFirst, + this.host.filterBy, + this.host.orderBy, + this.host.page[WorkflowGroup.RUNNING], + this.host.filterByCurrentUser, ] as const, }); } - hostConnected() { - // Start a timer when the host is connected - this._timerID = setInterval(() => { - this.value = new Date(); - // Update the host with new value - this.host.requestUpdate(); - }, this.timeout); - } - hostDisconnected() { - // Clear the timer when the host is disconnected - clearInterval(this._timerID); - this._timerID = undefined; - } + hostConnected() {} + hostDisconnected() {} } diff --git a/frontend/src/pages/org/workflows-list.ts b/frontend/src/pages/org/workflows-list.ts index 930be38b06..e9a6e92ba9 100644 --- a/frontend/src/pages/org/workflows-list.ts +++ b/frontend/src/pages/org/workflows-list.ts @@ -39,8 +39,8 @@ type SortDirection = "asc" | "desc"; const FILTER_BY_CURRENT_USER_STORAGE_KEY = "btrix.filterByCurrentUser.crawlConfigs"; -const INITIAL_PAGE_SIZE = 1; -const POLL_INTERVAL_SECONDS = 1; +const INITIAL_PAGE_SIZE = 10; +const POLL_INTERVAL_SECONDS = 10; // const ABORT_REASON_THROTTLE = "throttled"; // NOTE Backend pagination max is 1000 const SEEDS_MAX = 1000;