Skip to content

Commit

Permalink
Merge pull request #604 from mountaindude/599
Browse files Browse the repository at this point in the history
599
  • Loading branch information
mountaindude authored Jan 5, 2025
2 parents 82cb103 + 8f84137 commit 67b4103
Show file tree
Hide file tree
Showing 6 changed files with 397 additions and 168 deletions.
217 changes: 117 additions & 100 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"csv-parse": "^5.6.0",
"csv-stringify": "^6.5.2",
"enigma.js": "^2.14.0",
"esbuild": "^0.24.0",
"esbuild": "^0.24.2",
"form-data": "^4.0.1",
"fs-extra": "^11.2.0",
"handlebars": "^4.7.8",
Expand Down
52 changes: 47 additions & 5 deletions src/lib/cmd/qseow/vistask.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ const visOptions = {
arrows: 'to',
width: 5,
smooth: false,
font: {
size: 22, // Increase font size for text edge labels
},
},
layout: {
randomSeed: 5.5,
Expand Down Expand Up @@ -364,6 +367,17 @@ const prepareFile = async (url) => {
})
.concat(nodesNetwork);

// Add a text with edge count for the edges where edgeCount > 1
// No text for edges where edgeCount === 1
// Update the edge label in taskModel.edges[] with the edge count
taskModel.edges.map((edge) => {
if (edge.edgeCount > 1) {
edge.label = `${edge.edgeCount}`;
} else {
edge.label = '';
}
});

const networkTask = { nodes: nodesNetwork, edges: taskModel.edges };

templateData.nodes = JSON.stringify(nodesNetwork);
Expand Down Expand Up @@ -561,8 +575,8 @@ export async function visTask(options) {
taskNetwork = qlikSenseTasks.taskNetwork;
} else {
// Task id filters specified.
// Get all task chains the tasks are part of,
// then get the rMeta nodeoot nodes of each chain. They will be the starting points for the task tree.
// Get all task chains the tasks are part of, then get the root nodes of each chain.
// They will be the starting points for the task tree.

// Array to keep track of root nodes of task chains
const rootNodes = await qlikSenseTasks.getRootNodesFromFilter();
Expand All @@ -584,7 +598,7 @@ export async function visTask(options) {
});

// Get all nodes that are children of the root nodes
const { nodes, edges, tasks } = await qlikSenseTasks.getNodesAndEdgesFromRootNodes(rootNodes);
const { nodes, edges, tasks } = await qlikSenseTasks.getNetworkFromRootNodes(rootNodes);

