123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388 |
- 'use strict';
- /**
- * Licensed Materials - Property of IBM
- * IBM Cognos Products: 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(['./QueryDefinition', '../../../../lib/@waca/dashboard-common/dist/core/Model', '../../../../features/content/dataQueryExecution/DataQueryResult', '../QueryResultData', './QueryManagerGeoHelper', '../../../../lib/@waca/dashboard-common/dist/query/FacetDataObject', '../QueryResultObject', '../../../../features/content/dataQueryExecution/QueryResults', '../../../../features/content/dataQueryExecution/api/QueryResultsAPI', 'underscore'], function (QueryDefinition, Model, DataQueryResult, DeprecatedQueryResultData, Helper, FacetData, QueryResultObject, QueryResults, QueryResultsAPI, _) {
- 'use strict';
- /**
- * The QueryManager is responsible for hiding the details of querying for data,
- * filtering data and providing results to widgets.
- *
- * Widgets understand data as rows in 'slot order'.
- * The QueryManager does things like:
- * - Allowing widgets to form a query in a field order they specify (corresponding to slots and mappings)
- * to hide the fact that queries are made in 'dimension first' order
- * - returning the data back to the widget as rows in the same order (and as indexed columns or ranges)
- * - filtering, sorting.
- * - presenting a subset of rows
- */
- var QueryManager = Model.extend({
- init: function init(options) {
- QueryManager.inherited('init', this, arguments);
- this.dashboardAPI = options.dashboardAPI;
- this.logger = options.dashboardAPI && options.dashboardAPI.getGlassCoreSvc('.Logger');
- this.queryDefinition = new QueryDefinition({ dashboardAPI: this.dashboardAPI, logger: this.logger });
- //Latlong
- this.queryDefinitions = [];
- // the map used to cache refreshing queries
- this._refreshQueriesMap = {};
- },
- getVesion: function getVesion() {
- return 'endor';
- },
- clearCachedResponses: function clearCachedResponses() {
- this._topBottomQueryCache.clear();
- },
- /**
- * The following routine is used to send the needed queries as ajax calls
- * and return their promises in an array. We can wait for all the queries to
- * complete using this promises array.
- * @param {Array} queryOptionsArray object holding the querySpec and topBottomQuery
- * @param {String} sender (Optional) query sender
- * @return {Promise}
- */
- _sendQueriesAndGetPromises: function _sendQueriesAndGetPromises(queryOptionsArray, sender, useAPI) {
- var _this = this;
- var queryPromises = [];
- this.queryService.updateRequestId(sender);
- _.each(queryOptionsArray, function (queryOptions) {
- queryOptions.senderId = _this.visAPI.ownerWidget.id;
- queryOptions.sender = sender;
- var sourceIdOrModule = useAPI ? queryOptions.sourceIdOrModule.getSourceId() : queryOptions.sourceIdOrModule;
- var queryService = useAPI ? _this.dashboardAPI.getFeature('QueryService') : _this.queryService;
- // Run the main query Spec (if one is defined)
- if (queryOptions.querySpec) {
- var queryPromise = useAPI ? queryService.executeQuery(sourceIdOrModule, queryOptions.querySpec, sender) : queryService.runQuery(queryOptions);
- queryPromises.push({ layerId: queryOptions.layerId, promise: queryPromise });
- }
- // Run the top and bottom query if it exists
- if (queryOptions.topBottomQuery) {
- var topbottomQueryPromise = useAPI ? queryService.executeQuery(sourceIdOrModule, queryOptions.topBottomQuery, sender) : _this.queryService.runQuery({
- querySpec: queryOptions.topBottomQuery,
- sourceIdOrModule: queryOptions.sourceIdOrModule
- });
- queryPromises.push({
- layerId: queryOptions.layerId,
- promise: topbottomQueryPromise,
- type: 'topbottom'
- });
- }
- });
- return queryPromises;
- },
- _handleSuccessfulCompletionOfQueries: function _handleSuccessfulCompletionOfQueries(renderContext, queryPromiseResults, queryOptions, oriRequests) {
- var _this2 = this;
- var mainQueryResults;
- var completedTopBottomQuery;
- var promiseResults = _.toArray(queryPromiseResults);
- var oQueryResultObject = new QueryResultObject(renderContext);
- var sLayerId;
- _.each(promiseResults, function (result, index) {
- var bIsMainQuery = false;
- sLayerId = oriRequests[index].layerId;
- if (!oriRequests[index].type) {
- bIsMainQuery = true;
- mainQueryResults = result.data;
- }
- //We assume currently one layer has only one query request
- if (oriRequests[index + 1] && oriRequests[index + 1].layerId === sLayerId && oriRequests[index + 1].type === 'topbottom') {
- completedTopBottomQuery = promiseResults[index + 1].data;
- }
- var oQueryOption = _.find(queryOptions, function (entry) {
- return entry.layerId === sLayerId;
- });
- var mainQueryThreshold = oQueryOption.querySpec.limit;
- var topBottomByAggregateId = {};
- if (oQueryOption.topBottomQuery) {
- _this2._updateTopBottomByAggregateId(topBottomByAggregateId, completedTopBottomQuery);
- }
- if (bIsMainQuery) {
- // Should be able to just pass mapped data items, but due to DSS defect 274285, temporarily pass slots to build result.
- var slots = _this2.visAPI.ownerWidget.getAPI().getFeature('Visualization').getSlots();
- var deprecatedQueryResult = new DeprecatedQueryResultData(mainQueryResults);
- var queryResult = new DataQueryResult(mainQueryResults, slots);
- // Make the query spec and raw response available as properties in the query result data object
- if (queryOptions[index] && queryOptions[index].querySpec) {
- queryResult.setPropertyValue('QuerySpec.internal', JSON.stringify(queryOptions[index].querySpec));
- }
- // Ideally we should access the raw data from the ajax response as raw string,
- // but currentlt we don't have access to it because we query using modelling which we should mve away from
- queryResult.setPropertyValue('RawData.internal', JSON.stringify(mainQueryResults));
- var queryResultAPI = queryResult.getAPI();
- _this2._callUnprocessedResultDataHandlers && _this2._callUnprocessedResultDataHandlers(mainQueryResults, { layerId: sLayerId }, queryResultAPI);
- _this2.postProcessCallback(mainQueryResults);
- oQueryResultObject.addQueryResultObject(sLayerId, deprecatedQueryResult, mainQueryThreshold, {
- topBottomMappings: topBottomByAggregateId
- }, queryResultAPI);
- }
- });
- return oQueryResultObject;
- },
- _handleSucceededQueries: function _handleSucceededQueries(queryPromiseResults, oriRequests) {
- var queryResults = new QueryResults();
- var type = void 0;
- _.each(_.toArray(queryPromiseResults), function (result, index) {
- if (!oriRequests[index].type) {
- type = QueryResultsAPI.QUERY_RESULT_TYPE.MAIN;
- } else if (oriRequests[index].type === QueryResultsAPI.QUERY_RESULT_TYPE.TOPBOTTOM) {
- type = QueryResultsAPI.QUERY_RESULT_TYPE.TOPBOTTOM;
- }
- queryResults.addResult(result, oriRequests[index].layerId, type);
- });
- return queryResults.getAPI();
- },
- /**
- * Update a map of top bottom results mapped by their 'aggregateId'.
- * An aggregate id is a key which is the minimum needed to uniquely identify an aggregate (ie: its columnId + aggregate type).
- * eg:{ 3sum: { topResult: 100, bottomResult: -500 },
- * 7avg: { topResult: 10000, bottomResult: 600 },
- * 7sum: { topResult: 100000, bottomResult: 60 }}
- *
- * @param topBottomByAggregateId - The object to be updated which maps one or more AggregateIds to the top and bottom result rows.
- * @param completedTopBottomQuery - a query object that includes the descriptor of what was executed and the query response.
- * (as returned from queryCache.getQueryPromise() when it resolves).
- * @returns the updated topBottomByAgggregateId map
- */
- _updateTopBottomByAggregateId: function _updateTopBottomByAggregateId(topBottomByAggregateId, completedTopBottomQuery) {
- if (!completedTopBottomQuery || !completedTopBottomQuery.dataItems || completedTopBottomQuery.dataItems.length < 1) {
- return topBottomByAggregateId;
- }
- //min and max are stored in result pairs 2 per corresponding field (eg: result for field1, field2 is min1 max1 min2 max2)
- var data = completedTopBottomQuery.data[0].pt;
- var dataItems = completedTopBottomQuery.dataItems;
- var numDataItems = dataItems.length;
- for (var dataItemIndex = 0; dataItemIndex < numDataItems; dataItemIndex += 2) {
- // Need the min, max and aggregate type for the data item.
- var dataItemMin = dataItems[dataItemIndex];
- var dataItemAggType = dataItemMin.itemClass.h[0].aggregate;
- var areDataItemsAggregates = !!dataItemAggType; // Only do top and bottom for aggregates
- if (areDataItemsAggregates) {
- var dataItemMinValue = data[dataItemIndex].v; // First data item is the min
- var dataItemMaxValue = data[dataItemIndex + 1].v; // Second data item is the max
- var facetDataItemMin = new FacetData({ u: dataItemMinValue }); // Create Min and Max facet data items for legacy purposes.
- var facetDataItemMax = new FacetData({ u: dataItemMaxValue }); // TODO This can be cleaned up further when the legacy query code is updated.
- var columnName = dataItemMin.itemClass.h[0].u.split('_MIN')[0];
- this._updateTopBottom(topBottomByAggregateId, columnName + dataItemAggType, facetDataItemMin, facetDataItemMax);
- }
- }
- return topBottomByAggregateId;
- },
- /**
- * If the passed in arguments for bottom/top are less/greater than the current top/bottom values for this aggregate, widen the range.
- *
- * @param topBottomByAggregateId a map of aggregates keyed by their id (which is columnId + aggregation type).
- * @param aggregateId - the aggregateId to update the top and bottom for.
- * @param bottom - the bottom value to process
- * @param top - the top value to process
- */
- _updateTopBottom: function _updateTopBottom(topBottomByAggregateId, aggregateId, bottom, top) {
- if (!topBottomByAggregateId[aggregateId]) {
- topBottomByAggregateId[aggregateId] = {
- bottomResult: bottom,
- topResult: top
- };
- return;
- }
- if (bottom.useValue < topBottomByAggregateId[aggregateId].bottomResult.useValue) {
- topBottomByAggregateId[aggregateId].bottomResult = bottom;
- }
- if (top.useValue > topBottomByAggregateId[aggregateId].topResult.useValue) {
- topBottomByAggregateId[aggregateId].topResult = top;
- }
- },
- /**
- * Run the query and, when ready, process the queryResponses
- * queryResponses is an array of one or more query responses. The format is different for 'main queries' and 'minmax' queries..
- * [
- * //index 1: main query in queryResponses array is the result
- * {results: [['data', 'row' 1],['data', 'row' 2]], hasNext: false, hasPrev: false },
- * //index 2, 3 form: minmax query.
- * {queryDescriptor: {}, response: [['min1', 'max1', 'min2', 'max2'...]] },
- * {minmaxresponse2}
- * ]
- * @param renderContext - information about the current render (including the deferred to be resolved when the query is complete)
- * @param queryOptionsArray - any options to be put on the query (such as whether geoJson should be processed, a rowLimit should be imposed etc).
- * @param querySender (optional) - unique string identifier of the query sender (defaults to 'QueryManager')
- * @param isAuxQuery true if is an auxiliary query
- * @returns a promise.
- */
- whenQueryResultsReady: function whenQueryResultsReady() {
- var renderContext = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
- var queryOptionsArray = arguments[1];
- var querySender = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'QueryManager';
- var _this3 = this;
- var useAPI = arguments[3];
- var isAuxQuery = arguments[4];
- var aOrigPromises = renderContext.noDataQuery ? this._getEmptyData(queryOptionsArray) : this._sendQueriesAndGetPromises(queryOptionsArray, querySender, useAPI);
- var queryPromises = _.pluck(aOrigPromises, 'promise');
- return Promise.all(queryPromises).then(function (results) {
- var ETags = [];
- _.each(results, function (result, index) {
- if (!aOrigPromises[index].type && !useAPI) {
- ETags.push(result.ETag); //Main Query
- }
- });
- // Check the last modified for the query data change.
- if (renderContext.extraInfo && renderContext.extraInfo.sender === 'realtimeRefresh') {
- // Use querySpec as the key to check if the query is already executed.
- var bAtLeastOneQueryChanged = false;
- _.each(queryOptionsArray, function (queryOptions, index) {
- var url = JSON.stringify(queryOptions.querySpec);
- if (!_this3._refreshQueriesMap[url] || _this3._refreshQueriesMap[url] !== ETags[index]) {
- bAtLeastOneQueryChanged = true;
- _this3._refreshQueriesMap[url] = ETags[index];
- }
- });
- if (!bAtLeastOneQueryChanged) {
- // Returns empty result when the query data did not change for real time refreshing.
- renderContext.sameQueryData = true;
- }
- }
- var result = void 0;
- if (!useAPI) {
- result = _this3._handleSuccessfulCompletionOfQueries(renderContext, results, queryOptionsArray, aOrigPromises);
- } else {
- result = _this3._handleSucceededQueries(results, aOrigPromises);
- }
- if (!isAuxQuery) {
- // Cache result if is not summary queries.
- _this3._queryResults = result;
- }
- return result;
- }).catch(function (jqXHR) {
- return Promise.reject(jqXHR);
- });
- },
- _getEmptyData: function _getEmptyData(queryOptions) {
- var _this4 = this;
- return _.map(queryOptions, function (option, index) {
- var dataItems = _this4.queryDefinitions[index].getQueryDataItems();
- return {
- layerId: option.layerId,
- promise: Promise.resolve({
- data: {
- dataItems: _.map(dataItems, function (dataItem) {
- var emptyDataItem = {
- itemClass: {
- h: [{
- u: dataItem.itemId,
- d: _this4.visAPI.getMetadataColumn(dataItem.itemId).getLabel()
- }],
- id: dataItem.id
- }
- };
- // ordinal dataitems requires an aggregation
- // while categorical dataitems requires an empty items array
- if (dataItem.aggregate) {
- emptyDataItem.itemClass.h[0].aggregate = dataItem.aggregate;
- } else {
- emptyDataItem.items = [];
- }
- return emptyDataItem;
- }),
- data: []
- }
- })
- };
- });
- },
- _shouldRejectGeoQueryFail: function _shouldRejectGeoQueryFail(geoResponse) {
- if (geoResponse && geoResponse.status === 'Fail') {
- if (!geoResponse.mapboxData) {
- return true;
- }
- if (geoResponse.mapboxData.length === 0) {
- return true;
- }
- return false;
- }
- return false;
- },
- /*
- * load the field values for the field in use
- */
- _getResultFieldValuesAtFieldIndex: function _getResultFieldValuesAtFieldIndex(queryResults, fieldIndex) {
- var values = [];
- var oDataItem;
- for (var i = 0; i < queryResults.getDataRows().length; i++) {
- oDataItem = queryResults.getDataItemByIndex(i, fieldIndex);
- values.push(oDataItem.displayValue);
- }
- return values;
- },
- getQueryResults: function getQueryResults() {
- return this._queryResults;
- },
- getQueryDefinition: function getQueryDefinition(layerId) {
- if (!layerId) {
- if (!this.queryDefinitions[0]) {
- this.queryDefinitions.push(new QueryDefinition({ dashboardAPI: this.dashboardAPI, logger: this.logger }));
- return this.queryDefinitions[0];
- }
- }
- var oResult = _.find(this.queryDefinitions, function (def) {
- return def.layerId === layerId;
- });
- if (!oResult) {
- oResult = this.queryDefinitions.push(new QueryDefinition({ layerId: layerId, dashboardAPI: this.dashboardAPI, logger: this.logger }));
- }
- return oResult;
- },
- getQueryDefinitions: function getQueryDefinitions() {
- return this.queryDefinitions;
- }
- });
- return QueryManager;
- });
- //# sourceMappingURL=QueryManager.js.map
|