fix(webapp): project scoping for runs (#2553)

* fix: project scoping for runs

* Apply some 🐰 suggestions
This commit is contained in:
Saadi Myftija
2025-09-25 17:43:13 +02:00
committed by James Ritchie
parent 543d9cd887
commit 9ef745d049
8 changed files with 111 additions and 58 deletions
@@ -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,
},
},
},
},
},
});
@@ -28,10 +28,12 @@ type GetSpanResult = NonNullable<Awaited<ReturnType<(typeof eventRepository)["ge
export class SpanPresenter extends BasePresenter {
public async call({
userId,
projectSlug,
spanId,
runFriendlyId,
}: {
userId: string;
projectSlug: string;
spanId: string;
runFriendlyId: string;
@@ -39,6 +41,13 @@ export class SpanPresenter extends BasePresenter {
const project = await this._replica.project.findFirst({
where: {
slug: projectSlug,
organization: {
members: {
some: {
userId,
},
},
},
},
});
@@ -57,6 +66,7 @@ export class SpanPresenter extends BasePresenter {
},
where: {
friendlyId: runFriendlyId,
projectId: project.id,
},
});
@@ -64,13 +74,11 @@ export class SpanPresenter extends BasePresenter {
return;
}
const { traceId } = parentRun;
const eventStore = getTaskEventStoreTableForRun(parentRun);
const run = await this.getRun({
eventStore,
traceId,
environmentId: parentRun.runtimeEnvironmentId,
spanId,
createdAt: parentRun.createdAt,
completedAt: parentRun.completedAt,
@@ -82,10 +90,8 @@ export class SpanPresenter extends BasePresenter {
};
}
//get the run
const span = await this.#getSpan({
eventStore,
traceId,
spanId,
environmentId: parentRun.runtimeEnvironmentId,
projectId: parentRun.projectId,
@@ -105,24 +111,24 @@ export class SpanPresenter extends BasePresenter {
async getRun({
eventStore,
traceId,
environmentId,
spanId,
createdAt,
completedAt,
}: {
eventStore: TaskEventStoreTable;
traceId: string;
environmentId: string;
spanId: string;
createdAt: Date;
completedAt: Date | null;
}) {
const span = await eventRepository.getSpan(
eventStore,
const span = await eventRepository.getSpan({
storeTable: eventStore,
spanId,
traceId,
createdAt,
completedAt ?? undefined
);
environmentId,
startCreatedAt: createdAt,
endCreatedAt: completedAt ?? undefined,
});
if (!span) {
return;
@@ -412,14 +418,12 @@ export class SpanPresenter extends BasePresenter {
async #getSpan({
eventStore,
traceId,
spanId,
environmentId,
projectId,
createdAt,
completedAt,
}: {
traceId: string;
spanId: string;
environmentId: string;
projectId: string;
@@ -427,14 +431,15 @@ export class SpanPresenter extends BasePresenter {
createdAt: Date;
completedAt: Date | null;
}) {
const span = await eventRepository.getSpan(
eventStore,
const span = await eventRepository.getSpan({
storeTable: eventStore,
spanId,
traceId,
createdAt,
completedAt ?? undefined,
{ includeDebugLogs: true }
);
environmentId,
startCreatedAt: createdAt,
endCreatedAt: completedAt ?? undefined,
options: { includeDebugLogs: true },
});
if (!span) {
return;
}
@@ -142,7 +142,6 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const [error, result] = await tryCatch(
presenter.call({
userId,
organizationSlug,
showDeletedLogs: !!impersonationId,
projectSlug: projectParam,
runFriendlyId: runParam,
@@ -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,14 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
friendlyId: runParam,
project: {
slug: projectParam,
organization: {
slug: organizationSlug,
members: {
some: {
userId,
},
},
},
},
},
select: {
@@ -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);
@@ -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,
},
},
},
},
},
});
@@ -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";
+50 -28
View File
@@ -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;
@@ -996,6 +1004,7 @@ export class EventRepository {
const span = await this.#createSpanFromEvent(
storeTable,
preparedEvent,
environmentId,
startCreatedAt,
endCreatedAt
);
@@ -1081,6 +1090,7 @@ export class EventRepository {
async #createSpanFromEvent(
storeTable: TaskEventStoreTable,
event: PreparedEvent,
environmentId: string,
startCreatedAt: Date,
endCreatedAt?: Date
) {
@@ -1091,6 +1101,7 @@ export class EventRepository {
await this.#walkSpanAncestors(
storeTable,
event,
environmentId,
startCreatedAt,
endCreatedAt,
(ancestorEvent, level) => {
@@ -1185,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 }
@@ -1195,12 +1207,13 @@ export class EventRepository {
}
await startActiveSpan("walkSpanAncestors", async (s) => {
let parentEvent = await this.#getSpanEvent(
let parentEvent = await this.#getSpanEvent({
storeTable,
parentId,
spanId: parentId,
environmentId,
startCreatedAt,
endCreatedAt
);
endCreatedAt,
});
let level = 1;
while (parentEvent) {
@@ -1216,29 +1229,38 @@ export class EventRepository {
return;
}
parentEvent = await this.#getSpanEvent(
parentEvent = await this.#getSpanEvent({
storeTable,
preparedParentEvent.parentId,
spanId: preparedParentEvent.parentId,
environmentId,
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 },
startCreatedAt,
endCreatedAt,
undefined,