'use strict'; /** * Licensed Materials - Property of IBM * IBM Cognos Products: Dashboard * (C) Copyright IBM Corp. 2013, 2019 * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp. * * VisQueryBuilder * The VisQueryBuilder is responsible for turning the information in the dataMappingManager into a DSS query. */ define(['underscore', '../../../lib/@waca/core-client/js/core-client/ui/core/Class', './VisQueryDataItemBuilder', './QueryFilterSpec', './VisQueryDefinition'], function (_, Class, VisQueryDataItemBuilder, QueryFilterSpec, VisQueryDefinition) { 'use strict'; var VisQueryBuilder = null; // class declaration VisQueryBuilder = Class.extend({ init: function init(attributes) { console.debug('visQueryBuilder (lw) init'); VisQueryBuilder.inherited('init', this, arguments); _.forEach(attributes, function (attribute, key) { this[key] = attribute; }.bind(this)); this.visQueryDataItemBuilder = new VisQueryDataItemBuilder({ visAPI: attributes.visAPI }); }, /** * create a visualization query * @param queryContext - the context of the query. * @returns the queryDefinition in a form that can be executed. */ create: function create(queryContext) { var aMappedLayers = []; var oLayers = this._getQueryLayers(); if (oLayers) { _.each(oLayers.getLayers(), function (layer) { if (this.visAPI.isMappingComplete(layer.id)) { aMappedLayers.push(layer); } }.bind(this)); } if (aMappedLayers.length === 0) { aMappedLayers.push(this.visAPI.getDefaultLayer()); } queryContext.visibleItemsMap = this._createVisibleItemsMapFromLiveWidgetModel(); var aDataItems = this._createQueryDataItemsFromLiveWidgetModel(queryContext); var aProjections = this._createQueryProjectionsFromLiveWidgetModel(queryContext); //Mapping of all the projected data items (necessary for local filters) queryContext.projectedDataItemsMap = this._createVisibleItemsMapFromLiveWidgetModel(true); //Remove aFields, it should not be needed. var aFields = this._createQueryDefinitionFieldsFromLiveWidgetModel(); var aFilters = this.createLocalFilters(queryContext, aMappedLayers); var aQueryHints = this._createQueryHints(queryContext); var oVisQueryDefinition = new VisQueryDefinition({ 'layers': aMappedLayers, 'layeredEntities': { 'dataItems': aDataItems, 'projections': aProjections, 'fields': aFields, 'filters': aFilters }, 'commonEntities': { 'queryHints': aQueryHints } }); return oVisQueryDefinition; }, /** * Turn on multiEdgeSort based on following rules: * 1: if slot is isMultiMeasuresSeries and is Rank, does not consider as edge * 2: if slot is isMultiMeasuresSeries and is stacked, considered as one edge * 3: if slot is stacked, it is considered as one edge * 4: if slot is of type ordinal, does not consider as edge * if number of edges is more than one, then turn on multiEdgeSort otherwise not * @param queryHints the queryHint for query * @return {boolean} true if number of edges to sort is more than 1. * @private */ _queryHintMultiEdgeSort: function _queryHintMultiEdgeSort(queryHints) { var _this = this; var numberOfEdges = 0; if (queryHints && !queryHints.multiEdgeSort) { return false; } _.forEach(this.visAPI.getDataSlots(), function (slotAPI) { if (_this._canQuery(slotAPI) && slotAPI.getFinalSlotType() !== 'ordinal' && slotAPI.getDataItemAPIs().length > 0) { numberOfEdges++; } }); return numberOfEdges > 1; }, _createQueryHints: function _createQueryHints(queryContext) { var queryHints = {}; if (queryContext.entities && queryContext.entities.definition && queryContext.entities.definition.queryHints) { _.extend(queryHints, queryContext.entities.definition.queryHints); } if (queryContext.entities.preferredModelItems && queryContext.entities.preferredModelItems.length) { queryHints.preferredModelItems = queryContext.entities.preferredModelItems; } // Multi edge sort is only required by the main data query // and is not required by auxiliary queries such as summary queries (for performance reasons) if (queryContext.auxQuery || !this._queryHintMultiEdgeSort(queryHints)) { delete queryHints.multiEdgeSort; } if (queryContext.renderContext && queryContext.renderContext.extraInfo && queryContext.renderContext.extraInfo.sender === 'realtimeRefresh') { // Set the dataCacheExpiry to 40% of the refresh time var refreshValue = queryContext.renderContext.extraInfo.queryRefresh && queryContext.renderContext.extraInfo.queryRefresh.value; var dataCacheExpiryValue; switch (queryContext.renderContext.extraInfo.queryRefresh && queryContext.renderContext.extraInfo.queryRefresh.unit) { case 'seconds': dataCacheExpiryValue = parseInt(refreshValue * 4 / 10) || 1; break; case 'minutes': dataCacheExpiryValue = parseInt(refreshValue * 60 * 4 / 10) || 1; break; case 'hours': dataCacheExpiryValue = parseInt(refreshValue * 3600 * 4 / 10) || 1; break; default: dataCacheExpiryValue = 1; } queryHints.dataCacheExpiry = dataCacheExpiryValue.toString(); } else { switch (queryContext.entities && queryContext.entities.properties && queryContext.entities.properties.localCache) { case 'yes': queryHints.dataCacheExpiry = '3600'; break; case 'no': queryHints.dataCacheExpiry = '0'; break; default: } } return [queryHints]; }, //TODO: Remove the function and sub functions when 'fields' is not needed in the flow for Endor _createQueryDefinitionFieldsFromLiveWidgetModel: function _createQueryDefinitionFieldsFromLiveWidgetModel() { var fields = []; var slotAPIs = this._getQuerySlots(); _.each(slotAPIs, function (slotAPI) { fields = fields.concat(this._slotToFields(slotAPI)); }.bind(this)); return fields; }, _createQueryDataItemsFromLiveWidgetModel: function _createQueryDataItemsFromLiveWidgetModel(queryContext) { var slotAPIs = this._getQuerySlots(); // Set slot sort priority to highest data items sort priority plus 1. var priority = 0; var aDataItems = []; _.each(slotAPIs, function (slotAPI) { _.each(slotAPI.getDataItemAPIs(), function (dataItemAPI) { var sortObj = dataItemAPI.getSort(); if (sortObj && priority < sortObj.priority) { priority = sortObj.priority; } }); }); this.visQueryDataItemBuilder.setQuerySlotSortPriority(priority + 1); var multiMeasures = this.visAPI.findSlotDataItem(function (dataItemAPI) { return dataItemAPI.getItemId() === '_multiMeasuresSeries'; }); // If multi measures are dropped to one slot, should not do client (local) sort. var serverSort = multiMeasures ? true : false; this.visQueryDataItemBuilder.setServerSort(serverSort); _.each(slotAPIs, function (slotAPI) { aDataItems = aDataItems.concat(this._slotToDataItems(slotAPI, queryContext)); }.bind(this)); if (queryContext && queryContext.queryOptions) { // list of dataitems to be added to the generated dataitems list if (queryContext.queryOptions.extraDataItems) { aDataItems = aDataItems.concat(queryContext.queryOptions.extraDataItems); } // list of dataitem ids to be removed from the generated dataitems list if (typeof queryContext.queryOptions.filterDataItems === 'function') { aDataItems = _.filter(aDataItems, queryContext.queryOptions.filterDataItems); } } return aDataItems; }, /** * @param {boolean} includeAllDataItems As long as it can query, it will add the data items * This is for local filters, where we need all the project data items to pass. */ //Use the slots and assigned dataItems to create a map of all visible items in this visualization. _createVisibleItemsMapFromLiveWidgetModel: function _createVisibleItemsMapFromLiveWidgetModel(includeAllDataItems) { var visibleItemsMap = {}; this.visAPI.eachSlotDataItem(function (dataItemAPI, slotAPI) { // Exclude ordinal data items in visibleItemsMap. // Brushing should be converted to a filter when data item is in an ordinal slot type. // Adding isLatLong conditon for Lat/Ling data. It's a special type in that it's technically treated as categorical slots. if (this._canQuery(slotAPI) && (slotAPI.isLatLong() || slotAPI.getType() !== 'ordinal' || includeAllDataItems)) { visibleItemsMap[dataItemAPI.getItemId()] = visibleItemsMap[dataItemAPI.getItemId()] || []; var visibleItemInfo = { dataItemAPI: dataItemAPI, layerId: slotAPI.getLayerId(), viewId: slotAPI.getViewId() }; visibleItemsMap[dataItemAPI.getItemId()].push(visibleItemInfo); } }.bind(this)); return visibleItemsMap; }, _createQueryProjectionsFromLiveWidgetModel: function _createQueryProjectionsFromLiveWidgetModel(queryContext) { var aProjections = []; var slotAPIs = this._getQuerySlots(); _.each(slotAPIs, function (slotAPI) { aProjections = aProjections.concat(this._slotToProjections(slotAPI, queryContext)); }.bind(this)); if (queryContext && queryContext.queryOptions) { // list of projections to be added to the generated projections list if (queryContext.queryOptions.extraProjections) { aProjections = aProjections.concat(queryContext.queryOptions.extraProjections); } // list of projection ids to be removed from the generated projections list if (typeof queryContext.queryOptions.filterProjections === 'function') { aProjections = _.filter(aProjections, queryContext.queryOptions.filterProjections); } } return aProjections; }, _createQueryFilterSpec: function _createQueryFilterSpec(visibleItemsMap) { return new QueryFilterSpec(visibleItemsMap); }, //@returns the categorical items for the specified layer _getCategoryItemsForLayer: function _getCategoryItemsForLayer(layerId) { var slotAPIs = this._getQuerySlots(); var catSlotsForLayer = _.filter(slotAPIs, function (slotAPI) { return slotAPI.getLayerId() === layerId && slotAPI.getFinalSlotType() === 'category'; }); //Extract the dataItemId's for each slot in this layer. var itemIdsForLayer = []; _.each(catSlotsForLayer, function (categorySlot) { _.each(categorySlot.getDataItemAPIs(), function (dataItemAPI) { itemIdsForLayer.push(dataItemAPI.getItemId()); }); }); return itemIdsForLayer; }, //@returns true if categorySet1 and categorySet2 are different (in both directions) _categoriesAreDifferent: function _categoriesAreDifferent(categorySet1, categorySet2) { return _.difference(categorySet1 || [], categorySet2 || []).length > 0 || _.difference(categorySet2 || [], categorySet1 || []).length > 0; }, /** * Return the local query filters * @param queryContext * @param mappedLayers * @returns query filters */ createLocalFilters: function createLocalFilters(queryContext, mappedLayers) { var _this2 = this; var queryFilters = []; if (_.isEmpty(queryContext)) { return queryFilters; } var excludeNonProjectedRangeFilters = false; var categoryItemsForFirstMappedLayer = null; _.each(mappedLayers, function (mappedLayer) { //NonProjected range filters cannot be applied to any layer whose categories are not the same as the first mapped layer //(because the aggregate of different categories is different.) var categoryItemsForThisLayer = mappedLayers.length > 1 && _this2._getCategoryItemsForLayer(mappedLayer.id) || []; categoryItemsForFirstMappedLayer = categoryItemsForFirstMappedLayer || categoryItemsForThisLayer; excludeNonProjectedRangeFilters = _this2._categoriesAreDifferent(categoryItemsForFirstMappedLayer, categoryItemsForThisLayer); var filtersConvertedToItemSelections = []; _.each(queryContext.filtersConvertedToDataItemSelections, function (excludedFilterEntry) { filtersConvertedToItemSelections.push(excludedFilterEntry.getId()); }); var queryFilterSpec = _this2._createQueryFilterSpec( /*projected data items*/queryContext.projectedDataItemsMap); if (queryContext.entities) { queryFilterSpec.addFiltersToSpec(queryContext.entities.localFilters, { edgeFilterExceptions: filtersConvertedToItemSelections, layerId: mappedLayer.getId(), excludeNonProjectedRangeFilters: excludeNonProjectedRangeFilters, shouldApplyIsNullValueExpression: _this2._shouldApplyIsNullValueExpression.bind(_this2), addExtraQueryDataItem: queryContext && queryContext.queryOptions && queryContext.queryOptions.addExtraQueryDataItem }); queryFilterSpec.addFiltersToSpec(queryContext.entities.searchFilters, { edgeFilterExceptions: filtersConvertedToItemSelections }); var layerFilters = queryFilterSpec.hasFilterSpec() ? queryFilterSpec.getFilterSpec() : null; if (layerFilters) { _.each(layerFilters, function (layerFilter) { layerFilter.layerId = mappedLayer.getId(); queryFilters.push(layerFilter); }); } } }); if (queryContext.queryOptions && queryContext.queryOptions.extraFilters) { // list of filters to be added to the generated filters list queryFilters = queryFilters.concat(queryContext.queryOptions.extraFilters); } return queryFilters; }, /** * @returns true if need to combine isnull in exclude filter (in FilterEntry._buildEdgeNullValueExpresstionQuerySpec) * 'filters': [ { 'type': 'pre', 'expression': { 'or': [{ 'itemId': 'someItem', 'operator': 'isnull' }, { 'operator': 'notin', 'itemId': 'someItem', 'values': [ 'XXX' ] } ] } }] * * The function should return false for an OLAP column */ _shouldApplyIsNullValueExpression: function _shouldApplyIsNullValueExpression(filterEntry) { if (!filterEntry.columnId) { return false; } var column = this.visAPI.getMetadataColumn(filterEntry.columnId); return column && !column.isOlapColumn(); }, _canQuery: function _canQuery(slotAPI) { return !(slotAPI.isMultiMeasuresSeries() && !slotAPI.isStacked()); }, _getQuerySlots: function _getQuerySlots() { return _.filter(this.visAPI.getDataSlots(), function (slotAPI) { return this._canQuery(slotAPI); }.bind(this)); }, _getQueryLayers: function _getQueryLayers() { return this.visAPI.getLayers(); }, //TODO: Remove the function and sub functions when 'fields' is not needed in the flow for Endor _slotToFields: function _slotToFields(slotAPI) { //, bDoNotInverseSort) { var fields = []; _.each(slotAPI.getDataItemAPIs(), function (dataItemAPI, index) { if (!slotAPI.isMultiMeasuresSeriesOrValue(index)) { fields.push(this._slotItemToField(slotAPI, dataItemAPI)); } }.bind(this)); return fields; }, /** * @private * @return {Array} An array of projection IDs of all items assigned to a single slot. **/ _slotToProjections: function _slotToProjections(slotAPI, queryContext) { var projections = []; // filterProjections will be a function only for summary queries var filterProjections = queryContext && queryContext.queryOptions && queryContext.queryOptions.filterProjections; var isSummary = typeof filterProjections === 'function'; var validDataItemIds = _.filter(slotAPI.getDataItemRefs(), function (id, index) { return !slotAPI.isMultiMeasuresSeriesOrValue(index); }); // If the slot is stacked and if this is not a summary query, produce a single projection for the slot (using the slot id) if (slotAPI.isStacked() && validDataItemIds.length > 1 && !isSummary) { projections.push({ id: slotAPI.getId(), layerId: slotAPI.getLayerId() }); } else { _.each(validDataItemIds, function (dataItemId) { projections.push({ id: dataItemId, layerId: slotAPI.getLayerId() }); }); } return projections; }, /** * @private * @param slotAPI - the slotAPI of interest * @param queryContext - the context of the query. * @return {Array} An array of Data Item objects assigned to a single slot. **/ _slotToDataItems: function _slotToDataItems(slotAPI, queryContext) { var aDataItems = []; var nestList = []; _.each(slotAPI.getDataItemAPIs(), function (dataItemAPI, index) { if (!slotAPI.isMultiMeasuresSeriesOrValue(index)) { aDataItems.push(this.visQueryDataItemBuilder.build(slotAPI, dataItemAPI, queryContext, index)); //keep track of the list of dataItems assigned to a slot. //If a slot has multiple items and the slot is type stacked, //use this list as the nestList of a stacke nestList.push(dataItemAPI.getUniqueId()); } }.bind(this)); if (nestList.length > 1 && slotAPI.isStacked()) { //If the slot is stacked, produce a dataItem whose name matches the id //with nested items whose names match the dataItems. aDataItems.push({ id: slotAPI.getId(), nest: nestList, layerId: slotAPI.getLayerId() }); } return aDataItems; }, /** * @returns the field equivalent of this dataslot. */ //TODO: Remove the function and sub functions when 'fields' is not needed in the flow for Endor _slotItemToField: function _slotItemToField(slotAPI, dataItemAPI) { return { id: slotAPI.getId(), columnId: dataItemAPI.getItemId(), column: { isDateOrTimeType: function isDateOrTimeType() { return dataItemAPI.isDateOrTimeType(); }, getFormat: function getFormat() { return null; //Needed for summary view rendering. } }, //sort: sortOrder, //limit: mapping ? mapping.limit : undefined, //sourceCategory: mapping ? mapping.getSourceCategory() : undefined, //filterException: this.getFilterException(), aggType: dataItemAPI.getAggregationType(), getType: function () { return this._getSlotType(slotAPI, dataItemAPI); }.bind(this), label: dataItemAPI.getLabel(), dataType: dataItemAPI.getDataType(), layerId: slotAPI.getLayerId() //useInTopBottomQueries: this.shouldBeUsedInTopBottomQueries(), //categorySubType: slot.getCategorySubType(), //ignoreSlotForViz: this.definition.ignoreSlotForViz, //postSortResultsDescending: this.postSortResultsDescending() }; }, /** * Returns the type of this slot as one of category, ordinal or 'any'. * Type 'any' will only be returned for unmapped slots * @returns the slot type. For slots of type 'any', the mapping will be used to determine the type. */ _getSlotType: function _getSlotType(slotAPI, dataItemAPI) { if (slotAPI.getType() === 'any') { return dataItemAPI.getType() === 'attribute' ? 'category' : dataItemAPI.getType() === 'fact' ? 'ordinal' : 'category'; } return slotAPI.getType(); } }); return VisQueryBuilder; }); //# sourceMappingURL=VisQueryBuilder.js.map