Skip to content

Commit

Permalink
Merge pull request #308 from overmindtech/blast-risks-progress
Browse files Browse the repository at this point in the history
Rework blast radius and risks progress display; other bugfixes
  • Loading branch information
DavidS-ovm authored May 17, 2024
2 parents fa9defb + 1e892ba commit 94c79fe
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 84 deletions.
5 changes: 3 additions & 2 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

"connectrpc.com/connect"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/google/uuid"
"github.com/overmindtech/cli/tracing"
"github.com/overmindtech/sdp-go"
Expand Down Expand Up @@ -233,9 +234,9 @@ Then enter the code:
// sp := createSpinner()
// output += sp.View() + " Waiting for confirmation..."
case Authenticated:
output = "✅ Authenticated successfully. Press any key to continue."
output = lipgloss.NewStyle().Foreground(ColorPalette.BgSuccess).Render("✔︎") + " Authenticated successfully. Press any key to continue."
case ErrorAuthenticating:
output = "⛔️ Unable to authenticate. Try again."
output = lipgloss.NewStyle().Foreground(ColorPalette.BgDanger).Render("x") + " Unable to authenticate. Try again."
}

return containerStyle.Render(output)
Expand Down
5 changes: 3 additions & 2 deletions cmd/tea_initialisesources.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"connectrpc.com/connect"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/huh"
"github.com/charmbracelet/lipgloss"
"github.com/overmindtech/sdp-go"
"github.com/spf13/viper"
"golang.org/x/oauth2"
Expand Down Expand Up @@ -251,10 +252,10 @@ func (m initialiseSourcesModel) View() string {
view += fmt.Sprintf("\n%v", m.profileInputForm.View())
}
if m.awsSourceRunning {
view += "\nAWS Source: running"
view += fmt.Sprintf("\n %v AWS Source: running", lipgloss.NewStyle().Foreground(ColorPalette.BgSuccess).Render("✔︎"))
}
if m.stdlibSourceRunning {
view += "\nstdlib Source: running"
view += fmt.Sprintf("\n %v stdlib Source: running", lipgloss.NewStyle().Foreground(ColorPalette.BgSuccess).Render("✔︎"))
}
return view
}
Expand Down
31 changes: 20 additions & 11 deletions cmd/tea_snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -44,25 +50,28 @@ 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
}

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)
}
}
4 changes: 0 additions & 4 deletions cmd/tea_taskmodel.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
32 changes: 16 additions & 16 deletions cmd/tea_terraform.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,22 +82,6 @@ func (m cmdModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {

batch := []tea.Cmd{}

// update the main command
var cmd tea.Cmd
m.cmd, cmd = m.cmd.Update(msg)
if cmd != nil {
batch = append(batch, cmd)
}

// pass all messages to all tasks
for k, t := range m.tasks {
tm, cmd := t.Update(msg)
m.tasks[k] = tm
if cmd != nil {
batch = append(batch, cmd)
}
}

// special case the messages that need to be handled at this level
switch msg := msg.(type) {
case tea.KeyMsg:
Expand Down Expand Up @@ -145,6 +129,22 @@ func (m cmdModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
skipView(m.View())
}

// update the main command
var cmd tea.Cmd
m.cmd, cmd = m.cmd.Update(msg)
if cmd != nil {
batch = append(batch, cmd)
}

// pass all messages to all tasks
for k, t := range m.tasks {
tm, cmd := t.Update(msg)
m.tasks[k] = tm
if cmd != nil {
batch = append(batch, cmd)
}
}

return m, tea.Batch(batch...)
}

Expand Down
4 changes: 2 additions & 2 deletions cmd/terraform_apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{},
}
}
Expand Down
116 changes: 69 additions & 47 deletions cmd/terraform_plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(),
)
}

Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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:
Expand All @@ -379,7 +378,7 @@ func (m tfPlanModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
for _, ms := range msg.riskMilestones {
tm := NewTaskModel(ms.GetDescription())
m.riskMilestoneTasks = append(m.riskMilestoneTasks, tm)
cmds = append(cmds, tm.spinner.Tick)
cmds = append(cmds, tm.Init())
}
}
for i, ms := range msg.riskMilestones {
Expand All @@ -393,25 +392,46 @@ func (m tfPlanModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.riskMilestoneTasks[i].status = taskStatusDone
case sdp.RiskCalculationStatus_ProgressMilestone_STATUS_INPROGRESS:
m.riskMilestoneTasks[i].status = taskStatusRunning
cmds = append(cmds, m.riskMilestoneTasks[i].spinner.Tick)
case sdp.RiskCalculationStatus_ProgressMilestone_STATUS_SKIPPED:
m.riskMilestoneTasks[i].status = taskStatusSkipped
}
}
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)
Expand All @@ -424,7 +444,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 {
Expand All @@ -447,16 +470,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()))
}
Expand Down Expand Up @@ -517,7 +539,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"}
Expand Down

0 comments on commit 94c79fe

Please sign in to comment.