Skip to content

Commit b19ee54

Browse files
committed
Hardening graph editor + docs for release + upload playwright artifacts on failure
1 parent 5ebe5f0 commit b19ee54

18 files changed

Lines changed: 468 additions & 57 deletions

.github/workflows/CI.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,12 @@ jobs:
102102
- name: Install frontend dependencies
103103
working-directory: frontend
104104
run: npm ci
105+
- name: Run frontend unit tests
106+
working-directory: frontend
107+
run: npm test
108+
- name: Typecheck graph editor frontend
109+
working-directory: frontend
110+
run: npm run typecheck
105111
- name: Build graph editor frontend
106112
working-directory: frontend
107113
run: npm run build
@@ -111,3 +117,11 @@ jobs:
111117
- name: Run graph editor E2E tests
112118
working-directory: frontend
113119
run: npm run test:e2e
120+
- name: Upload Playwright artifacts
121+
if: failure()
122+
uses: actions/upload-artifact@v4
123+
with:
124+
name: graph-editor-playwright-artifacts
125+
path: |
126+
frontend/playwright-report/
127+
frontend/test-results/

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ test/Manifest.toml
88
docs/build/
99
benchmark/Manifest.toml
1010
frontend/node_modules/
11-
frontend/dist/
11+
docs/src/www/simple_dependency_graph.html

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
1515
Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a"
1616
MultiScaleTreeGraph = "dd4a991b-8a45-4075-bede-262ee62d5583"
1717
PlantMeteo = "4630fe09-e0fb-4da5-a846-781cb73437b6"
18+
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
1819
SHA = "ea8e919c-243c-51af-8825-aaa63cd721ce"
1920
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
2021
Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
@@ -39,6 +40,7 @@ JSON = "1"
3940
Markdown = "1.10"
4041
MultiScaleTreeGraph = "0.15.1"
4142
PlantMeteo = "0.8.2"
43+
Random = "1.10"
4244
SHA = "0.7.0"
4345
Statistics = "1.10"
4446
Tables = "1"

docs/make.jl

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,27 @@
11
#using Pkg
22
#Pkg.develop("PlantSimEngine")
33
using PlantSimEngine
4+
using PlantSimEngine.Examples
45
using PlantMeteo
56
using DataFrames, CSV
67
using Documenter
78
using CairoMakie
89

910
DocMeta.setdocmeta!(PlantSimEngine, :DocTestSetup, :(using PlantSimEngine, PlantMeteo, DataFrames, CSV, CairoMakie); recursive=true)
1011

