diff --git a/extension/src-language-server/stpa/diagram/diagram-controlStructure.ts b/extension/src-language-server/stpa/diagram/diagram-controlStructure.ts
index 91407a4..1b94b2f 100644
--- a/extension/src-language-server/stpa/diagram/diagram-controlStructure.ts
+++ b/extension/src-language-server/stpa/diagram/diagram-controlStructure.ts
@@ -264,21 +264,29 @@ export function translateCommandsToEdges(
// add missing feedback edges
if (addMissing && missingFeedback) {
// add feedback edge to each node to which a feedback is missing
+ let hasMissingFeedback = false;
for (const target of missingFeedback.get(source?.name ?? "") ?? []) {
if (source && target) {
+ hasMissingFeedback = true;
createEdgeForCommand(
source,
target,
idCache.uniqueId(`${source?.name}_missingFeedback_${target}`),
- EdgeType.FEEDBACK,
- // TODO: mark edge as missing to render it red
- ["MISSING FEEDBACK"],
+ EdgeType.MISSING_FEEDBACK,
+ ["MISSING"],
idToSNode,
idCache,
edges
);
}
}
+ // set flag for missing feedback to the source node
+ if (hasMissingFeedback) {
+ const sourceNode = idToSNode.get(idCache.getId(source) ?? "");
+ if (sourceNode && sourceNode.type === CS_NODE_TYPE) {
+ (sourceNode as CSNode).hasMissingFeedback = true;
+ }
+ }
}
return edges;
diff --git a/extension/src-language-server/stpa/diagram/stpa-interfaces.ts b/extension/src-language-server/stpa/diagram/stpa-interfaces.ts
index 6408a5d..d636190 100644
--- a/extension/src-language-server/stpa/diagram/stpa-interfaces.ts
+++ b/extension/src-language-server/stpa/diagram/stpa-interfaces.ts
@@ -55,6 +55,7 @@ export interface PastaPort extends SPort {
*/
export interface CSNode extends SNode {
level?: number;
+ hasMissingFeedback?: boolean;
}
/**
diff --git a/extension/src-language-server/stpa/diagram/stpa-model.ts b/extension/src-language-server/stpa/diagram/stpa-model.ts
index d541352..286688e 100644
--- a/extension/src-language-server/stpa/diagram/stpa-model.ts
+++ b/extension/src-language-server/stpa/diagram/stpa-model.ts
@@ -52,6 +52,7 @@ export enum STPAAspect {
export enum EdgeType {
CONTROL_ACTION,
FEEDBACK,
+ MISSING_FEEDBACK,
INPUT,
OUTPUT,
UNDEFINED
diff --git a/extension/src-webview/css/stpa-diagram.css b/extension/src-webview/css/stpa-diagram.css
index 6512724..623b865 100644
--- a/extension/src-webview/css/stpa-diagram.css
+++ b/extension/src-webview/css/stpa-diagram.css
@@ -19,7 +19,27 @@
.stpa-node {
stroke: black;
-}
+}
+
+/* red highlighting for visualization of missing feedback edges */
+.missing-feedback-label {
+ fill: red !important;
+}
+
+.missing-feedback-node {
+ stroke: red !important;
+ stroke-width: 3 !important;
+}
+
+.missing-edge {
+ stroke: red !important;
+ stroke-width: 3 !important;
+}
+
+.missing-edge-arrow {
+ stroke: red !important;
+ fill: red !important;
+}
/* Feedback edges */
.feedback-edge {
diff --git a/extension/src-webview/di.config.ts b/extension/src-webview/di.config.ts
index c274d38..ec783bd 100644
--- a/extension/src-webview/di.config.ts
+++ b/extension/src-webview/di.config.ts
@@ -95,6 +95,7 @@ import {
PolylineArrowEdgeView,
PortView,
STPAGraphView,
+ PastaLabelView,
STPANodeView,
} from "./stpa/stpa-views";
import { snippetModule } from './snippets/snippet-module';
@@ -119,8 +120,8 @@ const pastaDiagramModule = new ContainerModule((bind, unbind, isBound, rebind) =
// configure the diagram elements
const context = { bind, unbind, isBound, rebind };
- configureModelElement(context, "label", SLabel, SLabelView);
- configureModelElement(context, "label:xref", SLabel, SLabelView);
+ configureModelElement(context, "label", SLabel, PastaLabelView);
+ configureModelElement(context, "label:xref", SLabel, PastaLabelView);
configureModelElement(context, HEADER_LABEL_TYPE, SLabel, HeaderLabelView);
configureModelElement(context, "html", HtmlRoot, HtmlRootView);
configureModelElement(context, "pre-rendered", PreRenderedElement, PreRenderedView);
diff --git a/extension/src-webview/stpa/stpa-model.ts b/extension/src-webview/stpa/stpa-model.ts
index 566e6f6..414f214 100644
--- a/extension/src-webview/stpa/stpa-model.ts
+++ b/extension/src-webview/stpa/stpa-model.ts
@@ -72,6 +72,7 @@ export class PastaPort extends SPort {
*/
export class CSNode extends SNode {
level?: number;
+ hasMissingFeedback?: boolean;
static readonly DEFAULT_FEATURES = [connectableFeature, selectFeature, layoutContainerFeature, fadeFeature];
}
@@ -104,6 +105,7 @@ export enum STPAAspect {
export enum EdgeType {
CONTROL_ACTION,
FEEDBACK,
+ MISSING_FEEDBACK,
INPUT,
OUTPUT,
UNDEFINED,
diff --git a/extension/src-webview/stpa/stpa-views.tsx b/extension/src-webview/stpa/stpa-views.tsx
index 3155441..b07b72b 100644
--- a/extension/src-webview/stpa/stpa-views.tsx
+++ b/extension/src-webview/stpa/stpa-views.tsx
@@ -24,7 +24,7 @@ import { ColorStyleOption, DifferentFormsOption, RenderOptionsRegistry } from '.
import { SendModelRendererAction } from '../snippets/actions';
import { renderDiamond, renderHexagon, renderMirroredTriangle, renderOval, renderPentagon, renderRectangle, renderRoundedRectangle, renderTrapez, renderTriangle } from '../views-rendering';
import { collectAllChildren } from './helper-methods';
-import { CSEdge, CS_EDGE_TYPE, CS_INTERMEDIATE_EDGE_TYPE, EdgeType, STPAAspect, STPAEdge, STPANode, STPA_EDGE_TYPE, STPA_INTERMEDIATE_EDGE_TYPE } from './stpa-model';
+import { CSEdge, CSNode, CS_EDGE_TYPE, CS_INTERMEDIATE_EDGE_TYPE, CS_NODE_TYPE, EdgeType, STPAAspect, STPAEdge, STPANode, STPA_EDGE_TYPE, STPA_INTERMEDIATE_EDGE_TYPE } from './stpa-model';
/** Determines if path/aspect highlighting is currently on. */
let highlighting: boolean;
@@ -46,6 +46,8 @@ export class PolylineArrowEdgeView extends PolylineEdgeView {
const hidden = (edge.type === STPA_EDGE_TYPE || edge.type === STPA_INTERMEDIATE_EDGE_TYPE) && highlighting && !(edge as STPAEdge).highlight;
// feedback edges in the control structure should be dashed
const feedbackEdge = (edge.type === CS_EDGE_TYPE || edge.type === CS_INTERMEDIATE_EDGE_TYPE) && (edge as CSEdge).edgeType === EdgeType.FEEDBACK;
+ // edges that represent missing edges should be highlighted
+ const missing = (edge.type === CS_EDGE_TYPE || edge.type === CS_INTERMEDIATE_EDGE_TYPE) && (edge as CSEdge).edgeType === EdgeType.MISSING_FEEDBACK;
const colorStyle = this.renderOptionsRegistry.getValue(ColorStyleOption);
const printEdge = colorStyle === "black & white";
@@ -57,7 +59,7 @@ export class PolylineArrowEdgeView extends PolylineEdgeView {
aspect = (edge as STPAEdge).aspect % 2 === 0 || !lessColoredEdge ? (edge as STPAEdge).aspect : (edge as STPAEdge).aspect - 1;
}
return ;
+ class-feedback-edge={feedbackEdge} class-missing-edge={missing} class-greyed-out={hidden} aspect={aspect} d={path} />;
}
protected renderAdditionals(edge: SEdge, segments: Point[], context: RenderingContext): VNode[] {
@@ -76,8 +78,11 @@ export class PolylineArrowEdgeView extends PolylineEdgeView {
if (edge.type === STPA_EDGE_TYPE || edge.type === STPA_INTERMEDIATE_EDGE_TYPE) {
aspect = (edge as STPAEdge).aspect % 2 === 0 || !lessColoredEdge ? (edge as STPAEdge).aspect : (edge as STPAEdge).aspect - 1;
}
+ // edges that represent missing edges should be highlighted
+ const missing = (edge.type === CS_EDGE_TYPE || edge.type === CS_INTERMEDIATE_EDGE_TYPE) && (edge as CSEdge).edgeType === EdgeType.MISSING_FEEDBACK;
+
return [
-
];
@@ -208,8 +213,10 @@ export class CSNodeView extends RectangularNodeView {
const colorStyle = this.renderOptionsRegistry.getValue(ColorStyleOption);
const sprottyNode = colorStyle === "standard";
const printNode = !sprottyNode;
+ const missingFeedback = node.type === CS_NODE_TYPE && (node as CSNode).hasMissingFeedback;
return
- , context: RenderingContext): VNode | undefined {
return
{super.render(label, context)}
-
+ ;
}
}
+
+@injectable()
+export class PastaLabelView extends SLabelView {
+ render(label: Readonly, context: RenderingContext): VNode | undefined {
+ // label belongs to a node which may have missing feedback
+ const nodeMissingFeedback = label.parent.type === CS_NODE_TYPE && (label.parent as CSNode).hasMissingFeedback;
+ // label belongs to an edge which may be a missing feedback edge
+ const edgeMissingFeedback = (label.parent.type === CS_EDGE_TYPE || label.parent.type === CS_INTERMEDIATE_EDGE_TYPE) && (label.parent as CSEdge).edgeType === EdgeType.MISSING_FEEDBACK;
+ const missingFeedbackLabel = nodeMissingFeedback || edgeMissingFeedback;
+
+ const vnode = super.render(label, context);
+ if (vnode?.data?.class) {
+ vnode.data.class['missing-feedback-label'] = missingFeedbackLabel ?? false;
+ }
+
+ return vnode;
+ }
+}
\ No newline at end of file