import {Component} from 'react'; // eslint-disable-next-line no-restricted-imports import {withRouter, WithRouterProps} from 'react-router'; import { Result as SearchResult, SentryGlobalSearch, standardSDKSlug, } from '@sentry-internal/global-search'; import dompurify from 'dompurify'; import debounce from 'lodash/debounce'; import {Organization, Project} from 'sentry/types'; import parseHtmlMarks from 'sentry/utils/parseHtmlMarks'; import withLatestContext from 'sentry/utils/withLatestContext'; import {ChildProps, Result, ResultItem} from './types'; type Props = WithRouterProps & { /** * Render function that renders the global search result */ children: (props: ChildProps) => React.ReactNode; organization: Organization; /** * Specific platforms to filter results to */ platforms: string[]; project: Project; /** * The string to search the navigation routes for */ query: string; }; type State = { loading: boolean; results: Result[]; }; const MARK_TAGS = { highlightPreTag: '', highlightPostTag: '', }; class HelpSource extends Component { state: State = { loading: false, results: [], }; componentDidMount() { if (this.props.query !== undefined) { this.doSearch(this.props.query); } } componentDidUpdate(nextProps: Props) { if (nextProps.query !== this.props.query) { this.doSearch(nextProps.query); } } search = new SentryGlobalSearch(['docs', 'help-center', 'develop', 'blog']); async unbouncedSearch(query: string) { this.setState({loading: true}); const {platforms = []} = this.props; const searchResults = await this.search.query(query, { platforms: platforms.map(platform => standardSDKSlug(platform)?.slug!), }); const results = mapSearchResults(searchResults); this.setState({loading: false, results}); } doSearch = debounce(this.unbouncedSearch, 300); render() { return this.props.children({ isLoading: this.state.loading, results: this.state.results, }); } } function mapSearchResults(results: SearchResult[]) { const items: Result[] = []; results.forEach(section => { const sectionItems = section.hits.map(hit => { const title = parseHtmlMarks({ key: 'title', htmlString: hit.title ?? '', markTags: MARK_TAGS, }); const description = parseHtmlMarks({ key: 'description', htmlString: hit.text ?? '', markTags: MARK_TAGS, }); const item: ResultItem = { ...hit, sourceType: 'help', resultType: `help-${hit.site}` as ResultItem['resultType'], title: dompurify.sanitize(hit.title ?? ''), extra: hit.context.context1, description: hit.text ? dompurify.sanitize(hit.text) : undefined, to: hit.url, }; return {item, matches: [title, description], score: 1, refIndex: 0}; }); // The first element should indicate the section. if (sectionItems.length > 0) { sectionItems[0].item.sectionHeading = section.name; sectionItems[0].item.sectionCount = sectionItems.length; items.push(...sectionItems); return; } // If we didn't have any results for this section mark it as empty const emptyHeaderItem: ResultItem = { sourceType: 'help', resultType: `help-${section.site}` as ResultItem['resultType'], title: `No results in ${section.name}`, sectionHeading: section.name, empty: true, }; items.push({item: emptyHeaderItem, score: 1, refIndex: 0}); }); return items; } export {HelpSource}; export default withLatestContext(withRouter(HelpSource));