From 1807bac46f5df3533dd1cedfebad3434128529db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=B6ran=20Sander?= Date: Thu, 28 Sep 2023 23:38:08 +0200 Subject: [PATCH 1/3] fix(qseow): Test streams rather than spaces for QSEoW Fixes #86 --- src/lib/qseow/app.js | 4 ++-- src/qseow/qseow-app-nr.html | 2 +- src/qseow/qseow-app-nr.js | 5 ++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/lib/qseow/app.js b/src/lib/qseow/app.js index 43edb26..716b9c7 100644 --- a/src/lib/qseow/app.js +++ b/src/lib/qseow/app.js @@ -639,7 +639,7 @@ async function updateApps(node, apps) { }; } -// Function to look up apo IDs given app names, colletion names or managed space names +// Function to look up apo IDs given app names, tag names or stream names // Parameters: // node: node object // lookupSource: object containing entities to look up and translate into app IDs @@ -725,7 +725,7 @@ async function lookupAppId(node, lookupSource) { // Make sure we got an array if (!uniqueAppIds || !Array.isArray(uniqueAppIds)) { - node.log('Error getting unique app IDs in lookupAppId'); + node.error('Error getting unique app IDs in lookupAppId'); node.status({ fill: 'red', shape: 'ring', text: 'error getting unique app IDs' }); return false; } diff --git a/src/qseow/qseow-app-nr.html b/src/qseow/qseow-app-nr.html index ef02176..facfe83 100644 --- a/src/qseow/qseow-app-nr.html +++ b/src/qseow/qseow-app-nr.html @@ -190,7 +190,7 @@
  • Read: Read metadata for one or more apps.
  • Update: Update metadata for one or more apps.
  • Delete: Delete one or more apps.
  • -
  • App ID lookup - Lookup all app IDs associated with specified apps or spaces.
  • +
  • App ID lookup - Lookup all app IDs associated with specified apps, tags etc.
  • Duplicate: Duplicate one or more apps.
  • Get reload script: Get the reload script for one or more apps.
  • Set reload script: Set the reload script for one or more apps.
  • diff --git a/src/qseow/qseow-app-nr.js b/src/qseow/qseow-app-nr.js index 59aa2ba..5de55db 100644 --- a/src/qseow/qseow-app-nr.js +++ b/src/qseow/qseow-app-nr.js @@ -1,3 +1,4 @@ +/* eslint-disable no-underscore-dangle */ const { getApps, deleteApps, duplicateApps, updateApps, getAppLoadScript, setAppLoadScript, lookupAppId } = require('../lib/qseow/app'); const { getCandidateAppsPredefAndIncoming } = require('../lib/qseow/appconfig'); @@ -298,7 +299,7 @@ module.exports = function (RED) { // Get app info from Qlik Sense server const { uniqueAppIds, uniqueAppObjects } = await lookupAppId(node, msg.payload); - // Di we get any result in quniqueAppIds? + // Did we get any result in uniqueAppIds? if (!uniqueAppIds || !Array.isArray(uniqueAppIds)) { node.log('Error getting app IDs in lookupAppId'); node.status({ fill: 'red', shape: 'ring', text: 'error getting app IDs' }); @@ -320,9 +321,7 @@ module.exports = function (RED) { if (msg.parts) { outMsg1.parts = msg.parts; } - // eslint-disable-next-line no-underscore-dangle if (msg._msgid) { - // eslint-disable-next-line no-underscore-dangle outMsg1._msgid = msg._msgid; } if (msg.reset) { From fbd0d4ee91a9e22f53e05bc3acfa86f6b7ef1601 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=B6ran=20Sander?= Date: Fri, 29 Sep 2023 00:20:17 +0200 Subject: [PATCH 2/3] feat(qseow): Add operation task-id-lookup operation Implements #85 --- CHANGELOG.md | 5 +- src/lib/qseow/task.js | 172 +++++++++++++++++++++++++++++++++++ src/qseow/qseow-app-nr.js | 8 +- src/qseow/qseow-task-nr.html | 76 +++++++++++++--- src/qseow/qseow-task-nr.js | 87 ++++++++++++++++-- 5 files changed, 318 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4d67e6..32e4378 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/src/lib/qseow/task.js b/src/lib/qseow/task.js index 22d49bf..b9a226a 100644 --- a/src/lib/qseow/task.js +++ b/src/lib/qseow/task.js @@ -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 @@ -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, }; diff --git a/src/qseow/qseow-app-nr.js b/src/qseow/qseow-app-nr.js index 5de55db..40c72df 100644 --- a/src/qseow/qseow-app-nr.js +++ b/src/qseow/qseow-app-nr.js @@ -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; } diff --git a/src/qseow/qseow-task-nr.html b/src/qseow/qseow-task-nr.html index 8638756..558674c 100644 --- a/src/qseow/qseow-task-nr.html +++ b/src/qseow/qseow-task-nr.html @@ -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, @@ -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 @@ -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() { @@ -87,6 +111,7 @@ --> +
    @@ -95,6 +120,12 @@
    +
    + + +