@atlaskit/global-search
Version:
A cross-product search component (batteries included)
532 lines (447 loc) • 23.8 kB
JavaScript
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
import _regeneratorRuntime from "@babel/runtime/regenerator";
import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
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";
function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
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; }
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; } }
import React from 'react';
import GlobalQuickSearch from '../GlobalQuickSearch';
import performanceNow from '../../util/performance-now';
import { buildShownEventDetails } from '../../util/analytics-util';
import { firePreQueryShownEvent, firePostQueryShownEvent, fireExperimentExposureEvent } from '../../util/analytics-event-helper';
import { withAnalyticsEvents } from '@atlaskit/analytics-next';
import deepEqual from 'deep-equal';
import { CONF_OBJECTS_ITEMS_PER_PAGE } from '../../util/experiment-utils';
import { injectSearchSession } from '../SearchSessionProvider';
var resultMapToArray = function resultMapToArray(results) {
return results.map(function (result) {
return result.items;
});
};
var LOGGER_NAME = 'AK.GlobalSearch.QuickSearchContainer';
/**
* Container/Stateful Component that handles the data fetching and state handling when the user interacts with Search.
*/
export var QuickSearchContainer = /*#__PURE__*/function (_React$Component) {
_inherits(QuickSearchContainer, _React$Component);
var _super = _createSuper(QuickSearchContainer);
// used to terminate if component is unmounted while waiting for a promise
function QuickSearchContainer(props) {
var _this;
_classCallCheck(this, QuickSearchContainer);
_this = _super.call(this, props);
_defineProperty(_assertThisInitialized(_this), "unmounted", false);
_defineProperty(_assertThisInitialized(_this), "latestQueryVersion", 0);
_defineProperty(_assertThisInitialized(_this), "doSearch", /*#__PURE__*/function () {
var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(query, queryVersion, filters) {
var startTime, _yield$_this$props$ge, _results, timings, elapsedMs;
return _regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
startTime = performanceNow();
_this.latestQueryVersion = queryVersion;
_context.prev = 2;
_context.next = 5;
return _this.props.getSearchResults(query, _this.props.searchSessionId, startTime, queryVersion, filters.map(function (_ref2) {
var filter = _ref2.filter;
return filter;
}));
case 5:
_yield$_this$props$ge = _context.sent;
_results = _yield$_this$props$ge.results;
timings = _yield$_this$props$ge.timings;
if (!_this.unmounted) {
_context.next = 10;
break;
}
return _context.abrupt("return");
case 10:
elapsedMs = performanceNow() - startTime;
if (_this.state.latestSearchQuery === query) {
_this.setState({
searchResults: _results,
isError: false,
isLoading: false,
keepPreQueryState: false
}, function () {
_this.fireShownPostQueryEvent(startTime, elapsedMs, _this.state.searchResults || {}, // Remove 'any' as part of QS-740
_this.state.recentItems || {}, // Remove 'any' as part of QS-740
timings || {}, _this.props.searchSessionId, _this.state.latestSearchQuery, _this.state.currentFilters.map(function (_ref3) {
var filter = _ref3.filter;
return filter;
}) || [], _this.state.isLoading);
});
}
_context.next = 18;
break;
case 14:
_context.prev = 14;
_context.t0 = _context["catch"](2);
_this.props.logger.safeError(LOGGER_NAME, 'error while getting search results', _context.t0);
_this.setState({
isError: true,
isLoading: false,
keepPreQueryState: false
});
case 18:
case "end":
return _context.stop();
}
}
}, _callee, null, [[2, 14]]);
}));
return function (_x, _x2, _x3) {
return _ref.apply(this, arguments);
};
}());
_defineProperty(_assertThisInitialized(_this), "fireExperimentExposureEvent", function () {
var _this$props = _this.props,
createAnalyticsEvent = _this$props.createAnalyticsEvent,
features = _this$props.features,
searchSessionId = _this$props.searchSessionId;
if (createAnalyticsEvent) {
fireExperimentExposureEvent(features.abTest, searchSessionId, createAnalyticsEvent);
}
});
_defineProperty(_assertThisInitialized(_this), "fireShownPreQueryEvent", function (requestStartTime, renderStartTime) {
var searchSessionId = _this.props.searchSessionId;
var recentItems = _this.state.recentItems;
var _this$props2 = _this.props,
createAnalyticsEvent = _this$props2.createAnalyticsEvent,
getPreQueryDisplayedResults = _this$props2.getPreQueryDisplayedResults,
enablePreQueryFromAggregator = _this$props2.enablePreQueryFromAggregator,
referralContextIdentifiers = _this$props2.referralContextIdentifiers,
features = _this$props2.features;
if (createAnalyticsEvent && getPreQueryDisplayedResults) {
var elapsedMs = requestStartTime ? performanceNow() - requestStartTime : 0;
var renderTime = renderStartTime ? performanceNow() - renderStartTime : 0;
var resultsArray = resultMapToArray(getPreQueryDisplayedResults(recentItems, searchSessionId));
var eventAttributes = _objectSpread({}, buildShownEventDetails.apply(void 0, _toConsumableArray(resultsArray)));
firePreQueryShownEvent(eventAttributes, elapsedMs, renderTime, searchSessionId, createAnalyticsEvent, features.abTest, referralContextIdentifiers, !!enablePreQueryFromAggregator);
}
});
_defineProperty(_assertThisInitialized(_this), "fireShownPostQueryEvent", function (startTime, elapsedMs, searchResults, recentItems, timings, searchSessionId, latestSearchQuery, latestFilters, isLoading) {
var features = _this.props.features;
var performanceTiming = _objectSpread({
startTime: startTime,
elapsedMs: elapsedMs
}, timings);
var _this$props3 = _this.props,
createAnalyticsEvent = _this$props3.createAnalyticsEvent,
getPostQueryDisplayedResults = _this$props3.getPostQueryDisplayedResults,
referralContextIdentifiers = _this$props3.referralContextIdentifiers;
var filtersApplied = {};
var _iterator = _createForOfIteratorHelper(latestFilters),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var _filter = _step.value;
filtersApplied[_filter['@type']] = true;
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
if (createAnalyticsEvent && getPostQueryDisplayedResults) {
var resultsArray = resultMapToArray(getPostQueryDisplayedResults(searchResults, latestSearchQuery, recentItems, isLoading, searchSessionId));
var resultsDetails = buildShownEventDetails.apply(void 0, _toConsumableArray(resultsArray));
firePostQueryShownEvent(resultsDetails, performanceTiming, searchSessionId, latestSearchQuery, filtersApplied, createAnalyticsEvent, features.abTest, referralContextIdentifiers);
}
});
_defineProperty(_assertThisInitialized(_this), "handleSearch", function (newLatestSearchQuery, queryVersion, filters) {
if (_this.state.latestSearchQuery !== newLatestSearchQuery || filters !== _this.state.currentFilters) {
_this.setState({
latestSearchQuery: newLatestSearchQuery,
currentFilters: filters,
isLoading: true
});
}
if (newLatestSearchQuery.length === 0) {
// reset search results so that internal state between query and results stays consistent
_this.setState({
isError: false,
isLoading: false,
keepPreQueryState: true,
currentFilters: []
}, function () {
_this.fireShownPreQueryEvent();
});
} else {
_this.doSearch(newLatestSearchQuery, queryVersion, filters);
}
});
_defineProperty(_assertThisInitialized(_this), "retrySearch", function () {
_this.handleSearch(_this.state.latestSearchQuery, _this.latestQueryVersion, _this.state.currentFilters);
});
_defineProperty(_assertThisInitialized(_this), "handleAutocomplete", /*#__PURE__*/function () {
var _ref4 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2(query) {
var getAutocompleteSuggestions, _results2;
return _regeneratorRuntime.wrap(function _callee2$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
getAutocompleteSuggestions = _this.props.getAutocompleteSuggestions;
if (getAutocompleteSuggestions) {
_context2.next = 3;
break;
}
return _context2.abrupt("return");
case 3:
_context2.prev = 3;
_context2.next = 6;
return getAutocompleteSuggestions(query);
case 6:
_results2 = _context2.sent;
if (!_this.unmounted) {
_context2.next = 9;
break;
}
return _context2.abrupt("return");
case 9:
_this.setState({
autocompleteSuggestions: _results2
});
_context2.next = 15;
break;
case 12:
_context2.prev = 12;
_context2.t0 = _context2["catch"](3);
_this.props.logger.safeError(LOGGER_NAME, 'error while getting autocompletion', _context2.t0);
case 15:
case "end":
return _context2.stop();
}
}
}, _callee2, null, [[3, 12]]);
}));
return function (_x4) {
return _ref4.apply(this, arguments);
};
}());
_defineProperty(_assertThisInitialized(_this), "getMoreSearchResults", /*#__PURE__*/function () {
var _ref5 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3(scope) {
var product, currentResultsByScope, result, numberOfCurrentItems;
return _regeneratorRuntime.wrap(function _callee3$(_context3) {
while (1) {
switch (_context3.prev = _context3.next) {
case 0:
product = _this.props.product;
if (product === 'confluence') {
try {
// This is a hack, we assume product = confluence means that this cast is safe. When GenericResultsMap is gone
// we probably won't need this cast anymore.
currentResultsByScope = _this.state.searchResults; // @ts-ignore More hacks as there's no guarantee that the scope is one that is available here
result = currentResultsByScope[scope];
if (result) {
numberOfCurrentItems = result.numberOfCurrentItems || CONF_OBJECTS_ITEMS_PER_PAGE;
_this.setState({
searchResults: _objectSpread(_objectSpread({}, _this.state.searchResults), {}, _defineProperty({}, scope, _objectSpread(_objectSpread({}, result), {}, {
numberOfCurrentItems: numberOfCurrentItems + CONF_OBJECTS_ITEMS_PER_PAGE
})))
});
}
} catch (e) {
_this.props.logger.safeError(LOGGER_NAME, "error while getting more results for ".concat(scope), e);
_this.setState({
isLoading: false
});
}
}
case 2:
case "end":
return _context3.stop();
}
}
}, _callee3);
}));
return function (_x5) {
return _ref5.apply(this, arguments);
};
}());
_defineProperty(_assertThisInitialized(_this), "handleSearchSubmit", function (event) {
var handleSearchSubmit = _this.props.handleSearchSubmit;
if (handleSearchSubmit) {
handleSearchSubmit(event, _this.props.searchSessionId);
}
});
_defineProperty(_assertThisInitialized(_this), "handleFilter", function (filter) {
_this.handleSearch(_this.state.latestSearchQuery, _this.latestQueryVersion, filter);
});
_this.state = {
isLoading: true,
isError: false,
latestSearchQuery: '',
recentItems: null,
searchResults: null,
keepPreQueryState: true,
currentFilters: []
};
return _this;
}
_createClass(QuickSearchContainer, [{
key: "shouldComponentUpdate",
value: function shouldComponentUpdate(nextProps, nextState) {
return !deepEqual(nextProps, this.props) || !deepEqual(nextState, this.state);
}
}, {
key: "componentDidCatch",
value: function componentDidCatch(error, info) {
this.props.logger.safeError(LOGGER_NAME, 'component did catch an error', {
error: error,
info: info,
safeState: {
searchSessionId: this.props.searchSessionId,
latestSearchQuery: !!this.state.latestSearchQuery,
isLoading: this.state.isLoading,
isError: this.state.isError,
keepPreQueryState: this.state.keepPreQueryState,
recentItems: !!this.state.recentItems,
searchResults: !!this.state.searchResults
}
});
this.setState({
isError: true
});
}
}, {
key: "componentWillUnmount",
value: function componentWillUnmount() {
this.unmounted = true;
}
}, {
key: "componentDidMount",
value: function () {
var _componentDidMount = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee5() {
var _this2 = this;
var startTime, _this$props$getRecent, eagerRecentItemsPromise, lazyLoadedRecentItemsPromise, _yield$eagerRecentIte, _results3, renderStartTime;
return _regeneratorRuntime.wrap(function _callee5$(_context5) {
while (1) {
switch (_context5.prev = _context5.next) {
case 0:
startTime = performanceNow();
this.fireExperimentExposureEvent();
_context5.prev = 2;
_this$props$getRecent = this.props.getRecentItems(this.props.searchSessionId), eagerRecentItemsPromise = _this$props$getRecent.eagerRecentItemsPromise, lazyLoadedRecentItemsPromise = _this$props$getRecent.lazyLoadedRecentItemsPromise;
_context5.next = 6;
return eagerRecentItemsPromise;
case 6:
_yield$eagerRecentIte = _context5.sent;
_results3 = _yield$eagerRecentIte.results;
renderStartTime = performanceNow();
if (!this.unmounted) {
_context5.next = 11;
break;
}
return _context5.abrupt("return");
case 11:
this.setState({
recentItems: _results3,
isLoading: false
});
lazyLoadedRecentItemsPromise.then(function (lazyLoadedRecentItems) {
_this2.setState({
recentItems: Object.assign({}, _results3, lazyLoadedRecentItems)
}, /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee4() {
return _regeneratorRuntime.wrap(function _callee4$(_context4) {
while (1) {
switch (_context4.prev = _context4.next) {
case 0:
_this2.fireShownPreQueryEvent(startTime, renderStartTime);
case 1:
case "end":
return _context4.stop();
}
}
}, _callee4);
})));
});
_context5.next = 19;
break;
case 15:
_context5.prev = 15;
_context5.t0 = _context5["catch"](2);
this.props.logger.safeError(LOGGER_NAME, 'error while getting recent items', _context5.t0);
if (this.state.isLoading) {
this.setState({
isLoading: false
});
}
case 19:
case "end":
return _context5.stop();
}
}
}, _callee5, this, [[2, 15]]);
}));
function componentDidMount() {
return _componentDidMount.apply(this, arguments);
}
return componentDidMount;
}()
}, {
key: "render",
value: function render() {
var _this$props4 = this.props,
linkComponent = _this$props4.linkComponent,
getSearchResultsComponent = _this$props4.getSearchResultsComponent,
placeholder = _this$props4.placeholder,
selectedResultId = _this$props4.selectedResultId,
onSelectedResultIdChanged = _this$props4.onSelectedResultIdChanged,
inputControls = _this$props4.inputControls,
searchSessionId = _this$props4.searchSessionId,
advancedSearchId = _this$props4.advancedSearchId;
var _this$state = this.state,
isLoading = _this$state.isLoading,
latestSearchQuery = _this$state.latestSearchQuery,
isError = _this$state.isError,
searchResults = _this$state.searchResults,
recentItems = _this$state.recentItems,
keepPreQueryState = _this$state.keepPreQueryState,
autocompleteSuggestions = _this$state.autocompleteSuggestions,
currentFilters = _this$state.currentFilters;
return /*#__PURE__*/React.createElement(GlobalQuickSearch, {
onSearch: this.handleSearch,
onSearchSubmit: this.handleSearchSubmit,
onAutocomplete: this.handleAutocomplete,
isLoading: isLoading,
placeholder: placeholder,
linkComponent: linkComponent,
searchSessionId: searchSessionId,
selectedResultId: selectedResultId,
onSelectedResultIdChanged: onSelectedResultIdChanged,
inputControls: inputControls,
autocompleteSuggestions: autocompleteSuggestions,
filters: this.state.currentFilters,
advancedSearchId: advancedSearchId
}, getSearchResultsComponent({
retrySearch: this.retrySearch,
latestSearchQuery: latestSearchQuery,
isError: isError,
searchResults: searchResults,
isLoading: isLoading,
recentItems: recentItems,
keepPreQueryState: keepPreQueryState,
searchSessionId: searchSessionId,
searchMore: this.getMoreSearchResults,
currentFilters: currentFilters,
onFilterChanged: this.handleFilter
}));
}
}]);
return QuickSearchContainer;
}(React.Component);
export var BaseConfluenceQuickSearchContainer = injectSearchSession(withAnalyticsEvents()(QuickSearchContainer));
export var BaseJiraQuickSearchContainerJira = injectSearchSession(withAnalyticsEvents()(QuickSearchContainer));