Spesso sono incaricato di flussi relativamente complessi che i miei datori di lavoro vorranno sperimentare riguardo alla quantità di passaggi e al loro ordine. Sono un appaltatore e quando il mio lavoro viene consegnato a un altro sviluppatore, mi piacerebbe spedire qualcosa che sia facile da capire rapidamente - e un flusso con più di 10 passaggi è un bel boccone. Quindi ho scritto un prototipo che prende una struttura dati JSON fatta in casa, che consente
- Esecuzione di quella struttura dati con un oggetto stato passato a tutte le condizioni e proseguendo attraverso il vero percorso del diagramma di flusso.
- Trasformare la struttura dei dati in un grafico che può essere visualizzato, come nell'esempio seguente.
Itipisupportatisono:Condizione,Passo/StatoeTermina.
Hovistodiversiprogettistididiagrammidiflussolàfuoriemichiedevoseavrebbeavutosensoimplementareinunsottoinsiemedellalorostrutturadati,quindiidiagrammidiflussopossonoesseremodificatisiaincodicecheinunvisualdesigner.Forseconsentirebbel'esecuzionedeidiagrammidiflussoinaltrilinguaggidiprogrammazione,poichélamiaimplementazioneèsoloinTypeScript.
Esistonostandardperlestrutturedidatideldiagrammadiflussoe/oesisteunostrumentostandarddefactopermodificareidiagrammidiflussochedevonoessereancheeseguiti?
Questoèilprototipo.Sipregadinotarecheèmolto,moltosolounprototipo!
import{inspect}from"util"
import { Graph, json, alg } from "graphlib"
import * as fs from "fs"
let predicates = {
isSomeCondition2: () => false,
IsCvrActive: () => false,
shouldContactCustomerService: () => false,
canDeliverProduct: () => true,
isChangingSupplier: () => true,
isSomeCondition: () => true,
isReceipt: () => false
}
let flow =
{
type: "Flow", name: "Primary flow", nodes: [
{ type: "Step", name: "Enter CVR" },
{
type: "Condition", name: "Is CVR Active?", predicateKey: "IsCvrActive",
right: {
type: "Step", name: "CVR is inactive",
left: { type: "Terminate", name: "Terminate (inactive cvr)" }
}
},
{
type: "Condition", name: "Should contact customer service?", predicateKey: "shouldContactCustomerService",
left: {
type: "Step", name: "Contact customer service",
left: { type: "Terminate", name: "Terminate (contact customer service)" }
}
},
{
type: "Condition", name: "Can deliver product?", predicateKey: "canDeliverProduct",
right: {
type: "Step", name: "Cannot deliver product",
left: { type: "Terminate", name: "Terminate (cannot deliver product)" }
}
},
{
type: "Condition", name: "DataHub has PODs for CVR?", predicateKey: "isChangingSupplier",
left: { type: "Flow", name: "Switch provider", nodes: [
{ type: "Step", name: "Power destination addresses" }
]},
right: { type: "Flow", name: "Relocation", nodes: [
{ type: "Step", name: "Enter address" },
{ type: "Step", name: "Enter POD" },
{ type: "Step", name: "Enter estimated annual volume" },
{ type: "Step", name: "Enter latest meter reading" }
]}
},
{ type: "Step", name: "First payment" },
{
type: "Condition", name: "What receipt?", predicateKey: "isReceipt",
left: { type: "Step", name: "Receipt1" },
right: { type: "Step", name: "Receipt2" }
}]
}
let start = new Date()
//console.log("== EVALUATED")
//let evaluatedFlow = evaluateFlow(flow)
//console.log(evaluatedFlow)
var i = 0;
let graph = traverseFlow(flow)
console.log("PARENT Is this a condition?")
console.log(graph.parent('Is this a condition?'));
console.log("PARENT Enter CVR")
console.log(graph.parent('Enter CVR'));
console.log("== GRAPH")
var graphJson = json.write(graph)
fs.writeFileSync("./graph.html", writeHtml(graphJson));
//console.log(inspect(graphJson, { showHidden: false, depth: null }))
console.log("== MINIMUM STEPS")
//let minimumSteps = findMinimumSteps(graph)
//console.log(minimumSteps)
let end = <any>new Date() - <any>start
console.info("\n\nExecution time: %dms", end)
function findMinimumSteps(graph: Graph) {
let root = graph.sources()[0]
let sinks = graph.sinks().filter(node => graph.node(node).type !== "Terminate")
let dijkstraResult = alg.dijkstra(graph, root, e => graph.node(e.w).type === "Condition" ? 0 : 1)
console.log("=== DIJSKTRA")
console.log(dijkstraResult)
let shortestPath = sinks
.map(node => dijkstraResult[node])
.reduce((a, b) => a.distance < b.distance ? a : b)
return shortestPath.distance + 1
}
function traverseFlow(flow: any, graph?: Graph, parent?: any): Graph {
graph = graph || new Graph({ compound: true })
let sinks = parent ? [parent.name] : graph.sinks()
flow.nodes
.map(createFlowGraph)
.forEach((subGraph: Graph) => {
subGraph.nodes().forEach(node => {
graph.setNode(node, subGraph.node(node))
let parentFlow = subGraph.parent(node) || flow.name
if(graph.node(parentFlow)) {
graph.setParent(node, parentFlow)
}
})
subGraph.edges().forEach(edge => graph.setEdge(edge, subGraph.edge(edge)))
sinks.forEach(outNode => {
let label = graph.node(outNode)
if (label.type !== "Terminate" && label.type !== "Flow")
subGraph.sources().forEach(inNode => {
if(subGraph.node(inNode).type !== "Flow")
graph.setEdge(outNode, inNode, { label: label.type === "Condition" ? conditionLabel(label, inNode) : undefined })
})
})
let unconnectedConditions = subGraph.filterNodes(n =>
subGraph.node(n).type === "Condition" && (subGraph.outEdges(n) || []).length === 1).nodes()
sinks = subGraph.sinks().filter(node => subGraph.node(node).type !== "Flow").concat(unconnectedConditions)
})
return graph
}
function createFlowGraph(root: any): Graph {
let graph = new Graph({ compound: true })
traverseBinaryNode(graph, root)
return graph
}
function conditionLabel(condition: any, node: any) {
let { left, right } = condition
if(left && left.type === "Flow")
left = left.nodes[0]
if(right && right.type === "Flow")
right = right.nodes[0]
if(left && left.name === node)
return true
if(right && right.name === node)
return false
return !!condition.right
}
function traverseBinaryNode(g: Graph, node: any, parent?: any) {
if(node.type === "Flow")
g.setNode(node.name, (<any>Object).assign({}, node, { label: node.name, clusterLabelPos: 'top', style: 'fill: #fff' }))
else
g.setNode(node.name, (<any>Object).assign({}, node))
if (node.type === "Flow")
traverseFlow(node, g, parent)
if (node.left)
traverseBinaryNode(g, node.left, node)
if (node.right)
traverseBinaryNode(g, node.right, node)
if (parent && node.type !== "Flow")
g.setEdge(parent.name, node.name, { label: parent.type === "Condition" ? conditionLabel(parent, node.name) : undefined })
}
function evaluateFlow(flow: any) {
let steps = []
for (let node of flow.nodes) {
if (!evaluateNode(node, steps))
break
}
return steps
}
function evaluateNode(node: any, steps: any[]) {
steps.push(node)
switch (node.type) {
case "Condition": {
if (predicates[node.predicateKey]())
return evaluateNode(node.left, steps)
else if (node.right)
return evaluateNode(node.right, steps)
// Continue to the next binary tree in the flow
return true
}
case "Step": {
if (node.left)
return evaluateNode(node.left, steps)
// Continue to the next binary tree in the flow
return true
}
case "Terminate": {
// Stop evaluating the flow
return false
}
}
throw Error("Unknown node type")
}
function writeHtml(graphJson) {
return '
<!doctype html>
<html>
<head></head>
<body>
<script src="https://d3js.org/d3.v4.min.js"></script><scriptsrc="https://dagrejs.github.io/project/dagre-d3/latest/dagre-d3.js"></script>
<style id="css">
/* This sets the color for "TK" nodes to a light blue green. */
g.type-TK > rect {
fill: #00ffd0;
}
text {
font-weight: 300;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serf;
font-size: 14px;
}
.node rect {
stroke: #999;
fill: #fff;
stroke-width: 1.5px;
}
.edgePath path {
stroke: #333;
stroke-width: 1.5px;
}
.clusters rect {
fill: #00ffd0;
stroke: #999;
stroke-width: 1.5px;
}
</style>
<svg id="svg-canvas" width=3000 height=3000></svg>
<script>
// Here we"re setting nodeclass, which is used by our custom drawNodes function
// below.
var g = dagreD3.graphlib.json.read(JSON.parse(\'${JSON.stringify(graphJson, null, 2)}\')).setGraph({})
// Create the renderer
var render = new dagreD3.render();
// Set up an SVG group so that we can translate the final graph.
var svg = d3.select("svg"),
svgGroup = svg.append("g");
// Run the renderer. This is what draws the final graph.
render(d3.select("svg g"), g);
// Center the graph
var xCenterOffset = (svg.attr("width") - g.graph().width) / 2;
svgGroup.attr("transform", "translate(" + xCenterOffset + ", 20)");
svg.attr("height", g.graph().height + 40);
</script>
</body>
</html>
'
}
Prima fai npm install graphlib
quindi esegui tsc file.ts && node file.js
, quindi apri graph.html