ibm-igc-rest
Version:
Re-usable functions for interacting with IBM Information Governance Catalog's REST API
1,007 lines (916 loc) • 37.1 kB
JavaScript
/***
* Copyright 2016 IBM Corp. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
;
const request = require('request').defaults({jar: true});
const fs = require('fs');
const path = require('path');
const util = require('util');
/**
* Re-usable functions for interacting with IBM Information Governance Catalog's REST API
* @module ibm-igc-rest
* @license Apache-2.0
* @requires request
* @example
* // retrieves all of the "types" from IGC's REST API
* var igcrest = require('ibm-igc-rest');
* var commons = require('ibm-iis-commons');
* var restConnect = new commons.RestConnection("isadmin", "isadmin", "hostname", "9445");
* igcrest.setConnection(restConnect);
* igcrest.getTypes(function(err, resTypes) {
* // do something with the types within resTypes object
* });
*/
const RestIGC = (function() {
const hmDataContainerTypesToChildren = {
"database_table": "database_columns",
"data_file_record": "data_file_fields"
};
const hmDataChildrenToContainerTypes = {
"database_column": "database_table",
"data_file_field": "data_file_record"
};
let _restConnect = null;
/**
* Set the connection for the REST API
*
* @param {RestConnection} restConnect - RestConnection object, from ibm-iis-commons
*/
const setConnection = function(restConnect) {
_restConnect = restConnect;
};
/**
* Setup a re-usable session against the IGC REST API -- a connection must first
* be setup
* @see module:ibm-igc-rest.setConnection
* @see module:ibm-igc-rest.closeSession
*
* @returns {Promise} when resolved contains the opened sessionId
*/
const openSession = function() {
return new Promise(function(resolve, reject) {
// basically we'll setup a new session by running a very simple search,
// and picking up the session cookie from the returned response
makeRequest('POST', "/ibm/iis/igc-rest/v1/search/", {"types":['label'],"properties":['name']}, 'application/json').then(function(results) {
if (results.res.hasOwnProperty("headers") && results.res.headers.hasOwnProperty("set-cookie")) {
_restConnect.markSessionOpen();
resolve();
} else {
reject("ERROR: Unable to open a session.");
}
});
});
};
/**
* Logout of (close) a re-usable session against the IGC REST API
* @see module:ibm-igc-rest.setConnection
* @see module:ibm-igc-rest.openSession
*
* @returns {Promise} when resolved will have logged out / closed the session
*/
const closeSession = function() {
return new Promise(function(resolve, reject) {
getOther("/ibm/iis/igc-rest/v1/logout/", 200).then(function(success) {
_restConnect.markSessionClosed();
resolve();
}, function(failure) {
reject("ERROR: Unable to close session -- " + JSON.stringify(failure));
});
});
};
/**
* Replace any variables (text that starts with '$') that show up in a query
*
* @param {Object} json - the query (as a JSON object)
* @param {Dict} variables - a dictionary indexed by variable name
* @returns {Object}
*/
const replaceQueryVars = function(json, variables) {
if (json.hasOwnProperty("where")) {
for (let i = 0; i < json.where.conditions.length; i++ ) {
if (json.where.conditions[i].hasOwnProperty("value")) {
let value = json.where.conditions[i].value;
if (value.indexOf("$") === 0) {
value = value.substring(1, value.length);
json.where.conditions[i].value = variables[value];
}
}
}
}
return json;
};
/**
* Replace '$relatedObjectRID' in the query with the provided RID
*
* @param {Object} json - the query (as a JSON object)
* @param {string} rid - the RID to inject into the query
* @returns {Object}
*/
const replaceRelatedUpdateVars = function(json, rid) {
return JSON.parse(JSON.stringify(json).replace("$relatedObjectRID", rid));
};
/**
* Prepare the provided value for use via the REST API:
* - if XML, leave it as-is
* - if a string, surround it in double-quotes
* - if an object, convert to a JSON string
*
* @param {Object} value - the value to prepare
* @param {string} [contentType] - the type of content received as input
* @returns {Object}
*/
const _prepValue = function(value, contentType) {
if (contentType !== null && (contentType === "application/xml" || contentType === "text/xml" || contentType === "multipart/form-data")) {
// Do nothing -- we should not change the value of XML or multipart form data (i.e. uploaded files)...
} else if (typeof(value) === "string") {
value = "\"" + value + "\"";
} else {
value = JSON.stringify(value);
}
return value;
};
/**
* Checks for any error in the request, based on a non-successful status code
*
* @param {Object} res - the full response object from the request
* @param {integer} statusCodeSuccess - the numeric status code that indicates success
* @param {Function} reject - the reject function of the promise being handled
* @returns {string} a description of the error (if not using Promises)
*/
const _checkRequestError = function(res, statusCodeSuccess, reject) {
let err = null;
if (res.statusCode !== statusCodeSuccess) {
err = "Unsuccessful request " + res.statusCode;
err += "\n headers: " + util.inspect(res.headers);
reject(new Error(err));
}
return err;
};
/**
* Verify that one and only one item was returned by a query
*
* @param {Object} json - the data returned from a query (as a JSON object)
* @returns {Object} the single item returned
* @throws will throw an error if either no item or multiple items are found
*/
const verifySingleItem = function(json) {
if (json.items.length === 0) {
throw new Error("Did not find the entry to update.");
} else if (json.items.length > 1) {
throw new Error("Found multiple entries to update.");
}
return json.items[0];
};
/**
* Retrieve the first item returned by a query
*
* @param {Object} json - the data returned from a query (as a JSON object)
* @returns {Object}
* @throws will throw an error if no items are found
*/
const getSingleItem = function(json) {
if (json.items.length === 0) {
throw new Error("Did not find the entry to update.");
}
return json.items[0];
};
/**
* Log to the console the results of an update
*
* @param {Object} results - the data returned from an update (as a JSON object)
*/
const logUpdateResults = function(results) {
console.log("SUCCESS: The following updates were made -");
for (const key in results) {
if (results.hasOwnProperty(key)) {
console.log(" - " + key + " = " + results[key]);
}
}
};
/**
* Compare two objects for sorting purposes
*
* @returns {integer} -1 (a<b), 0 (a=b), 1 (a>b)
*/
const compareObjectsForSorting = function(a, b) {
if (a._id < b._id) {
return -1;
} else if (a._id > b._id) {
return 1;
} else {
return 0;
}
};
/**
* Retrieve the RID of the container of an asset (for example, the database table of a database column)
*
* @param {Object} assetObj - the asset object, as returned from REST API
* @returns {string} the RID of assetObj's container
*/
const getAssetContainerId = function(assetObj) {
const ctx = assetObj._context;
const assetType = assetObj._type;
const containerType = hmDataChildrenToContainerTypes[assetType];
for (let i = 0; i < ctx.length; i++) {
const type = ctx[i]._type;
if (type === containerType) {
return ctx[i]._id;
}
}
};
/**
* Get an identity object for the provided asset's container
*
* @param {Object} assetCtx - the context object for the asset
* @param {Object} containerId - the RID of the asset's container
* @param {identityCallback} callback - callback that handles the response, since further requests may be needed
*/
const getContainerIdentity = function(assetCtx, containerId, callback) {
const argsReceived = Array.prototype.splice.call(arguments, 3);
const identity = {};
identity._id = containerId;
let dataFileId = "";
for (let i = 0; i < assetCtx.length; i++) {
const type = assetCtx[i]._type;
if (type === "data_file") {
dataFileId = assetCtx[i]._id;
}
const name = assetCtx[i]._name;
identity[type] = name;
}
// Unfortunately with files we need a parent object, this non-blocking IO request
// in one instance but not others could cause headaches...
if (dataFileId !== "") {
argsReceived.unshift(dataFileId, "data_file", ["path"], 1, false, function(err, resDataFile) {
const argsReceived = Array.prototype.splice.call(arguments, 2);
identity.path = resDataFile.path;
argsReceived.unshift(err, identity);
return callback.apply(this, argsReceived);
});
getAssetPropertiesById.apply(this, argsReceived);
} else {
argsReceived.unshift(null, identity);
return callback.apply(this, argsReceived);
}
};
/**
* Get an identity object for the provided asset
*
* @param {Object} assetObj - the asset for which to get an identity object
* @param {Dict} containerIdentities - a dict cache of container identities
* @returns {Object} the identity of this object
*/
const getAssetIdentity = function(assetObj, containerIdentities) {
const containerId = getAssetContainerId(assetObj);
const containerIdentity = containerIdentities[containerId]; // this is a reference, not a copy!!!
const identity = {};
for (const key in containerIdentity) {
if (containerIdentity.hasOwnProperty(key)) {
identity[key] = containerIdentity[key];
}
}
identity._id = assetObj._id;
identity[assetObj._type] = assetObj._name;
return identity;
};
/**
* Constructs an asset identity string provide a REST API item (which must include '_context')
*
* @param {Object} restItem - a single entry from the 'items' array of a REST API response, including '_context' member
* @param {string} [delimiter] - a delimiter to use for separating the components of the identity (default: '::')
* @returns {string}
*/
const getItemIdentityString = function(restItem, delimiter) {
let identity = "";
if (delimiter === undefined || delimiter === "") {
delimiter = "::";
}
const aCtx = restItem._context;
for (let i = 0; i < aCtx.length; i++) {
identity = identity + delimiter + aCtx[i]._name;
}
identity = identity + delimiter + restItem._name;
return identity.substring(delimiter.length);
};
/**
* Make a request against IGC's REST API
*
* @see module:ibm-igc-rest.setServer
* @see module:ibm-igc-rest.setAuth
* @param {string} method - type of request, one of ['GET', 'PUT', 'POST', 'DELETE']
* @param {string} path - the path to the end-point (e.g. /ibm/iis/igc-rest/v1/...)
* @param {string} [input] - any input for the request, i.e. for PUT, POST
* @param {string} [contentType] - the type of content, e.g. 'application/json' or 'application/xml'
* @param {string} [drillDown] - the key into which to drill-down within the response
* @param {requestCallback} callback - callback that handles the response
* @throws will throw an error if connectivity details are incomplete or there is a fatal error during the request
*/
const makeRequest = function(method, path, input, contentType, drillDown, callback) {
callback = callback || function () {};
return new Promise(function(resolve, reject) {
const bInput = (typeof input !== 'undefined' && input !== null);
const bDrillDown = (typeof drillDown !== 'undefined' && drillDown !== null);
if (bInput) {
input = _prepValue(input, contentType);
}
if (typeof _restConnect === 'undefined' || _restConnect === undefined || _restConnect === null) {
reject(new Error("Setup incomplete: no connection found."));
return callback("Setup incomplete: no connection found.");
}
// Only pre-pend the base REST URL if the path is not already a fully-qualified URI
const uri = path.startsWith('http') ? path : _restConnect.baseURL + path;
const opts = {
uri: uri,
method: method,
strictSSL: false,
agent: _restConnect.agent
};
if (!_restConnect.sessionStatus) {
// Authorisation header should only be included the first time
// (when session has not been created); if a session exists, use it instead
opts.auth = _restConnect.auth;
}
if (bInput) {
if (contentType !== 'multipart/form-data') {
opts.headers = {
'Content-Type': contentType,
'Content-Length': input.length
};
opts.body = input;
} else {
opts.formData = input;
}
}
request(opts, function(error, response, body) {
let retVal = {};
retVal.res = response;
if (error !== null) {
reject(error);
return callback(error);
} else if (body === "") {
retVal.body = {};
} else if (bDrillDown) {
retVal.body = JSON.parse(body)[drillDown];
} else {
retVal.body = JSON.parse(body);
}
resolve(retVal);
return callback(retVal.res, retVal.body);
});
});
};
/**
* Create an asset
*
* @param {string} type - the type of asset to create
* @param {Object} value - the set of values with which to create the asset
* @param {requestCallback} [callback] - optional callback that handles the response (if not using Promises)
* @returns {Promise} when resolved contains the RID of the created asset
*/
const create = function(type, value, callback) {
callback = callback || function() {};
return new Promise(function(resolve, reject) {
value._type = type;
makeRequest('POST', "/ibm/iis/igc-rest/v1/assets", value, 'application/json').then(function(results) {
const err = _checkRequestError(results.res, 201, reject);
let rid = "";
if (results.res.headers.hasOwnProperty("Location")) {
rid = results.res.headers.Location.substring(results.res.headers.Location.lastIndexOf("/"));
} else if (results.res.headers.hasOwnProperty("location")) {
rid = results.res.headers.location.substring(results.res.headers.location.lastIndexOf("/"));
}
if (rid.length > 0) {
rid = rid.substring(1);
}
resolve(rid);
return callback(err, rid);
});
});
};
/**
* Update a RID with a specific set of data
*
* @param {string} rid - the RID of the asset to update
* @param {Object} value - the set of data with which to update the asset
* @param {requestCallback} [callback] - optional callback to handles the response (if not using Promises)
* @returns {Promise} when resolved contains the results of the update
*/
const update = function(rid, value, callback) {
callback = callback || function() {};
return new Promise(function(resolve, reject) {
makeRequest('PUT', "/ibm/iis/igc-rest/v1/assets/" + rid, value, 'application/json').then(function(results) {
const err = _checkRequestError(results.res, 200, reject);
resolve(results.body);
return callback(err, results.body);
});
});
};
/**
* Search IGC
*
* @param {Object} query - the search to run against IGC (as a JSON object)
* @param {requestCallback} [callback] - optional callback that handles the response (if not using Promises)
* @returns {Promise} when resolved contains the results of the search
*/
const search = function(query, callback) {
callback = callback || function() {};
return new Promise(function(resolve, reject) {
makeRequest('POST', "/ibm/iis/igc-rest/v1/search/", query, 'application/json').then(function(results) {
const err = _checkRequestError(results.res, 200, reject);
resolve(results.body);
return callback(err, results.body);
});
});
};
/**
* Get a list of all of the IGC asset types
*
* @param {requestCallback} [callback] - optional callback that handles the response (if not using Promises)
* @returns {Promise} when resolved contains the IGC types
*/
const getTypes = function(callback) {
callback = callback || function() {};
return new Promise(function(resolve, reject) {
makeRequest('GET', "/ibm/iis/igc-rest/v1/types/").then(function(results) {
const err = _checkRequestError(results.res, 200, reject);
resolve(results.body);
return callback(err, results.body);
});
});
};
/**
* Get a mapping of all asset types from display name to unique type id
*
* @param {requestCallback} [callback] - optional callback that handles the response (when not using Promises), with an object keyed by display name and each value the unique type id for that display name
* @returns {Promise} when resolved contains an object keyed by display name and each value the unique type id for that display name
*/
const getAssetTypeNamesToIds = function(callback) {
callback = callback || function() {};
return new Promise(function(resolve, reject) {
getTypes().then(function(resTypes) {
const typesToIds = {};
for (let i = 0; i < resTypes.length; i++) {
const name = resTypes[i]._name;
const id = resTypes[i]._id;
typesToIds[name] = id;
}
resolve(typesToIds);
return callback(null, typesToIds);
}, function(error) {
reject(error);
return callback(error, null);
});
});
};
/**
* Make a general GET request against IGC's REST API
*
* @param {string} path - the path to the end-point (e.g. /ibm/iis/igc-rest/v1/...)
* @param {integer} successCode - the HTTP response code that indicates success for this operation
* @param {requestCallback} [callback] - optional callback that handles the response (when not using Promises)
* @returns {Promise} when resolved contains the response body from the request
*/
const getOther = function(path, successCode, callback) {
callback = callback || function() {};
return new Promise(function(resolve, reject) {
makeRequest('GET', path).then(function(results) {
const err = _checkRequestError(results.res, successCode, reject);
resolve(results.body);
return callback(err, results.body);
});
});
};
/**
* Delete a specific asset from IGC
*
* @param {string} rid - the RID of the asset to delete
* @param {requestCallback} [callback] - optional callback that handles the response (when not using Promises)
* @returns {Promise} when resolved contains the result of the deletion
*/
const deleteAssetById = function(rid, callback) {
callback = callback || function() {};
return new Promise(function(resolve, reject) {
makeRequest('DELETE', "/ibm/iis/igc-rest/v1/assets/" + rid).then(function(results) {
const err = _checkRequestError(results.res, 200, reject);
resolve(results.body);
return callback(err, results.body);
});
});
};
/**
* Request IGC to detect lineage for a specific job (requires v11.5.0.1 GOVRUP3 or higher)
* - Actual status comes from the "message" within the callback results: starts with SUCCESS, WARNING or FAILURE
*
* @param {string} rid - the RID of the job for which to detect lineage
* @param {requestCallback} [callback] - optional callback that handles the response (when not using Promises)
* @returns {Promise} when resolved contains results of the lineage detection
*/
const detectLineageForJob = function(rid, callback) {
return getOther("/ibm/iis/igc-rest/v1/flows/detectFlows/dsjob/" + rid, 202, callback);
};
/**
* Create new lineage flow as defined by a flow XML document
*
* @param {string} xml - the flow document XML containing the lineage to upload
* @param {requestCallback} [callback] - optional callback that handles the response (when not using Promises)
* @returns {Promise} when resolved contains the results of the lineage flow upload
*/
const uploadLineageFlow = function(xml, callback) {
callback = callback || function() {};
return new Promise(function(resolve, reject) {
makeRequest('POST', "/ibm/iis/igc-rest/v1/flows/upload", xml, 'application/xml').then(function(results) {
const err = _checkRequestError(results.res, 200, reject);
resolve(results.body);
return callback(err, results.body);
});
});
};
/**
* Get list of bundles (asset type definitions) already deployed
*
* @param {requestCallback} [callback] - optional callback that handles the response (when not using Promises)
* @returns {Promise} when resolved contains a String[] of bundle names
*/
const getBundles = function(callback) {
return getOther("/ibm/iis/igc-rest/v1/bundles/", 200, callback);
};
/**
* Create a new Open IGC bundle (asset type definition)
*
* @param {string} zipFile - the location of the zip file from which to create the bundle
* @param {requestCallback} [callback] - optional callback that handles the response (when not using Promises)
* @returns {Promise} when resolved contains the results of the bundle upload
*/
const createBundle = function(zipFile, callback) {
callback = callback || function() {};
return new Promise(function(resolve, reject) {
const formData = {
file: {
value: fs.createReadStream(zipFile),
options: {
name: 'file',
filename: path.posix.basename(zipFile),
contentType: 'application/x-zip-compressed'
}
}
};
makeRequest('POST', "/ibm/iis/igc-rest/v1/bundles", formData, 'multipart/form-data').then(function(results) {
const err = _checkRequestError(results.res, 200, reject);
resolve(results.body);
return callback(err, results.body);
});
});
};
/**
* Update an existing Open IGC bundle (asset type definition)
*
* @param {string} zipFile - the location of the zip file from which to create the bundle
* @param {requestCallback} [callback] - optional callback that handles the response (when not using Promises)
* @returns {Promise} when resolved contains the results of the bundle upload
*/
const updateBundle = function(zipFile, callback) {
callback = callback || function() {};
return new Promise(function(resolve, reject) {
const formData = {
file: {
value: fs.createReadStream(zipFile),
options: {
name: 'file',
filename: path.posix.basename(zipFile),
contentType: 'application/x-zip-compressed'
}
}
};
makeRequest('PUT', "/ibm/iis/igc-rest/v1/bundles", formData, 'multipart/form-data').then(function(results) {
const err = _checkRequestError(results.res, 200, reject);
resolve(results.body);
return callback(err, results.body);
});
});
};
/**
* Create instances of assets defined by an Open IGC bundle
*
* @param {string} xml - the flow document XML containing the asset instance definitions
* @param {requestCallback} [callback] - optional callback that handles the response (when not using Promises)
* @returns {Promise} when resolved contains the results of the asset instantiations
*/
const createBundleAssets = function(xml, callback) {
callback = callback || function() {};
return new Promise(function(resolve, reject) {
makeRequest('POST', "/ibm/iis/igc-rest/v1/bundles/assets", xml, 'application/xml').then(function(results) {
const err = _checkRequestError(results.res, 200, reject);
resolve(results.body);
return callback(err, results.body);
});
});
};
/**
* Create a new Custom Attribute (available in v11.7 onwards only)
*
* @param {Object} json - the JSON object which describes the custom attribute
* @param {requestCallback} [callback] - optional callback that handles the response (when not using Promises)
* @returns {Promise} when resolved contains the results of the custom attribute creation
*/
const createCustomAttribute = function(json, callback) {
callback = callback || function() {};
return new Promise(function(resolve, reject) {
makeRequest('POST', "/ibm/iis/igc-rest/v1/administration/attributes", json, 'application/json').then(function(results) {
const err = _checkRequestError(results.res, 200, reject);
resolve(results.body);
return callback(err, results.body);
});
});
};
/**
* Update a new Custom Attribute (available in v11.7 onwards only)
*
* @param {string} rid - the RID of the custom attribute to update
* @param {Object} json - the JSON object which describes the custom attribute
* @param {requestCallback} [callback] - optional callback that handles the response (when not using Promises)
* @returns {Promise} when resolved contains the results of the custom attribute update
*/
const updateCustomAttribute = function(rid, json, callback) {
callback = callback || function() {};
return new Promise(function(resolve, reject) {
makeRequest('PUT', "/ibm/iis/igc-rest/v1/administration/attributes/" + rid, json, 'application/json').then(function(results) {
const err = _checkRequestError(results.res, 200, reject);
resolve(results.body);
return callback(err, results.body);
});
});
};
/**
* Get list of custom attributes already deployed
*
* @param {integer} maxItems - maximum number of custom attributes to retrieve
* @param {requestCallback} [callback] - optional callback that handles the response (when not using Promises)
* @returns {Promise} when resolved contains an array of objects with custom attribute definitions: "id", "name", "attributeType", and "appliesTo"[]
*/
const getCustomAttributes = function(maxItems, callback) {
return getOther("/ibm/iis/igc-rest/v1/administration/attributes/?begin=0&pageSize=" + maxItems, 200, callback);
};
/**
* Get a listing of all of the assets in a collection
*
* @param {string} collectionName
* @param {integer} maxItems - maximum number of items to retrieve
* @param {requestCallback} [callback] - optional callback that handles the response (when not using Promises)
* @returns {Promise} when resolved contains the list of assets in the collection
*/
const getAssetsInCollection = function(collectionName, maxItems, callback) {
callback = callback || function() {};
return new Promise(function(resolve, reject) {
// The pageSize here seems to be for collections that are found -- not assets
// within the collection; may cause issues with larger collections?
const json = {
"pageSize": maxItems,
"properties" : ["assets"],
"types" : ["collection"],
"where" :
{
"conditions" :
[
{
"property" : "name",
"operator" : "=",
"value" : collectionName
}
],
"operator" : "and"
}
};
search(json).then(function(results) {
let assets = [];
let err = null;
if (results.items.length > 1) {
err = "WARN: Found more than one collection called '" + collectionName + "' -- only taking assets from the first one.";
console.warn(err);
}
if (results.items.length > 0) {
assets = results.items[0].assets.items;
} else {
err = "WARN: No assets found in the collection '" + collectionName + "'.";
console.warn(err);
}
resolve(assets);
return callback(err, assets);
}, function(error) {
reject(error);
});
});
};
/**
* Request all details of an asset
*
* NOTE: this function should be used with caution -- it will build a large object and
* can be measurably slower (> 5x) than explicitly defining the properties and searching
* using 'getAssetPropertiesById' instead
*
* @see module:ibm-igc-rest.getAssetPropertiesById
* @param {string} rid - the RID of the asset
* @param {requestCallback} [callback] - optional callback that handles the response (when not using Promises)
* @returns {Promise} when resolved contains all of the asset's details
*/
const getAssetById = function(rid, callback) {
return getOther("/ibm/iis/igc-rest/v1/assets/" + rid, 200, callback);
};
/**
* Retrieve only the single specified property of an asset
*
* @param {string} rid - the RID of the asset
* @param {string} property - the property of the asset to retrieve (e.g. 'name')
* @param {requestCallback} [callback] - optional callback that handles the response (when not using Promises)
* @returns {Promise} when resolved contains the specified property of the asset
*/
const getAssetPropertyById = function(rid, property, callback) {
return getOther("/ibm/iis/igc-rest/v1/assets/" + rid + "/" + property, 200, callback);
};
/**
* Retrieve only the specified details of an asset
*
* @see module:ibm-igc-rest.getTypes
* @param {string} rid - the RID of the asset
* @param {string} type - the type of the asset
* @param {string[]} properties - array of properties to retrieve for the asset
* @param {integer} maxItems - maximum number of detailed properties
* @param {boolean} bIncludeContext - whether to include contextual information (true) or drill-down just to the resulting properties (false)
* @param {requestCallback} [callback] - optional callback that handles the response (when not using Promises)
* @returns {Promise} when resolved contains the specified properties of the asset
*/
const getAssetPropertiesById = function(rid, type, properties, maxItems, bIncludeContext, callback) {
callback = callback || function() {};
return new Promise(function(resolve, reject) {
if (!Array.isArray(properties)) {
properties = [ properties ];
}
const json = {
"pageSize": maxItems,
"properties" : properties,
"types" : [ type ],
"where" :
{
"conditions" :
[
{
"property" : "_id",
"operator" : "=",
"value" : rid
}
],
"operator" : "and"
}
};
search(json).then(function(results) {
let toReturn = {};
let err = null;
if (results.items.length > 1) {
err = "WARN: Found more than one asset with RID '" + rid + "' -- only returning the first one.";
console.warn(err);
}
if (results.items.length > 0) {
if (bIncludeContext) {
toReturn = results.items[0];
} else {
for (let i = 0; i < properties.length; i++) {
const prop = properties[i];
toReturn[prop] = results.items[0][prop];
}
}
}
resolve(toReturn);
return callback(err, toReturn);
}, function(error) {
reject(new Error("No assets found with RID '" + rid + "'.\n" + error));
});
});
};
/**
* Retrieve the next page of information
*
* @see module:ibm-igc-rest.search
* @param {Object} paging - the 'paging' sub-object of a results object
* @param {requestCallback} [callback] - optional callback that handles the response (when not using Promises)
* @returns {Promise} when resolved contains the next page of results
*/
const getNextPage = function(paging, callback) {
if (paging.hasOwnProperty('next')) {
return getOther(paging.next, 200, callback);
} else {
callback = callback || function() {};
return new Promise(function(resolve) {
resolve({ items: [] });
return callback(null, { items: [] });
});
}
};
/**
* Retrieve all remaining pages of information
*
* @see module:ibm-igc-rest.search
* @see module:ibm-igc-rest.getNextPage
* @param {Object} items - the 'items' sub-object of a results object
* @param {Object} paging - the 'paging' sub-object of a results object
* @param {itemSetCallback} [callback] - optional callback that provides the list of all items from all pages (when not using Promises)
* @returns {Promise} when resolved contains the list of all items from all pages of results
*/
const getAllPages = function(items, paging, callback) {
callback = callback || function() {};
return new Promise(function(resolve, reject) {
getNextPage(paging).then(function(results) {
if (results.items.length > 0) {
resolve(getAllPages(items.concat(results.items), results.paging, callback));
} else {
resolve(items.concat(results.items));
return callback(null, items.concat(results.items));
}
}, function(error) {
reject(error);
return callback(error, items);
});
});
};
/**
* @returns true iff the provided type is a data container
*/
const isDataContainer = function(type) {
return hmDataContainerTypesToChildren.hasOwnProperty(type);
};
/**
* @returns the data type name for the child object of the provided container type
*/
const getDataContainerChildTypes = function(type) {
return hmDataContainerTypesToChildren[type];
};
/**
* This callback is invoked as the result of obtaining a set of items, providing an array of items.
* @callback itemSetCallback
* @param {string} errorMessage - any error message, or null if no errors
* @param {Object[]} itemArray - an array of JSON objects, each being an item
*/
/**
* This callback is invoked as the result of an IGC REST API call, providing the response of that request.
* @callback requestCallback
* @param {string} errorMessage - any error message, or null if no errors
* @param {Object} responseObject - the JSON object containing the response
*/
/**
* This callback is invoked as the result of obtaining an object's identity, providing the response of that request.
* @callback identityCallback
* @param {string} errorMessage - any error message, or null if no errors
* @param {Object} identityObject - the JSON object containing the identity
*/
return {
setConnection: setConnection,
openSession: openSession,
closeSession: closeSession,
replaceQueryVars: replaceQueryVars,
replaceRelatedUpdateVars: replaceRelatedUpdateVars,
verifySingleItem: verifySingleItem,
getSingleItem: getSingleItem,
logUpdateResults: logUpdateResults,
compareObjectsForSorting: compareObjectsForSorting,
getAssetContainerId: getAssetContainerId,
getContainerIdentity: getContainerIdentity,
getAssetIdentity: getAssetIdentity,
getItemIdentityString: getItemIdentityString,
makeRequest: makeRequest,
create: create,
update: update,
search: search,
getTypes: getTypes,
getAssetTypeNamesToIds: getAssetTypeNamesToIds,
getOther: getOther,
deleteAssetById: deleteAssetById,
detectLineageForJob: detectLineageForJob,
uploadLineageFlow: uploadLineageFlow,
getBundles: getBundles,
createBundle: createBundle,
updateBundle: updateBundle,
createBundleAssets: createBundleAssets,
getCustomAttributes: getCustomAttributes,
createCustomAttribute: createCustomAttribute,
updateCustomAttribute: updateCustomAttribute,
getAssetsInCollection: getAssetsInCollection,
getAssetById: getAssetById,
getAssetPropertyById: getAssetPropertyById,
getAssetPropertiesById: getAssetPropertiesById,
getNextPage: getNextPage,
getAllPages: getAllPages,
isDataContainer: isDataContainer,
getDataContainerChildTypes: getDataContainerChildTypes
};
})();
module.exports = RestIGC;