From 20b1fbad02c83034ea1a21ae4b6f0a5a52dd405e Mon Sep 17 00:00:00 2001 From: David Schmitt Date: Thu, 16 May 2024 15:21:53 +0200 Subject: [PATCH] Rework blast radius and risks progress display Fixes https://github.com/overmindtech/cli/issues/304 --- cmd/tea_snapshot.go | 31 +++++++---- cmd/tea_taskmodel.go | 4 -- cmd/terraform_apply.go | 4 +- cmd/terraform_plan.go | 113 ++++++++++++++++++++++++----------------- 4 files changed, 89 insertions(+), 63 deletions(-) diff --git a/cmd/tea_snapshot.go b/cmd/tea_snapshot.go index 2184ad04..9e8098b6 100644 --- a/cmd/tea_snapshot.go +++ b/cmd/tea_snapshot.go @@ -4,11 +4,10 @@ import ( "fmt" tea "github.com/charmbracelet/bubbletea" - log "github.com/sirupsen/logrus" ) type snapshotModel struct { - title string + taskModel state string items uint32 edges uint32 @@ -28,11 +27,18 @@ type finishSnapshotMsg struct { edges uint32 } +func NewSnapShotModel(title string) snapshotModel { + return snapshotModel{ + taskModel: NewTaskModel(title), + state: "pending", + } +} + func (m snapshotModel) Init() tea.Cmd { - return nil + return m.taskModel.Init() } -func (m snapshotModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (m snapshotModel) Update(msg tea.Msg) (snapshotModel, tea.Cmd) { switch msg := msg.(type) { case startSnapshotMsg: m.state = msg.newState @@ -44,8 +50,11 @@ func (m snapshotModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.state = msg.newState m.items = msg.items m.edges = msg.edges + default: + var cmd tea.Cmd + m.taskModel, cmd = m.taskModel.Update(msg) + return m, cmd } - log.Debugf("updated %+v => %v", msg, m) return m, nil } @@ -53,16 +62,16 @@ func (m snapshotModel) View() string { // TODO: add spinner and/or progressbar; complication: we do not have a // expected number of items/edges to count towards for the progressbar if m.items == 0 && m.edges == 0 { - return fmt.Sprintf("%s - %s", m.title, m.state) + return fmt.Sprintf("%v - %v", m.taskModel.View(), m.state) } else if m.items == 1 && m.edges == 0 { - return fmt.Sprintf("%s - %s: 1 item", m.title, m.state) + return fmt.Sprintf("%v - %v: 1 item", m.taskModel.View(), m.state) } else if m.items == 1 && m.edges == 1 { - return fmt.Sprintf("%s - %s: 1 item, 1 edge", m.title, m.state) + return fmt.Sprintf("%v - %v: 1 item, 1 edge", m.taskModel.View(), m.state) } else if m.items > 1 && m.edges == 0 { - return fmt.Sprintf("%s - %s: %d items", m.title, m.state, m.items) + return fmt.Sprintf("%v - %v: %d items", m.taskModel.View(), m.state, m.items) } else if m.items > 1 && m.edges == 1 { - return fmt.Sprintf("%s - %s: %d items, 1 edge", m.title, m.state, m.items) + return fmt.Sprintf("%v - %v: %d items, 1 edge", m.taskModel.View(), m.state, m.items) } else { - return fmt.Sprintf("%s - %s: %d items, %d edges", m.title, m.state, m.items, m.edges) + return fmt.Sprintf("%v - %v: %d items, %d edges", m.taskModel.View(), m.state, m.items, m.edges) } } diff --git a/cmd/tea_taskmodel.go b/cmd/tea_taskmodel.go index c755f848..3070cd2f 100644 --- a/cmd/tea_taskmodel.go +++ b/cmd/tea_taskmodel.go @@ -108,10 +108,6 @@ func (m taskModel) View() string { label = lipgloss.NewStyle().Foreground(ColorPalette.LabelFaint).Render("+") case taskStatusRunning: label = m.spinner.View() - // all other lables are 7 cells wide - // for ansi.PrintableRuneWidth(label) <= 7 { - // label += " " - // } case taskStatusDone: label = lipgloss.NewStyle().Foreground(ColorPalette.BgSuccess).Render("✔︎") case taskStatusError: diff --git a/cmd/terraform_apply.go b/cmd/terraform_apply.go index 20c07c0d..89b43d97 100644 --- a/cmd/terraform_apply.go +++ b/cmd/terraform_apply.go @@ -81,9 +81,9 @@ Applying changes with ` + "`" + `terraform %v` + "`\n" processingHeader: processingHeader, startingChange: make(chan tea.Msg, 10), // provide a small buffer for sending updates, so we don't block the processing - startingChangeSnapshot: snapshotModel{title: "Starting Change", state: "pending"}, + startingChangeSnapshot: NewSnapShotModel("Starting Change"), endingChange: make(chan tea.Msg, 10), // provide a small buffer for sending updates, so we don't block the processing - endingChangeSnapshot: snapshotModel{title: "Ending Change", state: "pending"}, + endingChangeSnapshot: NewSnapShotModel("Ending Change"), progress: []string{}, } } diff --git a/cmd/terraform_plan.go b/cmd/terraform_plan.go index 2814f8fb..6dc14f80 100644 --- a/cmd/terraform_plan.go +++ b/cmd/terraform_plan.go @@ -233,20 +233,20 @@ type tfPlanModel struct { ctx context.Context // note that this ctx is not initialized on NewTfPlanModel to instead get a modified context through the loadSourcesConfigMsg that has a timeout and cancelFunction configured oi OvermindInstance - args []string - planTask taskModel - planHeader string - processingTask taskModel - processingHeader string + args []string + planTask taskModel + planHeader string revlinkWarmupFinished bool - runTfPlan bool - tfPlanFinished bool - processing chan tea.Msg - processingModel snapshotModel - progress []string - changeUrl string + runTfPlan bool + tfPlanFinished bool + processing chan tea.Msg + blastRadiusModel snapshotModel + progress []string + changeUrl string + + riskTask taskModel riskMilestones []*sdp.RiskCalculationStatus_ProgressMilestone riskMilestoneTasks []taskModel risks []*sdp.Risk @@ -277,26 +277,24 @@ func NewTfPlanModel(args []string) tea.Model { planHeader := `Running ` + "`" + `terraform %v` + "`\n" planHeader = fmt.Sprintf(planHeader, strings.Join(args, " ")) - processingHeader := ` Processing plan from ` + "`" + `terraform %v` + "`\n" - processingHeader = fmt.Sprintf(processingHeader, strings.Join(args, " ")) - return tfPlanModel{ - args: args, - planTask: NewTaskModel("Planning Changes"), - planHeader: planHeader, - processingTask: NewTaskModel("Processing Planned Changes"), - processingHeader: processingHeader, - - processing: make(chan tea.Msg, 10), // provide a small buffer for sending updates, so we don't block the processing - processingModel: snapshotModel{title: "Calculating Blast Radius", state: "pending"}, - progress: []string{}, + args: args, + planTask: NewTaskModel("Planning Changes"), + planHeader: planHeader, + + processing: make(chan tea.Msg, 10), // provide a small buffer for sending updates, so we don't block the processing + blastRadiusModel: NewSnapShotModel("Calculating Blast Radius"), + progress: []string{}, + + riskTask: NewTaskModel("Calculating Risks"), } } func (m tfPlanModel) Init() tea.Cmd { return tea.Batch( m.planTask.Init(), - m.processingTask.Init(), + m.blastRadiusModel.Init(), + m.riskTask.Init(), ) } @@ -324,7 +322,7 @@ func (m tfPlanModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { c.Env = append(c.Env, fmt.Sprintf("AWS_PROFILE=%v", aws_profile)) } - m.processingModel.state = "executing terraform plan" + m.blastRadiusModel.state = "executing terraform plan" if viper.GetString("ovm-test-fake") != "" { c = exec.CommandContext(m.ctx, "bash", "-c", "for i in $(seq 100); do echo fake terraform plan progress line $i of 100; done; sleep 1") @@ -354,21 +352,22 @@ func (m tfPlanModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } case triggerPlanProcessingMsg: - m.processingTask.status = taskStatusRunning - m.processingModel.state = "executed terraform plan" + m.blastRadiusModel.status = taskStatusRunning + m.blastRadiusModel.state = "executed terraform plan" cmds = append(cmds, m.processPlanCmd, - m.processingTask.spinner.Tick, + m.blastRadiusModel.spinner.Tick, m.waitForProcessingActivity, ) case processingActivityMsg: - m.processingModel.state = "processing" + m.blastRadiusModel.state = "processing" m.progress = append(m.progress, msg.text) cmds = append(cmds, m.waitForProcessingActivity) case processingFinishedActivityMsg: - m.processingModel.state = "finished" - m.processingTask.status = taskStatusDone + m.blastRadiusModel.status = taskStatusDone + m.blastRadiusModel.state = "finished" + m.riskTask.status = taskStatusDone m.progress = append(m.progress, msg.text) cmds = append(cmds, m.waitForProcessingActivity) case changeUpdatedMsg: @@ -398,20 +397,40 @@ func (m tfPlanModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } m.risks = msg.risks - m.processingModel.state = "Change updated" + + if len(m.riskMilestones) > 0 { + m.riskTask.status = taskStatusRunning + cmds = append(cmds, m.riskTask.spinner.Tick) + } else if len(m.risks) > 0 { + m.riskTask.status = taskStatusDone + } else { + var allSkipped = true + for _, ms := range m.riskMilestoneTasks { + if ms.status != taskStatusSkipped { + allSkipped = false + break + } + } + if allSkipped { + m.riskTask.status = taskStatusSkipped + } + } + + m.blastRadiusModel.status = taskStatusDone + m.blastRadiusModel.state = "Change updated" cmds = append(cmds, m.waitForProcessingActivity) case startSnapshotMsg: - mdl, cmd := m.processingModel.Update(msg) - m.processingModel = mdl.(snapshotModel) + var cmd tea.Cmd + m.blastRadiusModel, cmd = m.blastRadiusModel.Update(msg) cmds = append(cmds, m.waitForProcessingActivity, cmd) case progressSnapshotMsg: - mdl, cmd := m.processingModel.Update(msg) - m.processingModel = mdl.(snapshotModel) + var cmd tea.Cmd + m.blastRadiusModel, cmd = m.blastRadiusModel.Update(msg) cmds = append(cmds, m.waitForProcessingActivity, cmd) case finishSnapshotMsg: - mdl, cmd := m.processingModel.Update(msg) - m.processingModel = mdl.(snapshotModel) + var cmd tea.Cmd + m.blastRadiusModel, cmd = m.blastRadiusModel.Update(msg) cmds = append(cmds, tea.Sequence(cmd, func() tea.Msg { return delayQuitMsg{} })) case delayQuitMsg: cmds = append(cmds, tea.Quit) @@ -424,7 +443,10 @@ func (m tfPlanModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.planTask, cmd = m.planTask.Update(msg) cmds = append(cmds, cmd) - m.processingTask, cmd = m.processingTask.Update(msg) + m.blastRadiusModel, cmd = m.blastRadiusModel.Update(msg) + cmds = append(cmds, cmd) + + m.riskTask, cmd = m.riskTask.Update(msg) cmds = append(cmds, cmd) for i, ms := range m.riskMilestoneTasks { @@ -447,16 +469,15 @@ func (m tfPlanModel) View() string { bits = append(bits, markdownToString(m.planHeader)) } - if m.processingTask.status != taskStatusPending { - bits = append(bits, m.processingTask.View()) + if m.blastRadiusModel.status != taskStatusPending { + bits = append(bits, m.blastRadiusModel.View()) } - if m.tfPlanFinished { - bits = append(bits, m.processingHeader) - bits = append(bits, fmt.Sprintf(" %v", m.processingModel.View())) + if m.riskTask.status != taskStatusPending { + bits = append(bits, m.riskTask.View()) } - if m.changeUrl != "" && len(m.risks) == 0 { + if m.changeUrl != "" { for _, t := range m.riskMilestoneTasks { bits = append(bits, fmt.Sprintf(" %v", t.View())) } @@ -517,7 +538,7 @@ func (m tfPlanModel) processPlanCmd() tea.Msg { time.Sleep(time.Second) m.processing <- progressSnapshotMsg{newState: "fake processing"} time.Sleep(time.Second) - m.processing <- changeUpdatedMsg{url: "https://example.com"} + m.processing <- changeUpdatedMsg{url: "https://example.com/changes/abc"} time.Sleep(time.Second) m.processing <- processingActivityMsg{"Fake CalculateBlastRadiusResponse Status update: progress"}