diff --git a/build.gradle b/build.gradle index 86760cb7..d8e4dded 100644 --- a/build.gradle +++ b/build.gradle @@ -54,14 +54,15 @@ subprojects { proj -> publishing { repositories { - maven { - name = "ossrh" - url = "https://oss.sonatype.org/service/local/staging/deploy/maven2/" - credentials { - username = findProperty('ossrhUsername') - password = findProperty('ossrhPassword') - } - } +// maven { +// name = "ossrh" +// url = "https://oss.sonatype.org/service/local/staging/deploy/maven2/" +// credentials { +// username = findProperty('ossrhUsername') +// password = findProperty('ossrhPassword') +// } +// } + mavenLocal() } publications { @@ -98,8 +99,8 @@ subprojects { proj -> } } - signing { - sign publishing.publications.mavenJava - } +// signing { +// sign publishing.publications.mavenJava +// } } \ No newline at end of file diff --git a/structurizr-export/src/main/java/com/structurizr/export/AbstractDiagramExporter.java b/structurizr-export/src/main/java/com/structurizr/export/AbstractDiagramExporter.java index 81894c51..6479fb5d 100644 --- a/structurizr-export/src/main/java/com/structurizr/export/AbstractDiagramExporter.java +++ b/structurizr-export/src/main/java/com/structurizr/export/AbstractDiagramExporter.java @@ -36,7 +36,7 @@ public final Collection export(Workspace workspace) { throw new IllegalArgumentException("A workspace must be provided."); } - Collection diagrams = new ArrayList<>(); + Collection diagrams = new ArrayList<>() ; for (CustomView view : workspace.getViews().getCustomViews()) { Diagram diagram = export(view); @@ -295,7 +295,7 @@ public Diagram export(ComponentView view, Integer animationStep) { writer.writeLine(); } - boolean includeSoftwareSystemBoundaries = "true".equals(view.getProperties().getOrDefault("structurizr.softwareSystemBoundaries", "false")); + boolean includeSoftwareSystemBoundaries = "true".equalsIgnoreCase(getViewOrViewSetProperty(view, "structurizr.softwareSystemBoundaries", "false")); List containers = getBoundaryContainers(view); Set softwareSystems = containers.stream().map(Container::getSoftwareSystem).collect(Collectors.toCollection(LinkedHashSet::new)); @@ -394,8 +394,8 @@ public Diagram export(DynamicView view, String order) { } } else if (element instanceof Container) { // dynamic view with container scope - boolean includeSoftwareSystemBoundaries = "true".equals(view.getProperties().getOrDefault("structurizr.softwareSystemBoundaries", "false")); - + boolean includeSoftwareSystemBoundaries = "true".equalsIgnoreCase(getViewOrViewSetProperty(view, "structurizr.softwareSystemBoundaries", "false")); + List containers = getBoundaryContainers(view); Set softwareSystems = containers.stream().map(Container::getSoftwareSystem).collect(Collectors.toCollection(LinkedHashSet::new)); for (SoftwareSystem softwareSystem : softwareSystems) { diff --git a/structurizr-export/src/main/java/com/structurizr/export/plantuml/AbstractPlantUMLExporter.java b/structurizr-export/src/main/java/com/structurizr/export/plantuml/AbstractPlantUMLExporter.java index 52a974b2..864d4063 100644 --- a/structurizr-export/src/main/java/com/structurizr/export/plantuml/AbstractPlantUMLExporter.java +++ b/structurizr-export/src/main/java/com/structurizr/export/plantuml/AbstractPlantUMLExporter.java @@ -23,6 +23,7 @@ public abstract class AbstractPlantUMLExporter extends AbstractDiagramExporter { public static final String PLANTUML_INCLUDES_PROPERTY = "plantuml.includes"; public static final String PLANTUML_ANIMATION_PROPERTY = "plantuml.animation"; public static final String PLANTUML_SEQUENCE_DIAGRAM_PROPERTY = "plantuml.sequenceDiagram"; + public static final String PLANTUML_TEOZ_PROPERTY= "plantuml.teoz"; public static final String DIAGRAM_TITLE_TAG = "Diagram:Title"; public static final String DIAGRAM_DESCRIPTION_TAG = "Diagram:Description"; diff --git a/structurizr-export/src/main/java/com/structurizr/export/plantuml/StructurizrPlantUMLExporter.java b/structurizr-export/src/main/java/com/structurizr/export/plantuml/StructurizrPlantUMLExporter.java index c7153427..524bd578 100644 --- a/structurizr-export/src/main/java/com/structurizr/export/plantuml/StructurizrPlantUMLExporter.java +++ b/structurizr-export/src/main/java/com/structurizr/export/plantuml/StructurizrPlantUMLExporter.java @@ -33,6 +33,9 @@ public StructurizrPlantUMLExporter(ColorScheme colorScheme) { protected void writeHeader(ModelView view, IndentingWriter writer) { plantUMLStyles = new HashSet<>(); super.writeHeader(view, writer); + if (renderAsTeozDiagram(view)) { + writer.writeLine("!pragma teoz true"); + } if (renderAsSequenceDiagram(view)) { // do nothing @@ -114,7 +117,7 @@ protected void startGroupBoundary(ModelView view, String group, IndentingWriter if (!StringUtils.isNullOrEmpty(groupSeparator)) { groupName = group.substring(group.lastIndexOf(groupSeparator) + groupSeparator.length()); } - + ElementStyle elementStyle = findGroupStyle(view, group); PlantUMLGroupStyle plantUMLBoundaryStyle = new PlantUMLGroupStyle( group, @@ -146,7 +149,13 @@ protected void startGroupBoundary(ModelView view, String group, IndentingWriter Base64.getEncoder().encodeToString(group.getBytes())) ); writer.indent(); + } else { + writer.writeLine(String.format("box \"%s\" <<%s>>", groupName, classSelectorForGroup(group))); + writer.indent(); + writer.writeLine("box"); + writer.indent(); } + writer.writeLine(); } @Override @@ -155,25 +164,32 @@ protected void endGroupBoundary(ModelView view, IndentingWriter writer) { writer.outdent(); writer.writeLine("}"); writer.writeLine(); + } else { + writer.outdent(); + writer.writeLine("end box"); + writer.outdent(); + writer.writeLine("end box"); + writer.writeLine(); + + } } @Override protected void startSoftwareSystemBoundary(ModelView view, SoftwareSystem softwareSystem, IndentingWriter writer) { + ElementStyle elementStyle = findBoundaryStyle(view, softwareSystem); + PlantUMLBoundaryStyle plantUMLBoundaryStyle = new PlantUMLBoundaryStyle( + softwareSystem.getName(), + elementStyle.getBackground(), + elementStyle.getColor(), + elementStyle.getStroke(), + elementStyle.getStrokeWidth() != null ? elementStyle.getStrokeWidth() : DEFAULT_STROKE_WIDTH, + elementStyle.getBorder(), + elementStyle.getFontSize(), + "true".equalsIgnoreCase(elementStyle.getProperties().getOrDefault(PLANTUML_SHADOW, "false")) + ); + plantUMLStyles.add(plantUMLBoundaryStyle); if (!renderAsSequenceDiagram(view)) { - ElementStyle elementStyle = findBoundaryStyle(view, softwareSystem); - PlantUMLBoundaryStyle plantUMLBoundaryStyle = new PlantUMLBoundaryStyle( - softwareSystem.getName(), - elementStyle.getBackground(), - elementStyle.getColor(), - elementStyle.getStroke(), - elementStyle.getStrokeWidth() != null ? elementStyle.getStrokeWidth() : DEFAULT_STROKE_WIDTH, - elementStyle.getBorder(), - elementStyle.getFontSize(), - "true".equalsIgnoreCase(elementStyle.getProperties().getOrDefault(PLANTUML_SHADOW, "false")) - ); - plantUMLStyles.add(plantUMLBoundaryStyle); - writer.writeLine( String.format( "rectangle \"%s\\n%s\" <<%s>> {", @@ -182,7 +198,18 @@ protected void startSoftwareSystemBoundary(ModelView view, SoftwareSystem softwa typeOf(view, softwareSystem, true), plantUMLBoundaryStyle.getClassSelector() ) - ); + ); + writer.indent(); + } else { + writer.writeLine( + String.format("box \"%s\n%s\" <<%s>>", + softwareSystem.getName(), + typeOf(view, softwareSystem, true), + plantUMLBoundaryStyle.getClassSelector() + ) + ); + writer.indent(); + writer.writeLine("box"); writer.indent(); } } @@ -193,25 +220,30 @@ protected void endSoftwareSystemBoundary(ModelView view, IndentingWriter writer) writer.outdent(); writer.writeLine("}"); writer.writeLine(); + } else { + writer.outdent(); + writer.writeLine("end box"); + writer.outdent(); + writer.writeLine("end box"); + writer.writeLine(); } } @Override protected void startContainerBoundary(ModelView view, Container container, IndentingWriter writer) { + ElementStyle elementStyle = findBoundaryStyle(view, container); + PlantUMLBoundaryStyle plantUMLBoundaryStyle = new PlantUMLBoundaryStyle( + container.getName(), + elementStyle.getBackground(), + elementStyle.getColor(), + elementStyle.getStroke(), + elementStyle.getStrokeWidth() != null ? elementStyle.getStrokeWidth() : DEFAULT_STROKE_WIDTH, + elementStyle.getBorder(), + elementStyle.getFontSize(), + "true".equalsIgnoreCase(elementStyle.getProperties().getOrDefault(PLANTUML_SHADOW, "false")) + ); + plantUMLStyles.add(plantUMLBoundaryStyle); if (!renderAsSequenceDiagram(view)) { - ElementStyle elementStyle = findBoundaryStyle(view, container); - PlantUMLBoundaryStyle plantUMLBoundaryStyle = new PlantUMLBoundaryStyle( - container.getName(), - elementStyle.getBackground(), - elementStyle.getColor(), - elementStyle.getStroke(), - elementStyle.getStrokeWidth() != null ? elementStyle.getStrokeWidth() : DEFAULT_STROKE_WIDTH, - elementStyle.getBorder(), - elementStyle.getFontSize(), - "true".equalsIgnoreCase(elementStyle.getProperties().getOrDefault(PLANTUML_SHADOW, "false")) - ); - plantUMLStyles.add(plantUMLBoundaryStyle); - writer.writeLine( String.format( "rectangle \"%s\\n%s\" <<%s>> {", @@ -219,6 +251,17 @@ protected void startContainerBoundary(ModelView view, Container container, Inden calculateMetadataFontSize(findBoundaryStyle(view, container).getFontSize()), typeOf(view, container, true), plantUMLBoundaryStyle.getClassSelector())); writer.indent(); + } else { + writer.writeLine( + String.format("box \"%s\n%s\" <<%s>>", + container.getName(), + typeOf(view, container, true), + plantUMLBoundaryStyle.getClassSelector() + ) + ); + writer.indent(); + writer.writeLine("box"); + writer.indent(); } } @@ -228,6 +271,12 @@ protected void endContainerBoundary(ModelView view, IndentingWriter writer) { writer.outdent(); writer.writeLine("}"); writer.writeLine(); + } else { + writer.outdent(); + writer.writeLine("end box"); + writer.outdent(); + writer.writeLine("end box"); + writer.writeLine(); } } @@ -291,34 +340,11 @@ protected void endDeploymentNodeBoundary(ModelView view, IndentingWriter writer) @Override public Diagram export(DynamicView view) { - if (renderAsSequenceDiagram(view)) { - IndentingWriter writer = new IndentingWriter(); - writeHeader(view, writer); - - Set elements = new LinkedHashSet<>(); - for (RelationshipView relationshipView : view.getRelationships()) { - elements.add(relationshipView.getRelationship().getSource()); - elements.add(relationshipView.getRelationship().getDestination()); - } - - for (Element element : elements) { - writeElement(view, element, writer); - } - - if (!elements.isEmpty()) { - writer.writeLine(); + Diagram diagram = super.export(view); + if (renderAsSequenceDiagram(view)) { + diagram.setLegend(createLegend(view)); } - - writeRelationships(view, writer); - writeFooter(view, writer); - - Diagram diagram = createDiagram(view, writer.toString()); - diagram.setLegend(createLegend(view)); - return diagram; - } else { - return super.export(view); - } } @Override @@ -446,7 +472,6 @@ protected void writeRelationship(ModelView view, RelationshipView relationshipVi arrowStart = "<-"; arrowEnd = "-"; } - writer.writeLine( String.format("%s %s%s %s <<%s>> : %s%s", idOf(relationship.getSource()), @@ -546,7 +571,11 @@ protected Legend createLegend(ModelView view) { protected boolean renderAsSequenceDiagram(ModelView view) { return view instanceof DynamicView && "true".equalsIgnoreCase(getViewOrViewSetProperty(view, PLANTUML_SEQUENCE_DIAGRAM_PROPERTY, "false")); } - + + protected boolean renderAsTeozDiagram(ModelView view) { + return view instanceof DynamicView && "true".equalsIgnoreCase(getViewOrViewSetProperty(view, PLANTUML_TEOZ_PROPERTY, "false")); + } + private String classSelectorFor(ElementStyle elementStyle) { return "Element-" + Base64.getEncoder().encodeToString(elementStyle.getTag().getBytes()); } @@ -644,4 +673,5 @@ private String toLineStyle(ElementStyle elementStyle) { } } -} \ No newline at end of file +} + diff --git a/structurizr-export/src/test/.structurizr/1/images/Diagram1-thumbnail.png b/structurizr-export/src/test/.structurizr/1/images/Diagram1-thumbnail.png new file mode 100644 index 00000000..f73730f7 Binary files /dev/null and b/structurizr-export/src/test/.structurizr/1/images/Diagram1-thumbnail.png differ diff --git a/structurizr-export/src/test/.structurizr/1/images/thumbnail.png b/structurizr-export/src/test/.structurizr/1/images/thumbnail.png new file mode 100644 index 00000000..f73730f7 Binary files /dev/null and b/structurizr-export/src/test/.structurizr/1/images/thumbnail.png differ diff --git a/structurizr-export/src/test/.structurizr/index/_2.cfe b/structurizr-export/src/test/.structurizr/index/_2.cfe new file mode 100644 index 00000000..535f0802 Binary files /dev/null and b/structurizr-export/src/test/.structurizr/index/_2.cfe differ diff --git a/structurizr-export/src/test/.structurizr/index/_2.cfs b/structurizr-export/src/test/.structurizr/index/_2.cfs new file mode 100644 index 00000000..a3c050e6 Binary files /dev/null and b/structurizr-export/src/test/.structurizr/index/_2.cfs differ diff --git a/structurizr-export/src/test/.structurizr/index/_2.si b/structurizr-export/src/test/.structurizr/index/_2.si new file mode 100644 index 00000000..2fb4b8af Binary files /dev/null and b/structurizr-export/src/test/.structurizr/index/_2.si differ diff --git a/structurizr-export/src/test/.structurizr/index/segments_3 b/structurizr-export/src/test/.structurizr/index/segments_3 new file mode 100644 index 00000000..22aeaa79 Binary files /dev/null and b/structurizr-export/src/test/.structurizr/index/segments_3 differ diff --git a/structurizr-export/src/test/.structurizr/index/write.lock b/structurizr-export/src/test/.structurizr/index/write.lock new file mode 100644 index 00000000..e69de29b diff --git a/structurizr-export/src/test/java/com/structurizr/export/dot/DOTDiagramExporterTests.java b/structurizr-export/src/test/java/com/structurizr/export/dot/DOTDiagramExporterTests.java index 937af876..d4623580 100644 --- a/structurizr-export/src/test/java/com/structurizr/export/dot/DOTDiagramExporterTests.java +++ b/structurizr-export/src/test/java/com/structurizr/export/dot/DOTDiagramExporterTests.java @@ -573,7 +573,7 @@ public void test_GroupsExample() throws Exception { DOTExporter exporter = new DOTExporter(); Collection diagrams = exporter.export(workspace); - assertEquals(3, diagrams.size()); + assertEquals(4, diagrams.size()); Diagram diagram = diagrams.stream().filter(md -> md.getKey().equals("SystemLandscape")).findFirst().get(); assertEquals(""" @@ -703,6 +703,7 @@ public void test_GroupsExample() throws Exception { 3 -> 7 [id=13, label=<>, style="dashed", color="#444444", fontcolor="#444444"] 3 -> 8 [id=15, label=<>, style="dashed", color="#444444", fontcolor="#444444"] + 7 -> 8 [id=16, label=<>, style="dashed", color="#444444", fontcolor="#444444"] }""", diagram.getDefinition()); } diff --git a/structurizr-export/src/test/java/com/structurizr/export/dot/groups-Components.dot b/structurizr-export/src/test/java/com/structurizr/export/dot/groups-Components.dot new file mode 100644 index 00000000..77954547 --- /dev/null +++ b/structurizr-export/src/test/java/com/structurizr/export/dot/groups-Components.dot @@ -0,0 +1,36 @@ +digraph { + compound=true + graph [fontname="Arial", rankdir=TB, ranksep=1.0, nodesep=1.0] + node [fontname="Arial", shape=box, margin="0.4,0.3"] + edge [fontname="Arial"] + label=<
D - F - Components> + + 3 [id=3,shape=rect, label=<C
[Software System]>, style=filled, color="#9a9a9a", fillcolor="#dddddd", fontcolor="#000000"] + + subgraph cluster_6 { + margin=25 + label=<
F

[Container]> + labelloc=b + color="#444444" + fontcolor="#444444" + fillcolor="#444444" + + subgraph "cluster_group_Group 5" { + margin=25 + label=<
Group 5
> + labelloc=b + color="#cccccc" + fontcolor="#cccccc" + fillcolor="#ffffff" + style="dashed" + + 8 [id=8,shape=rect, label=<H
[Component]>, style=filled, color="#9a9a9a", fillcolor="#dddddd", fontcolor="#000000"] + } + + 7 [id=7,shape=rect, label=<G
[Component]>, style=filled, color="#9a9a9a", fillcolor="#dddddd", fontcolor="#000000"] + } + + 3 -> 7 [id=13, label=<>, style="dashed", color="#707070", fontcolor="#707070"] + 3 -> 8 [id=15, label=<>, style="dashed", color="#707070", fontcolor="#707070"] + 7 -> 8 [id=16, label=<>, style="dashed", color="#707070", fontcolor="#707070"] +} \ No newline at end of file diff --git a/structurizr-export/src/test/java/com/structurizr/export/dot/groups-Dynamic.dot b/structurizr-export/src/test/java/com/structurizr/export/dot/groups-Dynamic.dot new file mode 100644 index 00000000..fe4eb7d4 --- /dev/null +++ b/structurizr-export/src/test/java/com/structurizr/export/dot/groups-Dynamic.dot @@ -0,0 +1,32 @@ +digraph { + compound=true + graph [fontname="Arial", rankdir=TB, ranksep=1.0, nodesep=1.0] + node [fontname="Arial", shape=box, margin="0.4,0.3"] + edge [fontname="Arial"] + label=<
F - Dynamic> + + subgraph cluster_6 { + margin=25 + label=<
F

[Container]> + labelloc=b + color="#444444" + fontcolor="#444444" + fillcolor="#444444" + + subgraph "cluster_group_Group 5" { + margin=25 + label=<
Group 5
> + labelloc=b + color="#cccccc" + fontcolor="#cccccc" + fillcolor="#ffffff" + style="dashed" + + 8 [id=8,shape=rect, label=<H
[Component]>, style=filled, color="#9a9a9a", fillcolor="#dddddd", fontcolor="#000000"] + } + + 7 [id=7,shape=rect, label=<G
[Component]>, style=filled, color="#9a9a9a", fillcolor="#dddddd", fontcolor="#000000"] + } + + 7 -> 8 [id=16, label=<1. >, style="dashed", color="#707070", fontcolor="#707070"] +} \ No newline at end of file diff --git a/structurizr-export/src/test/java/com/structurizr/export/mermaid/MermaidDiagramExporterTests.java b/structurizr-export/src/test/java/com/structurizr/export/mermaid/MermaidDiagramExporterTests.java index fe176f04..8810ed87 100644 --- a/structurizr-export/src/test/java/com/structurizr/export/mermaid/MermaidDiagramExporterTests.java +++ b/structurizr-export/src/test/java/com/structurizr/export/mermaid/MermaidDiagramExporterTests.java @@ -88,7 +88,7 @@ public void test_GroupsExample() throws Exception { MermaidDiagramExporter exporter = new MermaidDiagramExporter(); Collection diagrams = exporter.export(workspace); - assertEquals(3, diagrams.size()); + assertEquals(4, diagrams.size()); Diagram diagram = diagrams.stream().filter(md -> md.getKey().equals("SystemLandscape")).findFirst().get(); assertEquals(""" @@ -185,6 +185,7 @@ public void test_GroupsExample() throws Exception { 3-. "
" .->7 3-. "
" .->8 + 7-. "
" .->8 end""", diagram.getDefinition()); } diff --git a/structurizr-export/src/test/java/com/structurizr/export/mermaid/groups-Components.mmd b/structurizr-export/src/test/java/com/structurizr/export/mermaid/groups-Components.mmd new file mode 100644 index 00000000..d4822374 --- /dev/null +++ b/structurizr-export/src/test/java/com/structurizr/export/mermaid/groups-Components.mmd @@ -0,0 +1,27 @@ +graph TB + linkStyle default fill:#ffffff + + subgraph diagram ["D - F - Components"] + style diagram fill:#ffffff,stroke:#ffffff + + 3["
C
[Software System]
"] + style 3 fill:#dddddd,stroke:#9a9a9a,color:#000000 + + subgraph 6 ["F"] + style 6 fill:#ffffff,stroke:#9a9a9a,color:#9a9a9a + + subgraph group1 ["Group 5"] + style group1 fill:#ffffff,stroke:#cccccc,color:#cccccc,stroke-dasharray:5 + + 8["
H
[Component]
"] + style 8 fill:#dddddd,stroke:#9a9a9a,color:#000000 + end + + 7["
G
[Component]
"] + style 7 fill:#dddddd,stroke:#9a9a9a,color:#000000 + end + + 3-. "
" .->7 + 3-. "
" .->8 + 7-. "
" .->8 + end \ No newline at end of file diff --git a/structurizr-export/src/test/java/com/structurizr/export/mermaid/groups-Dynamic.mmd b/structurizr-export/src/test/java/com/structurizr/export/mermaid/groups-Dynamic.mmd new file mode 100644 index 00000000..cf4b547e --- /dev/null +++ b/structurizr-export/src/test/java/com/structurizr/export/mermaid/groups-Dynamic.mmd @@ -0,0 +1,6 @@ +sequenceDiagram + + participant 7 as G
[Component] + participant 8 as H
[Component] + + 7->>8: \ No newline at end of file diff --git a/structurizr-export/src/test/java/com/structurizr/export/plantuml/C4PlantUMLDiagramExporterTests.java b/structurizr-export/src/test/java/com/structurizr/export/plantuml/C4PlantUMLDiagramExporterTests.java index a8d073c1..fcaafbb9 100644 --- a/structurizr-export/src/test/java/com/structurizr/export/plantuml/C4PlantUMLDiagramExporterTests.java +++ b/structurizr-export/src/test/java/com/structurizr/export/plantuml/C4PlantUMLDiagramExporterTests.java @@ -17,6 +17,145 @@ public class C4PlantUMLDiagramExporterTests extends AbstractExporterTests { + @Test + @Tag("IntegrationTest") + public void test_AmazonWebServicesExampleWithoutTags() throws Exception { + Workspace workspace = WorkspaceUtils.loadWorkspaceFromJson(new File("./src/test/resources/amazon-web-services.json")); + ThemeUtils.loadThemes(workspace); + workspace.getViews().getDeploymentViews().iterator().next().enableAutomaticLayout(AutomaticLayout.RankDirection.LeftRight, 300, 300); + workspace.getViews().getViews().forEach(v -> v.addProperty(C4PlantUMLExporter.C4PLANTUML_TAGS_PROPERTY, "false")); + + C4PlantUMLExporter exporter = new C4PlantUMLExporter(); + Collection diagrams = exporter.export(workspace); + assertEquals(1, diagrams.size()); + + Diagram diagram = diagrams.stream().findFirst().get(); + assertEquals(""" + @startuml + title Deployment View: X - Live + + set separator none + left to right direction + + + + !include + !include + !include + !include + + Deployment_Node(Live.AmazonWebServices, "Amazon Web Services", $type="", $descr="", $tags="", $link="") { + Deployment_Node(Live.AmazonWebServices.USEast1, "US-East-1", $type="", $descr="", $tags="", $link="") { + Deployment_Node(Live.AmazonWebServices.USEast1.Autoscalinggroup, "Autoscaling group", $type="", $descr="", $tags="", $link="") { + Deployment_Node(Live.AmazonWebServices.USEast1.Autoscalinggroup.AmazonEC2Ubuntuserver, "Amazon EC2 - Ubuntu server", $type="", $descr="", $tags="", $link="") { + Container(Live.AmazonWebServices.USEast1.Autoscalinggroup.AmazonEC2Ubuntuserver.WebApplication_1, "Web Application", $techn="Java and Spring Boot", $descr="", $tags="", $link="") + } + + } + + Deployment_Node(Live.AmazonWebServices.USEast1.AmazonRDS, "Amazon RDS", $type="", $descr="", $tags="", $link="") { + Deployment_Node(Live.AmazonWebServices.USEast1.AmazonRDS.MySQL, "MySQL", $type="", $descr="", $tags="", $link="") { + ContainerDb(Live.AmazonWebServices.USEast1.AmazonRDS.MySQL.DatabaseSchema_1, "Database Schema", $techn="", $descr="", $tags="", $link="") + } + + } + + Deployment_Node(Live.AmazonWebServices.USEast1.DNSrouter, "DNS router", $type="Route 53", $descr="Routes incoming requests based upon domain name.", $tags="", $link="") + Deployment_Node(Live.AmazonWebServices.USEast1.LoadBalancer, "Load Balancer", $type="Elastic Load Balancer", $descr="Automatically distributes incoming application traffic.", $tags="", $link="") + } + + } + + Rel(Live.AmazonWebServices.USEast1.LoadBalancer, Live.AmazonWebServices.USEast1.Autoscalinggroup.AmazonEC2Ubuntuserver.WebApplication_1, "Forwards requests to", $techn="HTTPS", $tags="", $link="") + Rel(Live.AmazonWebServices.USEast1.Autoscalinggroup.AmazonEC2Ubuntuserver.WebApplication_1, Live.AmazonWebServices.USEast1.AmazonRDS.MySQL.DatabaseSchema_1, "Reads from and writes to", $techn="MySQL Protocol/SSL", $tags="", $link="") + Rel(Live.AmazonWebServices.USEast1.DNSrouter, Live.AmazonWebServices.USEast1.LoadBalancer, "Forwards requests to", $techn="HTTPS", $tags="", $link="") + + SHOW_LEGEND(true) + hide stereotypes + @enduml""", diagram.getDefinition()); + } + + @Test + @Tag("IntegrationTest") + public void test_AmazonWebServicesExampleWithTags() throws Exception { + Workspace workspace = WorkspaceUtils.loadWorkspaceFromJson(new File("./src/test/resources/amazon-web-services.json")); + ThemeUtils.loadThemes(workspace); + workspace.getViews().getDeploymentViews().iterator().next().enableAutomaticLayout(AutomaticLayout.RankDirection.LeftRight, 300, 300); + workspace.getViews().getConfiguration().addProperty(C4PlantUMLExporter.C4PLANTUML_TAGS_PROPERTY, "true"); + + C4PlantUMLExporter exporter = new C4PlantUMLExporter(); + Collection diagrams = exporter.export(workspace); + assertEquals(1, diagrams.size()); + + Diagram diagram = diagrams.stream().findFirst().get(); + assertEquals(""" + @startuml + title Deployment View: X - Live + + set separator none + left to right direction + + + + !include + !include + !include + !include + + AddElementTag("Amazon Web Services - RDS", $bgColor="#ffffff", $borderColor="#3b48cc", $fontColor="#3b48cc", $sprite="img:https://static.structurizr.com/themes/amazon-web-services-2020.04.30/amazon-rds.png{scale=0.1}", $shadowing="", $borderStyle="solid") + AddElementTag("Amazon Web Services - Auto Scaling", $bgColor="#ffffff", $borderColor="#cc2264", $fontColor="#cc2264", $sprite="img:https://static.structurizr.com/themes/amazon-web-services-2020.04.30/aws-auto-scaling.png{scale=0.1}", $shadowing="", $borderStyle="solid") + AddElementTag("Amazon Web Services - Route 53", $bgColor="#ffffff", $borderColor="#693cc5", $fontColor="#693cc5", $sprite="img:https://static.structurizr.com/themes/amazon-web-services-2020.04.30/amazon-route-53.png{scale=0.1}", $shadowing="", $borderStyle="solid") + AddElementTag("Amazon Web Services - EC2", $bgColor="#ffffff", $borderColor="#d86613", $fontColor="#d86613", $sprite="img:https://static.structurizr.com/themes/amazon-web-services-2020.04.30/amazon-ec2.png{scale=0.1}", $shadowing="", $borderStyle="solid") + AddElementTag("Amazon Web Services - Region", $bgColor="#ffffff", $borderColor="#147eba", $fontColor="#147eba", $sprite="img:https://static.structurizr.com/themes/amazon-web-services-2020.04.30/region.png{scale=0.21428571428571427}", $shadowing="", $borderStyle="solid") + AddElementTag("Amazon Web Services - Elastic Load Balancing", $bgColor="#ffffff", $borderColor="#693cc5", $fontColor="#693cc5", $sprite="img:https://static.structurizr.com/themes/amazon-web-services-2020.04.30/elastic-load-balancing.png{scale=0.1}", $shadowing="", $borderStyle="solid") + AddElementTag("Application", $bgColor="#ffffff", $borderColor="#444444", $fontColor="#444444", $sprite="", $shadowing="", $borderStyle="solid") + AddElementTag("Amazon Web Services - RDS MySQL instance", $bgColor="#ffffff", $borderColor="#3b48cc", $fontColor="#3b48cc", $sprite="img:https://static.structurizr.com/themes/amazon-web-services-2020.04.30/amazon-rds-mysql-instance.png{scale=0.15}", $shadowing="", $borderStyle="solid") + AddElementTag("Amazon Web Services - Cloud", $bgColor="#ffffff", $borderColor="#232f3e", $fontColor="#232f3e", $sprite="img:https://static.structurizr.com/themes/amazon-web-services-2020.04.30/aws-cloud.png{scale=0.21428571428571427}", $shadowing="", $borderStyle="solid") + AddElementTag("Database", $bgColor="#ffffff", $borderColor="#444444", $fontColor="#444444", $sprite="", $shadowing="", $borderStyle="solid") + + AddRelTag("Relationship", $textColor="#444444", $lineColor="#444444", $lineStyle = DashedLine()) + + Deployment_Node(Live.AmazonWebServices, "Amazon Web Services", $type="", $descr="", $tags="Amazon Web Services - Cloud", $link="") { + Deployment_Node(Live.AmazonWebServices.USEast1, "US-East-1", $type="", $descr="", $tags="Amazon Web Services - Region", $link="") { + Deployment_Node(Live.AmazonWebServices.USEast1.Autoscalinggroup, "Autoscaling group", $type="", $descr="", $tags="Amazon Web Services - Auto Scaling", $link="") { + Deployment_Node(Live.AmazonWebServices.USEast1.Autoscalinggroup.AmazonEC2Ubuntuserver, "Amazon EC2 - Ubuntu server", $type="", $descr="", $tags="Amazon Web Services - EC2", $link="") { + Container(Live.AmazonWebServices.USEast1.Autoscalinggroup.AmazonEC2Ubuntuserver.WebApplication_1, "Web Application", $techn="Java and Spring Boot", $descr="", $tags="Application", $link="") + } + + } + + Deployment_Node(Live.AmazonWebServices.USEast1.AmazonRDS, "Amazon RDS", $type="", $descr="", $tags="Amazon Web Services - RDS", $link="") { + Deployment_Node(Live.AmazonWebServices.USEast1.AmazonRDS.MySQL, "MySQL", $type="", $descr="", $tags="Amazon Web Services - RDS MySQL instance", $link="") { + ContainerDb(Live.AmazonWebServices.USEast1.AmazonRDS.MySQL.DatabaseSchema_1, "Database Schema", $techn="", $descr="", $tags="Database", $link="") + } + + } + + Deployment_Node(Live.AmazonWebServices.USEast1.DNSrouter, "DNS router", $type="Route 53", $descr="Routes incoming requests based upon domain name.", $tags="Amazon Web Services - Route 53", $link="") + Deployment_Node(Live.AmazonWebServices.USEast1.LoadBalancer, "Load Balancer", $type="Elastic Load Balancer", $descr="Automatically distributes incoming application traffic.", $tags="Amazon Web Services - Elastic Load Balancing", $link="") + } + + } + + Rel(Live.AmazonWebServices.USEast1.LoadBalancer, Live.AmazonWebServices.USEast1.Autoscalinggroup.AmazonEC2Ubuntuserver.WebApplication_1, "Forwards requests to", $techn="HTTPS", $tags="Relationship", $link="") + Rel(Live.AmazonWebServices.USEast1.Autoscalinggroup.AmazonEC2Ubuntuserver.WebApplication_1, Live.AmazonWebServices.USEast1.AmazonRDS.MySQL.DatabaseSchema_1, "Reads from and writes to", $techn="MySQL Protocol/SSL", $tags="Relationship", $link="") + Rel(Live.AmazonWebServices.USEast1.DNSrouter, Live.AmazonWebServices.USEast1.LoadBalancer, "Forwards requests to", $techn="HTTPS", $tags="Relationship", $link="") + + SHOW_LEGEND(true) + hide stereotypes + @enduml""", diagram.getDefinition()); + } + @Test public void test_BigBankPlcExample() throws Exception { Workspace workspace = WorkspaceUtils.loadWorkspaceFromJson(new File("./src/test/resources/big-bank-plc.json")); @@ -482,152 +621,13 @@ public void test_BigBankPlcExample() throws Exception { @enduml""", diagram.getDefinition()); } - @Test - @Tag("IntegrationTest") - public void test_AmazonWebServicesExampleWithoutTags() throws Exception { - Workspace workspace = WorkspaceUtils.loadWorkspaceFromJson(new File("./src/test/resources/amazon-web-services.json")); - ThemeUtils.loadThemes(workspace); - workspace.getViews().getDeploymentViews().iterator().next().enableAutomaticLayout(AutomaticLayout.RankDirection.LeftRight, 300, 300); - workspace.getViews().getViews().forEach(v -> v.addProperty(C4PlantUMLExporter.C4PLANTUML_TAGS_PROPERTY, "false")); - - C4PlantUMLExporter exporter = new C4PlantUMLExporter(); - Collection diagrams = exporter.export(workspace); - assertEquals(1, diagrams.size()); - - Diagram diagram = diagrams.stream().findFirst().get(); - assertEquals(""" - @startuml - title Deployment View: X - Live - - set separator none - left to right direction - - - - !include - !include - !include - !include - - Deployment_Node(Live.AmazonWebServices, "Amazon Web Services", $type="", $descr="", $tags="", $link="") { - Deployment_Node(Live.AmazonWebServices.USEast1, "US-East-1", $type="", $descr="", $tags="", $link="") { - Deployment_Node(Live.AmazonWebServices.USEast1.Autoscalinggroup, "Autoscaling group", $type="", $descr="", $tags="", $link="") { - Deployment_Node(Live.AmazonWebServices.USEast1.Autoscalinggroup.AmazonEC2Ubuntuserver, "Amazon EC2 - Ubuntu server", $type="", $descr="", $tags="", $link="") { - Container(Live.AmazonWebServices.USEast1.Autoscalinggroup.AmazonEC2Ubuntuserver.WebApplication_1, "Web Application", $techn="Java and Spring Boot", $descr="", $tags="", $link="") - } - - } - - Deployment_Node(Live.AmazonWebServices.USEast1.AmazonRDS, "Amazon RDS", $type="", $descr="", $tags="", $link="") { - Deployment_Node(Live.AmazonWebServices.USEast1.AmazonRDS.MySQL, "MySQL", $type="", $descr="", $tags="", $link="") { - ContainerDb(Live.AmazonWebServices.USEast1.AmazonRDS.MySQL.DatabaseSchema_1, "Database Schema", $techn="", $descr="", $tags="", $link="") - } - - } - - Deployment_Node(Live.AmazonWebServices.USEast1.DNSrouter, "DNS router", $type="Route 53", $descr="Routes incoming requests based upon domain name.", $tags="", $link="") - Deployment_Node(Live.AmazonWebServices.USEast1.LoadBalancer, "Load Balancer", $type="Elastic Load Balancer", $descr="Automatically distributes incoming application traffic.", $tags="", $link="") - } - - } - - Rel(Live.AmazonWebServices.USEast1.LoadBalancer, Live.AmazonWebServices.USEast1.Autoscalinggroup.AmazonEC2Ubuntuserver.WebApplication_1, "Forwards requests to", $techn="HTTPS", $tags="", $link="") - Rel(Live.AmazonWebServices.USEast1.Autoscalinggroup.AmazonEC2Ubuntuserver.WebApplication_1, Live.AmazonWebServices.USEast1.AmazonRDS.MySQL.DatabaseSchema_1, "Reads from and writes to", $techn="MySQL Protocol/SSL", $tags="", $link="") - Rel(Live.AmazonWebServices.USEast1.DNSrouter, Live.AmazonWebServices.USEast1.LoadBalancer, "Forwards requests to", $techn="HTTPS", $tags="", $link="") - - SHOW_LEGEND(true) - hide stereotypes - @enduml""", diagram.getDefinition()); - } - - @Test - @Tag("IntegrationTest") - public void test_AmazonWebServicesExampleWithTags() throws Exception { - Workspace workspace = WorkspaceUtils.loadWorkspaceFromJson(new File("./src/test/resources/amazon-web-services.json")); - ThemeUtils.loadThemes(workspace); - workspace.getViews().getDeploymentViews().iterator().next().enableAutomaticLayout(AutomaticLayout.RankDirection.LeftRight, 300, 300); - workspace.getViews().getConfiguration().addProperty(C4PlantUMLExporter.C4PLANTUML_TAGS_PROPERTY, "true"); - - C4PlantUMLExporter exporter = new C4PlantUMLExporter(); - Collection diagrams = exporter.export(workspace); - assertEquals(1, diagrams.size()); - - Diagram diagram = diagrams.stream().findFirst().get(); - assertEquals(""" - @startuml - title Deployment View: X - Live - - set separator none - left to right direction - - - - !include - !include - !include - !include - - AddElementTag("Amazon Web Services - RDS", $bgColor="#ffffff", $borderColor="#3b48cc", $fontColor="#3b48cc", $sprite="img:https://static.structurizr.com/themes/amazon-web-services-2020.04.30/amazon-rds.png{scale=0.1}", $shadowing="", $borderStyle="solid") - AddElementTag("Amazon Web Services - Auto Scaling", $bgColor="#ffffff", $borderColor="#cc2264", $fontColor="#cc2264", $sprite="img:https://static.structurizr.com/themes/amazon-web-services-2020.04.30/aws-auto-scaling.png{scale=0.1}", $shadowing="", $borderStyle="solid") - AddElementTag("Amazon Web Services - Route 53", $bgColor="#ffffff", $borderColor="#693cc5", $fontColor="#693cc5", $sprite="img:https://static.structurizr.com/themes/amazon-web-services-2020.04.30/amazon-route-53.png{scale=0.1}", $shadowing="", $borderStyle="solid") - AddElementTag("Amazon Web Services - EC2", $bgColor="#ffffff", $borderColor="#d86613", $fontColor="#d86613", $sprite="img:https://static.structurizr.com/themes/amazon-web-services-2020.04.30/amazon-ec2.png{scale=0.1}", $shadowing="", $borderStyle="solid") - AddElementTag("Amazon Web Services - Region", $bgColor="#ffffff", $borderColor="#147eba", $fontColor="#147eba", $sprite="img:https://static.structurizr.com/themes/amazon-web-services-2020.04.30/region.png{scale=0.21428571428571427}", $shadowing="", $borderStyle="solid") - AddElementTag("Amazon Web Services - Elastic Load Balancing", $bgColor="#ffffff", $borderColor="#693cc5", $fontColor="#693cc5", $sprite="img:https://static.structurizr.com/themes/amazon-web-services-2020.04.30/elastic-load-balancing.png{scale=0.1}", $shadowing="", $borderStyle="solid") - AddElementTag("Application", $bgColor="#ffffff", $borderColor="#444444", $fontColor="#444444", $sprite="", $shadowing="", $borderStyle="solid") - AddElementTag("Amazon Web Services - RDS MySQL instance", $bgColor="#ffffff", $borderColor="#3b48cc", $fontColor="#3b48cc", $sprite="img:https://static.structurizr.com/themes/amazon-web-services-2020.04.30/amazon-rds-mysql-instance.png{scale=0.15}", $shadowing="", $borderStyle="solid") - AddElementTag("Amazon Web Services - Cloud", $bgColor="#ffffff", $borderColor="#232f3e", $fontColor="#232f3e", $sprite="img:https://static.structurizr.com/themes/amazon-web-services-2020.04.30/aws-cloud.png{scale=0.21428571428571427}", $shadowing="", $borderStyle="solid") - AddElementTag("Database", $bgColor="#ffffff", $borderColor="#444444", $fontColor="#444444", $sprite="", $shadowing="", $borderStyle="solid") - - AddRelTag("Relationship", $textColor="#444444", $lineColor="#444444", $lineStyle = DashedLine()) - - Deployment_Node(Live.AmazonWebServices, "Amazon Web Services", $type="", $descr="", $tags="Amazon Web Services - Cloud", $link="") { - Deployment_Node(Live.AmazonWebServices.USEast1, "US-East-1", $type="", $descr="", $tags="Amazon Web Services - Region", $link="") { - Deployment_Node(Live.AmazonWebServices.USEast1.Autoscalinggroup, "Autoscaling group", $type="", $descr="", $tags="Amazon Web Services - Auto Scaling", $link="") { - Deployment_Node(Live.AmazonWebServices.USEast1.Autoscalinggroup.AmazonEC2Ubuntuserver, "Amazon EC2 - Ubuntu server", $type="", $descr="", $tags="Amazon Web Services - EC2", $link="") { - Container(Live.AmazonWebServices.USEast1.Autoscalinggroup.AmazonEC2Ubuntuserver.WebApplication_1, "Web Application", $techn="Java and Spring Boot", $descr="", $tags="Application", $link="") - } - - } - - Deployment_Node(Live.AmazonWebServices.USEast1.AmazonRDS, "Amazon RDS", $type="", $descr="", $tags="Amazon Web Services - RDS", $link="") { - Deployment_Node(Live.AmazonWebServices.USEast1.AmazonRDS.MySQL, "MySQL", $type="", $descr="", $tags="Amazon Web Services - RDS MySQL instance", $link="") { - ContainerDb(Live.AmazonWebServices.USEast1.AmazonRDS.MySQL.DatabaseSchema_1, "Database Schema", $techn="", $descr="", $tags="Database", $link="") - } - - } - - Deployment_Node(Live.AmazonWebServices.USEast1.DNSrouter, "DNS router", $type="Route 53", $descr="Routes incoming requests based upon domain name.", $tags="Amazon Web Services - Route 53", $link="") - Deployment_Node(Live.AmazonWebServices.USEast1.LoadBalancer, "Load Balancer", $type="Elastic Load Balancer", $descr="Automatically distributes incoming application traffic.", $tags="Amazon Web Services - Elastic Load Balancing", $link="") - } - - } - - Rel(Live.AmazonWebServices.USEast1.LoadBalancer, Live.AmazonWebServices.USEast1.Autoscalinggroup.AmazonEC2Ubuntuserver.WebApplication_1, "Forwards requests to", $techn="HTTPS", $tags="Relationship", $link="") - Rel(Live.AmazonWebServices.USEast1.Autoscalinggroup.AmazonEC2Ubuntuserver.WebApplication_1, Live.AmazonWebServices.USEast1.AmazonRDS.MySQL.DatabaseSchema_1, "Reads from and writes to", $techn="MySQL Protocol/SSL", $tags="Relationship", $link="") - Rel(Live.AmazonWebServices.USEast1.DNSrouter, Live.AmazonWebServices.USEast1.LoadBalancer, "Forwards requests to", $techn="HTTPS", $tags="Relationship", $link="") - - SHOW_LEGEND(true) - hide stereotypes - @enduml""", diagram.getDefinition()); - } - @Test public void test_GroupsExample() throws Exception { Workspace workspace = WorkspaceUtils.loadWorkspaceFromJson(new File("./src/test/resources/groups.json")); C4PlantUMLExporter exporter = new C4PlantUMLExporter(); Collection diagrams = exporter.export(workspace); - assertEquals(3, diagrams.size()); + assertEquals(4, diagrams.size()); Diagram diagram = diagrams.stream().filter(md -> md.getKey().equals("SystemLandscape")).findFirst().get(); assertEquals(""" @@ -711,40 +711,41 @@ public void test_GroupsExample() throws Exception { diagram = diagrams.stream().filter(md -> md.getKey().equals("Components")).findFirst().get(); assertEquals(""" - @startuml - title Component View: D - F - - set separator none - top to bottom direction - - - - !include - !include - !include - - System(C, "C", $descr="", $tags="", $link="") - - Container_Boundary("D.F_boundary", "F", $tags="") { - AddBoundaryTag("Group 5", $borderColor="#cccccc", $fontColor="#cccccc", $borderStyle="dashed") - Boundary(group_1, "Group 5", $tags="Group 5") { - Component(D.F.H, "H", $techn="", $descr="", $tags="", $link="") - } - - Component(D.F.G, "G", $techn="", $descr="", $tags="", $link="") - } - - Rel(C, D.F.G, "", $techn="", $tags="", $link="") - Rel(C, D.F.H, "", $techn="", $tags="", $link="") - - SHOW_LEGEND(true) - hide stereotypes - @enduml""", diagram.getDefinition()); + @startuml + title Component View: D - F + + set separator none + top to bottom direction + + + + !include + !include + !include + + System(C, "C", $descr="", $tags="", $link="") + + Container_Boundary("D.F_boundary", "F", $tags="") { + AddBoundaryTag("Group 5", $borderColor="#cccccc", $fontColor="#cccccc", $borderStyle="dashed") + Boundary(group_1, "Group 5", $tags="Group 5") { + Component(D.F.H, "H", $techn="", $descr="", $tags="", $link="") + } + + Component(D.F.G, "G", $techn="", $descr="", $tags="", $link="") + } + + Rel(C, D.F.G, "", $techn="", $tags="", $link="") + Rel(C, D.F.H, "", $techn="", $tags="", $link="") + Rel(D.F.G, D.F.H, "", $techn="", $tags="", $link="") + + SHOW_LEGEND(true) + hide stereotypes + @enduml""", diagram.getDefinition()); } @Test diff --git a/structurizr-export/src/test/java/com/structurizr/export/plantuml/StructurizrPlantUMLDiagramExporterTests.java b/structurizr-export/src/test/java/com/structurizr/export/plantuml/StructurizrPlantUMLDiagramExporterTests.java index 116cdf14..1f7a8911 100644 --- a/structurizr-export/src/test/java/com/structurizr/export/plantuml/StructurizrPlantUMLDiagramExporterTests.java +++ b/structurizr-export/src/test/java/com/structurizr/export/plantuml/StructurizrPlantUMLDiagramExporterTests.java @@ -1,3174 +1,3517 @@ -package com.structurizr.export.plantuml; - -import com.structurizr.Workspace; -import com.structurizr.export.AbstractExporterTests; -import com.structurizr.export.Diagram; -import com.structurizr.model.*; -import com.structurizr.util.WorkspaceUtils; -import com.structurizr.view.*; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; - -import java.io.File; -import java.util.Collection; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class StructurizrPlantUMLDiagramExporterTests extends AbstractExporterTests { - - @Test - public void systemLandscapeView_NoStyling_Light() { - Workspace workspace = new Workspace("Name", "Description"); - SoftwareSystem a = workspace.getModel().addSoftwareSystem("A", "Description."); - SoftwareSystem b = workspace.getModel().addSoftwareSystem("B", "Description."); - a.uses(b, "Description", "Technology"); - - SystemLandscapeView view = workspace.getViews().createSystemLandscapeView("key", "Description"); - view.addDefaultElements(); - - StructurizrPlantUMLExporter exporter = new StructurizrPlantUMLExporter(ColorScheme.Light); - Diagram diagram = exporter.export(view); - - assertEquals(""" - @startuml - title System Landscape View\\nDescription - - set separator none - top to bottom direction - hide stereotype - - - - rectangle "==A\\n[Software System]\\n\\nDescription." <> as A - rectangle "==B\\n[Software System]\\n\\nDescription." <> as B - - A --> B <> : "Description\\n[Technology]" - - @enduml""", diagram.getDefinition()); - - assertEquals(""" - @startuml - - set separator none - hide stereotype - - - - rectangle "==Element" <> - - rectangle "." <<.Element-Transparent>> as 1 - 1 --> 1 <> : "Relationship" - - @enduml""", diagram.getLegend().getDefinition()); - } - - @Test - public void systemLandscapeView_NoStyling_Dark() { - Workspace workspace = new Workspace("Name", "Description"); - SoftwareSystem a = workspace.getModel().addSoftwareSystem("A", "Description."); - SoftwareSystem b = workspace.getModel().addSoftwareSystem("B", "Description."); - a.uses(b, "Description", "Technology"); - - SystemLandscapeView view = workspace.getViews().createSystemLandscapeView("key", "Description"); - view.addDefaultElements(); - - StructurizrPlantUMLExporter exporter = new StructurizrPlantUMLExporter(ColorScheme.Dark); - Diagram diagram = exporter.export(view); - - assertEquals(""" - @startuml - title System Landscape View\\nDescription - - set separator none - top to bottom direction - hide stereotype - - - - rectangle "==A\\n[Software System]\\n\\nDescription." <> as A - rectangle "==B\\n[Software System]\\n\\nDescription." <> as B - - A --> B <> : "Description\\n[Technology]" - - @enduml""", diagram.getDefinition()); - - assertEquals(""" - @startuml - - set separator none - hide stereotype - - - - rectangle "==Element" <> - - rectangle "." <<.Element-Transparent>> as 1 - 1 --> 1 <> : "Relationship" - - @enduml""", diagram.getLegend().getDefinition()); - } - - @Test - public void systemContextView_NoStyling_Light() { - Workspace workspace = new Workspace("Name", "Description"); - SoftwareSystem a = workspace.getModel().addSoftwareSystem("A", "Description."); - SoftwareSystem b = workspace.getModel().addSoftwareSystem("B", "Description."); - a.uses(b, "Description", "Technology"); - - SystemContextView view = workspace.getViews().createSystemContextView(a, "key", "Description"); - view.addDefaultElements(); - - StructurizrPlantUMLExporter exporter = new StructurizrPlantUMLExporter(ColorScheme.Light); - Diagram diagram = exporter.export(view); - - assertEquals(""" - @startuml - title System Context View: A\\nDescription - - set separator none - top to bottom direction - hide stereotype - - - - rectangle "==A\\n[Software System]\\n\\nDescription." <> as A - rectangle "==B\\n[Software System]\\n\\nDescription." <> as B - - A --> B <> : "Description\\n[Technology]" - - @enduml""", diagram.getDefinition()); - } - - @Test - public void containerView_NoStyling_Light() { - Workspace workspace = new Workspace("Name", "Description"); - SoftwareSystem a = workspace.getModel().addSoftwareSystem("Software System A", "Description."); - Container container1 = a.addContainer("Container 1", "Description", "Technology"); - Container container2 = a.addContainer("Container 2", "Description", "Technology"); - container1.uses(container2, "Description", "Technology"); - - ContainerView view = workspace.getViews().createContainerView(a, "key", "Description"); - view.addDefaultElements(); - - StructurizrPlantUMLExporter exporter = new StructurizrPlantUMLExporter(ColorScheme.Light); - Diagram diagram = exporter.export(view); - - assertEquals(""" - @startuml - title Container View: Software System A\\nDescription - - set separator none - top to bottom direction - hide stereotype - - - - rectangle "Software System A\\n[Software System]" <> { - rectangle "==Container 1\\n[Container: Technology]\\n\\nDescription" <> as SoftwareSystemA.Container1 - rectangle "==Container 2\\n[Container: Technology]\\n\\nDescription" <> as SoftwareSystemA.Container2 - } - - SoftwareSystemA.Container1 --> SoftwareSystemA.Container2 <> : "Description\\n[Technology]" - - @enduml""", diagram.getDefinition()); - } - - @Test - public void componentView_NoStyling_Light() { - Workspace workspace = new Workspace("Name", "Description"); - SoftwareSystem a = workspace.getModel().addSoftwareSystem("Software System", "Description."); - Container container = a.addContainer("Container", "Description", "Technology"); - Component component1 = container.addComponent("Component 1", "Description", "Technology"); - Component component2 = container.addComponent("Component 2", "Description", "Technology"); - component1.uses(component2, "Description", "Technology"); - - ComponentView view = workspace.getViews().createComponentView(container, "key", "Description"); - view.addDefaultElements(); - - StructurizrPlantUMLExporter exporter = new StructurizrPlantUMLExporter(ColorScheme.Light); - Diagram diagram = exporter.export(view); - - assertEquals(""" - @startuml - title Component View: Software System - Container\\nDescription - - set separator none - top to bottom direction - hide stereotype - - - - rectangle "Container\\n[Container: Technology]" <> { - rectangle "==Component 1\\n[Component: Technology]\\n\\nDescription" <> as SoftwareSystem.Container.Component1 - rectangle "==Component 2\\n[Component: Technology]\\n\\nDescription" <> as SoftwareSystem.Container.Component2 - } - - SoftwareSystem.Container.Component1 --> SoftwareSystem.Container.Component2 <> : "Description\\n[Technology]" - - @enduml""", diagram.getDefinition()); - } - - @Test - public void deploymentView_NoStyling_Light() { - Workspace workspace = new Workspace("Name", "Description"); - SoftwareSystem a = workspace.getModel().addSoftwareSystem("A"); - DeploymentNode node1 = workspace.getModel().addDeploymentNode("Node 1"); - node1.addDeploymentNode("Node 2").add(a); - node1.addDeploymentNode("Node 3").addInfrastructureNode("Infrastructure Node"); - - DeploymentView view = workspace.getViews().createDeploymentView("deployment", "Default"); - view.addDefaultElements(); - - StructurizrPlantUMLExporter exporter = new StructurizrPlantUMLExporter(ColorScheme.Light); - Diagram diagram = exporter.export(view); - - assertEquals(""" - @startuml - title Deployment View: Default\\nDefault - - set separator none - top to bottom direction - hide stereotype - - - - rectangle "Node 1\\n[Deployment Node]" <> as Default.Node1 { - rectangle "Node 2\\n[Deployment Node]" <> as Default.Node1.Node2 { - rectangle "==A\\n[Software System]" <> as Default.Node1.Node2.A_1 - } - - rectangle "Node 3\\n[Deployment Node]" <> as Default.Node1.Node3 { - rectangle "==Infrastructure Node\\n[Infrastructure Node]" <> as Default.Node1.Node3.InfrastructureNode - } - - } - - @enduml""", diagram.getDefinition()); - } - - @Test - public void dynamicView_CollaborationStyle_NoStyling_Light() { - Workspace workspace = new Workspace("Name", "Description"); - SoftwareSystem a = workspace.getModel().addSoftwareSystem("A"); - SoftwareSystem b = workspace.getModel().addSoftwareSystem("B"); - - a.uses(b, "Uses"); - - DynamicView view = workspace.getViews().createDynamicView("key", "Description"); - view.add(a, b); - - StructurizrPlantUMLExporter exporter = new StructurizrPlantUMLExporter(); - Diagram diagram = exporter.export(view); - - assertEquals(""" - @startuml - title Dynamic View\\nDescription - - set separator none - top to bottom direction - hide stereotype - - - - rectangle "==A\\n[Software System]" <> as A - rectangle "==B\\n[Software System]" <> as B - - A --> B <> : "1: Uses" - - @enduml""", diagram.getDefinition()); - } - - @Test - public void dynamicView_CollaborationStyle_Frames() { - Workspace workspace = new Workspace("Name", "Description"); - SoftwareSystem a = workspace.getModel().addSoftwareSystem("A"); - SoftwareSystem b = workspace.getModel().addSoftwareSystem("B"); - SoftwareSystem c = workspace.getModel().addSoftwareSystem("C"); - - a.uses(b, "Uses"); - b.uses(c, "Uses"); - - DynamicView view = workspace.getViews().createDynamicView("key", "Description"); - view.add(a, b); - view.add(b, c); - view.addProperty(StructurizrPlantUMLExporter.PLANTUML_ANIMATION_PROPERTY, "true"); - - StructurizrPlantUMLExporter exporter = new StructurizrPlantUMLExporter(); - List frames = exporter.export(view).getFrames(); - assertEquals(2, frames.size()); - - assertEquals(""" - @startuml - title Dynamic View\\nDescription - - set separator none - top to bottom direction - hide stereotype - - - - rectangle "==A\\n[Software System]" <> as A - rectangle "==B\\n[Software System]" <> as B - rectangle "==C\\n[Software System]" <> as C - hide C - - A --> B <> : "1: Uses" - B --> C <> : "2: Uses" - - @enduml""", frames.get(0).getDefinition()); - - assertEquals(""" - @startuml - title Dynamic View\\nDescription - - set separator none - top to bottom direction - hide stereotype - - - - rectangle "==A\\n[Software System]" <> as A - hide A - rectangle "==B\\n[Software System]" <> as B - rectangle "==C\\n[Software System]" <> as C - - A --> B <> : "1: Uses" - B --> C <> : "2: Uses" - - @enduml""", frames.get(1).getDefinition()); - } - - @Test - public void dynamicView_SequenceStyle_NoStyling_Light() { - Workspace workspace = new Workspace("Name", "Description"); - SoftwareSystem a = workspace.getModel().addSoftwareSystem("A"); - SoftwareSystem b = workspace.getModel().addSoftwareSystem("B"); - - a.uses(b, "Uses", "JSON/HTTPS"); - - DynamicView view = workspace.getViews().createDynamicView("key", "Description"); - view.add(a, b); - view.addProperty(StructurizrPlantUMLExporter.PLANTUML_SEQUENCE_DIAGRAM_PROPERTY, "true"); - - StructurizrPlantUMLExporter exporter = new StructurizrPlantUMLExporter(); - Diagram diagram = exporter.export(view); - - assertEquals(""" - @startuml - title Dynamic View\\nDescription - - set separator none - hide stereotype - - - - participant "A\\n[Software System]" as A <> #ffffff - participant "B\\n[Software System]" as B <> #ffffff - - A -> B <> : 1: Uses\\n[JSON/HTTPS] - - @enduml""", diagram.getDefinition()); - - assertEquals(""" - @startuml - - set separator none - hide stereotype - - - - rectangle "==Element" <> - - rectangle "." <<.Element-Transparent>> as 1 - 1 --> 1 <> : "Relationship" - - @enduml""", diagram.getLegend().getDefinition()); - } - - @Test - public void groups() throws Exception { - Workspace workspace = WorkspaceUtils.loadWorkspaceFromJson(new File("./src/test/resources/groups.json")); - - StructurizrPlantUMLExporter exporter = new StructurizrPlantUMLExporter(); - Collection diagrams = exporter.export(workspace); - assertEquals(3, diagrams.size()); - - Diagram diagram = diagrams.stream().filter(md -> md.getKey().equals("SystemLandscape")).findFirst().get(); - assertEquals(""" - @startuml - title System Landscape View - - set separator none - top to bottom direction - skinparam ranksep 60 - skinparam nodesep 30 - hide stereotype - - - - rectangle "Group 1" <> as groupR3JvdXAgMQ== { - rectangle "==B\\n[Software System]" <> as B - } - - rectangle "Group 2" <> as groupR3JvdXAgMg== { - rectangle "==C\\n[Software System]" <> as C - rectangle "Group 3" <> as groupR3JvdXAgMi9Hcm91cCAz { - rectangle "==D\\n[Software System]" <> as D - } - - } - - rectangle "==A\\n[Software System]" <> as A - - B --> C <> : "" - C --> D <> : "" - A --> B <> : "" - - @enduml""", diagram.getDefinition()); - - diagram = diagrams.stream().filter(md -> md.getKey().equals("Containers")).findFirst().get(); - assertEquals(""" - @startuml - title Container View: D - - set separator none - top to bottom direction - skinparam ranksep 60 - skinparam nodesep 30 - hide stereotype - - - - rectangle "==C\\n[Software System]" <> as C - - rectangle "D\\n[Software System]" <> { - rectangle "Group 4" <> as groupR3JvdXAgNA== { - rectangle "==F\\n[Container]" <> as D.F - } - - rectangle "==E\\n[Container]" <> as D.E - } - - C --> D.E <> : "" - C --> D.F <> : "" - - @enduml""", diagram.getDefinition()); - - diagram = diagrams.stream().filter(md -> md.getKey().equals("Components")).findFirst().get(); - assertEquals(""" - @startuml - title Component View: D - F - - set separator none - top to bottom direction - skinparam ranksep 60 - skinparam nodesep 30 - hide stereotype - - - - rectangle "==C\\n[Software System]" <> as C - - rectangle "F\\n[Container]" <> { - rectangle "Group 5" <> as groupR3JvdXAgNQ== { - rectangle "==H\\n[Component]" <> as D.F.H - } - - rectangle "==G\\n[Component]" <> as D.F.G - } - - C --> D.F.G <> : "" - C --> D.F.H <> : "" - - @enduml""", diagram.getDefinition()); - } - - @Test - public void nestedGroups() { - Workspace workspace = new Workspace("Name", "Description"); - workspace.getModel().addProperty("structurizr.groupSeparator", "/"); - - SoftwareSystem a = workspace.getModel().addSoftwareSystem("Team 1"); - a.setGroup("Organisation 1/Department 1/Team 1"); - - SoftwareSystem b = workspace.getModel().addSoftwareSystem("Team 2"); - b.setGroup("Organisation 1/Department 1/Team 2"); - - SoftwareSystem c = workspace.getModel().addSoftwareSystem("Organisation 1"); - c.setGroup("Organisation 1"); - - SoftwareSystem d = workspace.getModel().addSoftwareSystem("Organisation 2"); - d.setGroup("Organisation 2"); - - SoftwareSystem e = workspace.getModel().addSoftwareSystem("Department 1"); - e.setGroup("Organisation 1/Department 1"); - - SystemLandscapeView view = workspace.getViews().createSystemLandscapeView("SystemLandscape"); - view.addAllElements(); - - workspace.getViews().getConfiguration().getStyles().addElementStyle("Group:Organisation 1/Department 1/Team 1").color("#ff0000"); - workspace.getViews().getConfiguration().getStyles().addElementStyle("Group:Organisation 1/Department 1/Team 2").color("#0000ff"); - - StructurizrPlantUMLExporter exporter = new StructurizrPlantUMLExporter(); - Collection diagrams = exporter.export(workspace); - - Diagram diagram = diagrams.stream().filter(md -> md.getKey().equals("SystemLandscape")).findFirst().get(); - assertEquals(""" - @startuml - title System Landscape View - - set separator none - top to bottom direction - hide stereotype - - - - rectangle "Organisation 1" <> as groupT3JnYW5pc2F0aW9uIDE= { - rectangle "==Organisation 1\\n[Software System]" <> as Organisation1 - rectangle "Department 1" <> as groupT3JnYW5pc2F0aW9uIDEvRGVwYXJ0bWVudCAx { - rectangle "==Department 1\\n[Software System]" <> as Department1 - rectangle "Team 1" <> as groupT3JnYW5pc2F0aW9uIDEvRGVwYXJ0bWVudCAxL1RlYW0gMQ== { - rectangle "==Team 1\\n[Software System]" <> as Team1 - } - - rectangle "Team 2" <> as groupT3JnYW5pc2F0aW9uIDEvRGVwYXJ0bWVudCAxL1RlYW0gMg== { - rectangle "==Team 2\\n[Software System]" <> as Team2 - } - - } - - } - - rectangle "Organisation 2" <> as groupT3JnYW5pc2F0aW9uIDI= { - rectangle "==Organisation 2\\n[Software System]" <> as Organisation2 - } - - - @enduml""", diagram.getDefinition()); - } - - @Test - public void containerDiagramWithExternalContainers() { - Workspace workspace = new Workspace("Name"); - SoftwareSystem softwareSystem1 = workspace.getModel().addSoftwareSystem("Software System 1"); - Container container1 = softwareSystem1.addContainer("Container 1"); - SoftwareSystem softwareSystem2 = workspace.getModel().addSoftwareSystem("Software System 2"); - Container container2 = softwareSystem2.addContainer("Container 2"); - - container1.uses(container2, "Uses"); - - ContainerView containerView = workspace.getViews().createContainerView(softwareSystem1, "Containers", ""); - containerView.add(container1); - containerView.add(container2); - - Diagram diagram = new StructurizrPlantUMLExporter().export(containerView); - assertEquals(""" - @startuml - title Container View: Software System 1 - - set separator none - top to bottom direction - hide stereotype - - - - rectangle "Software System 1\\n[Software System]" <> { - rectangle "==Container 1\\n[Container]" <> as SoftwareSystem1.Container1 - } - - rectangle "Software System 2\\n[Software System]" <> { - rectangle "==Container 2\\n[Container]" <> as SoftwareSystem2.Container2 - } - - SoftwareSystem1.Container1 --> SoftwareSystem2.Container2 <> : "Uses" - - @enduml""", diagram.getDefinition()); - } - - @Test - public void componentDiagramWithExternalComponents() { - Workspace workspace = new Workspace("Name"); - - SoftwareSystem softwareSystem1 = workspace.getModel().addSoftwareSystem("Software System 1"); - Container container1 = softwareSystem1.addContainer("Container 1"); - Component component1 = container1.addComponent("Component 1"); - Component component2 = container1.addComponent("Component 2"); - - SoftwareSystem softwareSystem2 = workspace.getModel().addSoftwareSystem("Software System 2"); - Container container2 = softwareSystem2.addContainer("Container 2"); - Component component3 = container2.addComponent("Component 3"); - - component1.uses(component2, "Uses"); - component2.uses(component3, "Uses"); - - ComponentView componentView = workspace.getViews().createComponentView(container1, "Components", ""); - componentView.add(component1); - componentView.add(component2); - componentView.add(component3); - - Diagram diagram = new StructurizrPlantUMLExporter().export(componentView); - assertEquals(""" - @startuml - title Component View: Software System 1 - Container 1 - - set separator none - top to bottom direction - hide stereotype - - - - rectangle "Container 1\\n[Container]" <> { - rectangle "==Component 1\\n[Component]" <> as SoftwareSystem1.Container1.Component1 - rectangle "==Component 2\\n[Component]" <> as SoftwareSystem1.Container1.Component2 - } - - rectangle "Container 2\\n[Container]" <> { - rectangle "==Component 3\\n[Component]" <> as SoftwareSystem2.Container2.Component3 - } - - SoftwareSystem1.Container1.Component1 --> SoftwareSystem1.Container1.Component2 <> : "Uses" - SoftwareSystem1.Container1.Component2 --> SoftwareSystem2.Container2.Component3 <> : "Uses" - - @enduml""", diagram.getDefinition()); - } - - @Test - public void componentDiagramWithExternalComponentsAndSoftwareSystemBoundariesIncluded() { - Workspace workspace = new Workspace("Name"); - - SoftwareSystem softwareSystem1 = workspace.getModel().addSoftwareSystem("Software System 1"); - Container container1 = softwareSystem1.addContainer("Container 1"); - Component component1 = container1.addComponent("Component 1"); - Component component2 = container1.addComponent("Component 2"); - - SoftwareSystem softwareSystem2 = workspace.getModel().addSoftwareSystem("Software System 2"); - Container container2 = softwareSystem2.addContainer("Container 2"); - Component component3 = container2.addComponent("Component 3"); - - component1.uses(component2, "Uses"); - component2.uses(component3, "Uses"); - - ComponentView componentView = workspace.getViews().createComponentView(container1, "Components", ""); - componentView.add(component1); - componentView.add(component2); - componentView.add(component3); - componentView.addProperty("structurizr.softwareSystemBoundaries", "true"); - - Diagram diagram = new StructurizrPlantUMLExporter().export(componentView); - assertEquals(""" - @startuml - title Component View: Software System 1 - Container 1 - - set separator none - top to bottom direction - hide stereotype - - - - rectangle "Software System 1\\n[Software System]" <> { - rectangle "Container 1\\n[Container]" <> { - rectangle "==Component 1\\n[Component]" <> as SoftwareSystem1.Container1.Component1 - rectangle "==Component 2\\n[Component]" <> as SoftwareSystem1.Container1.Component2 - } - - } - - rectangle "Software System 2\\n[Software System]" <> { - rectangle "Container 2\\n[Container]" <> { - rectangle "==Component 3\\n[Component]" <> as SoftwareSystem2.Container2.Component3 - } - - } - - SoftwareSystem1.Container1.Component1 --> SoftwareSystem1.Container1.Component2 <> : "Uses" - SoftwareSystem1.Container1.Component2 --> SoftwareSystem2.Container2.Component3 <> : "Uses" - - @enduml""", diagram.getDefinition()); - } - - @Test - public void dynamicView_ExternalContainers() { - Workspace workspace = new Workspace("Name"); - SoftwareSystem softwareSystem1 = workspace.getModel().addSoftwareSystem("Software System 1"); - Container container1 = softwareSystem1.addContainer("Container 1"); - SoftwareSystem softwareSystem2 = workspace.getModel().addSoftwareSystem("Software System 2"); - Container container2 = softwareSystem2.addContainer("Container 2"); - - container1.uses(container2, "Uses"); - - DynamicView dynamicView = workspace.getViews().createDynamicView(softwareSystem1, "Dynamic", ""); - dynamicView.add(container1, container2); - - Diagram diagram = new StructurizrPlantUMLExporter().export(dynamicView); - assertEquals(""" - @startuml - title Dynamic View: Software System 1 - - set separator none - top to bottom direction - hide stereotype - - - - rectangle "Software System 1\\n[Software System]" <> { - rectangle "==Container 1\\n[Container]" <> as SoftwareSystem1.Container1 - } - - rectangle "Software System 2\\n[Software System]" <> { - rectangle "==Container 2\\n[Container]" <> as SoftwareSystem2.Container2 - } - - SoftwareSystem1.Container1 --> SoftwareSystem2.Container2 <> : "1: Uses" - - @enduml""", diagram.getDefinition()); - } - - @Test - public void dynamicView_ExternalComponents() { - Workspace workspace = new Workspace("Name"); - - SoftwareSystem softwareSystem1 = workspace.getModel().addSoftwareSystem("Software System 1"); - Container container1 = softwareSystem1.addContainer("Container 1"); - Component component1 = container1.addComponent("Component 1"); - Component component2 = container1.addComponent("Component 2"); - - SoftwareSystem softwareSystem2 = workspace.getModel().addSoftwareSystem("Software System 2"); - Container container2 = softwareSystem2.addContainer("Container 2"); - Component component3 = container2.addComponent("Component 3"); - - component1.uses(component2, "Uses"); - component2.uses(component3, "Uses"); - - DynamicView dynamicView = workspace.getViews().createDynamicView(container1, "Dynamic", ""); - dynamicView.add(component1, component2); - dynamicView.add(component2, component3); - - Diagram diagram = new StructurizrPlantUMLExporter().export(dynamicView); - assertEquals(""" - @startuml - title Dynamic View: Software System 1 - Container 1 - - set separator none - top to bottom direction - hide stereotype - - - - rectangle "Container 1\\n[Container]" <> { - rectangle "==Component 1\\n[Component]" <> as SoftwareSystem1.Container1.Component1 - rectangle "==Component 2\\n[Component]" <> as SoftwareSystem1.Container1.Component2 - } - - rectangle "Container 2\\n[Container]" <> { - rectangle "==Component 3\\n[Component]" <> as SoftwareSystem2.Container2.Component3 - } - - SoftwareSystem1.Container1.Component1 --> SoftwareSystem1.Container1.Component2 <> : "1: Uses" - SoftwareSystem1.Container1.Component2 --> SoftwareSystem2.Container2.Component3 <> : "2: Uses" - - @enduml""", diagram.getDefinition()); - } - - @Test - public void dynamicView_ExternalComponentsAndSoftwareSystemBoundariesIncluded() { - Workspace workspace = new Workspace("Name"); - - SoftwareSystem softwareSystem1 = workspace.getModel().addSoftwareSystem("Software System 1"); - Container container1 = softwareSystem1.addContainer("Container 1"); - Component component1 = container1.addComponent("Component 1"); - Component component2 = container1.addComponent("Component 2"); - - SoftwareSystem softwareSystem2 = workspace.getModel().addSoftwareSystem("Software System 2"); - Container container2 = softwareSystem2.addContainer("Container 2"); - Component component3 = container2.addComponent("Component 3"); - - component1.uses(component2, "Uses"); - component2.uses(component3, "Uses"); - - DynamicView dynamicView = workspace.getViews().createDynamicView(container1, "Dynamic", ""); - dynamicView.add(component1, component2); - dynamicView.add(component2, component3); - dynamicView.addProperty("structurizr.softwareSystemBoundaries", "true"); - - Diagram diagram = new StructurizrPlantUMLExporter().export(dynamicView); - assertEquals(""" - @startuml - title Dynamic View: Software System 1 - Container 1 - - set separator none - top to bottom direction - hide stereotype - - - - rectangle "Software System 1\\n[Software System]" <> { - rectangle "Container 1\\n[Container]" <> { - rectangle "==Component 1\\n[Component]" <> as SoftwareSystem1.Container1.Component1 - rectangle "==Component 2\\n[Component]" <> as SoftwareSystem1.Container1.Component2 - } - - } - - rectangle "Software System 2\\n[Software System]" <> { - rectangle "Container 2\\n[Container]" <> { - rectangle "==Component 3\\n[Component]" <> as SoftwareSystem2.Container2.Component3 - } - - } - - SoftwareSystem1.Container1.Component1 --> SoftwareSystem1.Container1.Component2 <> : "1: Uses" - SoftwareSystem1.Container1.Component2 --> SoftwareSystem2.Container2.Component3 <> : "2: Uses" - - @enduml""", diagram.getDefinition()); - } - - @Test - public void elementUrls() { - Workspace workspace = new Workspace("Name"); - SoftwareSystem softwareSystem = workspace.getModel().addSoftwareSystem("Software System"); - softwareSystem.setUrl("https://structurizr.com"); - - SystemLandscapeView view = workspace.getViews().createSystemLandscapeView("key"); - view.addDefaultElements(); - - Diagram diagram = new StructurizrPlantUMLExporter().export(view); - assertTrue(diagram.getDefinition().contains("as SoftwareSystem [[https://structurizr.com]]")); - } - - @Test - public void elementInstanceUrl() { - Workspace workspace = new Workspace("Name"); - SoftwareSystem a = workspace.getModel().addSoftwareSystem("A"); - a.setUrl("https://example.com/url1"); - SoftwareSystemInstance aInstance = workspace.getModel().addDeploymentNode("Node").add(a); - - DeploymentView view = workspace.getViews().createDeploymentView("deployment", "Default"); - view.add(aInstance); - - assertTrue(new StructurizrPlantUMLExporter().export(view).getDefinition().contains("as Default.Node.A_1 [[https://example.com/url1]]")); - - aInstance.setUrl("https://example.com/url2"); - assertTrue(new StructurizrPlantUMLExporter().export(view).getDefinition().contains("as Default.Node.A_1 [[https://example.com/url2]]")); - } - - @Test - public void newLineCharacterInElementName() { - Workspace workspace = new Workspace("Name"); - workspace.getModel().addSoftwareSystem("Software\nSystem"); - - SystemLandscapeView view = workspace.getViews().createSystemLandscapeView("key"); - view.addDefaultElements(); - - Diagram diagram = new StructurizrPlantUMLExporter().export(view); - assertEquals(""" - @startuml - title System Landscape View - - set separator none - top to bottom direction - hide stereotype - - - - rectangle "==Software\\nSystem\\n[Software System]" <> as SoftwareSystem - - @enduml""", diagram.getDefinition()); - } - - @Test - public void customView() { - Workspace workspace = new Workspace("Name"); - Model model = workspace.getModel(); - - CustomElement a = model.addCustomElement("A"); - CustomElement b = model.addCustomElement("B", "Custom", "Description"); - a.uses(b, "Uses"); - - CustomView view = workspace.getViews().createCustomView("key", "Title"); - view.addDefaultElements(); - - Diagram diagram = new StructurizrPlantUMLExporter().export(view); - assertEquals(""" - @startuml - title Title - - set separator none - top to bottom direction - hide stereotype - - - - rectangle "==A" <> as 1 - rectangle "==B\\n[Custom]\\n\\nDescription" <> as 2 - - 1 --> 2 <> : "Uses" - - @enduml""", diagram.getDefinition()); - } - - @Test - void renderWorkspaceWithUnicodeElementName() { - Workspace workspace = new Workspace("Name"); - workspace.getModel().addPerson("Пользователь"); - workspace.getViews().createSystemLandscapeView("key", "Description").addDefaultElements(); - - String diagramDefinition = new StructurizrPlantUMLExporter().export(workspace).stream().findFirst().get().getDefinition(); - - assertEquals(""" - @startuml - title System Landscape View\\nDescription - - set separator none - top to bottom direction - hide stereotype - - - - rectangle "==Пользователь\\n[Person]" <> as Пользователь - - @enduml""", diagramDefinition); - } - - @Test - public void font() { - Workspace workspace = new Workspace("Name"); - workspace.getModel().addPerson("User"); - SystemLandscapeView view = workspace.getViews().createSystemLandscapeView("key"); - view.addAllElements(); - workspace.getViews().getConfiguration().getBranding().setFont(new Font("Courier")); - - Diagram diagram = new StructurizrPlantUMLExporter().export(view); - assertTrue(diagram.getDefinition().contains(""" - - - rectangle "Group 1" <> as groupR3JvdXAgMQ== { - rectangle "==A\\n[Software System]" <> as A - } - - rectangle "Group 2" <> as groupR3JvdXAgMg== { - rectangle "==B\\n[Software System]" <> as B - } - - - A --> B <> : "1: Uses" - - @enduml""", diagram.getDefinition()); - } - - @Test - public void dynamicView_SoftwareSystemScopedWithGroups() { - Workspace workspace = new Workspace("Name"); - SoftwareSystem softwareSystemA = workspace.getModel().addSoftwareSystem("A"); - Container containerA = softwareSystemA.addContainer("A"); - containerA.setGroup("Group 1"); - SoftwareSystem softwareSystemB = workspace.getModel().addSoftwareSystem("B"); - Container containerB = softwareSystemB.addContainer("B"); - containerB.setGroup("Group 2"); - containerA.uses(containerB, "Uses"); - - DynamicView view = workspace.getViews().createDynamicView(softwareSystemA, "key"); - view.add(containerA, containerB); - - StructurizrPlantUMLExporter exporter = new StructurizrPlantUMLExporter(); - Diagram diagram = exporter.export(view); - assertEquals(""" - @startuml - title Dynamic View: A - - set separator none - top to bottom direction - hide stereotype - - - - rectangle "A\\n[Software System]" <> { - rectangle "Group 1" <> as groupR3JvdXAgMQ== { - rectangle "==A\\n[Container]" <> as A.A - } - - } - - rectangle "B\\n[Software System]" <> { - rectangle "Group 2" <> as groupR3JvdXAgMg== { - rectangle "==B\\n[Container]" <> as B.B - } - - } - - A.A --> B.B <> : "1: Uses" - - @enduml""", diagram.getDefinition()); - } - - @Test - public void dynamicView_ContainerScopedWithGroups() { - Workspace workspace = new Workspace("Name"); - SoftwareSystem softwareSystemA = workspace.getModel().addSoftwareSystem("A"); - Container containerA = softwareSystemA.addContainer("A"); - Component componentA = containerA.addComponent("A"); - componentA.setGroup("Group 1"); - SoftwareSystem softwareSystemB = workspace.getModel().addSoftwareSystem("B"); - Container containerB = softwareSystemB.addContainer("B"); - Component componentB = containerB.addComponent("B"); - componentB.setGroup("Group 2"); - componentA.uses(componentB, "Uses"); - - DynamicView view = workspace.getViews().createDynamicView(containerA, "key"); - view.add(componentA, componentB); - - StructurizrPlantUMLExporter exporter = new StructurizrPlantUMLExporter(); - Diagram diagram = exporter.export(view); - assertEquals(""" - @startuml - title Dynamic View: A - A - - set separator none - top to bottom direction - hide stereotype - - - - rectangle "A\\n[Container]" <> { - rectangle "Group 1" <> as groupR3JvdXAgMQ== { - rectangle "==A\\n[Component]" <> as A.A.A - } - - } - - rectangle "B\\n[Container]" <> { - rectangle "Group 2" <> as groupR3JvdXAgMg== { - rectangle "==B\\n[Component]" <> as B.B.B - } - - } - - A.A.A --> B.B.B <> : "1: Uses" - - @enduml""", diagram.getDefinition()); - } - - @Test - public void deploymentView_WithGroups() { - Workspace workspace = new Workspace("Name", ""); - SoftwareSystem softwareSystem = workspace.getModel().addSoftwareSystem("Software System"); - - DeploymentNode server1 = workspace.getModel().addDeploymentNode("Server 1"); - server1.setGroup("Group 1"); - - InfrastructureNode infrastructureNode1 = server1.addInfrastructureNode("Infrastructure Node 1"); - InfrastructureNode infrastructureNode2 = server1.addInfrastructureNode("Infrastructure Node 2"); - - SoftwareSystemInstance softwareSystemInstance = server1.add(softwareSystem); - softwareSystemInstance.setGroup("Group 2"); - infrastructureNode2.setGroup("Group 2"); - - DeploymentView view = workspace.getViews().createDeploymentView("key"); - view.add(infrastructureNode1); - view.add(infrastructureNode2); - view.add(softwareSystemInstance); - - StructurizrPlantUMLExporter exporter = new StructurizrPlantUMLExporter(); - Diagram diagram = exporter.export(view); - assertEquals(""" - @startuml - title Deployment View: Default - - set separator none - top to bottom direction - hide stereotype - - - - rectangle "Group 1" <> as groupR3JvdXAgMQ== { - rectangle "Server 1\\n[Deployment Node]" <> as Default.Server1 { - rectangle "Group 2" <> as groupR3JvdXAgMg== { - rectangle "==Infrastructure Node 2\\n[Infrastructure Node]" <> as Default.Server1.InfrastructureNode2 - rectangle "==Software System\\n[Software System]" <> as Default.Server1.SoftwareSystem_1 - } - - rectangle "==Infrastructure Node 1\\n[Infrastructure Node]" <> as Default.Server1.InfrastructureNode1 - } - - } - - @enduml""", diagram.getDefinition()); - } - - @Test - void light_group() { - Workspace workspace = new Workspace("Name"); - SoftwareSystem softwareSystem = workspace.getModel().addSoftwareSystem("Name"); - softwareSystem.setGroup("Name"); - - SystemLandscapeView view = workspace.getViews().createSystemLandscapeView("key"); - view.add(softwareSystem); - - StructurizrPlantUMLExporter exporter = new StructurizrPlantUMLExporter(); - Diagram diagram = exporter.export(view); - assertEquals(""" - @startuml - title System Landscape View - - set separator none - top to bottom direction - hide stereotype - - - - rectangle "Name" <> as groupTmFtZQ== { - rectangle "==Name\\n[Software System]" <> as Name - } - - - @enduml""", diagram.getDefinition()); - } - - @Test - void dark_group() { - Workspace workspace = new Workspace("Name"); - SoftwareSystem softwareSystem = workspace.getModel().addSoftwareSystem("Name"); - softwareSystem.setGroup("Name"); - - SystemLandscapeView view = workspace.getViews().createSystemLandscapeView("key"); - view.add(softwareSystem); - - StructurizrPlantUMLExporter exporter = new StructurizrPlantUMLExporter(ColorScheme.Dark); - Diagram diagram = exporter.export(view); - assertEquals(""" - @startuml - title System Landscape View - - set separator none - top to bottom direction - hide stereotype - - - - rectangle "Name" <> as groupTmFtZQ== { - rectangle "==Name\\n[Software System]" <> as Name - } - - - @enduml""", diagram.getDefinition()); - } - - @Test - @Tag("IntegrationTest") - public void amazonWebServicesExample_Light() throws Exception { - Workspace workspace = WorkspaceUtils.loadWorkspaceFromJson(new File("./src/test/resources/amazon-web-services.json")); - ThemeUtils.loadThemes(workspace); - - StructurizrPlantUMLExporter exporter = new StructurizrPlantUMLExporter(ColorScheme.Light); - Collection diagrams = exporter.export(workspace); - assertEquals(1, diagrams.size()); - - Diagram diagram = diagrams.stream().findFirst().get(); - assertEquals(""" - @startuml - title Deployment View: X - Live - - set separator none - left to right direction - skinparam ranksep 60 - skinparam nodesep 30 - hide stereotype - - - - rectangle "Amazon Web Services\\n[Deployment Node]\\n\\n" <> as Live.AmazonWebServices { - rectangle "US-East-1\\n[Deployment Node]\\n\\n" <> as Live.AmazonWebServices.USEast1 { - rectangle "Autoscaling group\\n[Deployment Node]\\n\\n" <> as Live.AmazonWebServices.USEast1.Autoscalinggroup { - rectangle "Amazon EC2 - Ubuntu server\\n[Deployment Node]\\n\\n" <> as Live.AmazonWebServices.USEast1.Autoscalinggroup.AmazonEC2Ubuntuserver { - rectangle "==Web Application\\n[Container: Java and Spring Boot]" <> as Live.AmazonWebServices.USEast1.Autoscalinggroup.AmazonEC2Ubuntuserver.WebApplication_1 - } - - } - - rectangle "Amazon RDS\\n[Deployment Node]\\n\\n" <> as Live.AmazonWebServices.USEast1.AmazonRDS { - rectangle "MySQL\\n[Deployment Node]\\n\\n" <> as Live.AmazonWebServices.USEast1.AmazonRDS.MySQL { - database "==Database Schema\\n[Container]" <> as Live.AmazonWebServices.USEast1.AmazonRDS.MySQL.DatabaseSchema_1 - } - - } - - rectangle "==DNS router\\n[Infrastructure Node: Route 53]\\n\\n\\n\\nRoutes incoming requests based upon domain name." <> as Live.AmazonWebServices.USEast1.DNSrouter - rectangle "==Load Balancer\\n[Infrastructure Node: Elastic Load Balancer]\\n\\n\\n\\nAutomatically distributes incoming application traffic." <> as Live.AmazonWebServices.USEast1.LoadBalancer - } - - } - - Live.AmazonWebServices.USEast1.LoadBalancer --> Live.AmazonWebServices.USEast1.Autoscalinggroup.AmazonEC2Ubuntuserver.WebApplication_1 <> : "Forwards requests to\\n[HTTPS]" - Live.AmazonWebServices.USEast1.Autoscalinggroup.AmazonEC2Ubuntuserver.WebApplication_1 --> Live.AmazonWebServices.USEast1.AmazonRDS.MySQL.DatabaseSchema_1 <> : "Reads from and writes to\\n[MySQL Protocol/SSL]" - Live.AmazonWebServices.USEast1.DNSrouter --> Live.AmazonWebServices.USEast1.LoadBalancer <> : "Forwards requests to\\n[HTTPS]" - - @enduml""", diagram.getDefinition()); - - assertEquals(""" - @startuml - - set separator none - hide stereotype - - - - rectangle "==Amazon Web Services - Auto Scaling\\n\\n" <> - - rectangle "==Amazon Web Services - Cloud\\n\\n" <> - - rectangle "==Amazon Web Services - EC2\\n\\n" <> - - rectangle "==Amazon Web Services - Elastic Load Balancing\\n\\n" <> - - rectangle "==Amazon Web Services - RDS\\n\\n" <> - - rectangle "==Amazon Web Services - RDS MySQL instance\\n\\n" <> - - rectangle "==Amazon Web Services - Region\\n\\n" <> - - rectangle "==Amazon Web Services - Route 53\\n\\n" <> - - rectangle "==Application" <> - - database "==Database" <> - - rectangle "." <<.Element-Transparent>> as 1 - 1 --> 1 <> : "Relationship" - - @enduml""", diagram.getLegend().getDefinition()); - } - - @Test - @Tag("IntegrationTest") - public void amazonWebServicesExample_Dark() throws Exception { - Workspace workspace = WorkspaceUtils.loadWorkspaceFromJson(new File("./src/test/resources/amazon-web-services.json")); - ThemeUtils.loadThemes(workspace); - - StructurizrPlantUMLExporter exporter = new StructurizrPlantUMLExporter(ColorScheme.Dark); - Collection diagrams = exporter.export(workspace); - assertEquals(1, diagrams.size()); - - Diagram diagram = diagrams.stream().findFirst().get(); - assertEquals(""" - @startuml - title Deployment View: X - Live - - set separator none - left to right direction - skinparam ranksep 60 - skinparam nodesep 30 - hide stereotype - - - - rectangle "Amazon Web Services\\n[Deployment Node]\\n\\n" <> as Live.AmazonWebServices { - rectangle "US-East-1\\n[Deployment Node]\\n\\n" <> as Live.AmazonWebServices.USEast1 { - rectangle "Autoscaling group\\n[Deployment Node]\\n\\n" <> as Live.AmazonWebServices.USEast1.Autoscalinggroup { - rectangle "Amazon EC2 - Ubuntu server\\n[Deployment Node]\\n\\n" <> as Live.AmazonWebServices.USEast1.Autoscalinggroup.AmazonEC2Ubuntuserver { - rectangle "==Web Application\\n[Container: Java and Spring Boot]" <> as Live.AmazonWebServices.USEast1.Autoscalinggroup.AmazonEC2Ubuntuserver.WebApplication_1 - } - - } - - rectangle "Amazon RDS\\n[Deployment Node]\\n\\n" <> as Live.AmazonWebServices.USEast1.AmazonRDS { - rectangle "MySQL\\n[Deployment Node]\\n\\n" <> as Live.AmazonWebServices.USEast1.AmazonRDS.MySQL { - database "==Database Schema\\n[Container]" <> as Live.AmazonWebServices.USEast1.AmazonRDS.MySQL.DatabaseSchema_1 - } - - } - - rectangle "==DNS router\\n[Infrastructure Node: Route 53]\\n\\n\\n\\nRoutes incoming requests based upon domain name." <> as Live.AmazonWebServices.USEast1.DNSrouter - rectangle "==Load Balancer\\n[Infrastructure Node: Elastic Load Balancer]\\n\\n\\n\\nAutomatically distributes incoming application traffic." <> as Live.AmazonWebServices.USEast1.LoadBalancer - } - - } - - Live.AmazonWebServices.USEast1.LoadBalancer --> Live.AmazonWebServices.USEast1.Autoscalinggroup.AmazonEC2Ubuntuserver.WebApplication_1 <> : "Forwards requests to\\n[HTTPS]" - Live.AmazonWebServices.USEast1.Autoscalinggroup.AmazonEC2Ubuntuserver.WebApplication_1 --> Live.AmazonWebServices.USEast1.AmazonRDS.MySQL.DatabaseSchema_1 <> : "Reads from and writes to\\n[MySQL Protocol/SSL]" - Live.AmazonWebServices.USEast1.DNSrouter --> Live.AmazonWebServices.USEast1.LoadBalancer <> : "Forwards requests to\\n[HTTPS]" - - @enduml""", diagram.getDefinition()); - - assertEquals(""" - @startuml - - set separator none - hide stereotype - - - - rectangle "==Amazon Web Services - Auto Scaling\\n\\n" <> - - rectangle "==Amazon Web Services - Cloud\\n\\n" <> - - rectangle "==Amazon Web Services - EC2\\n\\n" <> - - rectangle "==Amazon Web Services - Elastic Load Balancing\\n\\n" <> - - rectangle "==Amazon Web Services - RDS\\n\\n" <> - - rectangle "==Amazon Web Services - RDS MySQL instance\\n\\n" <> - - rectangle "==Amazon Web Services - Region\\n\\n" <> - - rectangle "==Amazon Web Services - Route 53\\n\\n" <> - - rectangle "==Application" <> - - database "==Database" <> - - rectangle "." <<.Element-Transparent>> as 1 - 1 --> 1 <> : "Relationship" - - @enduml""", diagram.getLegend().getDefinition()); - } - +package com.structurizr.export.plantuml; + +import com.structurizr.Workspace; +import com.structurizr.export.AbstractExporterTests; +import com.structurizr.export.Diagram; +import com.structurizr.model.*; +import com.structurizr.util.WorkspaceUtils; +import com.structurizr.view.*; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.util.Collection; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class StructurizrPlantUMLDiagramExporterTests extends AbstractExporterTests { + + @Test + public void systemLandscapeView_NoStyling_Light() { + Workspace workspace = new Workspace("Name", "Description"); + SoftwareSystem a = workspace.getModel().addSoftwareSystem("A", "Description."); + SoftwareSystem b = workspace.getModel().addSoftwareSystem("B", "Description."); + a.uses(b, "Description", "Technology"); + + SystemLandscapeView view = workspace.getViews().createSystemLandscapeView("key", "Description"); + view.addDefaultElements(); + + StructurizrPlantUMLExporter exporter = new StructurizrPlantUMLExporter(ColorScheme.Light); + Diagram diagram = exporter.export(view); + + assertEquals(""" + @startuml + title System Landscape View\\nDescription + + set separator none + top to bottom direction + hide stereotype + + + + rectangle "==A\\n[Software System]\\n\\nDescription." <> as A + rectangle "==B\\n[Software System]\\n\\nDescription." <> as B + + A --> B <> : "Description\\n[Technology]" + + @enduml""", diagram.getDefinition()); + + assertEquals(""" + @startuml + + set separator none + hide stereotype + + + + rectangle "==Element" <> + + rectangle "." <<.Element-Transparent>> as 1 + 1 --> 1 <> : "Relationship" + + @enduml""", diagram.getLegend().getDefinition()); + } + + @Test + public void systemLandscapeView_NoStyling_Dark() { + Workspace workspace = new Workspace("Name", "Description"); + SoftwareSystem a = workspace.getModel().addSoftwareSystem("A", "Description."); + SoftwareSystem b = workspace.getModel().addSoftwareSystem("B", "Description."); + a.uses(b, "Description", "Technology"); + + SystemLandscapeView view = workspace.getViews().createSystemLandscapeView("key", "Description"); + view.addDefaultElements(); + + StructurizrPlantUMLExporter exporter = new StructurizrPlantUMLExporter(ColorScheme.Dark); + Diagram diagram = exporter.export(view); + + assertEquals(""" + @startuml + title System Landscape View\\nDescription + + set separator none + top to bottom direction + hide stereotype + + + + rectangle "==A\\n[Software System]\\n\\nDescription." <> as A + rectangle "==B\\n[Software System]\\n\\nDescription." <> as B + + A --> B <> : "Description\\n[Technology]" + + @enduml""", diagram.getDefinition()); + + assertEquals(""" + @startuml + + set separator none + hide stereotype + + + + rectangle "==Element" <> + + rectangle "." <<.Element-Transparent>> as 1 + 1 --> 1 <> : "Relationship" + + @enduml""", diagram.getLegend().getDefinition()); + } + + @Test + public void systemContextView_NoStyling_Light() { + Workspace workspace = new Workspace("Name", "Description"); + SoftwareSystem a = workspace.getModel().addSoftwareSystem("A", "Description."); + SoftwareSystem b = workspace.getModel().addSoftwareSystem("B", "Description."); + a.uses(b, "Description", "Technology"); + + SystemContextView view = workspace.getViews().createSystemContextView(a, "key", "Description"); + view.addDefaultElements(); + + StructurizrPlantUMLExporter exporter = new StructurizrPlantUMLExporter(ColorScheme.Light); + Diagram diagram = exporter.export(view); + + assertEquals(""" + @startuml + title System Context View: A\\nDescription + + set separator none + top to bottom direction + hide stereotype + + + + rectangle "==A\\n[Software System]\\n\\nDescription." <> as A + rectangle "==B\\n[Software System]\\n\\nDescription." <> as B + + A --> B <> : "Description\\n[Technology]" + + @enduml""", diagram.getDefinition()); + } + + @Test + public void containerView_NoStyling_Light() { + Workspace workspace = new Workspace("Name", "Description"); + SoftwareSystem a = workspace.getModel().addSoftwareSystem("Software System A", "Description."); + Container container1 = a.addContainer("Container 1", "Description", "Technology"); + Container container2 = a.addContainer("Container 2", "Description", "Technology"); + container1.uses(container2, "Description", "Technology"); + + ContainerView view = workspace.getViews().createContainerView(a, "key", "Description"); + view.addDefaultElements(); + + StructurizrPlantUMLExporter exporter = new StructurizrPlantUMLExporter(ColorScheme.Light); + Diagram diagram = exporter.export(view); + + assertEquals(""" + @startuml + title Container View: Software System A\\nDescription + + set separator none + top to bottom direction + hide stereotype + + + + rectangle "Software System A\\n[Software System]" <> { + rectangle "==Container 1\\n[Container: Technology]\\n\\nDescription" <> as SoftwareSystemA.Container1 + rectangle "==Container 2\\n[Container: Technology]\\n\\nDescription" <> as SoftwareSystemA.Container2 + } + + SoftwareSystemA.Container1 --> SoftwareSystemA.Container2 <> : "Description\\n[Technology]" + + @enduml""", diagram.getDefinition()); + } + + @Test + public void componentView_NoStyling_Light() { + Workspace workspace = new Workspace("Name", "Description"); + SoftwareSystem a = workspace.getModel().addSoftwareSystem("Software System", "Description."); + Container container = a.addContainer("Container", "Description", "Technology"); + Component component1 = container.addComponent("Component 1", "Description", "Technology"); + Component component2 = container.addComponent("Component 2", "Description", "Technology"); + component1.uses(component2, "Description", "Technology"); + + ComponentView view = workspace.getViews().createComponentView(container, "key", "Description"); + view.addDefaultElements(); + + StructurizrPlantUMLExporter exporter = new StructurizrPlantUMLExporter(ColorScheme.Light); + Diagram diagram = exporter.export(view); + + assertEquals(""" + @startuml + title Component View: Software System - Container\\nDescription + + set separator none + top to bottom direction + hide stereotype + + + + rectangle "Container\\n[Container: Technology]" <> { + rectangle "==Component 1\\n[Component: Technology]\\n\\nDescription" <> as SoftwareSystem.Container.Component1 + rectangle "==Component 2\\n[Component: Technology]\\n\\nDescription" <> as SoftwareSystem.Container.Component2 + } + + SoftwareSystem.Container.Component1 --> SoftwareSystem.Container.Component2 <> : "Description\\n[Technology]" + + @enduml""", diagram.getDefinition()); + } + + @Test + public void deploymentView_NoStyling_Light() { + Workspace workspace = new Workspace("Name", "Description"); + SoftwareSystem a = workspace.getModel().addSoftwareSystem("A"); + DeploymentNode node1 = workspace.getModel().addDeploymentNode("Node 1"); + node1.addDeploymentNode("Node 2").add(a); + node1.addDeploymentNode("Node 3").addInfrastructureNode("Infrastructure Node"); + + DeploymentView view = workspace.getViews().createDeploymentView("deployment", "Default"); + view.addDefaultElements(); + + StructurizrPlantUMLExporter exporter = new StructurizrPlantUMLExporter(ColorScheme.Light); + Diagram diagram = exporter.export(view); + + assertEquals(""" + @startuml + title Deployment View: Default\\nDefault + + set separator none + top to bottom direction + hide stereotype + + + + rectangle "Node 1\\n[Deployment Node]" <> as Default.Node1 { + rectangle "Node 2\\n[Deployment Node]" <> as Default.Node1.Node2 { + rectangle "==A\\n[Software System]" <> as Default.Node1.Node2.A_1 + } + + rectangle "Node 3\\n[Deployment Node]" <> as Default.Node1.Node3 { + rectangle "==Infrastructure Node\\n[Infrastructure Node]" <> as Default.Node1.Node3.InfrastructureNode + } + + } + + @enduml""", diagram.getDefinition()); + } + + @Test + public void dynamicView_CollaborationStyle_NoStyling_Light() { + Workspace workspace = new Workspace("Name", "Description"); + SoftwareSystem a = workspace.getModel().addSoftwareSystem("A"); + SoftwareSystem b = workspace.getModel().addSoftwareSystem("B"); + + a.uses(b, "Uses"); + + DynamicView view = workspace.getViews().createDynamicView("key", "Description"); + view.add(a, b); + + StructurizrPlantUMLExporter exporter = new StructurizrPlantUMLExporter(); + Diagram diagram = exporter.export(view); + + assertEquals(""" + @startuml + title Dynamic View\\nDescription + + set separator none + top to bottom direction + hide stereotype + + + + rectangle "==A\\n[Software System]" <> as A + rectangle "==B\\n[Software System]" <> as B + + A --> B <> : "1: Uses" + + @enduml""", diagram.getDefinition()); + } + + @Test + public void dynamicView_CollaborationStyle_Frames() { + Workspace workspace = new Workspace("Name", "Description"); + SoftwareSystem a = workspace.getModel().addSoftwareSystem("A"); + SoftwareSystem b = workspace.getModel().addSoftwareSystem("B"); + SoftwareSystem c = workspace.getModel().addSoftwareSystem("C"); + + a.uses(b, "Uses"); + b.uses(c, "Uses"); + + DynamicView view = workspace.getViews().createDynamicView("key", "Description"); + view.add(a, b); + view.add(b, c); + view.addProperty(StructurizrPlantUMLExporter.PLANTUML_ANIMATION_PROPERTY, "true"); + + StructurizrPlantUMLExporter exporter = new StructurizrPlantUMLExporter(); + List frames = exporter.export(view).getFrames(); + assertEquals(2, frames.size()); + + assertEquals(""" + @startuml + title Dynamic View\\nDescription + + set separator none + top to bottom direction + hide stereotype + + + + rectangle "==A\\n[Software System]" <> as A + rectangle "==B\\n[Software System]" <> as B + rectangle "==C\\n[Software System]" <> as C + hide C + + A --> B <> : "1: Uses" + B --> C <> : "2: Uses" + + @enduml""", frames.get(0).getDefinition()); + + assertEquals(""" + @startuml + title Dynamic View\\nDescription + + set separator none + top to bottom direction + hide stereotype + + + + rectangle "==A\\n[Software System]" <> as A + hide A + rectangle "==B\\n[Software System]" <> as B + rectangle "==C\\n[Software System]" <> as C + + A --> B <> : "1: Uses" + B --> C <> : "2: Uses" + + @enduml""", frames.get(1).getDefinition()); + } + + @Test + public void dynamicView_SequenceStyle_NoStyling_Light() { + Workspace workspace = new Workspace("Name", "Description"); + SoftwareSystem a = workspace.getModel().addSoftwareSystem("A"); + SoftwareSystem b = workspace.getModel().addSoftwareSystem("B"); + + a.uses(b, "Uses", "JSON/HTTPS"); + + DynamicView view = workspace.getViews().createDynamicView("key", "Description"); + view.add(a, b); + view.addProperty(StructurizrPlantUMLExporter.PLANTUML_SEQUENCE_DIAGRAM_PROPERTY, "true"); + + StructurizrPlantUMLExporter exporter = new StructurizrPlantUMLExporter(); + Diagram diagram = exporter.export(view); + + assertEquals(""" + @startuml + title Dynamic View\\nDescription + + set separator none + hide stereotype + + + + participant "A\\n[Software System]" as A <> #ffffff + participant "B\\n[Software System]" as B <> #ffffff + + A -> B <> : 1: Uses\\n[JSON/HTTPS] + + @enduml""", diagram.getDefinition()); + + assertEquals(""" + @startuml + + set separator none + hide stereotype + + + + rectangle "==Element" <> + + rectangle "." <<.Element-Transparent>> as 1 + 1 --> 1 <> : "Relationship" + + @enduml""", diagram.getLegend().getDefinition()); + } + + @Test + public void groups() throws Exception { + Workspace workspace = WorkspaceUtils.loadWorkspaceFromJson(new File("./src/test/resources/groups.json")); + + StructurizrPlantUMLExporter exporter = new StructurizrPlantUMLExporter(); + Collection diagrams = exporter.export(workspace); + assertEquals(4, diagrams.size()); + + Diagram diagram = diagrams.stream().filter(md -> md.getKey().equals("SystemLandscape")).findFirst().get(); + assertEquals(""" + @startuml + title System Landscape View + + set separator none + top to bottom direction + skinparam ranksep 60 + skinparam nodesep 30 + hide stereotype + + + + rectangle "Group 1" <> as groupR3JvdXAgMQ== { + + rectangle "==B\\n[Software System]" <> as B + } + + rectangle "Group 2" <> as groupR3JvdXAgMg== { + + rectangle "==C\\n[Software System]" <> as C + rectangle "Group 3" <> as groupR3JvdXAgMi9Hcm91cCAz { + + rectangle "==D\\n[Software System]" <> as D + } + + } + + rectangle "==A\\n[Software System]" <> as A + + B --> C <> : "" + C --> D <> : "" + A --> B <> : "" + + @enduml""", diagram.getDefinition()); + + diagram = diagrams.stream().filter(md -> md.getKey().equals("Containers")).findFirst().get(); + assertEquals(""" + @startuml + title Container View: D + + set separator none + top to bottom direction + skinparam ranksep 60 + skinparam nodesep 30 + hide stereotype + + + + rectangle "==C\\n[Software System]" <> as C + + rectangle "D\\n[Software System]" <> { + rectangle "Group 4" <> as groupR3JvdXAgNA== { + + rectangle "==F\\n[Container]" <> as D.F + } + + rectangle "==E\\n[Container]" <> as D.E + } + + C --> D.E <> : "" + C --> D.F <> : "" + + @enduml""", diagram.getDefinition()); + + diagram = diagrams.stream().filter(md -> md.getKey().equals("Components")).findFirst().get(); + assertEquals(""" + @startuml + title Component View: D - F + + set separator none + top to bottom direction + skinparam ranksep 60 + skinparam nodesep 30 + hide stereotype + + + + rectangle "==C\\n[Software System]" <> as C + + rectangle "F\\n[Container]" <> { + rectangle "Group 5" <> as groupR3JvdXAgNQ== { + + rectangle "==H\\n[Component]" <> as D.F.H + } + + rectangle "==G\\n[Component]" <> as D.F.G + } + + C --> D.F.G <> : "" + C --> D.F.H <> : "" + D.F.G --> D.F.H <> : "" + + @enduml""", diagram.getDefinition()); + } + + @Test + public void nestedGroups() { + Workspace workspace = new Workspace("Name", "Description"); + workspace.getModel().addProperty("structurizr.groupSeparator", "/"); + + SoftwareSystem a = workspace.getModel().addSoftwareSystem("Team 1"); + a.setGroup("Organisation 1/Department 1/Team 1"); + + SoftwareSystem b = workspace.getModel().addSoftwareSystem("Team 2"); + b.setGroup("Organisation 1/Department 1/Team 2"); + + SoftwareSystem c = workspace.getModel().addSoftwareSystem("Organisation 1"); + c.setGroup("Organisation 1"); + + SoftwareSystem d = workspace.getModel().addSoftwareSystem("Organisation 2"); + d.setGroup("Organisation 2"); + + SoftwareSystem e = workspace.getModel().addSoftwareSystem("Department 1"); + e.setGroup("Organisation 1/Department 1"); + + SystemLandscapeView view = workspace.getViews().createSystemLandscapeView("SystemLandscape"); + view.addAllElements(); + + workspace.getViews().getConfiguration().getStyles().addElementStyle("Group:Organisation 1/Department 1/Team 1").color("#ff0000"); + workspace.getViews().getConfiguration().getStyles().addElementStyle("Group:Organisation 1/Department 1/Team 2").color("#0000ff"); + + StructurizrPlantUMLExporter exporter = new StructurizrPlantUMLExporter(); + Collection diagrams = exporter.export(workspace); + + Diagram diagram = diagrams.stream().filter(md -> md.getKey().equals("SystemLandscape")).findFirst().get(); + assertEquals(""" + @startuml + title System Landscape View + + set separator none + top to bottom direction + hide stereotype + + + + rectangle "Organisation 1" <> as groupT3JnYW5pc2F0aW9uIDE= { + + rectangle "==Organisation 1\\n[Software System]" <> as Organisation1 + rectangle "Department 1" <> as groupT3JnYW5pc2F0aW9uIDEvRGVwYXJ0bWVudCAx { + + rectangle "==Department 1\\n[Software System]" <> as Department1 + rectangle "Team 1" <> as groupT3JnYW5pc2F0aW9uIDEvRGVwYXJ0bWVudCAxL1RlYW0gMQ== { + + rectangle "==Team 1\\n[Software System]" <> as Team1 + } + + rectangle "Team 2" <> as groupT3JnYW5pc2F0aW9uIDEvRGVwYXJ0bWVudCAxL1RlYW0gMg== { + + rectangle "==Team 2\\n[Software System]" <> as Team2 + } + + } + + } + + rectangle "Organisation 2" <> as groupT3JnYW5pc2F0aW9uIDI= { + + rectangle "==Organisation 2\\n[Software System]" <> as Organisation2 + } + + + @enduml""", diagram.getDefinition()); + } + + + @Test + public void test_Sequence() throws Exception { + Workspace workspace = WorkspaceUtils.loadWorkspaceFromJson(new File("./src/test/resources/sequence.json")); + ThemeUtils.loadThemes(workspace); + + StructurizrPlantUMLExporter exporter = new StructurizrPlantUMLExporter(); + Collection diagrams = exporter.export(workspace); + assertEquals(2, diagrams.size()); + + Diagram diagram = diagrams.stream().filter(md -> md.getKey().equals("Sequence-Container")).findFirst().get(); + String actual = """ + @startuml + title Dynamic View: d - e + + set separator none + !pragma teoz true + hide stereotype + + + + box "A\\n[Software System]" <> + box + box "B\\n[Container]" <> + box + participant "C\\n[Component]" as A.B.C <> #ffffff + end box + end box + + end box + end box + + box "d\\n[Software System]" <> + box + box "e\\n[Container]" <> + box + box "group2" <> + box + + participant "f\\n[Component]" as d.e.f <> #ffffff + participant "g\\n[Component]" as d.e.g <> #ffffff + participant "h\\n[Component]" as d.e.h <> #ffffff + end box + end box + + end box + end box + + end box + end box + + d.e.f -> d.e.h <> : 1:\s + A.B.C -> d.e.f <> : 2:\s + d.e.f -> d.e.g <> : 3:\s + + @enduml"""; + + assertEquals(actual, diagram.getDefinition()); + + diagram = diagrams.stream().filter(md -> md.getKey().equals("Sequence-SoftwareSystem")).findFirst().get(); + assertEquals(""" + @startuml + title Dynamic View: A + + set separator none + !pragma teoz true + hide stereotype + + + + box "A\\n[Software System]" <> + box + participant "B\\n[Container]" as A.B <> #f5f5dc + end box + end box + + box "d\\n[Software System]" <> + box + box "group1" <> + box + + participant "e\\n[Container]" as d.e <> #f5f5dc + end box + end box + + end box + end box + + A.B -> d.e <> : 1:\s + + @enduml""", diagram.getDefinition()); + } + + @Test + public void containerDiagramWithExternalContainers() { + Workspace workspace = new Workspace("Name"); + SoftwareSystem softwareSystem1 = workspace.getModel().addSoftwareSystem("Software System 1"); + Container container1 = softwareSystem1.addContainer("Container 1"); + SoftwareSystem softwareSystem2 = workspace.getModel().addSoftwareSystem("Software System 2"); + Container container2 = softwareSystem2.addContainer("Container 2"); + + container1.uses(container2, "Uses"); + + ContainerView containerView = workspace.getViews().createContainerView(softwareSystem1, "Containers", ""); + containerView.add(container1); + containerView.add(container2); + + Diagram diagram = new StructurizrPlantUMLExporter().export(containerView); + assertEquals(""" + @startuml + title Container View: Software System 1 + + set separator none + top to bottom direction + hide stereotype + + + + rectangle "Software System 1\\n[Software System]" <> { + rectangle "==Container 1\\n[Container]" <> as SoftwareSystem1.Container1 + } + + rectangle "Software System 2\\n[Software System]" <> { + rectangle "==Container 2\\n[Container]" <> as SoftwareSystem2.Container2 + } + + SoftwareSystem1.Container1 --> SoftwareSystem2.Container2 <> : "Uses" + + @enduml""", diagram.getDefinition()); + } + + @Test + public void componentDiagramWithExternalComponents() { + Workspace workspace = new Workspace("Name"); + + SoftwareSystem softwareSystem1 = workspace.getModel().addSoftwareSystem("Software System 1"); + Container container1 = softwareSystem1.addContainer("Container 1"); + Component component1 = container1.addComponent("Component 1"); + Component component2 = container1.addComponent("Component 2"); + + SoftwareSystem softwareSystem2 = workspace.getModel().addSoftwareSystem("Software System 2"); + Container container2 = softwareSystem2.addContainer("Container 2"); + Component component3 = container2.addComponent("Component 3"); + + component1.uses(component2, "Uses"); + component2.uses(component3, "Uses"); + + ComponentView componentView = workspace.getViews().createComponentView(container1, "Components", ""); + componentView.add(component1); + componentView.add(component2); + componentView.add(component3); + + Diagram diagram = new StructurizrPlantUMLExporter().export(componentView); + assertEquals(""" + @startuml + title Component View: Software System 1 - Container 1 + + set separator none + top to bottom direction + hide stereotype + + + + rectangle "Container 1\\n[Container]" <> { + rectangle "==Component 1\\n[Component]" <> as SoftwareSystem1.Container1.Component1 + rectangle "==Component 2\\n[Component]" <> as SoftwareSystem1.Container1.Component2 + } + + rectangle "Container 2\\n[Container]" <> { + rectangle "==Component 3\\n[Component]" <> as SoftwareSystem2.Container2.Component3 + } + + SoftwareSystem1.Container1.Component1 --> SoftwareSystem1.Container1.Component2 <> : "Uses" + SoftwareSystem1.Container1.Component2 --> SoftwareSystem2.Container2.Component3 <> : "Uses" + + @enduml""", diagram.getDefinition()); + } + + @Test + public void componentDiagramWithExternalComponentsAndSoftwareSystemBoundariesIncluded() { + Workspace workspace = new Workspace("Name"); + + SoftwareSystem softwareSystem1 = workspace.getModel().addSoftwareSystem("Software System 1"); + Container container1 = softwareSystem1.addContainer("Container 1"); + Component component1 = container1.addComponent("Component 1"); + Component component2 = container1.addComponent("Component 2"); + + SoftwareSystem softwareSystem2 = workspace.getModel().addSoftwareSystem("Software System 2"); + Container container2 = softwareSystem2.addContainer("Container 2"); + Component component3 = container2.addComponent("Component 3"); + + component1.uses(component2, "Uses"); + component2.uses(component3, "Uses"); + + ComponentView componentView = workspace.getViews().createComponentView(container1, "Components", ""); + componentView.add(component1); + componentView.add(component2); + componentView.add(component3); + componentView.addProperty("structurizr.softwareSystemBoundaries", "true"); + + Diagram diagram = new StructurizrPlantUMLExporter().export(componentView); + assertEquals(""" + @startuml + title Component View: Software System 1 - Container 1 + + set separator none + top to bottom direction + hide stereotype + + + + rectangle "Software System 1\\n[Software System]" <> { + rectangle "Container 1\\n[Container]" <> { + rectangle "==Component 1\\n[Component]" <> as SoftwareSystem1.Container1.Component1 + rectangle "==Component 2\\n[Component]" <> as SoftwareSystem1.Container1.Component2 + } + + } + + rectangle "Software System 2\\n[Software System]" <> { + rectangle "Container 2\\n[Container]" <> { + rectangle "==Component 3\\n[Component]" <> as SoftwareSystem2.Container2.Component3 + } + + } + + SoftwareSystem1.Container1.Component1 --> SoftwareSystem1.Container1.Component2 <> : "Uses" + SoftwareSystem1.Container1.Component2 --> SoftwareSystem2.Container2.Component3 <> : "Uses" + + @enduml""", diagram.getDefinition()); + } + + @Test + public void dynamicView_ExternalContainers() { + Workspace workspace = new Workspace("Name"); + SoftwareSystem softwareSystem1 = workspace.getModel().addSoftwareSystem("Software System 1"); + Container container1 = softwareSystem1.addContainer("Container 1"); + SoftwareSystem softwareSystem2 = workspace.getModel().addSoftwareSystem("Software System 2"); + Container container2 = softwareSystem2.addContainer("Container 2"); + + container1.uses(container2, "Uses"); + + DynamicView dynamicView = workspace.getViews().createDynamicView(softwareSystem1, "Dynamic", ""); + dynamicView.add(container1, container2); + + Diagram diagram = new StructurizrPlantUMLExporter().export(dynamicView); + assertEquals(""" + @startuml + title Dynamic View: Software System 1 + + set separator none + top to bottom direction + hide stereotype + + + + rectangle "Software System 1\\n[Software System]" <> { + rectangle "==Container 1\\n[Container]" <> as SoftwareSystem1.Container1 + } + + rectangle "Software System 2\\n[Software System]" <> { + rectangle "==Container 2\\n[Container]" <> as SoftwareSystem2.Container2 + } + + SoftwareSystem1.Container1 --> SoftwareSystem2.Container2 <> : "1: Uses" + + @enduml""", diagram.getDefinition()); + } + + @Test + public void dynamicView_ExternalComponents() { + Workspace workspace = new Workspace("Name"); + + SoftwareSystem softwareSystem1 = workspace.getModel().addSoftwareSystem("Software System 1"); + Container container1 = softwareSystem1.addContainer("Container 1"); + Component component1 = container1.addComponent("Component 1"); + Component component2 = container1.addComponent("Component 2"); + + SoftwareSystem softwareSystem2 = workspace.getModel().addSoftwareSystem("Software System 2"); + Container container2 = softwareSystem2.addContainer("Container 2"); + Component component3 = container2.addComponent("Component 3"); + + component1.uses(component2, "Uses"); + component2.uses(component3, "Uses"); + + DynamicView dynamicView = workspace.getViews().createDynamicView(container1, "Dynamic", ""); + dynamicView.add(component1, component2); + dynamicView.add(component2, component3); + + Diagram diagram = new StructurizrPlantUMLExporter().export(dynamicView); + assertEquals(""" + @startuml + title Dynamic View: Software System 1 - Container 1 + + set separator none + top to bottom direction + hide stereotype + + + + rectangle "Container 1\\n[Container]" <> { + rectangle "==Component 1\\n[Component]" <> as SoftwareSystem1.Container1.Component1 + rectangle "==Component 2\\n[Component]" <> as SoftwareSystem1.Container1.Component2 + } + + rectangle "Container 2\\n[Container]" <> { + rectangle "==Component 3\\n[Component]" <> as SoftwareSystem2.Container2.Component3 + } + + SoftwareSystem1.Container1.Component1 --> SoftwareSystem1.Container1.Component2 <> : "1: Uses" + SoftwareSystem1.Container1.Component2 --> SoftwareSystem2.Container2.Component3 <> : "2: Uses" + + @enduml""", diagram.getDefinition()); + } + + @Test + public void dynamicView_ExternalComponentsAndSoftwareSystemBoundariesIncluded() { + Workspace workspace = new Workspace("Name"); + + SoftwareSystem softwareSystem1 = workspace.getModel().addSoftwareSystem("Software System 1"); + Container container1 = softwareSystem1.addContainer("Container 1"); + Component component1 = container1.addComponent("Component 1"); + Component component2 = container1.addComponent("Component 2"); + + SoftwareSystem softwareSystem2 = workspace.getModel().addSoftwareSystem("Software System 2"); + Container container2 = softwareSystem2.addContainer("Container 2"); + Component component3 = container2.addComponent("Component 3"); + + component1.uses(component2, "Uses"); + component2.uses(component3, "Uses"); + + DynamicView dynamicView = workspace.getViews().createDynamicView(container1, "Dynamic", ""); + dynamicView.add(component1, component2); + dynamicView.add(component2, component3); + dynamicView.addProperty("structurizr.softwareSystemBoundaries", "true"); + + Diagram diagram = new StructurizrPlantUMLExporter().export(dynamicView); + assertEquals(""" + @startuml + title Dynamic View: Software System 1 - Container 1 + + set separator none + top to bottom direction + hide stereotype + + + + rectangle "Software System 1\\n[Software System]" <> { + rectangle "Container 1\\n[Container]" <> { + rectangle "==Component 1\\n[Component]" <> as SoftwareSystem1.Container1.Component1 + rectangle "==Component 2\\n[Component]" <> as SoftwareSystem1.Container1.Component2 + } + + } + + rectangle "Software System 2\\n[Software System]" <> { + rectangle "Container 2\\n[Container]" <> { + rectangle "==Component 3\\n[Component]" <> as SoftwareSystem2.Container2.Component3 + } + + } + + SoftwareSystem1.Container1.Component1 --> SoftwareSystem1.Container1.Component2 <> : "1: Uses" + SoftwareSystem1.Container1.Component2 --> SoftwareSystem2.Container2.Component3 <> : "2: Uses" + + @enduml""", diagram.getDefinition()); + } + + @Test + public void elementUrls() { + Workspace workspace = new Workspace("Name"); + SoftwareSystem softwareSystem = workspace.getModel().addSoftwareSystem("Software System"); + softwareSystem.setUrl("https://structurizr.com"); + + SystemLandscapeView view = workspace.getViews().createSystemLandscapeView("key"); + view.addDefaultElements(); + + Diagram diagram = new StructurizrPlantUMLExporter().export(view); + assertTrue(diagram.getDefinition().contains("as SoftwareSystem [[https://structurizr.com]]")); + } + + @Test + public void elementInstanceUrl() { + Workspace workspace = new Workspace("Name"); + SoftwareSystem a = workspace.getModel().addSoftwareSystem("A"); + a.setUrl("https://example.com/url1"); + SoftwareSystemInstance aInstance = workspace.getModel().addDeploymentNode("Node").add(a); + + DeploymentView view = workspace.getViews().createDeploymentView("deployment", "Default"); + view.add(aInstance); + + assertTrue(new StructurizrPlantUMLExporter().export(view).getDefinition().contains("as Default.Node.A_1 [[https://example.com/url1]]")); + + aInstance.setUrl("https://example.com/url2"); + assertTrue(new StructurizrPlantUMLExporter().export(view).getDefinition().contains("as Default.Node.A_1 [[https://example.com/url2]]")); + } + + @Test + public void newLineCharacterInElementName() { + Workspace workspace = new Workspace("Name"); + workspace.getModel().addSoftwareSystem("Software\nSystem"); + + SystemLandscapeView view = workspace.getViews().createSystemLandscapeView("key"); + view.addDefaultElements(); + + Diagram diagram = new StructurizrPlantUMLExporter().export(view); + assertEquals(""" + @startuml + title System Landscape View + + set separator none + top to bottom direction + hide stereotype + + + + rectangle "==Software\\nSystem\\n[Software System]" <> as SoftwareSystem + + @enduml""", diagram.getDefinition()); + } + + @Test + public void customView() { + Workspace workspace = new Workspace("Name"); + Model model = workspace.getModel(); + + CustomElement a = model.addCustomElement("A"); + CustomElement b = model.addCustomElement("B", "Custom", "Description"); + a.uses(b, "Uses"); + + CustomView view = workspace.getViews().createCustomView("key", "Title"); + view.addDefaultElements(); + + Diagram diagram = new StructurizrPlantUMLExporter().export(view); + assertEquals(""" + @startuml + title Title + + set separator none + top to bottom direction + hide stereotype + + + + rectangle "==A" <> as 1 + rectangle "==B\\n[Custom]\\n\\nDescription" <> as 2 + + 1 --> 2 <> : "Uses" + + @enduml""", diagram.getDefinition()); + } + + @Test + void renderWorkspaceWithUnicodeElementName() { + Workspace workspace = new Workspace("Name"); + workspace.getModel().addPerson("Пользователь"); + workspace.getViews().createSystemLandscapeView("key", "Description").addDefaultElements(); + + String diagramDefinition = new StructurizrPlantUMLExporter().export(workspace).stream().findFirst().get().getDefinition(); + + assertEquals(""" + @startuml + title System Landscape View\\nDescription + + set separator none + top to bottom direction + hide stereotype + + + + rectangle "==Пользователь\\n[Person]" <> as Пользователь + + @enduml""", diagramDefinition); + } + + @Test + public void font() { + Workspace workspace = new Workspace("Name"); + workspace.getModel().addPerson("User"); + SystemLandscapeView view = workspace.getViews().createSystemLandscapeView("key"); + view.addAllElements(); + workspace.getViews().getConfiguration().getBranding().setFont(new Font("Courier")); + + Diagram diagram = new StructurizrPlantUMLExporter().export(view); + assertTrue(diagram.getDefinition().contains(""" + + + rectangle "Group 1" <> as groupR3JvdXAgMQ== { + + rectangle "==A\\n[Software System]" <> as A + } + + rectangle "Group 2" <> as groupR3JvdXAgMg== { + + rectangle "==B\\n[Software System]" <> as B + } + + + A --> B <> : "1: Uses" + + @enduml""", diagram.getDefinition()); + } + + @Test + public void dynamicView_SoftwareSystemScopedWithGroups() { + Workspace workspace = new Workspace("Name"); + SoftwareSystem softwareSystemA = workspace.getModel().addSoftwareSystem("A"); + Container containerA = softwareSystemA.addContainer("A"); + containerA.setGroup("Group 1"); + SoftwareSystem softwareSystemB = workspace.getModel().addSoftwareSystem("B"); + Container containerB = softwareSystemB.addContainer("B"); + containerB.setGroup("Group 2"); + containerA.uses(containerB, "Uses"); + + DynamicView view = workspace.getViews().createDynamicView(softwareSystemA, "key"); + view.add(containerA, containerB); + + StructurizrPlantUMLExporter exporter = new StructurizrPlantUMLExporter(); + Diagram diagram = exporter.export(view); + assertEquals(""" + @startuml + title Dynamic View: A + + set separator none + top to bottom direction + hide stereotype + + + + rectangle "A\\n[Software System]" <> { + rectangle "Group 1" <> as groupR3JvdXAgMQ== { + + rectangle "==A\\n[Container]" <> as A.A + } + + } + + rectangle "B\\n[Software System]" <> { + rectangle "Group 2" <> as groupR3JvdXAgMg== { + + rectangle "==B\\n[Container]" <> as B.B + } + + } + + A.A --> B.B <> : "1: Uses" + + @enduml""", diagram.getDefinition()); + } + + @Test + public void dynamicView_ContainerScopedWithGroups() { + Workspace workspace = new Workspace("Name"); + SoftwareSystem softwareSystemA = workspace.getModel().addSoftwareSystem("A"); + Container containerA = softwareSystemA.addContainer("A"); + Component componentA = containerA.addComponent("A"); + componentA.setGroup("Group 1"); + SoftwareSystem softwareSystemB = workspace.getModel().addSoftwareSystem("B"); + Container containerB = softwareSystemB.addContainer("B"); + Component componentB = containerB.addComponent("B"); + componentB.setGroup("Group 2"); + componentA.uses(componentB, "Uses"); + + DynamicView view = workspace.getViews().createDynamicView(containerA, "key"); + view.add(componentA, componentB); + + StructurizrPlantUMLExporter exporter = new StructurizrPlantUMLExporter(); + Diagram diagram = exporter.export(view); + assertEquals(""" + @startuml + title Dynamic View: A - A + + set separator none + top to bottom direction + hide stereotype + + + + rectangle "A\\n[Container]" <> { + rectangle "Group 1" <> as groupR3JvdXAgMQ== { + + rectangle "==A\\n[Component]" <> as A.A.A + } + + } + + rectangle "B\\n[Container]" <> { + rectangle "Group 2" <> as groupR3JvdXAgMg== { + + rectangle "==B\\n[Component]" <> as B.B.B + } + + } + + A.A.A --> B.B.B <> : "1: Uses" + + @enduml""", diagram.getDefinition()); + } + + @Test + public void deploymentView_WithGroups() { + Workspace workspace = new Workspace("Name", ""); + SoftwareSystem softwareSystem = workspace.getModel().addSoftwareSystem("Software System"); + + DeploymentNode server1 = workspace.getModel().addDeploymentNode("Server 1"); + server1.setGroup("Group 1"); + + InfrastructureNode infrastructureNode1 = server1.addInfrastructureNode("Infrastructure Node 1"); + InfrastructureNode infrastructureNode2 = server1.addInfrastructureNode("Infrastructure Node 2"); + + SoftwareSystemInstance softwareSystemInstance = server1.add(softwareSystem); + softwareSystemInstance.setGroup("Group 2"); + infrastructureNode2.setGroup("Group 2"); + + DeploymentView view = workspace.getViews().createDeploymentView("key"); + view.add(infrastructureNode1); + view.add(infrastructureNode2); + view.add(softwareSystemInstance); + + StructurizrPlantUMLExporter exporter = new StructurizrPlantUMLExporter(); + Diagram diagram = exporter.export(view); + assertEquals(""" + @startuml + title Deployment View: Default + + set separator none + top to bottom direction + hide stereotype + + + + rectangle "Group 1" <> as groupR3JvdXAgMQ== { + + rectangle "Server 1\\n[Deployment Node]" <> as Default.Server1 { + rectangle "Group 2" <> as groupR3JvdXAgMg== { + + rectangle "==Infrastructure Node 2\\n[Infrastructure Node]" <> as Default.Server1.InfrastructureNode2 + rectangle "==Software System\\n[Software System]" <> as Default.Server1.SoftwareSystem_1 + } + + rectangle "==Infrastructure Node 1\\n[Infrastructure Node]" <> as Default.Server1.InfrastructureNode1 + } + + } + + @enduml""", diagram.getDefinition()); + } + + @Test + void light_group() { + Workspace workspace = new Workspace("Name"); + SoftwareSystem softwareSystem = workspace.getModel().addSoftwareSystem("Name"); + softwareSystem.setGroup("Name"); + + SystemLandscapeView view = workspace.getViews().createSystemLandscapeView("key"); + view.add(softwareSystem); + + StructurizrPlantUMLExporter exporter = new StructurizrPlantUMLExporter(); + Diagram diagram = exporter.export(view); + assertEquals(""" + @startuml + title System Landscape View + + set separator none + top to bottom direction + hide stereotype + + + + rectangle "Name" <> as groupTmFtZQ== { + + rectangle "==Name\\n[Software System]" <> as Name + } + + + @enduml""", diagram.getDefinition()); + } + + @Test + void dark_group() { + Workspace workspace = new Workspace("Name"); + SoftwareSystem softwareSystem = workspace.getModel().addSoftwareSystem("Name"); + softwareSystem.setGroup("Name"); + + SystemLandscapeView view = workspace.getViews().createSystemLandscapeView("key"); + view.add(softwareSystem); + + StructurizrPlantUMLExporter exporter = new StructurizrPlantUMLExporter(ColorScheme.Dark); + Diagram diagram = exporter.export(view); + assertEquals(""" + @startuml + title System Landscape View + + set separator none + top to bottom direction + hide stereotype + + + + rectangle "Name" <> as groupTmFtZQ== { + + rectangle "==Name\\n[Software System]" <> as Name + } + + + @enduml""", diagram.getDefinition()); + } + + @Test + @Tag("IntegrationTest") + public void amazonWebServicesExample_Light() throws Exception { + Workspace workspace = WorkspaceUtils.loadWorkspaceFromJson(new File("./src/test/resources/amazon-web-services.json")); + ThemeUtils.loadThemes(workspace); + + StructurizrPlantUMLExporter exporter = new StructurizrPlantUMLExporter(ColorScheme.Light); + Collection diagrams = exporter.export(workspace); + assertEquals(1, diagrams.size()); + + Diagram diagram = diagrams.stream().findFirst().get(); + assertEquals(""" + @startuml + title Deployment View: X - Live + + set separator none + left to right direction + skinparam ranksep 60 + skinparam nodesep 30 + hide stereotype + + + + rectangle "Amazon Web Services\\n[Deployment Node]\\n\\n" <> as Live.AmazonWebServices { + rectangle "US-East-1\\n[Deployment Node]\\n\\n" <> as Live.AmazonWebServices.USEast1 { + rectangle "Autoscaling group\\n[Deployment Node]\\n\\n" <> as Live.AmazonWebServices.USEast1.Autoscalinggroup { + rectangle "Amazon EC2 - Ubuntu server\\n[Deployment Node]\\n\\n" <> as Live.AmazonWebServices.USEast1.Autoscalinggroup.AmazonEC2Ubuntuserver { + rectangle "==Web Application\\n[Container: Java and Spring Boot]" <> as Live.AmazonWebServices.USEast1.Autoscalinggroup.AmazonEC2Ubuntuserver.WebApplication_1 + } + + } + + rectangle "Amazon RDS\\n[Deployment Node]\\n\\n" <> as Live.AmazonWebServices.USEast1.AmazonRDS { + rectangle "MySQL\\n[Deployment Node]\\n\\n" <> as Live.AmazonWebServices.USEast1.AmazonRDS.MySQL { + database "==Database Schema\\n[Container]" <> as Live.AmazonWebServices.USEast1.AmazonRDS.MySQL.DatabaseSchema_1 + } + + } + + rectangle "==DNS router\\n[Infrastructure Node: Route 53]\\n\\n\\n\\nRoutes incoming requests based upon domain name." <> as Live.AmazonWebServices.USEast1.DNSrouter + rectangle "==Load Balancer\\n[Infrastructure Node: Elastic Load Balancer]\\n\\n\\n\\nAutomatically distributes incoming application traffic." <> as Live.AmazonWebServices.USEast1.LoadBalancer + } + + } + + Live.AmazonWebServices.USEast1.LoadBalancer --> Live.AmazonWebServices.USEast1.Autoscalinggroup.AmazonEC2Ubuntuserver.WebApplication_1 <> : "Forwards requests to\\n[HTTPS]" + Live.AmazonWebServices.USEast1.Autoscalinggroup.AmazonEC2Ubuntuserver.WebApplication_1 --> Live.AmazonWebServices.USEast1.AmazonRDS.MySQL.DatabaseSchema_1 <> : "Reads from and writes to\\n[MySQL Protocol/SSL]" + Live.AmazonWebServices.USEast1.DNSrouter --> Live.AmazonWebServices.USEast1.LoadBalancer <> : "Forwards requests to\\n[HTTPS]" + + @enduml""", diagram.getDefinition()); + + assertEquals(""" + @startuml + + set separator none + hide stereotype + + + + rectangle "==Amazon Web Services - Auto Scaling\\n\\n" <> + + rectangle "==Amazon Web Services - Cloud\\n\\n" <> + + rectangle "==Amazon Web Services - EC2\\n\\n" <> + + rectangle "==Amazon Web Services - Elastic Load Balancing\\n\\n" <> + + rectangle "==Amazon Web Services - RDS\\n\\n" <> + + rectangle "==Amazon Web Services - RDS MySQL instance\\n\\n" <> + + rectangle "==Amazon Web Services - Region\\n\\n" <> + + rectangle "==Amazon Web Services - Route 53\\n\\n" <> + + rectangle "==Application" <> + + database "==Database" <> + + rectangle "." <<.Element-Transparent>> as 1 + 1 --> 1 <> : "Relationship" + + @enduml""", diagram.getLegend().getDefinition()); + } + + @Test + @Tag("IntegrationTest") + public void amazonWebServicesExample_Dark() throws Exception { + Workspace workspace = WorkspaceUtils.loadWorkspaceFromJson(new File("./src/test/resources/amazon-web-services.json")); + ThemeUtils.loadThemes(workspace); + + StructurizrPlantUMLExporter exporter = new StructurizrPlantUMLExporter(ColorScheme.Dark); + Collection diagrams = exporter.export(workspace); + assertEquals(1, diagrams.size()); + + Diagram diagram = diagrams.stream().findFirst().get(); + assertEquals(""" + @startuml + title Deployment View: X - Live + + set separator none + left to right direction + skinparam ranksep 60 + skinparam nodesep 30 + hide stereotype + + + + rectangle "Amazon Web Services\\n[Deployment Node]\\n\\n" <> as Live.AmazonWebServices { + rectangle "US-East-1\\n[Deployment Node]\\n\\n" <> as Live.AmazonWebServices.USEast1 { + rectangle "Autoscaling group\\n[Deployment Node]\\n\\n" <> as Live.AmazonWebServices.USEast1.Autoscalinggroup { + rectangle "Amazon EC2 - Ubuntu server\\n[Deployment Node]\\n\\n" <> as Live.AmazonWebServices.USEast1.Autoscalinggroup.AmazonEC2Ubuntuserver { + rectangle "==Web Application\\n[Container: Java and Spring Boot]" <> as Live.AmazonWebServices.USEast1.Autoscalinggroup.AmazonEC2Ubuntuserver.WebApplication_1 + } + + } + + rectangle "Amazon RDS\\n[Deployment Node]\\n\\n" <> as Live.AmazonWebServices.USEast1.AmazonRDS { + rectangle "MySQL\\n[Deployment Node]\\n\\n" <> as Live.AmazonWebServices.USEast1.AmazonRDS.MySQL { + database "==Database Schema\\n[Container]" <> as Live.AmazonWebServices.USEast1.AmazonRDS.MySQL.DatabaseSchema_1 + } + + } + + rectangle "==DNS router\\n[Infrastructure Node: Route 53]\\n\\n\\n\\nRoutes incoming requests based upon domain name." <> as Live.AmazonWebServices.USEast1.DNSrouter + rectangle "==Load Balancer\\n[Infrastructure Node: Elastic Load Balancer]\\n\\n\\n\\nAutomatically distributes incoming application traffic." <> as Live.AmazonWebServices.USEast1.LoadBalancer + } + + } + + Live.AmazonWebServices.USEast1.LoadBalancer --> Live.AmazonWebServices.USEast1.Autoscalinggroup.AmazonEC2Ubuntuserver.WebApplication_1 <> : "Forwards requests to\\n[HTTPS]" + Live.AmazonWebServices.USEast1.Autoscalinggroup.AmazonEC2Ubuntuserver.WebApplication_1 --> Live.AmazonWebServices.USEast1.AmazonRDS.MySQL.DatabaseSchema_1 <> : "Reads from and writes to\\n[MySQL Protocol/SSL]" + Live.AmazonWebServices.USEast1.DNSrouter --> Live.AmazonWebServices.USEast1.LoadBalancer <> : "Forwards requests to\\n[HTTPS]" + + @enduml""", diagram.getDefinition()); + + assertEquals(""" + @startuml + title Dynamic View: Internet Banking System - API Application\\nSummarises how the sign in feature works in the single-page application. + + set separator none + hide stereotype + + + + box "API Application\\n[Container: Java and Spring MVC]" + participant "Sign In Controller\\n[Component: Spring MVC Rest Controller]" as InternetBankingSystem.APIApplication.SignInController <> #85bbf0 + participant "Security Component\\n[Component: Spring Bean]" as InternetBankingSystem.APIApplication.SecurityComponent <> #85bbf0 + end box + + participant "Single-Page Application\\n[Container: JavaScript and Angular]" as InternetBankingSystem.SinglePageApplication <> #438dd5 + database "Database\\n[Container: Oracle Database Schema]" as InternetBankingSystem.Database <> #438dd5 + + InternetBankingSystem.SinglePageApplication -> InternetBankingSystem.APIApplication.SignInController <> : 1: Submits credentials to\\n[JSON/HTTPS] + InternetBankingSystem.APIApplication.SignInController -> InternetBankingSystem.APIApplication.SecurityComponent <> : 2: Validates credentials using + InternetBankingSystem.APIApplication.SecurityComponent -> InternetBankingSystem.Database <> : 3: select * from users where username = ?\\n[SQL/TCP] + InternetBankingSystem.APIApplication.SecurityComponent <-- InternetBankingSystem.Database <> : 4: Returns user data to\\n[SQL/TCP] + InternetBankingSystem.APIApplication.SignInController <-- InternetBankingSystem.APIApplication.SecurityComponent <> : 5: Returns true if the hashed password matches + InternetBankingSystem.SinglePageApplication <-- InternetBankingSystem.APIApplication.SignInController <> : 6: Sends back an authentication token to\\n[JSON/HTTPS] + + @enduml""", diagram.getLegend().getDefinition()); + } + + @Test + public void test_BigBankPlcExample_StructurizrPlantUML_Seq() throws Exception { + Workspace workspace = WorkspaceUtils.loadWorkspaceFromJson(new File("./src/test/resources/big-bank-plc.json")); + StructurizrPlantUMLExporter exporter = new StructurizrPlantUMLExporter(); + Collection diagrams = exporter.export(workspace); + assertEquals(7, diagrams.size()); + + Diagram diagram = diagrams.stream().filter(d -> d.getKey().equals("SignIn")).findFirst().get(); + assertEquals(""" + @startuml + title Dynamic View: Internet Banking System - API Application\\nSummarises how the sign in feature works in the single-page application. + + set separator none + top to bottom direction + skinparam ranksep 60 + skinparam nodesep 30 + hide stereotype + + + + rectangle "API Application\\n[Container: Java and Spring MVC]" <> { + rectangle "==Sign In Controller\\n[Component: Spring MVC Rest Controller]\\n\\nAllows users to sign in to the Internet Banking System." <> as InternetBankingSystem.APIApplication.SignInController + rectangle "==Security Component\\n[Component: Spring Bean]\\n\\nProvides functionality related to signing in, changing passwords, etc." <> as InternetBankingSystem.APIApplication.SecurityComponent + } + + rectangle "==Single-Page Application\\n[Container: JavaScript and Angular]\\n\\nProvides all of the Internet banking functionality to customers via their web browser." <> as InternetBankingSystem.SinglePageApplication + database "==Database\\n[Container: Oracle Database Schema]\\n\\nStores user registration information, hashed authentication credentials, access logs, etc." <> as InternetBankingSystem.Database + + InternetBankingSystem.SinglePageApplication --> InternetBankingSystem.APIApplication.SignInController <> : "1: Submits credentials to\\n[JSON/HTTPS]" + InternetBankingSystem.APIApplication.SignInController --> InternetBankingSystem.APIApplication.SecurityComponent <> : "2: Validates credentials using" + InternetBankingSystem.APIApplication.SecurityComponent --> InternetBankingSystem.Database <> : "3: select * from users where username = ?\\n[SQL/TCP]" + InternetBankingSystem.APIApplication.SecurityComponent <-- InternetBankingSystem.Database <> : "4: Returns user data to\\n[SQL/TCP]" + InternetBankingSystem.APIApplication.SignInController <-- InternetBankingSystem.APIApplication.SecurityComponent <> : "5: Returns true if the hashed password matches" + InternetBankingSystem.SinglePageApplication <-- InternetBankingSystem.APIApplication.SignInController <> : "6: Sends back an authentication token to\\n[JSON/HTTPS]" + + @enduml""", diagram.getDefinition()); + + // and the sequence diagram version + workspace.getViews().getConfiguration().addProperty(exporter.PLANTUML_SEQUENCE_DIAGRAM_PROPERTY, "true"); + diagrams = exporter.export(workspace); + diagram = diagrams.stream().filter(d -> d.getKey().equals("SignIn")).findFirst().get(); + assertEquals(""" + @startuml + title Dynamic View: Internet Banking System - API Application\\nSummarises how the sign in feature works in the single-page application. + + set separator none + hide stereotype + + + + box "API Application\\n[Container: Java and Spring MVC]" <> + box + participant "Sign In Controller\\n[Component: Spring MVC Rest Controller]" as InternetBankingSystem.APIApplication.SignInController <> #85bbf0 + participant "Security Component\\n[Component: Spring Bean]" as InternetBankingSystem.APIApplication.SecurityComponent <> #85bbf0 + end box + end box + + participant "Single-Page Application\\n[Container: JavaScript and Angular]" as InternetBankingSystem.SinglePageApplication <> #438dd5 + database "Database\\n[Container: Oracle Database Schema]" as InternetBankingSystem.Database <> #438dd5 + + InternetBankingSystem.SinglePageApplication -> InternetBankingSystem.APIApplication.SignInController <> : 1: Submits credentials to\\n[JSON/HTTPS] + InternetBankingSystem.APIApplication.SignInController -> InternetBankingSystem.APIApplication.SecurityComponent <> : 2: Validates credentials using + InternetBankingSystem.APIApplication.SecurityComponent -> InternetBankingSystem.Database <> : 3: select * from users where username = ?\\n[SQL/TCP] + InternetBankingSystem.APIApplication.SecurityComponent <-- InternetBankingSystem.Database <> : 4: Returns user data to\\n[SQL/TCP] + InternetBankingSystem.APIApplication.SignInController <-- InternetBankingSystem.APIApplication.SecurityComponent <> : 5: Returns true if the hashed password matches + InternetBankingSystem.SinglePageApplication <-- InternetBankingSystem.APIApplication.SignInController <> : 6: Sends back an authentication token to\\n[JSON/HTTPS] + + @enduml""", diagram.getDefinition()); + } } \ No newline at end of file diff --git a/structurizr-export/src/test/java/com/structurizr/export/plantuml/c4plantuml/groups-Components.puml b/structurizr-export/src/test/java/com/structurizr/export/plantuml/c4plantuml/groups-Components.puml new file mode 100644 index 00000000..7ae5aa49 --- /dev/null +++ b/structurizr-export/src/test/java/com/structurizr/export/plantuml/c4plantuml/groups-Components.puml @@ -0,0 +1,27 @@ +@startuml +set separator none +title D - F - Components + +top to bottom direction + +!include +!include +!include + +System(C, "C", $descr="", $tags="", $link="") + +Container_Boundary("D.F_boundary", "F", $tags="") { + AddBoundaryTag("Group 5", $borderColor="#cccccc", $fontColor="#cccccc", $borderStyle="dashed") + Boundary(group_1, "Group 5", $tags="Group 5") { + Component(D.F.H, "H", $techn="", $descr="", $tags="", $link="") + } + + Component(D.F.G, "G", $techn="", $descr="", $tags="", $link="") +} + +Rel(C, D.F.G, "", $techn="", $tags="", $link="") +Rel(C, D.F.H, "", $techn="", $tags="", $link="") +Rel(D.F.G, D.F.H, "", $techn="", $tags="", $link="") + +SHOW_LEGEND(true) +@enduml \ No newline at end of file diff --git a/structurizr-export/src/test/java/com/structurizr/export/plantuml/c4plantuml/groups-Dynamic.puml b/structurizr-export/src/test/java/com/structurizr/export/plantuml/c4plantuml/groups-Dynamic.puml new file mode 100644 index 00000000..f8045e14 --- /dev/null +++ b/structurizr-export/src/test/java/com/structurizr/export/plantuml/c4plantuml/groups-Dynamic.puml @@ -0,0 +1,13 @@ +@startuml +set separator none +title F - Dynamic + +!include + +Component(D.F.G, "G", $techn="", $descr="", $tags="", $link="") +Component(D.F.H, "H", $techn="", $descr="", $tags="", $link="") + +Rel(D.F.G, D.F.H, "", $techn="", $tags="", $link="") + +SHOW_LEGEND(true) +@enduml \ No newline at end of file diff --git a/structurizr-export/src/test/java/com/structurizr/export/plantuml/structurizr/36141-SignIn-sequence.puml b/structurizr-export/src/test/java/com/structurizr/export/plantuml/structurizr/36141-SignIn-sequence.puml new file mode 100644 index 00000000..12088885 --- /dev/null +++ b/structurizr-export/src/test/java/com/structurizr/export/plantuml/structurizr/36141-SignIn-sequence.puml @@ -0,0 +1,53 @@ +@startuml +set separator none +title API Application - Dynamic + +skinparam { + arrowFontSize 10 + defaultTextAlignment center + wrapWidth 200 + maxMessageSize 100 +} + +hide stereotype + +skinparam sequenceParticipant<> { + BackgroundColor #438dd5 + FontColor #ffffff + BorderColor #2e6295 + shadowing false +} +skinparam sequenceParticipant<> { + BackgroundColor #85bbf0 + FontColor #000000 + BorderColor #5d82a8 + shadowing false +} +skinparam sequenceParticipant<> { + BackgroundColor #85bbf0 + FontColor #000000 + BorderColor #5d82a8 + shadowing false +} +skinparam sequenceParticipant<> { + BackgroundColor #438dd5 + FontColor #ffffff + BorderColor #2e6295 + shadowing false +} + +box "API Application\n[Container: Java and Spring MVC]" + participant "Sign In Controller\n[Component: Spring MVC Rest Controller]" as InternetBankingSystem.APIApplication.SignInController <> #85bbf0 + participant "Security Component\n[Component: Spring Bean]" as InternetBankingSystem.APIApplication.SecurityComponent <> #85bbf0 +end box + +participant "Single-Page Application\n[Container: JavaScript and Angular]" as InternetBankingSystem.SinglePageApplication <> #438dd5 +database "Database\n[Container: Oracle Database Schema]" as InternetBankingSystem.Database <> #438dd5 + +InternetBankingSystem.SinglePageApplication -[#707070]> InternetBankingSystem.APIApplication.SignInController : Submits credentials to\n[JSON/HTTPS] +InternetBankingSystem.APIApplication.SignInController -[#707070]> InternetBankingSystem.APIApplication.SecurityComponent : Validates credentials using +InternetBankingSystem.APIApplication.SecurityComponent -[#707070]> InternetBankingSystem.Database : select * from users where username = ?\n[SQL/TCP] +InternetBankingSystem.APIApplication.SecurityComponent <-[#707070]- InternetBankingSystem.Database : Returns user data to\n[SQL/TCP] +InternetBankingSystem.APIApplication.SignInController <-[#707070]- InternetBankingSystem.APIApplication.SecurityComponent : Returns true if the hashed password matches +InternetBankingSystem.SinglePageApplication <-[#707070]- InternetBankingSystem.APIApplication.SignInController : Sends back an authentication token to\n[JSON/HTTPS] +@enduml \ No newline at end of file diff --git a/structurizr-export/src/test/resources/.structurizr/1/images/Components-thumbnail.png b/structurizr-export/src/test/resources/.structurizr/1/images/Components-thumbnail.png new file mode 100644 index 00000000..7df34da9 Binary files /dev/null and b/structurizr-export/src/test/resources/.structurizr/1/images/Components-thumbnail.png differ diff --git a/structurizr-export/src/test/resources/.structurizr/1/images/Containers-thumbnail.png b/structurizr-export/src/test/resources/.structurizr/1/images/Containers-thumbnail.png new file mode 100644 index 00000000..a676b738 Binary files /dev/null and b/structurizr-export/src/test/resources/.structurizr/1/images/Containers-thumbnail.png differ diff --git a/structurizr-export/src/test/resources/.structurizr/1/images/Diagram1-thumbnail.png b/structurizr-export/src/test/resources/.structurizr/1/images/Diagram1-thumbnail.png new file mode 100644 index 00000000..cd88cb6d Binary files /dev/null and b/structurizr-export/src/test/resources/.structurizr/1/images/Diagram1-thumbnail.png differ diff --git a/structurizr-export/src/test/resources/.structurizr/1/images/Diagram2-thumbnail.png b/structurizr-export/src/test/resources/.structurizr/1/images/Diagram2-thumbnail.png new file mode 100644 index 00000000..6b15ae17 Binary files /dev/null and b/structurizr-export/src/test/resources/.structurizr/1/images/Diagram2-thumbnail.png differ diff --git a/structurizr-export/src/test/resources/.structurizr/1/images/Sequence-Container-thumbnail.png b/structurizr-export/src/test/resources/.structurizr/1/images/Sequence-Container-thumbnail.png new file mode 100644 index 00000000..8b61f5b2 Binary files /dev/null and b/structurizr-export/src/test/resources/.structurizr/1/images/Sequence-Container-thumbnail.png differ diff --git a/structurizr-export/src/test/resources/.structurizr/1/images/Sequence-SoftwareSystem-thumbnail.png b/structurizr-export/src/test/resources/.structurizr/1/images/Sequence-SoftwareSystem-thumbnail.png new file mode 100644 index 00000000..f856bdd6 Binary files /dev/null and b/structurizr-export/src/test/resources/.structurizr/1/images/Sequence-SoftwareSystem-thumbnail.png differ diff --git a/structurizr-export/src/test/resources/.structurizr/1/images/SystemLandscape-thumbnail.png b/structurizr-export/src/test/resources/.structurizr/1/images/SystemLandscape-thumbnail.png new file mode 100644 index 00000000..461f4991 Binary files /dev/null and b/structurizr-export/src/test/resources/.structurizr/1/images/SystemLandscape-thumbnail.png differ diff --git a/structurizr-export/src/test/resources/.structurizr/1/images/thumbnail.png b/structurizr-export/src/test/resources/.structurizr/1/images/thumbnail.png new file mode 100644 index 00000000..f856bdd6 Binary files /dev/null and b/structurizr-export/src/test/resources/.structurizr/1/images/thumbnail.png differ diff --git a/structurizr-export/src/test/resources/.structurizr/index/_2.cfe b/structurizr-export/src/test/resources/.structurizr/index/_2.cfe new file mode 100644 index 00000000..c094f058 Binary files /dev/null and b/structurizr-export/src/test/resources/.structurizr/index/_2.cfe differ diff --git a/structurizr-export/src/test/resources/.structurizr/index/_2.cfs b/structurizr-export/src/test/resources/.structurizr/index/_2.cfs new file mode 100644 index 00000000..4c59fa83 Binary files /dev/null and b/structurizr-export/src/test/resources/.structurizr/index/_2.cfs differ diff --git a/structurizr-export/src/test/resources/.structurizr/index/_2.si b/structurizr-export/src/test/resources/.structurizr/index/_2.si new file mode 100644 index 00000000..1f38ac01 Binary files /dev/null and b/structurizr-export/src/test/resources/.structurizr/index/_2.si differ diff --git a/structurizr-export/src/test/resources/.structurizr/index/segments_3 b/structurizr-export/src/test/resources/.structurizr/index/segments_3 new file mode 100644 index 00000000..a675befc Binary files /dev/null and b/structurizr-export/src/test/resources/.structurizr/index/segments_3 differ diff --git a/structurizr-export/src/test/resources/.structurizr/index/write.lock b/structurizr-export/src/test/resources/.structurizr/index/write.lock new file mode 100644 index 00000000..e69de29b diff --git a/structurizr-export/src/test/resources/groups.dsl b/structurizr-export/src/test/resources/groups.dsl index c2f39a5b..b3f80576 100644 --- a/structurizr-export/src/test/resources/groups.dsl +++ b/structurizr-export/src/test/resources/groups.dsl @@ -36,6 +36,7 @@ workspace { c -> e c -> g c -> h + g -> h } @@ -54,6 +55,17 @@ workspace { include * autolayout } + + dynamic f "Dynamic" { + g -> h + properties { + "plantuml.sequenceDiagram" "true" + "plantuml.teoz" "true" + "mermaid.sequenceDiagram" "true" + "mermaid.title" "true" + } + autoLayout + } } } diff --git a/structurizr-export/src/test/resources/groups.json b/structurizr-export/src/test/resources/groups.json index 9ffc544f..aa05397c 100644 --- a/structurizr-export/src/test/resources/groups.json +++ b/structurizr-export/src/test/resources/groups.json @@ -1,242 +1,242 @@ { - "id" : 0, - "name" : "Name", - "description" : "Description", - "properties" : { - "structurizr.dsl" : "d29ya3NwYWNlIHsKCiAgICBtb2RlbCB7CiAgICAgICAgcHJvcGVydGllcyB7CiAgICAgICAgICAgIHN0cnVjdHVyaXpyLmdyb3VwU2VwYXJhdG9yIC8KICAgICAgICB9CgogICAgICAgIGEgPSBzb2Z0d2FyZVN5c3RlbSAiQSIKCiAgICAgICAgZ3JvdXAgIkdyb3VwIDEiIHsKICAgICAgICAgICAgYiA9IHNvZnR3YXJlU3lzdGVtICJCIgogICAgICAgIH0KCiAgICAgICAgZ3JvdXAgIkdyb3VwIDIiIHsKICAgICAgICAgICAgYyA9IHNvZnR3YXJlU3lzdGVtICJDIgoKICAgICAgICAgICAgZ3JvdXAgIkdyb3VwIDMiIHsKICAgICAgICAgICAgICAgIGQgPSBzb2Z0d2FyZVN5c3RlbSAiRCIgewogICAgICAgICAgICAgICAgICAgIGUgPSBjb250YWluZXIgIkUiCgogICAgICAgICAgICAgICAgICAgIGdyb3VwICJHcm91cCA0IiB7CiAgICAgICAgICAgICAgICAgICAgICAgIGYgPSBjb250YWluZXIgIkYiIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGcgPSBjb21wb25lbnQgIkciCgogICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXAgIkdyb3VwIDUiIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoID0gY29tcG9uZW50ICJIIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgfQoKICAgICAgICBhIC0+IGIKICAgICAgICBiIC0+IGMKICAgICAgICBjIC0+IGUKICAgICAgICBjIC0+IGcKICAgICAgICBjIC0+IGgKCiAgICB9CgogICAgdmlld3MgewogICAgICAgIHN5c3RlbWxhbmRzY2FwZSAiU3lzdGVtTGFuZHNjYXBlIiB7CiAgICAgICAgICAgIGluY2x1ZGUgKgogICAgICAgICAgICBhdXRvbGF5b3V0CiAgICAgICAgfQoKICAgICAgICBjb250YWluZXIgZCAiQ29udGFpbmVycyIgewogICAgICAgICAgICBpbmNsdWRlICoKICAgICAgICAgICAgYXV0b2xheW91dAogICAgICAgIH0KCiAgICAgICAgY29tcG9uZW50IGYgIkNvbXBvbmVudHMiIHsKICAgICAgICAgICAgaW5jbHVkZSAqCiAgICAgICAgICAgIGF1dG9sYXlvdXQKICAgICAgICB9CiAgICB9Cgp9Cg==" - }, "configuration" : { }, + "description" : "Description", + "documentation" : { }, + "id" : 1, + "lastModifiedAgent" : "structurizr-ui", + "lastModifiedDate" : "2025-09-29T14:50:11Z", "model" : { + "properties" : { + "structurizr.groupSeparator" : "/" + }, "softwareSystems" : [ { + "documentation" : { }, "id" : "1", - "tags" : "Element,Software System", + "location" : "Unspecified", + "name" : "A", "properties" : { "structurizr.dsl.identifier" : "a" }, - "name" : "A", "relationships" : [ { + "destinationId" : "2", "id" : "9", - "tags" : "Relationship", - "properties" : { - "structurizr.dsl.identifier" : "370d771e-c1ab-4243-b9cd-2325145b20bc" - }, "sourceId" : "1", - "destinationId" : "2" + "tags" : "Relationship" } ], - "location" : "Unspecified", - "documentation" : { } + "tags" : "Element,Software System" }, { + "documentation" : { }, + "group" : "Group 1", "id" : "2", - "tags" : "Element,Software System", + "location" : "Unspecified", + "name" : "B", "properties" : { "structurizr.dsl.identifier" : "b" }, - "name" : "B", "relationships" : [ { + "destinationId" : "3", "id" : "10", - "tags" : "Relationship", - "properties" : { - "structurizr.dsl.identifier" : "923c8280-f475-422a-9fef-2b69220bc8e6" - }, "sourceId" : "2", - "destinationId" : "3" + "tags" : "Relationship" } ], - "group" : "Group 1", - "location" : "Unspecified", - "documentation" : { } + "tags" : "Element,Software System" }, { + "documentation" : { }, + "group" : "Group 2", "id" : "3", - "tags" : "Element,Software System", + "location" : "Unspecified", + "name" : "C", "properties" : { "structurizr.dsl.identifier" : "c" }, - "name" : "C", "relationships" : [ { + "destinationId" : "5", "id" : "11", - "tags" : "Relationship", - "properties" : { - "structurizr.dsl.identifier" : "cd5c9209-d4f5-4ffa-a323-dc0d5612cdb8" - }, "sourceId" : "3", - "destinationId" : "5" + "tags" : "Relationship" }, { - "id" : "12", - "sourceId" : "3", "destinationId" : "4", - "linkedRelationshipId" : "11" + "id" : "12", + "linkedRelationshipId" : "11", + "sourceId" : "3" }, { + "destinationId" : "7", "id" : "13", - "tags" : "Relationship", - "properties" : { - "structurizr.dsl.identifier" : "0f23b6e5-9276-4468-b3fb-9381f53c8cd7" - }, "sourceId" : "3", - "destinationId" : "7" + "tags" : "Relationship" }, { - "id" : "14", - "sourceId" : "3", "destinationId" : "6", - "linkedRelationshipId" : "13" + "id" : "14", + "linkedRelationshipId" : "13", + "sourceId" : "3" }, { + "destinationId" : "8", "id" : "15", - "tags" : "Relationship", - "properties" : { - "structurizr.dsl.identifier" : "b0b99a2e-129b-4f75-81c0-de9c67916229" - }, "sourceId" : "3", - "destinationId" : "8" + "tags" : "Relationship" } ], - "group" : "Group 2", - "location" : "Unspecified", - "documentation" : { } + "tags" : "Element,Software System" }, { - "id" : "4", - "tags" : "Element,Software System", - "properties" : { - "structurizr.dsl.identifier" : "d" - }, - "name" : "D", - "group" : "Group 2/Group 3", - "location" : "Unspecified", "containers" : [ { - "id" : "6", - "tags" : "Element,Container", + "documentation" : { }, + "id" : "5", + "name" : "E", "properties" : { - "structurizr.dsl.identifier" : "f" + "structurizr.dsl.identifier" : "e" }, - "name" : "F", - "group" : "Group 4", + "tags" : "Element,Container" + }, { "components" : [ { - "id" : "8", - "tags" : "Element,Component", + "documentation" : { }, + "id" : "7", + "name" : "G", "properties" : { - "structurizr.dsl.identifier" : "h" + "structurizr.dsl.identifier" : "g" }, - "name" : "H", - "group" : "Group 5", - "documentation" : { } + "relationships" : [ { + "destinationId" : "8", + "id" : "16", + "sourceId" : "7", + "tags" : "Relationship" + } ], + "tags" : "Element,Component" }, { - "id" : "7", - "tags" : "Element,Component", + "documentation" : { }, + "group" : "Group 5", + "id" : "8", + "name" : "H", "properties" : { - "structurizr.dsl.identifier" : "g" + "structurizr.dsl.identifier" : "h" }, - "name" : "G", - "documentation" : { } + "tags" : "Element,Component" } ], - "documentation" : { } - }, { - "id" : "5", - "tags" : "Element,Container", + "documentation" : { }, + "group" : "Group 4", + "id" : "6", + "name" : "F", "properties" : { - "structurizr.dsl.identifier" : "e" + "structurizr.dsl.identifier" : "f" }, - "name" : "E", - "documentation" : { } + "tags" : "Element,Container" } ], - "documentation" : { } - } ], - "properties" : { - "structurizr.groupSeparator" : "/" - } + "documentation" : { }, + "group" : "Group 2/Group 3", + "id" : "4", + "location" : "Unspecified", + "name" : "D", + "properties" : { + "structurizr.dsl.identifier" : "d" + }, + "tags" : "Element,Software System" + } ] + }, + "name" : "Name", + "properties" : { + "structurizr.dsl" : "d29ya3NwYWNlIHsKCiAgICBtb2RlbCB7CiAgICAgICAgcHJvcGVydGllcyB7CiAgICAgICAgICAgIHN0cnVjdHVyaXpyLmdyb3VwU2VwYXJhdG9yIC8KICAgICAgICB9CgogICAgICAgIGEgPSBzb2Z0d2FyZVN5c3RlbSAiQSIKCiAgICAgICAgZ3JvdXAgIkdyb3VwIDEiIHsKICAgICAgICAgICAgYiA9IHNvZnR3YXJlU3lzdGVtICJCIgogICAgICAgIH0KCiAgICAgICAgZ3JvdXAgIkdyb3VwIDIiIHsKICAgICAgICAgICAgYyA9IHNvZnR3YXJlU3lzdGVtICJDIgoKICAgICAgICAgICAgZ3JvdXAgIkdyb3VwIDMiIHsKICAgICAgICAgICAgICAgIGQgPSBzb2Z0d2FyZVN5c3RlbSAiRCIgewogICAgICAgICAgICAgICAgICAgIGUgPSBjb250YWluZXIgIkUiCgogICAgICAgICAgICAgICAgICAgIGdyb3VwICJHcm91cCA0IiB7CiAgICAgICAgICAgICAgICAgICAgICAgIGYgPSBjb250YWluZXIgIkYiIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGcgPSBjb21wb25lbnQgIkciCgogICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXAgIkdyb3VwIDUiIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoID0gY29tcG9uZW50ICJIIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgfQoKICAgICAgICBhIC0+IGIKICAgICAgICBiIC0+IGMKICAgICAgICBjIC0+IGUKICAgICAgICBjIC0+IGcKICAgICAgICBjIC0+IGgKICAgICAgICBnIC0+IGgKCiAgICB9CgogICAgdmlld3MgewogICAgICAgIHN5c3RlbWxhbmRzY2FwZSAiU3lzdGVtTGFuZHNjYXBlIiB7CiAgICAgICAgICAgIGluY2x1ZGUgKgogICAgICAgICAgICBhdXRvbGF5b3V0CiAgICAgICAgfQoKICAgICAgICBjb250YWluZXIgZCAiQ29udGFpbmVycyIgewogICAgICAgICAgICBpbmNsdWRlICoKICAgICAgICAgICAgYXV0b2xheW91dAogICAgICAgIH0KCiAgICAgICAgY29tcG9uZW50IGYgIkNvbXBvbmVudHMiIHsKICAgICAgICAgICAgaW5jbHVkZSAqCiAgICAgICAgICAgIGF1dG9sYXlvdXQKICAgICAgICB9CgogICAgICAgIGR5bmFtaWMgZiAiRHluYW1pYyIgewogICAgICAgICAgICBnIC0+IGgKICAgICAgICAgICAgcHJvcGVydGllcyB7CiAgICAgICAgICAgICAgICAicGxhbnR1bWwuc2VxdWVuY2VEaWFncmFtIiAidHJ1ZSIKICAgICAgICAgICAgICAgICJwbGFudHVtbC50ZW96IiAidHJ1ZSIKICAgICAgICAgICAgICAgICJtZXJtYWlkLnNlcXVlbmNlRGlhZ3JhbSIgInRydWUiCiAgICAgICAgICAgICAgICAibWVybWFpZC50aXRsZSIgInRydWUiCiAgICAgICAgICAgIH0KICAgICAgICAgICAgYXV0b0xheW91dAogICAgICAgIH0KICAgIH0KCn0=" }, - "documentation" : { }, "views" : { - "systemLandscapeViews" : [ { - "key" : "SystemLandscape", - "order" : 1, + "componentViews" : [ { "automaticLayout" : { + "applied" : true, + "edgeSeparation" : 0, "implementation" : "Graphviz", + "nodeSeparation" : 300, "rankDirection" : "TopBottom", "rankSeparation" : 300, - "nodeSeparation" : 300, - "edgeSeparation" : 0, - "vertices" : false, - "applied" : false + "vertices" : false }, - "enterpriseBoundaryVisible" : true, - "relationships" : [ { - "id" : "12" + "containerId" : "6", + "dimensions" : { + "height" : 2065, + "width" : 1212 + }, + "elements" : [ { + "id" : "3", + "x" : 200, + "y" : 165 }, { - "id" : "9" + "id" : "7", + "x" : 542, + "y" : 765 }, { - "id" : "10" + "id" : "8", + "x" : 488, + "y" : 1365 } ], - "elements" : [ { - "id" : "1", - "x" : 0, - "y" : 0 - }, { - "id" : "2", - "x" : 0, - "y" : 0 + "externalContainerBoundariesVisible" : false, + "key" : "Components", + "order" : 3, + "relationships" : [ { + "id" : "13" }, { - "id" : "3", - "x" : 0, - "y" : 0 + "id" : "15", + "vertices" : [ { + "x" : 259, + "y" : 661 + }, { + "x" : 259, + "y" : 1065 + } ] }, { - "id" : "4", - "x" : 0, - "y" : 0 + "id" : "16" } ] } ], + "configuration" : { + "branding" : { }, + "lastSavedView" : "Components", + "metadataSymbols" : "SquareBrackets", + "styles" : { }, + "terminology" : { } + }, "containerViews" : [ { - "key" : "Containers", - "order" : 2, - "softwareSystemId" : "4", "automaticLayout" : { + "applied" : true, + "edgeSeparation" : 0, "implementation" : "Graphviz", + "nodeSeparation" : 300, "rankDirection" : "TopBottom", "rankSeparation" : 300, - "nodeSeparation" : 300, - "edgeSeparation" : 0, - "vertices" : false, - "applied" : false + "vertices" : false + }, + "dimensions" : { + "height" : 1465, + "width" : 1660 }, - "externalSoftwareSystemBoundariesVisible" : true, - "relationships" : [ { - "id" : "14" - }, { - "id" : "11" - } ], "elements" : [ { "id" : "3", - "x" : 0, - "y" : 0 + "x" : 594, + "y" : 165 }, { "id" : "5", - "x" : 0, - "y" : 0 + "x" : 219, + "y" : 765 }, { "id" : "6", - "x" : 0, - "y" : 0 - } ] + "x" : 969, + "y" : 765 + } ], + "externalSoftwareSystemBoundariesVisible" : false, + "key" : "Containers", + "order" : 2, + "relationships" : [ { + "id" : "11" + }, { + "id" : "14" + } ], + "softwareSystemId" : "4" } ], - "componentViews" : [ { - "key" : "Components", - "order" : 3, + "dynamicViews" : [ { "automaticLayout" : { + "applied" : false, + "edgeSeparation" : 0, "implementation" : "Graphviz", + "nodeSeparation" : 300, "rankDirection" : "TopBottom", "rankSeparation" : 300, - "nodeSeparation" : 300, - "edgeSeparation" : 0, - "vertices" : false, - "applied" : false + "vertices" : false }, - "containerId" : "6", - "externalContainerBoundariesVisible" : true, - "relationships" : [ { - "id" : "15" - }, { - "id" : "13" - } ], + "elementId" : "6", "elements" : [ { - "id" : "3", - "x" : 0, - "y" : 0 - }, { "id" : "7", "x" : 0, "y" : 0 @@ -244,12 +244,63 @@ "id" : "8", "x" : 0, "y" : 0 + } ], + "externalBoundariesVisible" : false, + "key" : "Dynamic", + "order" : 4, + "properties" : { + "mermaid.sequenceDiagram" : "true", + "mermaid.title" : "true", + "plantuml.sequenceDiagram" : "true", + "plantuml.teoz" : "true" + }, + "relationships" : [ { + "id" : "16", + "order" : "1", + "response" : false } ] } ], - "configuration" : { - "branding" : { }, - "styles" : { }, - "terminology" : { } - } + "systemLandscapeViews" : [ { + "automaticLayout" : { + "applied" : true, + "edgeSeparation" : 0, + "implementation" : "Graphviz", + "nodeSeparation" : 300, + "rankDirection" : "TopBottom", + "rankSeparation" : 300, + "vertices" : false + }, + "dimensions" : { + "height" : 2648, + "width" : 930 + }, + "elements" : [ { + "id" : "1", + "x" : 239, + "y" : 165 + }, { + "id" : "2", + "x" : 239, + "y" : 765 + }, { + "id" : "3", + "x" : 239, + "y" : 1365 + }, { + "id" : "4", + "x" : 239, + "y" : 1965 + } ], + "enterpriseBoundaryVisible" : true, + "key" : "SystemLandscape", + "order" : 1, + "relationships" : [ { + "id" : "10" + }, { + "id" : "12" + }, { + "id" : "9" + } ] + } ] } } \ No newline at end of file diff --git a/structurizr-export/src/test/resources/sequence.dsl b/structurizr-export/src/test/resources/sequence.dsl new file mode 100644 index 00000000..f913d064 --- /dev/null +++ b/structurizr-export/src/test/resources/sequence.dsl @@ -0,0 +1,62 @@ +workspace { + + model { + properties { + structurizr.groupSeparator / + } + + a = softwareSystem "A" { + b = container "B" { + c = component "C" + } + } + d = softwareSystem "d" { + group1 = group "group1" { + e = container "e" { + group2 = group "group2" { + f = component "f" + g = component "g" + h = component "h" + } + } + } + } + + b -> e + f -> h + c -> f + f -> g + + } + + views { + styles { + element "Software System" { + background "LightBlue" + } + element "Container" { + background "Beige" + } + } + properties { + "plantuml.sequenceDiagram" "true" + "plantuml.teoz" "true" + "mermaid.sequenceDiagram" "true" + "mermaid.title" "true" + "structurizr.softwareSystemBoundaries" "true" + } + + dynamic a "Sequence-SoftwareSystem" { + b -> e + autoLayout + } + + dynamic e "Sequence-Container" { + f -> h + c -> f + f -> g + autoLayout + } + } + +} diff --git a/structurizr-export/src/test/resources/sequence.json b/structurizr-export/src/test/resources/sequence.json new file mode 100644 index 00000000..b0be5f9e --- /dev/null +++ b/structurizr-export/src/test/resources/sequence.json @@ -0,0 +1,252 @@ +{ + "configuration" : { }, + "description" : "Description", + "documentation" : { }, + "id" : 1, + "lastModifiedAgent" : "structurizr-ui", + "lastModifiedDate" : "2025-10-02T10:16:15Z", + "model" : { + "properties" : { + "structurizr.groupSeparator" : "/" + }, + "softwareSystems" : [ { + "containers" : [ { + "components" : [ { + "documentation" : { }, + "id" : "3", + "name" : "C", + "properties" : { + "structurizr.dsl.identifier" : "c" + }, + "relationships" : [ { + "destinationId" : "6", + "id" : "14", + "sourceId" : "3", + "tags" : "Relationship" + }, { + "destinationId" : "5", + "id" : "15", + "linkedRelationshipId" : "14", + "sourceId" : "3" + }, { + "destinationId" : "4", + "id" : "16", + "linkedRelationshipId" : "14", + "sourceId" : "3" + } ], + "tags" : "Element,Component" + } ], + "documentation" : { }, + "id" : "2", + "name" : "B", + "properties" : { + "structurizr.dsl.identifier" : "b" + }, + "relationships" : [ { + "destinationId" : "5", + "id" : "9", + "sourceId" : "2", + "tags" : "Relationship" + }, { + "destinationId" : "4", + "id" : "10", + "linkedRelationshipId" : "9", + "sourceId" : "2" + }, { + "destinationId" : "6", + "id" : "17", + "linkedRelationshipId" : "14", + "sourceId" : "2" + } ], + "tags" : "Element,Container" + } ], + "documentation" : { }, + "id" : "1", + "location" : "Unspecified", + "name" : "A", + "properties" : { + "structurizr.dsl.identifier" : "a" + }, + "relationships" : [ { + "destinationId" : "5", + "id" : "11", + "linkedRelationshipId" : "9", + "sourceId" : "1" + }, { + "destinationId" : "4", + "id" : "12", + "linkedRelationshipId" : "9", + "sourceId" : "1" + }, { + "destinationId" : "6", + "id" : "18", + "linkedRelationshipId" : "14", + "sourceId" : "1" + } ], + "tags" : "Element,Software System" + }, { + "containers" : [ { + "components" : [ { + "documentation" : { }, + "group" : "group2", + "id" : "6", + "name" : "f", + "properties" : { + "structurizr.dsl.identifier" : "f" + }, + "relationships" : [ { + "destinationId" : "8", + "id" : "13", + "sourceId" : "6", + "tags" : "Relationship" + }, { + "destinationId" : "7", + "id" : "19", + "sourceId" : "6", + "tags" : "Relationship" + } ], + "tags" : "Element,Component" + }, { + "documentation" : { }, + "group" : "group2", + "id" : "7", + "name" : "g", + "properties" : { + "structurizr.dsl.identifier" : "g" + }, + "tags" : "Element,Component" + }, { + "documentation" : { }, + "group" : "group2", + "id" : "8", + "name" : "h", + "properties" : { + "structurizr.dsl.identifier" : "h" + }, + "tags" : "Element,Component" + } ], + "documentation" : { }, + "group" : "group1", + "id" : "5", + "name" : "e", + "properties" : { + "structurizr.dsl.identifier" : "e" + }, + "tags" : "Element,Container" + } ], + "documentation" : { }, + "id" : "4", + "location" : "Unspecified", + "name" : "d", + "properties" : { + "structurizr.dsl.identifier" : "d" + }, + "tags" : "Element,Software System" + } ] + }, + "name" : "Name", + "properties" : { + "structurizr.dsl" : "d29ya3NwYWNlIHsKCiAgICBtb2RlbCB7CiAgICAgICAgcHJvcGVydGllcyB7CiAgICAgICAgICAgIHN0cnVjdHVyaXpyLmdyb3VwU2VwYXJhdG9yIC8KICAgICAgICB9CgogICAgICAgIGEgPSBzb2Z0d2FyZVN5c3RlbSAiQSIgewogICAgICAgICAgICBiID0gY29udGFpbmVyICJCIiB7CiAgICAgICAgICAgICAgICBjID0gY29tcG9uZW50ICJDIgogICAgICAgICAgICB9CiAgICAgICAgfQogICAgICAgIGQgPSBzb2Z0d2FyZVN5c3RlbSAiZCIgewogICAgICAgICAgICBncm91cDEgPSBncm91cCAiZ3JvdXAxIiB7CiAgICAgICAgICAgICAgICBlID0gY29udGFpbmVyICJlIiB7CiAgICAgICAgICAgICAgICAgICAgZ3JvdXAyID0gZ3JvdXAgImdyb3VwMiIgewogICAgICAgICAgICAgICAgICAgICAgICBmID0gY29tcG9uZW50ICJmIgogICAgICAgICAgICAgICAgICAgICAgICBnID0gY29tcG9uZW50ICJnIgogICAgICAgICAgICAgICAgICAgICAgICBoID0gY29tcG9uZW50ICJoIgogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgIH0KCiAgICAgICAgYiAtPiBlCiAgICAgICAgZiAtPiBoCiAgICAgICAgYyAtPiBmCiAgICAgICAgZiAtPiBnCgogICAgfQoKICAgIHZpZXdzIHsKICAgICAgICBzdHlsZXMgewogICAgICAgICAgICBlbGVtZW50ICJTb2Z0d2FyZSBTeXN0ZW0iIHsKICAgICAgICAgICAgICAgIGJhY2tncm91bmQgIkxpZ2h0Qmx1ZSIKICAgICAgICAgICAgfQogICAgICAgICAgICBlbGVtZW50ICJDb250YWluZXIiIHsKICAgICAgICAgICAgICAgIGJhY2tncm91bmQgIkJlaWdlIgogICAgICAgICAgICB9CiAgICAgICAgfQogICAgICAgIHByb3BlcnRpZXMgewogICAgICAgICAgICAicGxhbnR1bWwuc2VxdWVuY2VEaWFncmFtIiAidHJ1ZSIKICAgICAgICAgICAgInBsYW50dW1sLnRlb3oiICJ0cnVlIgogICAgICAgICAgICAibWVybWFpZC5zZXF1ZW5jZURpYWdyYW0iICJ0cnVlIgogICAgICAgICAgICAibWVybWFpZC50aXRsZSIgInRydWUiCiAgICAgICAgICAgICJzdHJ1Y3R1cml6ci5zb2Z0d2FyZVN5c3RlbUJvdW5kYXJpZXMiICJ0cnVlIgogICAgICAgIH0KICAgICAgICAKICAgICAgICBkeW5hbWljIGEgIlNlcXVlbmNlLVNvZnR3YXJlU3lzdGVtIiB7CiAgICAgICAgICAgIGIgLT4gZQogICAgICAgICAgICBhdXRvTGF5b3V0CiAgICAgICAgfQogICAgICAgICAgICAKICAgICAgICBkeW5hbWljIGUgIlNlcXVlbmNlLUNvbnRhaW5lciIgewogICAgICAgICAgICBmIC0+IGgKICAgICAgICAgICAgYyAtPiBmCiAgICAgICAgICAgIGYgLT4gZwogICAgICAgICAgICBhdXRvTGF5b3V0CiAgICAgICAgfQogICAgfQoKfQ==" + }, + "views" : { + "configuration" : { + "branding" : { }, + "lastSavedView" : "Sequence-Container", + "metadataSymbols" : "SquareBrackets", + "properties" : { + "mermaid.sequenceDiagram" : "true", + "mermaid.title" : "true", + "plantuml.sequenceDiagram" : "true", + "plantuml.teoz" : "true", + "structurizr.softwareSystemBoundaries" : "true" + }, + "styles" : { + "elements" : [ { + "background" : "#add8e6", + "tag" : "Software System" + }, { + "background" : "#f5f5dc", + "tag" : "Container" + } ] + }, + "terminology" : { } + }, + "dynamicViews" : [ { + "automaticLayout" : { + "applied" : false, + "edgeSeparation" : 0, + "implementation" : "Graphviz", + "nodeSeparation" : 300, + "rankDirection" : "TopBottom", + "rankSeparation" : 300, + "vertices" : false + }, + "elementId" : "1", + "elements" : [ { + "id" : "2", + "x" : 0, + "y" : 0 + }, { + "id" : "5", + "x" : 0, + "y" : 0 + } ], + "externalBoundariesVisible" : false, + "key" : "Sequence-SoftwareSystem", + "order" : 1, + "relationships" : [ { + "id" : "9", + "order" : "1", + "response" : false + } ] + }, { + "automaticLayout" : { + "applied" : true, + "edgeSeparation" : 0, + "implementation" : "Graphviz", + "nodeSeparation" : 300, + "rankDirection" : "TopBottom", + "rankSeparation" : 300, + "vertices" : false + }, + "dimensions" : { + "height" : 2131, + "width" : 1680 + }, + "elementId" : "5", + "elements" : [ { + "id" : "3", + "x" : 615, + "y" : 185 + }, { + "id" : "6", + "x" : 615, + "y" : 831 + }, { + "id" : "7", + "x" : 240, + "y" : 1431 + }, { + "id" : "8", + "x" : 990, + "y" : 1431 + } ], + "externalBoundariesVisible" : false, + "key" : "Sequence-Container", + "order" : 2, + "relationships" : [ { + "id" : "13", + "order" : "1", + "response" : false + }, { + "id" : "14", + "order" : "2", + "response" : false + }, { + "id" : "19", + "order" : "3", + "response" : false + } ] + } ] + } +} \ No newline at end of file