Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .server-changes/sessions-idle-status.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
area: webapp
type: fix
---

The Sessions list no longer shows an ever-growing duration for a session whose run finished long ago. The duration now stops at the last run's activity, and only sessions with a run still executing keep counting up.
29 changes: 22 additions & 7 deletions apps/webapp/app/components/sessions/v1/SessionsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -195,23 +195,38 @@ export function SessionsTable({
}

function SessionDuration({ session }: { session: SessionListItem }) {
// Active sessions tick live; closed/expired sessions freeze at the
// moment they ended (closedAt for explicit closes, expiresAt when the
// TTL ran out without a close call).
const endedAt =
// Closed and expired sessions freeze at the moment they ended.
const terminalEnd =
session.status === "CLOSED"
? session.closedAt
: session.status === "EXPIRED"
? session.expiresAt
: undefined;

if (endedAt) {
if (terminalEnd) {
return (
<>{formatDuration(new Date(session.createdAt), new Date(endedAt), { style: "short" })}</>
<>{formatDuration(new Date(session.createdAt), new Date(terminalEnd), { style: "short" })}</>
);
}

return <LiveTimer startTime={new Date(session.createdAt)} />;
// An open session ticks only while a run is genuinely executing; otherwise it
// freezes at the last run's completion so the duration doesn't climb forever.
if (session.hasLiveRun) {
return <LiveTimer startTime={new Date(session.createdAt)} />;
}

if (session.currentRunCompletedAt) {
return (
<>
{formatDuration(new Date(session.createdAt), new Date(session.currentRunCompletedAt), {
style: "short",
})}
</>
);
}

// Open session that never ran — nothing to measure.
return <span className="text-text-dimmed">–</span>;
}

function SessionActionsCell({ runPath, allRunsPath }: { runPath?: string; allRunsPath: string }) {
Expand Down
18 changes: 16 additions & 2 deletions apps/webapp/app/presenters/v3/SessionListPresenter.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
LEGACY_PLAYGROUND_TAG,
} from "~/services/sessionsRepository/sessionsRepository.server";
import { ServiceValidationError } from "~/v3/services/baseService.server";
import { isSessionLive } from "./isSessionLive";
import { findCurrentWorkerFromEnvironment } from "~/v3/models/workerDeployment.server";
import { runStore } from "~/v3/runStore.server";
import { startActiveSpan } from "~/v3/tracer.server";
Expand Down Expand Up @@ -196,7 +197,7 @@ export class SessionListPresenter {
projectId,
runtimeEnvironmentId: environmentId,
},
select: { id: true, friendlyId: true },
select: { id: true, friendlyId: true, status: true, completedAt: true },
},
this.replica
)
Expand All @@ -209,14 +210,21 @@ export class SessionListPresenter {

return {
sessions: sessions.map((session) => {
const currentRun = session.currentRunId ? runById.get(session.currentRunId) : undefined;

const status: SessionStatus =
session.closedAt != null
? "CLOSED"
: session.expiresAt != null && session.expiresAt.getTime() < now
? "EXPIRED"
: "ACTIVE";

const currentRun = session.currentRunId ? runById.get(session.currentRunId) : undefined;
// Whether a run is genuinely executing right now. Drives the duration
// cell (tick vs freeze); it does NOT affect the filterable status.
const hasLiveRun = isSessionLive({
hasCurrentRun: session.currentRunId != null,
currentRunStatus: currentRun?.status,
});
Comment on lines +222 to +227

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 PR description promises an "Idle" status but the code only changes the duration cell

The PR title and description say sessions with no live run should read "Idle" and that "the same derivation now backs the session detail page". The diff does neither: status in apps/webapp/app/presenters/v3/SessionListPresenter.server.ts:212-217 still only derives ACTIVE/CLOSED/EXPIRED from closedAt/expiresAt, no IDLE value is added to SessionStatus or SessionStatusCombo, and SessionPresenter.server.ts (detail page) is untouched. Only the duration cell freezes. Worth confirming whether the status half of the change was intentionally dropped, since the release note in .server-changes/sessions-idle-status.md (file name mentions "idle status") may also mislead users.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


return {
id: session.id,
Expand All @@ -239,6 +247,12 @@ export class SessionListPresenter {
updatedAt: session.updatedAt.toISOString(),
environment: displayableEnvironment,
currentRunFriendlyId: currentRun?.friendlyId,
hasLiveRun,
// Freeze point for the duration when the session isn't live: when its
// current run finished. Undefined when it never ran (renders a dash).
currentRunCompletedAt: currentRun?.completedAt
? currentRun.completedAt.toISOString()
: undefined,
};
}),
pagination: {
Expand Down
24 changes: 24 additions & 0 deletions apps/webapp/app/presenters/v3/isSessionLive.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { describe, expect, it } from "vitest";
import { isSessionLive } from "./isSessionLive";

describe("isSessionLive", () => {
it("is live when the current run is executing", () => {
expect(isSessionLive({ hasCurrentRun: true, currentRunStatus: "EXECUTING" })).toBe(true);
});

it("treats any non-final run status as live", () => {
expect(isSessionLive({ hasCurrentRun: true, currentRunStatus: "PENDING" })).toBe(true);
});

it("is not live when the current run has reached a terminal state", () => {
expect(isSessionLive({ hasCurrentRun: true, currentRunStatus: "EXPIRED" })).toBe(false);
});

it("is not live when there is no current run", () => {
expect(isSessionLive({ hasCurrentRun: false, currentRunStatus: undefined })).toBe(false);
});

it("is not live when the current run pointer can't be resolved (status unknown)", () => {
expect(isSessionLive({ hasCurrentRun: true, currentRunStatus: undefined })).toBe(false);
});
});
22 changes: 22 additions & 0 deletions apps/webapp/app/presenters/v3/isSessionLive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { type TaskRunStatus } from "@trigger.dev/database";
import { isFinalRunStatus } from "~/v3/taskStatus";

export type IsSessionLiveInput = {
/** Whether the session points at a current run at all. */
hasCurrentRun: boolean;
/**
* Status of the current run. `undefined` when there is no current run, or the
* pointer couldn't be resolved (stale / cross-env).
*/
currentRunStatus: TaskRunStatus | undefined;
};

/**
* A session is "live" when its current run is still executing (a non-final run
* status). This drives whether the session's duration ticks or freezes; it does
* NOT change the session's status, which stays the filterable
* `ACTIVE`/`CLOSED`/`EXPIRED` set derived from `closedAt`/`expiresAt`.
*/
export function isSessionLive({ hasCurrentRun, currentRunStatus }: IsSessionLiveInput): boolean {
return hasCurrentRun && currentRunStatus !== undefined && !isFinalRunStatus(currentRunStatus);
}
Loading
Loading