@atlaskit/global-search
Version:
A cross-product search component (batteries included)
468 lines (416 loc) • 22.4 kB
JavaScript
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));