@atlaskit/mention
Version:
A React component used to display user profiles in a list for 'Mention' functionality
207 lines (204 loc) • 8.53 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.DefaultMentionNameResolver = void 0;
exports.mergeNameResolverQueues = mergeNameResolverQueues;
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _types = require("../types");
var _analytics = require("../util/analytics");
/** A queue for user ids */
var DefaultMentionNameResolver = exports.DefaultMentionNameResolver = /*#__PURE__*/function () {
function DefaultMentionNameResolver(client) {
var _this = this;
var analyticsProps = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var onResolvedAll = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function () {};
(0, _classCallCheck2.default)(this, DefaultMentionNameResolver);
(0, _defineProperty2.default)(this, "nameCache", new Map());
(0, _defineProperty2.default)(this, "nameQueue", new Map());
(0, _defineProperty2.default)(this, "nameStartTime", new Map());
(0, _defineProperty2.default)(this, "processingQueue", new Map());
(0, _defineProperty2.default)(this, "debounce", 0);
(0, _defineProperty2.default)(this, "debounceOnResolve", null);
(0, _defineProperty2.default)(this, "isOnResolvedAllCalled", false);
(0, _defineProperty2.default)(this, "processQueue", function () {
clearTimeout(_this.debounce);
_this.debounce = 0;
var _this$splitQueueAtLim = _this.splitQueueAtLimit(),
queue = _this$splitQueueAtLim.queue,
extraQueue = _this$splitQueueAtLim.extraQueue;
_this.nameQueue = extraQueue;
_this.processingQueue = mergeNameResolverQueues(_this.processingQueue, queue);
_this.client.lookupMentionNames(Array.from(queue.keys())).then(function (response) {
response.forEach(function (mentionDetail) {
var id = mentionDetail.id;
queue.delete(id);
_this.resolveQueueItem(mentionDetail);
});
queue.forEach(function (_callback, id) {
// No response from client for these ids treat as unknown
_this.resolveQueueItem({
id: id,
status: _types.MentionNameStatus.UNKNOWN
});
});
}).catch(function () {
// Service completely failed, reject all items in the queue
queue.forEach(function (_callback, id) {
_this.resolveQueueItem({
id: id,
status: _types.MentionNameStatus.SERVICE_ERROR
});
});
});
// Make sure anything left in the queue gets processed.
if (_this.nameQueue.size > 0) {
_this.scheduleProcessQueue();
} else {
_this.scheduleOnAllResolved();
}
});
this.client = client;
this.fireHydrationEvent = (0, _analytics.fireAnalyticsMentionHydrationEvent)(analyticsProps);
// If provided, this will be called once all pending mentions in the queue are resolved.
// A sample usage is scrolling to a mention on page load, after the mentions have loadad.
this.onResolvedAll = onResolvedAll;
}
return (0, _createClass2.default)(DefaultMentionNameResolver, [{
key: "lookupName",
value: function lookupName(id) {
var _this2 = this;
var name = this.nameCache.get(id);
if (name) {
this.fireAnalytics(true, name);
if (this.nameQueue.size === 0) {
this.scheduleOnAllResolved();
}
return name;
}
return new Promise(function (resolve) {
var processingItems = _this2.processingQueue.get(id);
if (processingItems) {
_this2.processingQueue.set(id, [].concat((0, _toConsumableArray2.default)(processingItems), [resolve]));
}
var queuedItems = _this2.nameQueue.get(id) || [];
_this2.nameQueue.set(id, [].concat((0, _toConsumableArray2.default)(queuedItems), [resolve]));
if (queuedItems.length === 0 && !processingItems) {
_this2.nameStartTime.set(id, Date.now());
}
_this2.scheduleProcessQueue();
if (_this2.isQueueAtLimit()) {
_this2.processQueue();
}
});
}
}, {
key: "cacheName",
value: function cacheName(id, name) {
this.nameCache.set(id, {
id: id,
name: name,
status: _types.MentionNameStatus.OK
});
}
}, {
key: "scheduleProcessQueue",
value: function scheduleProcessQueue() {
if (!this.debounce) {
this.debounce = window.setTimeout(this.processQueue, DefaultMentionNameResolver.waitForBatch);
}
}
}, {
key: "scheduleOnAllResolved",
value: function scheduleOnAllResolved() {
var _this3 = this;
if (this.debounceOnResolve) {
clearTimeout(this.debounceOnResolve);
}
this.debounceOnResolve = window.setTimeout(function () {
if (_this3.isOnResolvedAllCalled) {
return;
}
_this3.onResolvedAll();
_this3.isOnResolvedAllCalled = true;
}, DefaultMentionNameResolver.waitForResolveAll);
}
}, {
key: "isQueueAtLimit",
value: function isQueueAtLimit() {
return this.nameQueue.size >= this.client.getLookupLimit();
}
}, {
key: "splitQueueAtLimit",
value: function splitQueueAtLimit() {
var values = Array.from(this.nameQueue.entries());
var splitPoint = this.client.getLookupLimit();
return {
queue: new Map(values.slice(0, splitPoint)),
extraQueue: new Map(values.slice(splitPoint))
};
}
}, {
key: "resolveQueueItem",
value: function resolveQueueItem(mentionDetail) {
var id = mentionDetail.id;
var resolvers = this.processingQueue.get(id);
if (resolvers) {
this.processingQueue.delete(id);
this.nameCache.set(id, mentionDetail);
resolvers.forEach(function (resolve) {
try {
resolve(mentionDetail);
} catch (_unused) {
// ignore - exception in consumer
}
});
this.fireAnalytics(false, mentionDetail);
}
}
}, {
key: "fireAnalytics",
value: function fireAnalytics(fromCache, mentionDetail) {
var id = mentionDetail.id;
var action = mentionDetail.status === _types.MentionNameStatus.OK ? 'completed' : 'failed';
var start = this.nameStartTime.get(id);
var duration = start ? Date.now() - start : 0;
this.nameStartTime.delete(id);
this.fireHydrationEvent(action, id, fromCache, duration);
}
}]);
}();
/**
* Merge the two queues making sure to merge callback arrays for items in queueB already in queueA.
* This addresses [this ticket](https://product-fabric.atlassian.net/browse/QS-3789).
*/
(0, _defineProperty2.default)(DefaultMentionNameResolver, "waitForBatch", 100);
// ms
(0, _defineProperty2.default)(DefaultMentionNameResolver, "waitForResolveAll", 800);
function mergeNameResolverQueues(queueA, queueB) {
var queueBeingMerged = new Map((0, _toConsumableArray2.default)(queueA));
// now add the items from the second queue that are not already in the
// merged queue being built
(0, _toConsumableArray2.default)(queueB).forEach(function (item) {
var _item = (0, _slicedToArray2.default)(item, 2),
key = _item[0],
queueBCallbacks = _item[1];
var itemAlreadyInMergedQueue = queueBeingMerged.has(key);
if (!itemAlreadyInMergedQueue) {
queueBeingMerged.set(key, queueBCallbacks);
} else {
var _queueBeingMerged$get;
// item already in merged queue, merge the callback arrays
var queueACallbacks = (_queueBeingMerged$get = queueBeingMerged.get(key)) !== null && _queueBeingMerged$get !== void 0 ? _queueBeingMerged$get : [];
var mergedCallbacks = new Set([].concat((0, _toConsumableArray2.default)(queueBCallbacks), (0, _toConsumableArray2.default)(queueACallbacks)));
var deduplicatedCallbacks = Array.from(mergedCallbacks.values()); // prevents calling them twice
queueBeingMerged.set(key, deduplicatedCallbacks);
}
});
return queueBeingMerged;
}