diff --git a/src/lib/qseow/app.js b/src/lib/qseow/app.js index f1e5122..c51272b 100644 --- a/src/lib/qseow/app.js +++ b/src/lib/qseow/app.js @@ -45,7 +45,7 @@ async function getApps(node, done, appIdsToGet) { }); } - // Return object containing apps, status and statusText + // Return object containing apps, and app IDs that don't exist return { app: response.data, appIdNoExist, @@ -57,6 +57,132 @@ async function getApps(node, done, appIdsToGet) { } } +// Functon to get apps from Qlik Sense server, based on app name +// Parameters: +// - node: the node object +// - appNames: an array of app names to get +async function getAppsByAppName(node, appNames) { + // Make sure appNames is an array + if (!Array.isArray(appNames)) { + node.status({ fill: 'red', shape: 'ring', text: 'error getting apps' }); + node.log(`Error getting apps from Qlik Sense server: appNames is not an array`); + return null; + } + + try { + const { axiosConfig, xref } = getAuth(node); + + // Build url. Quote app names using single quotes + axiosConfig.url = `/qrs/app/full?filter=name%20eq%20'${appNames.join(`'%20or%20name%20eq%20'`)}'&xrfkey=${xref}`; + + // Debug url + node.log(`URL: ${axiosConfig.url}`); + + // Get apps from Qlik Sense server + const response = await axios.request(axiosConfig); + + // Ensure response status is 200 + if (response.status !== 200) { + node.status({ fill: 'red', shape: 'ring', text: 'error getting apps' }); + node.log(`Error getting apps by app name from Qlik Sense server: ${response.status} ${response.statusText}`); + return null; + } + + // Return object containing apps + return { + app: response.data, + }; + } catch (err) { + // Log error + node.error(`Error when getting apps by app name: ${err}`); + return null; + } +} + +// Functon to get apps from Qlik Sense server, based on tag name +// Parameters: +// - node: the node object +// - tagNames: an array of tag names that the apps must have +async function getAppsByTagName(node, tagNames) { + // Make sure appNames is an array + if (!Array.isArray(tagNames)) { + node.status({ fill: 'red', shape: 'ring', text: 'error getting apps' }); + node.log(`Error getting apps from Qlik Sense server: tagNames is not an array`); + return null; + } + + try { + const { axiosConfig, xref } = getAuth(node); + + // Build url. Quote app names using single quotes. Use tags.name instead of name + axiosConfig.url = `/qrs/app/full?filter=tags.name%20eq%20'${tagNames.join(`'%20or%20tags.name%20eq%20'`)}'&xrfkey=${xref}`; + + // Debug url + node.log(`URL: ${axiosConfig.url}`); + + // Get apps from Qlik Sense server + const response = await axios.request(axiosConfig); + + // Ensure response status is 200 + if (response.status !== 200) { + node.status({ fill: 'red', shape: 'ring', text: 'error getting apps' }); + node.log(`Error getting apps by tag name from Qlik Sense server: ${response.status} ${response.statusText}`); + return null; + } + + // Return object containing apps + return { + app: response.data, + }; + } catch (err) { + // Log error + node.error(`Error when getting apps by tag name: ${err}`); + return null; + } +} + +// Functon to get apps from Qlik Sense server, based on stream name +// Parameters: +// - node: the node object +// - streamNames: an array of stream names that the apps must be in +async function getAppsByStreamName(node, streamNames) { + // Make sure appNames is an array + if (!Array.isArray(streamNames)) { + node.status({ fill: 'red', shape: 'ring', text: 'error getting apps' }); + node.log(`Error getting apps from Qlik Sense server: streamNames is not an array`); + return null; + } + + try { + const { axiosConfig, xref } = getAuth(node); + + // Build url. Quote app names using single quotes. Use stream.name instead of name + axiosConfig.url = `/qrs/app/full?filter=stream.name%20eq%20'${streamNames.join(`'%20or%20stream.name%20eq%20'`)}'&xrfkey=${xref}`; + + // Debug url + node.log(`URL: ${axiosConfig.url}`); + + // Get apps from Qlik Sense server + const response = await axios.request(axiosConfig); + + // Ensure response status is 200 + if (response.status !== 200) { + node.status({ fill: 'red', shape: 'ring', text: 'error getting apps' }); + node.log(`Error getting apps by stream name from Qlik Sense server: ${response.status} ${response.statusText}`); + return null; + } + + // Return object containing apps + return { + app: response.data, + }; + } catch (err) { + // Log error + node.error(`Error when getting apps by stream name: ${err}`); + return null; + } +} + // Function to delete apps on Qlik Sense server // Parameters: // - node: The node object @@ -451,9 +577,113 @@ async function updateApps(node, apps) { }; } +// Function to look up apo IDs given app names, colletion names or managed space names +// Parameters: +// node: node object +// lookupSource: object containing entities to look up and translate into app IDs +// +// Return +// Success: An object containing an array of unique app IDs and an array of unique app objects +// Failure: false +async function lookupAppId(node, lookupSource) { + const allAppIds = []; + const allAppObjects = []; + + // lookupSource.appName is an array of app names. + // Build filter string that can be used when calling QRS API + if (lookupSource.appName) { + // Get apps from Qlik Sense server + const resApps = await getAppsByAppName(node, lookupSource.appName); + + // Did we get any apps? If not, report error and return + // Also make sure we got an array + if (!resApps.app || !Array.isArray(resApps.app)) { + node.log('Error getting apps by app name in lookupAppId'); + node.status({ fill: 'red', shape: 'ring', text: 'error getting apps' }); + return false; + } + + // Iterate through array of apps and add app IDs to allAppIds array + resApps.app.forEach((app) => { + allAppIds.push(app.id); + allAppObjects.push(app); + }); + + // Debug + node.log(`allAppIds: ${JSON.stringify(allAppIds, null, 2)}`); + } + + // lookupSource.tagName is an array of tag names. + // Build filter string that can be used when calling QRS API + if (lookupSource.tagName) { + // Get apps from Qlik Sense server, based on which tags they have + const resApps = await getAppsByTagName(node, lookupSource.tagName); + + // Did we get any apps? If not, report error and return + // Also make sure we got an array + if (!resApps.app || !Array.isArray(resApps.app)) { + node.log('Error getting apps by tag name in lookupAppId'); + node.status({ fill: 'red', shape: 'ring', text: 'error getting apps' }); + return false; + } + + // Iterate through array of apps and add app IDs to allAppIds array + resApps.app.forEach((app) => { + allAppIds.push(app.id); + allAppObjects.push(app); + }); + } + + // lookupSource.streamName is an array of stream names. + // Build filter string that can be used when calling QRS API + if (lookupSource.streamName) { + // Get apps from Qlik Sense server, based on which streams they are in + const resApps = await getAppsByStreamName(node, lookupSource.streamName); + + // Did we get any apps? If not, report error and return + // Also make sure we got an array + if (!resApps.app || !Array.isArray(resApps.app)) { + node.log('Error getting apps by stream name in lookupAppId'); + node.status({ fill: 'red', shape: 'ring', text: 'error getting apps' }); + return false; + } + + // Iterate through array of apps and add app IDs to allAppIds array + resApps.app.forEach((app) => { + allAppIds.push(app.id); + allAppObjects.push(app); + }); + } + + // Remove duplicates from the allAppIds array + const uniqueAppIds = [...new Set(allAppIds)]; + + // Get the app objects for the unique app IDs + const uniqueAppObjects = []; + + // Make sure we got an array + if (!uniqueAppIds || !Array.isArray(uniqueAppIds)) { + node.log('Error getting unique app IDs in lookupAppId'); + node.status({ fill: 'red', shape: 'ring', text: 'error getting unique app IDs' }); + return false; + } + + uniqueAppIds.forEach((appId) => { + const appObject = allAppObjects.find((app) => app.id === appId); + uniqueAppObjects.push(appObject); + }); + + // Return object containing unique app IDs and app objects + return { + uniqueAppIds, + uniqueAppObjects, + }; +} + module.exports = { getApps, deleteApps, duplicateApps, updateApps, + lookupAppId, }; diff --git a/src/qscloud/qscloud-app-nr.html b/src/qscloud/qscloud-app-nr.html index ed54772..f5d2762 100644 --- a/src/qscloud/qscloud-app-nr.html +++ b/src/qscloud/qscloud-app-nr.html @@ -224,7 +224,7 @@

