package dashboardexecute import ( "context" "fmt" "sync" "github.com/turbot/steampipe/pkg/dashboard/dashboardevents" "github.com/turbot/steampipe/pkg/dashboard/dashboardtypes" "github.com/turbot/steampipe/pkg/db/db_common" "github.com/turbot/steampipe/pkg/workspace" ) type DashboardExecutor struct { // map of executions, keyed by session id executions map[string]*DashboardExecutionTree executionLock sync.Mutex } func newDashboardExecutor() *DashboardExecutor { return &DashboardExecutor{ executions: make(map[string]*DashboardExecutionTree), } } var Executor = newDashboardExecutor() func (e *DashboardExecutor) ExecuteDashboard(ctx context.Context, sessionId, dashboardName string, inputs map[string]interface{}, workspace *workspace.Workspace, client db_common.Client) (err error) { var executionTree *DashboardExecutionTree defer func() { // if there was an error executing, send an ExecutionError event if err != nil { errorEvent := &dashboardevents.ExecutionError{ Error: err, Session: sessionId, } workspace.PublishDashboardEvent(errorEvent) } }() // reset any existing executions for this session e.CancelExecutionForSession(ctx, sessionId) // now create a new execution executionTree, err = NewDashboardExecutionTree(dashboardName, sessionId, client, workspace) if err != nil { return err } // add to execution map e.setExecution(sessionId, executionTree) // if inputs have been passed, set them first if len(inputs) > 0 { executionTree.SetInputs(inputs) } go executionTree.Execute(ctx) return nil } func (e *DashboardExecutor) OnInputChanged(ctx context.Context, sessionId string, inputs map[string]interface{}, changedInput string) error { // find the execution executionTree, found := e.executions[sessionId] if !found { return fmt.Errorf("no dashboard running for session %s", sessionId) } // get the previous value of this input inputPrevValue := executionTree.inputValues[changedInput] // first see if any other inputs rely on the one which was just changed clearedInputs := e.clearDependentInputs(executionTree.Root, changedInput, inputs) if len(clearedInputs) > 0 { event := &dashboardevents.InputValuesCleared{ ClearedInputs: clearedInputs, Session: executionTree.sessionId, ExecutionId: executionTree.id, } executionTree.workspace.PublishDashboardEvent(event) } // if there are any dependent inputs, set their value to nil and send an event to the UI // if the dashboard run is complete, just re-execute if executionTree.GetRunStatus() == dashboardtypes.DashboardRunComplete || inputPrevValue != nil { return e.ExecuteDashboard( ctx, sessionId, executionTree.dashboardName, inputs, executionTree.workspace, executionTree.client) } // set the inputs executionTree.SetInputs(inputs) return nil } func (e *DashboardExecutor) clearDependentInputs(root dashboardtypes.DashboardNodeRun, changedInput string, inputs map[string]interface{}) []string { dependentInputs := root.GetInputsDependingOn(changedInput) clearedInputs := dependentInputs if len(dependentInputs) > 0 { for _, inputName := range dependentInputs { if inputs[inputName] != nil { // clear the input value inputs[inputName] = nil childDependentInputs := e.clearDependentInputs(root, inputName, inputs) clearedInputs = append(clearedInputs, childDependentInputs...) } } } return clearedInputs } func (e *DashboardExecutor) CancelExecutionForSession(_ context.Context, sessionId string) { // find the execution executionTree, found := e.getExecution(sessionId) if !found { // nothing to do return } // cancel if in progress executionTree.Cancel() // remove from execution tree e.removeExecution(sessionId) } // find the execution for the given session id func (e *DashboardExecutor) getExecution(sessionId string) (*DashboardExecutionTree, bool) { e.executionLock.Lock() defer e.executionLock.Unlock() executionTree, found := e.executions[sessionId] return executionTree, found } func (e *DashboardExecutor) setExecution(sessionId string, executionTree *DashboardExecutionTree) { e.executionLock.Lock() defer e.executionLock.Unlock() e.executions[sessionId] = executionTree } func (e *DashboardExecutor) removeExecution(sessionId string) { e.executionLock.Lock() defer e.executionLock.Unlock() delete(e.executions, sessionId) }