import {Component} from 'react'; import flatten from 'lodash/flatten'; import type {Fuse} from 'sentry/utils/fuzzySearch'; import {Result} from './types'; type ChildProps = { hasAnyResults: boolean; isLoading: boolean; results: Result[]; }; type Props = { children: (props: ChildProps) => React.ReactElement; params: {orgId: string}; query: string; sources: React.ComponentType[]; searchOptions?: Fuse.IFuseOptions; }; type SourceResult = { isLoading: boolean; results: Result[]; }; class SearchSources extends Component { // `allSources` will be an array of all result objects from each source renderResults(allSources: SourceResult[]) { const {children} = this.props; // loading means if any result has `isLoading` OR any result is null const isLoading = !!allSources.find(arg => arg.isLoading || arg.results === null); const foundResults = isLoading ? [] : flatten(allSources.map(({results}) => results || [])).sort( (a, b) => (a.score ?? 0) - (b.score ?? 0) ); const hasAnyResults = !!foundResults.length; return children({ isLoading, results: foundResults, hasAnyResults, }); } renderSources(sources: Props['sources'], results: SourceResult[], idx: number) { if (idx >= sources.length) { return this.renderResults(results); } const Source = sources[idx]; return ( {(args: SourceResult) => { // Mutate the array instead of pushing because we don't know how often // this child function will be called and pushing will cause duplicate // results to be pushed for all calls down the chain. results[idx] = args; return this.renderSources(sources, results, idx + 1); }} ); } render() { return this.renderSources( this.props.sources, new Array(this.props.sources.length), 0 ); } } export default SearchSources;