UNCLASSIFIED - NO CUI

Skip to content
Snippets Groups Projects
Commit 4fffed24 authored by graham.smith's avatar graham.smith Committed by hunter.congress
Browse files

Bull-1617 fuzzy search on 404 page

parent 0000243e
No related branches found
No related tags found
1 merge request!202Bull-1617 fuzzy search on 404 page
<template>
<div class="error-page">
<div class="error-page-container"></div>
<div class="error-page-content">
<div class="row justify-content-center pt-16">
<v-img :src="YodaFireLogo" max-width="200"></v-img>
......@@ -14,41 +13,107 @@
<router-link to="/contact-us">contact us</router-link> for help.
</p>
</div>
<div class="suggestions-container" v-if="pageSuggestions.length > 0">
<h3>Some of these related links might get you back on track:</h3>
<div class="d-flex flex-column pa-4">
<a
v-for="(result, idx) in pageSuggestions.slice(0, 5)"
:key="idx"
class="py-2"
:href="result.ref"
>
{{ result.title }}
</a>
</div>
</div>
</div>
</div>
</template>
<script>
import YodaFireLogo from "@/assets/images/Yoda_Fire.webp";
import Search from "@/api/search.js";
import { routesByPath } from "@/router/routes.js";
const MAX_SUGGESTIONS = 3;
export default {
name: "ErrorComponent",
components: {},
data() {
return {
YodaFireLogo,
pageSuggestions: [],
};
},
mounted() {
const searchApi = new Search();
// parse out the address bar path into tokens
const pathSearchTokens = decodeURI(this.$route.fullPath)
.replaceAll("/", " ")
.trim()
.split(" ");
// try increasing edit distances to find potential matches
for (let editDistance = 0; editDistance < 4; editDistance++) {
const tokens = pathSearchTokens.map((t) => `${t}~${editDistance}`);
// run search
const results = searchApi.search(tokens.join(" "));
// assemble suggestions
this.addSuggestions(results);
if (this.pageSuggestions.length >= MAX_SUGGESTIONS) {
break;
}
}
},
methods: {
/**
* Adds the search results from `searchResults` to pageSuggestions,
* without adding duplicates
*/
addSuggestions(searchResults) {
// early exit
if (searchResults.length === 0) {
return;
}
// get current keys (ref)
const refs = this.pageSuggestions.map((suggestion) => suggestion.ref);
// add each non-duplicate ref
for (const searchResult of searchResults) {
if (!refs[searchResult.ref]) {
const details = routesByPath[searchResult.ref];
this.pageSuggestions.push(
Object.assign(searchResult, {
title: details.meta?.title,
})
);
}
}
},
},
};
</script>
<style lang="scss">
<style lang="scss" scoped>
.error-page {
.error-page-container {
background-image: url(@/assets/images/tech-bg.webp);
position: absolute;
background-attachment: fixed;
background-size: cover;
background-repeat: no-repeat;
background-color: $bottom-bg;
width: 100%;
height: 100%;
bottom: 0;
z-index: 0;
height: 100%;
padding-top: 16px;
padding-bottom: 16px;
background-image: url(@/assets/images/tech-bg.webp);
background-attachment: fixed;
background-size: cover;
background-repeat: no-repeat;
background-color: $bottom-bg;
.error-title {
font-size: 48px;
line-height: 48px;
text-transform: none;
}
.error-page-content {
position: relative;
.error-title {
font-size: 48px;
line-height: 48px;
.suggestions-container {
h3 {
color: white;
text-transform: none;
}
}
......
import { mount, RouterLinkStub } from "@vue/test-utils";
import Err from "@/views/Err.vue";
import Search from "@/api/search.js";
import { expect } from "../../../node_modules/vitest/dist/index";
vi.mock("../../../src/api/search.js");
describe("Err.vue", () => {
afterEach(() => {
vi.clearAllMocks();
});
it("should attempt multiple searches with increasing fuzzyness", async () => {
const mockSearch = vi.fn().mockReturnValue([]);
Search.mockImplementation(() => ({
search: mockSearch,
}));
const wrapper = mount(Err, {
stubs: {
RouterLink: RouterLinkStub,
},
mocks: {
$route: {
fullPath: "/mock/path",
},
},
});
expect(mockSearch).toHaveBeenCalledTimes(4);
expect(mockSearch.mock.calls[0][0]).toEqual("mock~0 path~0");
expect(mockSearch.mock.calls[1][0]).toEqual("mock~1 path~1");
expect(mockSearch.mock.calls[2][0]).toEqual("mock~2 path~2");
expect(mockSearch.mock.calls[3][0]).toEqual("mock~3 path~3");
});
it("should add suggestions and exit early", async () => {
const mockSearch = vi
.fn()
.mockReturnValueOnce([{ ref: "/" }, { ref: "/services" }])
.mockReturnValueOnce([{ ref: "/products/cnap" }]);
Search.mockImplementation(() => ({
search: mockSearch,
}));
const wrapper = mount(Err, {
stubs: {
RouterLink: RouterLinkStub,
},
mocks: {
$route: {
fullPath: "/mock/path",
},
},
});
expect(mockSearch).toHaveBeenCalledTimes(2);
expect(mockSearch.mock.calls[0][0]).toEqual("mock~0 path~0");
expect(mockSearch.mock.calls[1][0]).toEqual("mock~1 path~1");
expect(wrapper.vm.pageSuggestions.length).toEqual(3);
});
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment