First pass at a requirement diagram.

This commit is contained in:
Josh Sharpe 2021-02-23 23:26:37 -05:00
parent 1f523ee843
commit 55251e1024
22 changed files with 2983 additions and 1305 deletions

945
dist/index.html vendored

File diff suppressed because it is too large Load Diff

775
dist/mermaid.core.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

777
dist/mermaid.js vendored

File diff suppressed because one or more lines are too long

2
dist/mermaid.js.map vendored

File diff suppressed because one or more lines are too long

10
dist/mermaid.min.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -15,6 +15,7 @@
- [User Journey](user-journey.md)
- [Gantt](gantt.md)
- [Pie Chart](pie.md)
- [Requirement Diagram](requirementDiagram.md)
- [Other Examples](examples.md)
- Adding mermaid✒

173
docs/requirementDiagram.md Normal file
View File

@ -0,0 +1,173 @@
# Requirement Diagram
**Edit this Page** [![N|Solid](img/GitHub-Mark-32px.png)](https://github.com/mermaid-js/mermaid/blob/develop/docs/requirementDiagram.md)
> A Requirement diagram provides a visualization for requirements and their connections, to each other and other documented elements. The modeling specs follow those defined by SysML v1.6.
Rendering requirements is straightforward.
```
requirementDiagram
requirement test_req {
id: 1
text: the test text.
risk: high
verifymethod: test
}
element test_entity {
type: simulation
}
test_entity - satisfies -> test_req
```
```mermaid
requirementDiagram
requirement test_req {
id: 1
text: the test text.
risk: high
verifymethod: test
}
element test_entity {
type: simulation
}
test_entity - satisfies -> test_req
```
## Syntax
There are three types of components to a requirement diagram: requirement, element, and relationship.
The grammar for defining each is defined below. Words denoted in angule brackets, such as ```<word>```, are enumerated keywords that have options elaborated in a table. ```user_defined_...``` is use in any place where user input is expected.
An important note on user text: all input can be surrounded in quotes or not. For example, both ```Id: "here is an example"``` and ```Id: here is an example``` are both valid. However, users must be careful with unquoted input. The parser will fail if another keyword is detected.
### Requirement
A requirement definition contains a requirement type, name, id, text, risk, and verification method. The syntax follows:
```
<type> user_defined_name {
id: user_defined_id
text: user_defined text
risk: <risk>
verifymethod: <method>
}
```
Type, risk, and method are enumerations defined in SysML.
| Keyword | Options |
| Type | requirement, functionalRequirement, interfaceRequirement, performanceRequirement, physicalRequirement, designConstraint |
| Risk | Low, Medium, High |
| VerifcationMethod | Analysis, Inspection, Test, Demonstration |
### Element
An element definition contains an element name, type, and document reference. These three are all user defined. The element feature is intended to be lightweight but allow requirements to be connected to portions of other documents.
```
element user_defined_name {
type: user_defined_type
docref: user_defined_ref
}
```
### Relationship
Relationships are comprised of a source node, destination node, and relationship type.
Each follows the definition format of
```
{name of source} - <type> -> {name of destination}
```
or
```
{name of destination} <- <type> - {name of source}
```
"name of source" and "name of destination" should be names of requirement or element nodes defined elsewhere.
A relationship type can be one of contains, copies, derives, satisfies, verifies, refines, or traces.
Each relationship is labeled in the diagram.
## Larger Example
This example uses all features of the diagram.
```mermaid
requirementDiagram
requirement test_req {
id: 1
text: the test text.
risk: high
verifymethod: test
}
functionalRequirement test_req2 {
id: 1.1
text: the second test text.
risk: low
verifymethod: inspection
}
performanceRequirement test_req3 {
id: 1.2
text: the third test text.
risk: medium
verifymethod: demonstration
}
interfaceRequirement test_req4 {
id: 1.2.1
text: the fourth test text.
risk: medium
verifymethod: analysis
}
physicalRequirement test_req5 {
id: 1.2.2
text: the fifth test text.
risk: medium
verifymethod: analysis
}
designConstraint test_req6 {
id: 1.2.3
text: the sixth test text.
risk: medium
verifymethod: analysis
}
element test_entity {
type: simulation
}
element test_entity2 {
type: word doc
docRef: reqs/test_entity
}
element test_entity3 {
type: "test suite"
docRef: github.com/all_the_tests
}
test_entity - satisfies -> test_req2
test_req - traces -> test_req2
test_req - contains -> test_req3
test_req3 - contains -> test_req4
test_req4 - derives -> test_req5
test_req5 - refines -> test_req6
test_entity3 - verifies -> test_req5
test_req <- copies - test_entity2 </div>
```

View File

@ -27,7 +27,7 @@
"e2e-upd": "yarn lint && jest e2e -u --config e2e/jest.config.js",
"dev": "webpack-dev-server --config webpack.config.e2e.js",
"test": "yarn lint && jest src/.*",
"test:watch": "jest --watch src",
"test:watch": "jest --watch src/diagrams/requirement/*",
"prepublishOnly": "yarn build && yarn test",
"prepare": "yarn build"
},
@ -112,4 +112,4 @@
"pre-push": "yarn test"
}
}
}
}

View File

@ -931,6 +931,36 @@ const config = {
***Default value: true**.
*/
useMaxWidth: true
},
/**
* The object containing configurations specific for req diagrams
*/
requirement: {
useWidth: undefined,
/**
*| Parameter | Description |Type | Required | Values|
*| --- | --- | --- | --- | --- |
*| useMaxWidth | See Notes | Boolean | Required | true, false |
*
***Notes:**
*When this flag is set to true, the diagram width is locked to 100% and
*scaled based on available space. If set to false, the diagram reserves its
*absolute width.
***Default value: true**.
*/
useMaxWidth: true,
rect_fill: '#f9f9f9',
text_color: '#333',
rect_border_size: '0.5px',
rect_border_color: '#bbb',
rect_min_width: 200,
rect_min_height: 200,
fontSize: 14,
rect_padding: 10,
line_height: 20
}
};

View File

