UNPKG

@atlaskit/global-search

Version:

A cross-product search component (batteries included)

537 lines (450 loc) 19.7 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = exports.FilterType = exports.EMPTY_CROSS_PRODUCT_SEARCH_RESPONSE = exports.DEFAULT_AB_TEST = void 0; var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator")); var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator")); 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 _Result = require("../model/Result"); var _JiraItemMapper = require("./JiraItemMapper"); var _ConfluenceItemMapper = require("./ConfluenceItemMapper"); var _utilServiceSupport = require("@atlaskit/util-service-support"); var _types = require("./types"); function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } var DEFAULT_AB_TEST = Object.freeze({ experimentId: 'default', abTestId: 'default', controlId: 'default' }); exports.DEFAULT_AB_TEST = DEFAULT_AB_TEST; var QUICKSEARCH_API_URL = 'quicksearch/v1'; var EMPTY_CROSS_PRODUCT_SEARCH_RESPONSE = { results: {} }; exports.EMPTY_CROSS_PRODUCT_SEARCH_RESPONSE = EMPTY_CROSS_PRODUCT_SEARCH_RESPONSE; var FilterType; exports.FilterType = FilterType; (function (FilterType) { FilterType["Spaces"] = "spaces"; FilterType["Contributors"] = "contributors"; })(FilterType || (exports.FilterType = FilterType = {})); var CachingCrossProductSearchClientImpl = /*#__PURE__*/function () { // result limit per scope function CachingCrossProductSearchClientImpl(url, cloudId, isUserAnonymous, prefetchResults) { (0, _classCallCheck2.default)(this, CachingCrossProductSearchClientImpl); (0, _defineProperty2.default)(this, "RESULT_LIMIT", 10); this.serviceConfig = { url: url }; this.cloudId = cloudId; this.isUserAnonymous = isUserAnonymous; this.abTestDataCache = prefetchResults ? prefetchResults.abTestPromise : {}; this.crossProductRecentsCache = prefetchResults ? prefetchResults.crossProductRecentItemsPromise : undefined; } (0, _createClass2.default)(CachingCrossProductSearchClientImpl, [{ key: "getNavAutocompleteSuggestions", value: function () { var _getNavAutocompleteSuggestions = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(query) { var path, results, matchingScope, matchingDocuments; return _regenerator.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: path = 'quicksearch/v1'; _context.next = 3; return this.makeRequest(path, { cloudId: this.cloudId, scopes: [_types.Scope.NavSearchCompleteConfluence], query: query }); case 3: results = _context.sent; matchingScope = results.scopes.find(function (scope) { return scope.id === _types.Scope.NavSearchCompleteConfluence; }); matchingDocuments = matchingScope ? matchingScope.results : []; return _context.abrupt("return", matchingDocuments.map(mapItemToNavCompletionString)); case 7: case "end": return _context.stop(); } } }, _callee, this); })); function getNavAutocompleteSuggestions(_x) { return _getNavAutocompleteSuggestions.apply(this, arguments); } return getNavAutocompleteSuggestions; }() }, { key: "getPeople", value: function () { var _getPeople = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee2(_ref) { var query, sessionId, referrerId, currentQuickSearchContext, _ref$resultLimit, resultLimit, isBootstrapQuery, scope, searchPromise; return _regenerator.default.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: query = _ref.query, sessionId = _ref.sessionId, referrerId = _ref.referrerId, currentQuickSearchContext = _ref.currentQuickSearchContext, _ref$resultLimit = _ref.resultLimit, resultLimit = _ref$resultLimit === void 0 ? 3 : _ref$resultLimit; isBootstrapQuery = !query; // We will use the bootstrap people cache if the query is a bootstrap query and there is a result cached if (!(isBootstrapQuery && this.bootstrapPeopleCache)) { _context2.next = 4; break; } return _context2.abrupt("return", this.bootstrapPeopleCache); case 4: scope = currentQuickSearchContext === 'confluence' ? _types.Scope.UserConfluence : currentQuickSearchContext === 'jira' ? _types.Scope.UserJira : null; if (!scope) { _context2.next = 9; break; } searchPromise = this.search({ query: query, sessionId: sessionId, referrerId: referrerId, scopes: [scope], modelParams: [], resultLimit: resultLimit }); if (isBootstrapQuery) { this.bootstrapPeopleCache = searchPromise; } return _context2.abrupt("return", searchPromise); case 9: return _context2.abrupt("return", { results: {} }); case 10: case "end": return _context2.stop(); } } }, _callee2, this); })); function getPeople(_x2) { return _getPeople.apply(this, arguments); } return getPeople; }() }, { key: "search", value: function () { var _search = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee3(_ref2) { var query, sessionId, referrerId, scopes, modelParams, _ref2$resultLimit, resultLimit, _ref2$filters, filters, _ref2$mapItemToResult, mapItemToResult, body, response; return _regenerator.default.wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: query = _ref2.query, sessionId = _ref2.sessionId, referrerId = _ref2.referrerId, scopes = _ref2.scopes, modelParams = _ref2.modelParams, _ref2$resultLimit = _ref2.resultLimit, resultLimit = _ref2$resultLimit === void 0 ? this.RESULT_LIMIT : _ref2$resultLimit, _ref2$filters = _ref2.filters, filters = _ref2$filters === void 0 ? [] : _ref2$filters, _ref2$mapItemToResult = _ref2.mapItemToResult, mapItemToResult = _ref2$mapItemToResult === void 0 ? postQueryMapItemToResult : _ref2$mapItemToResult; body = _objectSpread({ query: query, cloudId: this.cloudId, limit: resultLimit, scopes: scopes, filters: filters, searchSession: { sessionId: sessionId, referrerId: referrerId } }, modelParams.length > 0 ? { modelParams: modelParams } : {}); _context3.next = 4; return this.makeRequest(QUICKSEARCH_API_URL, body); case 4: response = _context3.sent; return _context3.abrupt("return", this.parseResponse(response, mapItemToResult)); case 6: case "end": return _context3.stop(); } } }, _callee3, this); })); function search(_x3) { return _search.apply(this, arguments); } return search; }() }, { key: "getRecentItems", value: function () { var _getRecentItems = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee4(_ref3) { var context, modelParams, _ref3$resultLimit, resultLimit, _ref3$filters, filters, mapItemToResult, scopes, recents, body, response; return _regenerator.default.wrap(function _callee4$(_context4) { while (1) { switch (_context4.prev = _context4.next) { case 0: context = _ref3.context, modelParams = _ref3.modelParams, _ref3$resultLimit = _ref3.resultLimit, resultLimit = _ref3$resultLimit === void 0 ? this.RESULT_LIMIT : _ref3$resultLimit, _ref3$filters = _ref3.filters, filters = _ref3$filters === void 0 ? [] : _ref3$filters, mapItemToResult = _ref3.mapItemToResult; if (!this.isUserAnonymous) { _context4.next = 3; break; } return _context4.abrupt("return", EMPTY_CROSS_PRODUCT_SEARCH_RESPONSE); case 3: scopes = mapContextToScopes(context); if (!this.crossProductRecentsCache) { _context4.next = 10; break; } _context4.next = 7; return this.crossProductRecentsCache; case 7: recents = _context4.sent; if (!areAllScopesInCache(scopes, recents)) { _context4.next = 10; break; } return _context4.abrupt("return", { results: recents }); case 10: body = _objectSpread({ query: '', cloudId: this.cloudId, limit: resultLimit, scopes: scopes, filters: filters }, modelParams.length > 0 ? { modelParams: modelParams } : {}); _context4.next = 13; return this.makeRequest(QUICKSEARCH_API_URL, body); case 13: response = _context4.sent; return _context4.abrupt("return", this.parseResponse(response, mapItemToResult)); case 15: case "end": return _context4.stop(); } } }, _callee4, this); })); function getRecentItems(_x4) { return _getRecentItems.apply(this, arguments); } return getRecentItems; }() }, { key: "getAbTestDataForProduct", value: function () { var _getAbTestDataForProduct = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee5(product) { var scope; return _regenerator.default.wrap(function _callee5$(_context5) { while (1) { switch (_context5.prev = _context5.next) { case 0: _context5.t0 = product; _context5.next = _context5.t0 === 'confluence' ? 3 : _context5.t0 === 'jira' ? 5 : 7; break; case 3: scope = _types.Scope.ConfluencePageBlogAttachment; return _context5.abrupt("break", 8); case 5: scope = _types.Scope.JiraIssue; return _context5.abrupt("break", 8); case 7: throw new Error('Invalid product for abtest'); case 8: _context5.next = 10; return this.getAbTestData(scope); case 10: return _context5.abrupt("return", _context5.sent); case 11: case "end": return _context5.stop(); } } }, _callee5, this); })); function getAbTestDataForProduct(_x5) { return _getAbTestDataForProduct.apply(this, arguments); } return getAbTestDataForProduct; }() /** * @deprecated use {getAbTestDataForProduct} instead. Using manually defined scopes here can * break caching behaviour. * * This will be moved into private scope in the near future. */ }, { key: "getAbTestData", value: function () { var _getAbTestData = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee6(scope) { var path, body, response, scopeWithAbTest, abTestPromise; return _regenerator.default.wrap(function _callee6$(_context6) { while (1) { switch (_context6.prev = _context6.next) { case 0: if (!this.abTestDataCache[scope]) { _context6.next = 2; break; } return _context6.abrupt("return", this.abTestDataCache[scope]); case 2: path = 'experiment/v1'; body = { cloudId: this.cloudId, scopes: [scope] }; _context6.next = 6; return this.makeRequest(path, body); case 6: response = _context6.sent; scopeWithAbTest = response.scopes.find(function (s) { return s.id === scope; }); abTestPromise = scopeWithAbTest ? Promise.resolve(scopeWithAbTest.abTest) : Promise.resolve(DEFAULT_AB_TEST); this.abTestDataCache[scope] = abTestPromise; return _context6.abrupt("return", abTestPromise); case 11: case "end": return _context6.stop(); } } }, _callee6, this); })); function getAbTestData(_x6) { return _getAbTestData.apply(this, arguments); } return getAbTestData; }() }, { key: "makeRequest", value: function () { var _makeRequest = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee7(path, body) { var options; return _regenerator.default.wrap(function _callee7$(_context7) { while (1) { switch (_context7.prev = _context7.next) { case 0: options = { path: path, requestInit: { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) } }; return _context7.abrupt("return", _utilServiceSupport.utils.requestService(this.serviceConfig, options)); case 2: case "end": return _context7.stop(); } } }, _callee7, this); })); function makeRequest(_x7, _x8) { return _makeRequest.apply(this, arguments); } return makeRequest; }() /** * Converts the raw xpsearch-aggregator response into a CrossProductSearchResults object containing * the results set and the experimentId that generated them. * * @param response * @param searchSessionId * @returns a CrossProductSearchResults object */ }, { key: "parseResponse", value: function parseResponse(response, mapItemToResult) { var abTest; var results = response.scopes.filter(function (scope) { return scope.results; }).reduce(function (resultsMap, scopeResult) { var items = scopeResult.results.map(function (result) { return mapItemToResult(scopeResult.id, result); }); // mapItemToResult returns a generic result type, technically we can't guarantee that the // type returned by `mapItemToResult` can be coerced into the expected type, e.g. there's // no guarantee the `Result` can be casted to `ConfluenceObjectResult`. We just make the assumption // here for now and suppress the typescript error resultsMap[scopeResult.id] = { // @ts-ignore items: items, totalSize: scopeResult.size !== undefined ? scopeResult.size : items.length }; if (!abTest) { abTest = scopeResult.abTest; } return resultsMap; }, {}); return { results: results, abTest: abTest }; } }]); return CachingCrossProductSearchClientImpl; }(); exports.default = CachingCrossProductSearchClientImpl; function mapPersonItemToResult(item) { var mention = item.nickname || item.name; return { resultType: _Result.ResultType.PersonResult, resultId: 'people-' + item.account_id, name: item.name, href: '/people/' + item.account_id, avatarUrl: item.picture, contentType: _Result.ContentType.Person, analyticsType: _Result.AnalyticsType.ResultPerson, mentionName: mention, presenceMessage: item.job_title || '' }; } function mapUrsResultItemToResult(item) { return { resultType: _Result.ResultType.PersonResult, resultId: 'people-' + item.id, name: item.name, href: '/people/' + item.id, avatarUrl: item.avatarUrl, contentType: _Result.ContentType.Person, analyticsType: _Result.AnalyticsType.ResultPerson, mentionName: item.nickname || '', presenceMessage: '' }; } function postQueryMapItemToResult(scope, item) { if (scope.startsWith('confluence')) { return (0, _ConfluenceItemMapper.mapConfluenceItemToResult)(scope, item); } if (scope.startsWith('jira')) { return (0, _JiraItemMapper.mapJiraItemToResult)(_Result.AnalyticsType.ResultJira)(item); } if (scope === _types.Scope.People) { return mapPersonItemToResult(item); } if (scope === _types.Scope.UserConfluence || scope === _types.Scope.UserJira) { return mapUrsResultItemToResult(item); } if (scope === _types.Scope.NavSearchCompleteConfluence) { throw new Error('nav.completion-confluence cannot be transformed into a result because it is not a search result'); } throw new Error("Non-exhaustive match for scope: ".concat(scope)); } function mapItemToNavCompletionString(item) { var completionItem = item; return completionItem.query; } function mapContextToScopes(context) { if (context === 'jira') { return [_types.Scope.JiraIssue, _types.Scope.JiraBoardProjectFilter]; } else { throw new Error("Supplied contet ".concat(context, " is not supported for pre-fetching")); } } function areAllScopesInCache(scopes, cache) { return scopes.filter(function (scope) { return cache[scope] === undefined; }).length === 0; }