From d762a78bde28d55c42b800b8726f3a608c7944e0 Mon Sep 17 00:00:00 2001 From: guerler Date: Mon, 10 Nov 2025 20:27:37 +0300 Subject: [PATCH 01/13] Allow selection of genomes from history fasta/2bit --- client/visualizations.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/visualizations.yml b/client/visualizations.yml index ea51399c8be..605de8c4a1a 100644 --- a/client/visualizations.yml +++ b/client/visualizations.yml @@ -44,7 +44,7 @@ hyphyvision: version: 0.0.0 igv: package: "@galaxyproject/igv" - version: 0.0.16 + version: 0.0.23 jupyterlite: package: "@galaxyproject/jupyterlite" version: 0.0.17 From f3a3de560d699ae9a4dc9f8cc05133c7d8d54e1c Mon Sep 17 00:00:00 2001 From: Ahmed Awan Date: Wed, 12 Nov 2025 20:51:17 -0600 Subject: [PATCH 02/13] [25.1] Add a client side rate limiter Adds a rate limiter middleware to the `openapi-fetch` client side API factory. For now, the maximum number of requests are defined in this `rateLimiter` utility file (and so is the window duration, retry delay for 429s and max retries). Maybe this could be defined in a config file? Also, we do not handle max retries for 429 responses (Retry-After) for now. --- client/src/api/client/index.ts | 15 ++++- client/src/api/client/rateLimiter.ts | 82 ++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 client/src/api/client/rateLimiter.ts diff --git a/client/src/api/client/index.ts b/client/src/api/client/index.ts index b3d53c1106b..01cd2a1a213 100644 --- a/client/src/api/client/index.ts +++ b/client/src/api/client/index.ts @@ -1,5 +1,6 @@ import createClient from "openapi-fetch"; +import { createRateLimiterMiddleware } from "@/api/client/rateLimiter"; import type { GalaxyApiPaths } from "@/api/schema"; import { getAppRoot } from "@/onload/loadConfig"; @@ -9,7 +10,19 @@ function getBaseUrl() { } function apiClientFactory() { - return createClient({ baseUrl: getBaseUrl() }); + const client = createClient({ baseUrl: getBaseUrl() }); + + // TODO: Adjust based on server limits (maybe this goes in Galaxy config?) + client.use( + createRateLimiterMiddleware({ + maxRequests: 100, + windowMs: 3000, + retryDelay: 1000, + maxRetries: 3, + }), + ); + + return client; } export type GalaxyApiClient = ReturnType; diff --git a/client/src/api/client/rateLimiter.ts b/client/src/api/client/rateLimiter.ts new file mode 100644 index 00000000000..5ead5873fb6 --- /dev/null +++ b/client/src/api/client/rateLimiter.ts @@ -0,0 +1,82 @@ +import type { Middleware } from "openapi-fetch"; + +interface RateLimitConfig { + /** Maximum requests per window */ + maxRequests?: number; + /** Time the requestwindow lasts in milliseconds */ + windowMs?: number; + /** Delay between retries on 429 */ + retryDelay?: number; + /** Maximum retry attempts */ + maxRetries?: number; +} + +const DEFAULT_CONFIG: Required = { + maxRequests: 100, + windowMs: 60000, + retryDelay: 1000, + maxRetries: 3, +}; + +/** + * Rate limiting middleware to control the rate of API requests. + * + * Uses a timed window and only allows a maximum number of requests per that window. + */ +export function createRateLimiterMiddleware(config: RateLimitConfig = {}): Middleware { + const cfg = { ...DEFAULT_CONFIG, ...config }; + + let requestCount = 0; + let windowStart = Date.now(); + + /** Resets the request window if the time has elapsed */ + function resetWindowIfNeeded() { + const now = Date.now(); + if (now - windowStart >= cfg.windowMs) { + requestCount = 0; + windowStart = now; + } + } + + /** Places a request in the rate limiter queue */ + async function placeRequestInQueue(): Promise { + resetWindowIfNeeded(); + + if (requestCount < cfg.maxRequests) { + requestCount++; + return; + } + + // Rate limit exceeded, wait for next window + const waitTime = cfg.windowMs - (Date.now() - windowStart); + + await new Promise((resolve) => setTimeout(resolve, waitTime)); + + // After waiting, try again (this will reset the window) + return placeRequestInQueue(); + } + + const middleware: Middleware = { + async onRequest({ request }) { + await placeRequestInQueue(); + return request; + }, + + async onResponse({ response: res }) { + // TODO: Implement max retries logic + + // Handle 429 Too Many Requests from server + if (res.status === 429) { + const retryAfter = res.headers.get("Retry-After"); + const delay = retryAfter ? parseInt(retryAfter) * 1000 : cfg.retryDelay; + + console.warn(`Received 429 from server, waiting ${delay}ms before retry`); + await new Promise((resolve) => setTimeout(resolve, delay)); + } + + return res; + }, + }; + + return middleware; +} From 5d91b557320fda2b029124cc2fd4d2f469e29278 Mon Sep 17 00:00:00 2001 From: Ahmed Awan Date: Tue, 18 Nov 2025 10:55:53 -0600 Subject: [PATCH 03/13] [25.1] Set `create_time` as the default time for sorting/display for invocations As opposed to `update_time`. --- .../Workflow/Invocation/InvocationScrollList.vue | 5 ++++- .../src/components/Workflow/WorkflowAnnotation.test.ts | 4 ++-- client/src/components/Workflow/WorkflowAnnotation.vue | 10 +++++----- .../WorkflowInvocationState.vue | 2 +- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/client/src/components/Workflow/Invocation/InvocationScrollList.vue b/client/src/components/Workflow/Invocation/InvocationScrollList.vue index ee65b3e0266..7e9290cb317 100644 --- a/client/src/components/Workflow/Invocation/InvocationScrollList.vue +++ b/client/src/components/Workflow/Invocation/InvocationScrollList.vue @@ -1,4 +1,5 @@