Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

17176 Global search results web components conversion #28812

Draft
wants to merge 19 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/applications/search/actions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export function fetchSearchResults(query, page, options) {
if (page) {
queryString = queryString.concat(`&page=${page}`);
}

if (!query) {
return dispatch({
type: FETCH_SEARCH_RESULTS_EMPTY,
Expand All @@ -34,7 +35,6 @@ export function fetchSearchResults(query, page, options) {
'search-results-total-pages':
response?.meta?.pagination?.totalPages,
'search-selection': 'All VA.gov',
'search-typeahead-enabled': options?.typeaheadEnabled,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed because typeahead will always be enabled in the sitewide search; this value isn't needed.

'search-location': options?.searchLocation,
'sitewide-search-app-used': options?.sitewideSearch,
'type-ahead-option-keyword-selected': options?.keywordSelected,
Expand Down
31 changes: 31 additions & 0 deletions src/applications/search/components/Breadcrumbs.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React, { useEffect } from 'react';
Copy link
Contributor Author

@randimays randimays Jul 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not new code; it was moved & renamed and converted from a class component to a function component.

import { VaBreadcrumbs } from '@department-of-veterans-affairs/web-components/react-bindings';
import { focusElement } from 'platform/utilities/ui';

const Breadcrumbs = () => {
useEffect(() => {
focusElement('#search-breadcrumbs');
}, []);

return (
<div className="row">
<VaBreadcrumbs
class="vads-u-margin-left--1p5"
id="search-breadcrumbs"
label="Breadcrumbs"
breadcrumbList={[
{
href: '/',
label: 'Home',
},
{
href: '/search',
label: 'Search VA.gov',
},
]}
/>
</div>
);
};

export default Breadcrumbs;
32 changes: 32 additions & 0 deletions src/applications/search/components/Errors.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react';
Copy link
Contributor Author

@randimays randimays Jul 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not new code; it was pulled out of SearchApp.jsx

import PropTypes from 'prop-types';

const Errors = ({ userInput }) => {
let errorMessage;

if (!userInput.trim().length) {
errorMessage = `Enter a search term that contains letters or numbers to find what you're looking for.`;
} else if (userInput.length > 255) {
errorMessage =
'The search is over the character limit. Shorten the search and try again.';
} else {
errorMessage = `We’re sorry. Something went wrong on our end, and your search
didn't go through. Please try again.`;
}

return (
<div className="vads-u-margin-bottom--1p5">
{/* this is the alert box for when searches fail due to server issues */}
<va-alert status="error" data-e2e-id="alert-box">
<h2 slot="headline">Your search didn’t go through</h2>
<p>{errorMessage}</p>
</va-alert>
</div>
);
};

Errors.propTypes = {
userInput: PropTypes.string.isRequired,
};

export default Errors;
29 changes: 29 additions & 0 deletions src/applications/search/components/MoreVASearchTools.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react';
Copy link
Contributor Author

@randimays randimays Jul 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not new code; it was pulled out of SearchApp.jsx


const MoreVASearchTools = () => (
<ul>
<li>
<va-link
href="https://search.usa.gov/search?affiliate=bvadecisions"
text="Look up Board of Veterans' Appeals (BVA) decisions"
/>
</li>
<li>
<va-link href="/find-forms/" text="Find a VA form" />
</li>
<li>
<va-link
href="https://www.va.gov/vapubs/"
text="VA handbooks and other publications"
/>
</li>
<li>
<va-link
href="https://www.vacareers.va.gov/job-search/index.asp"
text="Explore and apply for open VA jobs"
/>
</li>
</ul>
);

export default MoreVASearchTools;
42 changes: 42 additions & 0 deletions src/applications/search/components/RecommendedResults.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not new code; pulled out of SearchApp.jsx

import PropTypes from 'prop-types';
import Result from './Result';

const RecommendedResults = ({ query, searchData, typeaheadUsed }) => {
const { recommendedResults } = searchData;

if (recommendedResults && recommendedResults.length > 0) {
return (
<div>
<h3 className="vads-u-font-size--base vads-u-font-family--sans vads-u-color--gray-dark vads-u-font-weight--bold">
Our top recommendations for you
</h3>
<ul className="results-list" data-e2e-id="top-recommendations">
{recommendedResults.map((result, index) => (
<Result
index={index}
isBestBet
key={index}
query={query}
result={result}
searchData={searchData}
snippetKey="description"
typeaheadUsed={typeaheadUsed}
/>
))}
</ul>
<hr aria-hidden="true" />
</div>
);
}

return null;
};

RecommendedResults.propTypes = {
query: PropTypes.string,
searchData: PropTypes.object,
typeaheadUsed: PropTypes.bool,
};

export default RecommendedResults;
162 changes: 162 additions & 0 deletions src/applications/search/components/Result.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import React from 'react';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not new code; pulled from SearchApp.jsx

import PropTypes from 'prop-types';
import * as Sentry from '@sentry/browser';
import recordEvent from 'platform/monitoring/record-event';
import { apiRequest } from 'platform/utilities/api';
import { replaceWithStagingDomain } from 'platform/utilities/environment/stagingDomains';
import {
formatResponseString,
truncateResponseString,
removeDoubleBars,
} from '../utils';

const MAX_DESCRIPTION_LENGTH = 186;

const onSearchResultClick = ({
bestBet,
index,
query,
searchData,
title,
typeaheadUsed,
url,
}) => async e => {
const { currentPage, recommendedResults, totalEntries } = searchData;
e.preventDefault();

// clear the &t query param which is used to track typeahead searches
// removing this will better reflect how many typeahead searches result in at least one click
window.history.replaceState(
null,
document.title,
`${window.location.href.replace('&t=true', '')}`,
);

const bestBetPosition = index + 1;
const normalResultPosition = index + (recommendedResults?.length || 0) + 1;
const searchResultPosition = bestBet ? bestBetPosition : normalResultPosition;

const encodedUrl = encodeURIComponent(url);
const userAgent = encodeURIComponent(navigator.userAgent);
const encodedQuery = encodeURIComponent(query);
const apiRequestOptions = {
method: 'POST',
};
const moduleCode = bestBet ? 'BOOS' : 'I14Y';

// By implementing in this fashion (i.e. a promise chain), code that follows is not blocked by this api request. Following the link at the end of the
// function should happen regardless of the result of this api request, and it can happen before this request resolves.
apiRequest(
`https://api.va.gov/v0/search_click_tracking?position=${searchResultPosition}&query=${encodedQuery}&url=${encodedUrl}&user_agent=${userAgent}&module_code=${moduleCode}`,
apiRequestOptions,
).catch(error => {
Sentry.captureException(error);
randimays marked this conversation as resolved.
Show resolved Hide resolved
Sentry.captureMessage('search_click_tracking_error');
randimays marked this conversation as resolved.
Show resolved Hide resolved
});

if (bestBet) {
recordEvent({
event: 'nav-searchresults',
'nav-path': `Recommended Results -> ${title}`,
});
}

recordEvent({
event: 'onsite-search-results-click',
'search-page-path': document.location.pathname,
'search-query': query,
'search-result-chosen-page-url': url,
'search-result-chosen-title': title,
'search-results-n-current-page': currentPage,
'search-results-position': searchResultPosition,
'search-results-total-count': totalEntries,
'search-results-total-pages': Math.ceil(totalEntries / 10),
'search-results-top-recommendation': bestBet,
'search-result-type': 'title',
'search-selection': 'All VA.gov',
'search-typeahead-used': typeaheadUsed,
});

// relocate to clicked link page
window.location.href = url;
};

const Result = ({
index,
isBestBet,
query,
result,
searchData,
snippetKey = 'snippet',
typeaheadUsed,
}) => {
const strippedTitle = removeDoubleBars(
formatResponseString(result?.title, true),
);

if (result?.title && result?.url) {
return (
<li
key={result.url}
className="result-item vads-u-margin-top--1p5 vads-u-margin-bottom--4"
>
<h4
className="vads-u-display--inline vads-u-margin-top--1 vads-u-margin-bottom--0p25 vads-u-font-size--md vads-u-font-weight--bold vads-u-font-family--serif vads-u-text-decoration--underline"
data-e2e-id="result-title"
>
<va-link
disable-analytics
href={replaceWithStagingDomain(result.url)}
text={strippedTitle}
onClick={onSearchResultClick({
bestBet: isBestBet,
index,
query,
searchData,
title: strippedTitle,
typeaheadUsed,
url: replaceWithStagingDomain(result.url),
})}
/>
</h4>
<p className="result-url vads-u-color--green vads-u-font-size--base">
{replaceWithStagingDomain(result.url)}
</p>
<p
className="result-desc"
/* eslint-disable react/no-danger */
randimays marked this conversation as resolved.
Show resolved Hide resolved
dangerouslySetInnerHTML={{
__html: formatResponseString(
truncateResponseString(
result[snippetKey],
MAX_DESCRIPTION_LENGTH,
),
),
}}
/* eslint-enable react/no-danger */
/>
</li>
);
}

return null;
};

Result.propTypes = {
index: PropTypes.number.isRequired,
isBestBet: PropTypes.bool.isRequired,
query: PropTypes.string.isRequired,
result: PropTypes.shape({
title: PropTypes.string,
url: PropTypes.string,
}).isRequired,
searchData: PropTypes.shape({
currentPage: PropTypes.number,
recommendedResults: PropTypes.array,
totalEntries: PropTypes.number,
}).isRequired,
typeaheadUsed: PropTypes.bool.isRequired,
snippetKey: PropTypes.string,
};

export default Result;
Loading
Loading