@kui-shell/plugin-wskflow
Version:
Visualizations for Composer apps
755 lines • 34.6 kB
JavaScript
/*
* Copyright 2017 The Kubernetes Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
/* eslint-disable @typescript-eslint/explicit-member-accessibility */
import Debug from 'debug';
import { textualPropertiesOfCode } from './util';
import * as AST from './ast';
import renderSubtext from './subtext';
const debug = Debug('plugins/wskflow/fsm2graph');
const maxWidth = 100;
// const defaultWidth = 40
const defaultHeight = 20;
const defaultCharWidth = 5;
const defaultCharHeight = 10;
/**
* Capitalize a given string
*
*/
const capitalize = (str) => str.charAt(0).toUpperCase() + str.substring(1);
function id2log(id) {
return id
.replace(/-components/g, '')
.replace(/__origin/g, '')
.replace(/__terminus/g, '');
}
class RenderState {
constructor(acts) {
this.dummyCount = 0;
this.taskIndex = 0;
this.activations = acts;
if (acts) {
this.activations.sort((a, b) => {
return a.start - b.start;
});
}
this.visited = undefined; // see shell issue #602
this.graphData = {
id: 'root',
label: 'root',
children: [],
edges: []
};
}
addDummy(sources = [], targets, obj, options, directionS, directionT) {
const dummyId = 'dummy_' + this.dummyCount;
let o;
let port;
this.dummyCount++;
obj.children.push(this.drawNodeNew(dummyId, options.label || dummyId, options.type || 'Dummy', sources, options));
if (sources && sources.length > 0) {
o = this.drawEdgeNew(sources[0], dummyId, obj);
port = o.targetPort;
obj.edges.push(o);
for (let i = 1; i < sources.length; i++) {
obj.edges.push(this.drawEdgeNew(sources[i], dummyId, obj, undefined, directionS, undefined, port));
}
}
if (targets && targets.length > 0) {
o = this.drawEdgeNew(dummyId, targets[0], obj);
port = o.sourcePort;
obj.edges.push(o);
for (let i = 1; i < targets.length; i++) {
obj.edges.push(this.drawEdgeNew(dummyId, targets[i], obj, undefined, directionT, port, undefined));
}
}
return dummyId;
}
drawNodeNew(id, label, type, properties, options) {
// console.log(id)
const o = {
id,
label,
type,
ports: [],
value: options && options.value,
tooltip: options && options.tooltip,
tooltipHeader: options && options.tooltipHeader,
properties: {}
};
if (this.visited) {
if (id === 'Entry') {
this.visited[id] = [0];
o.visited = this.visited[id];
}
else if (id === 'Exit') {
if (Array.isArray(properties)) {
properties.forEach(p => {
if (this.visited[p]) {
this.visited[id] = [this.activations.length - 1];
}
});
}
o.visited = this.visited[id];
}
else {
const iid = id2log(id);
if (this.visited[iid]) {
if (type === 'action') {
this.visited[iid].forEach((v, i) => {
this.visited[iid][i]++;
}); // for actions, increase all index by one to point to the next activation in the array.
}
if (type === 'retain') {
if ((this.visited[iid].length >= 1 && id.endsWith('__origin')) ||
(this.visited[iid].length === 2 && id.endsWith('__terminus'))) {
o.visited = this.visited[iid];
}
}
else {
o.visited = this.visited[iid];
}
}
// console.log("iid:", id, iid, visited[iid], o.visited)
}
}
if (this.visited && (this.visited[id] || id === 'Entry')) {
o.visited = this.visited[id] || [0];
}
// if(type !== 'tryBody' && type !== 'handler'){
if (type !== 'try' && type !== 'handler') {
// o.properties["de.cau.cs.kieler.portConstraints"] = "FIXED_SIDE";
// DO NOT TOUCH THIS.
o.properties['de.cau.cs.kieler.portConstraints'] = 'FIXED_ORDER';
}
if (options && options.leftToRight) {
delete o.properties['de.cau.cs.kieler.portConstraints'];
}
if (type !== 'Dummy' && type !== 'Exit' && properties) {
// dummy and entry/exit nodes have no layout properties
o.layoutOptions = {};
Object.keys(properties).forEach(p => {
o.properties[p] = properties[p];
o.layoutOptions[p] = properties[p];
});
}
if (options && options.leftToRight) {
o.properties = {
direction: 'RIGHT',
'org.eclipse.elk.direction': 'RIGHT'
};
}
if (o.type === 'action') {
if (label.indexOf('|') !== -1) {
o.name = label.substring(0, label.indexOf('|'));
label = label.substring(label.indexOf('|') + 1);
o.label = label;
}
else {
o.name = label;
}
if (label.lastIndexOf('/') !== -1 && label.lastIndexOf('/') < label.length - 1) {
o.label = label.substring(label.lastIndexOf('/') + 1);
}
o.height = defaultHeight;
if (o.label.length < 40) {
o.width = o.label.length * defaultCharWidth + 10;
}
else {
o.label = o.label.substring(0, 40) + '...';
o.width = 40 * defaultCharWidth + 10;
}
/* if(o.width<defaultWidth)
o.width = defaultWidth; */
if (this.actions) {
if (this.actions[o.name] === undefined)
this.actions[o.name] = [];
this.actions[o.name].push(o.id);
}
o.taskIndex = this.taskIndex++;
}
else if (o.type === 'function') {
o.fullFunctionCode = label;
const prettyCode = label;
const { nLines, maxLineLength } = textualPropertiesOfCode(prettyCode);
// uncomment the second clause if you want always to display 1-liner functions inline in the view
if (options.renderFunctionsInView /* || nLines === 1 */) {
// ok cool, then render this function body directly in the view
const charWidthForCode = defaultCharWidth;
o.width = Math.min(maxWidth, maxLineLength * charWidthForCode);
o.height = Math.max(2.25, nLines) * defaultCharHeight; // use at least two lines; makes one-line functions look better
o.multiLineLabel = prettyCode.split(/[\n\r]/).map(line => {
const width = o.width / charWidthForCode;
if (width >= line.length) {
// not cropped
return line;
}
else {
return line.substring(0, width) + '\u2026'; // horizontal ellipsis unicode
}
});
o.prettyCode = prettyCode;
delete o.label;
}
else {
// otherwise, don't show any function code directly in the view; only in tooltip
o.width = 8;
o.height = 8;
o.tooltip = prettyCode;
delete o.label;
}
o.taskIndex = this.taskIndex++;
}
else if (o.type === 'try_catch') {
o.properties = {
direction: 'RIGHT',
'org.eclipse.elk.direction': 'RIGHT'
};
o.children = [
{
id: `${id}-body`,
label: 'try',
type: 'try',
ports: [],
properties: {},
children: [],
edges: [],
visited: this.visited ? this.visited[id2log(`${id}-body`)] : undefined
},
{
id: `${id}-handler`,
label: 'error handler',
type: 'handler',
ports: [],
properties: {},
children: [],
edges: [],
visited: this.visited ? this.visited[id2log(`${id}-<handler`)] : undefined
}
];
o.edges = [this.drawEdgeNew(`${id}-body`, `${id}-handler`, o, undefined, 'RIGHT')];
}
else if (o.type === 'Entry' || o.type === 'Exit') {
o.width = 18;
o.height = 18;
}
else if (o.type === 'retain') {
o.width = 4;
o.height = 4;
}
else if (o.type === 'Dummy') {
if (o.label === o.id) {
o.width = 4;
o.height = 4;
}
else {
// then this dummy node has a label
o.width = 0.875 * o.label.length * defaultCharWidth;
o.height = 0.875 * defaultCharHeight;
}
// Dummy node's `properties` is `sources`, used to determine if the dummy is visited
if (this.visited && Array.isArray(properties)) {
properties.forEach((_s) => {
// a source id
const s = id2log(_s);
if (this.visited[s]) {
// if the source is visited
this.visited[s].forEach(a => {
// find out if any of its activation was success
if (this.activations[a].response.success) {
// if so, dummy is visited
if (this.visited[o.id] === undefined) {
this.visited[o.id] = [];
o.visited = [];
}
this.visited[o.id].push(a);
o.visited.push(a);
}
});
}
});
}
}
else if (o.type === 'let' || o.type === 'literal') {
if (o.label.length > 30) {
o.width = 30 * defaultCharWidth + 10;
}
else {
o.width = o.label.length * defaultCharWidth + 10;
}
o.height = defaultHeight;
o.tooltip = o.label;
delete o.label;
o.width = 20;
o.height = 20;
}
else if (o.type === 'retry') {
o.children = [];
o.edges = [];
o.retryCount = label;
o.label = `Retry ${label} time${parseInt(label, 10) > 1 ? 's' : ''}`;
}
else if (o.type === 'repeat') {
o.children = [];
o.edges = [];
o.repeatCount = label;
o.label = `Repeat ${label} time${parseInt(label, 10) > 1 ? 's' : ''}`;
}
return o;
}
drawEdgeNew(sourceId, targetId, layer, type, direction, sourcePort, targetPort) {
// let sourcePort, targetPort;
for (let i = 0; i < layer.children.length; i++) {
if (layer.children[i].id === sourceId) {
if (type) {
if (type === 'true' || type === 'false') {
sourcePort = `${sourceId}_p${type}`;
layer.children[i].properties.choice = true;
}
}
else if (layer.children[i].properties.choice) {
sourcePort = `${sourceId}_pfalse`;
}
else {
sourcePort = `${sourceId}_p${layer.children[i].ports.length}`;
}
layer.children[i].ports.push({
id: sourcePort,
properties: { portSide: direction || 'SOUTH' }
});
}
if (layer.children[i].id === targetId) {
// console.log("found! "+targetId);
// targetPort = targetId+"_p"+layer.children[i].ports.length;
targetPort = `${targetId}_p${layer.children[i].ports.length}`;
layer.children[i].ports.push({
id: targetPort,
properties: { portSide: direction || 'NORTH' }
});
}
if (sourcePort && targetPort) {
break;
}
}
if (sourcePort === undefined || targetPort === undefined) {
debug('source or target not found', sourceId, targetId, layer, this.graphData);
}
return {
id: sourceId + '_' + sourcePort + '->' + targetId + '_' + targetPort,
source: sourceId,
sourcePort: sourcePort,
target: targetId,
targetPort: targetPort,
visited: this.visited ? !!(this.visited[sourceId] && this.visited[targetId]) : undefined
};
}
ir2graph(ir, gm, id, prevId, options = {}) {
// ir and graph model
if (ir.type === 'sequence' || ir.type === 'seq' || Array.isArray(ir)) {
// for an array of things, prevId is the previous element
// console.log(ir, gm, id, prevId);
let count = 0;
let prev;
let array;
if (AST.isSequence(ir)) {
array = ir.components;
}
else {
array = ir;
}
array.forEach(obj => {
if (obj.options && obj.options.helper) {
// do nothing
}
else {
prev = this.ir2graph(obj, gm, `${id}-${count}`, count > 0 ? prev : prevId, options);
count++;
}
});
return prev;
}
else {
// id = `${id}-${ir.type}`
if (AST.isAction(ir)) {
let name = ir.name;
if (ir.displayLabel) {
name += `|${ir.displayLabel}`;
}
gm.children.push(this.drawNodeNew(id, name, ir.type, undefined, options));
if (prevId) {
prevId.forEach(pid => gm.edges.push(this.drawEdgeNew(pid, id, gm)));
}
return [id];
}
else if (AST.isFunction(ir)) {
gm.children.push(this.drawNodeNew(id, ir.function.exec.prettyCode || ir.function.exec.code, ir.type, undefined, options));
if (prevId) {
prevId.forEach(pid => gm.edges.push(this.drawEdgeNew(pid, id, gm)));
}
return [id];
}
else if (AST.isConditional(ir)) {
const firstTestId = gm.children.length;
const lastTestId = this.ir2graph(ir.test, gm, `${id}-test`, undefined, options);
const firstConsId = gm.children.length;
const lastConsId = this.ir2graph(ir.consequent, gm, `${id}-consequent`, undefined, options);
// the if may not have an "else", i.e. "alternate"
let firstAltId;
let lastAltId;
if (ir.alternate.type !== 'empty') {
firstAltId = gm.children.length;
lastAltId = this.ir2graph(ir.alternate, gm, `${id}-alternate`, undefined, options);
}
if (prevId) {
// connect prevId to the first node in test
prevId.forEach(pid => gm.edges.push(this.drawEdgeNew(pid, gm.children[firstTestId].id, gm)));
}
// connect test to consequence
let ltid;
if (lastTestId.length > 1) {
// insert a dummy node to converge
ltid = this.addDummy(lastTestId, undefined, gm, options);
}
else {
ltid = lastTestId[0];
}
gm.edges.push(this.drawEdgeNew(ltid, gm.children[firstConsId].id, gm, 'true'));
if (lastAltId && lastAltId.length > 0) {
// may or may not have a alt branch
gm.edges.push(this.drawEdgeNew(ltid, gm.children[firstAltId].id, gm, 'false'));
}
else {
lastAltId = [ltid];
}
const exitConcentrator = this.addDummy(lastAltId.concat(lastConsId), undefined, gm, options);
return [exitConcentrator];
}
else if (AST.isTry(ir)) {
// insert a compound node for try
gm.children.push(this.drawNodeNew(id, 'Try-Catch', 'try_catch', undefined, options));
if (prevId) {
prevId.forEach(pid => gm.edges.push(this.drawEdgeNew(pid, id, gm)));
}
else {
gm.children[gm.children.length - 1].properties.compoundNoParents = true;
}
const tryCatchPart = gm.children[gm.children.length - 1];
const tryPart = tryCatchPart.children[0];
const catchPart = tryCatchPart.children[1];
this.ir2graph(ir.body, tryPart, tryPart.id + '-components', undefined, options);
this.ir2graph(ir.handler, catchPart, catchPart.id + '-components', undefined, options);
return [gm.children[gm.children.length - 1].id];
}
else if (AST.isWhile(ir) || AST.isDoWhile(ir)) {
let firstTestId;
let firstBodyId;
let lastTestId;
let lastBodyId;
if (AST.isWhile(ir)) {
firstTestId = gm.children.length;
lastTestId = this.ir2graph(ir.test, gm, `${id}-test`, undefined, options);
if (prevId) {
// connect prevId to the first node in test
prevId.forEach(pid => gm.edges.push(this.drawEdgeNew(pid, gm.children[firstTestId].id, gm)));
}
firstBodyId = gm.children.length;
lastBodyId = this.ir2graph(ir.body, gm, `${id}-body`, undefined, options);
}
else if (AST.isDoWhile(ir)) {
firstBodyId = gm.children.length;
lastBodyId = this.ir2graph(ir.body, gm, `${id}-body`, undefined, options);
firstTestId = gm.children.length;
lastTestId = this.ir2graph(ir.test, gm, `${id}-test`, undefined, options);
}
// connect test to consequence
let ltid;
let lbid;
if (lastTestId.length > 1) {
// insert a dummy node to converge
ltid = this.addDummy(lastTestId, undefined, gm, options);
}
else {
ltid = lastTestId[0];
}
if (lastBodyId.length > 1) {
// insert a dummy node to converge
lbid = this.addDummy(lastBodyId, undefined, gm, options);
}
else {
lbid = lastBodyId[0];
}
gm.edges.push(this.drawEdgeNew(ltid, gm.children[firstBodyId].id, gm, 'true')); // true edge for test, go to body
gm.edges.push(this.drawEdgeNew(lbid, gm.children[firstTestId].id, gm)); // edge loop back to the beginning of test
// for dowhile, add the edge from prev to the body at the end;
// this seems to make the ELK layout algorithm happier;
// otherwise, it crosses the prev->body and cond-yes->body edges
if (prevId && (ir.type === 'dowhile' || ir.type === 'dowhile_nosave')) {
// connect prevId to the first node in test
prevId.forEach(pid => gm.edges.push(this.drawEdgeNew(pid, gm.children[firstBodyId].id, gm)));
}
return [ltid];
}
else if (AST.isRetain(ir)) {
gm.children.push(this.drawNodeNew(`${id}__origin`, '', ir.type, undefined, options));
if (prevId) {
prevId.forEach(pid => gm.edges.push(this.drawEdgeNew(pid, `${id}__origin`, gm)));
}
const lastNodes = this.ir2graph(ir.components, gm, id, [`${id}__origin`], options);
gm.children.push(this.drawNodeNew(`${id}__terminus`, '', ir.type, undefined, options));
if (lastNodes) {
lastNodes.forEach(pid => gm.edges.push(this.drawEdgeNew(pid, `${id}__terminus`, gm)));
}
const forwardingEdge = this.drawEdgeNew(`${id}__origin`, `${id}__terminus`, gm, undefined, 'EAST');
// forwardingEdge.labels = [ { text: 'forwarding' } ]
forwardingEdge.properties = { type: 'retain' };
gm.edges.push(forwardingEdge);
return [`${id}__terminus`];
}
else if (AST.isRetryOrRepeat(ir)) {
gm.children.push(this.drawNodeNew(id, ir.count, ir.type, undefined, options));
if (prevId) {
prevId.forEach(pid => gm.edges.push(this.drawEdgeNew(pid, id, gm)));
}
// body is in ir.components
this.ir2graph(ir.components, gm.children[gm.children.length - 1], `${id}-components`, undefined, options);
return [gm.children[gm.children.length - 1].id];
}
else if (AST.isLet(ir)) {
// regular let
const s = JSON.stringify(ir.declarations, undefined, 4);
gm.children.push(this.drawNodeNew(id, s, ir.type, undefined, Object.assign(options, { value: ir.declarations })));
if (prevId) {
prevId.forEach(pid => gm.edges.push(this.drawEdgeNew(pid, id, gm)));
}
return this.ir2graph(ir.components, gm, `${id}-components`, [id], options);
}
else if (AST.isLiteral(ir)) {
const s = JSON.stringify(ir.value, undefined, 4);
gm.children.push(this.drawNodeNew(id, s, ir.type, undefined, Object.assign(options, { value: ir.value })));
if (prevId) {
prevId.forEach(pid => gm.edges.push(this.drawEdgeNew(pid, id, gm)));
}
return [id];
}
else if (AST.isFinally(ir)) {
const lastBodyNode = this.ir2graph(ir.body, gm, `${id}-body`, prevId, /* undefined, */ options);
return this.ir2graph(ir.finalizer, gm, `${id}-finalizer`, lastBodyNode, /* undefined, */ options);
}
else if (AST.isParallelLike(ir)) {
// par and map
const label = ir.type === 'map' || ir.type === 'forall' ? 'Parallel Map over Array' : 'Execute Tasks in Parallel';
const tooltipHeader = ir.type === 'par' || ir.type === 'parallel' ? 'Parallel' : ir.type === 'map' ? 'Map' : ir.type;
const tooltip = ir.type === 'map' || ir.type === 'forall'
? 'Executes a single task in parallel for each element of the input array'
: 'Executes a set of tasks in parallel, passing the same input data to each task';
let parent = prevId;
if (ir.set) {
// input set to the parallel construct
parent = this.ir2graph(ir.set, gm, `${id}-${ir.type}-set`, parent, options);
}
// render the "Fork" node
const fork = this.addDummy(parent, undefined, gm, {
label,
tooltipHeader,
tooltip
});
// for now, the parent of the body is the fork node; if the ir
// specifies an input set, then we'll change this
parent = [fork];
// render the par/map body
let exits;
if (AST.isMapLike(ir)) {
// for map, render the components as a sequence
// Fork (the "fork" node that we rendered just above)
// |
// a -> b -> c <-- we're doing this part now
// |
// Join (we'll render this just below)
const body = ir.components || ir.body;
const ellipsis = { type: 'literal', value: '...' };
const spreadItOut = [body, body, ellipsis, body];
exits = spreadItOut.map((component, idx) => {
return this.ir2graph(component, gm, `${id}-component-${idx}`, parent, options)[0];
});
}
else {
// for parallel, render the components as a fan-out
// Fork (the "fork" node that we rendered just above)
// / | \
// a b c <-- we're doing this part now
// \ | /
// Join (we'll render this just below)
exits = ir.components.map((component, idx) => {
return this.ir2graph(component, gm, `${id}-component-${idx}`, parent, options)[0];
});
}
// finally, render the "Join" node
const exitConcentrator = this.addDummy(exits, undefined, gm, {
label: 'Wait for Completion',
tooltipHeader: `${tooltipHeader} completion`,
tooltip: 'Wait for the parallel tasks to complete, and then return an array of results'
});
return [exitConcentrator];
}
else if (AST.isComponentBearing(ir)) {
// generic handler for any subgraph-via-body node
const label = capitalize(ir.type);
const type = ir.type;
const body = this.drawNodeNew(id, label, type, undefined, options);
body.children = [];
body.edges = [];
gm.children.push(body);
if (prevId) {
prevId.forEach(pid => gm.edges.push(this.drawEdgeNew(pid, id, gm)));
}
// render the components as a sequence
this.ir2graph(ir.components, body, `${id}-components`, undefined, options);
return [id];
}
else if (typeof ir === 'object') {
// generic handler for any opaque node
const type = 'action';
const label = ir.type;
gm.children.push(this.drawNodeNew(id, label, type, undefined, options));
if (prevId) {
prevId.forEach(pid => gm.edges.push(this.drawEdgeNew(pid, id, gm)));
}
debug('generic handler', ir, label, type);
return [id];
}
}
}
}
/**
* @return the cumulative number of nodes in the given composition that are not of type Function
*
*/
const numNonFunctions = (composition) => {
if (composition === undefined)
return 0;
if (composition.type === 'function') {
return 0;
}
else if (composition.type) {
// then this is a compound node of some type
let sum = 0;
for (const key in composition) {
sum += numNonFunctions(composition[key]);
}
return sum + 1;
}
else if (Array.isArray(composition)) {
return composition.reduce((sum, sub) => sum + numNonFunctions(sub), 0);
}
else {
return 0;
}
};
/**
* Heuristic: is this composition "pretty simple"?
*
*/
const isSimpleComposition = (ir) => {
const isShort = AST.isComponentArrayBearing(ir) ? ir.components.length <= 2 : true;
const numNonFuncs = numNonFunctions(ir);
const atMostOneNonFunction = numNonFuncs <= 3;
debug('isSimpleComposition', isShort, numNonFuncs);
return isShort && atMostOneNonFunction;
};
export default function fsm2graph(tab, ir, containerElement, acts, options, rule
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) {
return __awaiter(this, void 0, void 0, function* () {
// console.log(ir, containerElement, acts);
const renderState = new RenderState(acts);
if (renderState.activations) {
// parse the activations to get a list of states that was visted
debug('activations', renderState.activations);
renderState.visited = {};
renderState.activations.forEach((a, index) => {
if (a.logs) {
// states recorded in logs
a.logs.forEach(log => {
if (log.indexOf('Entering composition') !== -1) {
// a conductor path log
let path = log.substring(log.lastIndexOf(' ') + 1);
// replace all [,],.in path to - to use as a id, as css selector cannot have those characters
path = path.replace(/[\[\.]/g, '-').replace(/\]/g, ''); // eslint-disable-line no-useless-escape
if (renderState.visited[path] === undefined)
renderState.visited[path] = [];
renderState.visited[path].push(index);
}
});
}
});
Object.keys(renderState.visited).forEach(k => {
// make sure the compound node, if any, is included in visited too.
const seg = k.split('-');
seg.pop(); // kick out the last element == get the compound node id
const path = seg.join('-');
if (renderState.visited[path] === undefined)
renderState.visited[path] = [];
renderState.visited[path] = renderState.visited[path].concat(renderState.visited[k]); // join it back, value is all the items in the child arrays (not sure if it's necessary)
});
debug('visited nodes:', renderState.visited);
}
else {
renderState.actions = {};
}
debug('generating graph model');
const renderFunctionsInView = isSimpleComposition(ir);
const viewOptions = Object.assign({ renderFunctionsInView }, options);
if ((rule && rule.trigger) || AST.isOn(ir)) {
debug('using rule as start', rule);
const start = renderState.drawNodeNew('Entry', 'trigger', 'Entry');
start.properties.kind = 'trigger';
start.properties.kindDetail = rule ? rule.trigger.name : AST.isOn(ir) && ir.trigger;
start.width = 24;
renderState.graphData.children.push(start); // insert Entry node
if (AST.isOn(ir)) {
// no need to render the "on" wrapper, as we've already
// signified the trigger in the Start node
ir = ir.components;
}
}
else {
renderState.graphData.children.push(renderState.drawNodeNew('Entry', 'start', 'Entry')); // insert Entry node
}
let lastNodes = renderState.ir2graph(ir, renderState.graphData, 'composition', ['Entry'], // build the graph model, link the start of the graph to Entry
viewOptions); // <-- options to the rendering
if (lastNodes === undefined) {
lastNodes = ['Entry'];
}
renderState.graphData.children.push(renderState.drawNodeNew('Exit', 'end', 'Exit', lastNodes)); // insert Exit node
lastNodes.forEach(pid => renderState.graphData.edges.push(renderState.drawEdgeNew(pid, 'Exit', renderState.graphData))); // link the end of the graph to Exit
debug('graphData', renderState.graphData);
const subtext = yield renderSubtext(tab, renderState.actions, renderState.activations, renderState.graphData, options);
debug('subtext', subtext);
debug('inserting DOM, calling graph2doms');
const graph2doms = (yield import('./graph2doms')).default;
const response = yield graph2doms(tab, renderState.graphData, containerElement, renderState.activations);
return Object.assign(response, { subtext });
});
}
//# sourceMappingURL=fsm2graph.js.map