From fc0f2b3549c4f8815bd596a881306aeb12999c69 Mon Sep 17 00:00:00 2001 From: myftija Date: Thu, 25 Sep 2025 15:45:06 +0200 Subject: [PATCH 1/2] fix: project scoping for runs --- .../app/presenters/v3/RunPresenter.server.ts | 9 ++- .../app/presenters/v3/SpanPresenter.server.ts | 49 +++++++------ .../route.tsx | 1 - ...g.projects.$projectParam.runs.$runParam.ts | 11 ++- .../route.tsx | 3 + .../resources.taskruns.$runParam.cancel.ts | 15 +++- .../resources.taskruns.$runParam.debug.ts | 2 +- apps/webapp/app/v3/eventRepository.server.ts | 72 +++++++++++-------- 8 files changed, 104 insertions(+), 58 deletions(-) diff --git a/apps/webapp/app/presenters/v3/RunPresenter.server.ts b/apps/webapp/app/presenters/v3/RunPresenter.server.ts index 400e872a21..4ec0260e71 100644 --- a/apps/webapp/app/presenters/v3/RunPresenter.server.ts +++ b/apps/webapp/app/presenters/v3/RunPresenter.server.ts @@ -28,7 +28,6 @@ export class RunPresenter { public async call({ userId, projectSlug, - organizationSlug, environmentSlug, runFriendlyId, showDeletedLogs, @@ -36,7 +35,6 @@ export class RunPresenter { }: { userId: string; projectSlug: string; - organizationSlug: string; environmentSlug: string; runFriendlyId: string; showDeletedLogs: boolean; @@ -93,6 +91,13 @@ export class RunPresenter { friendlyId: runFriendlyId, project: { slug: projectSlug, + organization: { + members: { + some: { + userId, + }, + }, + }, }, }, }); diff --git a/apps/webapp/app/presenters/v3/SpanPresenter.server.ts b/apps/webapp/app/presenters/v3/SpanPresenter.server.ts index 7919e075b2..94d10cb1c2 100644 --- a/apps/webapp/app/presenters/v3/SpanPresenter.server.ts +++ b/apps/webapp/app/presenters/v3/SpanPresenter.server.ts @@ -28,10 +28,12 @@ type GetSpanResult = NonNullable { const [error, result] = await tryCatch( presenter.call({ userId, - organizationSlug, showDeletedLogs: !!impersonationId, projectSlug: projectParam, runFriendlyId: runParam, diff --git a/apps/webapp/app/routes/orgs.$organizationSlug.projects.$projectParam.runs.$runParam.ts b/apps/webapp/app/routes/orgs.$organizationSlug.projects.$projectParam.runs.$runParam.ts index 29aa557797..f11b1e42e8 100644 --- a/apps/webapp/app/routes/orgs.$organizationSlug.projects.$projectParam.runs.$runParam.ts +++ b/apps/webapp/app/routes/orgs.$organizationSlug.projects.$projectParam.runs.$runParam.ts @@ -3,14 +3,14 @@ import { type LoaderFunctionArgs } from "@remix-run/server-runtime"; import { z } from "zod"; import { prisma } from "~/db.server"; import { requireUserId } from "~/services/session.server"; -import { ProjectParamSchema, v3DeploymentPath, v3RunPath } from "~/utils/pathBuilder"; +import { ProjectParamSchema, v3RunPath } from "~/utils/pathBuilder"; const ParamSchema = ProjectParamSchema.extend({ runParam: z.string(), }); export const loader = async ({ request, params }: LoaderFunctionArgs) => { - await requireUserId(request); + const userId = await requireUserId(request); const { organizationSlug, projectParam, runParam } = ParamSchema.parse(params); const run = await prisma.taskRun.findFirst({ @@ -18,6 +18,13 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { friendlyId: runParam, project: { slug: projectParam, + organization: { + members: { + some: { + userId, + }, + }, + }, }, }, select: { diff --git a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx index 66d166294b..29a5cb873a 100644 --- a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx +++ b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx @@ -78,8 +78,10 @@ import { } from "~/utils/pathBuilder"; import { createTimelineSpanEventsFromSpanEvents } from "~/utils/timelineSpanEvents"; import { CompleteWaitpointForm } from "../resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.waitpoints.$waitpointFriendlyId.complete/route"; +import { requireUserId } from "~/services/session.server"; export const loader = async ({ request, params }: LoaderFunctionArgs) => { + const userId = await requireUserId(request); const { projectParam, organizationSlug, envParam, runParam, spanParam } = v3SpanParamsSchema.parse(params); @@ -90,6 +92,7 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { projectSlug: projectParam, spanId: spanParam, runFriendlyId: runParam, + userId, }); return typedjson(result); diff --git a/apps/webapp/app/routes/resources.taskruns.$runParam.cancel.ts b/apps/webapp/app/routes/resources.taskruns.$runParam.cancel.ts index 676d4dabe2..240d7d3d8e 100644 --- a/apps/webapp/app/routes/resources.taskruns.$runParam.cancel.ts +++ b/apps/webapp/app/routes/resources.taskruns.$runParam.cancel.ts @@ -1,9 +1,10 @@ import { parse } from "@conform-to/zod"; -import { ActionFunction, json } from "@remix-run/node"; +import { type ActionFunction, json } from "@remix-run/node"; import { z } from "zod"; import { prisma } from "~/db.server"; import { redirectWithErrorMessage, redirectWithSuccessMessage } from "~/models/message.server"; import { logger } from "~/services/logger.server"; +import { requireUserId } from "~/services/session.server"; import { CancelTaskRunService } from "~/v3/services/cancelTaskRun.server"; export const cancelSchema = z.object({ @@ -15,6 +16,7 @@ const ParamSchema = z.object({ }); export const action: ActionFunction = async ({ request, params }) => { + const userId = await requireUserId(request); const { runParam } = ParamSchema.parse(params); const formData = await request.formData(); @@ -25,9 +27,18 @@ export const action: ActionFunction = async ({ request, params }) => { } try { - const taskRun = await prisma.taskRun.findUnique({ + const taskRun = await prisma.taskRun.findFirst({ where: { friendlyId: runParam, + project: { + organization: { + members: { + some: { + userId, + }, + }, + }, + }, }, }); diff --git a/apps/webapp/app/routes/resources.taskruns.$runParam.debug.ts b/apps/webapp/app/routes/resources.taskruns.$runParam.debug.ts index ba48f7085a..d7acf18e51 100644 --- a/apps/webapp/app/routes/resources.taskruns.$runParam.debug.ts +++ b/apps/webapp/app/routes/resources.taskruns.$runParam.debug.ts @@ -1,4 +1,4 @@ -import { LoaderFunctionArgs } from "@remix-run/node"; +import { type LoaderFunctionArgs } from "@remix-run/node"; import { typedjson } from "remix-typedjson"; import { z } from "zod"; import { $replica } from "~/db.server"; diff --git a/apps/webapp/app/v3/eventRepository.server.ts b/apps/webapp/app/v3/eventRepository.server.ts index 263125ba75..176cfd039f 100644 --- a/apps/webapp/app/v3/eventRepository.server.ts +++ b/apps/webapp/app/v3/eventRepository.server.ts @@ -970,22 +970,30 @@ export class EventRepository { // A Span can be cancelled if it is partial and has a parent that is cancelled // And a span's duration, if it is partial and has a cancelled parent, is the time between the start of the span and the time of the cancellation event of the parent - public async getSpan( - storeTable: TaskEventStoreTable, - spanId: string, - traceId: string, - startCreatedAt: Date, - endCreatedAt?: Date, - options?: { includeDebugLogs?: boolean } - ) { - return await startActiveSpan("getSpan", async (s) => { - const spanEvent = await this.#getSpanEvent( + public async getSpan({ + storeTable, + spanId, + environmentId, + startCreatedAt, + endCreatedAt, + options, + }: { + storeTable: TaskEventStoreTable; + spanId: string; + environmentId: string; + startCreatedAt: Date; + endCreatedAt?: Date; + options?: { includeDebugLogs?: boolean }; + }) { + return await startActiveSpan("getSpan", async () => { + const spanEvent = await this.#getSpanEvent({ storeTable, spanId, + environmentId, startCreatedAt, endCreatedAt, - options - ); + options, + }); if (!spanEvent) { return; @@ -1195,12 +1203,12 @@ export class EventRepository { } await startActiveSpan("walkSpanAncestors", async (s) => { - let parentEvent = await this.#getSpanEvent( + let parentEvent = await this.#getSpanEvent({ storeTable, - parentId, + spanId: parentId, startCreatedAt, - endCreatedAt - ); + endCreatedAt, + }); let level = 1; while (parentEvent) { @@ -1216,29 +1224,37 @@ export class EventRepository { return; } - parentEvent = await this.#getSpanEvent( + parentEvent = await this.#getSpanEvent({ storeTable, - preparedParentEvent.parentId, + spanId: preparedParentEvent.parentId, startCreatedAt, - endCreatedAt - ); + endCreatedAt, + }); level++; } }); } - async #getSpanEvent( - storeTable: TaskEventStoreTable, - spanId: string, - startCreatedAt: Date, - endCreatedAt?: Date, - options?: { includeDebugLogs?: boolean } - ) { + async #getSpanEvent({ + storeTable, + spanId, + environmentId, + startCreatedAt, + endCreatedAt, + options, + }: { + storeTable: TaskEventStoreTable; + spanId: string; + environmentId?: string; + startCreatedAt: Date; + endCreatedAt?: Date; + options?: { includeDebugLogs?: boolean }; + }) { return await startActiveSpan("getSpanEvent", async (s) => { const events = await this.taskEventStore.findMany( storeTable, - { spanId }, + { spanId, ...(environmentId ? { environmentId } : {}) }, startCreatedAt, endCreatedAt, undefined, From 18048e74abe5aafe9cc1b8d6e840c08dcaee44de Mon Sep 17 00:00:00 2001 From: myftija Date: Thu, 25 Sep 2025 17:02:56 +0200 Subject: [PATCH 2/2] =?UTF-8?q?Apply=20some=20=F0=9F=90=B0=20suggestions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...zationSlug.projects.$projectParam.runs.$runParam.ts | 1 + apps/webapp/app/v3/eventRepository.server.ts | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/webapp/app/routes/orgs.$organizationSlug.projects.$projectParam.runs.$runParam.ts b/apps/webapp/app/routes/orgs.$organizationSlug.projects.$projectParam.runs.$runParam.ts index f11b1e42e8..63a89d7e0a 100644 --- a/apps/webapp/app/routes/orgs.$organizationSlug.projects.$projectParam.runs.$runParam.ts +++ b/apps/webapp/app/routes/orgs.$organizationSlug.projects.$projectParam.runs.$runParam.ts @@ -19,6 +19,7 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { project: { slug: projectParam, organization: { + slug: organizationSlug, members: { some: { userId, diff --git a/apps/webapp/app/v3/eventRepository.server.ts b/apps/webapp/app/v3/eventRepository.server.ts index 176cfd039f..d19a2963ee 100644 --- a/apps/webapp/app/v3/eventRepository.server.ts +++ b/apps/webapp/app/v3/eventRepository.server.ts @@ -1004,6 +1004,7 @@ export class EventRepository { const span = await this.#createSpanFromEvent( storeTable, preparedEvent, + environmentId, startCreatedAt, endCreatedAt ); @@ -1089,6 +1090,7 @@ export class EventRepository { async #createSpanFromEvent( storeTable: TaskEventStoreTable, event: PreparedEvent, + environmentId: string, startCreatedAt: Date, endCreatedAt?: Date ) { @@ -1099,6 +1101,7 @@ export class EventRepository { await this.#walkSpanAncestors( storeTable, event, + environmentId, startCreatedAt, endCreatedAt, (ancestorEvent, level) => { @@ -1193,6 +1196,7 @@ export class EventRepository { async #walkSpanAncestors( storeTable: TaskEventStoreTable, event: PreparedEvent, + environmentId: string, startCreatedAt: Date, endCreatedAt: Date | undefined, callback: (event: PreparedEvent, level: number) => { stop: boolean } @@ -1206,6 +1210,7 @@ export class EventRepository { let parentEvent = await this.#getSpanEvent({ storeTable, spanId: parentId, + environmentId, startCreatedAt, endCreatedAt, }); @@ -1227,6 +1232,7 @@ export class EventRepository { parentEvent = await this.#getSpanEvent({ storeTable, spanId: preparedParentEvent.parentId, + environmentId, startCreatedAt, endCreatedAt, }); @@ -1246,7 +1252,7 @@ export class EventRepository { }: { storeTable: TaskEventStoreTable; spanId: string; - environmentId?: string; + environmentId: string; startCreatedAt: Date; endCreatedAt?: Date; options?: { includeDebugLogs?: boolean }; @@ -1254,7 +1260,7 @@ export class EventRepository { return await startActiveSpan("getSpanEvent", async (s) => { const events = await this.taskEventStore.findMany( storeTable, - { spanId, ...(environmentId ? { environmentId } : {}) }, + { spanId, environmentId }, startCreatedAt, endCreatedAt, undefined,