'use strict'; /* *+------------------------------------------------------------------------+ *| Licensed Materials - Property of IBM *| IBM Cognos Products: BI Dashboard *| (C) Copyright IBM Corp. 2018, 2019 *| *| US Government Users Restricted Rights - Use, duplication or disclosure *| restricted by GSA ADP Schedule Contract with IBM Corp. *+------------------------------------------------------------------------+ */ define(['../../../lib/@waca/core-client/js/core-client/ui/core/Class', '../../../apiHelpers/SlotAPIHelper', 'dashboard-analytics/widgets/livewidget/query/QueryResultData'], function (Class, SlotAPIHelper, QueryResultData) { 'use strict'; /** * The ResultDataReader is a helper for the ResultsDataReaderTask but works at a single dataset level. * It is similar to a PostProcessor except that its results * are not fed into the main data stream for rendering. * Each feature that has the "ResultDataReaderPlugin" FeatureTag defined will be Called * at standard points through the data.... onDataStart, onDataRow, onDataDone. * (A feature can choose to implement one or all of those apis) **/ var ResultDataReader = Class.extend({ /** * @Constructor * @param {Object} widgetApi - The widget. * @param {Object} cbCreateQueryResultData (optional) - define a query result data API for testing etc. */ init: function init(widgetApi, visualization, cbCreateQueryResultData) { ResultDataReader.inherited('init', this, arguments); this.visualization = visualization; this._createQueryResultData = cbCreateQueryResultData || this._createQueryResultData; //Get all available features that are identified as resultDataReaderPlugins. //Since we ask for this on every read of the data, we can limit it to enabled features. this.plugins = widgetApi.getMatchingFeatures('ResultDataReaderPlugin', /*bEnabledOnly*/true); }, _createQueryResultData: function _createQueryResultData(resultData) { return new QueryResultData(resultData); }, hasPlugins: function hasPlugins() { return this.plugins.length > 0; }, onNewResults: function onNewResults(resultData) { this.plugins.forEach(function (plugin) { return plugin.onNewResults && plugin.onNewResults(resultData); }); }, /** * Process the supplied resultData (postProcessed query result for a single DSS query.) * @param queryResult - the resultData * @param dataViewId - the data view id (also known as the layerId) * @param renderContext - the renderContext (which flows through the renderSequnce) * @returns An array of 1 or more promises which is resolved when all work for all plugins is complete. */ processData: function processData(queryResult, dataViewId, renderContext) { //Wrap the processed data rows in a promise. if (this.plugins.length) { return this._readResultData(queryResult, dataViewId, renderContext); } return [Promise.resolve({ hasPlugins: false })]; }, //TODO: queryResultParm is normally a queryResult API but can be a query result object if the new query api is disabled... _readResultData: function _readResultData(queryResultParm, dataViewId, renderContext) { var useNewQueryApi = renderContext && renderContext.useAPI; var queryResult = useNewQueryApi ? queryResultParm //TODO: queryResult can be the old QueryResultData object if the new query api is disabled... : queryResultParm._resultData && this._createQueryResultData(queryResultParm._resultData); var rowHeaders = this._buildRowHeaders(dataViewId); var rowCount = useNewQueryApi ? queryResult.getRowCount() : queryResult.getDatapointCount(); this.plugins.forEach(function (plugin) { return plugin.onDataStart && plugin.onDataStart(queryResult, rowHeaders); }); for (var rowIdx = 0; rowIdx < rowCount; rowIdx += rowHeaders.valueSlotMeasureCount) { this.plugins.forEach(function (plugin) { return plugin.onDataRow && plugin.onDataRow(rowIdx); }); } var promises = []; this.plugins.forEach(function (plugin) { if (plugin.onDataDone) { //For a given plugin, onDataDone may or may not return a promise. //If any plugins do, we need to wait for them. var onDataDonePromise = plugin.onDataDone(renderContext); if (onDataDonePromise) { promises.push(onDataDonePromise); } } }); return promises.length ? promises : [Promise.resolve({})]; }, _buildRowHeaders: function _buildRowHeaders(dataViewId) { var measureItemHeaders = []; //measure is helpful for understanding (its actually 'ordinal') var mappedSlots = this.visualization.getSlots().getMappedSlotList(); var slotAPIs = dataViewId ? mappedSlots.filter(function (slotAPI) { return slotAPI.getDefinition().getDatasetIdList().indexOf(dataViewId) !== -1; }) : mappedSlots; var valueSlotMeasureCount = 1; //For any row of data, the categoryItemHeaders represents the set of categoryItemIds and the cell indexes where they are found. var categoryItemHeaders = { itemIds: [], //The itemIds of the categorical items. columnIndexes: [] //The column indexes where the categorical items occur (Note: when nested, 1 column index = 1 tuple) }; //The first level result column index represents how a row of data is returned. //EXAMPLE //When a slot is stackable, all items in the slot are returned as a 'tuple' (as a sub-array of the firstLevelArray) //When a slot is not stackable, each item in the slot is returned under a separate first level array index. //slot0, slot1 is stackable: [[2004, Summer], [Fax, Wednesday], 300] //slot0 is not stackable, slot1 is stackable...returns the same data as [[2004], [Summer], [Fax, Wednesday], 300] var firstLevelResultColumnIndex = 0; slotAPIs.forEach(function (slotAPI) { var isMultiMeasure = SlotAPIHelper.isMultiMeasuresValueSlot(slotAPI); //This flag signifies that there are multiple items in this slot under the same first level column. var isSlotStackable = slotAPI.isStacked() || isMultiMeasure; if (slotAPI.getDefinition().getType() === 'ordinal' && slotAPI.getDataItemList().length > 1 && isMultiMeasure) { //If there's an ordinal slot with more than 1 item assigned to it, it is the "multi-measure value" slot. //For this case, the pre-processor will create a virtual category to represent the seriesIndex //(one value per measure name) and the value that pairs with that name will be reported on that row. //ie: Year, Revenue, Cost becomes Year SERIES Value // 2007 10000 500 2007 Revenue 10000 // 2007 Cost 500 //Note that slotAPIs match the post-processed structure (values would be at slot index 2 (and that dataitem index)) valueSlotMeasureCount = slotAPI.getDataItemList().length; } slotAPI.getDataItemList().forEach(function (dataItemAPI, dataItemIndex) { var theMultiMeasureValueIndex = isMultiMeasure ? dataItemIndex : 0; if (dataItemAPI.getType() === 'fact') { //Set up a measureItemHeaders for each ordinal. measureItemHeaders.push({ dataItemUniqueId: dataItemAPI.getId(), index: firstLevelResultColumnIndex, valueItemIndex: theMultiMeasureValueIndex, //This will only be non-zero for the stacked-measures case aggregate: dataItemAPI.getAggregation(), multiMeasuresValueItem: isMultiMeasure }); } else { categoryItemHeaders.itemIds.push(dataItemAPI.getColumnId()); if (isSlotStackable) { //When collecting categories, getCellValue returns a tuple for stacked items. //Because of this, we only want the value index of the parent cell. //Unless this item does not have stackable items. if (dataItemIndex === 0) { categoryItemHeaders.columnIndexes.push(firstLevelResultColumnIndex); } } else { categoryItemHeaders.columnIndexes.push(firstLevelResultColumnIndex); } } //If the slot is not stackable (ie: table or crosstab), increment the column index for each item in the slot. firstLevelResultColumnIndex += !isSlotStackable ? 1 : 0; }); //If the slot is stackable, increment the column index once per slot. firstLevelResultColumnIndex += isSlotStackable ? 1 : 0; }); return { layerId: dataViewId, categoryItemHeaders: categoryItemHeaders, measureItemHeaders: measureItemHeaders, valueSlotMeasureCount: valueSlotMeasureCount }; } }); return ResultDataReader; }); //# sourceMappingURL=ResultDataReader.js.map