Skip to content

Commit 4a33a18

Browse files
committed
pkg/workflows/sdk: add WorkflowSpec.FormatChart for mermaid flowcharts
1 parent 62ca3f7 commit 4a33a18

17 files changed

+742
-86
lines changed

Makefile

+4
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,7 @@ lint-workspace:
4747

4848
lint:
4949
@./script/lint.sh $(GOLANGCI_LINT_VERSION) "$(GOLANGCI_LINT_COMMON_OPTS)" $(GOLANGCI_LINT_DIRECTORY) "--new-from-rev=origin/main"
50+
51+
.PHONY: test-quiet
52+
test-quiet:
53+
go test ./... | grep -v "\[no test files\]" | grep -v "\(cached\)"

go.mod

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
module github.com/smartcontractkit/chainlink-common
22

3-
go 1.22.0
4-
5-
toolchain go1.22.7
3+
go 1.23
64

75
require (
86
github.com/andybalholm/brotli v1.1.0

pkg/capabilities/capabilities.go

+21
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package capabilities
22

33
import (
4+
"cmp"
45
"context"
56
"fmt"
67
"regexp"
@@ -53,6 +54,26 @@ func (c CapabilityType) IsValid() error {
5354
return fmt.Errorf("invalid capability type: %s", c)
5455
}
5556

57+
func (c CapabilityType) cmpOrder() int {
58+
switch c {
59+
case CapabilityTypeTrigger:
60+
return 0
61+
case CapabilityTypeAction:
62+
return 1
63+
case CapabilityTypeConsensus:
64+
return 2
65+
case CapabilityTypeTarget:
66+
return 3
67+
case CapabilityTypeUnknown:
68+
return 4
69+
default:
70+
return 5
71+
}
72+
}
73+
func (c CapabilityType) Compare(c2 CapabilityType) int {
74+
return cmp.Compare(c.cmpOrder(), c2.cmpOrder())
75+
}
76+
5677
// CapabilityResponse is a struct for the Execute response of a capability.
5778
type CapabilityResponse struct {
5879
Value *values.Map

pkg/workflows/dependency_graph.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ func BuildDependencyGraph(spec sdk.WorkflowSpec) (*DependencyGraph, error) {
129129
Graph: g,
130130
Triggers: triggerSteps,
131131
}
132-
return wf, err
132+
return wf, nil
133133
}
134134

135135
var (
+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package workflows
2+
3+
import (
4+
"cmp"
5+
"fmt"
6+
"maps"
7+
"slices"
8+
"strings"
9+
"text/template"
10+
11+
"github.com/smartcontractkit/chainlink-common/pkg/capabilities"
12+
"github.com/smartcontractkit/chainlink-common/pkg/workflows/sdk"
13+
)
14+
15+
func (g *DependencyGraph) FormatChart() (string, error) {
16+
var sb strings.Builder
17+
steps := slices.Clone(g.Spec.Triggers)
18+
steps = append(steps, g.Spec.Steps()...)
19+
slices.SortFunc(steps, func(a, b sdk.StepDefinition) int {
20+
return cmp.Or(
21+
a.CapabilityType.Compare(b.CapabilityType),
22+
cmp.Compare(a.Ref, b.Ref),
23+
cmp.Compare(a.ID, b.ID),
24+
)
25+
})
26+
preds, err := g.Graph.PredecessorMap()
27+
if err != nil {
28+
return "", fmt.Errorf("failed to get graph predecessors: %w", err)
29+
}
30+
type stepAndOutput struct {
31+
Step sdk.StepDefinition
32+
Inputs []string
33+
}
34+
nodes := make([]stepAndOutput, len(steps))
35+
for i, step := range steps {
36+
inputs := slices.Collect(maps.Keys(preds[step.Ref]))
37+
if step.CapabilityType != capabilities.CapabilityTypeTrigger {
38+
inputs = append(inputs, KeywordTrigger)
39+
}
40+
slices.Sort(inputs)
41+
nodes[i] = stepAndOutput{Step: step, Inputs: inputs}
42+
}
43+
err = tmpl.Execute(&sb, nodes)
44+
if err != nil {
45+
return "", err
46+
}
47+
return sb.String(), nil
48+
}
49+
50+
var tmpl = template.Must(template.New("").Funcs(map[string]any{
51+
"replace": strings.ReplaceAll,
52+
}).Parse(`flowchart
53+
{{ range $i, $e := . }}
54+
{{ $ref := .Step.Ref -}}
55+
{{ $id := replace .Step.ID "@" "[at]" -}}
56+
{{ $name := printf "%s<br><i>(%s)</i>" .Step.CapabilityType $id -}}
57+
{{ if .Step.Ref -}}
58+
{{ $name = printf "<b>%s</b><br>%s" .Step.Ref $name -}}
59+
{{ else -}}
60+
{{ $ref = printf "%s%d" "unnamed" $i -}}
61+
{{ end -}}
62+
{{ if eq .Step.CapabilityType "trigger" -}}
63+
{{ $ref }}[\"{{$name}}"/]
64+
{{ else if eq .Step.CapabilityType "consensus" -}}
65+
{{ $ref }}[["{{$name}}"]]
66+
{{ else if eq .Step.CapabilityType "target" -}}
67+
{{ $ref }}[/"{{$name}}"\]
68+
{{ else -}}
69+
{{ $ref }}["{{$name}}"]
70+
{{ end -}}
71+
{{ if .Step.Inputs.OutputRef -}}
72+
{{ .Step.Inputs.OutputRef }} --> {{ .Step.Ref }}
73+
{{ else -}}
74+
{{ range $out := .Inputs -}}
75+
{{ $out }} --> {{ $ref }}
76+
{{ end -}}
77+
{{ end -}}
78+
{{ end -}}
79+
`))

0 commit comments

Comments
 (0)