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:
|
def cmd_watch(conn, task_id: str) -> None:
|
||||||
"""Polling monitor for a task. Updates every 5s. Exits on terminal pipeline state."""
|
"""Polling monitor for a task. Updates every 5s. Exits on terminal pipeline state."""
|
||||||
import sqlite3
|
|
||||||
task = models.get_task(conn, task_id)
|
task = models.get_task(conn, task_id)
|
||||||
if not task:
|
if not task:
|
||||||
print(f"Task '{task_id}' not found.")
|
print(f"Task '{task_id}' not found.")
|
||||||
|
|
@ -152,10 +151,7 @@ def cmd_watch(conn, task_id: str) -> None:
|
||||||
|
|
||||||
if pipeline:
|
if pipeline:
|
||||||
log = models.get_current_agent_log(conn, task_id, pipeline['created_at'])
|
log = models.get_current_agent_log(conn, task_id, pipeline['created_at'])
|
||||||
step_num = conn.execute(
|
step_num = models.count_agent_logs_since(conn, task_id, pipeline['created_at'])
|
||||||
'SELECT COUNT(*) FROM agent_logs WHERE task_id = ? AND created_at >= ?',
|
|
||||||
(task_id, pipeline['created_at']),
|
|
||||||
).fetchone()[0]
|
|
||||||
total = _parse_total_steps(pipeline.get('steps'))
|
total = _parse_total_steps(pipeline.get('steps'))
|
||||||
else:
|
else:
|
||||||
log, step_num, total = None, 0, '?'
|
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)
|
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(
|
def get_current_agent_log(
|
||||||
conn: sqlite3.Connection, task_id: str, since_iso: str
|
conn: sqlite3.Connection, task_id: str, since_iso: str
|
||||||
) -> dict | None:
|
) -> dict | None:
|
||||||
|
|
|
||||||
|
|
@ -373,6 +373,49 @@ def test_log_agent_run(conn):
|
||||||
assert log["success"] == 1 # SQLite boolean
|
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 --
|
# -- Pipelines --
|
||||||
|
|
||||||
def test_create_and_update_pipeline(conn):
|
def test_create_and_update_pipeline(conn):
|
||||||
|
|
|
||||||
|
|
@ -189,7 +189,7 @@ export interface ProjectLink {
|
||||||
id: number
|
id: number
|
||||||
from_project: string
|
from_project: string
|
||||||
to_project: string
|
to_project: string
|
||||||
link_type: string
|
type: string
|
||||||
description: string | null
|
description: string | null
|
||||||
created_at: string
|
created_at: string
|
||||||
}
|
}
|
||||||
|
|
@ -411,7 +411,7 @@ export const api = {
|
||||||
get<PipelineLog[]>(`/pipelines/${pipelineId}/logs?since_id=${sinceId}`),
|
get<PipelineLog[]>(`/pipelines/${pipelineId}/logs?since_id=${sinceId}`),
|
||||||
projectLinks: (projectId: string) =>
|
projectLinks: (projectId: string) =>
|
||||||
get<ProjectLink[]>(`/projects/${projectId}/links`),
|
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),
|
post<ProjectLink>('/project-links', data),
|
||||||
deleteProjectLink: (id: number) =>
|
deleteProjectLink: (id: number) =>
|
||||||
del<void>(`/project-links/${id}`),
|
del<void>(`/project-links/${id}`),
|
||||||
|
|
|
||||||
|
|
@ -395,7 +395,7 @@ const links = ref<ProjectLink[]>([])
|
||||||
const linksLoading = ref(false)
|
const linksLoading = ref(false)
|
||||||
const linksError = ref('')
|
const linksError = ref('')
|
||||||
const showAddLink = ref(false)
|
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 linkFormError = ref('')
|
||||||
const linkSaving = ref(false)
|
const linkSaving = ref(false)
|
||||||
|
|
||||||
|
|
@ -419,11 +419,11 @@ async function addLink() {
|
||||||
await api.createProjectLink({
|
await api.createProjectLink({
|
||||||
from_project: props.id,
|
from_project: props.id,
|
||||||
to_project: linkForm.value.to_project,
|
to_project: linkForm.value.to_project,
|
||||||
link_type: linkForm.value.link_type,
|
type: linkForm.value.type,
|
||||||
description: linkForm.value.description || undefined,
|
description: linkForm.value.description || undefined,
|
||||||
})
|
})
|
||||||
showAddLink.value = false
|
showAddLink.value = false
|
||||||
linkForm.value = { to_project: '', link_type: 'depends_on', description: '' }
|
linkForm.value = { to_project: '', type: 'depends_on', description: '' }
|
||||||
await loadLinks()
|
await loadLinks()
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
linkFormError.value = e.message
|
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-400 font-mono text-xs">{{ link.from_project }}</span>
|
||||||
<span class="text-gray-600">-></span>
|
<span class="text-gray-600">-></span>
|
||||||
<span class="text-gray-400 font-mono text-xs">{{ link.to_project }}</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>
|
<span v-if="link.description" class="text-gray-500 text-xs">{{ link.description }}</span>
|
||||||
</div>
|
</div>
|
||||||
<button @click="deleteLink(link.id)"
|
<button @click="deleteLink(link.id)"
|
||||||
|
|
@ -1379,7 +1379,7 @@ async function addDecision() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Add Link Modal -->
|
<!-- 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">
|
<form @submit.prevent="addLink" class="space-y-3">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-xs text-gray-500 mb-1">From (current project)</label>
|
<label class="block text-xs text-gray-500 mb-1">From (current project)</label>
|
||||||
|
|
@ -1396,7 +1396,7 @@ async function addDecision() {
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-xs text-gray-500 mb-1">Link type</label>
|
<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">
|
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="depends_on">depends_on</option>
|
||||||
<option value="triggers">triggers</option>
|
<option value="triggers">triggers</option>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue