Skip to content

Commit 0892600

Browse files
committed
refine workflowgraph core
1 parent 0920492 commit 0892600

11 files changed

Lines changed: 196 additions & 92 deletions

src/WorkflowGraph/ARCtrl.WorkflowGraph.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
</ItemGroup>
2020

2121
<ItemGroup>
22+
<PackageReference Include="DynamicObj" />
2223
<PackageReference Include="Siren" />
2324
</ItemGroup>
2425

src/WorkflowGraph/Builder.fs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -571,8 +571,7 @@ module Builder =
571571
addStepPorts state stepNodeId step
572572

573573
let runScope = GraphId.childScope scope $"{step.Id}/run"
574-
let resolvedRun, runNodeId = buildRunNode state runScope stepNodeId workflowFilePath step.Run
575-
step.Run <- resolvedRun
574+
let _, runNodeId = buildRunNode state runScope stepNodeId workflowFilePath step.Run
576575
addEdgeByType state EdgeKind.Calls stepNodeId runNodeId (Some "calls")
577576

578577
workflow.Steps

src/WorkflowGraph/README.md

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -37,22 +37,8 @@ let index = Adapters.ofInvestigation investigation
3737
## Output formats
3838

3939
```fsharp
40-
// Native outputs
4140
let mermaidText = WorkflowGraphSiren.toMermaid graph
4241
let markdown = WorkflowGraphSiren.toMarkdown graph
43-
44-
// Optional image conversion via external renderer callbacks
45-
// (for example Mermaid CLI or a rendering web service)
46-
let svgOpt = WorkflowGraphSiren.toSvgWith renderMermaidToSvg graph
47-
let pngBytesOpt = WorkflowGraphSiren.toPngBytesWith renderMermaidToPng graph
4842
```
4943

50-
Only Mermaid text and Markdown are produced directly by this library.
51-
SVG/PNG are callback-based extension points and require you to provide the renderer.
52-
53-
For .NET targets, save helpers are available:
54-
55-
- `WorkflowGraphSiren.saveMermaid`
56-
- `WorkflowGraphSiren.saveMarkdown`
57-
- `WorkflowGraphSiren.saveSvgWith`
58-
- `WorkflowGraphSiren.savePngWith`
44+
This library directly produces Mermaid text and Markdown-wrapped Mermaid output.

src/WorkflowGraph/WorkflowGraph.fs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,6 @@ module WorkflowGraphApi =
2727
let fromProcessingUnitToSiren = WorkflowGraphSiren.fromProcessingUnit
2828
let fromProcessingUnitToSirenResolved = WorkflowGraphSiren.fromProcessingUnitResolved
2929

30-
#if !FABLE_COMPILER
31-
let saveMermaid = WorkflowGraphSiren.saveMermaid
32-
let saveMarkdown = WorkflowGraphSiren.saveMarkdown
33-
let saveSvgWith = WorkflowGraphSiren.saveSvgWith
34-
let savePngWith = WorkflowGraphSiren.savePngWith
35-
#endif
36-
3730
let fromArcWorkflow = ofWorkflow
3831
let fromArcRun = ofRun
3932
let fromArcInvestigation = ofInvestigation

tests/WorkflowGraph/ARCtrl.WorkflowGraph.Tests.fsproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@
66
<GenerateProgramFile>false</GenerateProgramFile>
77
</PropertyGroup>
88
<ItemGroup>
9+
<Compile Include="TestHelpers.fs" />
910
<Compile Include="ReferenceParsing.Tests.fs" />
1011
<Compile Include="Builder.Tests.fs" />
1112
<Compile Include="Integration.Tests.fs" />
1213
<Compile Include="Adapters.Tests.fs" />
1314
<Compile Include="EdgeCases.Tests.fs" />
1415
<Compile Include="Visualization.Siren.Tests.fs" />
16+
<Compile Include="WorkflowGraph.Extensions.Tests.fs" />
1517
<Compile Include="Main.fs" />
1618
</ItemGroup>
1719
<ItemGroup>

tests/WorkflowGraph/Adapters.Tests.fs

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,10 @@ module Tests.Adapters
22

33
open ARCtrl
44
open ARCtrl.CWL
5-
open ARCtrl.FileSystem
65
open ARCtrl.WorkflowGraph
76
open TestingUtils
87
open CrossAsync
9-
10-
let private workflowFixturePath =
11-
ArcPathHelper.combineMany [|
12-
TestObjects.IO.testSimpleARCWithCWL
13-
"workflows"
14-
"ProteomIQon"
15-
"workflow.cwl"
16-
|]
17-
18-
let private runFixturePath =
19-
ArcPathHelper.combineMany [|
20-
TestObjects.IO.testSimpleARCWithCWL
21-
"runs"
22-
"tests"
23-
"run.cwl"
24-
|]
25-
26-
let private loadProcessingUnitFromPath (path: string) =
27-
crossAsync {
28-
let! content = FileSystemHelper.readFileTextAsync path
29-
return Decode.decodeCWLProcessingUnit content
30-
}
8+
open Tests.WorkflowGraphTestHelpers
319

