content/frontend/search/components/elastic_results.vue (175 lines of code) (raw):

<script> import { GlSearchBoxByClick, GlLink, GlLoadingIcon, GlSafeHtmlDirective as SafeHtml, GlPagination, } from '@gitlab/ui'; import isEqual from 'lodash.isequal'; import { getSearchParamsFromURL, updateURLParams, convertFilterValues } from '../search_helpers'; import { fetchResults } from '../../services/elastic_search_api'; import SearchFilters from './search_filters.vue'; import VersionSearch from './version_search.vue'; // Max results displayed per page. const MAX_RESULTS_PER_PAGE = 10; export default { components: { SearchFilters, VersionSearch, GlSearchBoxByClick, GlLink, GlLoadingIcon, GlPagination, }, directives: { SafeHtml, }, data() { const { qParam, pageParam, filterParam } = getSearchParamsFromURL(); return { query: qParam || '', submitted: false, loading: false, error: false, pageNumber: Number(pageParam) || 1, response: {}, results: [], pagingInfo: {}, activeFilters: convertFilterValues(filterParam.split(','), false) || [], }; }, computed: { resultSummary() { const { pagingStart, pagingEnd, totalResults } = this.pagingInfo; return `Showing ${pagingStart}-${pagingEnd} of ${totalResults} results`; }, noResults() { return this.query && this.submitted && !this.results.length && !this.loading && !this.error; }, showPager() { return ( this.submitted && this.results.length && this.pagingInfo.totalResults > MAX_RESULTS_PER_PAGE && !this.loading ); }, }, created() { // Provides this constant for the template. this.MAX_RESULTS_PER_PAGE = MAX_RESULTS_PER_PAGE; }, mounted() { if (this.query) { this.search(this.query, this.activeFilters); } }, methods: { handleError(error) { this.error = true; this.loading = false; throw new Error(`Error code ${error.code}: ${error.message}`); }, async search(query, filters) { this.results = []; if (!query) return; // If the query or filters changed, return to page 1 of results. if (query !== this.query || !isEqual(filters, this.activeFilters)) this.pageNumber = 1; this.query = query; this.activeFilters = filters; const pageInfo = { pageNumber: this.pageNumber, numResults: MAX_RESULTS_PER_PAGE, }; try { this.loading = true; this.response = await fetchResults(query, filters, pageInfo); const { results, pagingStart, pagingEnd, totalResults } = this.response || {}; this.results = results.length ? results : []; this.pagingInfo = { pagingStart, pagingEnd, totalResults, }; } catch (error) { this.handleError(error); } finally { this.loading = false; this.submitted = true; updateURLParams({ q: this.query, page: this.pageNumber, filters: convertFilterValues(this.activeFilters, true), }); } }, addEllipsis(snippet) { if (snippet == null) { return ''; } return snippet.endsWith('.') || snippet.endsWith('!') || snippet.endsWith('?') ? snippet : `${snippet} ...`; }, }, }; </script> <template> <div class="elastic-search gl-mb-9" data-elastic-exclude> <h1 class="gl-pt-0! gl-mt-7!">Search the latest docs</h1> <div class="gl-h-11 gl-mb-5"> <gl-search-box-by-click v-model="query" :value="query" @submit="search(query, activeFilters)" /> <div v-if="results.length" class="gl-font-sm gl-mb-5 gl-ml-1"> {{ resultSummary }} </div> </div> <div class="results-container gl-lg-display-flex"> <div v-if="submitted" class="results-sidebar gl-mb-5 lg-w-20p"> <search-filters :initial-selected="activeFilters" @filteredSearch="(filters) => search(query, filters)" /> <version-search :query="query" /> </div> <div class="lg-w-70p"> <gl-loading-icon v-if="loading" size="lg" class="gl-mt-5 gl-text-center" /> <ul v-if="results.length" class="gl-list-style-none gl-pl-2" data-testid="search-results"> <li v-for="result in results" :key="result.id" class="!gl-mb-5"> <gl-link v-safe-html="result.title" data-result-type="page" :href="`${result.url_path}`" class="gl-font-lg gl-border-bottom-0! gl-hover-text-decoration-underline:hover gl-mb-2" /> <p v-if="result.breadcrumbs" v-safe-html="result.breadcrumbs" class="gl-font-sm gl-text-gray-400" ></p> <p v-safe-html="addEllipsis(result.htmlSnippet)" class="result-snippet gl-mt-0!"></p> </li> </ul> <gl-pagination v-if="showPager" v-model="pageNumber" :per-page="MAX_RESULTS_PER_PAGE" :total-items="pagingInfo.totalResults" class="gl-mt-9" @input="search(query, activeFilters)" /> <p v-if="noResults" class="gl-py-5"> No results found. Try adjusting your search terms, or searching the <gl-link class="gl-font-base" href="https://forum.gitlab.com/">community forum</gl-link>. </p> <p v-if="error" class="gl-py-5" data-testid="search-error"> Error fetching results. Please try again later. </p> </div> </div> </div> </template>