Update requirementDb to class to encapsulate data

This commit is contained in:
yari-dewalt
2025-01-27 09:21:34 -08:00
parent 97788df7e3
commit 9609aced14
2 changed files with 298 additions and 288 deletions

View File

@@ -1,4 +1,5 @@
import { getConfig } from '../../diagram-api/diagramAPI.js'; import { getConfig } from '../../diagram-api/diagramAPI.js';
import type { DiagramDB } from '../../diagram-api/types.js';
import { log } from '../../logger.js'; import { log } from '../../logger.js';
import type { Node, Edge } from '../../rendering-util/types.js'; import type { Node, Edge } from '../../rendering-util/types.js';
@@ -22,320 +23,327 @@ import type {
VerifyType, VerifyType,
} from './types.js'; } from './types.js';
const RequirementType = { export class RequirementDB implements DiagramDB {
REQUIREMENT: 'Requirement', private relations: Relation[] = [];
FUNCTIONAL_REQUIREMENT: 'Functional Requirement', private latestRequirement: Requirement = this.getInitialRequirement();
INTERFACE_REQUIREMENT: 'Interface Requirement', private requirements = new Map<string, Requirement>();
PERFORMANCE_REQUIREMENT: 'Performance Requirement', private latestElement: Element = this.getInitialElement();
PHYSICAL_REQUIREMENT: 'Physical Requirement', private elements = new Map<string, Element>();
DESIGN_CONSTRAINT: 'Design Constraint', private classes = new Map<string, RequirementClass>();
}; private direction = 'TB';
const RiskLevel = { private RequirementType = {
LOW_RISK: 'Low', REQUIREMENT: 'Requirement',
MED_RISK: 'Medium', FUNCTIONAL_REQUIREMENT: 'Functional Requirement',
HIGH_RISK: 'High', INTERFACE_REQUIREMENT: 'Interface Requirement',
}; PERFORMANCE_REQUIREMENT: 'Performance Requirement',
PHYSICAL_REQUIREMENT: 'Physical Requirement',
DESIGN_CONSTRAINT: 'Design Constraint',
};
const VerifyType = { private RiskLevel = {
VERIFY_ANALYSIS: 'Analysis', LOW_RISK: 'Low',
VERIFY_DEMONSTRATION: 'Demonstration', MED_RISK: 'Medium',
VERIFY_INSPECTION: 'Inspection', HIGH_RISK: 'High',
VERIFY_TEST: 'Test', };
};
const Relationships = { private VerifyType = {
CONTAINS: 'contains', VERIFY_ANALYSIS: 'Analysis',
COPIES: 'copies', VERIFY_DEMONSTRATION: 'Demonstration',
DERIVES: 'derives', VERIFY_INSPECTION: 'Inspection',
SATISFIES: 'satisfies', VERIFY_TEST: 'Test',
VERIFIES: 'verifies', };
REFINES: 'refines',
TRACES: 'traces',
};
let direction = 'TB'; private Relationships = {
const getDirection = () => direction; CONTAINS: 'contains',
const setDirection = (dir: string) => { COPIES: 'copies',
direction = dir; DERIVES: 'derives',
}; SATISFIES: 'satisfies',
VERIFIES: 'verifies',
REFINES: 'refines',
TRACES: 'traces',
};
const getInitialRequirement = (): Requirement => ({ constructor() {
requirementId: '', this.clear();
text: '',
risk: '' as RiskLevel,
verifyMethod: '' as VerifyType,
name: '',
type: '' as RequirementType,
cssStyles: [],
classes: ['default'],
});
const getInitialElement = (): Element => ({ // Needed for JISON since it only supports direct properties
name: '', this.setDirection = this.setDirection.bind(this);
type: '', this.addRequirement = this.addRequirement.bind(this);
docRef: '', this.setNewReqId = this.setNewReqId.bind(this);
cssStyles: [], this.setNewReqRisk = this.setNewReqRisk.bind(this);
classes: ['default'], this.setNewReqText = this.setNewReqText.bind(this);
}); this.setNewReqVerifyMethod = this.setNewReqVerifyMethod.bind(this);
this.addElement = this.addElement.bind(this);
this.setNewElementType = this.setNewElementType.bind(this);
this.setNewElementDocRef = this.setNewElementDocRef.bind(this);
this.addRelationship = this.addRelationship.bind(this);
this.setCssStyle = this.setCssStyle.bind(this);
this.setClass = this.setClass.bind(this);
this.defineClass = this.defineClass.bind(this);
this.setAccTitle = this.setAccTitle.bind(this);
this.setAccDescription = this.setAccDescription.bind(this);
}
// Update initial declarations public getDirection() {
let relations: Relation[] = []; return this.direction;
let latestRequirement: Requirement = getInitialRequirement(); }
let requirements = new Map<string, Requirement>(); public setDirection(dir: string) {
let latestElement: Element = getInitialElement(); this.direction = dir;
let elements = new Map<string, Element>(); }
let classes = new Map<string, RequirementClass>();
// Add reset functions private resetLatestRequirement() {
const resetLatestRequirement = () => { this.latestRequirement = this.getInitialRequirement();
latestRequirement = getInitialRequirement(); }
};
const resetLatestElement = () => { private resetLatestElement() {
latestElement = getInitialElement(); this.latestElement = this.getInitialElement();
}; }
const addRequirement = (name: string, type: RequirementType) => { private getInitialRequirement(): Requirement {
if (!requirements.has(name)) { return {
requirements.set(name, { requirementId: '',
name, text: '',
type, risk: '' as RiskLevel,
requirementId: latestRequirement.requirementId, verifyMethod: '' as VerifyType,
text: latestRequirement.text, name: '',
risk: latestRequirement.risk, type: '' as RequirementType,
verifyMethod: latestRequirement.verifyMethod,
cssStyles: [], cssStyles: [],
classes: ['default'], classes: ['default'],
}); };
} }
resetLatestRequirement();
return requirements.get(name); private getInitialElement(): Element {
}; return {
name: '',
const getRequirements = () => requirements; type: '',
docRef: '',
const setNewReqId = (id: string) => {
if (latestRequirement !== undefined) {
latestRequirement.requirementId = id;
}
};
const setNewReqText = (text: string) => {
if (latestRequirement !== undefined) {
latestRequirement.text = text;
}
};
const setNewReqRisk = (risk: RiskLevel) => {
if (latestRequirement !== undefined) {
latestRequirement.risk = risk;
}
};
const setNewReqVerifyMethod = (verifyMethod: VerifyType) => {
if (latestRequirement !== undefined) {
latestRequirement.verifyMethod = verifyMethod;
}
};
const addElement = (name: string) => {
if (!elements.has(name)) {
elements.set(name, {
name,
type: latestElement.type,
docRef: latestElement.docRef,
cssStyles: [], cssStyles: [],
classes: ['default'], classes: ['default'],
}); };
log.info('Added new element: ', name);
} }
resetLatestElement();
return elements.get(name); public addRequirement(name: string, type: RequirementType) {
}; if (!this.requirements.has(name)) {
this.requirements.set(name, {
const getElements = () => elements; name,
type,
const setNewElementType = (type: string) => { requirementId: this.latestRequirement.requirementId,
if (latestElement !== undefined) { text: this.latestRequirement.text,
latestElement.type = type; risk: this.latestRequirement.risk,
} verifyMethod: this.latestRequirement.verifyMethod,
}; cssStyles: [],
classes: ['default'],
const setNewElementDocRef = (docRef: string) => {
if (latestElement !== undefined) {
latestElement.docRef = docRef;
}
};
const addRelationship = (type: RelationshipType, src: string, dst: string) => {
relations.push({
type,
src,
dst,
});
};
const getRelationships = () => relations;
const clear = () => {
relations = [];
resetLatestRequirement();
requirements = new Map();
resetLatestElement();
elements = new Map();
classes = new Map();
commonClear();
};
export const setCssStyle = function (ids: string[], styles: string[]) {
for (const id of ids) {
const node = requirements.get(id) ?? elements.get(id);
if (!styles || !node) {
return;
}
for (const s of styles) {
if (s.includes(',')) {
node.cssStyles.push(...s.split(','));
} else {
node.cssStyles.push(s);
}
}
}
};
export const setClass = function (ids: string[], classNames: string[]) {
for (const id of ids) {
const node = requirements.get(id) ?? elements.get(id);
if (node) {
for (const _class of classNames) {
node.classes.push(_class);
const styles = classes.get(_class)?.styles;
if (styles) {
node.cssStyles.push(...styles);
}
}
}
}
};
export const defineClass = function (ids: string[], style: string[]) {
for (const id of ids) {
let styleClass = classes.get(id);
if (styleClass === undefined) {
styleClass = { id, styles: [], textStyles: [] };
classes.set(id, styleClass);
}
if (style) {
style.forEach(function (s) {
if (/color/.exec(s)) {
const newStyle = s.replace('fill', 'bgFill'); // .replace('color', 'fill');
styleClass.textStyles.push(newStyle);
}
styleClass.styles.push(s);
}); });
} }
this.resetLatestRequirement();
requirements.forEach((value) => { return this.requirements.get(name);
if (value.classes.includes(id)) { }
value.cssStyles.push(...style.flatMap((s) => s.split(',')));
} public getRequirements() {
}); return this.requirements;
elements.forEach((value) => { }
if (value.classes.includes(id)) {
value.cssStyles.push(...style.flatMap((s) => s.split(','))); public setNewReqId(id: string) {
} if (this.latestRequirement !== undefined) {
this.latestRequirement.requirementId = id;
}
}
public setNewReqText(text: string) {
if (this.latestRequirement !== undefined) {
this.latestRequirement.text = text;
}
}
public setNewReqRisk(risk: RiskLevel) {
if (this.latestRequirement !== undefined) {
this.latestRequirement.risk = risk;
}
}
public setNewReqVerifyMethod(verifyMethod: VerifyType) {
if (this.latestRequirement !== undefined) {
this.latestRequirement.verifyMethod = verifyMethod;
}
}
public addElement(name: string) {
if (!this.elements.has(name)) {
this.elements.set(name, {
name,
type: this.latestElement.type,
docRef: this.latestElement.docRef,
cssStyles: [],
classes: ['default'],
});
log.info('Added new element: ', name);
}
this.resetLatestElement();
return this.elements.get(name);
}
public getElements() {
return this.elements;
}
public setNewElementType(type: string) {
if (this.latestElement !== undefined) {
this.latestElement.type = type;
}
}
public setNewElementDocRef(docRef: string) {
if (this.latestElement !== undefined) {
this.latestElement.docRef = docRef;
}
}
public addRelationship(type: RelationshipType, src: string, dst: string) {
this.relations.push({
type,
src,
dst,
}); });
} }
};
export const getClasses = () => { public getRelationships() {
return classes; return this.relations;
};
const getData = () => {
const config = getConfig();
const nodes: Node[] = [];
const edges: Edge[] = [];
for (const requirement of requirements.values()) {
const node = requirement as unknown as Node;
node.id = requirement.name;
node.cssStyles = requirement.cssStyles;
node.cssClasses = requirement.classes.join(' ');
node.shape = 'requirementBox';
node.look = config.look;
nodes.push(node);
} }
for (const element of elements.values()) { public clear() {
const node = element as unknown as Node; this.relations = [];
node.shape = 'requirementBox'; this.resetLatestRequirement();
node.look = config.look; this.requirements = new Map();
node.id = element.name; this.resetLatestElement();
node.cssStyles = element.cssStyles; this.elements = new Map();
node.cssClasses = element.classes.join(' '); this.classes = new Map();
commonClear();
nodes.push(node);
} }
for (const relation of relations) { public setCssStyle(ids: string[], styles: string[]) {
let counter = 0; for (const id of ids) {
const isContains = relation.type === Relationships.CONTAINS; const node = this.requirements.get(id) ?? this.elements.get(id);
const edge: Edge = { if (!styles || !node) {
id: `${relation.src}-${relation.dst}-${counter}`, return;
start: requirements.get(relation.src)?.name ?? elements.get(relation.src)?.name, }
end: requirements.get(relation.dst)?.name ?? elements.get(relation.dst)?.name, for (const s of styles) {
label: `&lt;&lt;${relation.type}&gt;&gt;`, if (s.includes(',')) {
classes: 'relationshipLine', node.cssStyles.push(...s.split(','));
style: ['fill:none', isContains ? '' : 'stroke-dasharray: 10,7'], } else {
labelpos: 'c', node.cssStyles.push(s);
thickness: 'normal', }
type: 'normal', }
pattern: isContains ? 'normal' : 'dashed', }
arrowTypeEnd: isContains ? 'requirement_contains' : 'requirement_arrow',
look: config.look,
};
edges.push(edge);
counter++;
} }
return { nodes, edges, other: {}, config, direction: getDirection() }; public setClass(ids: string[], classNames: string[]) {
}; for (const id of ids) {
const node = this.requirements.get(id) ?? this.elements.get(id);
if (node) {
for (const _class of classNames) {
node.classes.push(_class);
const styles = this.classes.get(_class)?.styles;
if (styles) {
node.cssStyles.push(...styles);
}
}
}
}
}
export default { public defineClass(ids: string[], style: string[]) {
Relationships, for (const id of ids) {
RequirementType, let styleClass = this.classes.get(id);
RiskLevel, if (styleClass === undefined) {
VerifyType, styleClass = { id, styles: [], textStyles: [] };
getConfig: () => getConfig().requirement, this.classes.set(id, styleClass);
addRequirement, }
getRequirements,
setNewReqId, if (style) {
setNewReqText, style.forEach(function (s) {
setNewReqRisk, if (/color/.exec(s)) {
setNewReqVerifyMethod, const newStyle = s.replace('fill', 'bgFill'); // .replace('color', 'fill');
setAccTitle, styleClass.textStyles.push(newStyle);
getAccTitle, }
setAccDescription, styleClass.styles.push(s);
getAccDescription, });
setDiagramTitle, }
getDiagramTitle,
getDirection, this.requirements.forEach((value) => {
setDirection, if (value.classes.includes(id)) {
addElement, value.cssStyles.push(...style.flatMap((s) => s.split(',')));
getElements, }
setNewElementType, });
setNewElementDocRef, this.elements.forEach((value) => {
addRelationship, if (value.classes.includes(id)) {
getRelationships, value.cssStyles.push(...style.flatMap((s) => s.split(',')));
clear, }
setCssStyle, });
setClass, }
defineClass, }
getClasses,
getData, public getClasses() {
}; return this.classes;
}
public getData() {
const config = getConfig();
const nodes: Node[] = [];
const edges: Edge[] = [];
for (const requirement of this.requirements.values()) {
const node = requirement as unknown as Node;
node.id = requirement.name;
node.cssStyles = requirement.cssStyles;
node.cssClasses = requirement.classes.join(' ');
node.shape = 'requirementBox';
node.look = config.look;
nodes.push(node);
}
for (const element of this.elements.values()) {
const node = element as unknown as Node;
node.shape = 'requirementBox';
node.look = config.look;
node.id = element.name;
node.cssStyles = element.cssStyles;
node.cssClasses = element.classes.join(' ');
nodes.push(node);
}
for (const relation of this.relations) {
let counter = 0;
const isContains = relation.type === this.Relationships.CONTAINS;
const edge: Edge = {
id: `${relation.src}-${relation.dst}-${counter}`,
start: this.requirements.get(relation.src)?.name ?? this.elements.get(relation.src)?.name,
end: this.requirements.get(relation.dst)?.name ?? this.elements.get(relation.dst)?.name,
label: `&lt;&lt;${relation.type}&gt;&gt;`,
classes: 'relationshipLine',
style: ['fill:none', isContains ? '' : 'stroke-dasharray: 10,7'],
labelpos: 'c',
thickness: 'normal',
type: 'normal',
pattern: isContains ? 'normal' : 'dashed',
arrowTypeEnd: isContains ? 'requirement_contains' : 'requirement_arrow',
look: config.look,
};
edges.push(edge);
counter++;
}
return { nodes, edges, other: {}, config, direction: this.getDirection() };
}
public setAccTitle = setAccTitle;
public getAccTitle = getAccTitle;
public setAccDescription = setAccDescription;
public getAccDescription = getAccDescription;
public setDiagramTitle = setDiagramTitle;
public getDiagramTitle = getDiagramTitle;
public getConfig = () => getConfig().class;
}

View File

@@ -1,13 +1,15 @@
import type { DiagramDefinition } from '../../diagram-api/types.js'; import type { DiagramDefinition } from '../../diagram-api/types.js';
// @ts-ignore: JISON doesn't support types // @ts-ignore: JISON doesn't support types
import parser from './parser/requirementDiagram.jison'; import parser from './parser/requirementDiagram.jison';
import db from './requirementDb.js'; import { RequirementDB } from './requirementDb.js';
import styles from './styles.js'; import styles from './styles.js';
import renderer from './requirementRenderer.js'; import renderer from './requirementRenderer.js';
export const diagram: DiagramDefinition = { export const diagram: DiagramDefinition = {
parser, parser,
db, get db() {
return new RequirementDB();
},
renderer, renderer,
styles, styles,
}; };