kin: auto-commit after pipeline
This commit is contained in:
parent
f6bccdceb3
commit
c767c6157a
5 changed files with 61 additions and 13 deletions
|
|
@ -139,7 +139,6 @@ def _render_ps(rows: list[dict]) -> None:
|
|||
|
||||
def cmd_watch(conn, task_id: str) -> None:
|
||||
"""Polling monitor for a task. Updates every 5s. Exits on terminal pipeline state."""
|
||||
import sqlite3
|
||||
task = models.get_task(conn, task_id)
|
||||
if not task:
|
||||
print(f"Task '{task_id}' not found.")
|
||||
|
|
@ -152,10 +151,7 @@ def cmd_watch(conn, task_id: str) -> None:
|
|||
|
||||
if pipeline:
|
||||
log = models.get_current_agent_log(conn, task_id, pipeline['created_at'])
|
||||
step_num = conn.execute(
|
||||
'SELECT COUNT(*) FROM agent_logs WHERE task_id = ? AND created_at >= ?',
|
||||
(task_id, pipeline['created_at']),
|
||||
).fetchone()[0]
|
||||
step_num = models.count_agent_logs_since(conn, task_id, pipeline['created_at'])
|
||||
total = _parse_total_steps(pipeline.get('steps'))
|
||||
else:
|
||||
log, step_num, total = None, 0, '?'
|
||||
|
|
|
|||
|
|
@ -543,6 +543,15 @@ def get_pipeline_for_watch(conn: sqlite3.Connection, task_id: str) -> dict | Non
|
|||
return _row_to_dict(row)
|
||||
|
||||
|
||||
def count_agent_logs_since(conn: sqlite3.Connection, task_id: str, since_iso: str) -> int:
|
||||
"""Return number of agent_logs for a task created at or after since_iso."""
|
||||
row = conn.execute(
|
||||
"SELECT COUNT(*) FROM agent_logs WHERE task_id = ? AND created_at >= ?",
|
||||
(task_id, since_iso),
|
||||
).fetchone()
|
||||
return row[0]
|
||||
|
||||
|
||||
def get_current_agent_log(
|
||||
conn: sqlite3.Connection, task_id: str, since_iso: str
|
||||
) -> dict | None:
|
||||
|
|
|
|||
|
|
@ -373,6 +373,49 @@ def test_log_agent_run(conn):
|
|||
assert log["success"] == 1 # SQLite boolean
|
||||
|
||||
|
||||
def test_count_agent_logs_since_returns_correct_count(conn):
|
||||
"""count_agent_logs_since возвращает количество логов >= since_iso."""
|
||||
models.create_project(conn, "p1", "P1", "/p1")
|
||||
models.create_task(conn, "P1-001", "p1", "Task")
|
||||
models.log_agent_run(conn, "p1", "developer", "implement", task_id="P1-001")
|
||||
models.log_agent_run(conn, "p1", "reviewer", "review", task_id="P1-001")
|
||||
|
||||
count = models.count_agent_logs_since(conn, "P1-001", "2000-01-01T00:00:00")
|
||||
assert count == 2
|
||||
|
||||
|
||||
def test_count_agent_logs_since_filters_by_task_id(conn):
|
||||
"""count_agent_logs_since не считает логи других задач."""
|
||||
models.create_project(conn, "p1", "P1", "/p1")
|
||||
models.create_task(conn, "P1-001", "p1", "Task A")
|
||||
models.create_task(conn, "P1-002", "p1", "Task B")
|
||||
models.log_agent_run(conn, "p1", "developer", "implement", task_id="P1-001")
|
||||
models.log_agent_run(conn, "p1", "developer", "implement", task_id="P1-002")
|
||||
|
||||
assert models.count_agent_logs_since(conn, "P1-001", "2000-01-01T00:00:00") == 1
|
||||
assert models.count_agent_logs_since(conn, "P1-002", "2000-01-01T00:00:00") == 1
|
||||
|
||||
|
||||
def test_count_agent_logs_since_excludes_before_cutoff(conn):
|
||||
"""count_agent_logs_since не считает логи строго до since_iso."""
|
||||
models.create_project(conn, "p1", "P1", "/p1")
|
||||
models.create_task(conn, "P1-001", "p1", "Task")
|
||||
models.log_agent_run(conn, "p1", "developer", "implement", task_id="P1-001")
|
||||
|
||||
# since_iso в далёком будущем — ни один лог не попадает
|
||||
count = models.count_agent_logs_since(conn, "P1-001", "2099-01-01T00:00:00")
|
||||
assert count == 0
|
||||
|
||||
|
||||
def test_count_agent_logs_since_empty_returns_zero(conn):
|
||||
"""count_agent_logs_since возвращает 0 при отсутствии логов."""
|
||||
models.create_project(conn, "p1", "P1", "/p1")
|
||||
models.create_task(conn, "P1-001", "p1", "Task")
|
||||
|
||||
count = models.count_agent_logs_since(conn, "P1-001", "2000-01-01T00:00:00")
|
||||
assert count == 0
|
||||
|
||||
|
||||
# -- Pipelines --
|
||||
|
||||
def test_create_and_update_pipeline(conn):
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ export interface ProjectLink {
|
|||
id: number
|
||||
from_project: string
|
||||
to_project: string
|
||||
link_type: string
|
||||
type: string
|
||||
description: string | null
|
||||
created_at: string
|
||||
}
|
||||
|
|
@ -411,7 +411,7 @@ export const api = {
|
|||
get<PipelineLog[]>(`/pipelines/${pipelineId}/logs?since_id=${sinceId}`),
|
||||
projectLinks: (projectId: string) =>
|
||||
get<ProjectLink[]>(`/projects/${projectId}/links`),
|
||||
createProjectLink: (data: { from_project: string; to_project: string; link_type: string; description?: string }) =>
|
||||
createProjectLink: (data: { from_project: string; to_project: string; type: string; description?: string }) =>
|
||||
post<ProjectLink>('/project-links', data),
|
||||
deleteProjectLink: (id: number) =>
|
||||
del<void>(`/project-links/${id}`),
|
||||
|
|
|
|||
|
|
@ -395,7 +395,7 @@ const links = ref<ProjectLink[]>([])
|
|||
const linksLoading = ref(false)
|
||||
const linksError = ref('')
|
||||
const showAddLink = ref(false)
|
||||
const linkForm = ref({ to_project: '', link_type: 'depends_on', description: '' })
|
||||
const linkForm = ref({ to_project: '', type: 'depends_on', description: '' })
|
||||
const linkFormError = ref('')
|
||||
const linkSaving = ref(false)
|
||||
|
||||
|
|
@ -419,11 +419,11 @@ async function addLink() {
|
|||
await api.createProjectLink({
|
||||
from_project: props.id,
|
||||
to_project: linkForm.value.to_project,
|
||||
link_type: linkForm.value.link_type,
|
||||
type: linkForm.value.type,
|
||||
description: linkForm.value.description || undefined,
|
||||
})
|
||||
showAddLink.value = false
|
||||
linkForm.value = { to_project: '', link_type: 'depends_on', description: '' }
|
||||
linkForm.value = { to_project: '', type: 'depends_on', description: '' }
|
||||
await loadLinks()
|
||||
} catch (e: any) {
|
||||
linkFormError.value = e.message
|
||||
|
|
@ -1368,7 +1368,7 @@ async function addDecision() {
|
|||
<span class="text-gray-400 font-mono text-xs">{{ link.from_project }}</span>
|
||||
<span class="text-gray-600">-></span>
|
||||
<span class="text-gray-400 font-mono text-xs">{{ link.to_project }}</span>
|
||||
<span class="px-1.5 py-0.5 text-[10px] bg-indigo-900/30 text-indigo-400 border border-indigo-800 rounded">{{ link.link_type }}</span>
|
||||
<span class="px-1.5 py-0.5 text-[10px] bg-indigo-900/30 text-indigo-400 border border-indigo-800 rounded">{{ link.type }}</span>
|
||||
<span v-if="link.description" class="text-gray-500 text-xs">{{ link.description }}</span>
|
||||
</div>
|
||||
<button @click="deleteLink(link.id)"
|
||||
|
|
@ -1379,7 +1379,7 @@ async function addDecision() {
|
|||
</div>
|
||||
|
||||
<!-- Add Link Modal -->
|
||||
<Modal v-if="showAddLink" title="Add Link" @close="showAddLink = false; linkForm = { to_project: '', link_type: 'depends_on', description: '' }; linkFormError = ''">
|
||||
<Modal v-if="showAddLink" title="Add Link" @close="showAddLink = false; linkForm = { to_project: '', type: 'depends_on', description: '' }; linkFormError = ''">
|
||||
<form @submit.prevent="addLink" class="space-y-3">
|
||||
<div>
|
||||
<label class="block text-xs text-gray-500 mb-1">From (current project)</label>
|
||||
|
|
@ -1396,7 +1396,7 @@ async function addDecision() {
|
|||
</div>
|
||||
<div>
|
||||
<label class="block text-xs text-gray-500 mb-1">Link type</label>
|
||||
<select v-model="linkForm.link_type"
|
||||
<select v-model="linkForm.type"
|
||||
class="w-full bg-gray-800 border border-gray-700 rounded px-3 py-2 text-sm text-gray-300">
|
||||
<option value="depends_on">depends_on</option>
|
||||
<option value="triggers">triggers</option>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue