UNPKG

@gooddata/gooddata-js

Version:
816 lines (815 loc) • 35.1 kB
"use strict"; var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); // (C) 2007-2020 GoodData Corporation var isPlainObject_1 = __importDefault(require("lodash/isPlainObject")); var get_1 = __importDefault(require("lodash/get")); var chunk_1 = __importDefault(require("lodash/chunk")); var flatten_1 = __importDefault(require("lodash/flatten")); var pick_1 = __importDefault(require("lodash/pick")); var util_1 = require("./util"); var xhr_1 = require("./xhr"); var referenceHandling_1 = require("./referenceHandling"); var execute_afm_convert_1 = require("./execution/execute-afm.convert"); /** * Functions for working with metadata objects * * @class metadata * @module metadata */ var MetadataModule = /** @class */ (function () { function MetadataModule(xhr) { this.xhr = xhr; } /** * Load all objects with given uris * (use bulk loading instead of getting objects one by one) * * @method getObjects * @param {String} projectId id of the project * @param {Array} objectUris array of uris for objects to be loaded * @return {Array} array of loaded elements */ MetadataModule.prototype.getObjects = function (projectId, objectUris) { var _this = this; var LIMIT = 50; var uri = "/gdc/md/" + projectId + "/objects/get"; var objectsUrisChunks = chunk_1.default(objectUris, LIMIT); var promises = objectsUrisChunks.map(function (objectUrisChunk) { var body = { get: { items: objectUrisChunk, }, }; return _this.xhr .post(uri, { body: body }) .then(function (r) { if (!r.response.ok) { throw new xhr_1.ApiResponseError(r.response.statusText, r.response, r.responseBody); } return r.getData(); }) .then(function (result) { return get_1.default(result, ["objects", "items"]).map(function (item) { if (item.visualizationObject) { return { visualizationObject: referenceHandling_1.convertReferencesToUris(item.visualizationObject), }; } if (item.visualizationWidget) { return { visualizationWidget: referenceHandling_1.convertReferencesToUris(item.visualizationWidget), }; } return item; }); }); }); return Promise.all(promises).then(flatten_1.default); }; /** * Loads all objects by query (fetches all pages, one by one) * * @method getObjectsByQuery * @param {String} projectId id of the project * @param {Object} options (see https://developer.gooddata.com/api endpoint: /gdc/md/{project_id}/objects/query) * - category {String} for example 'dataSets' or 'projectDashboard' * - mode {String} 'enriched' or 'raw' * - author {String} the URI of the author of the metadata objects * - limit {number} default is 50 (also maximum) * - deprecated {boolean} show also deprecated objects * @return {Promise<Array>} array of returned objects */ MetadataModule.prototype.getObjectsByQuery = function (projectId, options) { var _this = this; var getOnePage = function (uri, items) { if (items === void 0) { items = []; } return _this.xhr .get(uri) .then(function (r) { return r.getData(); }) .then(function (_a) { var objects = _a.objects; items.push.apply(items, objects.items); var nextUri = objects.paging.next; return nextUri ? getOnePage(nextUri, items) : items; }); }; var deprecated = options.deprecated ? { deprecated: 1 } : {}; var uri = "/gdc/md/" + projectId + "/objects/query"; var query = pick_1.default(__assign({ limit: 50 }, options, deprecated), [ "category", "mode", "author", "limit", "deprecated", ]); return getOnePage(uri + util_1.queryString(query)); }; /** * Get MD objects from using2 resource. Include only objects of given types * and take care about fetching only nearest objects if requested. * * @method getObjectUsing * @param {String} projectId id of the project * @param {String} uri uri of the object for which dependencies are to be found * @param {Object} options objects with options: * - types {Array} array of strings with object types to be included * - nearest {Boolean} whether to include only nearest dependencies * @return {jQuery promise} promise promise once resolved returns an array of * entries returned by using2 resource */ MetadataModule.prototype.getObjectUsing = function (projectId, uri, options) { if (options === void 0) { options = {}; } var _a = options.types, types = _a === void 0 ? [] : _a, _b = options.nearest, nearest = _b === void 0 ? false : _b; var resourceUri = "/gdc/md/" + projectId + "/using2"; var body = { inUse: { uri: uri, types: types, nearest: nearest ? 1 : 0, }, }; return this.xhr .post(resourceUri, { body: body }) .then(function (r) { if (!r.response.ok) { throw new xhr_1.ApiResponseError(r.response.statusText, r.response, r.getData()); } return r.getData(); }) .then(function (result) { return result.entries; }); }; /** * Get MD objects from using2 resource. Include only objects of given types * and take care about fetching only nearest objects if requested. * * @method getObjectUsingMany * @param {String} projectId id of the project * @param {Array} uris uris of objects for which dependencies are to be found * @param {Object} options objects with options: * - types {Array} array of strings with object types to be included * - nearest {Boolean} whether to include only nearest dependencies * @return {jQuery promise} promise promise once resolved returns an array of * entries returned by using2 resource */ MetadataModule.prototype.getObjectUsingMany = function (projectId, uris, options) { if (options === void 0) { options = {}; } var _a = options.types, types = _a === void 0 ? [] : _a, _b = options.nearest, nearest = _b === void 0 ? false : _b; var resourceUri = "/gdc/md/" + projectId + "/using2"; var body = { inUseMany: { uris: uris, types: types, nearest: nearest ? 1 : 0, }, }; return this.xhr .post(resourceUri, { body: body }) .then(function (r) { if (!r.response.ok) { throw new xhr_1.ApiResponseError(r.response.statusText, r.response, r.getData()); } return r.getData(); }) .then(function (result) { return result.useMany; }); }; /** * Returns all visualizationObjects metadata in a project specified by projectId param * * @method getVisualizations * @param {string} projectId Project identifier * @return {Array} An array of visualization objects metadata */ MetadataModule.prototype.getVisualizations = function (projectId) { return this.xhr .get("/gdc/md/" + projectId + "/query/visualizationobjects") .then(function (apiResponse) { return apiResponse.response.ok ? apiResponse.getData() : apiResponse.response; }) .then(util_1.getIn("query.entries")); }; /** * Returns all attributes in a project specified by projectId param * * @method getAttributes * @param {string} projectId Project identifier * @return {Array} An array of attribute objects */ MetadataModule.prototype.getAttributes = function (projectId) { return this.xhr .get("/gdc/md/" + projectId + "/query/attributes") .then(function (apiResponse) { return apiResponse.response.ok ? apiResponse.getData() : apiResponse.response; }) .then(util_1.getIn("query.entries")); }; /** * Returns all dimensions in a project specified by projectId param * * @method getDimensions * @param {string} projectId Project identifier * @return {Array} An array of dimension objects * @see getFolders */ MetadataModule.prototype.getDimensions = function (projectId) { return this.xhr .get("/gdc/md/" + projectId + "/query/dimensions") .then(function (apiResponse) { return apiResponse.response.ok ? apiResponse.getData() : apiResponse.response; }) .then(util_1.getIn("query.entries")); }; /** * Returns project folders. Folders can be of specific types and you can specify * the type you need by passing and optional `type` parameter * * @method getFolders * @param {String} projectId - Project identifier * @param {String} type - Optional, possible values are `metric`, `fact`, `attribute` * @return {Array} An array of dimension objects */ MetadataModule.prototype.getFolders = function (projectId, type) { var _this = this; // TODO enum? var getFolderEntries = function (pId, t) { var typeURL = t ? "?type=" + t : ""; return _this.xhr .get("/gdc/md/" + pId + "/query/folders" + typeURL) .then(function (r) { return r.getData(); }) .then(util_1.getIn("query.entries")); }; switch (type) { case "fact": case "metric": return getFolderEntries(projectId, type); case "attribute": return this.getDimensions(projectId); default: return Promise.all([ getFolderEntries(projectId, "fact"), getFolderEntries(projectId, "metric"), this.getDimensions(projectId), ]).then(function (_a) { var fact = _a[0], metric = _a[1], attribute = _a[2]; return { fact: fact, metric: metric, attribute: attribute }; }); } }; /** * Returns all facts in a project specified by the given projectId * * @method getFacts * @param {string} projectId Project identifier * @return {Array} An array of fact objects */ MetadataModule.prototype.getFacts = function (projectId) { return this.xhr .get("/gdc/md/" + projectId + "/query/facts") .then(function (apiResponse) { return apiResponse.response.ok ? apiResponse.getData() : apiResponse.response; }) .then(util_1.getIn("query.entries")); }; /** * Returns all metrics in a project specified by the given projectId * * @method getMetrics * @param {string} projectId Project identifier * @return {Array} An array of metric objects */ MetadataModule.prototype.getMetrics = function (projectId) { return this.xhr .get("/gdc/md/" + projectId + "/query/metrics") .then(function (apiResponse) { return apiResponse.response.ok ? apiResponse.getData() : apiResponse.response; }) .then(util_1.getIn("query.entries")); }; /** * Returns all metrics that are reachable (with respect to ldm of the project * specified by the given projectId) for given attributes * * @method getAvailableMetrics * @param {String} projectId - Project identifier * @param {Array} attrs - An array of attribute uris for which we want to get * available metrics * @return {Array} An array of reachable metrics for the given attrs * @see getAvailableAttributes * @see getAvailableFacts */ MetadataModule.prototype.getAvailableMetrics = function (projectId, attrs) { if (attrs === void 0) { attrs = []; } return this.xhr .post("/gdc/md/" + projectId + "/availablemetrics", { body: attrs }) .then(function (apiResponse) { return apiResponse.response.ok ? apiResponse.getData() : apiResponse.response; }) .then(function (data) { return data.entries; }); }; /** * Returns all attributes that are reachable (with respect to ldm of the project * specified by the given projectId) for given metrics (also called as drillCrossPath) * * @method getAvailableAttributes * @param {String} projectId - Project identifier * @param {Array} metrics - An array of metric uris for which we want to get * available attributes * @return {Array} An array of reachable attributes for the given metrics * @see getAvailableMetrics * @see getAvailableFacts */ MetadataModule.prototype.getAvailableAttributes = function (projectId, metrics) { if (metrics === void 0) { metrics = []; } return this.xhr .post("/gdc/md/" + projectId + "/drillcrosspaths", { body: metrics }) .then(function (apiResponse) { return (apiResponse.response.ok ? apiResponse.getData() : apiResponse.response); }) .then(function (r) { return r.drillcrosspath.links; }); }; /** * Returns all attributes that are reachable (with respect to ldm of the project * specified by the given projectId) for given metrics (also called as drillCrossPath) * * @method getAvailableFacts * @param {String} projectId - Project identifier * @param {Array} items - An array of metric or attribute uris for which we want to get * available facts * @return {Array} An array of reachable facts for the given items * @see getAvailableAttributes * @see getAvailableMetrics */ MetadataModule.prototype.getAvailableFacts = function (projectId, items) { if (items === void 0) { items = []; } return this.xhr .post("/gdc/md/" + projectId + "/availablefacts", { body: items }) .then(function (r) { return (r.response.ok ? r.getData() : r.response); }) .then(function (r) { return r.entries; }); }; /** * Get details of a metadata object specified by its uri * * @method getObjectDetails * @param uri uri of the metadata object for which details are to be retrieved * @return {Object} object details */ MetadataModule.prototype.getObjectDetails = function (uri) { return this.xhr.get(uri).then(function (r) { return r.getData(); }); }; /** * Get folders with items. * Returns array of folders, each having a title and items property which is an array of * corresponding items. Each item is either a metric or attribute, keeping its original * verbose structure. * * @method getFoldersWithItems * @param {String} type type of folders to return * @return {Array} Array of folder object, each containing title and * corresponding items. */ MetadataModule.prototype.getFoldersWithItems = function (projectId, type) { var _this = this; // fetch all folders of given type and process them return this.getFolders(projectId, type).then(function (folders) { // Helper public to get details for each metric in the given // array of links to the metadata objects representing the metrics. // @return the array of promises var getMetricItemsDetails = function (array) { return Promise.all(array.map(_this.getObjectDetails)).then(function (metricArgs) { return metricArgs.map(function (item) { return item.metric; }); }); }; // helper mapBy function function mapBy(array, key) { return array.map(function (item) { return item[key]; }); } // helper for sorting folder tree structure // sadly @returns void (sorting == mutating array in js) var sortFolderTree = function (structure) { structure.forEach(function (folder) { folder.items.sort(function (a, b) { if (a.meta.title < b.meta.title) { return -1; } else if (a.meta.title > b.meta.title) { return 1; } return 0; }); }); structure.sort(function (a, b) { if (a.title < b.title) { return -1; } else if (a.title > b.title) { return 1; } return 0; }); }; var foldersLinks = mapBy(folders, "link"); var foldersTitles = mapBy(folders, "title"); // fetch details for each folder return Promise.all(foldersLinks.map(_this.getObjectDetails)).then(function (folderDetails) { // if attribute, just parse everything from what we've received // and resolve. For metrics, lookup again each metric to get its // identifier. If passing unsupported type, reject immediately. if (type === "attribute") { // get all attributes, subtract what we have and add rest in unsorted folder return _this.getAttributes(projectId).then(function (attributes) { // get uris of attributes which are in some dimension folders var attributesInFolders = []; folderDetails.forEach(function (fd) { fd.dimension.content.attributes.forEach(function (attr) { attributesInFolders.push(attr.meta.uri); }); }); // unsortedUris now contains uris of all attributes which aren't in a folder var unsortedUris = attributes .filter(function (item) { return attributesInFolders.indexOf(item.link) === -1; }) .map(function (item) { return item.link; }); // now get details of attributes in no folders return Promise.all(unsortedUris.map(_this.getObjectDetails)).then(function (unsortedAttributeArgs) { // TODO add map to r.json // get unsorted attribute objects var unsortedAttributes = unsortedAttributeArgs.map(function (attr) { return attr.attribute; }); // create structure of folders with attributes var structure = folderDetails.map(function (folderDetail) { return { title: folderDetail.dimension.meta.title, items: folderDetail.dimension.content.attributes, }; }); // and append "Unsorted" folder with attributes to the structure structure.push({ title: "Unsorted", items: unsortedAttributes, }); sortFolderTree(structure); return structure; }); }); } else if (type === "metric") { var entriesLinks_1 = folderDetails.map(function (entry) { return mapBy(entry.folder.content.entries, "link"); }); // get all metrics, subtract what we have and add rest in unsorted folder return _this.getMetrics(projectId).then(function (metrics) { // get uris of metrics which are in some dimension folders var metricsInFolders = []; folderDetails.forEach(function (fd) { fd.folder.content.entries.forEach(function (metric) { metricsInFolders.push(metric.link); }); }); // unsortedUris now contains uris of all metrics which aren't in a folder var unsortedUris = metrics .filter(function (item) { return metricsInFolders.indexOf(item.link) === -1; }) .map(function (item) { return item.link; }); // sadly order of parameters of concat matters! (we want unsorted last) entriesLinks_1.push(unsortedUris); // now get details of all metrics return Promise.all(entriesLinks_1.map(function (linkArray) { return getMetricItemsDetails(linkArray); })).then(function (tree) { // TODO add map to r.json // all promises resolved, i.e. details for each metric are available var structure = tree.map(function (treeItems, idx) { // if idx is not in folders list than metric is in "Unsorted" folder return { title: foldersTitles[idx] || "Unsorted", items: treeItems, }; }); sortFolderTree(structure); return structure; }); }); } return Promise.reject(null); }); }); }; /** * Get identifier of a metadata object identified by its uri * * @method getObjectIdentifier * @param uri uri of the metadata object for which the identifier is to be retrieved * @return {String} object identifier */ MetadataModule.prototype.getObjectIdentifier = function (uri) { function idFinder(obj) { // TODO if (obj.attribute) { return obj.attribute.content.displayForms[0].meta.identifier; } else if (obj.dimension) { return obj.dimension.content.attributes.content.displayForms[0].meta.identifier; } else if (obj.metric) { return obj.metric.meta.identifier; } throw Error("Unknown object!"); } if (!isPlainObject_1.default(uri)) { return this.getObjectDetails(uri).then(function (data) { return idFinder(data); }); } return Promise.resolve(idFinder(uri)); }; /** * Get uri of an metadata object, specified by its identifier and project id it belongs to * * @method getObjectUri * @param {string} projectId id of the project * @param identifier identifier of the metadata object * @return {String} uri of the metadata object */ MetadataModule.prototype.getObjectUri = function (projectId, identifier) { return this.xhr .post("/gdc/md/" + projectId + "/identifiers", { body: { identifierToUri: [identifier], }, }) .then(function (r) { var data = r.getData(); var found = data.identifiers.find(function (pair) { return pair.identifier === identifier; }); if (found) { return found.uri; } throw new xhr_1.ApiResponseError("Object with identifier " + identifier + " not found in project " + projectId, r.response, r.responseBody); }); }; /** * Get uris specified by identifiers * * @method getUrisFromIdentifiers * @param {String} projectId id of the project * @param {Array} identifiers identifiers of the metadata objects * @return {Array} array of identifier + uri pairs */ MetadataModule.prototype.getUrisFromIdentifiers = function (projectId, identifiers) { return this.xhr .post("/gdc/md/" + projectId + "/identifiers", { body: { identifierToUri: identifiers, }, }) .then(function (r) { return r.getData(); }) .then(function (data) { return data.identifiers; }); }; /** * Get identifiers specified by uris * * @method getIdentifiersFromUris * @param {String} projectId id of the project * @param {Array} uris of the metadata objects * @return {Array} array of identifier + uri pairs */ MetadataModule.prototype.getIdentifiersFromUris = function (projectId, uris) { return this.xhr .post("/gdc/md/" + projectId + "/identifiers", { body: { uriToIdentifier: uris, }, }) .then(function (r) { return r.getData(); }) .then(function (data) { return data.identifiers; }); }; /** * Get attribute elements with their labels and uris. * * @param {String} projectId id of the project * @param {String} labelUri uri of the label (display form) * @param {Array<String>} patterns elements labels/titles (for EXACT mode), or patterns (for WILD mode) * @param {('EXACT'|'WILD')} mode match mode, currently only EXACT supported * @return {Array} array of elementLabelUri objects */ MetadataModule.prototype.translateElementLabelsToUris = function (projectId, labelUri, patterns, mode) { if (mode === void 0) { mode = "EXACT"; } return this.xhr .post("/gdc/md/" + projectId + "/labels", { body: { elementLabelToUri: [ { labelUri: labelUri, mode: mode, patterns: patterns, }, ], }, }) .then(function (r) { return (r.response.ok ? get_1.default(r.getData(), "elementLabelUri") : r.response); }); }; /** * Get valid elements of an attribute, specified by its identifier and project id it belongs to * * @method getValidElements * @param {string} projectId id of the project * @param id display form id of the metadata object * @param {Object} options objects with options: * - limit {Number} * - offset {Number} * - order {String} 'asc' or 'desc' * - filter {String} * - prompt {String} * - uris {Array} * - complement {Boolean} * - includeTotalCountWithoutFilters {Boolean} * - restrictiveDefinition {String} * - afm {Object} * @return {Object} ValidElements response with: * - items {Array} elements * - paging {Object} * - elementsMeta {Object} */ MetadataModule.prototype.getValidElements = function (projectId, id, options) { var _this = this; if (options === void 0) { options = {}; } var query = pick_1.default(options, ["limit", "offset", "order", "filter", "prompt"]); var queryParams = util_1.queryString(query); var pickedOptions = pick_1.default(options, [ "uris", "complement", "includeTotalCountWithoutFilters", "restrictiveDefinition", ]); var afm = options.afm; var getRequestBodyWithReportDefinition = function () { return _this.xhr .post("/gdc/app/projects/" + projectId + "/executeAfm/debug", { body: { execution: { afm: execute_afm_convert_1.convertAfm(afm), }, }, }) .then(function (response) { return response.getData(); }) .then(function (reportDefinitionResult) { return (__assign({}, pickedOptions, { restrictiveDefinitionContent: reportDefinitionResult.reportDefinitionWithInlinedMetrics.content })); }); }; var getOptions = afm ? getRequestBodyWithReportDefinition : function () { return Promise.resolve(pickedOptions); }; return getOptions().then(function (requestBody) { return _this.xhr .post(("/gdc/md/" + projectId + "/obj/" + id + "/validElements" + queryParams).replace(/\?$/, ""), { body: { validElementsRequest: requestBody, }, }) .then(function (response) { return response.getData(); }); }); }; /** * Get visualization by Uri and process data * * @method getVisualization * @param {String} visualizationUri */ MetadataModule.prototype.getVisualization = function (uri) { return this.getObjectDetails(uri).then(function (visualizationObject) { var mdObject = visualizationObject.visualizationObject; return { visualizationObject: referenceHandling_1.convertReferencesToUris(mdObject), }; }); }; /** * Save visualization * * @method saveVisualization * @param {String} visualizationUri */ MetadataModule.prototype.saveVisualization = function (projectId, visualization) { var converted = referenceHandling_1.convertUrisToReferences(visualization.visualizationObject); return this.createObject(projectId, { visualizationObject: converted }); }; /** * Update visualization * * @method updateVisualization * @param {String} visualizationUri */ MetadataModule.prototype.updateVisualization = function (projectId, visualizationUri, visualization) { var converted = referenceHandling_1.convertUrisToReferences(visualization.visualizationObject); return this.updateObject(projectId, visualizationUri, { visualizationObject: converted }); }; /** * Delete visualization * * @method deleteVisualization * @param {String} visualizationUri */ MetadataModule.prototype.deleteVisualization = function (visualizationUri) { return this.deleteObject(visualizationUri); }; /** * Delete object * * @experimental * @method deleteObject * @param {String} uri of the object to be deleted */ MetadataModule.prototype.deleteObject = function (uri) { return this.xhr.del(uri); }; /** * Create object * * @experimental * @method createObject * @param {String} projectId * @param {String} obj object definition */ MetadataModule.prototype.createObject = function (projectId, obj) { return this.xhr .post("/gdc/md/" + projectId + "/obj?createAndGet=true", { body: obj, }) .then(function (r) { return r.getData(); }); }; /** * Update object * * @experimental * @method updateObject * @param {String} projectId * @param {String} visualizationUri * @param {String} obj object definition */ MetadataModule.prototype.updateObject = function (projectId, visualizationUri, obj) { return this.xhr .put("/gdc/md/" + projectId + "/obj/" + visualizationUri, { body: obj, }) .then(function (r) { return r.getData(); }); }; /** * LDM manage * * @experimental * @method ldmManage * @param {String} projectId * @param {String} maql * @param {Object} options for polling (maxAttempts, pollStep) */ MetadataModule.prototype.ldmManage = function (projectId, maql, options) { var _this = this; if (options === void 0) { options = {}; } return this.xhr .post("/gdc/md/" + projectId + "/ldm/manage2", { body: { manage: { maql: maql } } }) .then(function (r) { return r.getData(); }) .then(function (response) { var manageStatusUri = response.entries[0].link; return util_1.handlePolling(_this.xhr.get.bind(_this.xhr), manageStatusUri, _this.isTaskFinished, options); }) .then(this.checkStatusForError); }; /** * ETL pull * * @experimental * @method etlPull * @param {String} projectId * @param {String} uploadsDir * @param {Object} options for polling (maxAttempts, pollStep) */ MetadataModule.prototype.etlPull = function (projectId, uploadsDir, options) { var _this = this; if (options === void 0) { options = {}; } return this.xhr .post("/gdc/md/" + projectId + "/etl/pull2", { body: { pullIntegration: uploadsDir } }) .then(function (r) { return r.getData(); }) .then(function (response) { var etlPullStatusUri = response.pull2Task.links.poll; return util_1.handlePolling(_this.xhr.get.bind(_this.xhr), etlPullStatusUri, _this.isTaskFinished, options); }) .then(this.checkStatusForError); }; MetadataModule.prototype.isTaskFinished = function (task) { var taskState = task.wTaskStatus.status; return taskState === "OK" || taskState === "ERROR"; }; MetadataModule.prototype.checkStatusForError = function (response) { if (response.wTaskStatus.status === "ERROR") { return Promise.reject(response); } return response; }; return MetadataModule; }()); exports.MetadataModule = MetadataModule;