Skip to content

Commit eb4dc42

Browse files
Merge pull request #4 from actionforge/cleanup
Support for new web app editor console and fixes and code cleanup
2 parents b61a016 + fe0ae11 commit eb4dc42

17 files changed

Lines changed: 165 additions & 61 deletions

core/errors.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package core
33
import (
44
"bytes"
55
"database/sql"
6+
"encoding/json"
67
"errors"
78
"fmt"
89
"net"
@@ -176,10 +177,108 @@ func indentString(input string, indentSpaces int, numbering bool) string {
176177
return strings.Join(lines, "\n")
177178
}
178179

180+
const (
181+
// web-safe hex equivalents for the frontend
182+
WebColorRed = "#FF5555" // for color.FgRed
183+
WebColorYellow = "#FFFF55" // for color.FgYellow
184+
WebColorCyan = "#8BE9FD" // for color.FgCyan
185+
WebColorMagenta = "#FF79C6" // for color.FgMagenta
186+
)
187+
188+
type CtxMsg struct {
189+
Message string `json:"msg"`
190+
Depth int `json:"level"`
191+
FullPath string `json:"fullPath"`
192+
}
193+
194+
type WebErrorPayload struct {
195+
IsLeafError bool `json:"isLeafError"`
196+
Context []CtxMsg `json:"context,omitempty"`
197+
198+
Error string `json:"error"`
199+
ErrorColor string `json:"errorColor"`
200+
201+
Hint string `json:"hint,omitempty"`
202+
HintColor string `json:"hintColor,omitempty"`
203+
204+
StackTrace []string `json:"stackTrace,omitempty"`
205+
}
206+
179207
func (e *LeafError) Format(f fmt.State, c rune) {
180208
switch c {
181209
case 'v':
182210

211+
// Below is the JSON output formatting for the web editor console
212+
if f.Flag('#') {
213+
payload := WebErrorPayload{
214+
IsLeafError: true,
215+
Error: e.ErrorWithCauses(),
216+
ErrorColor: "#FF5555",
217+
}
218+
219+
if e.Context != nil && len(e.Context.Visited) > 0 {
220+
var previousNode NodeBaseInterface
221+
for _, item := range e.Context.Visited {
222+
currentNode := item.Node
223+
224+
// TODO: (Seb) improve this
225+
// In the logs, group nodes appear twice, once when entered, and once
226+
// when the group-outputs node comes back and executes the group node again.
227+
// While this is expected for the execution flow, we don't want to have that
228+
// in the logs, it looks very confusing.
229+
if previousNode != nil &&
230+
strings.HasPrefix(previousNode.GetNodeTypeId(), "core/group-outputs@") &&
231+
strings.HasPrefix(currentNode.GetNodeTypeId(), "core/group@") {
232+
233+
previousNode = currentNode
234+
continue
235+
}
236+
237+
nodeNameOrLabel := currentNode.GetLabel()
238+
if nodeNameOrLabel == "" {
239+
nodeNameOrLabel = currentNode.GetName()
240+
}
241+
242+
var msg string
243+
if item.Execute {
244+
msg = fmt.Sprintf("execute '%s'", nodeNameOrLabel)
245+
} else {
246+
msg = fmt.Sprintf("request input from '%s'", nodeNameOrLabel)
247+
}
248+
249+
payload.Context = append(payload.Context, CtxMsg{
250+
Message: msg,
251+
Depth: strings.Count(currentNode.GetFullPath(), "/") + 1,
252+
FullPath: currentNode.GetFullPath(),
253+
})
254+
255+
previousNode = item.Node
256+
}
257+
}
258+
259+
payload.Hint = getErrorHint(e)
260+
if payload.Hint != "" {
261+
payload.HintColor = "#FFFF55"
262+
}
263+
264+
if f.Flag('+') {
265+
rawStack := e.StackTrace()
266+
payload.StackTrace = strings.Split(rawStack, "\n")
267+
}
268+
269+
jsonBytes, err := json.Marshal(payload)
270+
if err != nil {
271+
// Fallback in case of JSON error
272+
fmt.Fprint(f, e.Error())
273+
return
274+
}
275+
276+
fmt.Fprint(f, string(jsonBytes))
277+
return
278+
}
279+
280+
// This below is the regular terminal output formatting
281+
183282
var (
184283
tmpErrEmoji string
185284
tmpHintEmoji string
@@ -245,6 +344,7 @@ func (e *LeafError) Format(f fmt.State, c rune) {
245344

246345
fmt.Fprint(f, output)
247346
return
347+
248348
case 's':
249349
fmt.Fprint(f, e.Error())
250350
}

core/graph.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -561,7 +561,6 @@ func LoadNode(parent NodeBaseInterface, parentId string, nodeData any, validate
561561
return nil, "", nil
562562
}
563563

564-
// 1. Check ID
565564
// We attempt to get the ID. If it fails, we record the error but CONTINUE
566565
// processing (if validating) to check Type, Inputs, and Outputs.
567566
id, idErr := utils.GetTypedPropertyByPath[string](nodeI, "id")
@@ -571,7 +570,6 @@ func LoadNode(parent NodeBaseInterface, parentId string, nodeData any, validate
571570
}
572571
}
573572

574-
// 2. Check Type
575573
// If Type is missing, loading "makes no sense" as we cannot select a factory.
576574
// We must early out here.
577575
nodeType, typeErr := utils.GetTypedPropertyByPath[string](nodeI, "type")
@@ -582,6 +580,8 @@ func LoadNode(parent NodeBaseInterface, parentId string, nodeData any, validate
582580
return nil, "", nil
583581
}
584582

583+
nodeLabel, _ := utils.GetTypedPropertyByPath[string](nodeI, "label")
584+
585585
var (
586586
n NodeBaseInterface
587587
factoryErrs []error
@@ -615,6 +615,9 @@ func LoadNode(parent NodeBaseInterface, parentId string, nodeData any, validate
615615
}
616616

617617
if idErr == nil {
618+
if nodeLabel != "" {
619+
n.SetLabel(nodeLabel)
620+
}
618621
n.SetId(id)
619622
if parentId != "" {
620623
n.SetFullPath(parentId + "/" + id)

core/inputs.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,7 @@ func (n *Inputs) GetInputValues() map[InputId]any {
324324

325325
func (n *Inputs) InputValueById(ec *ExecutionState, host NodeWithInputs, inputId InputId, inputArrayPortId *InputId) (value any, err error) {
326326
var finalValue any
327+
var valueFound bool
327328
var inputDef InputDefinition
328329
var inputDefExists bool
329330

@@ -343,12 +344,11 @@ func (n *Inputs) InputValueById(ec *ExecutionState, host NodeWithInputs, inputId
343344
dstIsGroupInputsNode := strings.HasPrefix(dataSource.DstNode.GetNodeTypeId(), "core/group-inputs@")
344345
regardCache := !srcIsGroupNode && !srcIsGroupInputsNode && !srcIsGroupOutputsNode && !dstIsGroupInputsNode
345346

346-
var ok bool
347347
if regardCache {
348-
finalValue, ok = ec.GetDataFromOutputCache(dataSource.SrcNode.GetCacheId(), outputCacheIdForCache, cacheType)
348+
finalValue, valueFound = ec.GetDataFromOutputCache(dataSource.SrcNode.GetCacheId(), outputCacheIdForCache, cacheType)
349349
}
350350

351-
if ok {
351+
if valueFound {
352352
if utils.GetLogLevel() == utils.LogLevelDebug {
353353
utils.LogOut.Debugf("PushNodeVisit: (cached) %s, execute: %t\n", dataSource.SrcNode.GetId(), false)
354354
}
@@ -385,6 +385,7 @@ func (n *Inputs) InputValueById(ec *ExecutionState, host NodeWithInputs, inputId
385385
}
386386
}
387387
finalValue = v
388+
valueFound = true
388389

389390
if regardCache {
390391
ec.CacheDataOutput(dataSource.SrcNode.GetCacheId(), outputCacheIdForCache, finalValue, cacheType)
@@ -400,10 +401,12 @@ func (n *Inputs) InputValueById(ec *ExecutionState, host NodeWithInputs, inputId
400401
inputDef, inputDefExists = n.inputDefs[inputId]
401402
}
402403

403-
if userExists && inputValue != nil {
404+
if userExists {
404405
finalValue = inputValue
406+
valueFound = true
405407
} else if inputDefExists && inputDef.Default != nil {
406408
finalValue = inputDef.Default
409+
valueFound = true
407410
} else if inputDefExists && inputDef.Required {
408411
return nil, CreateErr(ec, &ErrNoInputValue{}, "no value for input '%v' (%s)", inputDef.Name, inputId)
409412
} else if inputDefExists {
@@ -414,7 +417,7 @@ func (n *Inputs) InputValueById(ec *ExecutionState, host NodeWithInputs, inputId
414417
}
415418
}
416419

417-
if finalValue == nil {
420+
if !valueFound {
418421
if inputDefExists {
419422
return nil, CreateErr(ec, &ErrNoInputValue{}, "no value for input '%v' (%s)", inputDef.Name, inputId)
420423
}

core/outputs.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,17 +164,19 @@ func (n *Outputs) SetOutputValue(ec *ExecutionState, outputId OutputId, value an
164164
}
165165

166166
func isValueValidForOutput(value any, expectedType string) bool {
167+
168+
if expectedType == "any" || expectedType == "unknown" {
169+
return true
170+
}
171+
172+
// if its not unknown or any but nil then the value is not compatible
167173
if value == nil {
168174
return false
169175
}
170176

171177
valueType := reflect.TypeOf(value)
172178
kind := valueType.Kind()
173179

174-
if expectedType == "any" || expectedType == "unknown" {
175-
return true
176-
}
177-
178180
_, mappingExists := validKindsForExpectedType[expectedType]
179181
if mappingExists {
180182
_, valid := validKindsForExpectedType[expectedType][kind]

nodes/http@v1.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -105,14 +105,6 @@ func (n *HttpNode) ExecuteImpl(c *core.ExecutionState, inputId core.InputId, pre
105105
defer resp.Body.Close()
106106
}
107107

108-
// Ensure the input reader is closed in all cases.
109-
// If closing the reader fails without a prior error,
110-
// treat it as an error which is part of the connection op.
111-
err = utils.SafeCloseReader(reader)
112-
if err != nil && connErr == nil {
113-
connErr = err
114-
}
115-
116108
var dsf core.DataStreamFactory
117109

118110
var statusCode int

nodes/start@v1.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ func (n *StartNode) ExecuteEntry(c *core.ExecutionState, outputValues map[core.O
2424

2525
dsf := core.DataStreamFactory{
2626
Reader: os.Stdin,
27+
Length: -1,
2728
}
2829

2930
err := n.Outputs.SetOutputValue(c, ni.Core_start_v1_Output_stdin, dsf, core.SetOutputValueOpts{})

nodes/test@v1_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ func Test_InputValueById_Casting(t *testing.T) {
5858

5959
SAME_AS_OUTPUT := core.OutputId("")
6060

61+
var constReader io.Reader = strings.NewReader("hello")
62+
6163
tests := []struct {
6264
output any // the value for the output port
6365

@@ -93,7 +95,8 @@ func Test_InputValueById_Casting(t *testing.T) {
9395

9496
// test stream ports
9597
{core.DataStreamFactory{
96-
Reader: strings.NewReader("hello"),
98+
Reader: constReader,
99+
Length: core.GetReaderLength(constReader),
97100
}, ni.Core_test_v1_Output_output_stream_foo123, SAME_AS_OUTPUT, ni.Core_test_v1_Input_input_stream_foo123, core.DataStreamFactory{}, expectError{}},
98101
{"hello", ni.Core_test_v1_Output_output_string_foo123, SAME_AS_OUTPUT, ni.Core_test_v1_Input_input_stream_foo123, strings.NewReader(""), expectError{}},
99102
{"hello", ni.Core_test_v1_Output_output_string_foo123, SAME_AS_OUTPUT, ni.Core_test_v1_Input_input_stream_foo123, core.DataStreamFactory{}, expectError{}},

sessions/session.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -945,7 +945,7 @@ func runGraphFromConn(ctx context.Context, graphData string, opts core.RunOpts,
945945
// send final error, even if error lines were already streamed
946946
sendEncryptedJSON(ws, map[string]string{
947947
"type": MsgTypeJobError,
948-
"error": fmt.Sprintf("Graph execution failed: %v", runErr),
948+
"error": fmt.Sprintf("%#v", runErr),
949949
}, sharedKey)
950950
return // Exit, the deferred lock release will still run
951951
}

tests_e2e/references/reference_app.sh_l12

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ hint:
2323

2424
stack trace:
2525
github.com/actionforge/actrun-cli/core.RunGraphFromFile
26-
graph.go:1028
26+
graph.go:1031
2727
github.com/actionforge/actrun-cli/cmd.cmdRootRun
2828
cmd_root.go:175
2929
github.com/spf13/cobra.(*Command).execute

tests_e2e/references/reference_dir-walk.sh_l56

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,15 @@ github.com/actionforge/actrun-cli/nodes.(*WalkNode).ExecuteImpl
3636
github.com/actionforge/actrun-cli/core.(*Executions).Execute
3737
executions.go:56
3838
github.com/actionforge/actrun-cli/nodes.(*StartNode).ExecuteImpl
39-
start@v1.go:49
39+
start@v1.go:50
4040
github.com/actionforge/actrun-cli/nodes.(*StartNode).ExecuteEntry
41-
start@v1.go:44
41+
start@v1.go:45
4242
github.com/actionforge/actrun-cli/core.RunGraph
4343
graph.go:426
4444
github.com/actionforge/actrun-cli/core.RunGraphFromString
45-
graph.go:1013
45+
graph.go:1016
4646
github.com/actionforge/actrun-cli/core.RunGraphFromFile
47-
graph.go:1031
47+
graph.go:1034
4848
github.com/actionforge/actrun-cli/cmd.cmdRootRun
4949
cmd_root.go:175
5050
github.com/spf13/cobra.(*Command).execute

0 commit comments

Comments
 (0)