UNPKG

@atlaskit/global-search

Version:

A cross-product search component (batteries included)

367 lines 23 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var tslib_1 = require("tslib"); var React = tslib_1.__importStar(require("react")); var react_intl_1 = require("react-intl"); // @ts-ignore var analytics_1 = require("@atlaskit/analytics"); var CrossProductSearchClient_1 = require("../../api/CrossProductSearchClient"); var types_1 = require("../../api/types"); var ScreenCounter_1 = require("../../util/ScreenCounter"); var SearchResultsUtil_1 = require("../SearchResultsUtil"); var performance_now_1 = tslib_1.__importDefault(require("../../util/performance-now")); var QuickSearchContainer_1 = require("../common/QuickSearchContainer"); var messages_1 = require("../../messages"); var NoResultsState_1 = tslib_1.__importDefault(require("./NoResultsState")); var SearchResults_1 = tslib_1.__importDefault(require("../common/SearchResults")); var SearchResultsUtil_2 = require("../SearchResultsUtil"); var AdvancedSearchGroup_1 = tslib_1.__importDefault(require("./AdvancedSearchGroup")); var ConfluenceSearchResultsMapper_1 = require("./ConfluenceSearchResultsMapper"); var experiment_utils_1 = require("../../util/experiment-utils"); var search_results_utils_1 = require("../../util/search-results-utils"); var model_parameters_1 = require("../../util/model-parameters"); var ConfluenceFilterGroup_1 = tslib_1.__importDefault(require("./ConfluenceFilterGroup")); var NoResultsInFilterState_1 = tslib_1.__importDefault(require("./NoResultsInFilterState")); var lodash_some_1 = tslib_1.__importDefault(require("lodash.some")); var FeaturesProvider_1 = require("../FeaturesProvider"); var getRecentItemMatches = function (query, recentItems) { if (!recentItems) { return []; } return recentItems.objects.items .filter(function (result) { return result.name.toLowerCase().indexOf(query.toLowerCase()) > -1; }) .slice(0, ConfluenceSearchResultsMapper_1.MAX_RECENT_RESULTS_TO_SHOW); }; var mergeSearchResultsWithRecentItems = function (searchResults, recentItems) { var defaultSearchResults = { objects: { items: [], totalSize: 0, }, spaces: { items: [], totalSize: 0, }, people: { items: [], totalSize: 0, }, }; var results = tslib_1.__assign(tslib_1.__assign({}, defaultSearchResults), searchResults); return { objects: { items: search_results_utils_1.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 = /** @class */ (function (_super) { tslib_1.__extends(ConfluenceQuickSearchContainer, _super); function ConfluenceQuickSearchContainer() { var _this = _super !== null && _super.apply(this, arguments) || this; _this.screenCounters = { preQueryScreenCounter: new ScreenCounter_1.SearchScreenCounter(), postQueryScreenCounter: new ScreenCounter_1.SearchScreenCounter(), }; _this.handleSearchSubmit = function (event, searchSessionId) { var _a = _this.props.onAdvancedSearch, onAdvancedSearch = _a === void 0 ? function () { } : _a; var target = event.target; var query = target.value; var defaultPrevented = false; onAdvancedSearch(Object.assign({}, event, { preventDefault: function () { defaultPrevented = true; event.preventDefault(); event.stopPropagation(); }, stopPropagation: function () { }, }), SearchResultsUtil_1.ConfluenceAdvancedSearchTypes.Content, query, searchSessionId); if (!defaultPrevented) { SearchResultsUtil_1.redirectToConfluenceAdvancedSearch(query); } }; _this.handleSearchErrorAnalyticsThunk = function (source) { return function (error) { _this.handleSearchErrorAnalytics(error, source); _this.props.logger.safeError(LOGGER_NAME, "error in promise " + source, error); }; }; _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 (p) { return p.then(function () { return performance_now_1.default() - startTime; }); }; return Promise.all([ confXpSearchPromise, mapPromiseToPerformanceTime(confXpSearchPromise), ]).then(function (_a) { var _b = tslib_1.__read(_a, 2), xpsearchResults = _b[0], confSearchElapsedMs = _b[1]; var spaces = xpsearchResults.results[types_1.Scope.ConfluenceSpace]; var objects = xpsearchResults.results[types_1.Scope.ConfluencePageBlogAttachment]; var people = xpsearchResults.results[types_1.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, }, }; }); }; _this.getRecentPeople = function (sessionId) { var _a = _this.props, peopleSearchClient = _a.peopleSearchClient, crossProductSearchClient = _a.crossProductSearchClient, features = _a.features, referralContextIdentifiers = _a.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_1.Scope.UserConfluence]; return recentPeople ? recentPeople.items : []; }); }; _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 SearchResultsUtil_1.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 (_a) { var _b = tslib_1.__read(_a, 2), recentlyViewedPages = _b[0], recentlyViewedSpaces = _b[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, }, }); }), }; }; _this.getAutocompleteSuggestions = function (query) { var _a = _this.props, autocompleteClient = _a.autocompleteClient, features = _a.features; if (features.isNavAutocompleteEnabled) { var crossProductSearchClient = _this.props.crossProductSearchClient; var navAutocompletePromise = SearchResultsUtil_1.handlePromiseError(crossProductSearchClient.getNavAutocompleteSuggestions(query), [query], _this.handleSearchErrorAnalyticsThunk(CCS_AUTOCOMPLETE)); return navAutocompletePromise; } else if (features.isAutocompleteEnabled) { var autocompletePromise = SearchResultsUtil_1.handlePromiseError(autocompleteClient.getAutocompleteSuggestions(query), [query], _this.handleSearchErrorAnalyticsThunk(CCS_AUTOCOMPLETE)); return autocompletePromise; } return Promise.resolve([]); }; _this.getPreQueryDisplayedResults = function (recentItems, searchSessionId) { var features = _this.props.features; return ConfluenceSearchResultsMapper_1.mapRecentResultsToUIGroups(recentItems, features, searchSessionId); }; _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 ConfluenceSearchResultsMapper_1.mapSearchResultsToUIGroups(mergedRecentSearchResults, features, searchSessionId, isLoading); }; _this.getNoResultsStateComponent = function (latestSearchQuery, searchSessionId, currentFilters, onFilterChanged) { var _a = _this.props, _b = _a.onAdvancedSearch, onAdvancedSearch = _b === void 0 ? function () { } : _b, referralContextIdentifiers = _a.referralContextIdentifiers; var filtersAreApplied = currentFilters.length > 0; if (filtersAreApplied && referralContextIdentifiers && referralContextIdentifiers.currentContainerName) { return (React.createElement(NoResultsInFilterState_1.default, { spaceTitle: referralContextIdentifiers.currentContainerName, query: latestSearchQuery, onClickAdvancedSearch: function (event, entity) { return onAdvancedSearch(event, entity, latestSearchQuery, searchSessionId); }, onClearFilters: function () { onFilterChanged([]); } })); } return (React.createElement(NoResultsState_1.default, { query: latestSearchQuery, onClick: function (event, entity) { return onAdvancedSearch(event, entity, latestSearchQuery, searchSessionId); } })); }; _this.getFilterComponent = function (searchResults) { return function (_a) { var latestSearchQuery = _a.latestSearchQuery, searchResultsTotalSize = _a.searchResultsTotalSize, isLoading = _a.isLoading, searchSessionId = _a.searchSessionId, currentFilters = _a.currentFilters, onFilterChanged = _a.onFilterChanged; var _b = _this.props, _c = _b.onAdvancedSearch, onAdvancedSearch = _c === void 0 ? function () { } : _c, features = _b.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_1 = filter ? filter.spaceKeys : []; return (React.createElement(ConfluenceFilterGroup_1.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 (event) { return onAdvancedSearch(event, SearchResultsUtil_1.ConfluenceAdvancedSearchTypes.Content, latestSearchQuery, searchSessionId, spaces_1); } })); } }; }; _this.createQueryBasedSpaceFilter = function (currentFilters, searchResults) { function instanceOfSpaceFilter(filter) { return filter.filter['@type'] === CrossProductSearchClient_1.FilterType.Spaces; } function createSpaceFilter(_a) { var spaces = _a.spaces; var space = spaces.items[0]; return space && space.key && space.avatarUrl ? { filter: { '@type': CrossProductSearchClient_1.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); }; _this.getSearchResultsComponent = function (_a) { var retrySearch = _a.retrySearch, latestSearchQuery = _a.latestSearchQuery, isError = _a.isError, searchResults = _a.searchResults, isLoading = _a.isLoading, recentItems = _a.recentItems, searchSessionId = _a.searchSessionId, searchMore = _a.searchMore, currentFilters = _a.currentFilters, onFilterChanged = _a.onFilterChanged; var _b = _this.props.onAdvancedSearch, onAdvancedSearch = _b === void 0 ? function () { } : _b; var onSearchMoreAdvancedSearchClicked = function (event) { onAdvancedSearch(event, SearchResultsUtil_1.ConfluenceAdvancedSearchTypes.Content, latestSearchQuery, searchSessionId); }; return (React.createElement(SearchResults_1.default, tslib_1.__assign({ 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 () { return (React.createElement(react_intl_1.FormattedHTMLMessage, tslib_1.__assign({}, messages_1.messages.no_recent_activity_body, { values: { url: SearchResultsUtil_2.getConfluenceAdvancedSearchLink() } }))); }, renderAdvancedSearchGroup: function (analyticsData) { return (React.createElement(AdvancedSearchGroup_1.default, { key: "advanced", query: latestSearchQuery, analyticsData: analyticsData, onClick: function (event, entity) { return onAdvancedSearch(event, entity, latestSearchQuery, searchSessionId); } })); }, getPreQueryGroups: function () { return _this.getPreQueryDisplayedResults(recentItems, searchSessionId); }, getPostQueryGroups: function () { return _this.getPostQueryDisplayedResults(searchResults, latestSearchQuery, recentItems, isLoading, searchSessionId); }, renderNoResult: function () { return _this.getNoResultsStateComponent(latestSearchQuery, searchSessionId, currentFilters, onFilterChanged); } }))); }; return _this; } ConfluenceQuickSearchContainer.prototype.searchCrossProductConfluence = function (query, sessionId, queryVersion, filters) { return tslib_1.__awaiter(this, void 0, void 0, function () { var _a, crossProductSearchClient, modelContext, referralContextIdentifiers, scopes, modelParams, referrerId, results; return tslib_1.__generator(this, function (_b) { switch (_b.label) { case 0: _a = this.props, crossProductSearchClient = _a.crossProductSearchClient, modelContext = _a.modelContext, referralContextIdentifiers = _a.referralContextIdentifiers; scopes = [types_1.Scope.ConfluencePageBlogAttachment]; if (!lodash_some_1.default(filters, function (filter) { return filter['@type'] === 'spaces'; })) { scopes = scopes.concat([types_1.Scope.ConfluenceSpace, types_1.Scope.People]); } modelParams = model_parameters_1.buildConfluenceModelParams(queryVersion, modelContext || {}); referrerId = referralContextIdentifiers && referralContextIdentifiers.searchReferrerId; return [4 /*yield*/, crossProductSearchClient.search({ query: query, sessionId: sessionId, referrerId: referrerId, scopes: scopes, modelParams: modelParams, resultLimit: experiment_utils_1.CONF_MAX_DISPLAYED_RESULTS, filters: filters, })]; case 1: results = _b.sent(); return [2 /*return*/, results]; } }); }); }; // TODO extract ConfluenceQuickSearchContainer.prototype.handleSearchErrorAnalytics = function (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); } } }; ConfluenceQuickSearchContainer.prototype.render = function () { var _a = this.props, linkComponent = _a.linkComponent, logger = _a.logger, inputControls = _a.inputControls, features = _a.features; var isAutocompleteEnabled = features.isAutocompleteEnabled; var isNavAutocompleteEnabled = features.isNavAutocompleteEnabled; return (React.createElement(QuickSearchContainer_1.BaseConfluenceQuickSearchContainer, { placeholder: this.props.intl.formatMessage(messages_1.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_1.ADVANCED_CONFLUENCE_SEARCH_RESULT_ID })); }; return ConfluenceQuickSearchContainer; }(React.Component)); exports.ConfluenceQuickSearchContainer = ConfluenceQuickSearchContainer; exports.default = FeaturesProvider_1.injectFeatures(react_intl_1.injectIntl(analytics_1.withAnalytics(ConfluenceQuickSearchContainer, {}, {}))); //# sourceMappingURL=ConfluenceQuickSearchContainer.js.map