From b9fb0c8d38885cda9b4c9411592a15f3bc43304c Mon Sep 17 00:00:00 2001 From: David Schmitt Date: Mon, 24 Jun 2024 12:02:08 +0200 Subject: [PATCH 1/3] Fix alignment with status sigils --- cmd/tea_submitplan.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/tea_submitplan.go b/cmd/tea_submitplan.go index 323fa084..98d53ee0 100644 --- a/cmd/tea_submitplan.go +++ b/cmd/tea_submitplan.go @@ -239,7 +239,7 @@ func (m submitPlanModel) View() string { } if m.changeUrl != "" && m.riskTask.status != taskStatusDone && time.Since(m.risksStarted) > 1500*time.Millisecond { - bits = append(bits, fmt.Sprintf(" │ Check the blast radius graph while you wait:\n │ %v\n", m.changeUrl)) + bits = append(bits, fmt.Sprintf(" │ Check the blast radius graph while you wait:\n │ %v\n", m.changeUrl)) } return strings.Join(bits, "\n") + "\n" From e354c983b49ba2f54c188596edf0942e6e05d120 Mon Sep 17 00:00:00 2001 From: David Schmitt Date: Mon, 24 Jun 2024 12:08:05 +0200 Subject: [PATCH 2/3] Ensure that the view gets updated before fatalErrors are handled As the channel processing is on a goroutine, while return values are handled directly by tea, the view update from the last message on the channel often loses the race against the CLI terminating from a fatalError. This change moves all error reporting into the channel, so that messages get processed in strict order, allowing tea to update the View a last time before exiting. --- cmd/tea_submitplan.go | 37 +++++++++++++++++++++++++------------ cmd/terraform_apply.go | 36 +++++++++++++++++++++++------------- 2 files changed, 48 insertions(+), 25 deletions(-) diff --git a/cmd/tea_submitplan.go b/cmd/tea_submitplan.go index 98d53ee0..e2adcde5 100644 --- a/cmd/tea_submitplan.go +++ b/cmd/tea_submitplan.go @@ -464,8 +464,9 @@ func (m submitPlanModel) submitPlanCmd() tea.Msg { planJson, err := tfPlanJsonCmd.Output() if err != nil { m.processing <- submitPlanUpdateMsg{m.removingSecretsTask.UpdateStatusMsg(taskStatusError)} + m.processing <- submitPlanUpdateMsg{fatalError{err: fmt.Errorf("processPlanCmd: failed to convert terraform plan to JSON: %w", err)}} close(m.processing) - return fatalError{err: fmt.Errorf("processPlanCmd: failed to convert terraform plan to JSON: %w", err)} + return nil } m.processing <- submitPlanUpdateMsg{m.removingSecretsTask.UpdateStatusMsg(taskStatusDone)} @@ -480,8 +481,9 @@ func (m submitPlanModel) submitPlanCmd() tea.Msg { mappingResponse, err := mappedItemDiffsFromPlan(ctx, planJson, m.planFile, log.Fields{}) if err != nil { m.processing <- submitPlanUpdateMsg{m.resourceExtractionTask.UpdateStatusMsg(taskStatusError)} + m.processing <- submitPlanUpdateMsg{fatalError{err: fmt.Errorf("processPlanCmd: failed to parse terraform plan: %w", err)}} close(m.processing) - return fatalError{err: fmt.Errorf("processPlanCmd: failed to parse terraform plan: %w", err)} + return nil } m.processing <- submitPlanUpdateMsg{m.removingSecretsTask.UpdateTitleMsg( fmt.Sprintf("Removed %v secrets", mappingResponse.RemovedSecrets), @@ -512,8 +514,9 @@ func (m submitPlanModel) submitPlanCmd() tea.Msg { ticketLink, err = getTicketLinkFromPlan(m.planFile) if err != nil { m.processing <- submitPlanUpdateMsg{m.uploadChangesTask.UpdateStatusMsg(taskStatusError)} + m.processing <- submitPlanUpdateMsg{fatalError{err: fmt.Errorf("processPlanCmd: failed to get ticket link from plan: %w", err)}} close(m.processing) - return err + return nil } } @@ -521,8 +524,9 @@ func (m submitPlanModel) submitPlanCmd() tea.Msg { changeUuid, err := getChangeUuid(ctx, m.oi, sdp.ChangeStatus_CHANGE_STATUS_DEFINING, ticketLink, false) if err != nil { m.processing <- submitPlanUpdateMsg{m.uploadChangesTask.UpdateStatusMsg(taskStatusError)} + m.processing <- submitPlanUpdateMsg{fatalError{err: fmt.Errorf("processPlanCmd: failed searching for existing changes: %w", err)}} close(m.processing) - return fatalError{err: fmt.Errorf("processPlanCmd: failed searching for existing changes: %w", err)} + return nil } title := changeTitle(viper.GetString("title")) @@ -534,8 +538,9 @@ func (m submitPlanModel) submitPlanCmd() tea.Msg { tfPlanOutput, err := tfPlanTextCmd.Output() if err != nil { m.processing <- submitPlanUpdateMsg{m.removingSecretsTask.UpdateStatusMsg(taskStatusError)} + m.processing <- submitPlanUpdateMsg{fatalError{err: fmt.Errorf("processPlanCmd: failed to convert terraform plan to JSON: %w", err)}} close(m.processing) - return fatalError{err: fmt.Errorf("processPlanCmd: failed to convert terraform plan to JSON: %w", err)} + return nil } codeChangesOutput := tryLoadText(ctx, viper.GetString("code-changes-diff")) @@ -557,15 +562,17 @@ func (m submitPlanModel) submitPlanCmd() tea.Msg { }) if err != nil { m.processing <- submitPlanUpdateMsg{m.uploadChangesTask.UpdateStatusMsg(taskStatusError)} + m.processing <- submitPlanUpdateMsg{fatalError{err: fmt.Errorf("processPlanCmd: failed to create a new change: %w", err)}} close(m.processing) - return fatalError{err: fmt.Errorf("processPlanCmd: failed to create a new change: %w", err)} + return nil } maybeChangeUuid := createResponse.Msg.GetChange().GetMetadata().GetUUIDParsed() if maybeChangeUuid == nil { m.processing <- submitPlanUpdateMsg{m.uploadChangesTask.UpdateStatusMsg(taskStatusError)} + m.processing <- submitPlanUpdateMsg{fatalError{err: fmt.Errorf("processPlanCmd: failed to read change id: %w", err)}} close(m.processing) - return fatalError{err: fmt.Errorf("processPlanCmd: failed to read change id: %w", err)} + return nil } changeUuid = *maybeChangeUuid @@ -597,8 +604,9 @@ func (m submitPlanModel) submitPlanCmd() tea.Msg { }) if err != nil { m.processing <- submitPlanUpdateMsg{m.uploadChangesTask.UpdateStatusMsg(taskStatusError)} + m.processing <- submitPlanUpdateMsg{fatalError{err: fmt.Errorf("processPlanCmd: failed to update change: %w", err)}} close(m.processing) - return fatalError{err: fmt.Errorf("processPlanCmd: failed to update change: %w", err)} + return nil } } @@ -619,8 +627,9 @@ func (m submitPlanModel) submitPlanCmd() tea.Msg { }) if err != nil { m.processing <- submitPlanUpdateMsg{m.blastRadiusTask.UpdateStatusMsg(taskStatusError)} + m.processing <- submitPlanUpdateMsg{fatalError{err: fmt.Errorf("processPlanCmd: failed to update planned changes: %w", err)}} close(m.processing) - return fatalError{err: fmt.Errorf("processPlanCmd: failed to update planned changes: %w", err)} + return nil } last_log := time.Now() @@ -657,8 +666,9 @@ func (m submitPlanModel) submitPlanCmd() tea.Msg { } if resultStream.Err() != nil { m.processing <- submitPlanUpdateMsg{m.blastRadiusTask.UpdateStatusMsg(taskStatusError)} + m.processing <- submitPlanUpdateMsg{fatalError{err: fmt.Errorf("processPlanCmd: error streaming results: %w", err)}} close(m.processing) - return fatalError{err: fmt.Errorf("processPlanCmd: error streaming results: %w", err)} + return nil } m.processing <- submitPlanUpdateMsg{m.blastRadiusTask.FinishMsg()} @@ -680,8 +690,9 @@ func (m submitPlanModel) submitPlanCmd() tea.Msg { }) if err != nil { m.processing <- submitPlanUpdateMsg{m.riskTask.UpdateStatusMsg(taskStatusError)} + m.processing <- submitPlanUpdateMsg{fatalError{err: fmt.Errorf("processPlanCmd: failed to get change risks: %w", err)}} close(m.processing) - return fatalError{err: fmt.Errorf("processPlanCmd: failed to get change risks: %w", err)} + return nil } m.processing <- submitPlanUpdateMsg{changeUpdatedMsg{ @@ -700,9 +711,11 @@ func (m submitPlanModel) submitPlanCmd() tea.Msg { } if ctx.Err() != nil { + err := ctx.Err() m.processing <- submitPlanUpdateMsg{m.riskTask.UpdateStatusMsg(taskStatusError)} + m.processing <- submitPlanUpdateMsg{fatalError{err: fmt.Errorf("processPlanCmd: context cancelled: %w", err)}} close(m.processing) - return fatalError{err: fmt.Errorf("processPlanCmd: context cancelled: %w", ctx.Err())} + return nil } } diff --git a/cmd/terraform_apply.go b/cmd/terraform_apply.go index 09690b50..4fa147d4 100644 --- a/cmd/terraform_apply.go +++ b/cmd/terraform_apply.go @@ -21,10 +21,10 @@ import ( // terraformApplyCmd represents the `terraform apply` command var terraformApplyCmd = &cobra.Command{ - Use: "apply [overmind options...] -- [terraform options...]", - Short: "Runs `terraform apply` between two full system configuration snapshots for tracking. This will be automatically connected with the Change created by the `plan` command.", + Use: "apply [overmind options...] -- [terraform options...]", + Short: "Runs `terraform apply` between two full system configuration snapshots for tracking. This will be automatically connected with the Change created by the `plan` command.", PreRun: PreRunSetup, - Run: CmdWrapper("apply", []string{"explore:read", "changes:write", "config:write", "request:receive"}, NewTfApplyModel), + Run: CmdWrapper("apply", []string{"explore:read", "changes:write", "config:write", "request:receive"}, NewTfApplyModel), } type tfApplyModel struct { @@ -428,7 +428,8 @@ func (m tfApplyModel) startStartChangeCmd() tea.Cmd { m.startingChange <- m.startingChangeSnapshot.ProgressMsg(fmt.Sprintf("progress %v", i), uint32(i), uint32(i)) time.Sleep(time.Second) } - return m.startingChangeSnapshot.FinishMsg() + m.startingChange <- m.startingChangeSnapshot.FinishMsg() + return nil } var err error @@ -436,13 +437,15 @@ func (m tfApplyModel) startStartChangeCmd() tea.Cmd { if ticketLink == "" { ticketLink, err = getTicketLinkFromPlan(m.planFile) if err != nil { - return fatalError{err: err} + m.startingChange <- fatalError{err: err} + return nil } } changeUuid, err := getChangeUuid(ctx, oi, sdp.ChangeStatus_CHANGE_STATUS_DEFINING, ticketLink, true) if err != nil { - return fatalError{err: fmt.Errorf("failed to identify change: %w", err)} + m.startingChange <- fatalError{err: fmt.Errorf("failed to identify change: %w", err)} + return nil } m.startingChange <- changeIdentifiedMsg{uuid: changeUuid} @@ -455,7 +458,8 @@ func (m tfApplyModel) startStartChangeCmd() tea.Cmd { }, }) if err != nil { - return fatalError{err: fmt.Errorf("failed to start change: %w", err)} + m.startingChange <- fatalError{err: fmt.Errorf("failed to start change: %w", err)} + return nil } var msg *sdp.StartChangeResponse @@ -480,10 +484,12 @@ func (m tfApplyModel) startStartChangeCmd() tea.Cmd { m.startingChange <- m.startingChangeSnapshot.ProgressMsg(stateLabel, msg.GetNumItems(), msg.GetNumEdges()) } if startStream.Err() != nil { - return fatalError{err: fmt.Errorf("failed to process start change: %w", startStream.Err())} + m.startingChange <- fatalError{err: fmt.Errorf("failed to process start change: %w", startStream.Err())} + return nil } - return m.startingChangeSnapshot.FinishMsg() + m.endingChange <- m.startingChangeSnapshot.FinishMsg() + return nil } } @@ -506,7 +512,8 @@ func (m tfApplyModel) startEndChangeCmd() tea.Cmd { m.endingChange <- m.endingChangeSnapshot.ProgressMsg(fmt.Sprintf("progress %v", i), uint32(i), uint32(i)) time.Sleep(time.Second) } - return m.endingChangeSnapshot.FinishMsg() + m.endingChange <- m.endingChangeSnapshot.FinishMsg() + return nil } m.endingChange <- m.endingChangeSnapshot.StartMsg() @@ -518,7 +525,8 @@ func (m tfApplyModel) startEndChangeCmd() tea.Cmd { }, }) if err != nil { - return fatalError{err: fmt.Errorf("failed to end change: %w", err)} + m.endingChange <- fatalError{err: fmt.Errorf("failed to end change: %w", err)} + return nil } var msg *sdp.EndChangeResponse @@ -543,10 +551,12 @@ func (m tfApplyModel) startEndChangeCmd() tea.Cmd { m.endingChange <- m.endingChangeSnapshot.ProgressMsg(stateLabel, msg.GetNumItems(), msg.GetNumEdges()) } if endStream.Err() != nil { - return fatalError{err: fmt.Errorf("failed to process end change: %w", endStream.Err())} + m.endingChange <- fatalError{err: fmt.Errorf("failed to process end change: %w", endStream.Err())} + return nil } - return m.endingChangeSnapshot.FinishMsg() + m.endingChange <- m.endingChangeSnapshot.FinishMsg() + return nil } } From c41654385b14f16fbea4e928db7acb8c808c34a1 Mon Sep 17 00:00:00 2001 From: David Schmitt Date: Mon, 24 Jun 2024 12:29:31 +0200 Subject: [PATCH 3/3] Implement nicer message when risk calculation fails --- cmd/tea_submitplan.go | 86 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 70 insertions(+), 16 deletions(-) diff --git a/cmd/tea_submitplan.go b/cmd/tea_submitplan.go index e2adcde5..55594d9f 100644 --- a/cmd/tea_submitplan.go +++ b/cmd/tea_submitplan.go @@ -2,6 +2,7 @@ package cmd import ( "context" + "errors" "fmt" "os" "os/exec" @@ -238,7 +239,7 @@ func (m submitPlanModel) View() string { } } - if m.changeUrl != "" && m.riskTask.status != taskStatusDone && time.Since(m.risksStarted) > 1500*time.Millisecond { + if m.changeUrl != "" && m.riskTask.status != taskStatusDone && m.riskTask.status != taskStatusError && time.Since(m.risksStarted) > 1500*time.Millisecond { bits = append(bits, fmt.Sprintf(" │ Check the blast radius graph while you wait:\n │ %v\n", m.changeUrl)) } @@ -277,6 +278,20 @@ func (m submitPlanModel) waitForSubmitPlanActivity() tea.Msg { return <-m.processing } +func (m submitPlanModel) risksError(msg string, err error) tea.Msg { + if m.changeUrl == "" { + if err == nil { + return fatalError{err: errors.New(msg)} + } + return fatalError{err: fmt.Errorf("%v: %w", msg, err)} + } else { + if err == nil { + return fatalError{err: fmt.Errorf("%v\nWe'll retry in the background. Find the results online when they're done: %v", msg, m.changeUrl)} + } + return fatalError{err: fmt.Errorf("%v: %w\nWe'll retry in the background. Find the results online when they're done: %v", msg, err, m.changeUrl)} + } +} + func (m submitPlanModel) submitPlanCmd() tea.Msg { ctx := m.ctx span := trace.SpanFromContext(ctx) @@ -336,6 +351,8 @@ func (m submitPlanModel) submitPlanCmd() tea.Msg { time.Sleep(time.Second) m.processing <- submitPlanUpdateMsg{m.blastRadiusTask.FinishMsg()} + // update local copy + m.changeUrl = "https://example.com/changes/abc" m.processing <- submitPlanUpdateMsg{changeUpdatedMsg{url: "https://example.com/changes/abc"}} time.Sleep(time.Second) @@ -401,6 +418,31 @@ func (m submitPlanModel) submitPlanCmd() tea.Msg { }} time.Sleep(1500 * time.Millisecond) + if viper.GetString("ovm-test-fake") == "risks-error" { + m.processing <- submitPlanUpdateMsg{changeUpdatedMsg{ + url: "https://example.com/changes/abc", + riskMilestones: []*sdp.RiskCalculationStatus_ProgressMilestone{ + { + Description: "fake done milestone", + Status: sdp.RiskCalculationStatus_ProgressMilestone_STATUS_DONE, + }, + { + Description: "fake inprogress milestone", + Status: sdp.RiskCalculationStatus_ProgressMilestone_STATUS_DONE, + }, + { + Description: "fake errored milestone", + Status: sdp.RiskCalculationStatus_ProgressMilestone_STATUS_ERROR, + }, + }, + risks: []*sdp.Risk{}, + }} + + m.processing <- submitPlanUpdateMsg{m.riskTask.UpdateStatusMsg(taskStatusError)} + m.processing <- submitPlanUpdateMsg{m.risksError("initial risk calculation errored", nil)} + close(m.processing) + return nil + } high := uuid.New() medium := uuid.New() low := uuid.New() @@ -464,7 +506,7 @@ func (m submitPlanModel) submitPlanCmd() tea.Msg { planJson, err := tfPlanJsonCmd.Output() if err != nil { m.processing <- submitPlanUpdateMsg{m.removingSecretsTask.UpdateStatusMsg(taskStatusError)} - m.processing <- submitPlanUpdateMsg{fatalError{err: fmt.Errorf("processPlanCmd: failed to convert terraform plan to JSON: %w", err)}} + m.processing <- submitPlanUpdateMsg{m.risksError("failed to convert terraform plan to JSON", err)} close(m.processing) return nil } @@ -481,7 +523,7 @@ func (m submitPlanModel) submitPlanCmd() tea.Msg { mappingResponse, err := mappedItemDiffsFromPlan(ctx, planJson, m.planFile, log.Fields{}) if err != nil { m.processing <- submitPlanUpdateMsg{m.resourceExtractionTask.UpdateStatusMsg(taskStatusError)} - m.processing <- submitPlanUpdateMsg{fatalError{err: fmt.Errorf("processPlanCmd: failed to parse terraform plan: %w", err)}} + m.processing <- submitPlanUpdateMsg{m.risksError("failed to parse terraform plan", err)} close(m.processing) return nil } @@ -514,7 +556,7 @@ func (m submitPlanModel) submitPlanCmd() tea.Msg { ticketLink, err = getTicketLinkFromPlan(m.planFile) if err != nil { m.processing <- submitPlanUpdateMsg{m.uploadChangesTask.UpdateStatusMsg(taskStatusError)} - m.processing <- submitPlanUpdateMsg{fatalError{err: fmt.Errorf("processPlanCmd: failed to get ticket link from plan: %w", err)}} + m.processing <- submitPlanUpdateMsg{m.risksError("failed to get ticket link from plan", err)} close(m.processing) return nil } @@ -524,7 +566,7 @@ func (m submitPlanModel) submitPlanCmd() tea.Msg { changeUuid, err := getChangeUuid(ctx, m.oi, sdp.ChangeStatus_CHANGE_STATUS_DEFINING, ticketLink, false) if err != nil { m.processing <- submitPlanUpdateMsg{m.uploadChangesTask.UpdateStatusMsg(taskStatusError)} - m.processing <- submitPlanUpdateMsg{fatalError{err: fmt.Errorf("processPlanCmd: failed searching for existing changes: %w", err)}} + m.processing <- submitPlanUpdateMsg{m.risksError("failed searching for existing changes", err)} close(m.processing) return nil } @@ -538,7 +580,7 @@ func (m submitPlanModel) submitPlanCmd() tea.Msg { tfPlanOutput, err := tfPlanTextCmd.Output() if err != nil { m.processing <- submitPlanUpdateMsg{m.removingSecretsTask.UpdateStatusMsg(taskStatusError)} - m.processing <- submitPlanUpdateMsg{fatalError{err: fmt.Errorf("processPlanCmd: failed to convert terraform plan to JSON: %w", err)}} + m.processing <- submitPlanUpdateMsg{m.risksError("failed to convert terraform plan to JSON", err)} close(m.processing) return nil } @@ -562,7 +604,7 @@ func (m submitPlanModel) submitPlanCmd() tea.Msg { }) if err != nil { m.processing <- submitPlanUpdateMsg{m.uploadChangesTask.UpdateStatusMsg(taskStatusError)} - m.processing <- submitPlanUpdateMsg{fatalError{err: fmt.Errorf("processPlanCmd: failed to create a new change: %w", err)}} + m.processing <- submitPlanUpdateMsg{m.risksError("failed to create a new change", err)} close(m.processing) return nil } @@ -570,7 +612,7 @@ func (m submitPlanModel) submitPlanCmd() tea.Msg { maybeChangeUuid := createResponse.Msg.GetChange().GetMetadata().GetUUIDParsed() if maybeChangeUuid == nil { m.processing <- submitPlanUpdateMsg{m.uploadChangesTask.UpdateStatusMsg(taskStatusError)} - m.processing <- submitPlanUpdateMsg{fatalError{err: fmt.Errorf("processPlanCmd: failed to read change id: %w", err)}} + m.processing <- submitPlanUpdateMsg{m.risksError("failed to read change id", err)} close(m.processing) return nil } @@ -604,7 +646,7 @@ func (m submitPlanModel) submitPlanCmd() tea.Msg { }) if err != nil { m.processing <- submitPlanUpdateMsg{m.uploadChangesTask.UpdateStatusMsg(taskStatusError)} - m.processing <- submitPlanUpdateMsg{fatalError{err: fmt.Errorf("processPlanCmd: failed to update change: %w", err)}} + m.processing <- submitPlanUpdateMsg{m.risksError("failed to update change", err)} close(m.processing) return nil } @@ -627,7 +669,7 @@ func (m submitPlanModel) submitPlanCmd() tea.Msg { }) if err != nil { m.processing <- submitPlanUpdateMsg{m.blastRadiusTask.UpdateStatusMsg(taskStatusError)} - m.processing <- submitPlanUpdateMsg{fatalError{err: fmt.Errorf("processPlanCmd: failed to update planned changes: %w", err)}} + m.processing <- submitPlanUpdateMsg{m.risksError("failed to update planned changes", err)} close(m.processing) return nil } @@ -666,7 +708,7 @@ func (m submitPlanModel) submitPlanCmd() tea.Msg { } if resultStream.Err() != nil { m.processing <- submitPlanUpdateMsg{m.blastRadiusTask.UpdateStatusMsg(taskStatusError)} - m.processing <- submitPlanUpdateMsg{fatalError{err: fmt.Errorf("processPlanCmd: error streaming results: %w", err)}} + m.processing <- submitPlanUpdateMsg{m.risksError("error streaming results", err)} close(m.processing) return nil } @@ -676,12 +718,15 @@ func (m submitPlanModel) submitPlanCmd() tea.Msg { changeUrl.Path = fmt.Sprintf("%v/changes/%v/blast-radius", changeUrl.Path, changeUuid) log.WithField("change-url", changeUrl.String()).Info("Change ready") - m.processing <- submitPlanUpdateMsg{changeUpdatedMsg{url: changeUrl.String()}} + // update local copy + m.changeUrl = changeUrl.String() + m.processing <- submitPlanUpdateMsg{changeUpdatedMsg{url: m.changeUrl}} /////////////////////////////////////////////////////////////////// // wait for risk calculation to happen /////////////////////////////////////////////////////////////////// m.processing <- submitPlanUpdateMsg{m.riskTask.UpdateStatusMsg(taskStatusRunning)} + risksErrored := false for { riskRes, err := client.GetChangeRisks(ctx, &connect.Request[sdp.GetChangeRisksRequest]{ Msg: &sdp.GetChangeRisksRequest{ @@ -690,13 +735,13 @@ func (m submitPlanModel) submitPlanCmd() tea.Msg { }) if err != nil { m.processing <- submitPlanUpdateMsg{m.riskTask.UpdateStatusMsg(taskStatusError)} - m.processing <- submitPlanUpdateMsg{fatalError{err: fmt.Errorf("processPlanCmd: failed to get change risks: %w", err)}} + m.processing <- submitPlanUpdateMsg{m.risksError("failed to get change risks", err)} close(m.processing) return nil } m.processing <- submitPlanUpdateMsg{changeUpdatedMsg{ - url: changeUrl.String(), + url: m.changeUrl, riskMilestones: riskRes.Msg.GetChangeRiskMetadata().GetRiskCalculationStatus().GetProgressMilestones(), risks: riskRes.Msg.GetChangeRiskMetadata().GetRisks(), }} @@ -705,19 +750,28 @@ func (m submitPlanModel) submitPlanCmd() tea.Msg { if status == sdp.RiskCalculationStatus_STATUS_UNSPECIFIED || status == sdp.RiskCalculationStatus_STATUS_INPROGRESS { time.Sleep(time.Second) // retry + } else if status == sdp.RiskCalculationStatus_STATUS_ERROR { + risksErrored = true + break } else { - // it's done (or errored) + // it's done break } if ctx.Err() != nil { err := ctx.Err() m.processing <- submitPlanUpdateMsg{m.riskTask.UpdateStatusMsg(taskStatusError)} - m.processing <- submitPlanUpdateMsg{fatalError{err: fmt.Errorf("processPlanCmd: context cancelled: %w", err)}} + m.processing <- submitPlanUpdateMsg{m.risksError("context cancelled", err)} close(m.processing) return nil } + } + if risksErrored { + m.processing <- submitPlanUpdateMsg{m.riskTask.UpdateStatusMsg(taskStatusError)} + m.processing <- submitPlanUpdateMsg{m.risksError("initial risk calculation errored", nil)} + close(m.processing) + return nil } m.processing <- submitPlanUpdateMsg{m.riskTask.UpdateStatusMsg(taskStatusDone)}