UNPKG

@atlaskit/global-search

Version:

A cross-product search component (batteries included)

468 lines (416 loc) 22.4 kB
import _slicedToArray from "@babel/runtime/helpers/slicedToArray"; import _extends from "@babel/runtime/helpers/extends"; import _classCallCheck from "@babel/runtime/helpers/classCallCheck"; import _createClass from "@babel/runtime/helpers/createClass"; import _assertThisInitialized from "@babel/runtime/helpers/assertThisInitialized"; import _inherits from "@babel/runtime/helpers/inherits"; import _possibleConstructorReturn from "@babel/runtime/helpers/possibleConstructorReturn"; import _getPrototypeOf from "@babel/runtime/helpers/getPrototypeOf"; import _defineProperty from "@babel/runtime/helpers/defineProperty"; import _taggedTemplateLiteral from "@babel/runtime/helpers/taggedTemplateLiteral"; var _templateObject, _templateObject2; function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(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) { _defineProperty(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; } import React from 'react'; import { injectIntl, FormattedHTMLMessage } from 'react-intl'; import styled from 'styled-components'; // AFP-2532 TODO: Fix automatic suppressions below // eslint-disable-next-line @atlassian/tangerine/import/entry-points import { gridSize } from '@atlaskit/theme'; import { withAnalytics } from '@atlaskit/analytics'; import { withAnalyticsEvents } from '@atlaskit/analytics-next'; import StickyFooter from '../common/StickyFooter'; import { SearchScreenCounter } from '../../util/ScreenCounter'; import { Scope } from '../../api/types'; import { BaseJiraQuickSearchContainerJira } from '../common/QuickSearchContainer'; import { messages } from '../../messages'; import SearchResultsComponent from '../common/SearchResults'; import NoResultsState from './NoResultsState'; import JiraAdvancedSearch from './JiraAdvancedSearch'; import { mapRecentResultsToUIGroups, mapSearchResultsToUIGroups, MAX_RECENT_RESULTS_TO_SHOW } from './JiraSearchResultsMapper'; import { handlePromiseError, JiraEntityTypes, redirectToJiraAdvancedSearch, ADVANCED_JIRA_SEARCH_RESULT_ID } from '../SearchResultsUtil'; import { AnalyticsType } from '../../model/Result'; import { getUniqueResultId } from '../ResultList'; import performanceNow from '../../util/performance-now'; import { fireSelectedAdvancedSearch } from '../../util/analytics-event-helper'; import AdvancedIssueSearchLink from './AdvancedIssueSearchLink'; import { getJiraMaxObjects } from '../../util/experiment-utils'; import { buildJiraModelParams } from '../../util/model-parameters'; import { injectFeatures } from '../FeaturesProvider'; import { mapJiraItemToResult } from '../../api/JiraItemMapper'; import { appendListWithoutDuplication } from '../../util/search-results-utils'; var JIRA_RESULT_LIMIT = 6; var JIRA_PREQUERY_RESULT_LIMIT = 10; var NoResultsAdvancedSearchContainer = styled.div(_templateObject || (_templateObject = _taggedTemplateLiteral(["\n margin-top: ", "px;\n"])), 4 * gridSize()); var BeforePreQueryStateContainer = styled.div(_templateObject2 || (_templateObject2 = _taggedTemplateLiteral(["\n margin-top: ", "px;\n"])), gridSize()); var containsQuery = function containsQuery(string, query) { return string.toLowerCase().indexOf(query.toLowerCase()) > -1; }; var getRecentItemMatches = function getRecentItemMatches(query, recentItems) { if (!recentItems) { return []; } var issueKeyMatches = recentItems.objects.filter(function (result) { return result.objectKey && containsQuery(result.objectKey, query); }); var titleMatches = recentItems.objects.filter(function (result) { return containsQuery(result.name, query); }); return issueKeyMatches.concat(titleMatches).slice(0, MAX_RECENT_RESULTS_TO_SHOW); }; var mergeSearchResultsWithRecentItems = function mergeSearchResultsWithRecentItems(searchResults, recentItems) { var defaultSearchResults = { objects: [], containers: [], people: [] }; var results = _objectSpread(_objectSpread({}, defaultSearchResults), searchResults); return { objects: appendListWithoutDuplication(recentItems, results.objects), containers: results.containers, people: results.people }; }; /** * NOTE: This component is only consumed internally as such avoid using optional props * i.e. instead of "propX?: something" use "propX: something | undefined" * * This improves type safety and prevent us from accidentally forgetting a parameter. */ var SCOPES = [Scope.JiraIssue, Scope.JiraBoardProjectFilter]; var LOGGER_NAME = 'AK.GlobalSearch.JiraQuickSearchContainer'; /** * Container/Stateful Component that handles the data fetching and state handling when the user interacts with Search. */ export var JiraQuickSearchContainer = /*#__PURE__*/function (_React$Component) { _inherits(JiraQuickSearchContainer, _React$Component); var _super = _createSuper(JiraQuickSearchContainer); function JiraQuickSearchContainer() { var _this; _classCallCheck(this, JiraQuickSearchContainer); 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)); _defineProperty(_assertThisInitialized(_this), "state", { selectedAdvancedSearchType: JiraEntityTypes.Issues }); _defineProperty(_assertThisInitialized(_this), "screenCounters", { preQueryScreenCounter: new SearchScreenCounter(), postQueryScreenCounter: new SearchScreenCounter() }); _defineProperty(_assertThisInitialized(_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() {} }), _this.state.selectedAdvancedSearchType, query, searchSessionId); if (!defaultPrevented) { redirectToJiraAdvancedSearch(_this.state.selectedAdvancedSearchType, query); } }); _defineProperty(_assertThisInitialized(_this), "handleAdvancedSearch", function (event, entity, query, searchSessionId, analyticsData, isLoading) { var _this$props = _this.props, referralContextIdentifiers = _this$props.referralContextIdentifiers, _this$props$onAdvance2 = _this$props.onAdvancedSearch, onAdvancedSearch = _this$props$onAdvance2 === void 0 ? function () {} : _this$props$onAdvance2; var eventData = _objectSpread(_objectSpread({ resultId: ADVANCED_JIRA_SEARCH_RESULT_ID }, analyticsData), {}, { query: query, // queryversion is missing contentType: entity, type: AnalyticsType.AdvancedSearchJira, isLoading: isLoading }); fireSelectedAdvancedSearch(eventData, searchSessionId, referralContextIdentifiers, _this.props.createAnalyticsEvent); onAdvancedSearch(event, entity, query, searchSessionId); }); _defineProperty(_assertThisInitialized(_this), "getPreQueryDisplayedResults", function (recentItems, searchSessionId) { var features = _this.props.features; return mapRecentResultsToUIGroups(recentItems, searchSessionId, features, _this.props.appPermission); }); _defineProperty(_assertThisInitialized(_this), "getPostQueryDisplayedResults", function (searchResults, latestSearchQuery, recentItems, isLoading, searchSessionId) { var features = _this.props.features; if (features.isInFasterSearchExperiment) { var currentSearchResults = isLoading || !searchResults ? {} : searchResults; var recentResults = getRecentItemMatches(latestSearchQuery, recentItems); var mergedRecentSearchResults = mergeSearchResultsWithRecentItems(currentSearchResults, recentResults); return mapSearchResultsToUIGroups(mergedRecentSearchResults, searchSessionId, features, _this.props.appPermission, latestSearchQuery); } return mapSearchResultsToUIGroups(searchResults, searchSessionId, features, _this.props.appPermission, latestSearchQuery); }); _defineProperty(_assertThisInitialized(_this), "getSearchResultsComponent", function (_ref) { var retrySearch = _ref.retrySearch, latestSearchQuery = _ref.latestSearchQuery, isError = _ref.isError, searchResults = _ref.searchResults, isLoading = _ref.isLoading, recentItems = _ref.recentItems, keepPreQueryState = _ref.keepPreQueryState, searchSessionId = _ref.searchSessionId, searchMore = _ref.searchMore, currentFilters = _ref.currentFilters, onFilterChanged = _ref.onFilterChanged; var query = latestSearchQuery; var _this$props2 = _this.props, referralContextIdentifiers = _this$props2.referralContextIdentifiers, _this$props2$onAdvanc = _this$props2.onAdvancedSearch, onAdvancedSearch = _this$props2$onAdvanc === void 0 ? function () {} : _this$props2$onAdvanc, appPermission = _this$props2.appPermission, features = _this$props2.features, isJiraPeopleProfilesEnabled = _this$props2.isJiraPeopleProfilesEnabled; return /*#__PURE__*/React.createElement(SearchResultsComponent, _extends({ query: query, isPreQuery: !query, isError: isError, isLoading: isLoading, retrySearch: retrySearch, keepPreQueryState: features.isInFasterSearchExperiment ? false : keepPreQueryState, searchSessionId: searchSessionId }, _this.screenCounters, { referralContextIdentifiers: referralContextIdentifiers, searchMore: searchMore, currentFilters: currentFilters, onFilterChanged: onFilterChanged, renderNoRecentActivity: function renderNoRecentActivity() { return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(FormattedHTMLMessage, messages.jira_no_recent_activity_body), /*#__PURE__*/React.createElement(NoResultsAdvancedSearchContainer, null, /*#__PURE__*/React.createElement(JiraAdvancedSearch, { appPermission: appPermission, query: query, analyticsData: { resultsCount: 0, wasOnNoResultsScreen: true }, onClick: function onClick(mouseEvent, entity) { return _this.handleAdvancedSearch(mouseEvent, entity, query, searchSessionId, { resultsCount: 0, wasOnNoResultsScreen: true }, isLoading); }, isJiraPeopleProfilesEnabled: isJiraPeopleProfilesEnabled }))); }, renderAdvancedSearchGroup: function renderAdvancedSearchGroup(analyticsData) { return /*#__PURE__*/React.createElement(StickyFooter, { style: { marginTop: "".concat(2 * gridSize(), "px") } }, /*#__PURE__*/React.createElement(JiraAdvancedSearch, { appPermission: appPermission, analyticsData: analyticsData, query: query, onClick: function onClick(mouseEvent, entity) { return _this.handleAdvancedSearch(mouseEvent, entity, query, searchSessionId, analyticsData, isLoading); }, isJiraPeopleProfilesEnabled: isJiraPeopleProfilesEnabled })); }, renderBeforePreQueryState: function renderBeforePreQueryState() { return /*#__PURE__*/React.createElement(BeforePreQueryStateContainer, null, /*#__PURE__*/React.createElement(AdvancedIssueSearchLink, { onClick: function onClick(_ref2) { var event = _ref2.event; return onAdvancedSearch(event, JiraEntityTypes.Issues, query, searchSessionId); } })); }, getPreQueryGroups: function getPreQueryGroups() { return _this.getPreQueryDisplayedResults(recentItems, searchSessionId); }, getPostQueryGroups: function getPostQueryGroups() { return _this.getPostQueryDisplayedResults(searchResults, latestSearchQuery, recentItems, isLoading, searchSessionId); }, renderNoResult: function renderNoResult() { return /*#__PURE__*/React.createElement(NoResultsState, { query: query, onAdvancedSearch: function onAdvancedSearch(mouseEvent, entity) { return _this.handleAdvancedSearch(mouseEvent, entity, query, searchSessionId, { resultsCount: 0, wasOnNoResultsScreen: true }, isLoading); }, isJiraPeopleProfilesEnabled: isJiraPeopleProfilesEnabled }); } })); }); _defineProperty(_assertThisInitialized(_this), "getRecentlyInteractedPeople", function () { /* the following code is temporarily feature flagged for performance reasons and will be shortly reinstated. https://product-fabric.atlassian.net/browse/QS-459 */ if (_this.props.features.disableJiraPreQueryPeopleSearch) { return Promise.resolve([]); } else { var peoplePromise = _this.props.peopleSearchClient.getRecentPeople(); return handlePromiseError(peoplePromise, [], function (error) { return _this.props.logger.safeError(LOGGER_NAME, 'error in recently interacted people promise', error); }); } }); _defineProperty(_assertThisInitialized(_this), "getJiraRecentItems", function () { var features = _this.props.features; return _this.props.crossProductSearchClient.getRecentItems({ context: 'jira', modelParams: [], resultLimit: getJiraMaxObjects(features.abTest, JIRA_PREQUERY_RESULT_LIMIT), mapItemToResult: function mapItemToResult(_, item) { return mapJiraItemToResult(AnalyticsType.RecentJira)(item); } }).then(function (xpRecentResults) { var objects = xpRecentResults.results[Scope.JiraIssue]; var containers = xpRecentResults.results[Scope.JiraBoardProjectFilter]; return { objects: objects ? objects.items : [], containers: containers ? containers.items : [], people: [] }; }).catch(function (error) { _this.props.logger.safeError(LOGGER_NAME, 'error in recent Jira items promise', error); return { objects: [], containers: [], people: [] }; }); }); _defineProperty(_assertThisInitialized(_this), "canSearchUsers", function () { /* the following code is temporarily feature flagged for performance reasons and will be shortly reinstated. https://product-fabric.atlassian.net/browse/QS-459 */ if (_this.props.features.disableJiraPreQueryPeopleSearch) { return Promise.resolve(false); } else { return handlePromiseError(_this.props.jiraClient.canSearchUsers(), false, function (error) { return _this.props.logger.safeError(LOGGER_NAME, 'error fetching browse user permission', error); }); } }); _defineProperty(_assertThisInitialized(_this), "getRecentItems", function () { return { eagerRecentItemsPromise: _this.getJiraRecentItems().then(function (results) { return { results: results }; }), lazyLoadedRecentItemsPromise: Promise.all([_this.getRecentlyInteractedPeople(), _this.canSearchUsers()]).then(function (_ref3) { var _ref4 = _slicedToArray(_ref3, 2), people = _ref4[0], canSearchUsers = _ref4[1]; return { people: canSearchUsers ? people : [] }; }) }; }); _defineProperty(_assertThisInitialized(_this), "getSearchResults", function (query, sessionId, startTime, queryVersion) { var features = _this.props.features; var referrerId = _this.props.referralContextIdentifiers && _this.props.referralContextIdentifiers.searchReferrerId; var crossProductSearchPromise = _this.props.crossProductSearchClient.search({ query: query, sessionId: sessionId, referrerId: referrerId, scopes: SCOPES, modelParams: buildJiraModelParams(queryVersion, _this.props.referralContextIdentifiers && _this.props.referralContextIdentifiers.currentContainerId), resultLimit: getJiraMaxObjects(features.abTest, JIRA_RESULT_LIMIT) }); var searchPeoplePromise = Promise.resolve([]); var mapPromiseToPerformanceTime = function mapPromiseToPerformanceTime(p) { return p.then(function () { return performanceNow() - startTime; }); }; return Promise.all([crossProductSearchPromise, searchPeoplePromise, mapPromiseToPerformanceTime(crossProductSearchPromise), mapPromiseToPerformanceTime(searchPeoplePromise), _this.canSearchUsers()]).then(function (_ref5) { var _ref6 = _slicedToArray(_ref5, 5), xpsearchResults = _ref6[0], peopleResults = _ref6[1], crossProductSearchElapsedMs = _ref6[2], peopleElapsedMs = _ref6[3], canSearchPeople = _ref6[4]; var objects = xpsearchResults.results[Scope.JiraIssue]; var containers = xpsearchResults.results[Scope.JiraBoardProjectFilter]; var objectItems = objects ? objects.items : []; _this.highlightMatchingFirstResult(query, objectItems); return { results: { objects: objectItems, containers: containers ? containers.items : [], people: canSearchPeople ? peopleResults : [] }, timings: { crossProductSearchElapsedMs: crossProductSearchElapsedMs, peopleElapsedMs: peopleElapsedMs }, abTest: xpsearchResults.abTest }; }); }); return _this; } _createClass(JiraQuickSearchContainer, [{ key: "highlightMatchingFirstResult", value: function highlightMatchingFirstResult(query, issueResults) { if (issueResults && issueResults.length > 0 && typeof issueResults[0].objectKey === 'string' && (issueResults[0].objectKey.toLowerCase() === query.toLowerCase() || !!+query && issueResults[0].objectKey.toLowerCase().endsWith("".concat(-query)))) { this.setState({ selectedResultId: getUniqueResultId(issueResults[0]) }); } } }, { key: "handleSelectedResultIdChanged", value: function handleSelectedResultIdChanged(newSelectedId) { this.setState({ selectedResultId: newSelectedId }); } }, { key: "render", value: function render() { var _this2 = this; var _this$props3 = this.props, linkComponent = _this$props3.linkComponent, createAnalyticsEvent = _this$props3.createAnalyticsEvent, logger = _this$props3.logger, features = _this$props3.features, referralContextIdentifiers = _this$props3.referralContextIdentifiers, isJiraPeopleProfilesEnabled = _this$props3.isJiraPeopleProfilesEnabled; var selectedResultId = this.state.selectedResultId; return /*#__PURE__*/React.createElement(BaseJiraQuickSearchContainerJira, { placeholder: this.props.intl.formatMessage(messages.jira_search_placeholder), linkComponent: linkComponent, getPreQueryDisplayedResults: function getPreQueryDisplayedResults(recentItems, searchSessionId) { return _this2.getPreQueryDisplayedResults(recentItems, searchSessionId); }, getPostQueryDisplayedResults: this.getPostQueryDisplayedResults, getSearchResultsComponent: this.getSearchResultsComponent, getRecentItems: this.getRecentItems, getSearchResults: this.getSearchResults, handleSearchSubmit: this.handleSearchSubmit // @ts-ignore , createAnalyticsEvent: createAnalyticsEvent, logger: logger, selectedResultId: selectedResultId, onSelectedResultIdChanged: function onSelectedResultIdChanged(newId) { return _this2.handleSelectedResultIdChanged(newId); }, referralContextIdentifiers: referralContextIdentifiers, product: "jira", features: features, advancedSearchId: ADVANCED_JIRA_SEARCH_RESULT_ID, isJiraPeopleProfilesEnabled: isJiraPeopleProfilesEnabled }); } }]); return JiraQuickSearchContainer; }(React.Component); var JiraQuickSearchContainerWithIntl = injectIntl(withAnalytics(JiraQuickSearchContainer, {}, {})); export default injectFeatures(withAnalyticsEvents()(JiraQuickSearchContainerWithIntl));