v2.2 extended analysis render update
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -0,0 +1,386 @@
|
||||
{
|
||||
"metadata" : {
|
||||
"triggers" : [ {
|
||||
"event" : "AUDIT_EVENT",
|
||||
"className" : "click.kamil.examples.enterprise.api.SecurityInterceptor",
|
||||
"methodName" : "preHandle",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"lineNumber" : 17
|
||||
}, {
|
||||
"event" : "PLACE_ORDER",
|
||||
"className" : "click.kamil.examples.enterprise.service.OrderServiceImpl",
|
||||
"methodName" : "place",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"lineNumber" : 16
|
||||
}, {
|
||||
"event" : "CANCEL_ORDER",
|
||||
"className" : "click.kamil.examples.enterprise.service.OrderServiceImpl",
|
||||
"methodName" : "cancel",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"lineNumber" : 21
|
||||
}, {
|
||||
"event" : "PAY_ORDER",
|
||||
"className" : "click.kamil.examples.enterprise.service.ReactivePaymentService",
|
||||
"methodName" : "processPayment",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"lineNumber" : 18
|
||||
}, {
|
||||
"event" : "SHIP_ORDER",
|
||||
"className" : "click.kamil.examples.enterprise.messaging.ShippingJmsListener",
|
||||
"methodName" : "onShippingReady",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"lineNumber" : 17
|
||||
}, {
|
||||
"event" : "RETURN_ORDER",
|
||||
"className" : "click.kamil.examples.enterprise.messaging.ReturnsRabbitListener",
|
||||
"methodName" : "onReturn",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"lineNumber" : 17
|
||||
} ],
|
||||
"entryPoints" : [ {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/enterprise/orders/place",
|
||||
"className" : "click.kamil.examples.enterprise.api.OrderController",
|
||||
"methodName" : "placeOrder",
|
||||
"metadata" : {
|
||||
"path" : "/api/enterprise/orders/place",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ ]
|
||||
}, {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/enterprise/orders/{id}/cancel",
|
||||
"className" : "click.kamil.examples.enterprise.api.OrderController",
|
||||
"methodName" : "cancelOrder",
|
||||
"metadata" : {
|
||||
"path" : "/api/enterprise/orders/{id}/cancel",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "id",
|
||||
"type" : "String",
|
||||
"annotations" : [ "PathVariable" ]
|
||||
} ]
|
||||
}, {
|
||||
"type" : "CUSTOM",
|
||||
"name" : "INTERCEPTOR: SecurityInterceptor.preHandle",
|
||||
"className" : "click.kamil.examples.enterprise.api.SecurityInterceptor",
|
||||
"methodName" : "preHandle",
|
||||
"metadata" : {
|
||||
"interceptorType" : "Spring MVC Interceptor"
|
||||
},
|
||||
"parameters" : [ ]
|
||||
}, {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/enterprise/payments",
|
||||
"className" : "click.kamil.examples.enterprise.api.ReactivePaymentController",
|
||||
"methodName" : "pay",
|
||||
"metadata" : {
|
||||
"path" : "/api/enterprise/payments",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "orderId",
|
||||
"type" : "String",
|
||||
"annotations" : [ "RequestBody" ]
|
||||
} ]
|
||||
}, {
|
||||
"type" : "JMS",
|
||||
"name" : "JMS: shipping.queue",
|
||||
"className" : "click.kamil.examples.enterprise.messaging.ShippingJmsListener",
|
||||
"methodName" : "onShippingReady",
|
||||
"metadata" : {
|
||||
"protocol" : "JMS",
|
||||
"destination" : "shipping.queue"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "orderId",
|
||||
"type" : "String",
|
||||
"annotations" : [ ]
|
||||
} ]
|
||||
}, {
|
||||
"type" : "RABBIT",
|
||||
"name" : "RABBIT: returns.queue",
|
||||
"className" : "click.kamil.examples.enterprise.messaging.ReturnsRabbitListener",
|
||||
"methodName" : "onReturn",
|
||||
"metadata" : {
|
||||
"protocol" : "RABBIT",
|
||||
"destination" : "returns.queue"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "orderId",
|
||||
"type" : "String",
|
||||
"annotations" : [ ]
|
||||
} ]
|
||||
} ],
|
||||
"callChains" : [ {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/enterprise/orders/place",
|
||||
"className" : "click.kamil.examples.enterprise.api.OrderController",
|
||||
"methodName" : "placeOrder",
|
||||
"metadata" : {
|
||||
"path" : "/api/enterprise/orders/place",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.examples.enterprise.api.OrderController.placeOrder", "click.kamil.examples.enterprise.service.OrderServiceImpl.place" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "PLACE_ORDER",
|
||||
"className" : "click.kamil.examples.enterprise.service.OrderServiceImpl",
|
||||
"methodName" : "place",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"lineNumber" : 16
|
||||
}
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/enterprise/orders/{id}/cancel",
|
||||
"className" : "click.kamil.examples.enterprise.api.OrderController",
|
||||
"methodName" : "cancelOrder",
|
||||
"metadata" : {
|
||||
"path" : "/api/enterprise/orders/{id}/cancel",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "id",
|
||||
"type" : "String",
|
||||
"annotations" : [ "PathVariable" ]
|
||||
} ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.examples.enterprise.api.OrderController.cancelOrder", "click.kamil.examples.enterprise.service.OrderServiceImpl.cancel" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "CANCEL_ORDER",
|
||||
"className" : "click.kamil.examples.enterprise.service.OrderServiceImpl",
|
||||
"methodName" : "cancel",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"lineNumber" : 21
|
||||
}
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "CUSTOM",
|
||||
"name" : "INTERCEPTOR: SecurityInterceptor.preHandle",
|
||||
"className" : "click.kamil.examples.enterprise.api.SecurityInterceptor",
|
||||
"methodName" : "preHandle",
|
||||
"metadata" : {
|
||||
"interceptorType" : "Spring MVC Interceptor"
|
||||
},
|
||||
"parameters" : [ ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.examples.enterprise.api.SecurityInterceptor.preHandle" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "AUDIT_EVENT",
|
||||
"className" : "click.kamil.examples.enterprise.api.SecurityInterceptor",
|
||||
"methodName" : "preHandle",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"lineNumber" : 17
|
||||
}
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/enterprise/payments",
|
||||
"className" : "click.kamil.examples.enterprise.api.ReactivePaymentController",
|
||||
"methodName" : "pay",
|
||||
"metadata" : {
|
||||
"path" : "/api/enterprise/payments",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "orderId",
|
||||
"type" : "String",
|
||||
"annotations" : [ "RequestBody" ]
|
||||
} ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.examples.enterprise.api.ReactivePaymentController.pay", "click.kamil.examples.enterprise.service.ReactivePaymentService.processPayment" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "PAY_ORDER",
|
||||
"className" : "click.kamil.examples.enterprise.service.ReactivePaymentService",
|
||||
"methodName" : "processPayment",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"lineNumber" : 18
|
||||
}
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "JMS",
|
||||
"name" : "JMS: shipping.queue",
|
||||
"className" : "click.kamil.examples.enterprise.messaging.ShippingJmsListener",
|
||||
"methodName" : "onShippingReady",
|
||||
"metadata" : {
|
||||
"protocol" : "JMS",
|
||||
"destination" : "shipping.queue"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "orderId",
|
||||
"type" : "String",
|
||||
"annotations" : [ ]
|
||||
} ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.examples.enterprise.messaging.ShippingJmsListener.onShippingReady" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "SHIP_ORDER",
|
||||
"className" : "click.kamil.examples.enterprise.messaging.ShippingJmsListener",
|
||||
"methodName" : "onShippingReady",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"lineNumber" : 17
|
||||
}
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "RABBIT",
|
||||
"name" : "RABBIT: returns.queue",
|
||||
"className" : "click.kamil.examples.enterprise.messaging.ReturnsRabbitListener",
|
||||
"methodName" : "onReturn",
|
||||
"metadata" : {
|
||||
"protocol" : "RABBIT",
|
||||
"destination" : "returns.queue"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "orderId",
|
||||
"type" : "String",
|
||||
"annotations" : [ ]
|
||||
} ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.examples.enterprise.messaging.ReturnsRabbitListener.onReturn" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "RETURN_ORDER",
|
||||
"className" : "click.kamil.examples.enterprise.messaging.ReturnsRabbitListener",
|
||||
"methodName" : "onReturn",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"lineNumber" : 17
|
||||
}
|
||||
} ],
|
||||
"properties" : { }
|
||||
},
|
||||
"name" : "click.kamil.examples.enterprise.config.EnterpriseStateMachineConfig",
|
||||
"renderChoicesAsDiamonds" : true,
|
||||
"startStates" : [ "NEW" ],
|
||||
"transitions" : [ {
|
||||
"type" : "EXTERNAL",
|
||||
"sourceStates" : [ {
|
||||
"rawName" : "\"NEW\"",
|
||||
"fullIdentifier" : "NEW"
|
||||
} ],
|
||||
"targetStates" : [ {
|
||||
"rawName" : "\"CHECK_AVAILABILITY\"",
|
||||
"fullIdentifier" : "CHECK_AVAILABILITY"
|
||||
} ],
|
||||
"event" : "PLACE_ORDER",
|
||||
"guard" : null,
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
"type" : "CHOICE",
|
||||
"sourceStates" : [ {
|
||||
"rawName" : "\"CHECK_AVAILABILITY\"",
|
||||
"fullIdentifier" : "CHECK_AVAILABILITY"
|
||||
} ],
|
||||
"targetStates" : [ {
|
||||
"rawName" : "\"PENDING_PAYMENT\"",
|
||||
"fullIdentifier" : "PENDING_PAYMENT"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"expression" : "(c) -> true",
|
||||
"isLambda" : true
|
||||
},
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
"type" : "CHOICE",
|
||||
"sourceStates" : [ {
|
||||
"rawName" : "\"CHECK_AVAILABILITY\"",
|
||||
"fullIdentifier" : "CHECK_AVAILABILITY"
|
||||
} ],
|
||||
"targetStates" : [ {
|
||||
"rawName" : "\"CANCELLED\"",
|
||||
"fullIdentifier" : "CANCELLED"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
"type" : "EXTERNAL",
|
||||
"sourceStates" : [ {
|
||||
"rawName" : "\"PENDING_PAYMENT\"",
|
||||
"fullIdentifier" : "PENDING_PAYMENT"
|
||||
} ],
|
||||
"targetStates" : [ {
|
||||
"rawName" : "\"PAID\"",
|
||||
"fullIdentifier" : "PAID"
|
||||
} ],
|
||||
"event" : "PAY_ORDER",
|
||||
"guard" : null,
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
"type" : "EXTERNAL",
|
||||
"sourceStates" : [ {
|
||||
"rawName" : "\"PAID\"",
|
||||
"fullIdentifier" : "PAID"
|
||||
} ],
|
||||
"targetStates" : [ {
|
||||
"rawName" : "\"SHIPPED\"",
|
||||
"fullIdentifier" : "SHIPPED"
|
||||
} ],
|
||||
"event" : "SHIP_ORDER",
|
||||
"guard" : null,
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
"type" : "EXTERNAL",
|
||||
"sourceStates" : [ {
|
||||
"rawName" : "\"SHIPPED\"",
|
||||
"fullIdentifier" : "SHIPPED"
|
||||
} ],
|
||||
"targetStates" : [ {
|
||||
"rawName" : "\"DELIVERED\"",
|
||||
"fullIdentifier" : "DELIVERED"
|
||||
} ],
|
||||
"event" : "FINALIZE",
|
||||
"guard" : null,
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
"type" : "EXTERNAL",
|
||||
"sourceStates" : [ {
|
||||
"rawName" : "\"PAID\"",
|
||||
"fullIdentifier" : "PAID"
|
||||
} ],
|
||||
"targetStates" : [ {
|
||||
"rawName" : "\"CANCELLED\"",
|
||||
"fullIdentifier" : "CANCELLED"
|
||||
} ],
|
||||
"event" : "CANCEL_ORDER",
|
||||
"guard" : null,
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
"type" : "EXTERNAL",
|
||||
"sourceStates" : [ {
|
||||
"rawName" : "\"DELIVERED\"",
|
||||
"fullIdentifier" : "DELIVERED"
|
||||
} ],
|
||||
"targetStates" : [ {
|
||||
"rawName" : "\"RETURNED\"",
|
||||
"fullIdentifier" : "RETURNED"
|
||||
} ],
|
||||
"event" : "RETURN_ORDER",
|
||||
"guard" : null,
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
} ],
|
||||
"endStates" : [ "CANCELLED", "DELIVERED", "RETURNED" ]
|
||||
}
|
||||
@@ -36,13 +36,28 @@
|
||||
}
|
||||
|
||||
#sidebar {
|
||||
width: 420px;
|
||||
width: 480px;
|
||||
min-width: 350px;
|
||||
max-width: 900px;
|
||||
background: var(--sidebar-bg);
|
||||
border-right: 1px solid var(--border);
|
||||
border-right: 2px solid var(--border);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: 10px 0 15px rgba(0,0,0,0.02);
|
||||
z-index: 10;
|
||||
overflow-x: hidden;
|
||||
resize: horizontal;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#sidebar::after {
|
||||
content: '⋮';
|
||||
position: absolute;
|
||||
right: 2px; top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: var(--border);
|
||||
font-size: 1.2rem;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
@@ -52,7 +67,6 @@
|
||||
header h1 { font-weight: 900; font-size: 1.6rem; color: var(--text); margin: 0 0 5px 0; letter-spacing: -0.5px; }
|
||||
header p { font-family: 'JetBrains Mono', monospace; font-size: 0.7rem; color: var(--text-muted); margin: 0 0 15px 0; opacity: 0.7; }
|
||||
|
||||
/* Tabs */
|
||||
.tabs {
|
||||
display: flex;
|
||||
padding: 0 30px;
|
||||
@@ -99,6 +113,8 @@
|
||||
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
|
||||
position: relative;
|
||||
word-break: break-all;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.ep-card:hover, .flow-card:hover {
|
||||
@@ -254,7 +270,6 @@
|
||||
function setupTabs() {
|
||||
const flows = metadata.flows || [];
|
||||
if (flows.length === 0) {
|
||||
// No flows? Hide the entire tab system to keep the UI clean
|
||||
document.querySelector('.tabs').style.display = 'none';
|
||||
document.querySelector('.tab-content[data-tab="flows"]')?.remove();
|
||||
return;
|
||||
@@ -272,8 +287,18 @@
|
||||
function populateSidebar() {
|
||||
const list = document.getElementById('ep-list');
|
||||
const entryPoints = metadata.metadata.entryPoints || [];
|
||||
const machineEvents = new Set(metadata.transitions.map(t => t.event).filter(Boolean));
|
||||
|
||||
entryPoints.forEach(ep => {
|
||||
// Quality Filter: Hide if it doesn't trigger a machine event
|
||||
const chains = (metadata.metadata.callChains || []).filter(c =>
|
||||
c.entryPoint.className === ep.className &&
|
||||
c.entryPoint.methodName === ep.methodName &&
|
||||
machineEvents.has(c.triggerPoint.event)
|
||||
);
|
||||
|
||||
if (chains.length === 0) return;
|
||||
|
||||
const card = document.createElement('div');
|
||||
card.className = 'ep-card';
|
||||
card.innerHTML = `
|
||||
@@ -292,7 +317,7 @@
|
||||
const flows = metadata.flows || [];
|
||||
|
||||
if (flows.length === 0) {
|
||||
list.innerHTML = '<p style="color:var(--text-muted); font-size:0.8rem; font-style:italic;">No business flows defined. Add a flows.json to your project to see them here.</p>';
|
||||
list.innerHTML = '<p style="color:var(--text-muted); font-size:0.8rem; font-style:italic;">No business flows defined.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -311,9 +336,10 @@
|
||||
|
||||
function highlightEntryPoint(ep) {
|
||||
clearHighlights();
|
||||
const machineEvents = new Set(metadata.transitions.map(t => t.event).filter(Boolean));
|
||||
const chains = metadata.metadata.callChains || [];
|
||||
const relevantEvents = chains
|
||||
.filter(c => c.entryPoint.className === ep.className && c.entryPoint.methodName === ep.methodName)
|
||||
.filter(c => c.entryPoint.className === ep.className && c.entryPoint.methodName === ep.methodName && machineEvents.has(c.triggerPoint.event))
|
||||
.map(c => c.triggerPoint.event);
|
||||
|
||||
if (relevantEvents.length === 0) return;
|
||||
@@ -326,7 +352,7 @@
|
||||
highlightEvents(flow.steps);
|
||||
}
|
||||
|
||||
function highlightEvents(events) {
|
||||
function highlightEvents(steps) {
|
||||
const svg = document.querySelector('svg');
|
||||
|
||||
// Dim everything first
|
||||
@@ -334,44 +360,66 @@
|
||||
el.classList.add('dimmed');
|
||||
});
|
||||
|
||||
events.forEach(evt => {
|
||||
const normalized = normalize(evt);
|
||||
const linkId = "#link_" + normalized;
|
||||
steps.forEach(step => {
|
||||
let linkId = "";
|
||||
if (step.includes("->")) {
|
||||
// Named Choice Branch: "SRC->TGT"
|
||||
const parts = step.split("->");
|
||||
linkId = "#link_anon_" + normalize(parts[0]) + "_" + normalize(parts[1]);
|
||||
} else {
|
||||
// Standard Event
|
||||
linkId = "#link_" + normalize(step);
|
||||
}
|
||||
|
||||
svg.querySelectorAll(`a[*|href="${linkId}"], a[href="${linkId}"]`).forEach(link => {
|
||||
link.classList.add('active-path');
|
||||
link.querySelectorAll('*').forEach(child => child.classList.remove('dimmed'));
|
||||
|
||||
// Surgical Selection
|
||||
let prev = link.previousElementSibling;
|
||||
let foundPath = false;
|
||||
let foundPolygon = false;
|
||||
while (prev) {
|
||||
if (!foundPath && prev.tagName === 'path') {
|
||||
prev.classList.remove('dimmed');
|
||||
prev.classList.add('active-path');
|
||||
foundPath = true;
|
||||
} else if (!foundPolygon && prev.tagName === 'polygon') {
|
||||
prev.classList.remove('dimmed');
|
||||
prev.classList.add('active-path');
|
||||
foundPolygon = true;
|
||||
} else if (['rect', 'circle', 'ellipse', 'text'].includes(prev.tagName)) {
|
||||
break;
|
||||
}
|
||||
if (foundPath && foundPolygon) break;
|
||||
prev = prev.previousElementSibling;
|
||||
svg.querySelectorAll('a').forEach(link => {
|
||||
const href = link.getAttribute('xlink:href') || link.getAttribute('href');
|
||||
if (href === linkId) {
|
||||
highlightLinkGroup(link);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Un-dim all state nodes for context
|
||||
svg.querySelectorAll('rect, circle, ellipse').forEach(el => {
|
||||
el.classList.remove('dimmed');
|
||||
let next = el.nextElementSibling;
|
||||
if (next && next.tagName === 'text') next.classList.remove('dimmed');
|
||||
svg.querySelectorAll('rect, circle, ellipse, polygon').forEach(el => {
|
||||
const isChoice = el.tagName === 'polygon' && el.points?.numberOfItems === 4;
|
||||
if (isChoice || ['rect', 'circle', 'ellipse'].includes(el.tagName)) {
|
||||
el.classList.remove('dimmed');
|
||||
let next = el.nextElementSibling;
|
||||
while(next && next.tagName === 'text') {
|
||||
next.classList.remove('dimmed');
|
||||
next = next.nextElementSibling;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function highlightLinkGroup(link) {
|
||||
link.classList.add('active-path');
|
||||
link.querySelectorAll('*').forEach(child => child.classList.remove('dimmed'));
|
||||
|
||||
// Find path and polygon immediately before the <a> tag (interleaved)
|
||||
let prev = link.previousElementSibling;
|
||||
let foundPath = false;
|
||||
let foundPolygon = false;
|
||||
while (prev) {
|
||||
if (prev.tagName === 'path' && !foundPath) {
|
||||
prev.classList.remove('dimmed');
|
||||
prev.classList.add('active-path');
|
||||
foundPath = true;
|
||||
} else if (prev.tagName === 'polygon' && !foundPolygon && prev.points?.numberOfItems !== 4) {
|
||||
prev.classList.remove('dimmed');
|
||||
prev.classList.add('active-path');
|
||||
foundPolygon = true;
|
||||
} else if (['rect', 'circle', 'ellipse'].includes(prev.tagName) ||
|
||||
(prev.tagName === 'polygon' && prev.points?.numberOfItems === 4)) {
|
||||
// Hit a state node, stop traversal
|
||||
break;
|
||||
}
|
||||
if (foundPath && foundPolygon) break;
|
||||
prev = prev.previousElementSibling;
|
||||
}
|
||||
}
|
||||
|
||||
function clearHighlights() {
|
||||
document.querySelectorAll('.dimmed, .active-path').forEach(el => {
|
||||
el.classList.remove('dimmed', 'active-path');
|
||||
@@ -386,27 +434,28 @@
|
||||
const href = link.getAttribute('xlink:href') || link.getAttribute('href');
|
||||
if (!href || !href.startsWith('#link_')) return;
|
||||
|
||||
const eventName = href.replace('#link_', '');
|
||||
const metaTrans = transitions.find(t => normalize(t.event) === eventName);
|
||||
const chains = (metadata.metadata.callChains || []).filter(c => normalize(c.triggerPoint.event) === eventName);
|
||||
const linkId = href.replace('#link_', '');
|
||||
const isAnon = linkId.startsWith('anon_');
|
||||
|
||||
let metaTrans = null;
|
||||
if (isAnon) {
|
||||
const parts = linkId.split('_'); // link_anon_SRC_TGT
|
||||
const src = parts[2];
|
||||
const tgt = parts[3];
|
||||
metaTrans = transitions.find(t =>
|
||||
!t.event &&
|
||||
normalize(t.sourceStates[0].fullIdentifier) === src &&
|
||||
normalize(t.targetStates[0].fullIdentifier) === tgt
|
||||
);
|
||||
} else {
|
||||
metaTrans = transitions.find(t => normalize(t.event) === linkId);
|
||||
}
|
||||
|
||||
if (metaTrans || chains.length > 0) {
|
||||
if (metaTrans || isAnon) {
|
||||
link.style.cursor = 'pointer';
|
||||
|
||||
let pathToTrigger = null;
|
||||
let prev = link.previousElementSibling;
|
||||
while (prev) {
|
||||
if (prev.tagName === 'path') {
|
||||
pathToTrigger = prev;
|
||||
break;
|
||||
}
|
||||
if (['rect', 'circle'].includes(prev.tagName)) break;
|
||||
prev = prev.previousElementSibling;
|
||||
}
|
||||
|
||||
const setupTippy = (el) => {
|
||||
tippy(el, {
|
||||
content: createTooltip(eventName, metaTrans, chains),
|
||||
content: createTooltip(isAnon ? "Internal logic" : linkId, metaTrans),
|
||||
allowHTML: true,
|
||||
theme: 'modern',
|
||||
interactive: true,
|
||||
@@ -415,11 +464,12 @@
|
||||
offset: [0, 20]
|
||||
});
|
||||
};
|
||||
|
||||
setupTippy(link);
|
||||
if (pathToTrigger) {
|
||||
pathToTrigger.style.cursor = 'pointer';
|
||||
setupTippy(pathToTrigger);
|
||||
let path = link.previousElementSibling;
|
||||
while(path && path.tagName !== 'path') path = path.previousElementSibling;
|
||||
if (path) {
|
||||
path.style.cursor = 'pointer';
|
||||
setupTippy(path);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -430,10 +480,12 @@
|
||||
return s.replace(/[^a-zA-Z0-9]/g, '_');
|
||||
}
|
||||
|
||||
function createTooltip(name, trans, chains) {
|
||||
function createTooltip(name, trans) {
|
||||
const chains = (metadata.metadata.callChains || []).filter(c => normalize(c.triggerPoint.event) === name);
|
||||
|
||||
let html = `<div class="tooltip-content">
|
||||
<div style="font-weight:900; font-size:1.1rem; margin-bottom:12px; border-bottom:1px solid #eee; padding-bottom:8px; line-height:1.3;">
|
||||
Event: <span style="color:var(--accent)">${name}</span>
|
||||
${name === 'Internal logic' ? name : 'Event: <span style="color:var(--accent)">' + name + '</span>'}
|
||||
</div>`;
|
||||
|
||||
if (trans && trans.guard) {
|
||||
@@ -446,37 +498,30 @@
|
||||
chains.forEach(c => {
|
||||
html += `<div style="margin-bottom:20px">
|
||||
<div style="font-weight:700; font-size:0.85rem; color:var(--text);">${c.entryPoint.name}</div>
|
||||
|
||||
${renderParameters(c.entryPoint.parameters)}
|
||||
|
||||
<div style="margin-left:10px; border-left:2px solid #e2e8f0; padding-left:12px; margin-top:10px">
|
||||
${c.methodChain.map((m, i) => `<div style="font-size:0.7rem; color:#475569; margin-top:4px; font-family:'JetBrains Mono', monospace;"><b>${i+1}</b> ${m}</div>`).join('')}
|
||||
</div>
|
||||
</div>`;
|
||||
});
|
||||
}
|
||||
|
||||
html += `</div>`;
|
||||
return html;
|
||||
}
|
||||
|
||||
function renderParameters(params) {
|
||||
if (!params || params.length === 0) return "";
|
||||
|
||||
let html = `<div style="font-size:0.65rem; font-weight:700; color:#94a3b8; margin-top:8px; margin-bottom:4px; text-transform:uppercase;">Input Data</div>`;
|
||||
html += `<div class="payload-box">`;
|
||||
|
||||
params.forEach(p => {
|
||||
const annotation = (p.annotations || []).find(a => a.endsWith("RequestBody") || a.endsWith("PathVariable") || a.endsWith("RequestParam"));
|
||||
const cleanAnn = annotation ? "@" + annotation.substring(annotation.lastIndexOf('.') + 1) : "";
|
||||
|
||||
html += `<div class="payload-param">
|
||||
<span style="color:#f43f5e; font-size:0.65rem; font-weight:bold;">${cleanAnn}</span>
|
||||
<span style="color:#38bdf8;">${p.type}</span>
|
||||
<span style="color:#a3e635;">${p.name}</span>
|
||||
</div>`;
|
||||
});
|
||||
|
||||
html += `</div>`;
|
||||
return html;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user