'use strict'; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } /** * Licensed Materials - Property of IBM * IBM Cognos Products: Dashboard * (C) Copyright IBM Corp. 2018, 2020 * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp. * */ define(['underscore', './SummaryTypes', './SummaryQueryResult', '../../../widgets/livewidget/nls/StringResources', '../../../filters/FilterSpecHelper'], function (_, SummaryTypes, SummaryQueryResult, StringResources, FilterSpecHelper) { 'use strict'; var OVERALL_SUMMARY_QUERY_KEY = '_overallSummaryQueryKey'; /** * The current implementation requires a summary query for each summary set which may introduce performance issues. * Add limit to restrict number of queries until we can send the summary queries together */ var NESTED_ITEM_LIMIT = 12; /** * @class SummaryFeature * Summary feature to run query for a visualization, * based on different combination of projections and query context. */ var SummaryFeature = function () { function SummaryFeature(_ref) { var content = _ref.content, features = _ref.features; _classCallCheck(this, SummaryFeature); // Local object to store category and measure data item unique ids. // Used to build summary query projections and query UIs to get summary result. this._oQueryProjectionObj = {}; // Object to store summary query result and is accessible by projected category unique ids. this._oQueriesResult = {}; this.logger = features['Dashboard.Logger']; this.visualization = features['Visualization']; this.renderSequence = features['RenderSequence']; this.renderSequence.registerRenderStepProvider(this); this.content = content; this._api = { getSummary: this._getSummary.bind(this), isNotSupported: this.isNotSupported.bind(this) }; } /** * @override * @public * @function getAPI * Get the Summary feature API * @returns the public api of the summary feature that is accessible by clients. */ SummaryFeature.prototype.getAPI = function getAPI() { return this._api; }; /** * Load the feature if a data slot has "summaries" in slot definition, * and "summaryValues" in the same (table) or another (e.g. crossTab) slot definition with data items * of aggregation type belongs to any of the following: * [ 'none', 'countdistinct', 'avg', 'calculated', 'automatic' ] */ SummaryFeature.prototype.isEnabled = function isEnabled() { return _.find(this.visualization.getSlots().getSlotList(), function (slot) { return slot.getDefinition().getProperty('summaries') || slot.getDefinition().getProperty('summaryValues'); }); }; /** * Check unsupported conditions: * 1) If crossjoin of data items set is greater than limit * 2) no value filters in the widget (we can try to allow this in relational) * 3) no OLAP */ SummaryFeature.prototype.isNotSupported = function isNotSupported() { // 1. Not supported when number of nesting is greater than limit. var numberOfNest = 1; var slotAPIs = this.visualization.getSlots().getMappedSlotList(); _.each(slotAPIs, function (slotAPI) { numberOfNest = slotAPI.getDefinition().getProperty('summaries') ? numberOfNest * slotAPI.getDataItemList().length : numberOfNest; }); var isNotSupported = numberOfNest > NESTED_ITEM_LIMIT; if (isNotSupported) { return isNotSupported; } // 2. Not supported when there is post fact filter. var localFilters = this.visualization.getLocalFilters().getFilterList(); isNotSupported = _.find(localFilters, function (entry) { return entry.aggregationType && entry.preOrPost === 'post' && FilterSpecHelper.isRange(entry); }) !== undefined; if (isNotSupported) { return isNotSupported; } // 3. Not supported when is OLAP // Just check one dataItem since data items are from all the same data source var dataItemAPIs = slotAPIs && slotAPIs.length && slotAPIs[0].getDataItemList(); isNotSupported = !dataItemAPIs || dataItemAPIs.length === 0 || !dataItemAPIs[0].getMetadataColumn() || dataItemAPIs[0].getMetadataColumn().isOlapColumn(); if (isNotSupported) { return isNotSupported; } var topBottoms = {}; slotAPIs.forEach(function (slot) { var dataItems = slot.getDataItemList(); dataItems.forEach(function (dataItem) { if (dataItem.getTopBottom()) { // 4. Not supported when there is a topBottom defined on a fact data item and is in summaryValues slot (RTC#250663) if (slot.getDefinition().getProperty('summaryValues') && dataItem.getType() === 'fact') { isNotSupported = true; } // Map of topBottoms per slot if (!topBottoms[slot.getId()]) { topBottoms[slot.getId()] = []; } topBottoms[slot.getId()].push(dataItem); } }); }); if (isNotSupported) { return isNotSupported; } // 5. Not supported when there are top/bottom on different slots (RTC#243655) return _.size(topBottoms) > 1; }; /** * @override * @public * @function getExtraRenderSequenceSteps * Get the extra render sequence task required for the summary feature * @returns {Object[]} Array of task definitions */ SummaryFeature.prototype.getRenderStepList = function getRenderStepList() { return this.isEnabled() ? [{ id: 'data-summary', dependencies: ['data'], modulePath: 'dashboard-analytics/extensions/client-summaries/content-features/SummaryTask', moduleOptions: { summaryFeature: this, visualization: this.visualization, content: this.content } }] : []; }; /** * @private * @public * @function _getSummary * Get summary result by UIs * @param {string[]} categoryUIds - array of unique category data item ids to identify a summary query result * @param {string} measureUId - the unique id of the measure to get the summary * @param {string[]} tupleIds - array of unique tuple id to get a summary tuple cell * @returns {Promise} A promise which resolves the summary query result when the result is ready */ SummaryFeature.prototype._getSummary = function _getSummary(categoryUIds, measureUId, tupleIds) { var _this = this; var originalCategoryUIds = categoryUIds.slice(0); var queryResultPromise = this._oQueriesResult[this._getCategoryQueryKey(categoryUIds)]; if (queryResultPromise) { return queryResultPromise.then(function (summaryResult) { return summaryResult.getValue(originalCategoryUIds, measureUId, tupleIds); }).catch(function (error) { // return Promise.reject(this._buildErrorObj(error)); return _this._buildErrorObj(error); }); } else { return Promise.resolve(this._buildErrorObj()); } }; /** * @returns error object from different error case */ SummaryFeature.prototype._buildErrorObj = function _buildErrorObj() { return { error: StringResources.get('errorCellWarning') }; }; /** * @returns {object} an object of an array of combination of category unique ids of the widget and an array of the measures with aggregation * types in SUMMARY_TYPES. If a widget has slot1 = [A, B, C], slot2 = [D, E, F], value slot = [G, H] where aggregate for G and H is avg. * @example result = { * aCategoryUIds: [[A],[AB],[ABC],[D],[DE],[EDF],[AD],[ADE],[ADEF],[ABD],[ABDE],[ABDEF],[ABCD],[ABCDE]], * aOrdinalUIds: [G, H] * } */ SummaryFeature.prototype.getProjectionListObj = function getProjectionListObj() { this._oQueryProjectionObj = {}; var aSummarySlotsDataItems = []; // An array of category slot dataItems array where the summary is corresponding to var aSummaryValuesDataItems = []; // An array of ordinal slot dataItems array where the summary is applied t var dataSlots = this.visualization.getSlots().getMappedSlotList(); dataSlots.forEach(function (slotAPI) { if (slotAPI.getDefinition().getProperty('summaries')) { var categoryDataItems = _.filter(slotAPI.getDataItemList(), function (dataItem) { return dataItem.getType() === 'attribute'; }); if (categoryDataItems) { aSummarySlotsDataItems.push(categoryDataItems); } } if (slotAPI.getDefinition().getProperty('summaryValues')) { var ordinalDataItems = _.filter(slotAPI.getDataItemList(), function (dataItem) { return SummaryTypes.indexOf(dataItem.getAggregation()) !== -1 && dataItem.getType() === 'fact'; }); aSummaryValuesDataItems = aSummaryValuesDataItems.concat(ordinalDataItems); } }); if (aSummarySlotsDataItems.length || aSummaryValuesDataItems.length) { if (aSummarySlotsDataItems.length) { // 1) Build unique id sets from all data items of all category slots. E.g. slot1 = [A, B, C], slot2 = [D, E, F] // aSlotDataItemUIdSets = [ // [A, AB, ABC], // [D, DE, EDF] // ] var aSlotDataItemUIdSets = this._buildIdSetsFromSlotsDataItems(aSummarySlotsDataItems); // 2) Build cross join set from direct unique id sets if (aSlotDataItemUIdSets.length) { aSlotDataItemUIdSets = this._crossJoinSlotUIdSets(aSlotDataItemUIdSets); } var aResultCategoryUIds = _.flatten(aSlotDataItemUIdSets, true); aResultCategoryUIds.pop(); // Remove the detail unique id combinations this._oQueryProjectionObj.aCategoryUIds = aResultCategoryUIds; } else { this._oQueryProjectionObj.aCategoryUIds = []; } if (aSummaryValuesDataItems.length) { // Array of summary values slots dataItems UIs. var aSummaryValuesUIds = _.map(aSummaryValuesDataItems, function (valueDataItem) { return valueDataItem.getId(); }); this._oQueryProjectionObj.aOrdinalUIds = aSummaryValuesUIds; } else { this._oQueryProjectionObj.aOrdinalUIds = []; } } return this._oQueryProjectionObj; }; SummaryFeature.prototype._buildIdSetsFromSlotsDataItems = function _buildIdSetsFromSlotsDataItems(aSummarySlotsDataItems) { var aSlotDataItemUIdSets = []; aSummarySlotsDataItems.forEach(function (dataItems) { var currentUIds = []; var aUIdsPerSlot = []; dataItems.forEach(function (dataItem) { currentUIds = currentUIds.concat(dataItem.getId()); aUIdsPerSlot.push(currentUIds); }); aSlotDataItemUIdSets.push(aUIdsPerSlot); }); return aSlotDataItemUIdSets; }; SummaryFeature.prototype._crossJoinSlotUIdSets = function _crossJoinSlotUIdSets(aSlotDataItemUIdSets) { var crossjointUIds = []; for (var i = 0; i < aSlotDataItemUIdSets.length; i++) { var aSet1 = aSlotDataItemUIdSets[i]; var j = i + 1; while (aSlotDataItemUIdSets[j] && aSlotDataItemUIdSets[j].length) { crossjointUIds.push(this._crossJoin(aSet1, aSlotDataItemUIdSets[j])); j++; } } return aSlotDataItemUIdSets.concat(crossjointUIds); }; /** * Build a crossjoin set of 2 sets. * @example * set1 = [A, AB, ABC] * set2 = [D, DE, DEF] * result = [ * AD, * ADE, * ADEF, * ABD, * ABDE, * ABDEF, * ABCD, * ABCDE, * ABCDEF // The last element generated is a detail row * ] * * @param {Array} aUIdSet1 first set which is an array of unique UIs * @param {Array} aUIdSet2 second set. An array of unique UIs * @returns a crossjoin set of aUIdSet1 and aUIdSet2 */ SummaryFeature.prototype._crossJoin = function _crossJoin(aUIdSet1, aUIdSet2) { var crossjointUIds = []; aUIdSet1.forEach(function (keySet1) { var jointKeys = []; aUIdSet2.forEach(function (keySet2) { jointKeys = keySet1.concat(keySet2); crossjointUIds.push(jointKeys); }); }); return crossjointUIds; }; /** * @private * @public * Add a summary result to queries map. * The corresponding entry is the query promise. * @param {integer} idx the index of the aCategoryUIds * @param {Object} queryPromise the summary query promise */ SummaryFeature.prototype.addSummaryQueryResult = function addSummaryQueryResult(idx, queryPromise) { var _this2 = this; this._oQueriesResult[this._getCategoryQueryKey(this._oQueryProjectionObj.aCategoryUIds[idx])] = queryPromise.then(function (queryResult) { return new SummaryQueryResult(queryResult.getResult(), _this2.logger); }).catch(function (error) { _this2.logger.error(error); return Promise.reject(error); }); }; /** * @private * @param {string []} aCategoryUId - an array of strings represent the category unique ids. * @returns string - unique summary query key */ SummaryFeature.prototype._getCategoryQueryKey = function _getCategoryQueryKey(aCategoryUId) { // The key is built from all category data item unique ids. // The overall summary query doesn't have any category unique id. var uniqueIds = aCategoryUId && aCategoryUId.length ? aCategoryUId : [OVERALL_SUMMARY_QUERY_KEY]; uniqueIds.sort(); return uniqueIds.join(); }; return SummaryFeature; }(); return SummaryFeature; }); //# sourceMappingURL=SummaryFeature.js.map