'use strict'; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /* *+------------------------------------------------------------------------+ *| Licensed Materials - Property of IBM *| IBM Cognos Products: BI Dashboard *| (C) Copyright IBM Corp. 2017, 2020 *| *| US Government Users Restricted Rights - Use, duplication or disclosure *| restricted by GSA ADP Schedule Contract with IBM Corp. *+------------------------------------------------------------------------+ */ define(['jquery', 'underscore', './QueryPostProcessor', '../../../../widgets/livewidget/nls/StringResources', '../../../../widgets/livewidget/query/QueryResultDataUtils', '../../../../apiHelpers/SlotAPIHelper'], function ($, _, QueryPostProcessor, StringResources, QueryResultDataUtils, SlotAPIHelper) { /** * This Class does Query result response post-processing such as processing the original query result * into a data re-structure to support multiple measures as series. **/ var PostProcessMeasuresAsSeries = function (_QueryPostProcessor) { _inherits(PostProcessMeasuresAsSeries, _QueryPostProcessor); /** * @Constructor * @param {Object} options */ function PostProcessMeasuresAsSeries(options) { _classCallCheck(this, PostProcessMeasuresAsSeries); var _this = _possibleConstructorReturn(this, _QueryPostProcessor.call(this, options)); _this.visualization = options.visualization; _this.queryDefinition = options.queryDefinition; return _this; } /** * Process Query result based on the passed options to constructor. * For instance, limit the rows of datapoints so that in the response, for * specified dataitem, 'product', there will be only three tuples returned * * @return {QueryResultData} */ PostProcessMeasuresAsSeries.prototype._processData = function _processData() { if (this._queryResultData && this._queryResultData.data && this._canProcess()) { var result = this._categorizeDataItems(); // post-process the data items this._processDataItems(result); // post-process the data points this._processDataPoints(result); } //Process Done return this._queryResultData; }; /** * Determine whether multiple measure series is supported */ PostProcessMeasuresAsSeries.prototype._canProcess = function _canProcess() { var slots = this._getDatasetSlotList(); var hasMultiMeasuresSeries = false, hasMultiMeasuresVal = false; _.each(slots, function (slot) { hasMultiMeasuresSeries = hasMultiMeasuresSeries || SlotAPIHelper.isMultiMeasuresSeriesSlot(slot); hasMultiMeasuresVal = hasMultiMeasuresVal || SlotAPIHelper.isMultiMeasuresValueSlot(slot); }); return hasMultiMeasuresSeries && hasMultiMeasuresVal; }; /** * Get the mapped projectionId for each dataItem. * * note: the logic here should be the same in visQueryBuilder.js:_slotToProjections() */ PostProcessMeasuresAsSeries.prototype._getQueryProjectionId = function _getQueryProjectionId(slot, dataItemIdx) { // We don't project MultiMeasureSeries dataItem if (SlotAPIHelper.isMultiMeasuresSeriesOrValue(slot, dataItemIdx)) { return null; } var dataItemList = slot.getDataItemList(); var validDataItemIds = _.filter(dataItemList, function (dataItem, idx) { return !SlotAPIHelper.isMultiMeasuresSeriesOrValue(slot, idx); }); if (slot.isStacked() && validDataItemIds.length > 1) { return slot.getId(); } return dataItemList[dataItemIdx].getId(); }; /** * Categorize the existing data items by category and ordinals * Keep track of data items original index and also the slot Id it which the data item is mapped. * The original index later allows the data points (prior to restructure) to access the mapping slot and data item. * * Despite the result data being the original data, the slot mapping already includes the multi-measures series mapping. * The slot mapping for mult-measures series needs to be ignored at this stage in order to match with the original data. */ PostProcessMeasuresAsSeries.prototype._categorizeDataItems = function _categorizeDataItems() { var result = { cats: [], ords: [], commonIndices: {}, mappedIndices: {}, extraDataItems: [] }; var resultQueryDataItems = this._queryResultData.dataItems; var allProjectionIds = _.map(resultQueryDataItems, function (dataItem) { return dataItem.itemClass.id; }); var mappedProjectionIds = []; var slotList = this._getDatasetSlotList(); for (var i = 0; i < slotList.length; i++) { var slot = slotList[i]; var dataItemList = slot.getDataItemList(); for (var dataItemIdx = 0; dataItemIdx < dataItemList.length; dataItemIdx++) { var queryProjectionId = this._getQueryProjectionId(slot, dataItemIdx); if (queryProjectionId) { var type = dataItemList[dataItemIdx].getType(); var idx = allProjectionIds.indexOf(queryProjectionId); var newEntry = { item: idx !== -1 ? resultQueryDataItems[idx] : undefined, slotId: slot.getId(), index: idx !== -1 ? idx : undefined }; if (newEntry.index !== undefined) { result.mappedIndices[newEntry.index] = true; if (type === 'fact' && !slot.getDefinition().isMultiMeasureSupported()) { result.commonIndices[newEntry.index] = true; } } (type === 'attribute' ? result.cats : result.ords).push(newEntry); mappedProjectionIds.push(queryProjectionId); } } } var extraDataItemIds = _.difference(allProjectionIds, mappedProjectionIds); resultQueryDataItems.forEach(function (dataItem, index) { if (extraDataItemIds.indexOf(dataItem.itemClass.id) !== -1) { result.commonIndices[index] = true; result.extraDataItems.push({ dataItem: dataItem }); } }); return result; }; /** * Process the data items * Consolidate the multiple measures to one measure data item and a extra series to indicate the measure */ PostProcessMeasuresAsSeries.prototype._processDataItems = function _processDataItems(dataItems) { var _this2 = this; var resultData = this._queryResultData; var slots = this._getDatasetSlotList(); // rebuild the dataItems from scratch! resultData.dataItems.length = 0; // maintain the order based on slots for (var i = 0; i < slots.length; i++) { var slot = slots[i]; var dataItem = void 0; var isCategory = slot.getDataItemList()[0].getType() === 'attribute'; if (isCategory) { if (SlotAPIHelper.isMultiMeasuresSeriesSlot(slot)) { var measures = _.map(dataItems.ords, function (ordinal) { var slotDefinition = _this2.visualization.getDefinition().getSlot(ordinal.slotId); var supportsMultiMeasure = slotDefinition.getProperty('multiMeasure'); if (ordinal && ordinal.item && ordinal.item.itemClass && supportsMultiMeasure) { return ordinal.item.itemClass.h[0]; } return undefined; }); measures = _.compact(measures); // apply the multiple measures as a series dataItem = this._getSeriesDataItemforMultipleMeasures(dataItems.cats, measures, slot); } else { dataItem = this._getDataItem(dataItems.cats, slot); } } else if (SlotAPIHelper.isMultiMeasuresValueSlot(slot)) { // apply the value for all measures dataItem = this._getValueDataItemForMultipleMeasures(slot); } else { var matchDataItems = this._getDataItem(dataItems.ords, slot, /*findAll*/true); resultData.dataItems.push.apply(resultData.dataItems, matchDataItems); } if (dataItem) { resultData.dataItems.push(dataItem); } } dataItems.extraDataItems.forEach(function (extraDataItem) { extraDataItem.index = resultData.dataItems.length; resultData.dataItems.push(extraDataItem.dataItem); }); }; /** * Find the data item mapped to a slot */ PostProcessMeasuresAsSeries.prototype._getDataItem = function _getDataItem(items, slot, findAll) { var slotItems = []; items.forEach(function (item) { if (item.slotId === slot.getId()) { slotItems.push(item.item); } }); return findAll ? slotItems : slotItems[0]; }; /** * Collect the unique values from a category data item */ PostProcessMeasuresAsSeries.prototype._getCategoryDataItemValues = function _getCategoryDataItemValues(categoryItem, stackIndex) { if (stackIndex > -1) { var stackValues = {}; // collect the unique values _.each(categoryItem.items, function (item) { stackValues[item.t[stackIndex].u] = item.t[stackIndex]; }); var keys = Object.keys(stackValues); return _.map(keys, function (key) { return stackValues[key]; }); } return []; }; /** * Create a category data item for the multiple measures series */ PostProcessMeasuresAsSeries.prototype._getSeriesDataItemforMultipleMeasures = function _getSeriesDataItemforMultipleMeasures(cats, measures, slot) { return { itemClass: this._getSeriesDataItemClass(cats, slot), items: this._getSeriesTupleItems(cats, measures, slot) }; }; /** * Collect column ids from data item list of a given slot */ PostProcessMeasuresAsSeries.prototype._getSlotColumnIds = function _getSlotColumnIds(slot) { return _.map(slot.getDataItemList(), function (dataItem) { return dataItem.getColumnId(); }); }; /** * Collect all series data item class */ PostProcessMeasuresAsSeries.prototype._getSeriesDataItemClass = function _getSeriesDataItemClass(cats, slot) { var ids = this._getSlotColumnIds(slot); var stackedItem = this._getDataItem(cats, slot); return { id: slot.getId(), h: _.map(ids, function (id, index) { if (SlotAPIHelper.isMultiMeasuresSeriesOrValue(slot, index)) { return { u: SlotAPIHelper.MULTI_MEASURES_SERIES, d: StringResources.get('MeasuresCaption') }; } else { if (stackedItem && stackedItem.itemClass) { return _.find(stackedItem.itemClass.h, function (header) { return header.u === id; }); } return undefined; } }) }; }; /** * Collect all series data item tuple items */ PostProcessMeasuresAsSeries.prototype._getSeriesTupleItems = function _getSeriesTupleItems(cats, measures, slot) { var _this3 = this; var rows = []; var baseRows = []; var catItem = this._getDataItem(cats, slot); var stackIds = catItem ? _.map(catItem.itemClass.h, function (header) { return header.u; }) : []; var ids = this._getSlotColumnIds(slot); var _loop = function _loop(index) { var id = ids[index]; var isMultiMeasureSeries = SlotAPIHelper.isMultiMeasuresSeriesOrValue(slot, index); var values = isMultiMeasureSeries ? measures : _this3._getCategoryDataItemValues(catItem, stackIds.indexOf(id)); //clean up rows and baseRows, so that no defect occurs in the loop with more than 2 ids. if (rows.length != 0) { baseRows = rows; } rows = []; if (baseRows.length > 0) { if (values.length > 0) { // Should preserve the drop order in multi measure series slot so iterate from baseRows to values for (var j = 0; j < baseRows.length; j++) { var row = baseRows[j]; // duplicate the values tuple var clone = values.slice(); for (var valueIndex = 0; valueIndex < clone.length; valueIndex++) { var value = clone[valueIndex]; var newRow = valueIndex === 0 ? row : $.extend(true, {}, row); newRow.t[index] = isMultiMeasureSeries ? { u: _this3._getMultiMeasureMun(valueIndex), d: value.d, aggregate: value.aggregate } : value; rows.push(newRow); } } } else { // since there are no data to duplicate, simply update the base rows with null data baseRows.forEach(function (row) { row.t[index] = { u: null, d: null }; }); } } else { // prepare the base tuple item rows baseRows.push.apply(baseRows, _.map(values, function (value, index) { return { t: [isMultiMeasureSeries ? { u: value && value.u ? _this3._getMultiMeasureMun(index) : 'undefined', d: value && value.d ? value.d : 'undefined', aggregate: value.aggregate } : value] }; })); } }; for (var index = 0; index < ids.length; index++) { _loop(index); } // finish with the base rows if not expecting a category (i.e. not nested) if (!catItem && rows.length === 0) { rows.push.apply(rows, baseRows.slice()); } return rows; }; /** * Create a measure data item for the multiple measure values */ PostProcessMeasuresAsSeries.prototype._getValueDataItemForMultipleMeasures = function _getValueDataItemForMultipleMeasures(slot) { return { itemClass: { id: slot.getId(), h: [{ u: SlotAPIHelper.MULTI_MEASURES_VALUE, d: StringResources.get('ValuesCaption') }] } }; }; /** * Process all data point to restructure the data for multiple measures value and series */ PostProcessMeasuresAsSeries.prototype._processDataPoints = function _processDataPoints(dataItems) { // prepare the new index for each slot var newIndices = this._allocateDataPointIndices(dataItems.extraDataItems); // restructured data var newData = []; var resultData = this._queryResultData; var multiMeasureSeriesId = this._getMultiMeasureSeriesId(); var resultDataData = resultData.data; for (var i = 0; i < resultDataData.length; i++) { var datapoint = resultDataData[i]; this._restructureDataPoint(dataItems, datapoint, newIndices, newData, multiMeasureSeriesId); } resultData.data = newData; }; /** * Allocate the datapoint index for each data item. * Since the multiple measures are wrapped into a single measure with an extra series for the measures, * None of the slots would have multiple data items. * * ie. category, multimeasure_series, category(series), value * indices: { * seriesIndex: 1, * valueIndex: 3, * catIndices: [0, 2] * } */ PostProcessMeasuresAsSeries.prototype._allocateDataPointIndices = function _allocateDataPointIndices(extraDataItems) { var catIndex = 0; var commonValueIndex = 0; var slots = this._getDatasetSlotList(); var indices = { seriesIndex: -1, valueIndex: -1, commonIndices: [], catIndices: [] }; slots.forEach(function (slot, slotIndex) { var isCategory = slot.getDataItemList()[0].getType() === 'attribute'; if (isCategory) { if (SlotAPIHelper.isMultiMeasuresSeriesSlot(slot)) { indices.seriesIndex = slotIndex; } else { indices.catIndices[catIndex++] = slotIndex; } } else if (SlotAPIHelper.isMultiMeasuresValueSlot(slot)) { indices.valueIndex = slotIndex; } else { slot.getDataItemList().forEach(function (dataItem, index) { indices.commonIndices[commonValueIndex++] = slotIndex + index; }); } }); extraDataItems.forEach(function (dataItem) { return indices.commonIndices[commonValueIndex++] = dataItem.index; }); return indices; }; /** * Restructure a single data point. * Consolidate multiple measures to one value * Categorize the measures to one category */ PostProcessMeasuresAsSeries.prototype._restructureDataPoint = function _restructureDataPoint(dataItems, datapoint, indices, newData, multiMeasureSeriesId) { var catValueIndex = 0; var commonValueIndex = 0; var point = []; var ordValues = []; // has to be in order var catItemInSeries = void 0; var seriesIndexValue = 0; var slots = this._getDatasetSlotList(); // Prepare the common portion of the data point for (var valueIndex = 0; valueIndex < datapoint.pt.length; valueIndex++) { var value = datapoint.pt[valueIndex]; var category = void 0; for (var i = 0; i < dataItems.cats.length; i++) { if (dataItems.cats[i].index === valueIndex) { category = dataItems.cats[i]; break; } } if (category) { var slot = void 0; for (var j = 0; j < slots.length; j++) { if (slots[j].getId() === category.slotId) { slot = slots[j]; break; } } if (SlotAPIHelper.isMultiMeasuresSeriesSlot(slot)) { catItemInSeries = category.item; seriesIndexValue = value; } else { point[indices.catIndices[catValueIndex++]] = value; } } else if (dataItems.commonIndices[valueIndex]) { point[indices.commonIndices[commonValueIndex++]] = value; } else if (dataItems.mappedIndices[valueIndex]) { // Keep track of the remaining ordinal values (as long as they're 'valid' - mapped to a slot) ordValues.push(value); } } // Duplicate the data point by each measure var processedIndices = []; for (var indexValue = 0; indexValue < ordValues.length; indexValue++) { var ordValue = ordValues[indexValue]; var pt = indexValue === 0 ? point : point.slice(); pt[indices.seriesIndex] = this._getSeriesIndex(dataItems, indexValue, catItemInSeries, seriesIndexValue, processedIndices, multiMeasureSeriesId); pt[indices.valueIndex] = ordValue; newData.push({ pt: pt }); processedIndices.push(indexValue); } }; PostProcessMeasuresAsSeries.prototype._getSeriesIndex = function _getSeriesIndex(dataItems, ordIndex, catItemInSeries, originalIndex, processedIndices, multiMeasureSeriesId) { // The multiSeriesResultItem has tuples built from _getSeriesTupleItems. It's a cross joined tuple set of the multi measure series var multiSeriesResultItem = this._queryResultData && this._queryResultData.dataItems && this._queryResultData.dataItems[QueryResultDataUtils.getDataItemIndex(this._queryResultData, multiMeasureSeriesId)]; // The target tuple to search in multiSeriesResultItem and return its index var measureMun = this._getMultiMeasureMun(ordIndex); var targetTuple = catItemInSeries && catItemInSeries.items[originalIndex].t.map(function (tuple) { return tuple.u; }) || []; targetTuple.push(measureMun); // Search the target tuple in the cross joined tuple set of the multi measure series, and return the index var resultIndex = -1; if (multiSeriesResultItem.items.length !== 0) { for (var idx = 0; idx < multiSeriesResultItem.items.length; idx++) { var tuple = multiSeriesResultItem.items[idx]; //check if tuple matches targetTuple & index is not in processedIndices var matched = this._isMatched(tuple, targetTuple, processedIndices, idx); if (matched) { resultIndex = idx; break; } } } return resultIndex; }; PostProcessMeasuresAsSeries.prototype._foundInTarget = function _foundInTarget(u, targetTuple) { var found = false; for (var j = 0; j < targetTuple.length; j++) { if (targetTuple[j] === u) { found = true; break; } } return found; }; PostProcessMeasuresAsSeries.prototype._isMatched = function _isMatched(tuple, targetTuple, processedIndices, idx) { //check if tuple matches targetTuple & index is not in processedIndices var idxContained = processedIndices.indexOf(idx) !== -1; if (idxContained === false) { var matches = 0; var len = tuple.t.length; for (var i = 0; i < len; i++) { if (this._foundInTarget(tuple.t[i].u, targetTuple)) { matches++; } } if (matches === tuple.t.length) { return true; } } return false; }; PostProcessMeasuresAsSeries.prototype._getMultiMeasureMun = function _getMultiMeasureMun(index) { if (!this._multiMeasureSlotAPI) { var slotList = this._getDatasetSlotList(); for (var i = 0; i < slotList.length; i++) { var slot = slotList[i]; if (SlotAPIHelper.isMultiMeasuresValueSlot(slot)) { this._multiMeasureSlotAPI = slot; break; } } } var dataItemAPIs = this._multiMeasureSlotAPI.getDataItemList(); return dataItemAPIs[index].getId(); }; /** * Find the slot ID where the multiMeasureSeries is */ PostProcessMeasuresAsSeries.prototype._getMultiMeasureSeriesId = function _getMultiMeasureSeriesId() { var slots = this._getDatasetSlotList(); var multiMeasureSeriesSlot = _.find(slots, function (slot) { return SlotAPIHelper.isMultiMeasuresSeriesSlot(slot); }); return multiMeasureSeriesSlot && multiMeasureSeriesSlot.getId(); }; PostProcessMeasuresAsSeries.prototype._getDatasetSlotList = function _getDatasetSlotList() { var _this4 = this; if (!this._datasetSlotList) { var slots = this.visualization.getSlots().getMappedSlotList(); this._datasetSlotList = slots.filter(function (slot) { return slot.getDefinition().getDatasetIdList().indexOf(_this4.queryDefinition.getId()) !== -1; }); } return this._datasetSlotList; }; return PostProcessMeasuresAsSeries; }(QueryPostProcessor); return PostProcessMeasuresAsSeries; }); //# sourceMappingURL=PostProcessMeasuresAsSeries.js.map