@@ -256,6 +281,14 @@ function RunsList({
rootOnlyDefault={rootOnlyDefault}
/>
+ {isLiveAvailable && (
+
+ )}
{!isShowingBulkActionInspector && (
{
+ const userId = await requireUserId(request);
+ const { projectParam, organizationSlug, envParam } = EnvironmentParamSchema.parse(params);
+
+ const project = await findProjectBySlug(organizationSlug, projectParam, userId);
+ if (!project) {
+ throw new Response("Project not found", { status: 404 });
+ }
+
+ const environment = await findEnvironmentBySlug(project.id, envParam, userId);
+ if (!environment) {
+ throw new Response("Environment not found", { status: 404 });
+ }
+
+ const url = new URL(request.url);
+ const sinceParam = url.searchParams.get("since");
+ const since = sinceParam && RunIdSchema.safeParse(sinceParam).success ? sinceParam : null;
+
+ if (!since) {
+ return typedjson(
+ { count: 0, hasMore: false },
+ { headers: { "Cache-Control": "no-store" } }
+ );
+ }
+
+ const filters = await getRunFiltersFromRequest(request);
+
+ const runsRepository = new RunsRepository({
+ clickhouse: clickhouseClient,
+ prisma: $replica as PrismaClient,
+ });
+
+ const ids = await runsRepository.listRunIds({
+ organizationId: project.organizationId,
+ projectId: project.id,
+ environmentId: environment.id,
+ tasks: filters.tasks,
+ versions: filters.versions,
+ statuses: filters.statuses,
+ tags: filters.tags,
+ period: filters.period,
+ from: filters.from,
+ to: filters.to,
+ batchId: filters.batchId,
+ runId: filters.runId,
+ bulkId: filters.bulkId,
+ scheduleId: filters.scheduleId,
+ rootOnly: filters.rootOnly,
+ queues: filters.queues,
+ machines: filters.machines,
+ errorId: filters.errorId,
+ page: { cursor: since, direction: "backward", size: COUNT_CAP },
+ });
+
+ return typedjson(
+ {
+ count: Math.min(ids.length, COUNT_CAP),
+ hasMore: ids.length > COUNT_CAP,
+ },
+ { headers: { "Cache-Control": "no-store" } }
+ );
+};
diff --git a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.refresh.ts b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.refresh.ts
new file mode 100644
index 00000000000..3b5c77a9fe2
--- /dev/null
+++ b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.refresh.ts
@@ -0,0 +1,52 @@
+import { type LoaderFunctionArgs } from "@remix-run/server-runtime";
+import { typedjson } from "remix-typedjson";
+import { z } from "zod";
+import { $replica } from "~/db.server";
+import { findProjectBySlug } from "~/models/project.server";
+import { findEnvironmentBySlug } from "~/models/runtimeEnvironment.server";
+import { NextRunListPresenter } from "~/presenters/v3/NextRunListPresenter.server";
+import { clickhouseClient } from "~/services/clickhouseInstance.server";
+import { requireUserId } from "~/services/session.server";
+import { EnvironmentParamSchema } from "~/utils/pathBuilder";
+
+const MAX_IDS = 100;
+const RunIdSchema = z.string().cuid();
+
+export const loader = async ({ request, params }: LoaderFunctionArgs) => {
+ const userId = await requireUserId(request);
+ const { projectParam, organizationSlug, envParam } = EnvironmentParamSchema.parse(params);
+
+ const project = await findProjectBySlug(organizationSlug, projectParam, userId);
+ if (!project) {
+ throw new Response("Project not found", { status: 404 });
+ }
+
+ const environment = await findEnvironmentBySlug(project.id, envParam, userId);
+ if (!environment) {
+ throw new Response("Environment not found", { status: 404 });
+ }
+
+ const url = new URL(request.url);
+ const idsParam = url.searchParams.get("ids") ?? "";
+ const candidateIds = idsParam
+ .split(",")
+ .map((id) => id.trim())
+ .filter((id) => id.length > 0)
+ .slice(0, MAX_IDS);
+ const runIds = candidateIds.filter((id) => RunIdSchema.safeParse(id).success);
+
+ const presenter = new NextRunListPresenter($replica, clickhouseClient);
+ const { runs } = await presenter.callByIds(project.organizationId, environment.id, {
+ userId,
+ runIds,
+ });
+
+ return typedjson(
+ { runs },
+ {
+ headers: {
+ "Cache-Control": "no-store",
+ },
+ }
+ );
+};
diff --git a/apps/webapp/app/utils/pathBuilder.ts b/apps/webapp/app/utils/pathBuilder.ts
index 7a151053f5a..57e2a3ab5cb 100644
--- a/apps/webapp/app/utils/pathBuilder.ts
+++ b/apps/webapp/app/utils/pathBuilder.ts
@@ -325,6 +325,26 @@ export function v3RunsPath(
return `${v3EnvironmentPath(organization, project, environment)}/runs${query}`;
}
+export function v3RunsRefreshPath(
+ organization: OrgForPath,
+ project: ProjectForPath,
+ environment: EnvironmentForPath
+) {
+ return `/resources/orgs/${organizationParam(organization)}/projects/${projectParam(
+ project
+ )}/env/${environmentParam(environment)}/runs/refresh`;
+}
+
+export function v3RunsCountNewPath(
+ organization: OrgForPath,
+ project: ProjectForPath,
+ environment: EnvironmentForPath
+) {
+ return `/resources/orgs/${organizationParam(organization)}/projects/${projectParam(
+ project
+ )}/env/${environmentParam(environment)}/runs/count-new`;
+}
+
export function v3CreateBulkActionPath(
organization: OrgForPath,
project: ProjectForPath,