'use strict'; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** * Licensed Materials - Property of IBM * IBM Cognos Products: BI * (C) Copyright IBM Corp. 2018, 2020 * 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/Events', '../../../app/nls/StringResources', 'underscore', '../../../lib/@waca/core-client/js/core-client/i18n/Formatter', 'moment-timezone'], function (Events, StringResources, _, Formatter, moment) { var DashboardPinningService = function (_Events) { _inherits(DashboardPinningService, _Events); function DashboardPinningService() { _classCallCheck(this, DashboardPinningService); var _this = _possibleConstructorReturn(this, _Events.call(this, arguments)); _this.PIN_VERSION = '3'; _this.PIN_URL = 'v1/users/~/pins'; _this._pinIdsToUpgradeInCM = []; _this._timeframePinBuckets = null; _this._timeframePinBucketNames = ['all', 'today', 'yesterday', 'pastWeek', 'pastMonth', 'earlier']; _this._pinIdMap = {}; return _this; } DashboardPinningService.prototype.initialize = function initialize(_ref) { var _this2 = this; var appController = _ref.appController; this._glassContext = appController.glassContext; return this._glassContext.getSvc('.UpgradeService').then(function (upgrades) { _this2._latestDashboardSpecVersion = upgrades.getLatestDashboardSpecVersion(); // Do some work ahead of time to figure out our time buckets var userProfileService = _this2._glassContext.getCoreSvc('.UserProfile'); _this2._timezone = userProfileService.preferences.timeZoneID || 'America/New_York'; _this2._today = moment().tz(_this2._timezone).startOf('day'); _this2._yesterday = _this2._today.clone().subtract(1, 'days'); _this2._pastWeek = _this2._today.clone().subtract(7, 'days'); _this2._pastMonth = _this2._today.clone().subtract(31, 'days'); }); }; /** * Used by search UI to get the cached pins. This UI only renders after we've queried for the pins, * so we return without a promise here which is what the search logic needs. * @param {String} timeframe */ DashboardPinningService.prototype.getCachedPins = function getCachedPins() { var timeframe = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'all'; return this._timeframePinBuckets[timeframe] || []; }; /** * Get the list of pins from the server * @param {String} timeframe - Must be one of the following 'all', 'today', 'yesterday', 'pastWeek', 'pastMonth', 'earlier' */ DashboardPinningService.prototype.getPins = function getPins() { var _this3 = this; var timeframe = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'all'; if (this._timeframePinBuckets) { return Promise.resolve(this._timeframePinBuckets[timeframe]); } return this._glassContext.getCoreSvc('.Ajax').ajax({ url: this.PIN_URL, type: 'GET', contentType: 'application/json; charset=utf-8', dataType: 'json' }).then(function () { var response = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; _this3._timeframePinBuckets = { all: [], today: [], yesterday: [], pastWeek: [], pastMonth: [], earlier: [] }; // Upgrade the pins locally (on the client) before returning them. We'll update the pins on the server lazilly return _this3._upgradePinsLocally(response.data); }).then(function (upgradedPins) { var pins = _this3._sortPins(upgradedPins); _this3._prepPinsForUI(pins); _this3._cachePinsInAppropriateTimeFrameBuckets(pins); // No need to wait for this to finish before the UI can render _this3._slowlySaveUpgradedPinsInCM(); return _this3._timeframePinBuckets[timeframe]; }).catch(function (result) { _this3._glassContext.getCoreSvc('.Logger').error('Failed to get pins', result); var message = _this3._retrieveErrorMessage({ result: result, defaultMessage: StringResources.get('pinRetrieveError') }); _this3._glassContext.appController.showErrorMessage(message, StringResources.get('pinErrorTitle')); return []; }); }; /** * Get the pin for the given ID * @param {String} pinId */ DashboardPinningService.prototype.getPin = function getPin(pinId) { if (this._pinIdMap[pinId]) { return Promise.resolve(this._pinIdMap[pinId]); } // We should never ask for a specific pin before querying all of them.. safe guard in case we do for some odd reason. return this.getPins().then(function (pins) { return _.find(pins, function (pin) { return pin.id === pinId; }); }); }; /** * Saves a pin in CM * @param {Object} pin Pin object to save */ DashboardPinningService.prototype.addPin = function addPin(pin) { var _this4 = this; pin.version = this.PIN_VERSION; var pinJSON = JSON.stringify(pin); return this._glassContext.getCoreSvc('.Ajax').ajax({ type: 'POST', url: this.PIN_URL, contentType: 'application/json; charset=utf-8', dataType: 'json', data: pinJSON }).then(function () { var result = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var newPin = result.data; _this4._prepPinsForUI([newPin]); _this4._addPinToCache(newPin, ['all', 'today', 'pastWeek', 'pastMonth'], true); _this4.trigger('pin:created', newPin); return newPin; }).catch(function (result) { var message = _this4._retrieveErrorMessage({ result: result, defaultMessage: StringResources.get('contentPinFailOld') }); var error = new Error(message); _this4._glassContext.getCoreSvc('.Logger').error('addPin error', error); throw error; }); }; /** * Removes the pin locally and shows a toast. Once the toast goes away (can no longer undo) * we complete the delete by deleting it from CM * @param {String} pinId */ DashboardPinningService.prototype.deletePin = function deletePin(pinId) { var _this5 = this; return new Promise(function (resolve, reject) { try { if (_this5._glassContext.getCoreSvc('.UserProfile').preferences.accessibilityFeatures) { _this5._glassContext.appController.showMessage(StringResources.get('deletePinConfirmationMessage'), StringResources.get('deletePinConfirm'), 'info', ['ok', 'cancel'], undefined, function (evt) { if (evt.btn === 'ok') { _this5._removePinFromCache(pinId); _this5.trigger('pin:fakeDeleted', pinId); _this5._completePinDelete(pinId); } resolve(); }); } else { var removedInfo = _this5._removePinFromCache(pinId); if (!removedInfo.removedPin) { _this5._glassContext.getCoreSvc('.Logger').error('Trying to remove a pin that is not in our array', pinId, _this5); } _this5.trigger('pin:fakeDeleted', pinId); var undidDelete = false; _this5._glassContext.appController.showToast(StringResources.get('pinDeletedToast'), { type: 'info', btnLabel: StringResources.get('undo'), callback: function callback() { undidDelete = true; _this5._addPinToCache(removedInfo.removedPin, removedInfo.timeframeBucketNames, false, true); _this5.trigger('pin:undoDeletion', pinId); }, newestOnTop: true, preventDuplicates: false, timeOut: 6000, extendedTimeOut: 1000, onHidden: function onHidden() { if (!undidDelete) { _this5._completePinDelete(pinId); } resolve(); } }); } } catch (error) { reject(error); } }); }; /** * The toast message is gone so it's safe to delete the pin from CM */ DashboardPinningService.prototype._completePinDelete = function _completePinDelete(pinId) { var _this6 = this; // If the pin we've just deleted is still in the queue to update in CM then remove it from the queue this._pinIdsToUpgradeInCM = this._pinIdsToUpgradeInCM.filter(function (id) { return id !== pinId; }); return this._glassContext.getCoreSvc('.Ajax').ajax({ type: 'DELETE', url: this.PIN_URL + '/' + pinId, contentType: 'application/json; charset=utf-8', dataType: 'json' }).then(function () { var result = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; _this6.trigger('pin:deleted', pinId); return result; }).catch(function (result) { var message = _this6._retrieveErrorMessage({ result: result, defaultMessage: StringResources.get('pinDeleteErrorOld') }); var error = new Error(message); _this6._glassContext.appController.showErrorMessage(message, StringResources.get('pinErrorTitle')); _this6._glassContext.getCoreSvc('.Logger').error('deletePin error', error); throw error; }); }; /** * Go through the upgraded pins one by one and push the upgraded spec to CM. Only * send one PUT request at a time to not hurt performance for the user. */ DashboardPinningService.prototype._slowlySaveUpgradedPinsInCM = function _slowlySaveUpgradedPinsInCM() { var _this7 = this; var pinId = this._pinIdsToUpgradeInCM.shift(); if (pinId) { return this.getPin(pinId).then(function (pin) { _this7._glassContext.getCoreSvc('.Logger').info('Updating upgraded pin spec in CM', pin); return _this7._glassContext.getCoreSvc('.Ajax').ajax({ type: 'PUT', url: _this7.PIN_URL + '/' + pinId, contentType: 'application/json; charset=utf-8', dataType: 'json', data: JSON.stringify(pin) }).then(function () { return _this7._slowlySaveUpgradedPinsInCM(); }).catch(function (error) { // Stop trying to save the upgrades on the first error. Possible timeout or logged off. _this7._glassContext.getCoreSvc('.Logger').error('Pin upgrade failed to save in CM.', error); }); }); } return Promise.resolve(); }; /** * Removes a pin from our caches * @param {String} pinId */ DashboardPinningService.prototype._removePinFromCache = function _removePinFromCache(pinId) { var _this8 = this; if (this._pinIdMap) { delete this._pinIdMap[pinId]; } // Need to keep track of the timeframe buckets this pin was in case the user undoes the delete var timeframeBucketNames = []; var removedPin = null; if (this._timeframePinBuckets) { var filterOutPin = function filterOutPin(pin, timeframeBucketName) { if (pin.id === pinId) { removedPin = pin; timeframeBucketNames.push(timeframeBucketName); return false; } return true; }; this._timeframePinBucketNames.forEach(function (bucketName) { _this8._timeframePinBuckets[bucketName] = _this8._timeframePinBuckets[bucketName].filter(function (pin) { return filterOutPin(pin, bucketName); }); }); } return { removedPin: removedPin, timeframeBucketNames: timeframeBucketNames }; }; /** * Adds a pin to our caches * @param {Object} pin - the pin to add * @param {[String]} buckets - the list of buckets to add the pin to * @param {Boolean} addToBeginning - Should the pin get added to the beginning of the list * @param {Boolean} sortPins - should the timeframe bucket be sorted after the pin gets added. */ DashboardPinningService.prototype._addPinToCache = function _addPinToCache(pin) { var buckets = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this._timeframePinBucketNames; var _this9 = this; var addToBeginning = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; var sortPins = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; // If we haven't queried for all the pins yet then no need to add the pin. We'll grab them // all when the pin panel is open. if (!this._timeframePinBuckets) { return; } this._pinIdMap[pin.id] = pin; buckets.forEach(function (bucket) { if (addToBeginning) { _this9._timeframePinBuckets[bucket].unshift(pin); } else { _this9._timeframePinBuckets[bucket].push(pin); } // Currently only true when we undo a delete. We need to resort the buckets once the pin is placed back if (sortPins) { _this9._sortPins(_this9._timeframePinBuckets[bucket]); } }); }; DashboardPinningService.prototype._retrieveErrorMessage = function _retrieveErrorMessage(input) { input = input || {}; var message = input.defaultMessage; try { var text = JSON.parse(input.result.responseText); message = text.error; } catch (e) { // ignore JSON parsing errors } return message; }; /** * Convert properties from the pin object to the required values for the UI */ DashboardPinningService.prototype._prepPinsForUI = function _prepPinsForUI(pins) { var _this10 = this; var dashboardApi = this._getCurrentContentViewDashboardApi(); var smartNamingService = dashboardApi.getDashboardCoreSvc('.SmartNamingSvc'); pins.forEach(function (pin) { // process pin date if (pin.timestamp) { pin.pinAge = Formatter.formatDateTime(pin.timestamp, { type: 'datetime', formatLength: 'short', timezone: _this10._timezone }); } // smart name pin.displayName = smartNamingService.getPinName(pin); // check for pin image if (!pin.thumbUri) { pin.defaultImage = 'wfg_pin'; } }); }; /** * Caches the pins into their timeframe buckets */ DashboardPinningService.prototype._cachePinsInAppropriateTimeFrameBuckets = function _cachePinsInAppropriateTimeFrameBuckets(pins) { var _this11 = this; pins.forEach(function (pin) { var timestamp = moment.tz(pin.timestamp, _this11._timezone); _this11._timeframePinBuckets.all.push(pin); var timeframeBucketNames = null; if (timestamp.isSame(_this11._today, 'day')) { timeframeBucketNames = ['today', 'pastWeek', 'pastMonth']; } else if (timestamp.isSame(_this11._yesterday, 'day')) { timeframeBucketNames = ['yesterday', 'pastWeek', 'pastMonth']; } else if (timestamp.isAfter(_this11._pastWeek)) { timeframeBucketNames = ['pastWeek', 'pastMonth']; } else if (timestamp.isAfter(_this11._pastMonth)) { timeframeBucketNames = ['pastMonth']; } else { timeframeBucketNames = ['earlier']; } _this11._addPinToCache(pin, timeframeBucketNames); }); }; /** * Sorts the given array of pins by modified date, with the newest first in the list. */ DashboardPinningService.prototype._sortPins = function _sortPins(pins) { return pins.sort(function (a, b) { var aTime = a.timestamp; var bTime = b.timestamp; return aTime > bTime ? -1 : aTime < bTime ? 1 : 0; }); }; /** * Upgrade the pin specs but do not push the upgraded specs to CM yet. We'll do that slowly once we render the Pins panel * @param {*} pins */ DashboardPinningService.prototype._upgradePinsLocally = function _upgradePinsLocally() { var _this12 = this; var pins = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; //const upgradePromises = []; var upgradeOptions = { ajaxSvc: this._glassContext.getCoreSvc('.Ajax'), services: this._getCurrentContentViewServices(), logger: this._glassContext.getCoreSvc('.Logger'), pinUpgrade: true, glassContext: this._glassContext, dashboardApi: this._getCurrentContentViewDashboardApi(), showErrorToast: false }; var pinsToUpgrade = []; return this._glassContext.getSvc('.UpgradeService').then(function (upgradeService) { pins.forEach(function (pin) { if (pin.content && pin.content.specVersion !== _this12._latestDashboardSpecVersion) { pinsToUpgrade.push(pin); } }); if (pinsToUpgrade.length > 0) { console.log(pinsToUpgrade.length + ' pins to upgrade.'); } /** * Our upgrade service uses static upgrade classes and some of those keep state during the upgrade process. * For this reason we have to run the upgrades in sequence instead of in parallel. Use the reduce method * to programatically chain promises */ return new Promise(function (resolve) { var upgradePromise = pinsToUpgrade.reduce(function (currentPromise, pin) { return currentPromise.then(function () { try { return _this12._upgradePinSpec(pin, upgradeService, upgradeOptions).then(function () { _this12._pinIdsToUpgradeInCM.push(pin.id); }).catch(function (error) { // if one pin fails, omit it from the list and move on _this12._glassContext.getCoreSvc('.Logger').error('Unable to upgrade pin:', pin, 'Failed with error: ', error); }); } catch (error) { // if one pin fails, omit it from the list and move on _this12._glassContext.getCoreSvc('.Logger').error('Unable to upgrade pin:', pin, 'Failed with error: ', error); } }); }, Promise.resolve([])); upgradePromise.then(function () { resolve(pins); }); }); }); }; DashboardPinningService.prototype._upgradePinSpec = function _upgradePinSpec(pinSpec, upgradeService, upgradeOptions) { var _this13 = this; // assuming the board spec version is 6 if there is no version attribute var boardSpec = { version: pinSpec.version ? pinSpec.content.specVersion : 6, layout: pinSpec.content.layout, widgets: this._getObjectFromArrayById(pinSpec.content.widgets) }; // Old pin specs used datasetShapings while every other part of the code uses datasetShaping if (pinSpec.content.datasetShapings) { boardSpec.datasetShaping = this._getObjectFromArrayById(pinSpec.content.datasetShapings); } else if (pinSpec.content.datasetShaping) { boardSpec.datasetShaping = this._getObjectFromArrayById(pinSpec.content.datasetShaping); } if (pinSpec.content.dataSources) { boardSpec.dataSources = pinSpec.content.dataSources; } if (pinSpec.content.episodes) { boardSpec.timeline = { episodes: this._getObjectFromArrayById(pinSpec.content.episodes) }; } // save the original version var upgrades = pinSpec.content.upgrades || []; var original = boardSpec.version; if (upgrades.indexOf(original) === -1) { upgrades.push(original); pinSpec.content.upgrades = upgrades; } else { this._glassContext.getCoreSvc('.Logger').warn('Pin spec with id "' + pinSpec.id + '" and board spec version "' + pinSpec.content.specVersion + '" being upgraded to an previously upgraded spec version: ', original); } return upgradeService.upgrade(boardSpec, null /*means latest*/, upgradeOptions).then(function (upgradedBoardSpec) { // TODO the logic to create a pin spec from a board spec should be put in one place // currently it is here and in PinAction. See Work Item 187395 var upgradedPinContent = { layout: upgradedBoardSpec.layout, widgets: _.toArray(upgradedBoardSpec.widgets), specVersion: upgradedBoardSpec.version, upgrades: upgrades }; if (upgradedBoardSpec.dataSources) { upgradedPinContent.dataSources = upgradedBoardSpec.dataSources; } if (pinSpec.content.episodes) { upgradedPinContent.episodes = _.toArray(upgradedBoardSpec.timeline.episodes); } // if for some reason the spec did not upgrade fully it may still // contain datasetShaping if (upgradedBoardSpec.datasetShaping) { upgradedPinContent.datasetShaping = _.toArray(upgradedBoardSpec.datasetShaping); } pinSpec.content = upgradedPinContent; // we should be the newest pin version now pinSpec.version = _this13.PIN_VERSION; return pinSpec; }).catch(function (error) { // IF we have an error.obj then the upgrade had some succefull steps, return that spec if (error.obj) { _this13._glassContext.getCoreSvc('.Logger').warn('Pin spec did not fully upgrade', error, pinSpec); return error.obj; } throw error; }); }; DashboardPinningService.prototype._getObjectFromArrayById = function _getObjectFromArrayById(array) { return _.object(_.map(array, function (item) { return [item.id, item]; })); }; /** * Yuck yuck yuck.. look away. This is really only needed since the pin service (glass service) needs access to * some dashboard specific services when querying/adding pins. In the long run a lot of these services should * be moved to glass services since they can be shared accross dashboards. */ DashboardPinningService.prototype._getCurrentContentViewDashboardApi = function _getCurrentContentViewDashboardApi() { try { return this._glassContext.appController.currentAppView.currentContentView.getDashboardApi(); } catch (e) { this._glassContext.getCoreSvc('.Logger').error('Trying to use the pinning service outside of a dashboard.', e); } }; /** * Yuck yuck yuck.. look away. This is really only needed since the pin service (glass service) needs access to * some dashboard specific services when querying/adding pins. In the long run a lot of these services should * be moved to glass services since they can be shared accross dashboards. */ DashboardPinningService.prototype._getCurrentContentViewServices = function _getCurrentContentViewServices() { try { return this._glassContext.appController.currentAppView.currentContentView.services; } catch (e) { this._glassContext.getCoreSvc('.Logger').error('Trying to use the pinning service outside of a dashboard.', e); } }; return DashboardPinningService; }(Events); return DashboardPinningService; }); //# sourceMappingURL=DashboardPinningService.js.map