'use strict'; /** * Licensed Materials - Property of IBM * IBM Watson Analytics (C) Copyright IBM Corp. 2018, 2019 * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp. * @module dashboard-analytics/widgets/livewidget/visapi/support/VisPossibleKeyDriversManager */ define(['dashboard-analytics/lib/@waca/core-client/js/core-client/ui/core/Class', 'underscore', '../../query/CommonQueryBuilder', '../../../../widgets/livewidget/nls/StringResources', '../../../../widgets/livewidget/query/QueryFilterSpec'], function (Class, _, CommonQueryBuilder, stringResources, QueryFilterSpec) { 'use strict'; /* * We want to limit possible key drivers to 250 so we don't overwhelm predict * or dss. More changes to come for this. */ var MAX_POSSIBLE_KEY_DRIVERS = 250; /** * Possible key drivers manager is an object which manages the interaction between * other components and the possible key driver service and model. * To make use of the response data from the service a calling * component must first ensure that the information has been queried. To do * this they must wait for the promise from whenPossibleKeyDriversAreReady to be * resolved. */ var VisPossibleKeyDriversManager = Class.extend({ // FPD Messages to add to the info indicator FPD_RESPONSE_WARNINGS: 'fpd_response_warnings', FPD_RESPONSE_ERRORS: 'fpd_response_errors', init: function init(options) { VisPossibleKeyDriversManager.inherited('init', this, arguments); this.visAPI = options.visAPI; this.logger = options.logger; this.ownerWidget = options.ownerWidget; this.content = this.ownerWidget.content; this.smartsEnabled = options.smartsEnabled; this.possibleDriversResponse = {}; }, isNewEditDriversEnabled: function isNewEditDriversEnabled() { if (this.ownerWidget.dashboardApi) { // Access featureChecker to check feature flag for new edit drivers dialog var featureChecker = this.ownerWidget.dashboardApi && this.ownerWidget.dashboardApi.getGlassCoreSvc('.FeatureChecker'); return !(featureChecker && featureChecker.checkValue('explore', 'newEditDriversDialog', 'false')); } return false; }, /*************************************************************************** * Public methods * ***************************************************************************/ /*************************************************************************** * Render Sequence methods * ***************************************************************************/ _clearWarningsAndErrorsFromIndicator: function _clearWarningsAndErrorsFromIndicator() { var visView = this.ownerWidget.getCurrentVis(); visView && visView.infoIndicator && visView.infoIndicator.clearMessagesWithIds([this.FPD_RESPONSE_WARNINGS, this.FPD_RESPONSE_ERRORS]); }, /** * Called during the KeyDriverTask of the render sequence. * @returns {Promise} which is resolved when we have a valid possible key drivers response * The response will be of the format: * { * recommendedDrivers: {Array[fieldIDForExpression, label, selected]} * nonRecommendedDrivers: {Array[fieldIDForExpression, label, selected]} */ whenPossibleKeyDriversAreReady: function whenPossibleKeyDriversAreReady(renderContext, targetId) { var _this = this; var useModelData = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; // Only get the possible drivers if mappings is complete. // TODO: Remove smartsEnabled once smarts is up and running stably. this._clearWarningsAndErrorsFromIndicator(); var visualization = this.content.getFeature('Visualization'); if (this.smartsEnabled && visualization.getSlots().isMappingComplete()) { /* * Possible drivers are cached per target ID so if users change the * target data item we do not always have to refetch them. We also want to * retry if we are currently in an error state. */ var inErrorState = this.errorState && this.errorState.inErrorState; if (!inErrorState && this._useCache(targetId)) { this._addWarningsAndErrorsToInfoIndicator(this.possibleDriversResponse[targetId].messages); return Promise.resolve(this.possibleDriversResponse[targetId]); } else { // So its not cached, lets check the model. var modelPossibleKeyDrivers = this.visAPI.getPossibleKeyDrivers(); if (!inErrorState && useModelData && modelPossibleKeyDrivers && modelPossibleKeyDrivers[targetId]) { // Its in the model so lets use it. var desc = modelPossibleKeyDrivers[targetId]; var messages = desc.messages; this._cacheKeyDrivers(targetId, desc); this._addWarningsAndErrorsToInfoIndicator(messages); return Promise.resolve(this.possibleDriversResponse[targetId]); } else { // Reset the error state to start fresh this.errorState = {}; // Alright, not in the cache and not in the model. Lets query for it. return this._queryForPossibleKeyDriversForTargetId(targetId).then(function (driverDesc) { var recommendedDrivers = driverDesc.possibleKeyDrivers; var messages = driverDesc.messages; var driversToUse = recommendedDrivers.length > MAX_POSSIBLE_KEY_DRIVERS ? recommendedDrivers.splice(0, MAX_POSSIBLE_KEY_DRIVERS) : recommendedDrivers; var optionsForSave = { messages: messages, targetId: targetId, recommended: driversToUse, isSilent: true, // Don't want to triger events on initial save. saveInModel: useModelData // save result in model only if we need to use model data }; _this._addWarningsAndErrorsToInfoIndicator(messages); var formattedDriverDesc = _this._getFormattedDriversDescription(optionsForSave); _this._saveKeyDriversInModelAndCache(formattedDriverDesc, optionsForSave); return Promise.resolve(_this.possibleDriversResponse[targetId]); }); } } } else { return Promise.resolve({}); } }, _useCache: function _useCache(targetId) { var response = this.possibleDriversResponse && this.possibleDriversResponse[targetId]; var keyDrivers = response && !response.cacheInvalidated && response.recommendedDrivers; return keyDrivers && keyDrivers.length > 0; }, _cacheKeyDrivers: function _cacheKeyDrivers(targetId, possibleDrivers) { this.possibleDriversResponse[targetId] = JSON.parse(JSON.stringify(possibleDrivers)); }, /* * Invalidate the key drivers cache * Calls to whenPossibleKeyDriversAreReady() will skip the cache when invalidated and rely on either model or query * * @param {string} targetId - targetId of the mapped column */ invalidateCache: function invalidateCache(targetId) { if (this.possibleDriversResponse[targetId]) { this.possibleDriversResponse[targetId].cacheInvalidated = true; } }, /*************************************************************************** * Data Request methods * ***************************************************************************/ /** * Execute a predict fast pattern detection request * * @param {string} targetId - targetId of the mapped column * @param {string[]} possibleKeyDrivers - list of possible key drivers * @param {string[]} methods - list of methods inquiring for the FPD request * @param {boolean} isPreview - indicator of whether the owner widget is a preview widget and do not need prompting for FPD request */ executeFastPatternDetectionRequest: function executeFastPatternDetectionRequest(targetId, possibleKeyDrivers, methods, promptControlFunctions) { var isPreview = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false; var module = this.visAPI.getModule(); var options = { sourceIdOrModule: module, querySpec: { version: '1.1', targetId: targetId, inputs: possibleKeyDrivers, filters: this._getPredictFilters(), dataFilters: this._getDataFilters(), methods: methods || ['drivers'] }, promptControlFunctions: promptControlFunctions, isPreview: isPreview }; return this.visAPI.getQueryManager().queryService.runPredictQuery(options); }, /** * @param {String} id of the mapped column. * @returns {Object} possibleKeyDrivers - {Array[ids]}, {Object} errorState */ getEnabledPossibleKeyDrivers: function getEnabledPossibleKeyDrivers(targetId) { /** * We need to find all selected possible key drivers. Basically all recommended * or non recommended drivers that are selected. */ var results = []; // TODO: Remove case where smarts isn't enabled when smarts is up and running stably. if (this.smartsEnabled) { if (this.possibleDriversResponse[targetId]) { var selectedRecommendedDrivers = _.filter(this.possibleDriversResponse[targetId].recommendedDrivers, function (item) { return item.selected; }); var selectedNonRecommendedDrivers = _.filter(this.possibleDriversResponse[targetId].nonRecommendedDrivers, function (item) { return item.selected; }); var totalSelected = selectedRecommendedDrivers.concat(selectedNonRecommendedDrivers); // We only need the ids of the selected possible key drivers. results = _.pluck(totalSelected, 'fieldIDForExpression'); } return { possibleKeyDrivers: results, errorState: this.errorState }; } else { var module = this.visAPI.getModule(); // Get the selected inputs. module.getMetadataColumns(function (col) { var columnId = col.getId(); var columnType = col.getType(); if (columnType === 'fact' || columnType === 'attribute') { // Ensure the selected column ids are still valid. if (columnId !== targetId) { results.push(columnId); } } }); return { possibleKeyDrivers: results, errorState: this.errorState }; } }, /*************************************************************************** * Dialog methods * ***************************************************************************/ /** * Return the possible drivers in a state that is easily displayable. * This is called when the dialog is created/refreshed. * @param {String} id of the mapped column. * @returns {Array} of objects with a form similar to: * { * id:{string} * label:{string} * selected: {boolean} * } */ getPossibleKeyDriversDisplayState: function getPossibleKeyDriversDisplayState(itemId) { var desc = this.possibleDriversResponse[itemId]; return this._getFormattedDisplayStateFromDriverDescription(itemId, desc); }, /** * Update the model with the new possible key driver state. Called when a user * has modified the possible key driver states through the dialog. * @param {String} id of the mapped column. * @param {Array} of key drivers indicating their selection state. * { * selected: {Boolean} true iff the key driver is currently selected (i.e. on) * label: {String} localized label used to display the key driver in a dialog * id: {String} ID used to store the key driver when selected in the spec. * } */ possibleKeyDriverSelectionStateChanged: function possibleKeyDriverSelectionStateChanged(targetId, keyDriversDisplayStates) { // Remove the label property as its existence will affect the comparisons we will make this._removeColumnLabelsFromDisplayStates(keyDriversDisplayStates); var modelKeyDrivers = this.visAPI.getPossibleKeyDrivers(); var possibleDriverState = modelKeyDrivers[targetId] || this.possibleDriversResponse[targetId]; // Only update the model (cause a re-render) if the selection state has changed. if (this._haveSelectedKeyDriversChanged(keyDriversDisplayStates.recommendedDrivers, possibleDriverState.recommendedDrivers) || this._haveSelectedKeyDriversChanged(keyDriversDisplayStates.nonRecommendedDrivers, possibleDriverState.nonRecommendedDrivers)) { possibleDriverState.recommendedDrivers = keyDriversDisplayStates.recommendedDrivers; possibleDriverState.nonRecommendedDrivers = keyDriversDisplayStates.nonRecommendedDrivers; modelKeyDrivers = {}; modelKeyDrivers[targetId] = possibleDriverState; this.visAPI.setPossibleKeyDrivers(modelKeyDrivers); this.possibleDriversResponse[targetId].recommendedDrivers = keyDriversDisplayStates.recommendedDrivers; this.possibleDriversResponse[targetId].nonRecommendedDrivers = keyDriversDisplayStates.nonRecommendedDrivers; } }, /** * Query the Smarts service for new possible key drivers. This is called from * the dialog when a user decides to 'Reset to default'. * Note, this does not change the current key drivers in the model. * @param {String} id of the mapped column. * @returns {Promise} resolved with possible drivers formatted in a way easily displayed. */ getNewPossibleKeyDriversForTargetId: function getNewPossibleKeyDriversForTargetId(targetId) { var _this2 = this; return this._queryForPossibleKeyDriversForTargetId(targetId).then(function (driverDesc) { var recommendedDrivers = driverDesc.possibleKeyDrivers; var messages = driverDesc.messages; var options = { messages: messages, targetId: targetId, recommended: recommendedDrivers, isSilent: true }; // We have the drivers, now format the response and return it in a displayable state. var formattedDriverDesc = _this2._getFormattedDriversDescription(options); return _this2._getFormattedDisplayStateFromDriverDescription(targetId, formattedDriverDesc); }); }, /*************************************************************************** * Private methods * ***************************************************************************/ /** * @param {Object} formattedDriverDesc - recommended and nonRecommended drivers. * @param {Object} options - targetId, recommendedDrivers array, isSilent for model save, saveInModel for the case of comet viz. */ _saveKeyDriversInModelAndCache: function _saveKeyDriversInModelAndCache(formattedDriverDesc, options) { this._cacheKeyDrivers(options.targetId, formattedDriverDesc); if (options.saveInModel) { var currentModelDrivers = {}; currentModelDrivers[options.targetId] = formattedDriverDesc; this.visAPI.setPossibleKeyDrivers(currentModelDrivers, options.isSilent ? { silent: true } : {}); } }, /** * @returns {boolean} true iff the new selected key driver ids does not match the current state */ _haveSelectedKeyDriversChanged: function _haveSelectedKeyDriversChanged(newKeyDriverSelections) { var _this3 = this; var currentSelections = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; var hasChanged = null; // If lengths don't match then things have changed. if (newKeyDriverSelections.length !== currentSelections.length) { hasChanged = true; } // If both lengths are 0, nothing has changed. if (newKeyDriverSelections.length === 0 && currentSelections.length === 0) { hasChanged = false; } // Lengths are the same but maybe the contents do not match? if (hasChanged === null) { hasChanged = _.find(currentSelections, function (currentItem) { var newSelect = null; newSelect = _.find(newKeyDriverSelections, function (newSelect) { if (_this3.isNewEditDriversEnabled()) { return newSelect.fieldIDForExpression === currentItem.fieldIDForExpression || newSelect.selected === currentItem.selected; } return newSelect.fieldIDForExpression === currentItem.fieldIDForExpression; }); var isEqual = newSelect && _.isEqual(newSelect, currentItem); return !isEqual; }); } return hasChanged ? true : false; }, _getMessagesForInfoIndicator: function _getMessagesForInfoIndicator(msgTypeId, msgTypeLabel, arrayOfMessages) { var items = []; arrayOfMessages.forEach(function (fpdMsg) { items.push({ id: fpdMsg.id, label: fpdMsg.caption }); }); return { id: msgTypeId, label: msgTypeLabel, items: items }; }, _addWarningsAndErrorsToInfoIndicator: function _addWarningsAndErrorsToInfoIndicator(messages) { if (!messages || !messages.fpdWarnings && !messages.fpdErrors) { return; } var warnings = messages.fpdWarnings; var errors = messages.fpdErrors; var visView = this.ownerWidget.getCurrentVis(); var usages = this.visAPI.getDefinition().usages || []; var isAdvancedVisualization = usages.indexOf('Advanced') !== -1; if (visView && visView.infoIndicator && isAdvancedVisualization) { var infosForIndicator = []; infosForIndicator.push(this._getMessagesForInfoIndicator(this.FPD_RESPONSE_ERRORS, stringResources.get('fpd_response_errors'), errors)); infosForIndicator.push(this._getMessagesForInfoIndicator(this.FPD_RESPONSE_WARNINGS, stringResources.get('fpd_response_warnings'), warnings)); visView.infoIndicator.addInfo(infosForIndicator); } }, /** * Query the smarts service for the recommended drivers. * @param {String} targetId - id on the mapped column * @param {Array} of drivers, all in selected state. */ _queryForPossibleKeyDriversForTargetId: function _queryForPossibleKeyDriversForTargetId(targetId) { var _this4 = this; var module = this.visAPI.getModule(); return this.visAPI.whenPossibleKeyDriversQueryIsReady(targetId, module.getAssetId()).then(function (queryResponse) { var possibleKeyDrivers = []; if (queryResponse.result === 'SUCCESS') { // Clear the error state and set the possible key drivers _this4.errorState = {}; possibleKeyDrivers = queryResponse.possibleDrivers; } else { // Error, lets set the error state, log it _this4.errorState = { inErrorState: true, error: queryResponse.error }; _this4.logger.error(queryResponse.error); } if (possibleKeyDrivers) { // Currently smarts only tells us the selected ones possibleKeyDrivers.forEach(function (driver) { driver.selected = true; }); } return { possibleKeyDrivers: possibleKeyDrivers, messages: { fpdWarnings: queryResponse.fpdWarnings, fpdErrors: queryResponse.fpdErrors } }; }); }, /** * @param {Object} options possibly containing recommended and nonrecommended drivers. * @returns {Object} containing recommended and nonrecommended drivers. */ _getFormattedDriversDescription: function _getFormattedDriversDescription(options) { // Default to empty if they are not specified. return { recommendedDrivers: options.recommended || [], nonRecommendedDrivers: options.nonRecommended || [], messages: options.messages || {} }; }, /** * @returns if the specified column is found in the selected Ids array. */ _isColSelected: function _isColSelected(colId, selectedIds) { return selectedIds.indexOf(colId) >= 0; }, /** * Get the predict topbottom filter spec * @return predict topbottom filter spec */ _getPredictFilters: function _getPredictFilters() { var filters = []; // spiral has only one slot with one data item var slots = this.content.getFeature('Visualization').getSlots().getSlotList(); var dataItemList = slots[0].getDataItemList(); // obtain the topcount from the data item var top = dataItemList && dataItemList.length ? dataItemList[0].getTopBottom() : null; if (top && top.type === 'topcount') { filters.push({ topcount: top.value }); } return filters; }, /** * Get the predict sampling data filter spec * @return filter spec for sampling data */ _getDataFilters: function _getDataFilters() { // apply the page context filters var pageContext = this.visAPI.getPageContextAPI(); var contextItems = pageContext.getNetPageContextItems({ scope: this.visAPI.getScope(), sourceId: this.visAPI.getModule().getSourceId(), eventGroupId: this.visAPI.getEventGroupId() }); var filters = CommonQueryBuilder.buildFilterFromPageContext(_.map(contextItems, function (item) { return item.getPageContextSpecItem(); })); // apply local filters var localFiltersSpecs = this._getLocalFiltersSpecs(); _.each(localFiltersSpecs, function (localFilterSpec) { filters.push(localFilterSpec); }); return filters; }, _getLocalFiltersSpecs: function _getLocalFiltersSpecs() { var localFiltersDef = this.visAPI.getLocalFilters(); if (localFiltersDef) { var queryFilterSpec = new QueryFilterSpec(); queryFilterSpec.addFiltersToSpec(localFiltersDef); if (queryFilterSpec.hasFilterSpec()) { return queryFilterSpec.getFilterSpec(); } } return null; }, /** * Find the non recommended possible key drivers (basically, all those in the * data set that are not the target and are not recommended). * @param {String} target id of interest. * @returns {Array} of key drivers in the form of: * @returns {Object} * @returns {fieldIDForExpression} id of the column * @returns {label} of the column */ _getUnrecommendedColumns: function _getUnrecommendedColumns(itemId) { var _this5 = this; var module = this.visAPI.getModule(); var inputs = []; // Go through all the fact or attribute columns for this module. module.getMetadataColumns(function (col) { if (_this5._isMetadataColumnNonRecommended(itemId, col)) { inputs.push({ fieldIDForExpression: col.getId(), label: col.getLabel() }); } }); return inputs; }, /** * @param {String} targetId of the mapped column * @param {Object} col metadata column * @returns {Boolean} true iff the column is to be considered in the non recommended list. */ _isMetadataColumnNonRecommended: function _isMetadataColumnNonRecommended(targetId, col) { var isNonRecommended = false; var columnType = col.getType(); if ((columnType === 'fact' || columnType === 'attribute') && !col.isHidden()) { var columnId = col.getId(); // Check if its part of the recommended list. var partOfRecommended = _.find(this.possibleDriversResponse[targetId].recommendedDrivers, function (recommendedKeyDriver) { return recommendedKeyDriver.fieldIDForExpression === columnId; }); if (!partOfRecommended && columnId !== targetId) { isNonRecommended = true; } } return isNonRecommended; }, /** * The recommended drivers returned by the smarts service will not have the * label needed to display the drivers. We need to look it up. */ _addColumnLabelsToTheDriverDesc: function _addColumnLabelsToTheDriverDesc(formattedDriverDesc) { var _this6 = this; var formattedDriverDescClone = formattedDriverDesc ? {} : null; if (formattedDriverDesc && formattedDriverDesc.recommendedDrivers) { formattedDriverDescClone = JSON.parse(JSON.stringify(formattedDriverDesc)); formattedDriverDescClone.recommendedDrivers.forEach(function (driver) { var dataSource = _this6.content.getFeature('Visualization').getDataSource(); var column = dataSource.getMetadataColumn(driver.fieldIDForExpression); if (column) { driver.label = column.getLabel(); } }); } return formattedDriverDescClone; }, /** * The recommended drivers coming from the ui dialog include a label property which * was added via _addColumnLabelsToTheDriverDesc before. Remove this label property. */ _removeColumnLabelsFromDisplayStates: function _removeColumnLabelsFromDisplayStates(keyDriversDisplayStates) { if (keyDriversDisplayStates.recommendedDrivers) { // This data comes from the state, remove label property before passing it along keyDriversDisplayStates.recommendedDrivers.forEach(function (driver) { driver.label && delete driver.label; }); } }, /** * @returns {Object} inErrorState - true if we are in error state, {String} caption - error message. */ getPossibleKeyDriversErrorState: function getPossibleKeyDriversErrorState() { var inErrorState = this.errorState && this.errorState.inErrorState !== undefined ? this.errorState.inErrorState : false; var caption = this.errorState && this.errorState.error ? this.errorState.error.caption : ''; var state = { inErrorState: inErrorState, caption: caption }; return state; }, _getMaxPossibleDrivers: function _getMaxPossibleDrivers(recommendedDrivers, searchAbleColumns) { var totalPossibleDrivers = recommendedDrivers.length + searchAbleColumns.length; return totalPossibleDrivers > MAX_POSSIBLE_KEY_DRIVERS ? MAX_POSSIBLE_KEY_DRIVERS : totalPossibleDrivers; }, /** * @param {String} targetId of the column of interest. * @param {Object} formattedDriverDesc description of the current drivers. This * includes: * recommendedDrivers * nonRecommendedDrivers * @returns the information needed to display the possible drivers. */ _getFormattedDisplayStateFromDriverDescription: function _getFormattedDisplayStateFromDriverDescription(targetId, formattedDriverDesc) { var formattedDriverDescWithLabel = this._addColumnLabelsToTheDriverDesc(formattedDriverDesc); /* Get the format needed for display, this includes the recommended drivers list, the list of searchable columns to add as non-recommended, and the current user selected non-recommended drivers. */ var recommendedDrivers = formattedDriverDescWithLabel.recommendedDrivers || []; var nonRecommendedDrivers = formattedDriverDescWithLabel.nonRecommendedDrivers || []; var selectedDrivers = [].concat(recommendedDrivers, nonRecommendedDrivers).filter(function (item) { return item.selected === true; }); var searchAbleColumns = this._getUnrecommendedColumns(targetId); var result = { recommendedDrivers: recommendedDrivers, searchableColumns: searchAbleColumns, nonRecommendedDrivers: nonRecommendedDrivers, maxPossibleDrivers: this._getMaxPossibleDrivers(recommendedDrivers, searchAbleColumns), selectedDrivers: selectedDrivers, formattedDriverDesc: formattedDriverDesc }; return result; }, /** * @param {Object} dataItem of the column of interest. * @returns {Object} with possible drivers. */ getKeyDrivers: function getKeyDrivers(dataItem) { var module = this.visAPI.getModule(); // TODO: Remove dataItem.getItemId() when new slots panel feature flag is turned on var itemId = dataItem.getItemId && dataItem.getItemId() || dataItem.getColumnId(); var displayState = this.getPossibleKeyDriversDisplayState(itemId); var recommendedDrivers = displayState.recommendedDrivers; var nonRecommendedDrivers = displayState.nonRecommendedDrivers; var selectedDrivers = [].concat(recommendedDrivers, nonRecommendedDrivers).filter(function (item) { return item.selected === true; }); var recommendedFieldIds = recommendedDrivers.map(function (item) { return item.fieldIDForExpression; }); var nonRecommendedFieldIds = nonRecommendedDrivers.map(function (item) { return item.fieldIDForExpression; }); var selectedFieldIds = selectedDrivers.map(function (item) { return item.fieldIDForExpression; }); var recommendedItems = module.getMetadataColumns(function (col) { return recommendedFieldIds.indexOf(col.getId()) !== -1; }).map(function (col) { return col.moserObject; }); var nonRecommendedItems = module.getMetadataColumns(function (col) { return nonRecommendedFieldIds.indexOf(col.getId()) !== -1; }).map(function (col) { return col.moserObject; }); var selectedItems = module.getMetadataColumns(function (col) { return selectedFieldIds.indexOf(col.getId()) !== -1; }).map(function (col) { return col.moserObject; }); var initialItems = !nonRecommendedItems.length ? recommendedItems : selectedItems; var initialFieldIds = !selectedFieldIds.length ? recommendedFieldIds : selectedFieldIds; return { initialFieldIds: initialFieldIds, initialItems: initialItems, recommendedDrivers: recommendedDrivers, nonRecommendedDrivers: nonRecommendedDrivers, recommendedFieldIds: recommendedFieldIds, nonRecommendedFieldIds: nonRecommendedFieldIds, recommendedItems: recommendedItems, nonRecommendedItems: nonRecommendedItems }; } }); return VisPossibleKeyDriversManager; }); //# sourceMappingURL=VisPossibleKeyDriversManager.js.map