@gooddata/gooddata-js
Version:
GoodData JavaScript SDK
332 lines (331 loc) • 15.7 kB
JavaScript
;
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;