App IDs in incoming message

] -

Operations

+

Input and output messages, per operation

Read

Read metadata for one or more apps.

diff --git a/src/qscloud/qscloud-app-nr.js b/src/qscloud/qscloud-app-nr.js index 2c0a47c..015db44 100644 --- a/src/qscloud/qscloud-app-nr.js +++ b/src/qscloud/qscloud-app-nr.js @@ -229,7 +229,7 @@ module.exports = function (RED) { return false; } - // Concatenate all app IDs into one array + // Concatenate all app IDs and objects into output message outMsg.payload.appId.push(...uniqueAppIds); outMsg.payload.appObj.push(...uniqueAppObjects); diff --git a/src/qseow/qseow-app-nr.html b/src/qseow/qseow-app-nr.html index 6393c3f..d973167 100644 --- a/src/qseow/qseow-app-nr.html +++ b/src/qseow/qseow-app-nr.html @@ -77,6 +77,15 @@ if (appSourceInput2.val() === 'msg-in') { appIdEditorRow.hide(); } + } else if (appOpInput.val() === 'app-id-lookup') { + // Hide the app source selection that offers both "predefined" and "msg-in" options + appSourceInput1Row.hide(); + + // Hide the app source selection that only offers option "msg-in" + appSourceInput2Row.hide(); + + // Hide the app ID editor + appIdEditorRow.hide(); } else if (appOpInput.val() === 'dupl') { // Hide the app source selection that offers both "predefined" and "msg-in" options appSourceInput1Row.hide(); @@ -124,6 +133,7 @@ + @@ -155,6 +165,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.
  • Duplicate: Duplicate one or more apps.
  • @@ -163,16 +174,7 @@

    Shared properties

    Operation-specific properties

    @@ -278,6 +280,29 @@

    Input message

  • payload.appIdNoExist: Array of app IDs, one for each app that was not found on the Sense server.
  • +

    App ID lookup

    +

    Lookup app IDs given app/tag/stream names.

    +

    Useful if there is a need to get app IDs that are needed later in a flow.

    + + +

    Input message

    +

    The input message may contain zero or more of the following properties:

    + + +

    Output message

    +

    The output message includes the following properties:

    + +

    Duplicate

    Duplicate one or more apps.

    All info needed for the update operation must be in the input message.

    diff --git a/src/qseow/qseow-app-nr.js b/src/qseow/qseow-app-nr.js index 0d66a62..05f630c 100644 --- a/src/qseow/qseow-app-nr.js +++ b/src/qseow/qseow-app-nr.js @@ -1,3 +1,4 @@ +const { lookupAppId } = require('../lib/qseow/app'); const { getApps, deleteApps, duplicateApps, updateApps } = require('../lib/qseow/app'); const { getCandidateAppsPredefAndIncoming } = require('../lib/qseow/appconfig'); @@ -20,14 +21,6 @@ module.exports = function (RED) { payload: {}, }; - // // Process incoming apps from the incoming message - // let appIdsPredefined; - // let appsIncoming; - // let appsCreated = []; - - // const appsProcess = []; - // const appIdsNoExists = []; - // Which operation to perform? if (node.op === 'c') { // Create apps @@ -213,6 +206,67 @@ module.exports = function (RED) { // Send message to output 1 send(outMsg1); + } else if (node.op === 'app-id-lookup') { + // Lookup app IDs + node.log('Looking up app IDs on Qlik Sense server...'); + node.status({ fill: 'yellow', shape: 'dot', text: 'looking up app IDs' }); + + // Make sure there is a msg.payload object + if (!msg.payload) { + node.status({ fill: 'red', shape: 'ring', text: 'msg.payload is missing' }); + done('msg.payload is missing'); + return; + } + + // If msg.payload.appName exists it should be an array + if (msg.payload.appName && !Array.isArray(msg.payload.appName)) { + node.status({ fill: 'red', shape: 'ring', text: 'msg.payload.appName is not an array' }); + done('msg.payload.appName is not an array'); + 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'); + return; + } + + // Add app arrays to out message + outMsg1.payload = { appId: [], appObj: [] }; + + try { + // Get app info from Qlik Sense server + const { uniqueAppIds, uniqueAppObjects } = await lookupAppId(node, msg.payload); + + // Di we get any result in quniqueAppIds? + if (!uniqueAppIds || !Array.isArray(uniqueAppIds)) { + node.log('Error getting app IDs in lookupAppId'); + node.status({ fill: 'red', shape: 'ring', text: 'error getting app IDs' }); + return; + } + + // Did we get any results in uniqueAppObjects? + if (!uniqueAppObjects || !Array.isArray(uniqueAppObjects)) { + node.log('Error getting app objects in lookupAppId'); + node.status({ fill: 'red', shape: 'ring', text: 'error getting app objects' }); + return; + } + + // Concatenate all app IDs and objects into output message + outMsg1.payload.appId.push(...uniqueAppIds); + outMsg1.payload.appObj.push(...uniqueAppObjects); + + // Send message to output 1 + send(outMsg1); + } catch (err) { + node.error(err); + done(err); + } + + // Log success + node.log(`Found ${outMsg1.payload.appId.length} matching apps on Qlik Sense server.`); + node.status({ fill: 'green', shape: 'dot', text: 'apps IDs retrieved' }); } else if (node.op === 'dupl') { // Duplicate apps node.log('Duplicating apps on Qlik Sense server...');