UNPKG

@atlaskit/global-search

Version:

A cross-product search component (batteries included)

585 lines (493 loc) 27.2 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = exports.ConfluenceQuickSearchContainer = void 0; var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator")); var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator")); var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray")); var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); var _assertThisInitialized2 = _interopRequireDefault(require("@babel/runtime/helpers/assertThisInitialized")); var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits")); var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn")); var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _react = _interopRequireDefault(require("react")); var _reactIntl = require("react-intl"); var _analytics = require("@atlaskit/analytics"); var _CrossProductSearchClient = require("../../api/CrossProductSearchClient"); var _types = require("../../api/types"); var _ScreenCounter = require("../../util/ScreenCounter"); var _SearchResultsUtil = require("../SearchResultsUtil"); var _performanceNow = _interopRequireDefault(require("../../util/performance-now")); var _QuickSearchContainer = require("../common/QuickSearchContainer"); var _messages = require("../../messages"); var _NoResultsState = _interopRequireDefault(require("./NoResultsState")); var _SearchResults = _interopRequireDefault(require("../common/SearchResults")); var _AdvancedSearchGroup = _interopRequireDefault(require("./AdvancedSearchGroup")); var _ConfluenceSearchResultsMapper = require("./ConfluenceSearchResultsMapper"); var _experimentUtils = require("../../util/experiment-utils"); var _searchResultsUtils = require("../../util/search-results-utils"); var _modelParameters = require("../../util/model-parameters"); var _ConfluenceFilterGroup = _interopRequireDefault(require("./ConfluenceFilterGroup")); var _NoResultsInFilterState = _interopRequireDefault(require("./NoResultsInFilterState")); var _some = _interopRequireDefault(require("lodash/some")); var _FeaturesProvider = require("../FeaturesProvider"); function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = (0, _getPrototypeOf2.default)(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = (0, _getPrototypeOf2.default)(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return (0, _possibleConstructorReturn2.default)(this, result); }; } function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } 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 getRecentItemMatches = function getRecentItemMatches(query, recentItems) { if (!recentItems) { return []; } return recentItems.objects.items.filter(function (result) { return result.name.toLowerCase().indexOf(query.toLowerCase()) > -1; }).slice(0, _ConfluenceSearchResultsMapper.MAX_RECENT_RESULTS_TO_SHOW); }; var mergeSearchResultsWithRecentItems = function mergeSearchResultsWithRecentItems(searchResults, recentItems) { var defaultSearchResults = { objects: { items: [], totalSize: 0 }, spaces: { items: [], totalSize: 0 }, people: { items: [], totalSize: 0 } }; var results = _objectSpread(_objectSpread({}, defaultSearchResults), searchResults); return { objects: { items: (0, _searchResultsUtils.appendListWithoutDuplication)(recentItems, results.objects.items), // We don't add the 3 extra results to the total. // The rationale here is that the server results will eventually contain the 3 recent items // so the total here already includes the recent items. // In the case where we don't know the number from the server, we also can't show more so // this numeber should just be the size of the current list. totalSize: results.objects.totalSize, numberOfCurrentItems: results.objects.numberOfCurrentItems }, spaces: results.spaces, people: results.people }; }; var LOGGER_NAME = 'AK.GlobalSearch.ConfluenceQuickSearchContainer'; var CCS_AUTOCOMPLETE = 'ccsearch-autocomplete'; /** * Container Component that handles the data fetching when the user interacts with Search. */ var ConfluenceQuickSearchContainer = /*#__PURE__*/function (_React$Component) { (0, _inherits2.default)(ConfluenceQuickSearchContainer, _React$Component); var _super = _createSuper(ConfluenceQuickSearchContainer); function ConfluenceQuickSearchContainer() { var _this; (0, _classCallCheck2.default)(this, ConfluenceQuickSearchContainer); for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } _this = _super.call.apply(_super, [this].concat(args)); (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "screenCounters", { preQueryScreenCounter: new _ScreenCounter.SearchScreenCounter(), postQueryScreenCounter: new _ScreenCounter.SearchScreenCounter() }); (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "handleSearchSubmit", function (event, searchSessionId) { var _this$props$onAdvance = _this.props.onAdvancedSearch, onAdvancedSearch = _this$props$onAdvance === void 0 ? function () {} : _this$props$onAdvance; var target = event.target; var query = target.value; var defaultPrevented = false; onAdvancedSearch(Object.assign({}, event, { preventDefault: function preventDefault() { defaultPrevented = true; event.preventDefault(); event.stopPropagation(); }, stopPropagation: function stopPropagation() {} }), _SearchResultsUtil.ConfluenceAdvancedSearchTypes.Content, query, searchSessionId); if (!defaultPrevented) { (0, _SearchResultsUtil.redirectToConfluenceAdvancedSearch)(query); } }); (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "handleSearchErrorAnalyticsThunk", function (source) { return function (error) { _this.handleSearchErrorAnalytics(error, source); _this.props.logger.safeError(LOGGER_NAME, "error in promise ".concat(source), error); }; }); (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "getSearchResults", function (query, sessionId, startTime, queryVersion, filters) { var confXpSearchPromise = _this.searchCrossProductConfluence(query, sessionId, queryVersion, filters); confXpSearchPromise.catch(_this.handleSearchErrorAnalyticsThunk('xpsearch-confluence')); var mapPromiseToPerformanceTime = function mapPromiseToPerformanceTime(p) { return p.then(function () { return (0, _performanceNow.default)() - startTime; }); }; return Promise.all([confXpSearchPromise, mapPromiseToPerformanceTime(confXpSearchPromise)]).then(function (_ref) { var _ref2 = (0, _slicedToArray2.default)(_ref, 2), xpsearchResults = _ref2[0], confSearchElapsedMs = _ref2[1]; var spaces = xpsearchResults.results[_types.Scope.ConfluenceSpace]; var objects = xpsearchResults.results[_types.Scope.ConfluencePageBlogAttachment]; var people = xpsearchResults.results[_types.Scope.People]; return { results: { objects: { items: objects ? objects.items : [], totalSize: objects ? objects.totalSize : 0 }, spaces: { items: spaces ? spaces.items : [], totalSize: spaces ? spaces.totalSize : 0 }, people: { items: people ? people.items : [], totalSize: people ? people.totalSize : 0 } }, timings: { confSearchElapsedMs: confSearchElapsedMs } }; }); }); (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "getRecentPeople", function (sessionId) { var _this$props = _this.props, peopleSearchClient = _this$props.peopleSearchClient, crossProductSearchClient = _this$props.crossProductSearchClient, features = _this$props.features, referralContextIdentifiers = _this$props.referralContextIdentifiers; var referrerId = referralContextIdentifiers && referralContextIdentifiers.searchReferrerId; // We want to be consistent with the search results when prefetching is enabled so we will use URS (via aggregator) to get the // bootstrapped people results, see prefetchResults.ts. return !features.useUrsForBootstrapping ? peopleSearchClient.getRecentPeople() : crossProductSearchClient.getPeople({ query: '', sessionId: sessionId, referrerId: referrerId, currentQuickSearchContext: 'confluence', resultLimit: 3 }).then(function (xProductResult) { var recentPeople = xProductResult.results[_types.Scope.UserConfluence]; return recentPeople ? recentPeople.items : []; }); }); (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "getRecentItems", function (sessionId) { var confluenceClient = _this.props.confluenceClient; var recentActivityPromisesMap = { 'recent-confluence-items': confluenceClient.getRecentItems(), 'recent-confluence-spaces': confluenceClient.getRecentSpaces() }; var recentActivityPromises = Object.keys(recentActivityPromisesMap).map(function (key) { return (0, _SearchResultsUtil.handlePromiseError)(recentActivityPromisesMap[key], [], _this.handleSearchErrorAnalyticsThunk(key)); }); // NOTE: // We lose type safety here as typescript assumes there's no guarantee the order in which a map // gets converted into promises. Also there is currently no way (and no way in the forseeable future) // to get typescript to convert union types into tuple types (https://github.com/Microsoft/TypeScript/issues/13298) var required = Promise.all(recentActivityPromises).then(function (_ref3) { var _ref4 = (0, _slicedToArray2.default)(_ref3, 2), recentlyViewedPages = _ref4[0], recentlyViewedSpaces = _ref4[1]; return { results: { objects: { items: recentlyViewedPages, totalSize: recentlyViewedPages.length }, spaces: { items: recentlyViewedSpaces, totalSize: recentlyViewedSpaces.length }, people: { items: [], totalSize: 0 } } }; }); return { eagerRecentItemsPromise: required, lazyLoadedRecentItemsPromise: _this.getRecentPeople(sessionId).then(function (recentPeople) { return { people: { items: recentPeople, totalSize: recentPeople.length } }; }) }; }); (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "getAutocompleteSuggestions", function (query) { var _this$props2 = _this.props, autocompleteClient = _this$props2.autocompleteClient, features = _this$props2.features; if (features.isNavAutocompleteEnabled) { var crossProductSearchClient = _this.props.crossProductSearchClient; var navAutocompletePromise = (0, _SearchResultsUtil.handlePromiseError)(crossProductSearchClient.getNavAutocompleteSuggestions(query), [query], _this.handleSearchErrorAnalyticsThunk(CCS_AUTOCOMPLETE)); return navAutocompletePromise; } else if (features.isAutocompleteEnabled) { var autocompletePromise = (0, _SearchResultsUtil.handlePromiseError)(autocompleteClient.getAutocompleteSuggestions(query), [query], _this.handleSearchErrorAnalyticsThunk(CCS_AUTOCOMPLETE)); return autocompletePromise; } return Promise.resolve([]); }); (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "getPreQueryDisplayedResults", function (recentItems, searchSessionId) { var features = _this.props.features; return (0, _ConfluenceSearchResultsMapper.mapRecentResultsToUIGroups)(recentItems, features, searchSessionId); }); (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "getPostQueryDisplayedResults", function (searchResults, latestSearchQuery, recentItems, isLoading, searchSessionId) { var features = _this.props.features; var currentSearchResults = isLoading || !searchResults ? {} : searchResults; var recentResults = getRecentItemMatches(latestSearchQuery, recentItems); var mergedRecentSearchResults = mergeSearchResultsWithRecentItems(currentSearchResults, recentResults); return (0, _ConfluenceSearchResultsMapper.mapSearchResultsToUIGroups)(mergedRecentSearchResults, features, searchSessionId, isLoading // Hide the lozenge for the faster search screen ); }); (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "getNoResultsStateComponent", function (latestSearchQuery, searchSessionId, currentFilters, onFilterChanged) { var _this$props3 = _this.props, _this$props3$onAdvanc = _this$props3.onAdvancedSearch, onAdvancedSearch = _this$props3$onAdvanc === void 0 ? function () {} : _this$props3$onAdvanc, referralContextIdentifiers = _this$props3.referralContextIdentifiers; var filtersAreApplied = currentFilters.length > 0; if (filtersAreApplied && referralContextIdentifiers && referralContextIdentifiers.currentContainerName) { return /*#__PURE__*/_react.default.createElement(_NoResultsInFilterState.default, { spaceTitle: referralContextIdentifiers.currentContainerName, query: latestSearchQuery, onClickAdvancedSearch: function onClickAdvancedSearch(event, entity) { return onAdvancedSearch(event, entity, latestSearchQuery, searchSessionId); }, onClearFilters: function onClearFilters() { onFilterChanged([]); } }); } return /*#__PURE__*/_react.default.createElement(_NoResultsState.default, { query: latestSearchQuery, onClick: function onClick(event, entity) { return onAdvancedSearch(event, entity, latestSearchQuery, searchSessionId); } }); }); (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "getFilterComponent", function (searchResults) { return function (_ref5) { var latestSearchQuery = _ref5.latestSearchQuery, searchResultsTotalSize = _ref5.searchResultsTotalSize, isLoading = _ref5.isLoading, searchSessionId = _ref5.searchSessionId, currentFilters = _ref5.currentFilters, onFilterChanged = _ref5.onFilterChanged; var _this$props4 = _this.props, _this$props4$onAdvanc = _this$props4.onAdvancedSearch, _onAdvancedSearch = _this$props4$onAdvanc === void 0 ? function () {} : _this$props4$onAdvanc, features = _this$props4.features; if (!features.spaceballsExperimentEnabled || searchResults === null) { return; } // only show filter post-query if (!latestSearchQuery) { return; } // don't show space filter if there are no results in all spaces if (currentFilters.length === 0 && searchResultsTotalSize === 0) { return; } var filterObject = _this.createQueryBasedSpaceFilter(currentFilters, searchResults); if (filterObject && filterObject.filter.spaceKeys[0] && filterObject.metadata) { var _filter = filterObject.filter; var _spaces = _filter ? _filter.spaceKeys : []; return /*#__PURE__*/_react.default.createElement(_ConfluenceFilterGroup.default, { onFilterChanged: onFilterChanged, isDisabled: isLoading, spaceTitle: filterObject.metadata.spaceTitle, spaceAvatar: filterObject.metadata.spaceAvatar, spaceKey: _filter.spaceKeys[0], isFilterOn: currentFilters.length !== 0, searchSessionId: searchSessionId, onAdvancedSearch: function onAdvancedSearch(event) { return _onAdvancedSearch(event, _SearchResultsUtil.ConfluenceAdvancedSearchTypes.Content, latestSearchQuery, searchSessionId, _spaces); } }); } }; }); (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "createQueryBasedSpaceFilter", function (currentFilters, searchResults) { function instanceOfSpaceFilter(filter) { return filter.filter['@type'] === _CrossProductSearchClient.FilterType.Spaces; } function createSpaceFilter(_ref6) { var spaces = _ref6.spaces; var space = spaces.items[0]; return space && space.key && space.avatarUrl ? { filter: { '@type': _CrossProductSearchClient.FilterType.Spaces, spaceKeys: [space.key] }, metadata: { spaceAvatar: space.avatarUrl, spaceTitle: space.name } } : undefined; } var existingSpaceFilter = currentFilters.find(instanceOfSpaceFilter); if (existingSpaceFilter) { return existingSpaceFilter; } return createSpaceFilter(searchResults); }); (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "getSearchResultsComponent", function (_ref7) { var retrySearch = _ref7.retrySearch, latestSearchQuery = _ref7.latestSearchQuery, isError = _ref7.isError, searchResults = _ref7.searchResults, isLoading = _ref7.isLoading, recentItems = _ref7.recentItems, searchSessionId = _ref7.searchSessionId, searchMore = _ref7.searchMore, currentFilters = _ref7.currentFilters, onFilterChanged = _ref7.onFilterChanged; var _this$props$onAdvance2 = _this.props.onAdvancedSearch, onAdvancedSearch = _this$props$onAdvance2 === void 0 ? function () {} : _this$props$onAdvance2; var onSearchMoreAdvancedSearchClicked = function onSearchMoreAdvancedSearchClicked(event) { onAdvancedSearch(event, _SearchResultsUtil.ConfluenceAdvancedSearchTypes.Content, latestSearchQuery, searchSessionId); }; return /*#__PURE__*/_react.default.createElement(_SearchResults.default, (0, _extends2.default)({ query: latestSearchQuery, isPreQuery: !latestSearchQuery, isError: isError, onFilterChanged: onFilterChanged, getFilterComponent: _this.getFilterComponent(searchResults), isLoading: isLoading, retrySearch: retrySearch, searchMore: searchMore, currentFilters: currentFilters, onSearchMoreAdvancedSearchClicked: onSearchMoreAdvancedSearchClicked, keepPreQueryState: false, searchSessionId: searchSessionId }, _this.screenCounters, { referralContextIdentifiers: _this.props.referralContextIdentifiers, renderNoRecentActivity: function renderNoRecentActivity() { return /*#__PURE__*/_react.default.createElement(_reactIntl.FormattedHTMLMessage, (0, _extends2.default)({}, _messages.messages.no_recent_activity_body, { values: { url: (0, _SearchResultsUtil.getConfluenceAdvancedSearchLink)() } })); }, renderAdvancedSearchGroup: function renderAdvancedSearchGroup(analyticsData) { return /*#__PURE__*/_react.default.createElement(_AdvancedSearchGroup.default, { key: "advanced", query: latestSearchQuery, analyticsData: analyticsData, onClick: function onClick(event, entity) { return onAdvancedSearch(event, entity, latestSearchQuery, searchSessionId); } }); }, getPreQueryGroups: function getPreQueryGroups() { return _this.getPreQueryDisplayedResults(recentItems, searchSessionId); }, getPostQueryGroups: function getPostQueryGroups() { return _this.getPostQueryDisplayedResults(searchResults, latestSearchQuery, recentItems, isLoading, searchSessionId); }, renderNoResult: function renderNoResult() { return _this.getNoResultsStateComponent(latestSearchQuery, searchSessionId, currentFilters, onFilterChanged); } })); }); return _this; } (0, _createClass2.default)(ConfluenceQuickSearchContainer, [{ key: "searchCrossProductConfluence", value: function () { var _searchCrossProductConfluence = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(query, sessionId, queryVersion, filters) { var _this$props5, crossProductSearchClient, modelContext, referralContextIdentifiers, scopes, modelParams, referrerId, results; return _regenerator.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: _this$props5 = this.props, crossProductSearchClient = _this$props5.crossProductSearchClient, modelContext = _this$props5.modelContext, referralContextIdentifiers = _this$props5.referralContextIdentifiers; scopes = [_types.Scope.ConfluencePageBlogAttachment]; if (!(0, _some.default)(filters, function (filter) { return filter['@type'] === 'spaces'; })) { scopes = scopes.concat([_types.Scope.ConfluenceSpace, _types.Scope.People]); } modelParams = (0, _modelParameters.buildConfluenceModelParams)(queryVersion, modelContext || {}); referrerId = referralContextIdentifiers && referralContextIdentifiers.searchReferrerId; _context.next = 7; return crossProductSearchClient.search({ query: query, sessionId: sessionId, referrerId: referrerId, scopes: scopes, modelParams: modelParams, resultLimit: _experimentUtils.CONF_MAX_DISPLAYED_RESULTS, filters: filters }); case 7: results = _context.sent; return _context.abrupt("return", results); case 9: case "end": return _context.stop(); } } }, _callee, this); })); function searchCrossProductConfluence(_x, _x2, _x3, _x4) { return _searchCrossProductConfluence.apply(this, arguments); } return searchCrossProductConfluence; }() // TODO extract }, { key: "handleSearchErrorAnalytics", value: function handleSearchErrorAnalytics(error, source) { var firePrivateAnalyticsEvent = this.props.firePrivateAnalyticsEvent; if (firePrivateAnalyticsEvent) { try { firePrivateAnalyticsEvent('atlassian.fabric.global-search.search-error', { name: error.name, message: error.message, source: source }); } catch (e) { this.props.logger.safeError(LOGGER_NAME, 'Can not fire event atlassian.fabric.global-search.search-error', e); } } } }, { key: "render", value: function render() { var _this$props6 = this.props, linkComponent = _this$props6.linkComponent, logger = _this$props6.logger, inputControls = _this$props6.inputControls, features = _this$props6.features; var isAutocompleteEnabled = features.isAutocompleteEnabled; var isNavAutocompleteEnabled = features.isNavAutocompleteEnabled; return /*#__PURE__*/_react.default.createElement(_QuickSearchContainer.BaseConfluenceQuickSearchContainer, { placeholder: this.props.intl.formatMessage(_messages.messages.confluence_search_placeholder), linkComponent: linkComponent, getSearchResultsComponent: this.getSearchResultsComponent, referralContextIdentifiers: this.props.referralContextIdentifiers, getRecentItems: this.getRecentItems, getSearchResults: this.getSearchResults, getAutocompleteSuggestions: isAutocompleteEnabled || isNavAutocompleteEnabled ? this.getAutocompleteSuggestions : undefined, product: "confluence", handleSearchSubmit: this.handleSearchSubmit, getPreQueryDisplayedResults: this.getPreQueryDisplayedResults, getPostQueryDisplayedResults: this.getPostQueryDisplayedResults, logger: logger, inputControls: inputControls, features: features, advancedSearchId: _SearchResultsUtil.ADVANCED_CONFLUENCE_SEARCH_RESULT_ID }); } }]); return ConfluenceQuickSearchContainer; }(_react.default.Component); exports.ConfluenceQuickSearchContainer = ConfluenceQuickSearchContainer; var _default = (0, _FeaturesProvider.injectFeatures)((0, _reactIntl.injectIntl)((0, _analytics.withAnalytics)(ConfluenceQuickSearchContainer, {}, {}))); exports.default = _default;