Skip to content

Commit

Permalink
Use case with actors and system boundaries
Browse files Browse the repository at this point in the history
  • Loading branch information
teyc committed Dec 19, 2024
1 parent a4a909b commit 4fae63b
Show file tree
Hide file tree
Showing 8 changed files with 322 additions and 43 deletions.
117 changes: 117 additions & 0 deletions demos/usecase.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Use case tests</title>
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo=" />
<style>
body {
font-family: 'Roboto', sans-serif;
padding: 20px;
}
hr {
color: black;
}
div.mermaid {
/* font-family: 'trebuchet ms', verdana, arial; */
font-family: 'Courier New', Courier, monospace !important;
}
</style>
</head>
<body>
<h2>Basic Use Case</h2>
<pre class="mermaid">
usecase-beta
User -> (Open Bank Account)
User --> (Get Loan)
(Get Loan) --> (Credit Check)
</pre>
<hr />

<h2>Use Case with Title and Aliases</h2>
<pre class="mermaid">
usecase-beta
title Simple use case
systemboundary
title Acme System
(Start)
(Use) as (Use the application)
(Another use case)
end
User -> (Start)
User --> (Use)
(Use) --> (Another use case)
</pre>
<hr />

<h2>Complex Use Case</h2>
<pre class="mermaid">
usecase-beta
title Student Management System Use Cases

actor Student
actor Admin
service Authentication
service Grades
service Courses

systemboundary
title Student Management System
(Login) {
- Authenticate
}
(Submit Assignment) {
- Upload Assignment
}
(View Grades)
(Manage Users) {
- Add User
- Edit User
- Delete User
}
(Manage Courses) {
- Add Course
- Edit Course
- Delete Course
}
(Generate Reports) {
- Generate User Report
- Generate Course Report
}
(View Grades) {
- View User Grades
- View Course Grades
}
end

