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>