Give each workflow run a parent task with no agent
This commit is contained in:
@@ -661,7 +661,7 @@ private struct MergeButton: View {
|
||||
? "Waiting for the merge in progress in this project to finish"
|
||||
: task.mergeAfterReview
|
||||
? "Merges when the running review finishes with no findings; open to merge now instead (⌘S)"
|
||||
: task.parentTaskId != nil
|
||||
: task.parentTaskId != nil && task.workflowStage == nil
|
||||
? "Merge this subtask into its parent task's branch, or into \(target) (⌘S)"
|
||||
: "The agent merges \(target) in, then byre squashes into one commit on \(target) and pushes it to origin; held while another task in the project is merging (⌘S)"
|
||||
Button(intoParent ? "Merging into parent" : held ? "Merge held" : task.mergeAfterReview ? "Merge after review" : keepOpen == false ? "Merge & complete" : "Merge") {
|
||||
|
||||
@@ -91,7 +91,7 @@ func confirmMerge(_ taskId: String, _ store: Store, _ overlays: Overlays, keepOp
|
||||
return
|
||||
}
|
||||
let target = store.projects.first { $0.id == task.projectId }?.defaultBranch ?? "the default branch"
|
||||
let parent = task.parentTaskId.flatMap { store.findTask($0) }
|
||||
let parent = task.workflowStage == nil ? task.parentTaskId.flatMap { store.findTask($0) } : nil
|
||||
let coAuthors: [Participant]
|
||||
do {
|
||||
let client = store.api.client
|
||||
|
||||
@@ -195,7 +195,7 @@ private struct TaskPane: View {
|
||||
|
||||
@ViewBuilder
|
||||
private var tabBody: some View {
|
||||
switch store.focus.tab {
|
||||
switch store.shownTab {
|
||||
case .transcript:
|
||||
ChatView(kind: .task(store.selectedTaskId))
|
||||
.id("\(store.selectedTaskId ?? "")/\(store.selectedTaskId.flatMap { store.pickedMember($0) } ?? "")")
|
||||
@@ -342,16 +342,19 @@ private struct TabStrip: View {
|
||||
}
|
||||
|
||||
private func info() -> Info {
|
||||
let tab = store.focus.tab
|
||||
let workflow = store.selectedTaskId.flatMap { store.findTask($0) }?.workflowRun != nil
|
||||
let tab = store.shownTab
|
||||
let run = store.selectedTaskId.flatMap { store.findTask($0) }?.workflow != nil
|
||||
let team = store.selectedTaskId.map { !store.team($0).isEmpty } ?? false
|
||||
let order = taskTabs.filter { t in
|
||||
if run {
|
||||
return t == .workflow || t == .project
|
||||
}
|
||||
if t == tab {
|
||||
return true
|
||||
}
|
||||
switch t {
|
||||
case .board: return team
|
||||
case .workflow: return workflow
|
||||
case .workflow: return false
|
||||
default: return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ struct ProjectView: View {
|
||||
return
|
||||
}
|
||||
let active = (store.tasks[p.id] ?? []).filter { $0.status == .active }
|
||||
let rows = nested(store, active.filter { !$0.background }).map(\.task.id) + active.filter(\.background).map(\.id)
|
||||
let rows = nested(store, active).map(\.task.id)
|
||||
if rows.isEmpty {
|
||||
return
|
||||
}
|
||||
@@ -200,11 +200,7 @@ private struct ProjectBlock: View {
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.vertical, 6)
|
||||
}
|
||||
TaskRows(active: active.filter { !$0.background })
|
||||
ForEach(active.filter(\.background), id: \.id) { task in
|
||||
BackgroundRow(task: task)
|
||||
.id(task.id)
|
||||
}
|
||||
TaskRows(active: active)
|
||||
heading("TODO")
|
||||
TodoBlock(project: project, todo: todo, active: active)
|
||||
}
|
||||
@@ -229,12 +225,17 @@ private func nested(_ store: Store, _ active: [ByreTask]) -> [(task: ByreTask, d
|
||||
add(child, depth + 1)
|
||||
}
|
||||
}
|
||||
for task in store.byRank(active.filter { $0.parentTaskId == nil || !ids.contains($0.parentTaskId!) }) {
|
||||
let roots = active.filter { $0.parentTaskId == nil || !ids.contains($0.parentTaskId!) }
|
||||
for task in store.byRank(roots.filter { !$0.background }) + roots.filter(\.background) {
|
||||
add(task, 0)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
private func rowIndent(_ task: ByreTask, _ depth: Int) -> CGFloat {
|
||||
task.parentTaskId != nil ? 20 * CGFloat(depth - 1) : 0
|
||||
}
|
||||
|
||||
private struct TaskRows: View {
|
||||
let active: [ByreTask]
|
||||
@Environment(Store.self) private var store
|
||||
@@ -243,8 +244,13 @@ private struct TaskRows: View {
|
||||
if !active.isEmpty {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
ForEach(nested(store, active), id: \.task.id) { row in
|
||||
TaskRow(task: row.task, depth: row.depth)
|
||||
.id(row.task.id)
|
||||
if row.task.background {
|
||||
BackgroundRow(task: row.task, depth: row.depth)
|
||||
.id(row.task.id)
|
||||
} else {
|
||||
TaskRow(task: row.task, depth: row.depth)
|
||||
.id(row.task.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.bottom, 8)
|
||||
@@ -285,14 +291,10 @@ private struct TaskRow: View {
|
||||
let ts = store.taskState(task)
|
||||
let color = taskPalette[task.color]
|
||||
let count = store.inbox.filter { $0.taskId == task.id }.count
|
||||
HoverRow(selected: task.id == store.selectedTaskId, indent: task.parentTaskId != nil ? 20 * CGFloat(depth - 1) : 0) {
|
||||
HoverRow(selected: task.id == store.selectedTaskId, indent: rowIndent(task, depth)) {
|
||||
store.focusTask(task.id)
|
||||
} content: {
|
||||
if let parent = task.parentTaskId {
|
||||
Text("↳")
|
||||
.foregroundStyle(Palette.muted)
|
||||
.help("Subtask of \(store.findTask(parent)?.name ?? parent)")
|
||||
}
|
||||
SubtaskMark(task: task)
|
||||
StateMark(state: ts, color: color)
|
||||
Text(task.name)
|
||||
.foregroundStyle(color)
|
||||
@@ -318,15 +320,30 @@ private struct TaskRow: View {
|
||||
}
|
||||
}
|
||||
|
||||
private struct BackgroundRow: View {
|
||||
private struct SubtaskMark: View {
|
||||
let task: ByreTask
|
||||
@Environment(Store.self) private var store
|
||||
|
||||
var body: some View {
|
||||
if let parent = task.parentTaskId {
|
||||
Text("↳")
|
||||
.foregroundStyle(Palette.muted)
|
||||
.help("Subtask of \(store.findTask(parent)?.name ?? parent)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct BackgroundRow: View {
|
||||
let task: ByreTask
|
||||
let depth: Int
|
||||
@Environment(Store.self) private var store
|
||||
|
||||
var body: some View {
|
||||
let ts = store.taskState(task)
|
||||
HoverRow(selected: task.id == store.selectedTaskId) {
|
||||
HoverRow(selected: task.id == store.selectedTaskId, indent: rowIndent(task, depth)) {
|
||||
store.focusTask(task.id)
|
||||
} content: {
|
||||
SubtaskMark(task: task)
|
||||
Text(task.name)
|
||||
.lineLimit(1)
|
||||
.truncationMode(.tail)
|
||||
|
||||
@@ -391,6 +391,14 @@ final class Store {
|
||||
projects.first { $0.id == focus.projectId }
|
||||
}
|
||||
|
||||
var shownTab: TaskTab {
|
||||
let run = selectedTaskId.flatMap { findTask($0) }?.workflow != nil
|
||||
if run {
|
||||
return focus.tab == .project ? .project : .workflow
|
||||
}
|
||||
return focus.tab == .workflow ? .transcript : focus.tab
|
||||
}
|
||||
|
||||
func setFocus(taskId: String?? = .none, projectId: String?? = .none, tab: TaskTab? = nil) async throws {
|
||||
let task = taskId ?? focus.taskId
|
||||
let project = task != nil ? findTask(task!)?.projectId : (projectId ?? focus.projectId)
|
||||
@@ -527,7 +535,7 @@ final class Store {
|
||||
}
|
||||
|
||||
func unreadSummary(_ task: ByreTask) -> Bool {
|
||||
task.workflow == nil && task.summarySeq > (summaryReads[task.id] ?? 0)
|
||||
task.workflowStage == nil && task.summarySeq > (summaryReads[task.id] ?? 0)
|
||||
}
|
||||
|
||||
var unreadSummaries: Int {
|
||||
|
||||
@@ -66,6 +66,7 @@ private func stateColor(_ state: String) -> Color {
|
||||
struct WorkflowView: View {
|
||||
@Environment(Store.self) private var store
|
||||
@ViewState private var run: WorkflowRun?
|
||||
@ViewState private var runId: String?
|
||||
@ViewState private var error = ""
|
||||
@ViewState private var target = 0
|
||||
@ViewState private var loading = false
|
||||
@@ -74,8 +75,8 @@ struct WorkflowView: View {
|
||||
var body: some View {
|
||||
let task = store.selectedTaskId.flatMap { store.findTask($0) }
|
||||
VStack(spacing: 0) {
|
||||
if let task, let runId = task.workflowRun {
|
||||
if let run, run.run == runId {
|
||||
if let task, task.workflow != nil {
|
||||
if let run, runId == task.id {
|
||||
HStack(alignment: .firstTextBaseline, spacing: 10) {
|
||||
Text(run.name).font(Fonts.font(13, weight: 700))
|
||||
Text(run.title).foregroundStyle(Palette.muted)
|
||||
@@ -114,13 +115,14 @@ struct WorkflowView: View {
|
||||
EmptyText(text: "Loading…")
|
||||
}
|
||||
} else {
|
||||
EmptyText(text: task != nil ? "This task isn't part of a workflow run." : "No task selected.")
|
||||
EmptyText(text: task != nil ? "This task isn't a workflow run." : "No task selected.")
|
||||
}
|
||||
}
|
||||
.frame(maxHeight: .infinity, alignment: .top)
|
||||
.task(id: store.selectedTaskId) {
|
||||
target += 1
|
||||
run = nil
|
||||
runId = nil
|
||||
error = ""
|
||||
await load()
|
||||
let off = store.events.add { ev in
|
||||
@@ -134,7 +136,7 @@ struct WorkflowView: View {
|
||||
off()
|
||||
target += 1
|
||||
}
|
||||
.onChange(of: task?.workflowRun) {
|
||||
.onChange(of: task?.workflow) {
|
||||
if run == nil && !loading && error.isEmpty {
|
||||
Task {
|
||||
await load()
|
||||
@@ -144,13 +146,13 @@ struct WorkflowView: View {
|
||||
}
|
||||
|
||||
private func relevant(_ ev: ByreEvent) -> Bool {
|
||||
guard ev.seq != 0, let run else {
|
||||
guard ev.seq != 0, let run, let runId else {
|
||||
return false
|
||||
}
|
||||
if ev.kind == "task.created" {
|
||||
return ev.payload.value["workflow_run"] as? String == run.run
|
||||
return ev.payload.value["parent_task_id"] as? String == runId
|
||||
}
|
||||
let ids = Set(run.stages.flatMap { $0.attempts.map(\.taskId) })
|
||||
let ids = Set(run.stages.flatMap { $0.attempts.map(\.taskId) } + [runId])
|
||||
guard let taskId = ev.taskId else {
|
||||
return false
|
||||
}
|
||||
@@ -158,7 +160,7 @@ struct WorkflowView: View {
|
||||
}
|
||||
|
||||
private func load() async {
|
||||
guard let taskId = store.selectedTaskId, store.findTask(taskId)?.workflowRun != nil else {
|
||||
guard let taskId = store.selectedTaskId, store.findTask(taskId)?.workflow != nil else {
|
||||
return
|
||||
}
|
||||
if loading {
|
||||
@@ -172,6 +174,7 @@ struct WorkflowView: View {
|
||||
let fresh = try await store.api.client.getWorkflowRun(path: .init(taskId: taskId)).ok.body.json
|
||||
if started == target {
|
||||
run = fresh
|
||||
runId = taskId
|
||||
error = ""
|
||||
}
|
||||
} catch {
|
||||
|
||||
Reference in New Issue
Block a user