Student -> (Login) -> Authentication
Student -> (Submit Assignment) -> Courses
Student -> (View Grades) -> Grades
Admin -> (Login) -> Authentication
Admin -> (Manage Users) -> Courses
Admin -> (Manage Courses) -> Courses
Admin -> (Generate Reports) -> Courses, Grades
Admin -> (View Grades) -> Grades
</pre>
<script type="module">
import mermaid from './mermaid.esm.mjs';
mermaid.initialize({
theme: 'forest',
logLevel: 3,
securityLevel: 'loose',
flowchart: { curve: 'basis' },
});
</script>
<script>
function testClick(nodeId) {
console.log('clicked', nodeId);
let originalBgColor = document.querySelector('body').style.backgroundColor;
document.querySelector('body').style.backgroundColor = 'yellow';
setTimeout(function () {
document.querySelector('body').style.backgroundColor = originalBgColor;
}, 100);
}
</script>
</body>
</html>
6 changes: 5 additions & 1 deletion packages/mermaid/src/config.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1488,7 +1488,11 @@ export interface SankeyDiagramConfig extends BaseDiagramConfig {
* This interface was referenced by `MermaidConfig`'s JSON-Schema
* via the `definition` "UsecaseDiagramConfig".
*/
export interface UsecaseDiagramConfig extends BaseDiagramConfig {}
export interface UsecaseDiagramConfig extends BaseDiagramConfig {
diagramMarginX?: number;
diagramMarginY?: number;
}

/**
* The object containing configurations specific for packet diagrams.
*
Expand Down
28 changes: 28 additions & 0 deletions packages/mermaid/src/diagrams/usecase/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
export interface UsecaseStyleOptions {
clusterBkg: string;
clusterBorder: string;
lineColor: string;
mainBkg: string;
}

const getStyles = (options: UsecaseStyleOptions) =>
`
.actor {
stroke: ${options.lineColor};
background: white;
}
.node {
fill: ${options.mainBkg};
}
.cluster rect {
fill: ${options.clusterBkg};
stroke: ${options.clusterBorder};
stroke-width: 1px;
}
.flowchart-link {
stroke: ${options.lineColor};
fill: none;
}
`;

export default getStyles;
117 changes: 86 additions & 31 deletions packages/mermaid/src/diagrams/usecase/usecaseDB.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { BaseDiagramConfig, MermaidConfig } from '../../config.type.js';
import { getConfig } from '../../diagram-api/diagramAPI.js';
import type { LayoutData, MermaidConfig } from '../../mermaid.js';
import type { LayoutData } from '../../mermaid.js';
import type { Edge, Node } from '../../rendering-util/types.js';
import common from '../common/common.js';
import {
Expand Down Expand Up @@ -44,47 +45,98 @@ export class UsecaseDB {
this.links.push(new UsecaseLink(sourceNode, targetNode, arrow));
}

addSystemBoundary(elements: string[], title?: string) {
this.systemBoundaries.push({ elements, title });
addSystemBoundary(useCases: string[], title?: string) {
if (title) {
title = common.sanitizeText(title.trim(), getConfig());
if (title.startsWith('title')) {
title = title.slice(5).trim();
}
}
this.systemBoundaries.push({ id: 'boundary-' + this.systemBoundaries.length, useCases, title });
}

getActors() {
return this.links.map((link) => link.source.id).filter((source) => !source.startsWith('('));
}

getConfig() {
return getConfig().usecase!;
return getConfig() as BaseDiagramConfig;
}

getData(): LayoutData {
const edges: Edge[] = this.links.map((link) => ({
id: `${link.source.ID}-${link.target.ID}`,
type: 'normal',
id: `${link.source.id}-${link.target.id}`,
classes: 'edge-thickness-normal edge-pattern-solid flowchart-link',
start: link.source.id,
end: link.target.id,
arrowTypeStart: 'none',
arrowTypeEnd: 'arrow_point',
label: '',
minlen: 1,
pattern: 'normal',
thickness: 'normal',
type: 'arrow_point',
}));

const baseNode = {
shape: 'squareRect',
cssClasses: 'default',
padding: 15,
look: 'classic',
isGroup: false,
styles: [],
};

const parentLookup = new Map(
this.getSystemBoundaries().flatMap((boundary) =>
boundary.useCases.map((useCase) => [useCase, boundary.id])
)
);

const nodes: Node[] = [
...this.nodes.map((node) => ({
id: node.ID,
type: 'normal',
label: this.aliases.get(node.ID) ?? node.ID,
isGroup: false,
})),
...this.systemBoundaries.map((boundary) => ({
id: boundary.title ?? 'System Boundary',
type: 'normal',
label: boundary.title ?? 'System Boundary',
isGroup: true,
children: boundary.elements.map((element) => ({
id: element,
type: 'normal',
label: this.aliases.get(element) ?? element,
isGroup: false,
})),
})),
...this.nodes.map(
(node) =>
({
...baseNode,
id: node.id,
label: this.aliases.get(node.id) ?? node.id,
parentId: parentLookup.get(node.id),
}) as Node
),
...this.getSystemBoundaries().map(
(boundary) =>
({
...baseNode,
id: boundary.id,
type: 'normal',
label: boundary.title ?? 'System Boundary',
shape: 'rect',
isGroup: true,
styles: [],
}) as Node
),
];

const config = this.getConfig() as MermaidConfig;
nodes
.filter((node) => node.label?.startsWith('(') && node.label.endsWith(')'))
.forEach((node) => {
node.label = node.label!.slice(1, -1);
node.rx = 50;
node.ry = 50;
});

// @ts-ignore TODO fix types
const config: MermaidConfig = this.getConfig();

return {
nodes,
edges,
config,
markers: ['point', 'circle', 'cross'],
other: {},
direction: 'LR',
rankSpacing: 50,
type: 'usecase',
};
}

Expand All @@ -93,10 +145,7 @@ export class UsecaseDB {
}

getSystemBoundaries() {
return this.systemBoundaries.map((boundary) => ({
useCases: boundary.elements,
title: boundary.title,
}));
return this.systemBoundaries;
}

getAccDescription = getAccDescription;
Expand All @@ -119,7 +168,8 @@ export class UsecaseDB {

export class UsecaseSystemBoundary {
constructor(
public elements: string[],
public id: string,
public useCases: string[],
public title?: string
) {}
}
Expand All @@ -133,7 +183,7 @@ export class UsecaseLink {
}

export class UsecaseNode {
constructor(public ID: string) {}
constructor(public id: string) {}
}

// Export an instance of the class
Expand All @@ -142,11 +192,16 @@ export default {
clear: db.clear.bind(db),
addRelationship: db.addRelationship.bind(db),
addAlias: db.addAlias.bind(db),
getAccDescription,
getAccTitle,
getActors: db.getActors.bind(db),
getConfig: db.getConfig.bind(db),
getData: db.getData.bind(db),
getRelationships: db.getRelationships.bind(db),
getDiagramTitle: db.getDiagramTitle.bind(db),
getSystemBoundaries: db.getSystemBoundaries.bind(db),
setAccDescription,
setAccTitle,
setDiagramTitle: db.setDiagramTitle.bind(db),
addSystemBoundary: db.addSystemBoundary.bind(db),
};
2 changes: 2 additions & 0 deletions packages/mermaid/src/diagrams/usecase/usecaseDiagram.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { DiagramDefinition } from '../../diagram-api/types.js';
// @ts-ignore: jison doesn't export types
import parser from './parser/usecase.jison';
import db from './usecaseDB.js';
import styles from './styles.js';
import renderer from './usecaseRenderer.js';
import { prepareTextForParsing } from './usecaseUtils.js';

Expand All @@ -10,6 +11,7 @@ parser.parse = (text: string) => originalParse(prepareTextForParsing(text));

export const diagram: DiagramDefinition = {
parser,
styles,
db,
renderer,
};
Loading

0 comments on commit 4fae63b

Please sign in to comment.