12+
function build_graph_viewer_example()
13+
mapping = ModelMapping(
14+
ToyDegreeDaysCumulModel(),
15+
ToyLAIModel(),
16+
Beer(0.5),
17+
)
18+
path = joinpath(@__DIR__, "src", "www", "simple_dependency_graph.html")
19+
write_graph_view(path, mapping)
20+
return nothing
21+
end
22+
23+
build_graph_viewer_example()
24+
1125
makedocs(;
1226
modules=[PlantSimEngine],
1327
authors="Rémi Vezy <VEZY@users.noreply.github.com> and contributors",
@@ -30,9 +44,9 @@ makedocs(;
3044
"Key Concepts" => "./prerequisites/key_concepts.md",
3145
"Julia language basics" => "./prerequisites/julia_basics.md",
3246
],
33-
"Step by step - Single-scale simulations" => [
34-
"Detailed first simulation" => "./step_by_step/detailed_first_example.md",
35-
"Coupling" => "./step_by_step/simple_model_coupling.md",
47+
"Getting Started" => [
48+
"First simulation" => "./step_by_step/detailed_first_example.md",
49+
"Model Coupling" => "./step_by_step/simple_model_coupling.md",
3650
"Model Switching" => "./step_by_step/model_switching.md",
3751
"Graph visualization and editing" => "./step_by_step/graph_visualization_editor.md",
3852
"Quick examples" => "./step_by_step/quick_and_dirty_examples.md",

docs/src/index.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,16 @@ Depth = 5
4949

5050
**Why choose PlantSimEngine?**
5151

52-
- **Simplicity**: Write less code, focus on your model's logic, and let the framework handle the rest.
52+
- **Simplicity**: Write less code, focus on your model's logic, and let the framework handle the rest. You can also inspect and edit model coupling directly from the graph view:
53+
54+
```@raw html
55+
<iframe
56+
src="www/simple_dependency_graph.html"
57+
style="width: 100%; height: 720px; border: 1px solid #d8cfc2; border-radius: 8px; background: #f7f0e7;"
58+
title="PlantSimEngine dependency graph example"
59+
></iframe>
60+
```
61+
5362
- **Modularity**: Each model component can be developed, tested, and improved independently. Assemble complex simulations by reusing pre-built, high-quality modules.
5463
- **Standardisation**: Clear, enforceable guidelines ensure that all models adhere to best practices. This built-in consistency means that once you implement a model, it works seamlessly with others in the ecosystem.
5564
- **Optimised Performance**: Don't re-invent the wheel. Delegating low-level tasks to PlantSimEngine guarantees that your model will benefit from every improvement in the framework. Enjoy faster prototyping, robust simulations, and efficient execution using Julia's high-performance capabilities.
@@ -74,6 +83,7 @@ Depth = 5
7483

7584
## Batteries included
7685

86+
- **Interactive graph editor**: Compose your model by interactively adding sub-models in a graph editor, and let the framework handle the coupling and execution.
7787
- **Automated Management**: Seamlessly handle inputs, outputs, time-steps, objects, and dependency resolution.
7888
- **Iterative Development**: Fast and interactive prototyping of models with built-in constraints to avoid errors and sensible defaults to streamline the model writing process.
7989
- **Control Your Degrees of Freedom**: Fix variables to constant values or force to observations, use simpler models for specific processes to reduce complexity.

docs/src/step_by_step/graph_visualization_editor.md

Lines changed: 158 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,97 @@
11
# Graph visualization and editing
22

3-
`PlantSimEngine` can export a dependency graph view from a [`ModelMapping`](@ref). The static viewer is available from the core package and does not require any web server dependency.
3+
`PlantSimEngine` can display the dependency graph created from a [`ModelMapping`](@ref). Use it when you want to check which model computes which variable, inspect missing initial values, explain a model pipeline in documentation, or interactively build and revise a mapping.
4+
5+
There are two entry points:
6+
7+
- [`write_graph_view`](@ref) writes a standalone HTML viewer. This is available from `PlantSimEngine` itself and does not start a server.
8+
- [`edit_graph`](@ref) starts a local browser editor. This is loaded by a Julia package extension when `HTTP.jl` is available and loaded in the session.
9+
10+
## Static graph viewer
11+
12+
The static viewer is the right tool for documentation, reports, or any read-only inspection. It contains the graph, search, the inspector, scale filters, relationship filters, and overview/detail modes, but it does not modify the [`ModelMapping`](@ref).
13+
14+
```@setup graph_viewer
15+
using PlantSimEngine
16+
using PlantSimEngine.Examples
17+
```
18+
19+
Here is a small pedagogical mapping with three models:
20+
21+
```@example graph_viewer
22+
mapping = ModelMapping(
23+
ToyDegreeDaysCumulModel(),
24+
ToyLAIModel(),
25+
Beer(0.5),
26+
)
27+
nothing # hide
28+
```
29+
30+
The thermal time model computes `TT_cu`, the LAI model consumes `TT_cu` and computes `LAI`, and the Beer model consumes `LAI` and computes `aPPFD`. The generated viewer below is the same HTML file you would get by calling [`write_graph_view`](@ref):
31+
32+
```@raw html
33+
<iframe
34+
src="../www/simple_dependency_graph.html"
35+
style="width: 100%; height: 720px; border: 1px solid #d8cfc2; border-radius: 8px; background: #f7f0e7;"
36+
title="PlantSimEngine dependency graph example"
37+
></iframe>
38+
```
39+
40+
To write the viewer yourself:
441

542
```julia
643
using PlantSimEngine
744
using PlantSimEngine.Examples
845

946
mapping = ModelMapping(
47+
ToyDegreeDaysCumulModel(),
1048
ToyLAIModel(),
11-
Beer(0.5);
12-
status=(TT_cu=1.0:200.0,),
49+
Beer(0.5),
1350
)
1451

1552
write_graph_view("dependency_graph.html", mapping)
1653
```
1754

18-
The same serialization path is used by the interactive editor. The editor is implemented as a Julia package extension, so the HTTP/WebSocket stack is loaded only when [`HTTP.jl`](https://github.com/JuliaWeb/HTTP.jl) is available and loaded in the active session.
55+
The returned file path is absolute, so you can print it, open it in a browser, or embed it in another documentation site.
56+
57+
## Embedding a graph in package documentation
58+
59+
For package documentation built with Documenter, generate the HTML file before `makedocs` and place it somewhere under `docs/src`, for example `docs/src/www/model_graph.html`:
60+
61+
```julia
62+
# docs/make.jl
63+
using Documenter
64+
using PlantSimEngine
65+
using YourPackage
66+
67+
mapping = YourPackage.default_mapping()
68+
write_graph_view(joinpath(@__DIR__, "src", "www", "model_graph.html"), mapping)
69+
70+
makedocs(;
71+
# ...
72+
)
73+
```
74+
75+
Then embed it from a markdown page:
76+
77+
```html
78+
<iframe
79+
src="../www/model_graph.html"
80+
style="width: 100%; height: 720px; border: 1px solid #d8cfc2; border-radius: 8px;"
81+
title="Model dependency graph"
82+
></iframe>
83+
```
84+
85+
Use the right relative path for the page where the iframe lives. A page in `docs/src/multiscale/` usually needs `../www/model_graph.html`; a page at the root of `docs/src/` usually needs `www/model_graph.html`.
86+
87+
!!! tip
88+
This is the same pattern used to show large package mappings, such as the XPalm dependency graph, directly inside package documentation. The viewer is static, so it works on GitHub Pages without a Julia server.
89+
90+
## Interactive editor
91+
92+
The interactive editor uses the same graph JSON as the static viewer, but it keeps a WebSocket connection open to Julia. Julia remains the source of truth: the browser sends edit commands, Julia applies them to the [`ModelMapping`](@ref), recompiles graph diagnostics, and sends the updated graph back to the browser.
93+
94+
The editor is implemented as a package extension. Load `HTTP` before calling [`edit_graph`](@ref):
1995

2096
```julia
2197
using PlantSimEngine
@@ -25,7 +101,7 @@ using HTTP
25101
mapping = ModelMapping(
26102
ToyLAIModel(),
27103
Beer(0.5);
28-
status=(TT_cu=1.0:200.0,),
104+
status=(TT_cu=1.0,),
29105
)
30106

31107
session = edit_graph(mapping)
@@ -45,7 +121,7 @@ By default, `edit_graph` opens `session.url` in the system default browser. Pass
45121
session = edit_graph(mapping; open_browser=false)
46122
```
47123

48-
The browser sends edit commands to Julia over a WebSocket. Julia remains the source of truth: it applies the edit, rebuilds the [`ModelMapping`](@ref), recompiles graph diagnostics, and sends the updated graph back to the browser.
124+
The URL contains a session token and the server listens on `127.0.0.1` by default. Treat that URL as a local capability: anyone who can reach it can edit the live mapping. If you intentionally bind to another host, pass `allow_remote=true` only on a trusted network.
49125

50126
To stop the HTTP/WebSocket session, run:
51127

@@ -60,6 +136,44 @@ edited_mapping = current_mapping(session)
60136
close(session)
61137
```
62138

139+
!!! note
140+
If `HTTP` is not loaded, `edit_graph(mapping)` throws an error explaining that the interactive editor requires `using HTTP`. Static graph visualization through [`write_graph_view`](@ref), `graph_view`, and [`graph_view_json`](@ref) remains available without loading `HTTP`.
141+
142+
## What you can edit
143+
144+
The editor supports the same mapping operations as the Julia graph-edit API:
145+
146+
- add a model by choosing a scale, a model type, parameter values, and a rate;
147+
- update an existing model's parameter values, scale, or rate from the inspector;
148+
- remove a model from the inspector or from the selected model node;
149+
- add new scales while configuring a model;
150+
- set a mapped input variable from the inspector;
151+
- draw a connection from an output port to an input port to create a mapping;
152+
- map a scalar source value or a vector of values from one or several source scales;
153+
- mark or unmark a variable as [`PreviousTimeStep`](@ref);
154+
- use undo and redo inside the live session.
155+
156+
The `+` buttons beside variables are suggestions from the current model library:
157+
158+
- on an input, `+` lists models that can compute that variable as an output;
159+
- on an output, `+` lists models that can consume that variable as an input.
160+
161+
Clicking a suggested model opens the add-model panel with that model preselected, so you can set its scale, parameters, and rate before adding it.
162+
163+
## Cycles
164+
165+
The simulation dependency graph must be acyclic when it runs. The viewer can still compile a non-throwing graph view for cyclic or incomplete mappings, so the editor can show the problem instead of failing immediately.
166+
167+
When a cycle is detected:
168+
169+
- cycle edges are drawn in red;
170+
- the cycle call-to-action asks you to choose a break point in the graph;
171+
- clicking the scissors button on a highlighted input wraps that input in [`PreviousTimeStep`](@ref).
172+
173+
This means the consumer model uses the variable value from the previous timestep, so that current-step dependency is removed and the graph can run again.
174+
175+
## Mapping code and saving
176+
63177
The web editor also exposes a dedicated "Mapping code" panel. It shows the current [`ModelMapping`](@ref) as Julia code, and can write that code to a `.jl` file so it can be copied/pasted or reused in scripts. The generated file is intentionally plain Julia: it imports the packages needed by the selected models and defines a top-level `mapping` variable:
64178

65179
```julia
@@ -73,14 +187,43 @@ mapping = ModelMapping(
73187

74188
After writing a file once, every successful edit, undo, redo, or recent-file load automatically rewrites that same file. The session also keeps a recovery autosave in the temporary directory. The top-left "Open" button can reopen a mapping script from a file path or from the recent mapping list. Use git or another version-control system for mapping scripts that matter for a simulation workflow.
75189

76-
The editor extension currently supports the same edit operations as the Julia API:
190+
The `Status(...)` entries in generated code are rebuilt from the current mapping. Variables computed by models are omitted, even if they were present in the original status, and only variables still required for initialization are kept.
77191

78-
- add, remove, and replace a model at a scale;
79-
- update an existing model's parameter values, scale, or rate from the inspector;
80-
- keep the model default rate, or set a custom `ClockSpec(dt, phase)` when adding a model;
81-
- set a mapped input variable, either from the inspector or by drawing a connection from an output port to an input port;
82-
- map a scalar source value or a vector of values from one or several source scales;
83-
- mark or unmark a variable as [`PreviousTimeStep`](@ref);
84-
- undo and redo edits inside the live session.
192+
Because the generated script only defines `mapping`, users can include it directly from a simulation script:
193+
194+
```julia
195+
include("mapping.generated.jl")
196+
run!(mapping, meteo)
197+
```
198+
199+
## Models from external packages
200+
201+
The editor does not use a separate model registry. It discovers models from the Julia session by traversing the loaded subtype tree under [`AbstractModel`](@ref).
202+
203+
This means packages become available when you load them:
204+
205+
```julia
206+
using PlantSimEngine
207+
using PlantSimEngine.Examples
208+
using PlantBiophysics
209+
using HTTP
210+
211+
session = edit_graph()
212+
```
213+
214+
After `using PlantBiophysics`, the editor can list the process and model types that `PlantBiophysics` loaded into the session, provided those models follow the normal PlantSimEngine contract:
215+
216+
- process abstract types are subtypes of [`AbstractModel`](@ref);
217+
- concrete model structs are subtypes of those process types;
218+
- models define `inputs_` and `outputs_`;
219+
- model parameters are stored in struct fields, with an optional zero-argument constructor for default values.
220+
221+
Constructor fields become parameter rows in the add-model and edit-model panels. For parametric models, fields that share the same type parameter also share the same type dropdown. The available parameter type choices are `float`, `integer`, `boolean`, `symbol`, `string`, `nothing`, and `julia`. Julia validates the final constructor call; if construction fails, the diagnostic is returned to the editor.
222+
223+
You can inspect the currently visible library from Julia:
224+
225+
```@example graph_viewer
226+
available_models(:light_interception)
227+
```
85228

86-
If `HTTP` is not loaded, `edit_graph(mapping)` throws an error explaining that the interactive editor requires `using HTTP`. Static graph visualization through [`write_graph_view`](@ref), `graph_view`, and [`graph_view_json`](@ref) remains available without loading `HTTP`.
229+
If a package is not loaded with `using PackageName`, its model types are not present in the Julia session and the editor cannot list them.

examples/ToySingleToMultiScale.jl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,7 @@ struct ToyTt_CuModel <: AbstractTt_CuModel
5353
end
5454

5555
function PlantSimEngine.run!(::ToyTt_CuModel, models, status, meteo, constants, extra=nothing)
56-
status.TT_cu +=
57-
meteo.TT
56+
status.TT_cu += meteo.TT
5857
end
5958

6059
function PlantSimEngine.inputs_(::ToyTt_CuModel)

0 commit comments

Comments
 (0)