Skip to content

Commit

Permalink
feat(qseow): Add operation task-id-lookup operation
Browse files Browse the repository at this point in the history
Implements #85
  • Loading branch information
mountaindude committed Sep 28, 2023
1 parent 1807bac commit fbd0d4e
Show file tree
Hide file tree
Showing 5 changed files with 318 additions and 30 deletions.
5 changes: 2 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@

## [0.2.6](https://github.com/ptarmiganlabs/ctrl-q-nr/compare/v0.2.5...v0.2.6) (2023-09-18)


### Bug Fixes

* Pass-through of parts/reset message properties ([2db6663](https://github.com/ptarmiganlabs/ctrl-q-nr/commit/2db66632b36e968b5ab14e36d37a34da2d4cf769)), closes [#81](https://github.com/ptarmiganlabs/ctrl-q-nr/issues/81)
* **qseow:** Respect ignore-anauthorised-cert settings for all calls to QSEoW ([7427151](https://github.com/ptarmiganlabs/ctrl-q-nr/commit/7427151f8a9a0a9f4ec276b049b8f2cc184f0fc7))
- Pass-through of parts/reset message properties ([2db6663](https://github.com/ptarmiganlabs/ctrl-q-nr/commit/2db66632b36e968b5ab14e36d37a34da2d4cf769)), closes [#81](https://github.com/ptarmiganlabs/ctrl-q-nr/issues/81)
- **qseow:** Respect ignore-anauthorised-cert settings for all calls to QSEoW ([7427151](https://github.com/ptarmiganlabs/ctrl-q-nr/commit/7427151f8a9a0a9f4ec276b049b8f2cc184f0fc7))

## [0.2.5](https://github.com/ptarmiganlabs/ctrl-q-nr/compare/v0.2.4...v0.2.5) (2023-09-18)

Expand Down
172 changes: 172 additions & 0 deletions src/lib/qseow/task.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,96 @@
const axios = require('axios');

const { getTaskTags } = require('./tag');
const { getAuth } = require('./auth');

// Function to get tasks from Qlik Sense server, based on task names
// Parameters:
// - node: the node object
// - taskNames: an array of task names
// Return: an object containing an array of task objects
async function getTasksByName(node, taskNames) {
// Make sure appNames is an array
if (!Array.isArray(taskNames)) {
node.status({ fill: 'red', shape: 'ring', text: 'error getting tasks by name' });
node.log(`Error getting tasks from Qlik Sense server: taskNames is not an array.`);
return null;
}

try {
// Set up authentication
const { axiosConfig, xref } = getAuth(node);

// Build url that can be used with QRS API. Quote task names with single quotes
axiosConfig.url = `/qrs/task/full?filter=name%20eq%20'${taskNames.join(`'%20or%20name%20eq%20'`)}'&xrfkey=${xref}`;

// Debug url
node.log(`URL: ${axiosConfig.url}`);

// Get tasks from Qlik Sense server
const response = await axios(axiosConfig);

// Ensure response status is 200
if (response.status !== 200) {
node.status({ fill: 'red', shape: 'ring', text: 'error getting tasks by name' });
node.log(`Error getting tasks from Qlik Sense server: ${response.status} ${response.statusText}`);
return null;
}

// Return object containing an array of task objects
return {
task: response.data,
};
} catch (err) {
// Log error
node.error(`Error when getting tasks by name: ${err}`);
return null;
}
}

// Function to get tasks from Qlik Sense server, based on tag names
// Parameters:
// - node: the node object
// - tagNames: an array of tag names
// Return: an object called "tag" containing an array of task objects
async function getTasksByTagName(node, tagNames) {
// Make sure tagNames is an array
if (!Array.isArray(tagNames)) {
node.status({ fill: 'red', shape: 'ring', text: 'error getting tasks by tag name' });
node.log(`Error getting tasks from Qlik Sense server: tagNames is not an array.`);
return null;
}

try {
// Set up authentication
const { axiosConfig, xref } = getAuth(node);

// Build url that can be used with QRS API. Quote task names with single quotes
axiosConfig.url = `/qrs/task/full?filter=tags.name%20eq%20'${tagNames.join(`'%20or%20tags.name%20eq%20'`)}'&xrfkey=${xref}`;

// Debug url
node.log(`URL: ${axiosConfig.url}`);

// Get tasks from Qlik Sense server
const response = await axios(axiosConfig);

// Ensure response status is 200
if (response.status !== 200) {
node.status({ fill: 'red', shape: 'ring', text: 'error getting tasks by tag name' });
node.log(`Error getting tasks from Qlik Sense server: ${response.status} ${response.statusText}`);
return null;
}

// Return object containing an array of task objects
return {
task: response.data,
};
} catch (err) {
// Log error
node.error(`Error when getting tasks by tag name: ${err}`);
return null;
}
}

// Function to start tasks on Qlik Sense server
// Parameters:
// - node: the node object
Expand Down Expand Up @@ -149,6 +238,89 @@ async function startTasks(node, done, taskIdsToStart) {
};
}

// Function to look up task IDs given task names or tag names
// Parameters:
// - node: the node object
// - lookupSource: object containing entities to look up and translate into task IDs

// Return
// Success: An object containing an array of unique task IDs and an array of unique task objects
// Failure: false
async function lookupTaskId(node, lookupSource) {
const allTaskIds = [];
const allTaskObjects = [];

// lookupSource.taskName is an array of task names
// Build filter string that can be used with QRS API
if (lookupSource.taskName) {
// Get tasks from Qlik Sense server
// Return a task property, which contains an array task objects
const resTasks = await getTasksByName(node, lookupSource.taskName);

// Make sure we got a response that contains the correct data
if (!resTasks || !resTasks.task || !Array.isArray(resTasks.task)) {
node.error('Error when getting tasks by name in lookupTaskIds()');
node.status({ fill: 'red', shape: 'ring', text: 'error getting tasks by name' });
return false;
}

// Loop through array of tasks and add task IDs to allTaskIds array
resTasks.task.forEach((task) => {
allTaskIds.push(task.id);
allTaskObjects.push(task);
});

// Debug
node.log(`allTaskIds: ${JSON.stringify(allTaskIds, null, 4)}`);
}

// lookupSource.tagName is an array of tag names
// Build filter string that can be used with QRS API
if (lookupSource.tagName) {
// Get tasks from Qlik Sense server, based on which tag names they have
const resTasks = await getTasksByTagName(node, lookupSource.tagName);

// Make sure we got a response that contains the correct data
if (!resTasks || !resTasks.task || !Array.isArray(resTasks.task)) {
node.error('Error when getting tasks by tag name in lookupTaskIds()');
node.status({ fill: 'red', shape: 'ring', text: 'error getting tasks by tag name' });
return false;
}

// Loop through array of tasks and add task IDs to allTaskIds array
resTasks.task.forEach((task) => {
allTaskIds.push(task.id);
allTaskObjects.push(task);
});
}

// Remove duplicates from allTaskIds array
const uniqueTaskIds = [...new Set(allTaskIds)];

// Get the task objects for the unique task IDs
const uniqueTaskObjects = [];

// Make sure we got an array
if (!uniqueTaskIds || !Array.isArray(uniqueTaskIds)) {
node.error('Error when getting unique task IDs in lookupTaskIds()');
node.status({ fill: 'red', shape: 'ring', text: 'error getting unique task IDs' });
return false;
}

// Loop through array of unique task IDs and get the task objects
uniqueTaskIds.forEach((taskId) => {
const taskObject = allTaskObjects.find((task) => task.id === taskId);
uniqueTaskObjects.push(taskObject);
});

// Return object containing unique task IDs and unique task objects
return {
uniqueTaskIds,
uniqueTaskObjects,
};
}

module.exports = {
startTasks,
lookupTaskId,
};
8 changes: 4 additions & 4 deletions src/qseow/qseow-app-nr.js
Original file line number Diff line number Diff line change
Expand Up @@ -285,10 +285,10 @@ module.exports = function (RED) {
return;
}

// If msg.payload.spaceName exists it should be an array
if (msg.payload.spaceName && !Array.isArray(msg.payload.spaceName)) {
node.status({ fill: 'red', shape: 'ring', text: 'msg.payload.spaceName is not an array' });
done('msg.payload.spaceName is not an array');
// If msg.payload.streamName exists it should be an array
if (msg.payload.streamName && !Array.isArray(msg.payload.streamName)) {
node.status({ fill: 'red', shape: 'ring', text: 'msg.payload.streamName is not an array' });
done('msg.payload.streamName is not an array');
return;
}

Expand Down
76 changes: 63 additions & 13 deletions src/qseow/qseow-task-nr.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
defaults: {
name: { value: '' },
server: { value: '', type: 'qseow-sense-server' },
op: { value: 'start', options: ['start'], required: true },
op: { value: 'start', options: ['start', 'task-id-lookup'], required: true },
taskId: { value: '', required: false },
taskSource1: { value: 'predefined', options: ['predefined', 'msg-in'] },
taskSource2: { value: 'msg-in', options: ['msg-in'] },
},
inputs: 1,
outputs: 1,
Expand All @@ -33,7 +34,14 @@
// eslint-disable-next-line no-undef
const taskSourceInput1 = $('#node-input-taskSource1');
// eslint-disable-next-line no-undef
const taskSourceInput1Row = $('#node-input-taskSource1').parent();
// eslint-disable-next-line no-undef
const taskSourceInput2 = $('#node-input-taskSource2');
// eslint-disable-next-line no-undef
const taskSourceInput2Row = $('#node-input-taskSource2').parent();
// eslint-disable-next-line no-undef
const taskIdEditorRow = $('#node-input-taskId-editor').parent();

function updateTaskIdSource() {
if (appOpInput.val() === 'c') {
// Create task
Expand All @@ -44,18 +52,34 @@
} else if (appOpInput.val() === 'd') {
// Delete task
taskIdEditorRow.hide();
} else if (appOpInput.val() === 'task-id-lookup') {
// Hide the task source selection that offers both "predefined" and "msg-in" options
taskSourceInput1Row.hide();

// Show the task source selection that offers only the "msg-in" option
taskSourceInput2Row.show();

// Hide the task ID editor
taskIdEditorRow.hide();
} else if (appOpInput.val() === 'start') {
// Start task
// Show the task source selection that offers both "predefined" and "msg-in" options
taskSourceInput1Row.show();

// Hide the task source selection that offers only the "msg-in" option
taskSourceInput2Row.hide();

if (taskSourceInput1.val() === 'predefined') {
// Show the task source selection that offers both "predefined" and "msg-in" options
taskIdEditorRow.show();
} else {
// Hide the task ID editor
taskIdEditorRow.hide();
}
}
}
appOpInput.on('change', updateTaskIdSource);
taskSourceInput1.on('change', updateTaskIdSource);
taskSourceInput2.on('change', updateTaskIdSource);
updateTaskIdSource();
},
oneditsave() {
Expand Down Expand Up @@ -87,6 +111,7 @@
<option value="u">Update</option>
<option value="d">Delete</option> -->
<option value="start">Start</option>
<option value="task-id-lookup">Task ID lookup</option>
</select>
<div class="form-row">
<label for="node-input-taskSource1"><i class="fa fa-cog"></i> Source of task IDs</label>
Expand All @@ -95,6 +120,12 @@
<option value="msg-in">Task IDs in incoming message</option>
</select>
</div>
<div class="form-row">
<label for="node-input-taskSource2"><i class="fa fa-cog"></i> Source of task IDs</label>
<select id="node-input-taskSource2">
<option value="msg-in">Task IDs in incoming message</option>
</select>
</div>
<div class="form-row" id="node-input-taskId-editor-row" style="display:none">
<label for="node-input-taskId-editor"><i class="fa fa-tasks"></i> Task IDs</label>
<div style="height: 250px; min-height:150px;" class="node-text-taskId" id="node-input-taskId-editor"></div>
Expand Down Expand Up @@ -124,6 +155,7 @@ <h2>Shared properties</h2>
<strong>Operation:</strong> The operation to perform on the task(s). The options are:
<ul>
<li><strong>Start:</strong> Start one or more tasks.</li>
<li><strong>Task ID lookup:</strong> Lookup task IDs based on task names, tags etc.</li>
</ul>
</li>
</ul>
Expand All @@ -134,10 +166,7 @@ <h3>Start task properties</h3>
<p>The tasks to be started can be defined in one of two ways:</p>
<ul>
<li><strong>Predefined task IDs:</strong> The task IDs are defined in the node itself.</li>
<li>
<strong>Task IDs in incoming message:</strong> The task IDs are in the <code>payload.taskId</code> property of the incoming
message.
</li>
<li><strong>Task IDs in incoming message:</strong> The task IDs are in the incoming message.</li>
</ul>

<h4>Predefined task IDs</h4>
Expand Down Expand Up @@ -168,11 +197,20 @@ <h4>Task IDs in incoming message</h4>
</pre
>

<h2>Input</h2>
<p>The node's logic will trigger when it receives a message on its input.</p>
<h2>Input and output messages, per operation</h2>

<p>
Different operations use different properties in the input message.<br />
Similarly, different operations set different properties in the output message.
</p>

<h3>Start task</h3>

<h4>Input message</h4>
<p>If task IDs in the incoming message are used, those IDs must be in the <code>payload.taskId</code> array.</p>

<h2>Output</h2>
<p>The node will output a message with the following properties:</p>
<h4>Output message</h4>
<p>The output message includes the following properties:</p>
<ul>
<li><strong>payload.started.reloadTask:</strong> Array of reload task objects that were started.</li>
<li><strong>payload.started.externalProgramTask:</strong> Array of external program task objects that were started.</li>
Expand All @@ -181,7 +219,19 @@ <h2>Output</h2>
<li><strong>payload.taskIdNoExist:</strong> Array of task IDs that does not exist on the Sense server.</li>
</ul>

<h2>Example usage</h2>
<p>Here is an example of how to use the qseow-task node in a Node-RED flow:</p>
<pre></pre>
<h3>Task ID lookup</h3>

<h4>Input message</h4>
<p>The input message includes the following properties:</p>
<ul>
<li><strong>payload.taskName:</strong> Array of task names to look up.</li>
<li><strong>payload.tagName:</strong> Array of tag names to look up.</li>
</ul>

<h4>Output message</h4>
<p>The output message includes the following properties:</p>
<ul>
<li><strong>payload.taskId:</strong> Array of task IDs, one for each task that matched the search criteria.</li>
<li><strong>payload.taskObj:</strong> Array of task objects, one for each task that matched the search criteria.</li>
</ul>
</script>

0 comments on commit fbd0d4e

Please sign in to comment.