Skip to content

Commit

Permalink
Merge pull request #4466 from Valentine14th/feature/1838_actor_creati…
Browse files Browse the repository at this point in the history
…on_destruction

Feature/1838 actor creation destruction
  • Loading branch information
sidharthv96 committed Jul 2, 2023
2 parents 29c866b + dfc4665 commit 9c011ab
Show file tree
Hide file tree
Showing 8 changed files with 449 additions and 69 deletions.
75 changes: 75 additions & 0 deletions cypress/integration/rendering/sequencediagram.spec.js
Expand Up @@ -156,6 +156,81 @@ context('Sequence diagram', () => {
`
);
});
it('should render a sequence diagram with basic actor creation and destruction', () => {
imgSnapshotTest(
`
sequenceDiagram
Alice ->> Bob: Hello Bob, how are you ?
Bob ->> Alice: Fine, thank you. And you?
create participant Polo
Alice ->> Polo: Hi Polo!
create actor Ola1 as Ola
Polo ->> Ola1: Hiii
Ola1 ->> Alice: Hi too
destroy Ola1
Alice --x Ola1: Bye!
Alice ->> Bob: And now?
create participant Ola2 as Ola
Alice ->> Ola2: Hello again
destroy Alice
Alice --x Ola2: Bye for me!
destroy Bob
Ola2 --> Bob: The end
`
);
});
it('should render a sequence diagram with actor creation and destruction coupled with backgrounds, loops and notes', () => {
imgSnapshotTest(
`
sequenceDiagram
accTitle: test the accTitle
accDescr: Test a description
participant Alice
participant Bob
autonumber 10 10
rect rgb(200, 220, 100)
rect rgb(200, 255, 200)
Alice ->> Bob: Hello Bob, how are you?
create participant John as John<br />Second Line
Bob-->>John: How about you John?
end
Bob--x Alice: I am good thanks!
Bob-x John: I am good thanks!
Note right of John: John thinks a long<br />long time, so long<br />that the text does<br />not fit on a row.
Bob-->Alice: Checking with John...
Note over John:wrap: John looks like he's still thinking, so Bob prods him a bit.
Bob-x John: Hey John - we're still waiting to know<br />how you're doing
Note over John:nowrap: John's trying hard not to break his train of thought.
destroy John
Bob-x John: John! Cmon!
Note over John: After a few more moments, John<br />finally snaps out of it.
end
autonumber off
alt either this
create actor Lola
Alice->>+Lola: Yes
Lola-->>-Alice: OK
else or this
autonumber
Alice->>Lola: No
else or this will happen
Alice->Lola: Maybe
end
autonumber 200
par this happens in parallel
destroy Bob
Alice -->> Bob: Parallel message 1
and
Alice -->> Lola: Parallel message 2
end
`
);
});
context('font settings', () => {
it('should render different note fonts when configured', () => {
imgSnapshotTest(
Expand Down
37 changes: 37 additions & 0 deletions docs/syntax/sequenceDiagram.md
Expand Up @@ -94,6 +94,43 @@ sequenceDiagram
J->>A: Great!
```

### Actor Creation and Destruction

It is possible to create and destroy actors by messages. To do so, add a create or destroy directive before the message.

create participant B
A --> B: Hello

Create directives support actor/participant distinction and aliases. The sender or the recipient of a message can be destroyed but only the recipient can be created.

```mermaid-example
sequenceDiagram
Alice->>Bob: Hello Bob, how are you ?
Bob->>Alice: Fine, thank you. And you?
create participant Carl
Alice->>Carl: Hi Carl!
create actor D as Donald
Carl->>D: Hi!
destroy Carl
Alice-xCarl: We are too many
destroy Bob
Bob->>Alice: I agree
```

```mermaid
sequenceDiagram
Alice->>Bob: Hello Bob, how are you ?
Bob->>Alice: Fine, thank you. And you?
create participant Carl
Alice->>Carl: Hi Carl!
create actor D as Donald
Carl->>D: Hi!
destroy Carl
Alice-xCarl: We are too many
destroy Bob
Bob->>Alice: I agree
```

### Grouping / Box

The actor(s) can be grouped in vertical boxes. You can define a color (if not, it will be transparent) and/or a descriptive label using the following notation:
Expand Down
Expand Up @@ -38,6 +38,8 @@
"box" { this.begin('LINE'); return 'box'; }
"participant" { this.begin('ID'); return 'participant'; }
"actor" { this.begin('ID'); return 'participant_actor'; }
"create" return 'create';
"destroy" { this.begin('ID'); return 'destroy'; }
<ID>[^\->:\n,;]+?([\-]*[^\->:\n,;]+?)*?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; }
<ALIAS>"as" { this.popState(); this.popState(); this.begin('LINE'); return 'AS'; }
<ALIAS>(?:) { this.popState(); this.popState(); return 'NEWLINE'; }
Expand Down Expand Up @@ -138,6 +140,7 @@ directive

statement
: participant_statement
| 'create' participant_statement {$2.type='createParticipant'; $$=$2;}
| 'box' restOfLine box_section end
{
$3.unshift({type: 'boxStart', boxData:yy.parseBoxData($2) });
Expand Down Expand Up @@ -234,10 +237,11 @@ else_sections
;

participant_statement
: 'participant' actor 'AS' restOfLine 'NEWLINE' {$2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;}
| 'participant' actor 'NEWLINE' {$2.type='addParticipant';$$=$2;}
| 'participant_actor' actor 'AS' restOfLine 'NEWLINE' {$2.type='addActor';$2.description=yy.parseMessage($4); $$=$2;}
| 'participant_actor' actor 'NEWLINE' {$2.type='addActor'; $$=$2;}
: 'participant' actor 'AS' restOfLine 'NEWLINE' {$2.draw='participant'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;}
| 'participant' actor 'NEWLINE' {$2.draw='participant'; $2.type='addParticipant';$$=$2;}
| 'participant_actor' actor 'AS' restOfLine 'NEWLINE' {$2.draw='actor'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;}
| 'participant_actor' actor 'NEWLINE' {$2.draw='actor'; $2.type='addParticipant'; $$=$2;}
| 'destroy' actor 'NEWLINE' {$2.type='destroyParticipant'; $$=$2;}
;

note_statement
Expand Down
52 changes: 49 additions & 3 deletions packages/mermaid/src/diagrams/sequence/sequenceDb.js
Expand Up @@ -14,12 +14,16 @@ import {

let prevActor = undefined;
let actors = {};
let createdActors = {};
let destroyedActors = {};
let boxes = [];
let messages = [];
const notes = [];
let sequenceNumbersEnabled = false;
let wrapEnabled;
let currentBox = undefined;
let lastCreated = undefined;
let lastDestroyed = undefined;

export const parseDirective = function (statement, context, type) {
mermaidAPI.parseDirective(this, statement, context, type);
Expand Down Expand Up @@ -165,6 +169,12 @@ export const getBoxes = function () {
export const getActors = function () {
return actors;
};
export const getCreatedActors = function () {
return createdActors;
};
export const getDestroyedActors = function () {
return destroyedActors;
};
export const getActor = function (id) {
return actors[id];
};
Expand Down Expand Up @@ -194,6 +204,8 @@ export const autoWrap = () => {

export const clear = function () {
actors = {};
createdActors = {};
destroyedActors = {};
boxes = [];
messages = [];
sequenceNumbersEnabled = false;
Expand Down Expand Up @@ -459,10 +471,21 @@ export const apply = function (param) {
});
break;
case 'addParticipant':
addActor(param.actor, param.actor, param.description, 'participant');
addActor(param.actor, param.actor, param.description, param.draw);
break;
case 'createParticipant':
if (actors[param.actor]) {
throw new Error(
"It is not possible to have actors with the same id, even if one is destroyed before the next is created. Use 'AS' aliases to simulate the behavior"
);
}
lastCreated = param.actor;
addActor(param.actor, param.actor, param.description, param.draw);
createdActors[param.actor] = messages.length;
break;
case 'addActor':
addActor(param.actor, param.actor, param.description, 'actor');
case 'destroyParticipant':
lastDestroyed = param.actor;
destroyedActors[param.actor] = messages.length;
break;
case 'activeStart':
addSignal(param.actor, undefined, undefined, param.signalType);
Expand All @@ -486,6 +509,27 @@ export const apply = function (param) {
addDetails(param.actor, param.text);
break;
case 'addMessage':
if (lastCreated) {
if (param.to !== lastCreated) {
throw new Error(
'The created participant ' +
lastCreated +
' does not have an associated creating message after its declaration. Please check the sequence diagram.'
);
} else {
lastCreated = undefined;
}
} else if (lastDestroyed) {
if (param.to !== lastDestroyed && param.from !== lastDestroyed) {
throw new Error(
'The destroyed participant ' +
lastDestroyed +
' does not have an associated destroying message after its declaration. Please check the sequence diagram.'
);
} else {
lastDestroyed = undefined;
}
}
addSignal(param.from, param.to, param.msg, param.signalType);
break;
case 'boxStart':
Expand Down Expand Up @@ -566,6 +610,8 @@ export default {
showSequenceNumbers,
getMessages,
getActors,
getCreatedActors,
getDestroyedActors,
getActor,
getActorKeys,
getActorProperty,
Expand Down
64 changes: 61 additions & 3 deletions packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js
Expand Up @@ -1404,6 +1404,62 @@ link a: Tests @ https://tests.contoso.com/?svc=alice@contoso.com
expect(boxes[0].actorKeys).toEqual(['a', 'b']);
expect(boxes[0].fill).toEqual('Aqua');
});

it('should handle simple actor creation', async () => {
const str = `
sequenceDiagram
participant a as Alice
a ->>b: Hello Bob?
create participant c
b-->>c: Hello c!
c ->> b: Hello b?
create actor d as Donald
a ->> d: Hello Donald?
`;
await mermaidAPI.parse(str);
const actors = diagram.db.getActors();
const createdActors = diagram.db.getCreatedActors();
expect(actors['c'].name).toEqual('c');
expect(actors['c'].description).toEqual('c');
expect(actors['c'].type).toEqual('participant');
expect(createdActors['c']).toEqual(1);
expect(actors['d'].name).toEqual('d');
expect(actors['d'].description).toEqual('Donald');
expect(actors['d'].type).toEqual('actor');
expect(createdActors['d']).toEqual(3);
});
it('should handle simple actor destruction', async () => {
const str = `
sequenceDiagram
participant a as Alice
a ->>b: Hello Bob?
destroy a
b-->>a: Hello Alice!
b ->> c: Where is Alice?
destroy c
b ->> c: Where are you?
`;
await mermaidAPI.parse(str);
const destroyedActors = diagram.db.getDestroyedActors();
expect(destroyedActors['a']).toEqual(1);
expect(destroyedActors['c']).toEqual(3);
});
it('should handle the creation and destruction of the same actor', async () => {
const str = `
sequenceDiagram
a ->>b: Hello Bob?
create participant c
b ->>c: Hello c!
c ->> b: Hello b?
destroy c
b ->> c : Bye c !
`;
await mermaidAPI.parse(str);
const createdActors = diagram.db.getCreatedActors();
const destroyedActors = diagram.db.getDestroyedActors();
expect(createdActors['c']).toEqual(1);
expect(destroyedActors['c']).toEqual(3);
});
});
describe('when checking the bounds in a sequenceDiagram', function () {
beforeAll(() => {
Expand Down Expand Up @@ -1973,7 +2029,9 @@ participant Alice`;
expect(bounds.startx).toBe(0);
expect(bounds.starty).toBe(0);
expect(bounds.stopx).toBe(conf.width);
expect(bounds.stopy).toBe(models.lastActor().y + models.lastActor().height + conf.boxMargin);
expect(bounds.stopy).toBe(
models.lastActor().stopy + models.lastActor().height + conf.boxMargin
);
});
});
});
Expand Down Expand Up @@ -2025,7 +2083,7 @@ participant Alice
expect(bounds.startx).toBe(0);
expect(bounds.starty).toBe(0);
expect(bounds.stopy).toBe(
models.lastActor().y + models.lastActor().height + mermaid.sequence.boxMargin
models.lastActor().stopy + models.lastActor().height + mermaid.sequence.boxMargin
);
});
it('should handle one actor, when logLevel is 3 (dfg0)', async () => {
Expand All @@ -2045,7 +2103,7 @@ participant Alice
expect(bounds.startx).toBe(0);
expect(bounds.starty).toBe(0);
expect(bounds.stopy).toBe(
models.lastActor().y + models.lastActor().height + mermaid.sequence.boxMargin
models.lastActor().stopy + models.lastActor().height + mermaid.sequence.boxMargin
);
});
it('should hide sequence numbers when autonumber is removed when autonumber is enabled', async () => {
Expand Down

0 comments on commit 9c011ab

Please sign in to comment.