From 819ed75c27098ba0357bc8dc58f02238af44d0f8 Mon Sep 17 00:00:00 2001 From: granbellllc-png Date: Wed, 11 Nov 2026 05:20:42 +0400 Subject: [PATCH 1/2] fix(runs): do not apply default 7d period when explicit runIds are supplied (#5110) When explicit runIds are provided to bulk actions or run list queries without an explicit time filter (period, from, or to), convertRunListInputOptionsToFilterRunsOptions previously called timeFilters unconditionally, reintroducing the default 7d time bound. As a result, explicit runIds older than 7 days were silently excluded from bulk actions and list queries. This patch guards the call to timeFilters: when explicit runIds are supplied and no explicit time filter is requested, period remains undefined so all explicitly targeted runs are included. Filter-only queries retain the 7d default. --- .../runsRepository/runsRepository.server.ts | 23 ++-- .../test/runsRepositoryConvert.runIds.test.ts | 115 ++++++++++++++++++ 2 files changed, 131 insertions(+), 7 deletions(-) create mode 100644 apps/webapp/test/runsRepositoryConvert.runIds.test.ts diff --git a/apps/webapp/app/services/runsRepository/runsRepository.server.ts b/apps/webapp/app/services/runsRepository/runsRepository.server.ts index b1a5b79ab4c..032852af6d5 100644 --- a/apps/webapp/app/services/runsRepository/runsRepository.server.ts +++ b/apps/webapp/app/services/runsRepository/runsRepository.server.ts @@ -361,13 +361,22 @@ export async function convertRunListInputOptionsToFilterRunsOptions( period: undefined, }; - // Convert time period to ms - const time = timeFilters({ - period: options.period, - from: options.from, - to: options.to, - }); - convertedOptions.period = time.period ? (parseDuration(time.period) ?? undefined) : undefined; + // Convert time period to ms. + // Explicit run-id selections target specific, already-existing runs, so we + // don't apply a default time bound (which could otherwise exclude a selected run). + const hasExplicitRunIds = Boolean(options.runId && options.runId.length > 0); + const hasExplicitTimeFilter = Boolean( + options.period !== undefined || options.from !== undefined || options.to !== undefined + ); + + if (!hasExplicitRunIds || hasExplicitTimeFilter) { + const time = timeFilters({ + period: options.period, + from: options.from, + to: options.to, + }); + convertedOptions.period = time.period ? (parseDuration(time.period) ?? undefined) : undefined; + } // Cross-DB resolution: BatchTaskRun is a RUN-OPS table. A run-ops batch resident on the // dedicated run-ops DB must resolve via the store's NEW->LEGACY probe — a single control-plane diff --git a/apps/webapp/test/runsRepositoryConvert.runIds.test.ts b/apps/webapp/test/runsRepositoryConvert.runIds.test.ts new file mode 100644 index 00000000000..ca92b40b4aa --- /dev/null +++ b/apps/webapp/test/runsRepositoryConvert.runIds.test.ts @@ -0,0 +1,115 @@ +import { describe, expect, it, vi } from "vitest"; + +// Prevent run-store / db / env singletons from constructing at import +vi.mock("~/db.server", () => ({})); +vi.mock("~/v3/runStore.server", () => ({ runStore: {} })); +vi.mock("~/env.server", () => ({ env: {} })); + +import parseDuration from "parse-duration"; +import { convertRunListInputOptionsToFilterRunsOptions } from "~/services/runsRepository/runsRepository.server"; + +describe("convertRunListInputOptionsToFilterRunsOptions with explicit runIds vs filters (#5110)", () => { + const mockPrisma = {} as any; + const mockStore = {} as any; + + it("leaves period undefined when explicit runIds are supplied without time filters", async () => { + const result = await convertRunListInputOptionsToFilterRunsOptions( + { + organizationId: "org_123", + projectId: "proj_123", + environmentId: "env_123", + runId: ["run_recent", "run_older_than_7_days"], + }, + mockPrisma, + mockStore + ); + + // Period must remain undefined so runs older than 7 days are not silently excluded + expect(result.period).toBeUndefined(); + expect(result.runId).toEqual(["run_recent", "run_older_than_7_days"]); + }); + + it("applies the default 7d period when no explicit runIds are supplied (filter-only)", async () => { + const result = await convertRunListInputOptionsToFilterRunsOptions( + { + organizationId: "org_123", + projectId: "proj_123", + environmentId: "env_123", + statuses: ["COMPLETED_SUCCESSFULLY"], + }, + mockPrisma, + mockStore + ); + + expect(result.period).toBe(parseDuration("7d")); + }); + + it("applies the default 7d period when runId is an empty array", async () => { + const result = await convertRunListInputOptionsToFilterRunsOptions( + { + organizationId: "org_123", + projectId: "proj_123", + environmentId: "env_123", + runId: [], + }, + mockPrisma, + mockStore + ); + + expect(result.period).toBe(parseDuration("7d")); + }); + + it("honors explicit period when supplied alongside runIds", async () => { + const result = await convertRunListInputOptionsToFilterRunsOptions( + { + organizationId: "org_123", + projectId: "proj_123", + environmentId: "env_123", + runId: ["run_1"], + period: "30d", + }, + mockPrisma, + mockStore + ); + + expect(result.period).toBe(parseDuration("30d")); + expect(result.runId).toEqual(["run_1"]); + }); + + it("honors explicit from/to when supplied alongside runIds without defaulting period", async () => { + const from = Date.now() - 30 * 24 * 60 * 60 * 1000; + const to = Date.now(); + const result = await convertRunListInputOptionsToFilterRunsOptions( + { + organizationId: "org_123", + projectId: "proj_123", + environmentId: "env_123", + runId: ["run_1"], + from, + to, + }, + mockPrisma, + mockStore + ); + + expect(result.period).toBeUndefined(); + expect(result.from).toBe(from); + expect(result.to).toBe(to); + expect(result.runId).toEqual(["run_1"]); + }); + + it("honors custom period on filter-only queries", async () => { + const result = await convertRunListInputOptionsToFilterRunsOptions( + { + organizationId: "org_123", + projectId: "proj_123", + environmentId: "env_123", + period: "14d", + }, + mockPrisma, + mockStore + ); + + expect(result.period).toBe(parseDuration("14d")); + }); +}); From 118a3015537911d7d1cd5baf042a7aefb6aa18d8 Mon Sep 17 00:00:00 2001 From: granbellllc-png Date: Wed, 11 Nov 2026 05:21:30 +0400 Subject: [PATCH 2/2] test(webapp): add unit tests for runsRepository explicit runIds conversion (#5110) --- apps/webapp/test/runsRepositoryConvert.runIds.test.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/apps/webapp/test/runsRepositoryConvert.runIds.test.ts b/apps/webapp/test/runsRepositoryConvert.runIds.test.ts index ca92b40b4aa..0b7c28ae07d 100644 --- a/apps/webapp/test/runsRepositoryConvert.runIds.test.ts +++ b/apps/webapp/test/runsRepositoryConvert.runIds.test.ts @@ -5,7 +5,6 @@ vi.mock("~/db.server", () => ({})); vi.mock("~/v3/runStore.server", () => ({ runStore: {} })); vi.mock("~/env.server", () => ({ env: {} })); -import parseDuration from "parse-duration"; import { convertRunListInputOptionsToFilterRunsOptions } from "~/services/runsRepository/runsRepository.server"; describe("convertRunListInputOptionsToFilterRunsOptions with explicit runIds vs filters (#5110)", () => { @@ -24,7 +23,7 @@ describe("convertRunListInputOptionsToFilterRunsOptions with explicit runIds vs mockStore ); - // Period must remain undefined so runs older than 7 days are not silently excluded + // Period must remain undefined so runs older than 7 days are not silently excluded (#5110) expect(result.period).toBeUndefined(); expect(result.runId).toEqual(["run_recent", "run_older_than_7_days"]); }); @@ -41,7 +40,7 @@ describe("convertRunListInputOptionsToFilterRunsOptions with explicit runIds vs mockStore ); - expect(result.period).toBe(parseDuration("7d")); + expect(result.period).toBe(7 * 24 * 60 * 60 * 1000); // 604800000 ms }); it("applies the default 7d period when runId is an empty array", async () => { @@ -56,7 +55,7 @@ describe("convertRunListInputOptionsToFilterRunsOptions with explicit runIds vs mockStore ); - expect(result.period).toBe(parseDuration("7d")); + expect(result.period).toBe(7 * 24 * 60 * 60 * 1000); }); it("honors explicit period when supplied alongside runIds", async () => { @@ -72,7 +71,7 @@ describe("convertRunListInputOptionsToFilterRunsOptions with explicit runIds vs mockStore ); - expect(result.period).toBe(parseDuration("30d")); + expect(result.period).toBe(30 * 24 * 60 * 60 * 1000); expect(result.runId).toEqual(["run_1"]); }); @@ -110,6 +109,6 @@ describe("convertRunListInputOptionsToFilterRunsOptions with explicit runIds vs mockStore ); - expect(result.period).toBe(parseDuration("14d")); + expect(result.period).toBe(14 * 24 * 60 * 60 * 1000); }); });