@ -74,11 +74,28 @@ const placeholderToBreak = s => {
return s.replace(/#br#/g, '<br/>');
};
const getUrl = useAbsolute => {
let url = '';
if (useAbsolute) {
url =
window.location.protocol +
'//' +
window.location.host +
window.location.pathname +
window.location.search;
url = url.replace(/\(/g, '\\(');
url = url.replace(/\)/g, '\\)');
}
return url;
};
export default {
getRows,
sanitizeText,
hasBreaks,
splitBreaks,
lineBreakRegex,
removeScript
removeScript,
getUrl
};

View File

@ -1,20 +1,19 @@
import { setConfig } from '../../../config';
import erDb from '../erDb';
import erDiagram from './erDiagram';
import { setConfig } from '../../../config';
import log from '../../../logger';
setConfig({
securityLevel: 'strict'
});
describe('when parsing ER diagram it...', function() {
describe('when parsing ER diagram it...', function () {
beforeEach(function() {
beforeEach(function () {
erDiagram.parser.yy = erDb;
erDiagram.parser.yy.clear();
});
it ('should allow stand-alone entities with no relationships', function() {
it('should allow stand-alone entities with no relationships', function () {
const line1 = 'ISLAND';
const line2 = 'MAINLAND';
erDiagram.parser.parse(`erDiagram\n${line1}\n${line2}`);
@ -23,7 +22,7 @@ describe('when parsing ER diagram it...', function() {
expect(erDb.getRelationships().length).toBe(0);
});
it ('should allow hyphens and underscores in entity names', function() {
it('should allow hyphens and underscores in entity names', function () {
const line1 = 'DUCK-BILLED-PLATYPUS';
const line2 = 'CHARACTER_SET';
erDiagram.parser.parse(`erDiagram\n${line1}\n${line2}`);
@ -33,7 +32,7 @@ describe('when parsing ER diagram it...', function() {
expect(entities.hasOwnProperty('CHARACTER_SET')).toBe(true);
});
it('should allow an entity with a single attribute to be defined', function() {
it('should allow an entity with a single attribute to be defined', function () {
const entity = 'BOOK';
const attribute = 'string title';
@ -43,7 +42,7 @@ describe('when parsing ER diagram it...', function() {
expect(entities[entity].attributes.length).toBe(1);
});
it('should allow an entity with multiple attributes to be defined', function() {
it('should allow an entity with multiple attributes to be defined', function () {
const entity = 'BOOK';
const attribute1 = 'string title';
const attribute2 = 'string author';
@ -54,7 +53,7 @@ describe('when parsing ER diagram it...', function() {
expect(entities[entity].attributes.length).toBe(3);
});
it('should allow attribute definitions to be split into multiple blocks', function() {
it('should allow attribute definitions to be split into multiple blocks', function () {
const entity = 'BOOK';
const attribute1 = 'string title';
const attribute2 = 'string author';
@ -65,7 +64,7 @@ describe('when parsing ER diagram it...', function() {
expect(entities[entity].attributes.length).toBe(3);
});
it('should allow an empty attribute block', function() {
it('should allow an empty attribute block', function () {
const entity = 'BOOK';
erDiagram.parser.parse(`erDiagram\n${entity} {}`);
@ -74,7 +73,7 @@ describe('when parsing ER diagram it...', function() {
expect(entities[entity].attributes.length).toBe(0);
});
it('should allow an attribute block to start immediately after the entity name', function() {
it('should allow an attribute block to start immediately after the entity name', function () {
const entity = 'BOOK';
erDiagram.parser.parse(`erDiagram\n${entity}{}`);
@ -83,7 +82,7 @@ describe('when parsing ER diagram it...', function() {
expect(entities[entity].attributes.length).toBe(0);
});
it('should allow an attribute block to be separated from the entity name by spaces', function() {
it('should allow an attribute block to be separated from the entity name by spaces', function () {
const entity = 'BOOK';
erDiagram.parser.parse(`erDiagram\n${entity} {}`);
@ -92,7 +91,7 @@ describe('when parsing ER diagram it...', function() {
expect(entities[entity].attributes.length).toBe(0);
});
it('should allow whitespace before and after attribute definitions', function() {
it('should allow whitespace before and after attribute definitions', function () {
const entity = 'BOOK';
const attribute = 'string title';
@ -102,7 +101,7 @@ describe('when parsing ER diagram it...', function() {
expect(entities[entity].attributes.length).toBe(1);
});
it('should allow no whitespace before and after attribute definitions', function() {
it('should allow no whitespace before and after attribute definitions', function () {
const entity = 'BOOK';
const attribute = 'string title';
@ -112,7 +111,7 @@ describe('when parsing ER diagram it...', function() {
expect(entities[entity].attributes.length).toBe(1);
});
it('should associate two entities correctly', function() {
it('should associate two entities correctly', function () {
erDiagram.parser.parse('erDiagram\nCAR ||--o{ DRIVER : "insured for"');
const entities = erDb.getEntities();
const relationships = erDb.getRelationships();
@ -125,7 +124,7 @@ describe('when parsing ER diagram it...', function() {
expect(relationships[0].relSpec.relType).toBe(erDb.Identification.IDENTIFYING);
});
it('should not create duplicate entities', function() {
it('should not create duplicate entities', function () {
const line1 = 'CAR ||--o{ DRIVER : "insured for"';
const line2 = 'DRIVER ||--|| LICENSE : has';
erDiagram.parser.parse(`erDiagram\n${line1}\n${line2}`);
@ -134,7 +133,7 @@ describe('when parsing ER diagram it...', function() {
expect(Object.keys(entities).length).toBe(3);
});
it('should create the role specified', function() {
it('should create the role specified', function () {
const teacherRole = 'is teacher of';
const line1 = `TEACHER }o--o{ STUDENT : "${teacherRole}"`;
erDiagram.parser.parse(`erDiagram\n${line1}`);
@ -143,32 +142,32 @@ describe('when parsing ER diagram it...', function() {
expect(rels[0].roleA).toBe(`${teacherRole}`);
});
it('should allow recursive relationships', function() {
it('should allow recursive relationships', function () {
erDiagram.parser.parse('erDiagram\nNODE ||--o{ NODE : "leads to"');
expect(Object.keys(erDb.getEntities()).length).toBe(1);
});
it('should allow more than one relationship between the same two entities', function() {
it('should allow more than one relationship between the same two entities', function () {
const line1 = 'CAR ||--o{ PERSON : "insured for"';
const line2 = 'CAR }o--|| PERSON : "owned by"';
erDiagram.parser.parse(`erDiagram\n${line1}\n${line2}`);
const entities = erDb.getEntities();
const rels = erDb.getRelationships();
const rels = erDb.getRelationships();
expect(Object.keys(entities).length).toBe(2);
expect(rels.length).toBe(2);
});
it('should limit the number of relationships between the same two entities', function() {
it('should limit the number of relationships between the same two entities', function () {
/* TODO */
});
it ('should not allow multiple relationships between the same two entities unless the roles are different', function() {
it('should not allow multiple relationships between the same two entities unless the roles are different', function () {
/* TODO */
});
it('should handle only-one-to-one-or-more relationships', function() {
it('should handle only-one-to-one-or-more relationships', function () {
erDiagram.parser.parse('erDiagram\nA ||--|{ B : has');
const rels = erDb.getRelationships();
@ -178,7 +177,7 @@ describe('when parsing ER diagram it...', function() {
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ONLY_ONE);
});
it('should handle only-one-to-zero-or-more relationships', function() {
it('should handle only-one-to-zero-or-more relationships', function () {
erDiagram.parser.parse('erDiagram\nA ||..o{ B : has');
const rels = erDb.getRelationships();
@ -189,7 +188,7 @@ describe('when parsing ER diagram it...', function() {
});
it('should handle zero-or-one-to-zero-or-more relationships', function() {
it('should handle zero-or-one-to-zero-or-more relationships', function () {
erDiagram.parser.parse('erDiagram\nA |o..o{ B : has');
const rels = erDb.getRelationships();
@ -199,7 +198,7 @@ describe('when parsing ER diagram it...', function() {
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ZERO_OR_ONE);
});
it('should handle zero-or-one-to-one-or-more relationships', function() {
it('should handle zero-or-one-to-one-or-more relationships', function () {
erDiagram.parser.parse('erDiagram\nA |o--|{ B : has');
const rels = erDb.getRelationships();
@ -209,7 +208,7 @@ describe('when parsing ER diagram it...', function() {
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ZERO_OR_ONE);
});
it('should handle one-or-more-to-only-one relationships', function() {
it('should handle one-or-more-to-only-one relationships', function () {
erDiagram.parser.parse('erDiagram\nA }|--|| B : has');
const rels = erDb.getRelationships();
@ -219,7 +218,7 @@ describe('when parsing ER diagram it...', function() {
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ONE_OR_MORE);
});
it('should handle zero-or-more-to-only-one relationships', function() {
it('should handle zero-or-more-to-only-one relationships', function () {
erDiagram.parser.parse('erDiagram\nA }o--|| B : has');
const rels = erDb.getRelationships();
@ -229,7 +228,7 @@ describe('when parsing ER diagram it...', function() {
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ZERO_OR_MORE);
});
it('should handle zero-or-more-to-zero-or-one relationships', function() {
it('should handle zero-or-more-to-zero-or-one relationships', function () {
erDiagram.parser.parse('erDiagram\nA }o..o| B : has');
const rels = erDb.getRelationships();
@ -239,7 +238,7 @@ describe('when parsing ER diagram it...', function() {
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ZERO_OR_MORE);
});
it('should handle one-or-more-to-zero-or-one relationships', function() {
it('should handle one-or-more-to-zero-or-one relationships', function () {
erDiagram.parser.parse('erDiagram\nA }|..o| B : has');
const rels = erDb.getRelationships();
@ -249,7 +248,7 @@ describe('when parsing ER diagram it...', function() {
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ONE_OR_MORE);
});
it('should handle zero-or-one-to-only-one relationships', function() {
it('should handle zero-or-one-to-only-one relationships', function () {
erDiagram.parser.parse('erDiagram\nA |o..|| B : has');
const rels = erDb.getRelationships();
@ -259,7 +258,7 @@ describe('when parsing ER diagram it...', function() {
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ZERO_OR_ONE);
});
it('should handle only-one-to-only-one relationships', function() {
it('should handle only-one-to-only-one relationships', function () {
erDiagram.parser.parse('erDiagram\nA ||..|| B : has');
const rels = erDb.getRelationships();
@ -269,7 +268,7 @@ describe('when parsing ER diagram it...', function() {
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ONLY_ONE);
});
it('should handle only-one-to-zero-or-one relationships', function() {
it('should handle only-one-to-zero-or-one relationships', function () {
erDiagram.parser.parse('erDiagram\nA ||--o| B : has');
const rels = erDb.getRelationships();
@ -279,7 +278,7 @@ describe('when parsing ER diagram it...', function() {
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ONLY_ONE);
});
it('should handle zero-or-one-to-zero-or-one relationships', function() {
it('should handle zero-or-one-to-zero-or-one relationships', function () {
erDiagram.parser.parse('erDiagram\nA |o..o| B : has');
const rels = erDb.getRelationships();
@ -289,7 +288,7 @@ describe('when parsing ER diagram it...', function() {
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ZERO_OR_ONE);
});
it('should handle zero-or-more-to-zero-or-more relationships', function() {
it('should handle zero-or-more-to-zero-or-more relationships', function () {
erDiagram.parser.parse('erDiagram\nA }o--o{ B : has');
const rels = erDb.getRelationships();
@ -299,7 +298,7 @@ describe('when parsing ER diagram it...', function() {
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ZERO_OR_MORE);
});
it('should handle one-or-more-to-one-or-more relationships', function() {
it('should handle one-or-more-to-one-or-more relationships', function () {
erDiagram.parser.parse('erDiagram\nA }|..|{ B : has');
const rels = erDb.getRelationships();
@ -309,7 +308,7 @@ describe('when parsing ER diagram it...', function() {
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ONE_OR_MORE);
});
it('should handle zero-or-more-to-one-or-more relationships', function() {
it('should handle zero-or-more-to-one-or-more relationships', function () {
erDiagram.parser.parse('erDiagram\nA }o--|{ B : has');
const rels = erDb.getRelationships();
@ -319,7 +318,7 @@ describe('when parsing ER diagram it...', function() {
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ZERO_OR_MORE);
});
it('should handle one-or-more-to-zero-or-more relationships', function() {
it('should handle one-or-more-to-zero-or-more relationships', function () {
erDiagram.parser.parse('erDiagram\nA }|..o{ B : has');
const rels = erDb.getRelationships();
@ -329,38 +328,38 @@ describe('when parsing ER diagram it...', function() {
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ONE_OR_MORE);
});
it('should represent identifying relationships properly', function() {
it('should represent identifying relationships properly', function () {
erDiagram.parser.parse('erDiagram\nHOUSE ||--|{ ROOM : contains');
const rels = erDb.getRelationships();
expect(rels[0].relSpec.relType).toBe(erDb.Identification.IDENTIFYING);
});
it('should represent non-identifying relationships properly', function() {
it('should represent non-identifying relationships properly', function () {
erDiagram.parser.parse('erDiagram\n PERSON ||..o{ POSSESSION : owns');
const rels = erDb.getRelationships();
expect(rels[0].relSpec.relType).toBe(erDb.Identification.NON_IDENTIFYING);
});
it('should not accept a syntax error', function() {
it('should not accept a syntax error', function () {
const doc = 'erDiagram\nA xxx B : has';
expect(() => {
erDiagram.parser.parse(doc);
}).toThrowError();
});
it('should allow an empty quoted label', function() {
it('should allow an empty quoted label', function () {
erDiagram.parser.parse('erDiagram\nCUSTOMER ||--|{ ORDER : ""');
const rels = erDb.getRelationships();
expect(rels[0].roleA).toBe('');
});
it('should allow an non-empty quoted label', function() {
it('should allow an non-empty quoted label', function () {
erDiagram.parser.parse('erDiagram\nCUSTOMER ||--|{ ORDER : "places"');
const rels = erDb.getRelationships();
expect(rels[0].roleA).toBe('places');
});
it('should allow an non-empty unquoted label', function() {
it('should allow an non-empty unquoted label', function () {
erDiagram.parser.parse('erDiagram\nCUSTOMER ||--|{ ORDER : places');
const rels = erDb.getRelationships();
expect(rels[0].roleA).toBe('places');

View File

@ -0,0 +1,174 @@
/** mermaid
* https://knsv.github.io/mermaid
* (c) 2015 Knut Sveidqvist
* MIT license.
*/
%lex
%options case-insensitive
%x string
%x token
%x unqString
%%
(\r?\n)+ return 'NEWLINE';
\s+ /* skip all whitespace */
\#[^\n]* /* skip comments */
\%%[^\n]* /* skip comments */
<<EOF>> return 'EOF';
"requirementDiagram" return 'RD';
"{" return 'STRUCT_START';
"}" return 'STRUCT_STOP';
":" return 'COLONSEP';
"id" return 'ID';
"text" return 'TEXT';
"risk" return 'RISK';
"verifyMethod" return 'VERIFYMTHD';
"requirement" return 'REQUIREMENT';
"functionalRequirement" return 'FUNCTIONAL_REQUIREMENT';
"interfaceRequirement" return 'INTERFACE_REQUIREMENT';
"performanceRequirement" return 'PERFORMANCE_REQUIREMENT';
"physicalRequirement" return 'PHYSICAL_REQUIREMENT';
"designConstraint" return 'DESIGN_CONSTRAINT';
"low" return 'LOW_RISK';
"medium" return 'MED_RISK';
"high" return 'HIGH_RISK';
"analysis" return 'VERIFY_ANALYSIS';
"demonstration" return 'VERIFY_DEMONSTRATION';
"inspection" return 'VERIFY_INSPECTION';
"test" return 'VERIFY_TEST';
"element" return 'ELEMENT';
"contains" return 'CONTAINS';
"copies" return 'COPIES';
"derives" return 'DERIVES';
"satisfies" return 'SATISFIES';
"verifies" return 'VERIFIES';
"refines" return 'REFINES';
"traces" return 'TRACES';
"type" return 'TYPE';
"docref" return 'DOCREF';
"<-" return 'END_ARROW_L';
"->" {return 'END_ARROW_R';}
"-" {return 'LINE';}
["] { this.begin("string"); }
<string>["] { this.popState(); }
<string>[^"]* { return "qString"; }
[\w][^\r\n\{\<\>\-\=]* { yytext = yytext.trim(); return 'unqString';}
/lex
%start start
%% /* language grammar */
start
: directive start
| RD NEWLINE diagram EOF;
diagram
: /* empty */ { $$ = [] }
| requirementDef diagram
| elementDef diagram
| relationshipDef diagram
| NEWLINE diagram;
requirementDef
: requirementType requirementName STRUCT_START NEWLINE requirementBody
{ yy.addRequirement($2, $1) };
requirementBody
: ID COLONSEP id NEWLINE requirementBody
{ yy.setNewReqId($3); }
| TEXT COLONSEP text NEWLINE requirementBody
{ yy.setNewReqText($3); }
| RISK COLONSEP riskLevel NEWLINE requirementBody
{ yy.setNewReqRisk($3); }
| VERIFYMTHD COLONSEP verifyType NEWLINE requirementBody
{ yy.setNewReqVerifyMethod($3); }
| NEWLINE requirementBody
| STRUCT_STOP;
requirementType
: REQUIREMENT
{ $$=yy.RequirementType.REQUIREMENT;}
| FUNCTIONAL_REQUIREMENT
{ $$=yy.RequirementType.FUNCTIONAL_REQUIREMENT;}
| INTERFACE_REQUIREMENT
{ $$=yy.RequirementType.INTERFACE_REQUIREMENT;}
| PERFORMANCE_REQUIREMENT
{ $$=yy.RequirementType.PERFORMANCE_REQUIREMENT;}
| PHYSICAL_REQUIREMENT
{ $$=yy.RequirementType.PHYSICAL_REQUIREMENT;}
| DESIGN_CONSTRAINT
{ $$=yy.RequirementType.DESIGN_CONSTRAINT;};
riskLevel
: LOW_RISK { $$=yy.RiskLevel.LOW_RISK;}
| MED_RISK { $$=yy.RiskLevel.MED_RISK;}
| HIGH_RISK { $$=yy.RiskLevel.HIGH_RISK;};
verifyType
: VERIFY_ANALYSIS
{ $$=yy.VerifyType.VERIFY_ANALYSIS;}
| VERIFY_DEMONSTRATION
{ $$=yy.VerifyType.VERIFY_DEMONSTRATION;}
| VERIFY_INSPECTION
{ $$=yy.VerifyType.VERIFY_INSPECTION;}
| VERIFY_TEST
{ $$=yy.VerifyType.VERIFY_TEST;};
elementDef
: ELEMENT elementName STRUCT_START NEWLINE elementBody
{ yy.addElement($2) };
elementBody
: TYPE COLONSEP type NEWLINE elementBody
{ yy.setNewElementType($3); }
| DOCREF COLONSEP ref NEWLINE elementBody
{ yy.setNewElementDocRef($3); }
| NEWLINE elementBody
| STRUCT_STOP;
relationshipDef
: id END_ARROW_L relationship LINE id
{ yy.addRelationship($3, $5, $1) }
| id LINE relationship END_ARROW_R id
{ yy.addRelationship($3, $1, $5) };
relationship
: CONTAINS
{ $$=yy.Relationships.CONTAINS;}
| COPIES
{ $$=yy.Relationships.COPIES;}
| DERIVES
{ $$=yy.Relationships.DERIVES;}
| SATISFIES
{ $$=yy.Relationships.SATISFIES;}
| VERIFIES
{ $$=yy.Relationships.VERIFIES;}
| REFINES
{ $$=yy.Relationships.REFINES;}
| TRACES
{ $$=yy.Relationships.TRACES;};
requirementName: unqString | qString;
id : unqString | qString;
text : unqString | qString;
elementName : unqString | qString;
type : unqString | qString;
ref : unqString | qString;
%%

View File

@ -0,0 +1,577 @@
import { setConfig } from '../../../config';
import requirementDb from '../requirementDb';
import reqDiagram from './requirementDiagram';
setConfig({
securityLevel: 'strict'
});
describe('when parsing requirement diagram it...', function() {
beforeEach(function() {
reqDiagram.parser.yy = requirementDb;
reqDiagram.parser.yy.clear();
});
it('will accept full requirement definition', function() {
const expectedName = "test_req";
const expectedId = "test_id";
const expectedText = "the test text."
const expectedRisk = requirementDb.RiskLevel.HIGH_RISK;
const expectedVerifyMethod = requirementDb.VerifyType.VERIFY_ANALYSIS;
let lines = [
`requirementDiagram`,
``,
`requirement ${expectedName} {`,
`id: ${expectedId}`,
`text: ${expectedText}`,
`risk: ${expectedRisk}`,
`verifymethod: ${expectedVerifyMethod}`,
`}`,
];
let doc = lines.join("\n");
reqDiagram.parser.parse(doc);
expect(Object.keys(requirementDb.getRequirements()).length).toBe(1);
let foundReq = requirementDb.getRequirements()[expectedName];
expect(foundReq).toBeDefined();
expect(foundReq.id).toBe(expectedId);
expect(foundReq.text).toBe(expectedText);
expect(Object.keys(requirementDb.getElements()).length).toBe(0);
expect(Object.keys(requirementDb.getRelationships()).length).toBe(0);
});
it('will accept full element definition', function() {
const expectedName = "test_el";
const expectedType = "test_type";
const expectedDocRef = "test_ref"
let lines = [
`requirementDiagram`,
``,
`element ${expectedName} {`,
`type: ${expectedType}`,
`docref: ${expectedDocRef}`,
`}`,
];
let doc = lines.join("\n");
reqDiagram.parser.parse(doc);
expect(Object.keys(requirementDb.getRequirements()).length).toBe(0);
expect(Object.keys(requirementDb.getElements()).length).toBe(1);
let foundElement = requirementDb.getElements()[expectedName];
expect(foundElement).toBeDefined();
expect(foundElement.type).toBe(expectedType);
expect(foundElement.docRef).toBe(
expectedDocRef);
expect(Object.keys(requirementDb.getRelationships()).length).toBe(0);
});
it('will accept full relationship definition', function() {
const expectedSrc = "a";
const expectedDest = "b";
const expectedType = requirementDb.Relationships.CONTAINS;
let lines = [
`requirementDiagram`,
``,
`${expectedSrc} - ${expectedType} -> ${expectedDest}`,
];
let doc = lines.join("\n");
reqDiagram.parser.parse(doc);
expect(Object.keys(requirementDb.getRequirements()).length).toBe(0);
expect(Object.keys(requirementDb.getElements()).length).toBe(0);
expect(Object.keys(requirementDb.getRelationships()).length).toBe(1);
let foundRelationship = requirementDb.getRelationships()[0];
expect(foundRelationship.src).toBe(expectedSrc);
expect(foundRelationship.dst).toBe(expectedDest);
});
it('will accept "requirement" type of requirement definition', function() {
const expectedName = "test_req";
const expectedType = requirementDb.RequirementType.REQUIREMENT;
const expectedId = "test_id";
const expectedText = "the test text."
const expectedRisk = requirementDb.RiskLevel.HIGH_RISK;
const expectedVerifyMethod = requirementDb.VerifyType.VERIFY_ANALYSIS;
let lines = [
`requirementDiagram`,
``,
`requirement ${expectedName} {`,
`id: ${expectedId}`,
`text: ${expectedText}`,
`risk: ${expectedRisk}`,
`verifymethod: ${expectedVerifyMethod}`,
`}`,
];
let doc = lines.join("\n");
reqDiagram.parser.parse(doc);
let foundReq = requirementDb.getRequirements()[expectedName];
expect(foundReq).toBeDefined();
expect(foundReq.type).toBe(expectedType);
});
it('will accept "functionalRequirement" type of requirement definition', function() {
const expectedName = "test_req";
const expectedType = requirementDb.RequirementType.FUNCTIONAL_REQUIREMENT;
const expectedId = "test_id";
const expectedText = "the test text."
const expectedRisk = requirementDb.RiskLevel.HIGH_RISK;
const expectedVerifyMethod = requirementDb.VerifyType.VERIFY_ANALYSIS;
let lines = [
`requirementDiagram`,
``,
`functionalRequirement ${expectedName} {`,
`id: ${expectedId}`,
`text: ${expectedText}`,
`risk: ${expectedRisk}`,
`verifymethod: ${expectedVerifyMethod}`,
`}`,
];
let doc = lines.join("\n");
reqDiagram.parser.parse(doc);
let foundReq = requirementDb.getRequirements()[expectedName];
expect(foundReq).toBeDefined();
expect(foundReq.type).toBe(expectedType);
});
it('will accept "interfaceRequirement" type of requirement definition', function() {
const expectedName = "test_req";
const expectedType = requirementDb.RequirementType.INTERFACE_REQUIREMENT;
const expectedId = "test_id";
const expectedText = "the test text."
const expectedRisk = requirementDb.RiskLevel.HIGH_RISK;
const expectedVerifyMethod = requirementDb.VerifyType.VERIFY_ANALYSIS;
let lines = [
`requirementDiagram`,
``,
`interfaceRequirement ${expectedName} {`,
`id: ${expectedId}`,
`text: ${expectedText}`,
`risk: ${expectedRisk}`,
`verifymethod: ${expectedVerifyMethod}`,
`}`,
];
let doc = lines.join("\n");
reqDiagram.parser.parse(doc);
let foundReq = requirementDb.getRequirements()[expectedName];
expect(foundReq).toBeDefined();
expect(foundReq.type).toBe(expectedType);
});
it('will accept "performanceRequirement" type of requirement definition', function() {
const expectedName = "test_req";
const expectedType = requirementDb.RequirementType.PERFORMANCE_REQUIREMENT;
const expectedId = "test_id";
const expectedText = "the test text."
const expectedRisk = requirementDb.RiskLevel.HIGH_RISK;
const expectedVerifyMethod = requirementDb.VerifyType.VERIFY_ANALYSIS;
let lines = [
`requirementDiagram`,
``,
`performanceRequirement ${expectedName} {`,
`id: ${expectedId}`,
`text: ${expectedText}`,
`risk: ${expectedRisk}`,
`verifymethod: ${expectedVerifyMethod}`,
`}`,
];
let doc = lines.join("\n");
reqDiagram.parser.parse(doc);
let foundReq = requirementDb.getRequirements()[expectedName];
expect(foundReq).toBeDefined();
expect(foundReq.type).toBe(expectedType);
});
it('will accept "physicalRequirement" type of requirement definition', function() {
const expectedName = "test_req";
const expectedType = requirementDb.RequirementType.PHYSICAL_REQUIREMENT;
const expectedId = "test_id";
const expectedText = "the test text."
const expectedRisk = requirementDb.RiskLevel.HIGH_RISK;
const expectedVerifyMethod = requirementDb.VerifyType.VERIFY_ANALYSIS;
let lines = [
`requirementDiagram`,
``,
`physicalRequirement ${expectedName} {`,
`id: ${expectedId}`,
`text: ${expectedText}`,
`risk: ${expectedRisk}`,
`verifymethod: ${expectedVerifyMethod}`,
`}`,
];
let doc = lines.join("\n");
reqDiagram.parser.parse(doc);
let foundReq = requirementDb.getRequirements()[expectedName];
expect(foundReq).toBeDefined();
expect(foundReq.type).toBe(expectedType);
});
it('will accept "designConstraint" type of requirement definition', function() {
const expectedName = "test_req";
const expectedType = requirementDb.RequirementType.DESIGN_CONSTRAINT;
const expectedId = "test_id";
const expectedText = "the test text."
const expectedRisk = requirementDb.RiskLevel.HIGH_RISK;
const expectedVerifyMethod = requirementDb.VerifyType.VERIFY_ANALYSIS;
let lines = [
`requirementDiagram`,
``,
`designConstraint ${expectedName} {`,
`id: ${expectedId}`,
`text: ${expectedText}`,
`risk: ${expectedRisk}`,
`verifymethod: ${expectedVerifyMethod}`,
`}`,
];
let doc = lines.join("\n");
reqDiagram.parser.parse(doc);
let foundReq = requirementDb.getRequirements()[expectedName];
expect(foundReq).toBeDefined();
expect(foundReq.type).toBe(expectedType);
});
it('will accept "low" type of risk requirement definition', function() {
const expectedName = "test_req";
const expectedType = "designConstraint";
const expectedId = "test_id";
const expectedText = "the test text."
const expectedRisk = requirementDb.RiskLevel.LOW_RISK;
const expectedVerifyMethod = requirementDb.VerifyType.VERIFY_ANALYSIS;
let lines = [
`requirementDiagram`,
``,
`${expectedType} ${expectedName} {`,
`id: ${expectedId}`,
`text: ${expectedText}`,
`risk: ${expectedRisk}`,
`verifymethod: ${expectedVerifyMethod}`,
`}`,
];
let doc = lines.join("\n");
reqDiagram.parser.parse(doc);
let foundReq = requirementDb.getRequirements()[expectedName];
expect(foundReq).toBeDefined();
expect(foundReq.risk).toBe(expectedRisk);
});
it('will accept "medium" type of risk requirement definition', function() {
const expectedName = "test_req";
const expectedType = "designConstraint";
const expectedId = "test_id";
const expectedText = "the test text."
const expectedRisk = requirementDb.RiskLevel.MED_RISK;
const expectedVerifyMethod = requirementDb.VerifyType.VERIFY_ANALYSIS;
let lines = [
`requirementDiagram`,
``,
`${expectedType} ${expectedName} {`,
`id: ${expectedId}`,
`text: ${expectedText}`,
`risk: ${expectedRisk}`,
`verifymethod: ${expectedVerifyMethod}`,
`}`,
];
let doc = lines.join("\n");
reqDiagram.parser.parse(doc);
let foundReq = requirementDb.getRequirements()[expectedName];
expect(foundReq).toBeDefined();
expect(foundReq.risk).toBe(expectedRisk);
});
it('will accept "high" type of risk requirement definition', function() {
const expectedName = "test_req";
const expectedType = "designConstraint";
const expectedId = "test_id";
const expectedText = "the test text."
const expectedRisk = requirementDb.RiskLevel.HIGH_RISK;
const expectedVerifyMethod = requirementDb.VerifyType.VERIFY_ANALYSIS;
let lines = [
`requirementDiagram`,
``,
`${expectedType} ${expectedName} {`,
`id: ${expectedId}`,
`text: ${expectedText}`,
`risk: ${expectedRisk}`,
`verifymethod: ${expectedVerifyMethod}`,
`}`,
];
let doc = lines.join("\n");
reqDiagram.parser.parse(doc);
let foundReq = requirementDb.getRequirements()[expectedName];
expect(foundReq).toBeDefined();
expect(foundReq.risk).toBe(expectedRisk);
});
it('will accept "Analysis" type of verification method requirement definition', function() {
const expectedName = "test_req";
const expectedType = "designConstraint";
const expectedId = "test_id";
const expectedText = "the test text."
const expectedRisk = requirementDb.RiskLevel.HIGH_RISK;
const expectedVerifyMethod = requirementDb.VerifyType.VERIFY_ANALYSIS;
let lines = [
`requirementDiagram`,
``,
`${expectedType} ${expectedName} {`,
`id: ${expectedId}`,
`text: ${expectedText}`,
`risk: ${expectedRisk}`,
`verifymethod: ${expectedVerifyMethod}`,
`}`,
];
let doc = lines.join("\n");
reqDiagram.parser.parse(doc);
let foundReq = requirementDb.getRequirements()[expectedName];
expect(foundReq).toBeDefined();
expect(foundReq.verifyMethod).toBe(expectedVerifyMethod);
});
it('will accept "Inspection" type of verification method requirement definition', function() {
const expectedName = "test_req";
const expectedType = "designConstraint";
const expectedId = "test_id";
const expectedText = "the test text."
const expectedRisk = requirementDb.RiskLevel.HIGH_RISK;
const expectedVerifyMethod = requirementDb.VerifyType.VERIFY_INSPECTION;
let lines = [
`requirementDiagram`,
``,
`${expectedType} ${expectedName} {`,
`id: ${expectedId}`,
`text: ${expectedText}`,
`risk: ${expectedRisk}`,
`verifymethod: ${expectedVerifyMethod}`,
`}`,
];
let doc = lines.join("\n");
reqDiagram.parser.parse(doc);
let foundReq = requirementDb.getRequirements()[expectedName];
expect(foundReq).toBeDefined();
expect(foundReq.verifyMethod).toBe(expectedVerifyMethod);
});
it('will accept "Test" type of verification method requirement definition', function() {
const expectedName = "test_req";
const expectedType = "designConstraint";
const expectedId = "test_id";
const expectedText = "the test text."
const expectedRisk = requirementDb.RiskLevel.HIGH_RISK;
const expectedVerifyMethod = requirementDb.VerifyType.VERIFY_TEST;
let lines = [
`requirementDiagram`,
``,
`${expectedType} ${expectedName} {`,
`id: ${expectedId}`,
`text: ${expectedText}`,
`risk: ${expectedRisk}`,
`verifymethod: ${expectedVerifyMethod}`,
`}`,
];
let doc = lines.join("\n");
reqDiagram.parser.parse(doc);
let foundReq = requirementDb.getRequirements()[expectedName];
expect(foundReq).toBeDefined();
expect(foundReq.verifyMethod).toBe(expectedVerifyMethod);
});
it('will accept "Demonstration" type of verification method requirement definition', function() {
const expectedName = "test_req";
const expectedType = requirementDb.RequirementType.DESIGN_CONSTRAINT;
const expectedId = "test_id";
const expectedText = "the test text."
const expectedRisk = requirementDb.RiskLevel.HIGH_RISK;
const expectedVerifyMethod = requirementDb.VerifyType.VERIFY_DEMONSTRATION;
let lines = [
`requirementDiagram`,
``,
`designConstraint ${expectedName} {`,
`id: ${expectedId}`,
`text: ${expectedText}`,
`risk: ${expectedRisk}`,
`verifymethod: ${expectedVerifyMethod}`,
`}`,
];
let doc = lines.join("\n");
reqDiagram.parser.parse(doc);
let foundReq = requirementDb.getRequirements()[expectedName];
expect(foundReq).toBeDefined();
expect(foundReq.verifyMethod).toBe(expectedVerifyMethod);
});
it('will accept contains relationship definition', function() {
const expectedSrc = "a";
const expectedDest = "b";
const expectedType = requirementDb.Relationships.CONTAINS;
let lines = [
`requirementDiagram`,
``,
`${expectedSrc} - ${expectedType} -> ${expectedDest}`,
];
let doc = lines.join("\n");
reqDiagram.parser.parse(doc);
let foundRelationship = requirementDb.getRelationships()[0];
expect(foundRelationship.type).toBe(expectedType);
});
it('will accept copies relationship definition', function() {
const expectedSrc = "a";
const expectedDest = "b";
const expectedType = requirementDb.Relationships.COPIES;
let lines = [
`requirementDiagram`,
``,
`${expectedSrc} - ${expectedType} -> ${expectedDest}`,
];
let doc = lines.join("\n");
reqDiagram.parser.parse(doc);
let foundRelationship = requirementDb.getRelationships()[0];
expect(foundRelationship.type).toBe(expectedType);
});
it('will accept derives relationship definition', function() {
const expectedSrc = "a";
const expectedDest = "b";
const expectedType = requirementDb.Relationships.DERIVES;
let lines = [
`requirementDiagram`,
``,
`${expectedSrc} - ${expectedType} -> ${expectedDest}`,
];
let doc = lines.join("\n");
reqDiagram.parser.parse(doc);
let foundRelationship = requirementDb.getRelationships()[0];
expect(foundRelationship.type).toBe(expectedType);
});
it('will accept satisfies relationship definition', function() {
const expectedSrc = "a";
const expectedDest = "b";
const expectedType = requirementDb.Relationships.SATISFIES;
let lines = [
`requirementDiagram`,
``,
`${expectedSrc} - ${expectedType} -> ${expectedDest}`,
];
let doc = lines.join("\n");
reqDiagram.parser.parse(doc);
let foundRelationship = requirementDb.getRelationships()[0];
expect(foundRelationship.type).toBe(expectedType);
});
it('will accept verifies relationship definition', function() {
const expectedSrc = "a";
const expectedDest = "b";
const expectedType = requirementDb.Relationships.VERIFIES;
let lines = [
`requirementDiagram`,
``,
`${expectedSrc} - ${expectedType} -> ${expectedDest}`,
];
let doc = lines.join("\n");
reqDiagram.parser.parse(doc);
let foundRelationship = requirementDb.getRelationships()[0];
expect(foundRelationship.type).toBe(expectedType);
});
it('will accept refines relationship definition', function() {
const expectedSrc = "a";
const expectedDest = "b";
const expectedType = requirementDb.Relationships.REFINES;
let lines = [
`requirementDiagram`,
``,
`${expectedSrc} - ${expectedType} -> ${expectedDest}`,
];
let doc = lines.join("\n");
reqDiagram.parser.parse(doc);
let foundRelationship = requirementDb.getRelationships()[0];
expect(foundRelationship.type).toBe(expectedType);
});
it('will accept traces relationship definition', function() {
const expectedSrc = "a";
const expectedDest = "b";
const expectedType = requirementDb.Relationships.TRACES;
let lines = [
`requirementDiagram`,
``,
`${expectedSrc} - ${expectedType} -> ${expectedDest}`,
];
let doc = lines.join("\n");
reqDiagram.parser.parse(doc);
let foundRelationship = requirementDb.getRelationships()[0];
expect(foundRelationship.type).toBe(expectedType);
});
});

View File

@ -0,0 +1,162 @@
import configApi from '../../config';
import { log } from '../../logger';
import mermaidAPI from '../../mermaidAPI';
let relations = [];
let latestRequirement = {};
let requirements = {};
let latestElement = {};
let elements = {};
const RequirementType = {
REQUIREMENT: 'Requirement',
FUNCTIONAL_REQUIREMENT: 'Functional Requirement',
INTERFACE_REQUIREMENT: 'Interface Requirement',
PERFORMANCE_REQUIREMENT: 'Performance Requirement',
PHYSICAL_REQUIREMENT: 'Physical Requirement',
DESIGN_CONSTRAINT: 'Design Constraint'
};
const RiskLevel = {
LOW_RISK: 'Low',
MED_RISK: 'Medium',
HIGH_RISK: 'High'
};
const VerifyType = {
VERIFY_ANALYSIS: 'Analysis',
VERIFY_DEMONSTRATION: 'Demonstration',
VERIFY_INSPECTION: 'Inspection',
VERIFY_TEST: 'Test'
};
const Relationships = {
CONTAINS: 'contains',
COPIES: 'copies',
DERIVES: 'derives',
SATISFIES: 'satisfies',
VERIFIES: 'verifies',
REFINES: 'refines',
TRACES: 'traces'
};
export const parseDirective = function(statement, context, type) {
mermaidAPI.parseDirective(this, statement, context, type);
};
const addRequirement = (name, type) => {
if (typeof requirements[name] === 'undefined') {
requirements[name] = {
name,
type,
id: latestRequirement.id,
text: latestRequirement.text,
risk: latestRequirement.risk,
verifyMethod: latestRequirement.verifyMethod
};
}
latestRequirement = {};
return requirements[name];
};
const getRequirements = () => requirements;
const setNewReqId = id => {
if (typeof latestRequirement != 'undefined') {
latestRequirement.id = id;
}
};
const setNewReqText = text => {
if (typeof latestRequirement != 'undefined') {
latestRequirement.text = text;
}
};
const setNewReqRisk = risk => {
if (typeof latestRequirement != 'undefined') {
latestRequirement.risk = risk;
}
};
const setNewReqVerifyMethod = verifyMethod => {
if (typeof latestRequirement != 'undefined') {
latestRequirement.verifyMethod = verifyMethod;
}
};
const addElement = name => {
if (typeof elements[name] === 'undefined') {
elements[name] = {
name,
type: latestElement.type,
docRef: latestElement.docRef
};
log.info('Added new requirement: ', name);
}
latestElement = {};
return elements[name];
};
const getElements = () => elements;
const setNewElementType = type => {
if (typeof latestElement != 'undefined') {
latestElement.type = type;
}
};
const setNewElementDocRef = docRef => {
if (typeof latestElement != 'undefined') {
latestElement.docRef = docRef;
}
};
const addRelationship = (type, src, dst) => {
relations.push({
type,
src,
dst
});
};
const getRelationships = () => relations;
const clear = () => {
relations = [];
latestRequirement = {};
requirements = {};
latestElement = {};
elements = {};
};
export default {
RequirementType,
RiskLevel,
VerifyType,
Relationships,
parseDirective,
getConfig: () => configApi.getConfig().req,
addRequirement,
getRequirements,
setNewReqId,
setNewReqText,
setNewReqRisk,
setNewReqVerifyMethod,
addElement,
getElements,
setNewElementType,
setNewElementDocRef,
addRelationship,
getRelationships,
clear
};

View File

@ -0,0 +1,69 @@
const ReqMarkers = {
CONTAINS: 'contains',
ARROW: 'arrow'
};
const insertLineEndings = (parentNode, conf) => {
let containsNode = parentNode
.append('defs')
.append('marker')
.attr('id', ReqMarkers.CONTAINS + '_line_ending')
.attr('refX', 0)
.attr('refY', conf.line_height / 2)
.attr('markerWidth', conf.line_height)
.attr('markerHeight', conf.line_height)
.attr('orient', 'auto')
.append('g');
containsNode
.append('circle')
.attr('cx', conf.line_height / 2)
.attr('cy', conf.line_height / 2)
.attr('r', conf.line_height / 2)
.attr('stroke', conf.rect_border_color)
.attr('stroke-width', 1)
.attr('fill', 'none');
containsNode
.append('line')
.attr('x1', 0)
.attr('x2', conf.line_height)
.attr('y1', conf.line_height / 2)
.attr('y2', conf.line_height / 2)
.attr('stroke', conf.rect_border_color)
.attr('stroke-width', 1);
containsNode
.append('line')
.attr('y1', 0)
.attr('y2', conf.line_height)
.attr('x1', conf.line_height / 2)
.attr('x2', conf.line_height / 2)
.attr('stroke', conf.rect_border_color)
.attr('stroke-width', 1);
parentNode
.append('defs')
.append('marker')
.attr('id', ReqMarkers.ARROW + '_line_ending')
.attr('refX', conf.line_height)
.attr('refY', 0.5 * conf.line_height)
.attr('markerWidth', conf.line_height)
.attr('markerHeight', conf.line_height)
.attr('orient', 'auto')
.append('path')
.attr(
'd',
`M0,0
L${conf.line_height},${conf.line_height / 2}
M${conf.line_height},${conf.line_height / 2}
L0,${conf.line_height}`
)
.attr('stroke-width', 1)
.attr('stroke', conf.rect_border_color);
};
export default {
ReqMarkers,
insertLineEndings
};

View File

@ -0,0 +1,374 @@
import { line, select } from 'd3';
import dagre from 'dagre';
import graphlib from 'graphlib';
import * as configApi from '../../config';
import { log } from '../../logger';
import { configureSvgSize } from '../../utils';
import common from '../common/common';
import { parser } from './parser/requirementDiagram';
import requirementDb from './requirementDb';
import { insertLineEndings, ReqMarkers } from './requirementMarkers';
const conf = {};
let relCnt = 0;
export const setConf = function(cnf) {
if (typeof cnf === 'undefined') {
return;
}
const keys = Object.keys(cnf);
for (let i = 0; i < keys.length; i++) {
conf[keys[i]] = cnf[keys[i]];
}
};
const newRectNode = (parentNode, id) => {
return parentNode
.insert('rect', '#' + id)
.attr('class', 'req reqBox')
.attr('fill', conf.rect_fill)
.attr('fill-opacity', '100%')
.attr('stroke', conf.rect_border_color)
.attr('stroke-size', conf.rect_border_size)
.attr('x', 0)
.attr('y', 0)
.attr('width', conf.rect_min_width + 'px')
.attr('height', conf.rect_min_height + 'px');
};
const newTitleNode = (parentNode, id, txts) => {
let x = conf.rect_min_width / 2;
let title = parentNode
.append('text')
.attr('class', 'req reqLabel reqTitle')
.attr('id', id)
.attr('x', x)
.attr('y', 0)
.attr('dominant-baseline', 'hanging')
.attr(
'style',
'font-family: ' + configApi.getConfig().fontFamily + '; font-size: ' + conf.fontSize + 'px'
);
let i = 0;
txts.forEach(textStr => {
if (i == 0) {
title
.append('tspan')
.attr('text-anchor', 'middle')
.attr('x', conf.rect_min_width / 2)
.attr('dy', conf.rect_padding)
.text(textStr);
} else {
title
.append('tspan')
.attr('text-anchor', 'middle')
.attr('x', conf.rect_min_width / 2)
.attr('dy', conf.line_height * 0.75)
.text(textStr);
}
i++;
});
let yPadding = 1.5 * conf.rect_padding;
let linePadding = i * conf.line_height * 0.75;
let totalY = yPadding + linePadding;
parentNode
.append('line')
.attr('x1', '0')
.attr('x2', conf.rect_min_width)
.attr('y1', totalY)
.attr('y2', totalY)
.attr('style', `stroke: ${conf.rect_border_color}; stroke-width: 1`);
return {
titleNode: title,
y: totalY
};
};
const newBodyNode = (parentNode, id, txts, yStart) => {
let body = parentNode
.append('text')
.attr('class', 'req reqLabel')
.attr('id', id)
.attr('x', conf.rect_padding)
.attr('y', yStart)
.attr('dominant-baseline', 'hanging')
.attr(
'style',
'font-family: ' + configApi.getConfig().fontFamily + '; font-size: ' + conf.fontSize + 'px'
);
let currentRow = 0;
const charLimit = 30;
let wrappedTxts = [];
txts.forEach(textStr => {
let currentTextLen = textStr.length;
while (currentTextLen > charLimit && currentRow < 3) {
let firstPart = textStr.substring(0, charLimit);
textStr = textStr.substring(charLimit, textStr.length);
currentTextLen = textStr.length;
wrappedTxts[wrappedTxts.length] = firstPart;
currentRow++;
}
if (currentRow == 3) {
let lastStr = wrappedTxts[wrappedTxts.length - 1];
wrappedTxts[wrappedTxts.length - 1] = lastStr.substring(0, lastStr.length - 4) + '...';
} else {
wrappedTxts[wrappedTxts.length] = textStr;
}
currentRow = 0;
});
wrappedTxts.forEach(textStr => {
body
.append('tspan')
.attr('x', conf.rect_padding)
.attr('dy', conf.line_height)
.text(textStr);
});
return body;
};
const addEdgeLabel = (parentNode, svgPath, conf, txt) => {
// Find the half-way point
const len = svgPath.node().getTotalLength();
const labelPoint = svgPath.node().getPointAtLength(len * 0.5);
// Append a text node containing the label
const labelId = 'rel' + relCnt;
relCnt++;
const labelNode = parentNode
.append('text')
.attr('class', 'er relationshipLabel')
.attr('id', labelId)
.attr('x', labelPoint.x)
.attr('y', labelPoint.y)
.attr('text-anchor', 'middle')
.attr('dominant-baseline', 'middle')
.attr('style', 'font-family: ' + conf.fontFamily + '; font-size: ' + conf.fontSize + 'px')
.text(txt);
// Figure out how big the opaque 'container' rectangle needs to be
const labelBBox = labelNode.node().getBBox();
// Insert the opaque rectangle before the text label
parentNode
.insert('rect', '#' + labelId)
.attr('class', 'req reqLabelBox')
.attr('x', labelPoint.x - labelBBox.width / 2)
.attr('y', labelPoint.y - labelBBox.height / 2)
.attr('width', labelBBox.width)
.attr('height', labelBBox.height)
.attr('fill', 'white')
.attr('fill-opacity', '85%');
};
const drawRelationshipFromLayout = function(svg, rel, g, insert) {
// Find the edge relating to this relationship
const edge = g.edge(rel.src, rel.dst);
// Get a function that will generate the line path
const lineFunction = line()
.x(function(d) {
return d.x;
})
.y(function(d) {
return d.y;
});
// Insert the line at the right place
const svgPath = svg
.insert('path', '#' + insert)
.attr('class', 'er relationshipLine')
.attr('d', lineFunction(edge.points))
.attr('stroke', conf.rect_border_color)
.attr('fill', 'none');
if (rel.type == requirementDb.Relationships.CONTAINS) {
svgPath.attr(
'marker-start',
'url(' + common.getUrl(conf.arrowMarkerAbsolute) + '#' + rel.type + '_line_ending' + ')'
);
} else {
svgPath.attr('stroke-dasharray', '10,7');
svgPath.attr(
'marker-end',
'url(' +
common.getUrl(conf.arrowMarkerAbsolute) +
'#' +
ReqMarkers.ARROW +
'_line_ending' +
')'
);
}
addEdgeLabel(svg, svgPath, conf, `<<${rel.type}>>`);
return;
};
export const drawReqs = (reqs, graph, svgNode) => {
Object.keys(reqs).forEach(reqName => {
let req = reqs[reqName];
const groupNode = svgNode.append('g').attr('id', reqName);
const textId = 'req-' + reqName;
const rectNode = newRectNode(groupNode, textId);
let nodes = [];
let titleNodeInfo = newTitleNode(groupNode, reqName + '_title', [
`<<${req.type}>>`,
`${req.name}`
]);
nodes.push(titleNodeInfo.titleNode);
let bodyNode = newBodyNode(
groupNode,
reqName + '_body',
[
`Id: ${req.id}`,
`Text: ${req.text}`,
`Risk: ${req.risk}`,
`Verification: ${req.verifyMethod}`
],
titleNodeInfo.y
);
nodes.push(bodyNode);
const rectBBox = rectNode.node().getBBox();
// Add the entity to the graph
graph.setNode(reqName, {
width: rectBBox.width,
height: rectBBox.height,
shape: 'rect',
id: reqName
});
});
};
export const drawElements = (els, graph, svgNode) => {
Object.keys(els).forEach(elName => {
let el = els[elName];
const id = elName.replace(/\./g, '_');
const groupNode = svgNode.append('g').attr('id', id);
const textId = 'element-' + id;
const rectNode = newRectNode(groupNode, textId);
let nodes = [];
let titleNodeInfo = newTitleNode(groupNode, textId + '_title', [`<<Element>>`, `${elName}`]);
nodes.push(titleNodeInfo.titleNode);
let bodyNode = newBodyNode(
groupNode,
textId + '_body',
[`Type: ${el.type || 'Not Specified'}`, `Doc Ref: ${el.docref || 'None'}`],
titleNodeInfo.y
);
nodes.push(bodyNode);
const rectBBox = rectNode.node().getBBox();
// Add the entity to the graph
graph.setNode(id, {
width: rectBBox.width,
height: rectBBox.height,
shape: 'rect',
id: id
});
});
};
const addRelationships = (relationships, g) => {
relationships.forEach(function(r) {
g.setEdge(r.src, r.dst, { relationship: r });
});
return relationships;
};
const adjustEntities = function(svgNode, graph) {
graph.nodes().forEach(function(v) {
if (typeof v !== 'undefined' && typeof graph.node(v) !== 'undefined') {
svgNode.select('#' + v);
svgNode
.select('#' + v)
.attr(
'transform',
'translate(' +
(graph.node(v).x - graph.node(v).width / 2) +
',' +
(graph.node(v).y - graph.node(v).height / 2) +
' )'
);
}
});
return;
};
export const draw = (text, id) => {
log.info('Drawing requirements!');
parser.yy = requirementDb;
parser.parse(text);
const svg = select(`[id='${id}']`);
insertLineEndings(svg, conf);
const g = new graphlib.Graph({
multigraph: false,
compound: false,
directed: true
})
.setGraph({
rankdir: conf.layoutDirection,
marginx: 20,
marginy: 20,
nodesep: 100,
edgesep: 100,
ranksep: 100
})
.setDefaultEdgeLabel(function() {
return {};
});
let requirements = requirementDb.getRequirements();
let elements = requirementDb.getElements();
let relationships = requirementDb.getRelationships();
drawReqs(requirements, g, svg);
drawElements(elements, g, svg);
addRelationships(relationships, g);
dagre.layout(g);
adjustEntities(svg, g);
relationships.forEach(function(rel) {
drawRelationshipFromLayout(svg, rel, g, id);
});
// svg.attr('height', '500px');
const padding = conf.rect_padding;
const svgBounds = svg.node().getBBox();
const width = svgBounds.width + padding * 2;
const height = svgBounds.height + padding * 2;
configureSvgSize(svg, height, width, conf.useMaxWidth);
svg.attr('viewBox', `${svgBounds.x - padding} ${svgBounds.y - padding} ${width} ${height}`);
};
export default {
setConf,
draw
};

View File

@ -0,0 +1,3 @@
const getStyles = () => ``;
export default getStyles;

View File

@ -13,9 +13,47 @@
*
* @name mermaidAPI
*/
import Stylis from 'stylis';
import { select } from 'd3';
import Stylis from 'stylis';
import pkg from '../package.json';
import * as configApi from './config';
import classDb from './diagrams/class/classDb';
import classRenderer from './diagrams/class/classRenderer';
import classRendererV2 from './diagrams/class/classRenderer-v2';
import classParser from './diagrams/class/parser/classDiagram';
import erDb from './diagrams/er/erDb';
import erRenderer from './diagrams/er/erRenderer';
import erParser from './diagrams/er/parser/erDiagram';
import flowDb from './diagrams/flowchart/flowDb';
import flowRenderer from './diagrams/flowchart/flowRenderer';
import flowRendererV2 from './diagrams/flowchart/flowRenderer-v2';
import flowParser from './diagrams/flowchart/parser/flow';
import ganttDb from './diagrams/gantt/ganttDb';
import ganttRenderer from './diagrams/gantt/ganttRenderer';
import ganttParser from './diagrams/gantt/parser/gantt';
import gitGraphAst from './diagrams/git/gitGraphAst';
import gitGraphRenderer from './diagrams/git/gitGraphRenderer';
import gitGraphParser from './diagrams/git/parser/gitGraph';
import infoDb from './diagrams/info/infoDb';
import infoRenderer from './diagrams/info/infoRenderer';
import infoParser from './diagrams/info/parser/info';
import pieParser from './diagrams/pie/parser/pie';
import pieDb from './diagrams/pie/pieDb';
import pieRenderer from './diagrams/pie/pieRenderer';
import requirementParser from './diagrams/requirement/parser/requirementDiagram';
import requirementDb from './diagrams/requirement/requirementDb';
import requirementRenderer from './diagrams/requirement/requirementRenderer';
import sequenceParser from './diagrams/sequence/parser/sequenceDiagram';
import sequenceDb from './diagrams/sequence/sequenceDb';
import sequenceRenderer from './diagrams/sequence/sequenceRenderer';
import stateParser from './diagrams/state/parser/stateDiagram';
import stateDb from './diagrams/state/stateDb';
import stateRenderer from './diagrams/state/stateRenderer';
import stateRendererV2 from './diagrams/state/stateRenderer-v2';
import journeyDb from './diagrams/user-journey/journeyDb';
import journeyRenderer from './diagrams/user-journey/journeyRenderer';
import journeyParser from './diagrams/user-journey/parser/journey';
import errorRenderer from './errorRenderer';
// import * as configApi from './config';
// // , {
// // setConfig,
@ -26,44 +64,9 @@ import pkg from '../package.json';
// // configApi.defaultConfig
// // }
import { log, setLogLevel } from './logger';
import utils, { assignWithDepth } from './utils';
import flowRenderer from './diagrams/flowchart/flowRenderer';
import flowRendererV2 from './diagrams/flowchart/flowRenderer-v2';
import flowParser from './diagrams/flowchart/parser/flow';
import flowDb from './diagrams/flowchart/flowDb';
import sequenceRenderer from './diagrams/sequence/sequenceRenderer';
import sequenceParser from './diagrams/sequence/parser/sequenceDiagram';
import sequenceDb from './diagrams/sequence/sequenceDb';
import ganttRenderer from './diagrams/gantt/ganttRenderer';
import ganttParser from './diagrams/gantt/parser/gantt';
import ganttDb from './diagrams/gantt/ganttDb';
import classRenderer from './diagrams/class/classRenderer';
import classRendererV2 from './diagrams/class/classRenderer-v2';
import classParser from './diagrams/class/parser/classDiagram';
import classDb from './diagrams/class/classDb';
import stateRenderer from './diagrams/state/stateRenderer';
import stateRendererV2 from './diagrams/state/stateRenderer-v2';
import stateParser from './diagrams/state/parser/stateDiagram';
import stateDb from './diagrams/state/stateDb';
import gitGraphRenderer from './diagrams/git/gitGraphRenderer';
import gitGraphParser from './diagrams/git/parser/gitGraph';
import gitGraphAst from './diagrams/git/gitGraphAst';
import infoRenderer from './diagrams/info/infoRenderer';
import errorRenderer from './errorRenderer';
import infoParser from './diagrams/info/parser/info';
import infoDb from './diagrams/info/infoDb';
import pieRenderer from './diagrams/pie/pieRenderer';
import pieParser from './diagrams/pie/parser/pie';
import pieDb from './diagrams/pie/pieDb';
import erDb from './diagrams/er/erDb';
import erParser from './diagrams/er/parser/erDiagram';
import erRenderer from './diagrams/er/erRenderer';
import journeyParser from './diagrams/user-journey/parser/journey';
import journeyDb from './diagrams/user-journey/journeyDb';
import journeyRenderer from './diagrams/user-journey/journeyRenderer';
import * as configApi from './config';
import getStyles from './styles';
import theme from './themes';
import utils, { assignWithDepth } from './utils';
function parse(text) {
const graphInit = utils.detectInit(text);
@ -134,6 +137,13 @@ function parse(text) {
parser = journeyParser;
parser.parser.yy = journeyDb;
break;
case 'requirement':
case 'requirementDiagram':
console.log('RequirementDiagram');
log.debug('RequirementDiagram');
parser = requirementParser;
parser.parser.yy = requirementDb;
break;
}
parser.parser.yy.graphType = graphType;
parser.parser.yy.parseError = (str, hash) => {
@ -393,6 +403,10 @@ const render = function(id, _txt, cb, container) {
journeyRenderer.setConf(cnf.journey);
journeyRenderer.draw(txt, id, pkg.version);
break;
case 'requirement':
requirementRenderer.setConf(cnf.requirement);
requirementRenderer.draw(txt, id, pkg.version);
break;
}
} catch (e) {
// errorRenderer.setConf(cnf.class);
@ -539,6 +553,7 @@ function updateRendererConfigs(conf) {
pieRenderer.setConf(conf.class);
erRenderer.setConf(conf.er);
journeyRenderer.setConf(conf.journey);
requirementRenderer.setConf(conf.requirement);
errorRenderer.setConf(conf.class);
}

View File

@ -5,6 +5,7 @@ import gantt from './diagrams/gantt/styles';
import git from './diagrams/git/styles';
import info from './diagrams/info/styles';
import pie from './diagrams/pie/styles';
import requirement from './diagrams/requirement/styles';
import sequence from './diagrams/sequence/styles';
import stateDiagram from './diagrams/state/styles';
import journey from './diagrams/user-journey/styles';
@ -23,7 +24,8 @@ const themes = {
info,
pie,
er,
journey
journey,
requirement
};
export const calcThemeVariables = (theme, userOverRides) => theme.calcColors(userOverRides);

View File

@ -147,9 +147,8 @@ export const detectDirective = function(text, type = null) {
return result.length === 1 ? result[0] : result;
} catch (error) {
log.error(
`ERROR: ${error.message} - Unable to parse directive${
type !== null ? ' type:' + type : ''
} based on the text:${text}`
`ERROR: ${error.message} - Unable to parse directive
${type !== null ? ' type:' + type : ''} based on the text:${text}`
);
return { type: null, args: null };
}
@ -221,6 +220,10 @@ export const detectType = function(text) {
return 'journey';
}
if (text.match(/^\s*requirement/) || text.match(/^\s*requirementDiagram/)) {
return 'requirement';
}
return 'flowchart';
};