UNPKG

@kui-shell/plugin-wskflow

Version:

Visualizations for Composer apps

755 lines • 34.6 kB
/* * 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