From de8dfa8f4be7435400f72bf4f838df2b62de1d3f Mon Sep 17 00:00:00 2001 From: vmarcella Date: Fri, 30 Aug 2024 15:18:11 -0700 Subject: [PATCH 1/5] [refactor] input out of the model. --- internal/engine/interactive/input.go | 198 ++++++++++++++++++ .../interactive/{interactive.go => model.go} | 192 +---------------- 2 files changed, 200 insertions(+), 190 deletions(-) create mode 100644 internal/engine/interactive/input.go rename internal/engine/interactive/{interactive.go => model.go} (71%) diff --git a/internal/engine/interactive/input.go b/internal/engine/interactive/input.go new file mode 100644 index 00000000..b1fd932b --- /dev/null +++ b/internal/engine/interactive/input.go @@ -0,0 +1,198 @@ +package interactive + +import ( + "strconv" + + "github.com/Azure/InnovationEngine/internal/engine/common" + "github.com/Azure/InnovationEngine/internal/engine/environments" + "github.com/Azure/InnovationEngine/internal/lib" + "github.com/Azure/InnovationEngine/internal/logging" + "github.com/Azure/InnovationEngine/internal/patterns" + "github.com/charmbracelet/bubbles/key" + tea "github.com/charmbracelet/bubbletea" +) + +// All interactive mode inputs. +type InteractiveModeCommands struct { + execute key.Binding + executeAll key.Binding + executeMany key.Binding + next key.Binding + pause key.Binding + previous key.Binding + quit key.Binding +} + +// NewInteractiveModeCommands creates a new set of interactive mode commands. +func NewInteractiveModeCommands() InteractiveModeCommands { + return InteractiveModeCommands{ + execute: key.NewBinding( + key.WithKeys("e"), + key.WithHelp("e", "Execute the current command."), + ), + quit: key.NewBinding( + key.WithKeys("q"), + key.WithHelp("q", "Quit the scenario."), + ), + previous: key.NewBinding( + key.WithKeys("left"), + key.WithHelp("←", "Go to the previous command."), + ), + next: key.NewBinding( + key.WithKeys("right"), + key.WithHelp("→", "Go to the next command."), + ), + // Only enabled when in the azure environment. + executeAll: key.NewBinding( + key.WithKeys("a"), + key.WithHelp("a", "Execute all remaining commands."), + ), + executeMany: key.NewBinding( + key.WithKeys("m"), + key.WithHelp("m", "Execute the next commands."), + ), + pause: key.NewBinding( + key.WithKeys("p"), + key.WithHelp("p", "Pause execution of commands."), + ), + } +} + +func handleUserInput( + model InteractiveModeModel, + message tea.KeyMsg, +) (InteractiveModeModel, []tea.Cmd) { + var commands []tea.Cmd + + // If we're recording input for a multi-char command, + if model.recordingInput { + isNumber := lib.IsNumber(message.String()) + + // If the input is a number, append it to the recorded input. + if message.Type == tea.KeyRunes && isNumber { + model.recordedInput += message.String() + return model, commands + } + + // If the input is not a number, we'll stop recording input and reset + // the commands remaining to the recorded input. + if message.Type == tea.KeyEnter || !isNumber { + commandsRemaining, _ := strconv.Atoi(model.recordedInput) + + if commandsRemaining > len(model.codeBlockState)-model.currentCodeBlock { + commandsRemaining = len(model.codeBlockState) - model.currentCodeBlock + } + + logging.GlobalLogger.Debugf("Will execute the next %d steps", commandsRemaining) + model.stepsToBeExecuted = commandsRemaining + commands = append(commands, func() tea.Msg { + return tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'e'}} + }) + + model.recordingInput = false + model.recordedInput = "" + logging.GlobalLogger.Debugf( + "Recording input stopped and previously recorded input cleared.", + ) + return model, commands + } + } + + switch { + case key.Matches(message, model.commands.execute): + if model.executingCommand { + logging.GlobalLogger.Info("Command is already executing, ignoring execute command") + break + } + + // Prevent the user from executing a command if the previous command has + // not been executed successfully or executed at all. + previousCodeBlock := model.currentCodeBlock - 1 + if previousCodeBlock >= 0 { + previousCodeBlockState := model.codeBlockState[previousCodeBlock] + if !previousCodeBlockState.Success { + logging.GlobalLogger.Info( + "Previous command has not been executed successfully, ignoring execute command", + ) + break + } + } + + // Prevent the user from executing a command if the current command has + // already been executed successfully. + codeBlockState := model.codeBlockState[model.currentCodeBlock] + if codeBlockState.Success { + logging.GlobalLogger.Info( + "Command has already been executed successfully, ignoring execute command", + ) + break + } + + codeBlock := codeBlockState.CodeBlock + + model.executingCommand = true + + // If we're on the last step and the command is an SSH command, we need + // to report the status before executing the command. This is needed for + // one click deployments and does not affect the normal execution flow. + if model.currentCodeBlock == len(model.codeBlockState)-1 && + patterns.SshCommand.MatchString(codeBlock.Content) { + model.azureStatus.Status = "Succeeded" + environments.AttachResourceURIsToAzureStatus( + &model.azureStatus, + model.resourceGroupName, + model.environment, + ) + + commands = append(commands, tea.Sequence( + common.UpdateAzureStatus(model.azureStatus, model.environment), + func() tea.Msg { + return common.ExecuteCodeBlockSync(codeBlock, lib.CopyMap(model.env)) + })) + + } else { + commands = append(commands, common.ExecuteCodeBlockAsync( + codeBlock, + lib.CopyMap(model.env), + )) + } + + case key.Matches(message, model.commands.previous): + if model.executingCommand { + logging.GlobalLogger.Info("Command is already executing, ignoring execute command") + break + } + if model.currentCodeBlock > 0 { + model.currentCodeBlock-- + } + case key.Matches(message, model.commands.next): + if model.executingCommand { + logging.GlobalLogger.Info("Command is already executing, ignoring execute command") + break + } + if model.currentCodeBlock < len(model.codeBlockState)-1 { + model.currentCodeBlock++ + } + + case key.Matches(message, model.commands.quit): + commands = append(commands, tea.Quit) + + case key.Matches(message, model.commands.executeAll): + model.stepsToBeExecuted = len(model.codeBlockState) - model.currentCodeBlock + commands = append( + commands, + func() tea.Msg { + return tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'e'}} + }, + ) + case key.Matches(message, model.commands.executeMany): + model.recordingInput = true + case key.Matches(message, model.commands.pause): + if !model.executingCommand { + logging.GlobalLogger.Info("No command is currently executing, ignoring pause command") + } + model.stepsToBeExecuted = 0 + } + + return model, commands +} diff --git a/internal/engine/interactive/interactive.go b/internal/engine/interactive/model.go similarity index 71% rename from internal/engine/interactive/interactive.go rename to internal/engine/interactive/model.go index 83d67e70..1ecb5b79 100644 --- a/internal/engine/interactive/interactive.go +++ b/internal/engine/interactive/model.go @@ -2,14 +2,12 @@ package interactive import ( "fmt" - "strconv" "strings" "time" "github.com/Azure/InnovationEngine/internal/az" "github.com/Azure/InnovationEngine/internal/engine/common" "github.com/Azure/InnovationEngine/internal/engine/environments" - "github.com/Azure/InnovationEngine/internal/lib" "github.com/Azure/InnovationEngine/internal/logging" "github.com/Azure/InnovationEngine/internal/patterns" "github.com/Azure/InnovationEngine/internal/ui" @@ -22,17 +20,6 @@ import ( "github.com/charmbracelet/lipgloss" ) -// All interactive mode inputs. -type InteractiveModeCommands struct { - execute key.Binding - executeAll key.Binding - executeMany key.Binding - next key.Binding - pause key.Binding - previous key.Binding - quit key.Binding -} - type interactiveModeComponents struct { paginator paginator.Model stepViewport viewport.Model @@ -103,146 +90,6 @@ func initializeComponents(model InteractiveModeModel, width, height int) interac return components } -// Handle user input for interactive mode. -func handleUserInput( - model InteractiveModeModel, - message tea.KeyMsg, -) (InteractiveModeModel, []tea.Cmd) { - var commands []tea.Cmd - - // If we're recording input for a multi-char command, - if model.recordingInput { - isNumber := lib.IsNumber(message.String()) - - // If the input is a number, append it to the recorded input. - if message.Type == tea.KeyRunes && isNumber { - model.recordedInput += message.String() - return model, commands - } - - // If the input is not a number, we'll stop recording input and reset - // the commands remaining to the recorded input. - if message.Type == tea.KeyEnter || !isNumber { - commandsRemaining, _ := strconv.Atoi(model.recordedInput) - - if commandsRemaining > len(model.codeBlockState)-model.currentCodeBlock { - commandsRemaining = len(model.codeBlockState) - model.currentCodeBlock - } - - logging.GlobalLogger.Debugf("Will execute the next %d steps", commandsRemaining) - model.stepsToBeExecuted = commandsRemaining - commands = append(commands, func() tea.Msg { - return tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'e'}} - }) - - model.recordingInput = false - model.recordedInput = "" - logging.GlobalLogger.Debugf( - "Recording input stopped and previously recorded input cleared.", - ) - return model, commands - } - } - - switch { - case key.Matches(message, model.commands.execute): - if model.executingCommand { - logging.GlobalLogger.Info("Command is already executing, ignoring execute command") - break - } - - // Prevent the user from executing a command if the previous command has - // not been executed successfully or executed at all. - previousCodeBlock := model.currentCodeBlock - 1 - if previousCodeBlock >= 0 { - previousCodeBlockState := model.codeBlockState[previousCodeBlock] - if !previousCodeBlockState.Success { - logging.GlobalLogger.Info( - "Previous command has not been executed successfully, ignoring execute command", - ) - break - } - } - - // Prevent the user from executing a command if the current command has - // already been executed successfully. - codeBlockState := model.codeBlockState[model.currentCodeBlock] - if codeBlockState.Success { - logging.GlobalLogger.Info( - "Command has already been executed successfully, ignoring execute command", - ) - break - } - - codeBlock := codeBlockState.CodeBlock - - model.executingCommand = true - - // If we're on the last step and the command is an SSH command, we need - // to report the status before executing the command. This is needed for - // one click deployments and does not affect the normal execution flow. - if model.currentCodeBlock == len(model.codeBlockState)-1 && - patterns.SshCommand.MatchString(codeBlock.Content) { - model.azureStatus.Status = "Succeeded" - environments.AttachResourceURIsToAzureStatus( - &model.azureStatus, - model.resourceGroupName, - model.environment, - ) - - commands = append(commands, tea.Sequence( - common.UpdateAzureStatus(model.azureStatus, model.environment), - func() tea.Msg { - return common.ExecuteCodeBlockSync(codeBlock, lib.CopyMap(model.env)) - })) - - } else { - commands = append(commands, common.ExecuteCodeBlockAsync( - codeBlock, - lib.CopyMap(model.env), - )) - } - - case key.Matches(message, model.commands.previous): - if model.executingCommand { - logging.GlobalLogger.Info("Command is already executing, ignoring execute command") - break - } - if model.currentCodeBlock > 0 { - model.currentCodeBlock-- - } - case key.Matches(message, model.commands.next): - if model.executingCommand { - logging.GlobalLogger.Info("Command is already executing, ignoring execute command") - break - } - if model.currentCodeBlock < len(model.codeBlockState)-1 { - model.currentCodeBlock++ - } - - case key.Matches(message, model.commands.quit): - commands = append(commands, tea.Quit) - - case key.Matches(message, model.commands.executeAll): - model.stepsToBeExecuted = len(model.codeBlockState) - model.currentCodeBlock - commands = append( - commands, - func() tea.Msg { - return tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'e'}} - }, - ) - case key.Matches(message, model.commands.executeMany): - model.recordingInput = true - case key.Matches(message, model.commands.pause): - if !model.executingCommand { - logging.GlobalLogger.Info("No command is currently executing, ignoring pause command") - } - model.stepsToBeExecuted = 0 - } - - return model, commands -} - func (components *interactiveModeComponents) updateViewportHeight(terminalHeight int) { stepViewportPercent := 0.4 outputViewportPercent := 0.2 @@ -615,44 +462,9 @@ func NewInteractiveModeModel( ui.CommandPrompt(language) + codeBlockState[0].CodeBlock.Content, } - // Configure extra keybinds used for executing the many/all commands. - executeAllKeybind := key.NewBinding( - key.WithKeys("a"), - key.WithHelp("a", "Execute all remaining commands."), - ) - - executeManyKeybind := key.NewBinding( - key.WithKeys("m"), - key.WithHelp("m", "Execute the next commands."), - ) - pauseKeybind := key.NewBinding( - key.WithKeys("p", "Pause execution of commands."), - ) - return InteractiveModeModel{ - scenarioTitle: title, - commands: InteractiveModeCommands{ - execute: key.NewBinding( - key.WithKeys("e"), - key.WithHelp("e", "Execute the current command."), - ), - quit: key.NewBinding( - key.WithKeys("q"), - key.WithHelp("q", "Quit the scenario."), - ), - previous: key.NewBinding( - key.WithKeys("left"), - key.WithHelp("←", "Go to the previous command."), - ), - next: key.NewBinding( - key.WithKeys("right"), - key.WithHelp("→", "Go to the next command."), - ), - // Only enabled when in the azure environment. - executeAll: executeAllKeybind, - executeMany: executeManyKeybind, - pause: pauseKeybind, - }, + scenarioTitle: title, + commands: NewInteractiveModeCommands(), stepsToBeExecuted: 0, env: env, From 7576a82d623b90674d4b8d9a9988a7cdd748c13e Mon Sep 17 00:00:00 2001 From: vmarcella Date: Fri, 30 Aug 2024 17:55:37 -0700 Subject: [PATCH 2/5] [update] components to have it's own module and refactor the codeblock to use Status over Success for checking the execution state of codeblocks. --- internal/engine/common/codeblock.go | 18 ++++- internal/engine/interactive/components.go | 88 ++++++++++++++++++++ internal/engine/interactive/input.go | 4 +- internal/engine/interactive/model.go | 98 +++++------------------ internal/engine/test/model.go | 6 +- internal/engine/test/model_test.go | 8 +- 6 files changed, 132 insertions(+), 90 deletions(-) create mode 100644 internal/engine/interactive/components.go diff --git a/internal/engine/common/codeblock.go b/internal/engine/common/codeblock.go index 89360233..e9fd5098 100644 --- a/internal/engine/common/codeblock.go +++ b/internal/engine/common/codeblock.go @@ -2,6 +2,12 @@ package common import "github.com/Azure/InnovationEngine/internal/parsers" +const ( + STATUS_SUCCESS = "success" + STATUS_FAILURE = "failure" + STATUS_PENDING = "pending" +) + // State for the codeblock in interactive mode. Used to keep track of the // state of each codeblock. type StatefulCodeBlock struct { @@ -12,12 +18,20 @@ type StatefulCodeBlock struct { StdOut string `json:"stdOut"` StepName string `json:"stepName"` StepNumber int `json:"stepNumber"` - Success bool `json:"success"` + Status string `json:"success"` SimilarityScore float64 `json:"similarityScore"` } // Checks if a codeblock was executed by looking at the // output, errors, and if success is true. func (s StatefulCodeBlock) WasExecuted() bool { - return s.StdOut != "" || s.StdErr != "" || s.Error != nil || s.Success + return s.Status != STATUS_PENDING +} + +func (s StatefulCodeBlock) Succeeded() bool { + return s.Status == STATUS_SUCCESS +} + +func (s StatefulCodeBlock) Failed() bool { + return s.Status == STATUS_FAILURE } diff --git a/internal/engine/interactive/components.go b/internal/engine/interactive/components.go new file mode 100644 index 00000000..5bf9add2 --- /dev/null +++ b/internal/engine/interactive/components.go @@ -0,0 +1,88 @@ +package interactive + +import ( + "github.com/charmbracelet/bubbles/paginator" + "github.com/charmbracelet/bubbles/viewport" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" +) + +type interactiveModeComponents struct { + paginator paginator.Model + stepViewport viewport.Model + outputViewport viewport.Model + azureCLIViewport viewport.Model +} + +// Initializes the viewports for the interactive mode model. +func initializeComponents(model InteractiveModeModel, width, height int) interactiveModeComponents { + // paginator setup + p := paginator.New() + p.TotalPages = len(model.codeBlockState) + p.Type = paginator.Dots + // Dots + p.ActiveDot = lipgloss.NewStyle(). + Foreground(lipgloss.AdaptiveColor{Light: "235", Dark: "252"}). + Render("•") + p.InactiveDot = lipgloss.NewStyle(). + Foreground(lipgloss.AdaptiveColor{Light: "250", Dark: "238"}). + Render("•") + + p.KeyMap.PrevPage = model.commands.previous + p.KeyMap.NextPage = model.commands.next + + stepViewport := viewport.New(width, 4) + outputViewport := viewport.New(width, 2) + azureCLIViewport := viewport.New(width, height) + + components := interactiveModeComponents{ + paginator: p, + stepViewport: stepViewport, + outputViewport: outputViewport, + azureCLIViewport: azureCLIViewport, + } + + components.updateViewportHeight(height) + return components +} + +func (components *interactiveModeComponents) updateViewportHeight(terminalHeight int) { + stepViewportPercent := 0.4 + outputViewportPercent := 0.2 + stepViewportHeight := int(float64(terminalHeight) * stepViewportPercent) + outputViewportHeight := int(float64(terminalHeight) * outputViewportPercent) + + if stepViewportHeight < 4 { + stepViewportHeight = 4 + } + + if outputViewportHeight < 2 { + outputViewportHeight = 2 + } + + components.stepViewport.Height = stepViewportHeight + components.outputViewport.Height = outputViewportHeight + components.azureCLIViewport.Height = terminalHeight - 1 +} + +func updateComponents( + components interactiveModeComponents, + currentBlock int, + message tea.Msg, +) (interactiveModeComponents, []tea.Cmd) { + var commands []tea.Cmd + var command tea.Cmd + + components.paginator.Page = currentBlock + + components.stepViewport, command = components.stepViewport.Update(message) + commands = append(commands, command) + + components.outputViewport, command = components.outputViewport.Update(message) + commands = append(commands, command) + + components.azureCLIViewport, command = components.azureCLIViewport.Update(message) + commands = append(commands, command) + + return components, commands +} diff --git a/internal/engine/interactive/input.go b/internal/engine/interactive/input.go index b1fd932b..4b4c9551 100644 --- a/internal/engine/interactive/input.go +++ b/internal/engine/interactive/input.go @@ -110,7 +110,7 @@ func handleUserInput( previousCodeBlock := model.currentCodeBlock - 1 if previousCodeBlock >= 0 { previousCodeBlockState := model.codeBlockState[previousCodeBlock] - if !previousCodeBlockState.Success { + if !previousCodeBlockState.Succeeded() { logging.GlobalLogger.Info( "Previous command has not been executed successfully, ignoring execute command", ) @@ -121,7 +121,7 @@ func handleUserInput( // Prevent the user from executing a command if the current command has // already been executed successfully. codeBlockState := model.codeBlockState[model.currentCodeBlock] - if codeBlockState.Success { + if codeBlockState.Succeeded() { logging.GlobalLogger.Info( "Command has already been executed successfully, ignoring execute command", ) diff --git a/internal/engine/interactive/model.go b/internal/engine/interactive/model.go index 1ecb5b79..69b2d0da 100644 --- a/internal/engine/interactive/model.go +++ b/internal/engine/interactive/model.go @@ -13,20 +13,11 @@ import ( "github.com/Azure/InnovationEngine/internal/ui" "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/bubbles/paginator" - "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/glamour" "github.com/charmbracelet/lipgloss" ) -type interactiveModeComponents struct { - paginator paginator.Model - stepViewport viewport.Model - outputViewport viewport.Model - azureCLIViewport viewport.Model -} - type InteractiveModeModel struct { azureStatus environments.AzureDeploymentStatus codeBlockState map[int]common.StatefulCodeBlock @@ -58,57 +49,6 @@ func (model InteractiveModeModel) Init() tea.Cmd { })) } -// Initializes the viewports for the interactive mode model. -func initializeComponents(model InteractiveModeModel, width, height int) interactiveModeComponents { - // paginator setup - p := paginator.New() - p.TotalPages = len(model.codeBlockState) - p.Type = paginator.Dots - // Dots - p.ActiveDot = lipgloss.NewStyle(). - Foreground(lipgloss.AdaptiveColor{Light: "235", Dark: "252"}). - Render("•") - p.InactiveDot = lipgloss.NewStyle(). - Foreground(lipgloss.AdaptiveColor{Light: "250", Dark: "238"}). - Render("•") - - p.KeyMap.PrevPage = model.commands.previous - p.KeyMap.NextPage = model.commands.next - - stepViewport := viewport.New(width, 4) - outputViewport := viewport.New(width, 2) - azureCLIViewport := viewport.New(width, height) - - components := interactiveModeComponents{ - paginator: p, - stepViewport: stepViewport, - outputViewport: outputViewport, - azureCLIViewport: azureCLIViewport, - } - - components.updateViewportHeight(height) - return components -} - -func (components *interactiveModeComponents) updateViewportHeight(terminalHeight int) { - stepViewportPercent := 0.4 - outputViewportPercent := 0.2 - stepViewportHeight := int(float64(terminalHeight) * stepViewportPercent) - outputViewportHeight := int(float64(terminalHeight) * outputViewportPercent) - - if stepViewportHeight < 4 { - stepViewportHeight = 4 - } - - if outputViewportHeight < 2 { - outputViewportHeight = 2 - } - - components.stepViewport.Height = stepViewportHeight - components.outputViewport.Height = outputViewportHeight - components.azureCLIViewport.Height = terminalHeight - 1 -} - // Updates the intractive mode model func (model InteractiveModeModel) Update(message tea.Msg) (tea.Model, tea.Cmd) { var commands []tea.Cmd @@ -139,9 +79,11 @@ func (model InteractiveModeModel) Update(message tea.Msg) (tea.Model, tea.Cmd) { // Update the state of the codeblock which finished executing. codeBlockState := model.codeBlockState[step] + codeBlockState.StdOut = message.StdOut codeBlockState.StdErr = message.StdErr - codeBlockState.Success = true + codeBlockState.Status = common.STATUS_SUCCESS + model.codeBlockState[step] = codeBlockState logging.GlobalLogger.Infof("Finished executing:\n %s", codeBlockState.CodeBlock.Content) @@ -223,7 +165,7 @@ func (model InteractiveModeModel) Update(message tea.Msg) (tea.Model, tea.Cmd) { codeBlockState := model.codeBlockState[step] codeBlockState.StdOut = message.StdOut codeBlockState.StdErr = message.StdErr - codeBlockState.Success = false + codeBlockState.Status = common.STATUS_FAILURE model.codeBlockState[step] = codeBlockState model.CommandLines = append(model.CommandLines, codeBlockState.StdErr) @@ -309,27 +251,26 @@ func (model InteractiveModeModel) Update(message tea.Msg) (tea.Model, tea.Cmd) { renderedStepSection, ) - if block.Success { - model.components.outputViewport.SetContent(block.StdOut) + if block.WasExecuted() { + if block.Succeeded() { + model.components.outputViewport.SetContent(block.StdOut) + } else { + model.components.outputViewport.SetContent(block.StdErr) + } } else { - model.components.outputViewport.SetContent(block.StdErr) + model.components.outputViewport.SetContent("") } model.components.azureCLIViewport.SetContent(strings.Join(model.CommandLines, "\n")) // Update all the viewports and append resulting commands. - var command tea.Cmd - - model.components.paginator.Page = model.currentCodeBlock - - model.components.stepViewport, command = model.components.stepViewport.Update(message) - commands = append(commands, command) - - model.components.outputViewport, command = model.components.outputViewport.Update(message) - commands = append(commands, command) - - model.components.azureCLIViewport, command = model.components.azureCLIViewport.Update(message) - commands = append(commands, command) + updatedComponents, componentCommands := updateComponents( + model.components, + model.currentCodeBlock, + message, + ) + commands = append(commands, componentCommands...) + model.components = updatedComponents return model, tea.Batch(commands...) } @@ -449,7 +390,7 @@ func NewInteractiveModeModel( StdOut: "", StdErr: "", Error: nil, - Success: false, + Status: common.STATUS_PENDING, } totalCodeBlocks += 1 @@ -466,7 +407,6 @@ func NewInteractiveModeModel( scenarioTitle: title, commands: NewInteractiveModeCommands(), stepsToBeExecuted: 0, - env: env, subscription: subscription, resourceGroupName: "", diff --git a/internal/engine/test/model.go b/internal/engine/test/model.go index b3388300..ad2c3f68 100644 --- a/internal/engine/test/model.go +++ b/internal/engine/test/model.go @@ -116,7 +116,7 @@ func (model TestModeModel) Update(message tea.Msg) (tea.Model, tea.Cmd) { codeBlockState := model.codeBlockState[step] codeBlockState.StdOut = message.StdOut codeBlockState.StdErr = message.StdErr - codeBlockState.Success = true + codeBlockState.Status = common.STATUS_SUCCESS codeBlockState.SimilarityScore = message.SimilarityScore model.codeBlockState[step] = codeBlockState @@ -174,7 +174,7 @@ func (model TestModeModel) Update(message tea.Msg) (tea.Model, tea.Cmd) { codeBlockState.StdOut = message.StdOut codeBlockState.StdErr = message.StdErr codeBlockState.Error = message.Error - codeBlockState.Success = false + codeBlockState.Status = common.STATUS_FAILURE codeBlockState.SimilarityScore = message.SimilarityScore model.codeBlockState[step] = codeBlockState @@ -278,7 +278,7 @@ func NewTestModeModel( StdOut: "", StdErr: "", Error: nil, - Success: false, + Status: common.STATUS_PENDING, } totalCodeBlocks += 1 diff --git a/internal/engine/test/model_test.go b/internal/engine/test/model_test.go index 6bd025ff..129004ed 100644 --- a/internal/engine/test/model_test.go +++ b/internal/engine/test/model_test.go @@ -57,7 +57,7 @@ func TestTestModeModel(t *testing.T) { assert.Equal(t, "bash", state.CodeBlock.Language) assert.Equal(t, "header1", state.CodeBlock.Header) assert.Equal(t, "echo 'hello world'", state.CodeBlock.Content) - assert.Equal(t, false, state.Success) + assert.Equal(t, false, state.Failed()) }) t.Run( @@ -89,7 +89,7 @@ func TestTestModeModel(t *testing.T) { // Assert outputs of the executed block. assert.Equal(t, "hello world\n", executedBlock.StdOut) assert.Equal(t, "", executedBlock.StdErr) - assert.Equal(t, true, executedBlock.Success) + assert.Equal(t, true, executedBlock.Succeeded()) } }, ) @@ -124,7 +124,7 @@ func TestTestModeModel(t *testing.T) { // Assert outputs of the executed block. assert.Equal(t, "hello world\n", executedBlock.StdOut) assert.Equal(t, "", executedBlock.StdErr) - assert.Equal(t, true, executedBlock.Success) + assert.Equal(t, true, executedBlock.Succeeded()) } else { assert.Fail(t, "Model is not a TestModeModel") } @@ -188,7 +188,7 @@ func TestTestModeModel(t *testing.T) { // Assert outputs of the executed block. assert.Equal(t, "hello world\n", executedBlock.StdOut) assert.Equal(t, "", executedBlock.StdErr) - assert.Equal(t, true, executedBlock.Success) + assert.Equal(t, true, executedBlock.Succeeded()) } else { assert.Fail(t, "Model is not a TestModeModel") From 2844f92f622c73ea71b418d61575c535b1a0ec19 Mon Sep 17 00:00:00 2001 From: vmarcella Date: Fri, 30 Aug 2024 17:58:21 -0700 Subject: [PATCH 3/5] [add] comment explaining the status checks. --- internal/engine/interactive/model.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/engine/interactive/model.go b/internal/engine/interactive/model.go index 69b2d0da..f76b613a 100644 --- a/internal/engine/interactive/model.go +++ b/internal/engine/interactive/model.go @@ -251,6 +251,7 @@ func (model InteractiveModeModel) Update(message tea.Msg) (tea.Model, tea.Cmd) { renderedStepSection, ) + // Only set the output if the block was executed, otherwise reset it. if block.WasExecuted() { if block.Succeeded() { model.components.outputViewport.SetContent(block.StdOut) From c80134b8351bf52df8c8b4e1c5dd496ce53002fa Mon Sep 17 00:00:00 2001 From: vmarcella Date: Fri, 30 Aug 2024 18:09:15 -0700 Subject: [PATCH 4/5] [update] documentation to reflect the status change. --- docs/specs/test-reporting.md | 14 +++++++------- internal/engine/common/codeblock.go | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/specs/test-reporting.md b/docs/specs/test-reporting.md index 98a59c87..58381685 100644 --- a/docs/specs/test-reporting.md +++ b/docs/specs/test-reporting.md @@ -30,7 +30,7 @@ specific runs, and share the results with others. ## Requirements -- [x] The user can generate a test report by running `ie test //report=` +- [x] The user can generate a test report by running `ie test --report=` - [x] Reports capture the yaml metadata of the scenario. - [x] Reports store the variables declared in the scenario and their values. - [x] The report is generated in JSON format. @@ -43,7 +43,7 @@ specific runs, and share the results with others. - The report will be generated in JSON format, but in the future we may consider other formats like yaml or HTML. JSON format was chosen for v1 because it is easy to parse, and it is a common format for sharing data. -- Users must specify `//report=` to generate a report. If the path is not +- Users must specify `--report=` to generate a report. If the path is not specified, the report will not be generated. ### Report schema @@ -109,8 +109,8 @@ documentation about each field until then. "stepName": "First step", // The step number "stepNumber": 0, - // Whether the step was successful or not - "success": true, + // The status of the codeblock (success, failure, or pending if never executed). + "status": "success", // The computed similarity score of the output (between 0 - 1) "similarityScore": 0 }, @@ -133,7 +133,7 @@ documentation about each field until then. "stdOut": "", "stepName": "Second step", "stepNumber": 1, - "success": true, + "status": "success", "similarityScore": 0 } ] @@ -186,7 +186,7 @@ The output of the command above should look like this: "stdOut": "Hello, world!\n", "stepName": "First step", "stepNumber": 0, - "success": true, + "status": "success", "similarityScore": 1 }, { @@ -208,7 +208,7 @@ The output of the command above should look like this: "stdOut": "", "stepName": "Second step", "stepNumber": 1, - "success": true, + "status": "success", "similarityScore": 1 } ] diff --git a/internal/engine/common/codeblock.go b/internal/engine/common/codeblock.go index e9fd5098..ece04a5f 100644 --- a/internal/engine/common/codeblock.go +++ b/internal/engine/common/codeblock.go @@ -18,7 +18,7 @@ type StatefulCodeBlock struct { StdOut string `json:"stdOut"` StepName string `json:"stepName"` StepNumber int `json:"stepNumber"` - Status string `json:"success"` + Status string `json:"status"` SimilarityScore float64 `json:"similarityScore"` } From 9f06d82dd8f47432be106157d0e72290c8e75ae2 Mon Sep 17 00:00:00 2001 From: vmarcella Date: Fri, 30 Aug 2024 18:21:25 -0700 Subject: [PATCH 5/5] [update] test case to be consistent with others. --- internal/engine/test/model_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/engine/test/model_test.go b/internal/engine/test/model_test.go index 129004ed..23c1c62b 100644 --- a/internal/engine/test/model_test.go +++ b/internal/engine/test/model_test.go @@ -57,7 +57,7 @@ func TestTestModeModel(t *testing.T) { assert.Equal(t, "bash", state.CodeBlock.Language) assert.Equal(t, "header1", state.CodeBlock.Header) assert.Equal(t, "echo 'hello world'", state.CodeBlock.Content) - assert.Equal(t, false, state.Failed()) + assert.Equal(t, true, state.Succeeded()) }) t.Run(