3210
let tests_adapters =
3311
testList "Adapters" [

tests/WorkflowGraph/EdgeCases.Tests.fs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,68 @@ steps:
8282
Expect.sequenceEqual nodeIds1 nodeIds2 ""
8383
Expect.sequenceEqual edgeIds1 edgeIds2 ""
8484

85+
testCase "building same processing unit instance twice is deterministic" <| fun () ->
86+
let yaml = """cwlVersion: v1.2
87+
class: Workflow
88+
inputs: {}
89+
outputs: {}
90+
steps:
91+
step1:
92+
run: ./tool.cwl
93+
in: {}
94+
out: [out]"""
95+
let processingUnit = Decode.decodeCWLProcessingUnit yaml
96+
let resolvedTool = Decode.decodeCommandLineTool TestObjects.CWL.CommandLineTool.cwlFile
97+
let options =
98+
WorkflowGraphBuildOptions.defaultOptions
99+
|> WorkflowGraphBuildOptions.withRootWorkflowFilePath (Some "workflow.cwl")
100+
|> WorkflowGraphBuildOptions.withTryResolveRunPath (Some (fun path ->
101+
if path.EndsWith("tool.cwl") then Some (CommandLineTool resolvedTool) else None
102+
))
103+
104+
let graph1 = Builder.buildWith options processingUnit
105+
let graph2 = Builder.buildWith options processingUnit
106+
107+
let nodeIds1 = graph1.Nodes |> Seq.map (fun n -> n.Id) |> Seq.sort |> Seq.toArray
108+
let nodeIds2 = graph2.Nodes |> Seq.map (fun n -> n.Id) |> Seq.sort |> Seq.toArray
109+
let edgeIds1 = graph1.Edges |> Seq.map (fun e -> e.Id) |> Seq.sort |> Seq.toArray
110+
let edgeIds2 = graph2.Edges |> Seq.map (fun e -> e.Id) |> Seq.sort |> Seq.toArray
111+
112+
Expect.sequenceEqual nodeIds1 nodeIds2 ""
113+
Expect.sequenceEqual edgeIds1 edgeIds2 ""
114+
115+
testCase "buildWith does not mutate workflow step run payloads" <| fun () ->
116+
let yaml = """cwlVersion: v1.2
117+
class: Workflow
118+
inputs: {}
119+
outputs: {}
120+
steps:
121+
step1:
122+
run: ./tool.cwl
123+
in: {}
124+
out: [out]"""
125+
let processingUnit = Decode.decodeCWLProcessingUnit yaml
126+
let resolvedTool = Decode.decodeCommandLineTool TestObjects.CWL.CommandLineTool.cwlFile
127+
let options =
128+
WorkflowGraphBuildOptions.defaultOptions
129+
|> WorkflowGraphBuildOptions.withRootWorkflowFilePath (Some "workflow.cwl")
130+
|> WorkflowGraphBuildOptions.withTryResolveRunPath (Some (fun path ->
131+
if path.EndsWith("tool.cwl") then Some (CommandLineTool resolvedTool) else None
132+
))
133+
134+
let assertRunString () =
135+
match processingUnit with
136+
| Workflow wf ->
137+
match wf.Steps.[0].Run with
138+
| RunString runPath -> Expect.equal runPath "./tool.cwl" ""
139+
| other -> Expect.isTrue false $"Expected RunString but got %A{other}"
140+
| _ ->
141+
Expect.isTrue false "Expected workflow processing unit"
142+
143+
assertRunString ()
144+
let _graph = Builder.buildWith options processingUnit
145+
assertRunString ()
146+
85147
testCase "scatter fields do not break graph build and are preserved as metadata" <| fun () ->
86148
let graph = buildGraphFromYaml TestObjects.CWL.Workflow.workflowWithScatterAndScatterMethodFile
87149
let stepNodeOpt =

tests/WorkflowGraph/Integration.Tests.fs

Lines changed: 2 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,10 @@ module Tests.Integration
22

33
open ARCtrl
44
open ARCtrl.CWL
5-
open ARCtrl.FileSystem
65
open ARCtrl.WorkflowGraph
7-
open TestingUtils
86
open CrossAsync
9-
10-
let private workflowFixturePath =
11-
ArcPathHelper.combineMany [|
12-
TestObjects.IO.testSimpleARCWithCWL
13-
"workflows"
14-
"ProteomIQon"
15-
"workflow.cwl"
16-
|]
17-
18-
let private runFixturePath =
19-
ArcPathHelper.combineMany [|
20-
TestObjects.IO.testSimpleARCWithCWL
21-
"runs"
22-
"tests"
23-
"run.cwl"
24-
|]
7+
open TestingUtils
8+
open Tests.WorkflowGraphTestHelpers
259

2610
let private countEdges kind (graph: WorkflowGraph) =
2711
graph.Edges
@@ -33,33 +17,6 @@ let private countNodes predicate (graph: WorkflowGraph) =
3317
|> Seq.filter predicate
3418
|> Seq.length
3519

36-
let private loadProcessingUnitFromPath (path: string) =
37-
crossAsync {
38-
let! content = FileSystemHelper.readFileTextAsync path
39-
return Decode.decodeCWLProcessingUnit content
40-
}
41-
42-
let private buildRunResolverFromFixtures () =
43-
crossAsync {
44-
let! relativePaths = FileSystemHelper.getAllFilePathsAsync TestObjects.IO.testSimpleARCWithCWL
45-
let cwlRelativePaths =
46-
relativePaths
47-
|> Array.filter (fun p -> p.EndsWith(".cwl", System.StringComparison.OrdinalIgnoreCase))
48-
let! resolvedEntries =
49-
cwlRelativePaths
50-
|> Array.map (fun relativePath ->
51-
crossAsync {
52-
let absolutePath = ArcPathHelper.combine TestObjects.IO.testSimpleARCWithCWL relativePath
53-
let! content = FileSystemHelper.readFileTextAsync absolutePath
54-
let unit = Decode.decodeCWLProcessingUnit content
55-
return ArcPathHelper.normalizePathKey relativePath, unit
56-
}
57-
)
58-
|> CrossAsync.all
59-
let map = resolvedEntries |> Map.ofArray
60-
return fun (path: string) -> map |> Map.tryFind (ArcPathHelper.normalizePathKey path)
61-
}
62-
6320
let tests_workflowFixture =
6421
testList "workflow.cwl fixture" [
6522
testCaseCrossAsync "builds without diagnostics" (crossAsync {

tests/WorkflowGraph/Main.fs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ let all =
1111
Tests.Adapters.main
1212
Tests.EdgeCases.main
1313
Tests.VisualizationSiren.main
14+
Tests.WorkflowGraphExtensions.main
1415
]
1516

1617
#if !TESTS_ALL

tests/WorkflowGraph/TestHelpers.fs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
module Tests.WorkflowGraphTestHelpers
2+
3+
open ARCtrl
4+
open ARCtrl.CWL
5+
open ARCtrl.FileSystem
6+
open TestingUtils
7+
open CrossAsync
8+
9+
let workflowFixturePath =
10+
ArcPathHelper.combineMany [|
11+
TestObjects.IO.testSimpleARCWithCWL
12+
"workflows"
13+
"ProteomIQon"
14+
"workflow.cwl"
15+
|]
16+
17+
let runFixturePath =
18+
ArcPathHelper.combineMany [|
19+
TestObjects.IO.testSimpleARCWithCWL
20+
"runs"
21+
"tests"
22+
"run.cwl"
23+
|]
24+
25+
let loadProcessingUnitFromPath (path: string) =
26+
crossAsync {
27+
let! content = FileSystemHelper.readFileTextAsync path
28+
return Decode.decodeCWLProcessingUnit content
29+
}
30+
31+
let buildRunResolverFromFixtures () =
32+
crossAsync {
33+
let! relativePaths = FileSystemHelper.getAllFilePathsAsync TestObjects.IO.testSimpleARCWithCWL
34+
let cwlRelativePaths =
35+
relativePaths
36+
|> Array.filter (fun p -> p.EndsWith(".cwl", System.StringComparison.OrdinalIgnoreCase))
37+
let! resolvedEntries =
38+
cwlRelativePaths
39+
|> Array.map (fun relativePath ->
40+
crossAsync {
41+
let absolutePath = ArcPathHelper.combine TestObjects.IO.testSimpleARCWithCWL relativePath
42+
let! content = FileSystemHelper.readFileTextAsync absolutePath
43+
let processingUnit = Decode.decodeCWLProcessingUnit content
44+
return ArcPathHelper.normalizePathKey relativePath, processingUnit
45+
}
46+
)
47+
|> CrossAsync.all
48+
let map = resolvedEntries |> Map.ofArray
49+
return fun (path: string) -> map |> Map.tryFind (ArcPathHelper.normalizePathKey path)
50+
}

0 commit comments

Comments
 (0)