Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework blast radius and risks progress display; other bugfixes #308

Merged
merged 3 commits into from
May 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading