123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514 |
- 'use strict';
- /*
- *+------------------------------------------------------------------------+
- *| Licensed Materials - Property of IBM
- *| IBM Cognos Products: BI Dashboard
- *| (C) Copyright IBM Corp. 2017, 2019
- *|
- *| US Government Users Restricted Rights - Use, duplication or disclosure
- *| restricted by GSA ADP Schedule Contract with IBM Corp.
- *+------------------------------------------------------------------------+
- */
- define(['jquery', 'underscore', './VisQueryPostProcessor', '../../nls/StringResources', '../QueryResultDataUtils', '../../../../apiHelpers/SlotAPIHelper'], function ($, _, VisQueryPostprocessor, StringResources, QueryResultDataUtils, SlotAPIHelper) {
- 'use strict';
- /**
- * 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 PostprocessorClass = VisQueryPostprocessor.extend({
- /**
- * @Constructor
- * @param {Object} options
- */
- init: function init(options) {
- PostprocessorClass.inherited('init', this, arguments);
- this.mappingAPI = options.mappingAPI;
- },
- /**
- * 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}
- */
- _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
- */
- _canProcess: function _canProcess() {
- var slotAPIs = this.mappingAPI.getSlotAPIs();
- var hasMultiMeasuresSeries = false,
- hasMultiMeasuresVal = false;
- _.each(slotAPIs, function (slotAPI) {
- hasMultiMeasuresSeries = hasMultiMeasuresSeries || slotAPI.isMultiMeasuresSeries();
- hasMultiMeasuresVal = hasMultiMeasuresVal || slotAPI.isMultiMeasuresValue();
- });
- return hasMultiMeasuresSeries && hasMultiMeasuresVal;
- },
- /**
- * Get the mapped projectionId for each dataItem.
- *
- * note: the logic here should be the same in visQueryBuilder.js:_slotToProjections()
- */
- _getQueryProjectionId: function _getQueryProjectionId(slotAPI, dataItemIdx) {
- // We don't project MultiMeasureSeries dataItem
- if (slotAPI.isMultiMeasuresSeriesOrValue(dataItemIdx)) return null;
- var validDataItemIds = _.filter(slotAPI.getDataItemRefs(), function (dataItemUniqueId, ind) {
- return !slotAPI.isMultiMeasuresSeriesOrValue(ind);
- });
- if (slotAPI.isStacked() && validDataItemIds.length > 1) {
- return slotAPI.getId();
- }
- return slotAPI.getDataItemAPI(dataItemIdx).getUniqueId();
- },
- /**
- * 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.
- */
- _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 slotAPIs = this.mappingAPI.getSlotAPIs();
- for (var i = 0; i < slotAPIs.length; i++) {
- var slot = slotAPIs[i];
- for (var dataItemIdx = 0; dataItemIdx < slot.getDataItemAPIs().length; dataItemIdx++) {
- var queryProjectionId = this._getQueryProjectionId(slot, dataItemIdx);
- if (queryProjectionId) {
- 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 (slot.getFinalSlotType() === 'ordinal' && !slot.getDefinition().multiMeasure) {
- result.commonIndices[newEntry.index] = true;
- }
- }
- (slot.getFinalSlotType() === 'category' ? 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
- */
- _processDataItems: function _processDataItems(dataItems) {
- var resultData = this._queryResultData;
- var slotAPIs = this.mappingAPI.getSlotAPIs();
- // rebuild the dataItems from scratch!
- resultData.dataItems.length = 0;
- // maintain the order based on slots
- _.each(slotAPIs, function (slotAPI) {
- var dataItem;
- var isCategory = slotAPI.getFinalSlotType() === 'category';
- if (isCategory) {
- if (slotAPI.isMultiMeasuresSeries()) {
- var measures = _.map(dataItems.ords, function (ordinal) {
- if (ordinal && ordinal.item && ordinal.item.itemClass && ordinal.slotId === 'values') {
- return ordinal.item.itemClass.h[0];
- }
- return undefined;
- });
- measures = _.compact(measures);
- // apply the multiple measures as a series
- dataItem = this._getSeriesDataItemforMultipleMeasures(dataItems.cats, measures, slotAPI);
- } else {
- dataItem = this._getDataItem(dataItems.cats, slotAPI);
- }
- } else if (slotAPI.isMultiMeasuresValue()) {
- // apply the value for all measures
- dataItem = this._getValueDataItemforMultipleMeasures(slotAPI);
- } else {
- dataItem = this._getDataItem(dataItems.ords, slotAPI);
- }
- if (dataItem) {
- resultData.dataItems.push(dataItem);
- }
- }.bind(this));
- dataItems.extraDataItems.forEach(function (extraDataItem) {
- extraDataItem.index = resultData.dataItems.length;
- resultData.dataItems.push(extraDataItem.dataItem);
- });
- },
- /**
- * Find the data item mapped to a slotAPI
- */
- _getDataItem: function _getDataItem(items, slotAPI) {
- var slotItem = _.find(items, function (item) {
- return item.slotId === slotAPI.getId();
- });
- return slotItem ? slotItem.item : null;
- },
- /**
- * Collect the unique values from a category data item
- */
- _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
- */
- _getSeriesDataItemforMultipleMeasures: function _getSeriesDataItemforMultipleMeasures(cats, measures, slotAPI) {
- return {
- itemClass: this._getSeriesDataItemClass(cats, slotAPI),
- items: this._getSeriesTupleItems(cats, measures, slotAPI)
- };
- },
- /**
- * Collect all data item Ids from a given slot
- */
- _getSlotDataItemIds: function _getSlotDataItemIds(slotAPI) {
- return _.map(slotAPI.getDataItemAPIs(), function (dataItemAPI) {
- return dataItemAPI.getItemId();
- });
- },
- /**
- * Collect all series data item class
- */
- _getSeriesDataItemClass: function _getSeriesDataItemClass(cats, slotAPI) {
- var ids = this._getSlotDataItemIds(slotAPI);
- var stackedItem = this._getDataItem(cats, slotAPI);
- return {
- id: slotAPI.getId(),
- h: _.map(ids, function (id, index) {
- if (slotAPI.isMultiMeasuresSeriesOrValue(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
- */
- _getSeriesTupleItems: function _getSeriesTupleItems(cats, measures, slotAPI) {
- var rows = [];
- var baseRows = [];
- var catItem = this._getDataItem(cats, slotAPI);
- var stackIds = catItem ? _.map(catItem.itemClass.h, function (header) {
- return header.u;
- }) : [];
- var ids = this._getSlotDataItemIds(slotAPI);
- _.each(ids, function (id, index) {
- var _this = this;
- var isMultiMeasureSeries = slotAPI.isMultiMeasuresSeriesOrValue(index);
- var values = isMultiMeasureSeries ? measures : this._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
- _.each(baseRows, function (row) {
- // duplicate the values tuple
- var clone = values.slice();
- _.each(clone, function (value, valueIndex) {
- var newRow = valueIndex === 0 ? row : $.extend(true, {}, row);
- newRow.t[index] = isMultiMeasureSeries ? {
- u: _this._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
- _.each(baseRows, 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 ? _this._getMultiMeasureMun(index) : 'undefined',
- d: value && value.d ? value.d : 'undefined',
- aggregate: value.aggregate
- } : value]
- };
- }));
- }
- }.bind(this));
- // 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
- */
- _getValueDataItemforMultipleMeasures: function _getValueDataItemforMultipleMeasures(slotAPI) {
- return {
- itemClass: {
- id: slotAPI.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
- */
- _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();
- _.each(resultData.data, function (datapoint) {
- this._restructureDataPoint(dataItems, datapoint, newIndices, newData, multiMeasureSeriesId);
- }.bind(this));
- 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]
- * }
- */
- _allocateDataPointIndices: function _allocateDataPointIndices(extraDataItems) {
- var catIndex = 0;
- var commonValueIndex = 0;
- /**
- * [todo] livewidget-cleanup: need to be refactored to use the new api.
- */
- var slotAPIs = this.mappingAPI.getSlotAPIs();
- var indices = {
- seriesIndex: -1,
- valueIndex: -1,
- commonIndices: [],
- catIndices: []
- };
- _.each(slotAPIs, function (slotAPI, slotIndex) {
- var isCategory = slotAPI.getFinalSlotType() === 'category';
- if (isCategory) {
- if (slotAPI.isMultiMeasuresSeries()) {
- indices.seriesIndex = slotIndex;
- } else {
- indices.catIndices[catIndex++] = slotIndex;
- }
- } else if (slotAPI.isMultiMeasuresValue()) {
- indices.valueIndex = slotIndex;
- } else {
- indices.commonIndices[commonValueIndex++] = slotIndex;
- }
- }.bind(this));
- 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
- */
- _restructureDataPoint: function _restructureDataPoint(dataItems, datapoint, indices, newData, multiMeasureSeriesId) {
- var _this2 = this;
- var catValueIndex = 0;
- var commonValueIndex = 0;
- var point = [];
- var ordValues = []; // has to be in order
- var catItemInSeries = void 0;
- var seriesIndexValue = 0;
- var slotAPIs = this.mappingAPI.getSlotAPIs();
- // Prepare the common portion of the data point
- _.each(datapoint.pt, function (value, valueIndex) {
- var category = _.find(dataItems.cats, function (cat) {
- return cat.index === valueIndex;
- });
- if (category) {
- var slotAPI = _.find(slotAPIs, function (slotAPI) {
- return slotAPI.getId() === category.slotId;
- });
- if (slotAPI.isMultiMeasuresSeries()) {
- 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 = [];
- _.each(ordValues, function (ordValue, indexValue) {
- var pt = indexValue === 0 ? point : point.slice();
- pt[indices.seriesIndex] = _this2._getSeriesIndex(dataItems, indexValue, catItemInSeries, seriesIndexValue, processedIndices, multiMeasureSeriesId);
- pt[indices.valueIndex] = ordValue;
- newData.push({ pt: pt });
- processedIndices.push(indexValue);
- });
- },
- _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 && _.map(catItemInSeries.items[originalIndex].t, 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) {
- _.find(multiSeriesResultItem.items, function (tuple, idx) {
- var muns = _.pluck(tuple.t, 'u');
- var matched = _.difference(targetTuple, muns).length === 0 && !_.contains(processedIndices, idx);
- if (matched) {
- resultIndex = idx;
- }
- return matched;
- });
- }
- return resultIndex;
- },
- _getMultiMeasureMun: function _getMultiMeasureMun(index) {
- this._multiMeasureSlotAPI = this._multiMeasureSlotAPI || _.find(this.mappingAPI.getSlotAPIs(), function (slotAPI) {
- return slotAPI.isMultiMeasuresValue();
- });
- var dataItemAPIs = this._multiMeasureSlotAPI.getDataItemAPIs();
- return dataItemAPIs[index].getUniqueId();
- },
- /**
- * Find the slot ID where the multiMeasureSeries is
- */
- _getMultiMeasureSeriesId: function _getMultiMeasureSeriesId() {
- var slotAPIs = this.mappingAPI.getSlotAPIs();
- var multiMeasureSeriesSlot = _.find(slotAPIs, function (slotAPI) {
- return slotAPI.isMultiMeasuresSeries();
- });
- return multiMeasureSeriesSlot && multiMeasureSeriesSlot.getId();
- }
- });
- return PostprocessorClass;
- });
- //# sourceMappingURL=PostProcessMeasuresAsSeries.js.map
|