From 777bebb32ef2fee5a2f70d1f467d598fcee63c12 Mon Sep 17 00:00:00 2001 From: konard Date: Tue, 14 Jul 2026 08:07:50 +0000 Subject: [PATCH 1/2] Initial commit with task details Adding .gitkeep for PR creation (default mode). This file will be removed when the task is complete. Issue: https://github.com/xlabtg/teleton-agent/issues/710 --- .gitkeep | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitkeep diff --git a/.gitkeep b/.gitkeep new file mode 100644 index 00000000..fdb085a0 --- /dev/null +++ b/.gitkeep @@ -0,0 +1 @@ +# .gitkeep file auto-generated at 2026-07-14T08:07:50.158Z for PR creation at branch issue-710-3d52617211ad for issue https://github.com/xlabtg/teleton-agent/issues/710 \ No newline at end of file From 783887136ee3c144af562d48edf16316e2d572ec Mon Sep 17 00:00:00 2001 From: konard Date: Tue, 14 Jul 2026 08:30:29 +0000 Subject: [PATCH 2/2] =?UTF-8?q?fix(web):=20=D1=81=D0=BE=D1=85=D1=80=D0=B0?= =?UTF-8?q?=D0=BD=D1=8F=D1=82=D1=8C=20=D0=B2=D1=8B=D0=B1=D1=80=D0=B0=D0=BD?= =?UTF-8?q?=D0=BD=D1=8B=D0=B9=20=D0=B7=D0=B0=D0=BF=D1=83=D1=81=D0=BA=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B8=20=D0=BE=D0=BF=D1=80=D0=BE=D1=81=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitkeep | 1 - .../__tests__/pipelineRunSelection.test.ts | 21 +++++++++++++++++++ web/src/lib/pipelineRunSelection.ts | 8 +++++++ web/src/pages/Pipelines.tsx | 17 +++++++++++---- 4 files changed, 42 insertions(+), 5 deletions(-) delete mode 100644 .gitkeep create mode 100644 web/src/lib/__tests__/pipelineRunSelection.test.ts create mode 100644 web/src/lib/pipelineRunSelection.ts diff --git a/.gitkeep b/.gitkeep deleted file mode 100644 index fdb085a0..00000000 --- a/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -# .gitkeep file auto-generated at 2026-07-14T08:07:50.158Z for PR creation at branch issue-710-3d52617211ad for issue https://github.com/xlabtg/teleton-agent/issues/710 \ No newline at end of file diff --git a/web/src/lib/__tests__/pipelineRunSelection.test.ts b/web/src/lib/__tests__/pipelineRunSelection.test.ts new file mode 100644 index 00000000..c69f8dd8 --- /dev/null +++ b/web/src/lib/__tests__/pipelineRunSelection.test.ts @@ -0,0 +1,21 @@ +import { describe, expect, it } from "vitest"; + +import { selectRunIdToLoad } from "../pipelineRunSelection"; + +describe("selectRunIdToLoad", () => { + it("keeps the user's selected run across polls", () => { + expect(selectRunIdToLoad("older-run", ["newest-run", "older-run"])).toBe("older-run"); + }); + + it("auto-selects the newest run when no run is selected", () => { + expect(selectRunIdToLoad(null, ["newest-run", "older-run"])).toBe("newest-run"); + }); + + it("selects the newest run when the previous selection belongs to another pipeline", () => { + expect(selectRunIdToLoad("other-pipeline-run", ["newest-run", "older-run"])).toBe("newest-run"); + }); + + it("clears the selection when there are no runs", () => { + expect(selectRunIdToLoad("older-run", [])).toBeNull(); + }); +}); diff --git a/web/src/lib/pipelineRunSelection.ts b/web/src/lib/pipelineRunSelection.ts new file mode 100644 index 00000000..4b847889 --- /dev/null +++ b/web/src/lib/pipelineRunSelection.ts @@ -0,0 +1,8 @@ +export function selectRunIdToLoad( + selectedRunId: string | null, + runIds: readonly string[] +): string | null { + if (runIds.length === 0) return null; + if (selectedRunId && runIds.includes(selectedRunId)) return selectedRunId; + return runIds[0] ?? null; +} diff --git a/web/src/pages/Pipelines.tsx b/web/src/pages/Pipelines.tsx index dbb09985..d37c4322 100644 --- a/web/src/pages/Pipelines.tsx +++ b/web/src/pages/Pipelines.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { api, type PipelineData, @@ -8,6 +8,7 @@ import { type PipelineStepData, } from "../lib/api"; import { useTranslation } from "react-i18next"; +import { selectRunIdToLoad } from "../lib/pipelineRunSelection"; interface StepDraft { id: string; @@ -438,6 +439,7 @@ export function Pipelines() { const [saving, setSaving] = useState(false); const [running, setRunning] = useState(false); const [error, setError] = useState(null); + const selectedRunIdRef = useRef(null); const selected = useMemo( () => pipelines.find((pipeline) => pipeline.id === selectedId) ?? pipelines[0] ?? null, @@ -462,7 +464,9 @@ export function Pipelines() { const loadRunDetail = useCallback(async (pipelineId: string, runId: string) => { try { const res = await api.pipelineRunDetail(pipelineId, runId); - setSelectedRun(res.data ?? null); + const detail = res.data ?? null; + selectedRunIdRef.current = detail?.run.id ?? null; + setSelectedRun(detail); } catch (err) { setError(err instanceof Error ? err.message : String(err)); } @@ -475,9 +479,14 @@ export function Pipelines() { const res = await api.pipelineRunsList(pipelineId); const next = res.data ?? []; setRuns(next); - if (next[0]) { - await loadRunDetail(pipelineId, next[0].id); + const runId = selectRunIdToLoad( + selectedRunIdRef.current, + next.map((run) => run.id) + ); + if (runId) { + await loadRunDetail(pipelineId, runId); } else { + selectedRunIdRef.current = null; setSelectedRun(null); } } catch (err) {