UNPKG

@gooddata/gooddata-js

Version:
332 lines (331 loc) • 15.7 kB
"use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); 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 invariant_1 = __importDefault(require("invariant")); var qs_1 = __importDefault(require("qs")); var range_1 = __importDefault(require("lodash/range")); var get_1 = __importDefault(require("lodash/get")); var xhr_1 = require("../xhr"); var execute_afm_convert_1 = require("./execute-afm.convert"); exports.DEFAULT_LIMIT = 1000; /** * This interface represents error caused during second part of api execution (data fetching) * and contains information about first execution part if that part was successful. */ var ApiExecutionResponseError = /** @class */ (function (_super) { __extends(ApiExecutionResponseError, _super); function ApiExecutionResponseError(error, executionResponse) { var _this = _super.call(this, error.message, error.response, error.responseBody) || this; _this.executionResponse = executionResponse; return _this; } return ApiExecutionResponseError; }(xhr_1.ApiResponseError)); exports.ApiExecutionResponseError = ApiExecutionResponseError; var ExecuteAfmModule = /** @class */ (function () { function ExecuteAfmModule(xhr) { this.xhr = xhr; } /** * Execute AFM and fetch all data results * * @method executeAfm * @param {String} projectId - GD project identifier * @param {AFM.IExecution} execution - See https://github.com/gooddata/gooddata-typings/blob/v2.1.0/src/AFM.ts#L2 * * @returns {Promise<Execution.IExecutionResponses>} Structure with `executionResponse` and `executionResult` - * See https://github.com/gooddata/gooddata-typings/blob/v2.1.0/src/Execution.ts#L113 */ ExecuteAfmModule.prototype.executeAfm = function (projectId, execution) { var _this = this; validateNumOfDimensions(get_1.default(execution, "execution.resultSpec.dimensions").length); return this.getExecutionResponse(projectId, execution).then(function (executionResponse) { return _this.getExecutionResult(executionResponse.links.executionResult) .then(function (executionResult) { return { executionResponse: executionResponse, executionResult: executionResult }; }) .catch(function (error) { throw new ApiExecutionResponseError(error, executionResponse); }); }); }; /** * Execute AFM and return execution's response; the response describes dimensionality of the results and * includes link to poll for the results. * * @method getExecutionResponse * @param {string} projectId - GD project identifier * @param {AFM.IExecution} execution - See https://github.com/gooddata/gooddata-typings/blob/v2.1.0/src/AFM.ts#L2 * * @returns {Promise<Execution.IExecutionResponse>} Promise with `executionResponse` * See https://github.com/gooddata/gooddata-typings/blob/v2.1.0/src/Execution.ts#L69 */ ExecuteAfmModule.prototype.getExecutionResponse = function (projectId, execution) { validateNumOfDimensions(get_1.default(execution, "execution.resultSpec.dimensions").length); return this.xhr .post("/gdc/app/projects/" + projectId + "/executeAfm", { body: execute_afm_convert_1.convertExecutionToJson(execution) }) .then(function (apiResponse) { return apiResponse.getData(); }) .then(unwrapExecutionResponse); }; /** * Execute saved visualization and get all data. * * NOTE: all functionality related to executeVisualization is experimental and subject to possible breaking changes * in the future; location and shape of this interface WILL change when the functionality is made GA. * * @param {string} projectId - GD project identifier * @param {IVisualizationExecution} visExecution - execution payload * * @private * @internal */ ExecuteAfmModule.prototype._executeVisualization = function (projectId, visExecution) { // We have ONE-3961 as followup to take this out of experimental mode var _this = this; return this._getVisExecutionResponse(projectId, visExecution).then(function (executionResponse) { return _this.getExecutionResult(executionResponse.links.executionResult).then(function (executionResult) { return { executionResponse: executionResponse, executionResult: executionResult }; }); }); }; /** * * Execute visualization and return the response; the response describes dimensionality of the results and * includes link to poll for the results. * * NOTE: all functionality related to executeVisualization is experimental and subject to possible breaking changes * in the future; location and shape of this interface WILL change when the functionality is made GA. * * @param {string} projectId - GD project identifier * @param {IVisualizationExecution} visExecution - execution payload * * @private * @internal */ ExecuteAfmModule.prototype._getVisExecutionResponse = function (projectId, visExecution) { // We have ONE-3961 as followup to take this out of experimental mode var body = createExecuteVisualizationBody(visExecution); return this.xhr .post("/gdc/app/projects/" + projectId + "/executeVisualization", { body: body }) .then(function (apiResponse) { return apiResponse.getData(); }) .then(unwrapExecutionResponse); }; // // working with results // /** * Get one page of Result from Execution (with requested limit and offset) * * @method getPartialExecutionResult * @param {string} executionResultUri * @param {number[]} limit - limit for each dimension * @param {number[]} offset - offset for each dimension * * @returns {Promise<Execution.IExecutionResult | null>} * Promise with `executionResult` or `null` (null means empty response - HTTP 204) * See https://github.com/gooddata/gooddata-typings/blob/v2.1.0/src/Execution.ts#L88 */ ExecuteAfmModule.prototype.getPartialExecutionResult = function (executionResultUri, limit, offset) { var executionResultUriQueryPart = getExecutionResultUriQueryPart(executionResultUri); var numOfDimensions = Number(qs_1.default.parse(executionResultUriQueryPart).dimensions); validateNumOfDimensions(numOfDimensions); return this.getPage(executionResultUri, limit, offset); }; /** * Get whole ExecutionResult * * @method getExecutionResult * @param {string} executionResultUri * * @returns {Promise<Execution.IExecutionResult | null>} * Promise with `executionResult` or `null` (null means empty response - HTTP 204) * See https://github.com/gooddata/gooddata-typings/blob/v2.1.0/src/Execution.ts#L88 */ ExecuteAfmModule.prototype.getExecutionResult = function (executionResultUri) { var executionResultUriQueryPart = getExecutionResultUriQueryPart(executionResultUri); var numOfDimensions = Number(qs_1.default.parse(executionResultUriQueryPart).dimensions); validateNumOfDimensions(numOfDimensions); var limit = Array(numOfDimensions).fill(exports.DEFAULT_LIMIT); var offset = Array(numOfDimensions).fill(0); return this.getAllPages(executionResultUri, limit, offset); }; ExecuteAfmModule.prototype.getPage = function (executionResultUri, limit, offset) { return this.fetchExecutionResult(executionResultUri, limit, offset).then(function (executionResultWrapper) { return executionResultWrapper ? unwrapExecutionResult(executionResultWrapper) : null; }); }; ExecuteAfmModule.prototype.getAllPages = function (executionResultUri, limit, offset, prevExecutionResult) { var _this = this; return this.fetchExecutionResult(executionResultUri, limit, offset).then(function (executionResultWrapper) { if (!executionResultWrapper) { return null; } var executionResult = unwrapExecutionResult(executionResultWrapper); var newExecutionResult = prevExecutionResult ? mergePage(prevExecutionResult, executionResult) : executionResult; var _a = executionResult.paging, offset = _a.offset, total = _a.total; var nextOffset = getNextOffset(limit, offset, total); var nextLimit = getNextLimit(limit, nextOffset, total); return nextPageExists(nextOffset, total) ? _this.getAllPages(executionResultUri, nextLimit, nextOffset, newExecutionResult) : newExecutionResult; }); }; ExecuteAfmModule.prototype.fetchExecutionResult = function (executionResultUri, limit, offset) { var uri = replaceLimitAndOffsetInUri(executionResultUri, limit, offset); return this.xhr .get(uri) .then(function (apiResponse) { return (apiResponse.response.status === 204 ? null : apiResponse.getData()); }); }; return ExecuteAfmModule; }()); exports.ExecuteAfmModule = ExecuteAfmModule; function getExecutionResultUriQueryPart(executionResultUri) { return executionResultUri.split(/\?(.+)/)[1]; } function unwrapExecutionResponse(executionResponseWrapper) { return executionResponseWrapper.executionResponse; } function unwrapExecutionResult(executionResultWrapper) { return executionResultWrapper.executionResult; } function validateNumOfDimensions(numOfDimensions) { invariant_1.default(numOfDimensions === 1 || numOfDimensions === 2, numOfDimensions + " dimensions are not allowed. Only 1 or 2 dimensions are supported."); } function createExecuteVisualizationBody(visExecution) { var _a = visExecution.visualizationExecution, reference = _a.reference, resultSpec = _a.resultSpec, filters = _a.filters; var resultSpecProp = resultSpec ? { resultSpec: resultSpec } : undefined; var filtersProp = filters ? { filters: filters } : undefined; return JSON.stringify({ visualizationExecution: __assign({ reference: reference }, resultSpecProp, filtersProp), }); } function replaceLimitAndOffsetInUri(oldUri, limit, offset) { var _a = oldUri.split(/\?(.+)/), uriPart = _a[0], queryPart = _a[1]; var query = __assign({}, qs_1.default.parse(queryPart), { limit: limit.join(","), offset: offset.join(",") }); return uriPart + qs_1.default.stringify(query, { addQueryPrefix: true }); } exports.replaceLimitAndOffsetInUri = replaceLimitAndOffsetInUri; function getNextOffset(limit, offset, total) { var numOfDimensions = total.length; var defaultNextRowsOffset = offset[0] + limit[0]; if (numOfDimensions === 1) { return [defaultNextRowsOffset]; } var defaultNextColumnsOffset = offset[1] + limit[1]; var nextColumnsExist = offset[1] + limit[1] < total[1]; var nextRowsOffset = nextColumnsExist ? offset[0] // stay in the same rows : defaultNextRowsOffset; // go to the next rows var nextColumnsOffset = nextColumnsExist ? defaultNextColumnsOffset // next columns for the same rows : 0; // start in the beginning of the next rows return [nextRowsOffset, nextColumnsOffset]; } exports.getNextOffset = getNextOffset; function getNextLimit(limit, nextOffset, total) { var numOfDimensions = total.length; validateNumOfDimensions(numOfDimensions); var getSingleNextLimit = function (limit, nextOffset, total) { return nextOffset + limit > total ? total - nextOffset : limit; }; // prevent set up lower limit than possible for 2nd dimension in the beginning of the next rows if (numOfDimensions === 2 && nextOffset[1] === 0 && // beginning of the next rows limit[0] < total[1] // limit from 1st dimension should be used in 2nd dimension ) { return [getSingleNextLimit(limit[0], nextOffset[0], total[0]), limit[0]]; } return range_1.default(numOfDimensions).map(function (i) { return getSingleNextLimit(limit[i], nextOffset[i], total[i]); }); } exports.getNextLimit = getNextLimit; function nextPageExists(nextOffset, total) { // expression "return nextLimit[0] > 0" also returns correct result return nextOffset[0] < total[0]; } exports.nextPageExists = nextPageExists; function mergeHeaderItemsForEachAttribute(dimension, headerItems, result) { var _a; if (headerItems && result.headerItems) { for (var attrIdx = 0; attrIdx < headerItems[dimension].length; attrIdx += 1) { (_a = result.headerItems[dimension][attrIdx]).push.apply(_a, headerItems[dimension][attrIdx]); } } } // works only for one or two dimensions function mergePage(prevExecutionResult, executionResult) { var result = prevExecutionResult; var headerItems = executionResult.headerItems, data = executionResult.data, paging = executionResult.paging; var mergeHeaderItems = function (dimension) { // for 1 dimension we already have the headers from first page var otherDimension = dimension === 0 ? 1 : 0; var isEdge = paging.offset[otherDimension] === 0; if (isEdge) { mergeHeaderItemsForEachAttribute(dimension, headerItems, result); } }; // merge data var rowOffset = paging.offset[0]; if (result.data[rowOffset]) { // appending columns to existing rows for (var i = 0; i < data.length; i += 1) { var columns = data[i]; var resultData = result.data[i + rowOffset]; resultData.push.apply(resultData, columns); } } else { // appending new rows var resultData = result.data; var currentPageData = data; resultData.push.apply(resultData, currentPageData); } // merge headerItems if (paging.offset.length > 1) { mergeHeaderItems(0); mergeHeaderItems(1); } else { mergeHeaderItemsForEachAttribute(0, headerItems, result); } // update page count if (paging.offset.length === 1) { result.paging.count = [get_1.default(result, "headerItems[0][0]", []).length]; } if (paging.offset.length === 2) { result.paging.count = [ get_1.default(result, "headerItems[0][0]", []).length, get_1.default(result, "headerItems[1][0]", []).length, ]; } return result; } exports.mergePage = mergePage;