Skip to content

Commit 346095e

Browse files
Closes #408.
1 parent 4e8cfbf commit 346095e

File tree

8 files changed

+187
-33
lines changed

8 files changed

+187
-33
lines changed

changelog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
- structurizr-dsl: Fixes https://github.com/structurizr/java/issues/399 (Archetype tags sometimes missing).
99
- structurizr-dsl: Fixes https://github.com/structurizr/java/issues/392 (SVG not supported in base 64 encoding not mentioned in documentation).
1010
- structurizr-dsl: Adds support for setting the symbols surrounding element/relationship metadata used when rendering diagrams.
11+
- structurizr-dsl: Fixes https://github.com/structurizr/java/issues/408 (Animation steps cannot be added to deployment views via static structure element references).
12+
- structurizr-dsl: Adds support for specifying view animation steps via element expressions (deployment views only; others to follow).
1113
- structurizr-export: Adds support for rank and node separation to the StructurizrPlantUMLExporter.
1214

1315
## v4.0.0 (28th March 2025)

structurizr-core/src/main/java/com/structurizr/view/DeploymentView.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -403,14 +403,14 @@ private void addAnimationStep(Element... elements) {
403403
}
404404
}
405405

406-
if (elementsInThisAnimationStep.size() == 0) {
407-
throw new IllegalArgumentException("None of the specified container instances exist in this view.");
406+
if (elementsInThisAnimationStep.isEmpty()) {
407+
throw new IllegalArgumentException("None of the specified elements exist in this view.");
408408
}
409409

410410
for (RelationshipView relationshipView : this.getRelationships()) {
411411
if (
412412
(elementsInThisAnimationStep.contains(relationshipView.getRelationship().getSource()) && elementIdsInPreviousAnimationSteps.contains(relationshipView.getRelationship().getDestination().getId())) ||
413-
(elementIdsInPreviousAnimationSteps.contains(relationshipView.getRelationship().getSource().getId()) && elementsInThisAnimationStep.contains(relationshipView.getRelationship().getDestination()))
413+
(elementIdsInPreviousAnimationSteps.contains(relationshipView.getRelationship().getSource().getId()) && elementsInThisAnimationStep.contains(relationshipView.getRelationship().getDestination()))
414414
) {
415415
relationshipsInThisAnimationStep.add(relationshipView.getRelationship());
416416
}

structurizr-dsl/src/main/java/com/structurizr/dsl/DeploymentViewAnimationStepParser.java

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,78 @@
11
package com.structurizr.dsl;
22

3-
import com.structurizr.model.ContainerInstance;
4-
import com.structurizr.model.Element;
5-
import com.structurizr.model.InfrastructureNode;
6-
import com.structurizr.model.StaticStructureElementInstance;
3+
import com.structurizr.model.*;
74
import com.structurizr.view.DeploymentView;
85

96
import java.util.ArrayList;
107
import java.util.List;
8+
import java.util.Set;
119

1210
final class DeploymentViewAnimationStepParser extends AbstractParser {
1311

12+
private static final String GRAMMAR = "<identifier|element expression> [identifier|element expression...]";
13+
1414
void parse(DeploymentViewDslContext context, Tokens tokens) {
15-
// animationStep <identifier> [identifier...]
15+
// animationStep <identifier|element expression> [identifier|element expression...]
1616

1717
if (!tokens.includes(1)) {
18-
throw new RuntimeException("Expected: animationStep <identifier> [identifier...]");
18+
throw new RuntimeException("Expected: animationStep " + GRAMMAR);
1919
}
2020

2121
parse(context, context.getView(), tokens, 1);
2222
}
2323

2424
void parse(DeploymentViewAnimationDslContext context, Tokens tokens) {
25-
// animationStep <identifier> [identifier...]
25+
// <identifier|element expression> [identifier|element expression...]
2626

2727
if (!tokens.includes(0)) {
28-
throw new RuntimeException("Expected: <identifier> [identifier...]");
28+
throw new RuntimeException("Expected: " + GRAMMAR);
2929
}
3030

3131
parse(context, context.getView(), tokens, 0);
3232
}
3333

34-
void parse(DslContext context, DeploymentView view, Tokens tokens, int startIndex) {
34+
private void parse(DslContext context, DeploymentView view, Tokens tokens, int startIndex) {
3535
List<StaticStructureElementInstance> staticStructureElementInstances = new ArrayList<>();
3636
List<InfrastructureNode> infrastructureNodes = new ArrayList<>();
3737

3838
for (int i = startIndex; i < tokens.size(); i++) {
39-
String identifier = tokens.get(i);
40-
41-
Element element = context.getElement(identifier);
42-
if (element == null) {
43-
throw new RuntimeException("The element \"" + identifier + "\" does not exist");
44-
}
45-
46-
if (element instanceof StaticStructureElementInstance) {
47-
staticStructureElementInstances.add((StaticStructureElementInstance)element);
48-
}
49-
50-
if (element instanceof InfrastructureNode) {
51-
infrastructureNodes.add((InfrastructureNode)element);
39+
String token = tokens.get(i);
40+
41+
if (ExpressionParser.isExpression(token.toLowerCase())) {
42+
Set<ModelItem> elements = new DeploymentViewExpressionParser().parseExpression(token, context);
43+
44+
for (ModelItem element : elements) {
45+
if (element instanceof StaticStructureElementInstance) {
46+
staticStructureElementInstances.add((StaticStructureElementInstance)element);
47+
}
48+
49+
if (element instanceof InfrastructureNode) {
50+
infrastructureNodes.add((InfrastructureNode)element);
51+
}
52+
}
53+
} else {
54+
Set<ModelItem> elements = new DeploymentViewExpressionParser().parseIdentifier(token, context);
55+
56+
if (elements.isEmpty()) {
57+
throw new RuntimeException("The element \"" + token + "\" does not exist");
58+
}
59+
60+
for (ModelItem element : elements) {
61+
if (element instanceof StaticStructureElementInstance) {
62+
staticStructureElementInstances.add((StaticStructureElementInstance)element);
63+
}
64+
65+
if (element instanceof InfrastructureNode) {
66+
infrastructureNodes.add((InfrastructureNode)element);
67+
}
68+
}
5269
}
5370
}
5471

5572
if (!(staticStructureElementInstances.isEmpty() && infrastructureNodes.isEmpty())) {
5673
view.addAnimation(staticStructureElementInstances.toArray(new StaticStructureElementInstance[0]), infrastructureNodes.toArray(new InfrastructureNode[0]));
74+
} else {
75+
throw new RuntimeException("No software system instances, container instances, or infrastructure nodes were found");
5776
}
5877
}
5978

structurizr-dsl/src/main/java/com/structurizr/dsl/DeploymentViewContentParser.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,11 @@ final class DeploymentViewContentParser extends ModelViewContentParser {
1414

1515
void parseInclude(DeploymentViewDslContext context, Tokens tokens) {
1616
if (!tokens.includes(FIRST_IDENTIFIER_INDEX)) {
17-
throw new RuntimeException("Expected: include <*|identifier> [*|identifier...]");
17+
throw new RuntimeException("Expected: include <*|identifier|expression> [*|identifier|expression...]");
1818
}
1919

2020
DeploymentView view = context.getView();
2121

22-
// include <identifier> [identifier...]
2322
for (int i = FIRST_IDENTIFIER_INDEX; i < tokens.size(); i++) {
2423
String token = tokens.get(i);
2524

@@ -36,12 +35,12 @@ void parseInclude(DeploymentViewDslContext context, Tokens tokens) {
3635

3736
void parseExclude(DeploymentViewDslContext context, Tokens tokens) {
3837
if (!tokens.includes(FIRST_IDENTIFIER_INDEX)) {
39-
throw new RuntimeException("Expected: exclude <identifier> [identifier...]");
38+
throw new RuntimeException("Expected: exclude <identifier|expression> [identifier|expression...]");
4039
}
4140

4241
DeploymentView view = context.getView();
4342

44-
// exclude <identifier> [identifier...]
43+
4544
for (int i = FIRST_IDENTIFIER_INDEX; i < tokens.size(); i++) {
4645
String token = tokens.get(i);
4746

structurizr-dsl/src/test/java/com/structurizr/dsl/DeploymentViewAnimationStepParserTests.java

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.structurizr.dsl;
22

3+
import com.structurizr.model.DeploymentNode;
4+
import com.structurizr.view.DeploymentView;
35
import org.junit.jupiter.api.Test;
46

57
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -15,7 +17,7 @@ void test_parseExplicit_ThrowsAnException_WhenElementsAreMissing() {
1517
parser.parse((DeploymentViewDslContext)null, tokens("animationStep"));
1618
fail();
1719
} catch (Exception e) {
18-
assertEquals("Expected: animationStep <identifier> [identifier...]", e.getMessage());
20+
assertEquals("Expected: animationStep <identifier|element expression> [identifier|element expression...]", e.getMessage());
1921
}
2022
}
2123

@@ -25,7 +27,42 @@ void test_parseImplicit_ThrowsAnException_WhenElementsAreMissing() {
2527
parser.parse((DeploymentViewAnimationDslContext)null, tokens());
2628
fail();
2729
} catch (Exception e) {
28-
assertEquals("Expected: <identifier> [identifier...]", e.getMessage());
30+
assertEquals("Expected: <identifier|element expression> [identifier|element expression...]", e.getMessage());
31+
}
32+
}
33+
34+
@Test
35+
void test_parse_ThrowsAnException_WhenTheElementDoesNotExist() {
36+
DeploymentView view = workspace.getViews().createDeploymentView("key", "Description");
37+
38+
DeploymentViewAnimationDslContext context = new DeploymentViewAnimationDslContext(view);
39+
IdentifiersRegister map = new IdentifiersRegister();
40+
context.setIdentifierRegister(map);
41+
42+
try {
43+
parser.parse(context, tokens("dn"));
44+
fail();
45+
} catch (Exception e) {
46+
assertEquals("The element/relationship \"dn\" does not exist", e.getMessage());
47+
}
48+
}
49+
50+
@Test
51+
void test_parse_ThrowsAnException_WhenNoAnimatableElementsAreFound() {
52+
DeploymentNode deploymentNode = workspace.getModel().addDeploymentNode("Deployment Node");
53+
DeploymentView view = workspace.getViews().createDeploymentView("key", "Description");
54+
view.add(deploymentNode);
55+
56+
DeploymentViewAnimationDslContext context = new DeploymentViewAnimationDslContext(view);
57+
IdentifiersRegister map = new IdentifiersRegister();
58+
map.register("dn", deploymentNode);
59+
context.setIdentifierRegister(map);
60+
61+
try {
62+
parser.parse(context, tokens("dn"));
63+
fail();
64+
} catch (Exception e) {
65+
assertEquals("No software system instances, container instances, or infrastructure nodes were found", e.getMessage());
2966
}
3067
}
3168

structurizr-dsl/src/test/java/com/structurizr/dsl/DeploymentViewContentParserTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ void test_parseInclude_ThrowsAnException_WhenTheNoElementsAreSpecified() {
1717
parser.parseInclude(new DeploymentViewDslContext(null), tokens("include"));
1818
fail();
1919
} catch (RuntimeException iae) {
20-
assertEquals("Expected: include <*|identifier> [*|identifier...]", iae.getMessage());
20+
assertEquals("Expected: include <*|identifier|expression> [*|identifier|expression...]", iae.getMessage());
2121
}
2222
}
2323

@@ -210,7 +210,7 @@ void test_parseExclude_ThrowsAnException_WhenTheNoElementsAreSpecified() {
210210
parser.parseExclude(new DeploymentViewDslContext(null), tokens("exclude"));
211211
fail();
212212
} catch (RuntimeException iae) {
213-
assertEquals("Expected: exclude <identifier> [identifier...]", iae.getMessage());
213+
assertEquals("Expected: exclude <identifier|expression> [identifier|expression...]", iae.getMessage());
214214
}
215215
}
216216

structurizr-dsl/src/test/java/com/structurizr/dsl/DslTests.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1576,4 +1576,26 @@ void test_textBlock() throws Exception {
15761576
- Line 3""", softwareSystem.getDescription());
15771577
}
15781578

1579+
@Test
1580+
void test_deploymentAnimation() throws Exception {
1581+
StructurizrDslParser parser = new StructurizrDslParser();
1582+
parser.parse(new File("src/test/resources/dsl/deployment-animation.dsl"));
1583+
1584+
Workspace workspace = parser.getWorkspace();
1585+
Container webapp = workspace.getModel().getSoftwareSystemWithName("Software System").getContainerWithName("Web Application");
1586+
Container db = workspace.getModel().getSoftwareSystemWithName("Software System").getContainerWithName("Database Schema");
1587+
ContainerInstance webappInstance = workspace.getModel().getDeploymentNodeWithName("Deployment Node", "Live").getContainerInstances().stream().filter(ci -> ci.getContainer().equals(webapp)).findFirst().get();
1588+
ContainerInstance dbInstance = workspace.getModel().getDeploymentNodeWithName("Deployment Node", "Live").getContainerInstances().stream().filter(ci -> ci.getContainer().equals(db)).findFirst().get();
1589+
1590+
for (DeploymentView deploymentView : workspace.getViews().getDeploymentViews()) {
1591+
assertEquals(2, deploymentView.getAnimations().size());
1592+
1593+
// step 1
1594+
assertTrue(deploymentView.getAnimations().get(0).getElements().contains(webappInstance.getId()));
1595+
1596+
// step 2
1597+
assertTrue(deploymentView.getAnimations().get(1).getElements().contains(dbInstance.getId()));
1598+
}
1599+
}
1600+
15791601
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
workspace {
2+
3+
model {
4+
ss = softwaresystem "Software System" {
5+
webapp = container "Web Application" {
6+
tag "UI"
7+
}
8+
db = container "Database Schema" {
9+
tag "DB"
10+
}
11+
}
12+
13+
webapp -> db
14+
15+
live = deploymentEnvironment "Live" {
16+
dn = deploymentNode "Deployment Node" {
17+
webappInstance = containerInstance webapp
18+
dbInstance = containerInstance db
19+
}
20+
}
21+
}
22+
23+
views {
24+
deployment ss "Live" {
25+
include *
26+
27+
// add animation steps via container instance identifiers
28+
animation {
29+
webappInstance
30+
dbInstance
31+
}
32+
}
33+
34+
deployment ss "Live" {
35+
include *
36+
37+
// add animation steps via container identifiers
38+
animation {
39+
webapp
40+
db
41+
}
42+
}
43+
44+
deployment ss "Live" {
45+
include *
46+
47+
// add animation steps via element expressions
48+
animation {
49+
webapp
50+
webapp->
51+
}
52+
}
53+
54+
deployment ss "Live" {
55+
include *
56+
57+
// add animation steps via element expressions
58+
animation {
59+
webappInstance
60+
webappInstance->
61+
}
62+
}
63+
64+
deployment ss "Live" {
65+
include *
66+
67+
// add animation steps via element expressions
68+
animation {
69+
element.tag==UI
70+
element.tag==DB
71+
}
72+
}
73+
}
74+
75+
}

0 commit comments

Comments
 (0)