Skip to content

Commit

Permalink
feat(qseow): Add app ID lookup operation to qseow-app node
Browse files Browse the repository at this point in the history
Implements #65
  • Loading branch information
mountaindude committed Sep 13, 2023
1 parent f502b31 commit b18afa7
Show file tree
Hide file tree
Showing 5 changed files with 330 additions and 21 deletions.
232 changes: 231 additions & 1 deletion src/lib/qseow/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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,
};
2 changes: 1 addition & 1 deletion src/qscloud/qscloud-app-nr.html
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ <h3>App IDs in incoming message</h3>
]
</pre>

<h2>Operations</h2>
<h2>Input and output messages, per operation</h2>

<h3>Read</h3>
<p>Read metadata for one or more apps.</p>
Expand Down
2 changes: 1 addition & 1 deletion src/qscloud/qscloud-app-nr.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
45 changes: 35 additions & 10 deletions src/qseow/qseow-app-nr.html
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -124,6 +133,7 @@
<option value="r">Read</option>
<option value="u">Update</option>
<option value="d">Delete</option>
<option value="app-id-lookup">App ID lookup</option>
<option value="dupl">Duplicate</option>
</select>
</div>
Expand Down Expand Up @@ -155,6 +165,7 @@
<li><strong>Read:</strong> Read metadata for one or more apps.</li>
<li><strong>Update:</strong> Update metadata for one or more apps.</li>
<li><strong>Delete:</strong> Delete one or more apps.</li>
<li><b>App ID lookup</b> - Lookup all app IDs associated with specified apps or spaces.</li>
<li><strong>Duplicate:</strong> Duplicate one or more apps.</li>
</ul>

Expand All @@ -163,16 +174,7 @@ <h2>Shared properties</h2>
<ul>
<li><strong>Name:</strong> The name of the node.</li>
<li><strong>Qlik Sense server:</strong> The Qlik Sense server to connect to.</li>
<li>
<strong>Operation:</strong> The operation to perform. The options are:
<ul>
<!-- <li><strong>Create:</strong> Create one or more apps.</li> -->
<li><strong>Read:</strong> Read metadata for one or more apps.</li>
<li><strong>Update:</strong> Update metadata for one or more apps.</li>
<li><strong>Delete:</strong> Delete one or more apps.</li>
<li><strong>Duplicate:</strong> Duplicate one or more apps.</li>
</ul>
</li>
<li><strong>Operation:</strong> The operation to perform. See above for options.</li>
</ul>

<h2>Operation-specific properties</h2>
Expand Down Expand Up @@ -278,6 +280,29 @@ <h4>Input message</h4>
<li><strong>payload.appIdNoExist:</strong> Array of app IDs, one for each app that was not found on the Sense server.</li>
</ul>

<h3>App ID lookup</h3>
<p>Lookup app IDs given app/tag/stream names.</p>
<p>Useful if there is a need to get app IDs that are needed later in a flow.</p>
<ul>
<li>If a specified app/tag/stream name matches more than one one app, app IDs for all matching apps will be returned.</li>
<li>App IDs will be de-duplicated by the node, i.e. there will not be any duplicates in the returned app IDs.</li>
</ul>

<h4>Input message</h4>
<p>The input message may contain zero or more of the following properties:</p>
<ul>
<li><strong>payload.appName:</strong> Array of app names, one for each app to be looked up.</li>
<li><strong>payload.tagName:</strong> Array of tag names, one for each tag to be looked up.</li>
<li><strong>payload.streamName:</strong> Array of stream names, one for each stream to be looked up.</li>
</ul>

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

<h3>Duplicate</h3>
<p>Duplicate one or more apps.</p>
<p>All info needed for the update operation must be in the input message.</p>
Expand Down
Loading

0 comments on commit b18afa7

Please sign in to comment.