Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
3 changes: 2 additions & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@
<PackageVersion Include="AWSSDK.SQS" Version="4.0.0.1" />
<PackageVersion Include="AWSSDK.S3" Version="4.0.0.1" />
<PackageVersion Include="Elastic.Aspire.Hosting.Elasticsearch" Version="9.3.0" />
<PackageVersion Include="Elastic.Clients.Elasticsearch" Version="9.1.4" />
<PackageVersion Include="FakeItEasy" Version="8.3.0" />
<PackageVersion Include="Elastic.Ingest.Elasticsearch" Version="0.11.3" />
<PackageVersion Include="Elastic.Ingest.Elasticsearch" Version="0.14.0" />
<PackageVersion Include="InMemoryLogger" Version="1.0.66" />
<PackageVersion Include="MartinCostello.Logging.XUnit.v3" Version="0.6.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.4" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { useSearchActions, useSearchTerm } from '../search.store'
import { EuiButton, EuiSpacer, EuiText, useEuiTheme } from '@elastic/eui'
import {
EuiButton,
EuiIcon,
EuiSpacer,
EuiText,
useEuiTheme,
} from '@elastic/eui'
import { css } from '@emotion/react'
import * as React from 'react'

Expand All @@ -26,7 +32,16 @@ export const AskAiSuggestions = (props: Props) => {
`
return (
<>
<EuiText size="xs">Ask Elastic Docs AI Assistant</EuiText>
<div
css={css`
display: flex;
gap: ${euiTheme.size.s};
align-items: center;
`}
>
<EuiIcon type="sparkles" color="subdued" size="s" />
<EuiText size="xs">Ask Elastic Docs AI Assistant</EuiText>
</div>
<EuiSpacer size="s" />
{searchTerm && (
<EuiButton
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,32 @@
import { useSearchTerm } from '../search.store'
import { useSearchQuery } from './useSearchQuery'
import { SearchResultItem, useSearchQuery } from './useSearchQuery'
import {
EuiButton,
useEuiFontSize,
EuiHighlight,
EuiLink,
EuiLoadingSpinner,
EuiSpacer,
EuiText,
useEuiTheme,
EuiIcon,
EuiPagination,
} from '@elastic/eui'
import { css } from '@emotion/react'
import { useDebounce } from '@uidotdev/usehooks'
import * as React from 'react'
import { useEffect, useMemo, useState } from 'react'

export const SearchResults = () => {
const searchTerm = useSearchTerm()
const { data, error, isLoading } = useSearchQuery()
const [activePage, setActivePage] = useState(0)
const debouncedSearchTerm = useDebounce(searchTerm, 300)
useEffect(() => {
setActivePage(0)
}, [debouncedSearchTerm])
const { data, error, isLoading, isFetching } = useSearchQuery({
searchTerm,
pageNumber: activePage + 1,
})
const { euiTheme } = useEuiTheme()

if (!searchTerm) {
Expand All @@ -23,88 +37,193 @@ export const SearchResults = () => {
return <div>Error loading search results: {error.message}</div>
}

if (isLoading) {
return (
<div>
<EuiLoadingSpinner size="s" /> Loading search results...
return (
<div>
<div
css={css`
display: flex;
gap: ${euiTheme.size.s};
align-items: center;
`}
>
{isLoading || isFetching ? (
<EuiLoadingSpinner size="s" />
) : (
<EuiIcon type="search" color="subdued" size="s" />
)}
<EuiText size="xs">
Search results for{' '}
<span
css={css`
font-weight: ${euiTheme.font.weight.bold};
`}
>
{searchTerm}
</span>
</EuiText>
</div>
)
}
<EuiSpacer size="s" />
{data && (
<>
<ul>
{data.results.map((result) => (
<SearchResultListItem item={result} />
))}
</ul>
<div
css={css`
display: flex;
justify-content: flex-end;
`}
>
<EuiPagination
aria-label="Many pages example"
pageCount={Math.min(data.pageCount, 10)}
activePage={activePage}
onPageClick={(activePage) =>
setActivePage(activePage)
}
/>
</div>
</>
)}
</div>
)
}

if (!data || data.results.length === 0) {
return <EuiText size="xs">No results found for "{searchTerm}"</EuiText>
}
interface SearchResultListItemProps {
item: SearchResultItem
}

function SearchResultListItem({ item: result }: SearchResultListItemProps) {
const { euiTheme } = useEuiTheme()
const searchTerm = useSearchTerm()
const highlightSearchTerms = useMemo(
() => searchTerm.toLowerCase().split(' '),
[searchTerm]
)

const buttonCss = css`
border: none;
vertical-align: top;
justify-content: flex-start;
block-size: 100%;
padding-block: 4px;
& > span {
justify-content: flex-start;
align-items: flex-start;
}
svg {
color: ${euiTheme.colors.textSubdued};
}
.euiIcon {
margin-top: 4px;
}
`
if (highlightSearchTerms.includes('esql')) {
highlightSearchTerms.push('es|ql')
}

const trimDescription = (description: string) => {
const limit = 200
return description.length > limit
? description.slice(0, limit) + '...'
: description
if (highlightSearchTerms.includes('dotnet')) {
highlightSearchTerms.push('.net')
}
return (
<li key={result.url}>
<div
tabIndex={0}
css={css`
display: flex;
align-items: flex-start;
gap: ${euiTheme.size.s};
padding-inline: ${euiTheme.size.s};
padding-block: ${euiTheme.size.xs};
border-radius: ${euiTheme.border.radius.small};
:hover {
background-color: ${euiTheme.colors.backgroundTransparentSubdued};
`}
>
<EuiIcon
type="document"
color="subdued"
css={css`
margin-top: ${euiTheme.size.xs};
`}
/>
<div
css={css`
width: 100%;
text-align: left;
`}
>
<EuiLink
tabIndex={-1}
href={result.url}
css={css`
.euiMark {
background-color: ${euiTheme.colors
.backgroundLightWarning};
font-weight: inherit;
}
`}
>
<EuiHighlight
search={highlightSearchTerms}
highlightAll={true}
>
{result.title}
</EuiHighlight>
</EuiLink>
<Breadcrumbs
parents={result.parents}
highlightSearchTerms={highlightSearchTerms}
/>
</div>
</div>
</li>
)
}

function Breadcrumbs({
parents,
highlightSearchTerms,
}: {
parents: SearchResultItem['parents']
highlightSearchTerms: string[]
}) {
const { euiTheme } = useEuiTheme()
const { fontSize: smallFontsize } = useEuiFontSize('xs')
return (
<div
css={`
li:not(:first-child) {
margin-top: ${euiTheme.size.xs};
}
<ul
css={css`
margin-top: 2px;
display: flex;
gap: 0 ${euiTheme.size.xs};
flex-wrap: wrap;
list-style: none;
`}
>
<EuiText size="xs">Search Results for "{searchTerm}"</EuiText>
<EuiSpacer size="s" />
<ul>
{data.results.map((result) => (
<li key={result.url}>
<EuiButton
css={buttonCss}
iconType="document"
color="text"
size="s"
fullWidth
>
<div
{parents
.slice(1) // skip /docs
.map((parent) => (
<li
key={'breadcrumb-' + parent.url}
css={css`
&:not(:last-child)::after {
content: '/';
margin-left: ${euiTheme.size.xs};
font-size: ${smallFontsize};
color: ${euiTheme.colors.text};
margin-top: -1px;
}
display: inline-flex;
`}
>
<EuiLink href={parent.url} color="text" tabIndex={-1}>
<EuiText
size="xs"
color="subdued"
css={css`
width: 100%;
text-align: left;
.euiMark {
background-color: transparent;
text-decoration: underline;
color: inherit;
font-weight: inherit;
}
`}
>
{result.title}
<EuiSpacer size="xs" />
<EuiText
css={css`
text-wrap: pretty;
`}
textAlign="left"
size="xs"
color="subdued"
<EuiHighlight
search={highlightSearchTerms}
highlightAll={true}
>
{trimDescription(result.description)}
</EuiText>
</div>
</EuiButton>
{/*<EuiIcon type="document" color="subdued" />*/}
{/*<EuiText>{result.title}</EuiText>*/}
{parent.title}
</EuiHighlight>
</EuiText>
</EuiLink>
</li>
))}
</ul>
</div>
</ul>
)
}
Loading
Loading