taskNetwork = { nodes, edges, tasks };
}
Expand All @@ -594,13 +608,16 @@ export async function visTask(options) {
logger.info('Looking for circular task chains in the task network');

try {
const circularTaskChains = findCircularTaskChains(taskNetwork, logger);
const result = findCircularTaskChains(taskNetwork, logger);

// Errros?
if (circularTaskChains === false) {
if (result === false) {
return false;
}

const circularTaskChains = result.circularTaskChains;
const duplicateEdges = result.duplicateEdges;

// De-duplicate circular task chains (where fromTask.id and toTask.id matches in two different chains).
const deduplicatedCircularTaskChain = circularTaskChains.filter((chain, index, self) => {
return self.findIndex((c) => c.fromTask.id === chain.fromTask.id && c.toTask.id === chain.toTask.id) === index;
Expand All @@ -619,6 +636,31 @@ export async function visTask(options) {
} else {
logger.info('No circular task chains found in task model');
}

// Log duplicate edges, if any were found.
// De-duplicate first. If two edges have the same from and to task id, and the same rule state, it's a duplicate.
const deduplicatedDuplicateEdges = duplicateEdges.filter((edge, index, self) => {
return (
self.findIndex(
(e) =>
e.parentNode.id === edge.parentNode.id &&
e.downstreamNode.id === edge.downstreamNode.id &&
e.ruleState === edge.ruleState
) === index
);
});

if (deduplicatedDuplicateEdges?.length > 0) {
logger.warn('');
logger.warn(`Found ${deduplicatedDuplicateEdges.length} duplicate task triggers in task model, across all examined tasks.`);
for (const duplicate of deduplicatedDuplicateEdges) {
logger.warn(
`Multiple downstream nodes (${duplicate.duplicateEdgeCount}) with the same ID and the same trigger relationship "${duplicate.ruleState}" with the parent node.`
);
logger.warn(` Parent node : [${duplicate.parentNode.id}] "${duplicate.parentNode.completeTaskObject?.name}"`);
logger.warn(` Downstream node : [${duplicate.downstreamNode.id}] "${duplicate.downstreamNode.completeTaskObject?.name}"`);
}
}
} catch (error) {
catchLog('FIND CIRCULAR TASK CHAINS', error);
return false;
Expand Down
49 changes: 36 additions & 13 deletions src/lib/task/class_alltasks.js
Original file line number Diff line number Diff line change
Expand Up @@ -325,16 +325,16 @@ export class QlikSenseTasks {
* - De-duplicate nodes where applicable. Node id is unique, but may appear at different places in the task network.
* - Keep track of edges between nodes and store them in edgesToVisualize
* - Store nodes in nodesToVisualize
* - Detect cyclic dependencies. Log warning if detected.
* - Detect identical, duplicate edges between nodes. Log warning if detected.
* - Detect cyclic dependencies to avoid infinite loops
*
* @param {Array} rootNodes - An array of root node objects from which the extraction begins.
* @returns {Promise<object>} - A promise that resolves to an object containing the nodes and edges.
* Object properties:
* - nodes: Array of nodes,
* - nodes: Array of nodes
* - edges: Array of edges
* - tasks: Array of tasks
*/
async getNodesAndEdgesFromRootNodes(rootNodes) {
async getNetworkFromRootNodes(rootNodes) {
// De-duplicate root nodes
const uniqueRootNodes = rootNodes.filter((node, index, self) => {
return index === self.findIndex((t) => t.id === node.id);
Expand All @@ -353,9 +353,38 @@ export class QlikSenseTasks {
logger.verbose(`No subgraph found for root node ${rootNode.id}.`);
continue;
}
nodesFound.push(...subGraph.nodes);
edgesFound.push(...subGraph.edges);
tasksFound.push(...subGraph.tasks);

// Note: There are corner cases that need to be handled here.
// - Overlapping subnetworks. For example, Root1 > Node1 > Node2 and Root2 > Node1 > Node2.
// In this case, Node1 and Node2 should NOT be duplicated in the final result.

// Only add nodes if they are not already in the nodesFound array
if (subGraph.nodes) {
for (const node of subGraph.nodes) {
if (!nodesFound.find((t) => t.id === node.id)) {
nodesFound.push(node);
}
}
}

// Only add edges if they are not already in the edgesFound array
if (subGraph.edges) {
for (const edge of subGraph.edges) {
if (!edgesFound.find((t) => t.from === edge.from && t.to === edge.to)) {
// Add all edges (there can be multiple edges between the same nodes!) to the edgesFound array
const edges = subGraph.edges.filter((e) => e.from === edge.from && e.to === edge.to);
edgesFound.push(...edges);
}
}
}
// Only add tasks if they are not already in the tasksFound array
if (subGraph.tasks) {
for (const task of subGraph.tasks) {
if (!tasksFound.find((t) => t.taskId === task.taskId)) {
tasksFound.push(task);
}
}
}
}

// De-duplicate nodes using node id
Expand All @@ -373,12 +402,6 @@ export class QlikSenseTasks {
);
});

// De-duplicate edges.
// Edges are identical if they connect the same two nodes, i.e. have the same from and to properties.
edgesFound = edgesFound.filter((edge, index, self) => {
return index === self.findIndex((t) => t.from === edge.from && t.to === edge.to);
});

return { nodes: nodesFound, edges: edgesFound, tasks: tasksFound };
}
}
Loading

0 comments on commit 67b4103

Please sign in to comment.