Merge pull request #3063 from financelurker/feature/3062_critical_region_and_break_in_sequence_diagrams
feat: adding "Critical Region"/"Option" and "Break" blocks to sequence diagram
This commit is contained in:
commit
ee61a26faf
|
@ -452,6 +452,42 @@ context('Sequence diagram', () => {
|
|||
{}
|
||||
);
|
||||
});
|
||||
it('should render rect around and inside criticals', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
A ->> B: 1
|
||||
rect rgb(204, 0, 102)
|
||||
critical yes
|
||||
C ->> C: 1
|
||||
option no
|
||||
rect rgb(0, 204, 204)
|
||||
C ->> C: 0
|
||||
end
|
||||
end
|
||||
end
|
||||
B ->> A: Return
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('should render rect around and inside breaks', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
A ->> B: 1
|
||||
rect rgb(204, 0, 102)
|
||||
break yes
|
||||
rect rgb(0, 204, 204)
|
||||
C ->> C: 0
|
||||
end
|
||||
end
|
||||
end
|
||||
B ->> A: Return
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('should render autonumber when configured with such', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
|
|
|
@ -230,6 +230,70 @@ sequenceDiagram
|
|||
end
|
||||
```
|
||||
|
||||
## Critical Region
|
||||
|
||||
It is possible to show actions that must happen automatically with conditional handling of circumstances.
|
||||
|
||||
This is done by the notation
|
||||
|
||||
```
|
||||
critical [Action that must be performed]
|
||||
... statements ...
|
||||
option [Circumstance A]
|
||||
... statements ...
|
||||
option [Circumstance B]
|
||||
... statements ...
|
||||
end
|
||||
```
|
||||
|
||||
See the example below:
|
||||
|
||||
```mermaid-example
|
||||
sequenceDiagram
|
||||
critical Establish a connection to the DB
|
||||
Service-->DB: connect
|
||||
option Network timeout
|
||||
Service-->Service: Log error
|
||||
option Credentials rejected
|
||||
Service-->Service: Log different error
|
||||
end
|
||||
```
|
||||
|
||||
It is also possible to have no options at all
|
||||
|
||||
```mermaid-example
|
||||
sequenceDiagram
|
||||
critical Establish a connection to the DB
|
||||
Service-->DB: connect
|
||||
end
|
||||
```
|
||||
|
||||
This critical block can also be nested, equivalently to the `par` statement as seen above.
|
||||
|
||||
## Break
|
||||
|
||||
It is possible to indicate a stop of the sequence within the flow (usually used to model exceptions).
|
||||
|
||||
This is done by the notation
|
||||
|
||||
```
|
||||
break [something happened]
|
||||
... statements ...
|
||||
end
|
||||
```
|
||||
|
||||
See the example below:
|
||||
|
||||
```mermaid-example
|
||||
sequenceDiagram
|
||||
Consumer-->API: Book something
|
||||
API-->BookingService: Start booking process
|
||||
break when the booking process fails
|
||||
API-->Consumer: show failure
|
||||
end
|
||||
API-->BillingService: Start billing process
|
||||
```
|
||||
|
||||
## Background Highlighting
|
||||
|
||||
It is possible to highlight flows by providing colored background rects. This is done by the notation
|
||||
|
|
|
@ -47,6 +47,9 @@
|
|||
"else" { this.begin('LINE'); return 'else'; }
|
||||
"par" { this.begin('LINE'); return 'par'; }
|
||||
"and" { this.begin('LINE'); return 'and'; }
|
||||
"critical" { this.begin('LINE'); return 'critical'; }
|
||||
"option" { this.begin('LINE'); return 'option'; }
|
||||
"break" { this.begin('LINE'); return 'break'; }
|
||||
<LINE>(?:[:]?(?:no)?wrap:)?[^#\n;]* { this.popState(); return 'restOfLine'; }
|
||||
"end" return 'end';
|
||||
"left of" return 'left_of';
|
||||
|
@ -172,9 +175,28 @@ statement
|
|||
// End
|
||||
$3.push({type: 'parEnd', signalType: yy.LINETYPE.PAR_END});
|
||||
$$=$3;}
|
||||
| critical restOfLine option_sections end
|
||||
{
|
||||
// critical start
|
||||
$3.unshift({type: 'criticalStart', criticalText:yy.parseMessage($2), signalType: yy.LINETYPE.CRITICAL_START});
|
||||
// Content in critical is already in $3
|
||||
// critical end
|
||||
$3.push({type: 'criticalEnd', signalType: yy.LINETYPE.CRITICAL_END});
|
||||
$$=$3;}
|
||||
| break restOfLine document end
|
||||
{
|
||||
$3.unshift({type: 'breakStart', breakText:yy.parseMessage($2), signalType: yy.LINETYPE.BREAK_START});
|
||||
$3.push({type: 'breakEnd', optText:yy.parseMessage($2), signalType: yy.LINETYPE.BREAK_END});
|
||||
$$=$3;}
|
||||
| directive
|
||||
;
|
||||
|
||||
option_sections
|
||||
: document
|
||||
| document option restOfLine option_sections
|
||||
{ $$ = $1.concat([{type: 'option', optionText:yy.parseMessage($3), signalType: yy.LINETYPE.CRITICAL_OPTION}, $4]); }
|
||||
;
|
||||
|
||||
par_sections
|
||||
: document
|
||||
| document and restOfLine par_sections
|
||||
|
|
|
@ -190,6 +190,11 @@ export const LINETYPE = {
|
|||
SOLID_POINT: 24,
|
||||
DOTTED_POINT: 25,
|
||||
AUTONUMBER: 26,
|
||||
CRITICAL_START: 27,
|
||||
CRITICAL_OPTION: 28,
|
||||
CRITICAL_END: 29,
|
||||
BREAK_START: 30,
|
||||
BREAK_END: 31,
|
||||
};
|
||||
|
||||
export const ARROWTYPE = {
|
||||
|
@ -422,6 +427,21 @@ export const apply = function (param) {
|
|||
case 'parEnd':
|
||||
addSignal(undefined, undefined, undefined, param.signalType);
|
||||
break;
|
||||
case 'criticalStart':
|
||||
addSignal(undefined, undefined, param.criticalText, param.signalType);
|
||||
break;
|
||||
case 'option':
|
||||
addSignal(undefined, undefined, param.optionText, param.signalType);
|
||||
break;
|
||||
case 'criticalEnd':
|
||||
addSignal(undefined, undefined, undefined, param.signalType);
|
||||
break;
|
||||
case 'breakStart':
|
||||
addSignal(undefined, undefined, param.breakText, param.signalType);
|
||||
break;
|
||||
case 'breakEnd':
|
||||
addSignal(undefined, undefined, undefined, param.signalType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -843,6 +843,80 @@ end`;
|
|||
expect(messages[7].from).toBe('Bob');
|
||||
expect(messages[8].type).toBe(parser.yy.LINETYPE.ALT_END);
|
||||
});
|
||||
it('it should handle critical statements without options', function () {
|
||||
const str = `
|
||||
sequenceDiagram
|
||||
critical Establish a connection to the DB
|
||||
Service-->DB: connect
|
||||
end`;
|
||||
|
||||
mermaidAPI.parse(str);
|
||||
const actors = parser.yy.getActors();
|
||||
|
||||
expect(actors.Service.description).toBe('Service');
|
||||
expect(actors.DB.description).toBe('DB');
|
||||
|
||||
const messages = parser.yy.getMessages();
|
||||
|
||||
expect(messages.length).toBe(3);
|
||||
expect(messages[0].type).toBe(parser.yy.LINETYPE.CRITICAL_START);
|
||||
expect(messages[1].from).toBe('Service');
|
||||
expect(messages[2].type).toBe(parser.yy.LINETYPE.CRITICAL_END);
|
||||
});
|
||||
it('it should handle critical statements with options', function () {
|
||||
const str = `
|
||||
sequenceDiagram
|
||||
critical Establish a connection to the DB
|
||||
Service-->DB: connect
|
||||
option Network timeout
|
||||
Service-->Service: Log error
|
||||
option Credentials rejected
|
||||
Service-->Service: Log different error
|
||||
end`;
|
||||
|
||||
mermaidAPI.parse(str);
|
||||
const actors = parser.yy.getActors();
|
||||
|
||||
expect(actors.Service.description).toBe('Service');
|
||||
expect(actors.DB.description).toBe('DB');
|
||||
|
||||
const messages = parser.yy.getMessages();
|
||||
|
||||
expect(messages.length).toBe(7);
|
||||
expect(messages[0].type).toBe(parser.yy.LINETYPE.CRITICAL_START);
|
||||
expect(messages[1].from).toBe('Service');
|
||||
expect(messages[2].type).toBe(parser.yy.LINETYPE.CRITICAL_OPTION);
|
||||
expect(messages[3].from).toBe('Service');
|
||||
expect(messages[4].type).toBe(parser.yy.LINETYPE.CRITICAL_OPTION);
|
||||
expect(messages[5].from).toBe('Service');
|
||||
expect(messages[6].type).toBe(parser.yy.LINETYPE.CRITICAL_END);
|
||||
});
|
||||
it('it should handle break statements', function () {
|
||||
const str = `
|
||||
sequenceDiagram
|
||||
Consumer-->API: Book something
|
||||
API-->BookingService: Start booking process
|
||||
break when the booking process fails
|
||||
API-->Consumer: show failure
|
||||
end
|
||||
API-->BillingService: Start billing process`;
|
||||
|
||||
mermaidAPI.parse(str);
|
||||
const actors = parser.yy.getActors();
|
||||
|
||||
expect(actors.Consumer.description).toBe('Consumer');
|
||||
expect(actors.API.description).toBe('API');
|
||||
|
||||
const messages = parser.yy.getMessages();
|
||||
|
||||
expect(messages.length).toBe(6);
|
||||
expect(messages[0].from).toBe('Consumer');
|
||||
expect(messages[1].from).toBe('API');
|
||||
expect(messages[2].type).toBe(parser.yy.LINETYPE.BREAK_START);
|
||||
expect(messages[3].from).toBe('API');
|
||||
expect(messages[4].type).toBe(parser.yy.LINETYPE.BREAK_END);
|
||||
expect(messages[5].from).toBe('API');
|
||||
});
|
||||
it('it should handle par statements a sequenceDiagram', function () {
|
||||
const str = `
|
||||
sequenceDiagram
|
||||
|
|
|
@ -763,6 +763,45 @@ export const draw = function (text, id) {
|
|||
if (msg.message.visible) parser.yy.enableSequenceNumbers();
|
||||
else parser.yy.disableSequenceNumbers();
|
||||
break;
|
||||
case parser.yy.LINETYPE.CRITICAL_START:
|
||||
adjustLoopHeightForWrap(
|
||||
loopWidths,
|
||||
msg,
|
||||
conf.boxMargin,
|
||||
conf.boxMargin + conf.boxTextMargin,
|
||||
(message) => bounds.newLoop(message)
|
||||
);
|
||||
break;
|
||||
case parser.yy.LINETYPE.CRITICAL_OPTION:
|
||||
adjustLoopHeightForWrap(
|
||||
loopWidths,
|
||||
msg,
|
||||
conf.boxMargin + conf.boxTextMargin,
|
||||
conf.boxMargin,
|
||||
(message) => bounds.addSectionToLoop(message)
|
||||
);
|
||||
break;
|
||||
case parser.yy.LINETYPE.CRITICAL_END:
|
||||
loopModel = bounds.endLoop();
|
||||
svgDraw.drawLoop(diagram, loopModel, 'critical', conf);
|
||||
bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos());
|
||||
bounds.models.addLoop(loopModel);
|
||||
break;
|
||||
case parser.yy.LINETYPE.BREAK_START:
|
||||
adjustLoopHeightForWrap(
|
||||
loopWidths,
|
||||
msg,
|
||||
conf.boxMargin,
|
||||
conf.boxMargin + conf.boxTextMargin,
|
||||
(message) => bounds.newLoop(message)
|
||||
);
|
||||
break;
|
||||
case parser.yy.LINETYPE.BREAK_END:
|
||||
loopModel = bounds.endLoop();
|
||||
svgDraw.drawLoop(diagram, loopModel, 'break', conf);
|
||||
bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos());
|
||||
bounds.models.addLoop(loopModel);
|
||||
break;
|
||||
default:
|
||||
try {
|
||||
// lastMsg = msg
|
||||
|
@ -1165,6 +1204,8 @@ const calculateLoopBounds = function (messages, actors) {
|
|||
case parser.yy.LINETYPE.ALT_START:
|
||||
case parser.yy.LINETYPE.OPT_START:
|
||||
case parser.yy.LINETYPE.PAR_START:
|
||||
case parser.yy.LINETYPE.CRITICAL_START:
|
||||
case parser.yy.LINETYPE.BREAK_START:
|
||||
stack.push({
|
||||
id: msg.id,
|
||||
msg: msg.message,
|
||||
|
@ -1175,6 +1216,7 @@ const calculateLoopBounds = function (messages, actors) {
|
|||
break;
|
||||
case parser.yy.LINETYPE.ALT_ELSE:
|
||||
case parser.yy.LINETYPE.PAR_AND:
|
||||
case parser.yy.LINETYPE.CRITICAL_OPTION:
|
||||
if (msg.message) {
|
||||
current = stack.pop();
|
||||
loops[current.id] = current;
|
||||
|
@ -1186,6 +1228,8 @@ const calculateLoopBounds = function (messages, actors) {
|
|||
case parser.yy.LINETYPE.ALT_END:
|
||||
case parser.yy.LINETYPE.OPT_END:
|
||||
case parser.yy.LINETYPE.PAR_END:
|
||||
case parser.yy.LINETYPE.CRITICAL_END:
|
||||
case parser.yy.LINETYPE.BREAK_END:
|
||||
current = stack.pop();
|
||||
loops[current.id] = current;
|
||||
break;
|
||||
|
|
Loading…
Reference in New Issue