UNPKG

@atlaskit/global-search

Version:

A cross-product search component (batteries included)

532 lines (447 loc) 23.8 kB
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));