Skip to content

Commit

Permalink
[Search Playground] Search Preview and Document Viewer (#192096)
Browse files Browse the repository at this point in the history
## Summary

Adds Search preview functionality to Search Playground
Adds Unified Document Viewer flyout to the Search Playground
All of these code are under the same UI setting that we had for Search
Playground.

https://github.com/user-attachments/assets/cd590414-b22f-4cde-a7e1-ca139aefe178

This introduces new dependencies to the plugin, locally there was no
problem visible (i.e. no warnings or something weird in Kibana logs)

### Checklist

Delete any items that are not applicable to this PR.

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [ ] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
- [x] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [ ] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
- [x] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [x] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)

### Risk Matrix

| Risk | Probability | Severity | Mitigation/Notes |

|---------------------------|-------------|----------|-------------------------|
| Potential breaking in Chat Playground Need to be tested | Low | Medium
| It should be tested by at least another person feature flag off in
Chat playground both Stack and Serverless |

---------

Co-authored-by: kibanamachine <[email protected]>
(cherry picked from commit ca8e53f)
  • Loading branch information
efegurkan committed Sep 16, 2024
1 parent 8f01c22 commit 8fa5ce5
Show file tree
Hide file tree
Showing 18 changed files with 493 additions and 87 deletions.
8 changes: 8 additions & 0 deletions x-pack/plugins/search_playground/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,16 @@
* 2.0.
*/

import { Pagination } from './types';

export const PLUGIN_ID = 'searchPlayground';
export const PLUGIN_NAME = 'Playground';
export const PLUGIN_PATH = '/app/search_playground';

export const SEARCH_MODE_FEATURE_FLAG_ID = 'searchPlayground:searchModeEnabled';

export const DEFAULT_PAGINATION: Pagination = {
from: 0,
size: 10,
total: 0,
};
7 changes: 7 additions & 0 deletions x-pack/plugins/search_playground/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export enum APIRoutes {
POST_CHAT_MESSAGE = '/internal/search_playground/chat',
POST_QUERY_SOURCE_FIELDS = '/internal/search_playground/query_source_fields',
GET_INDICES = '/internal/search_playground/indices',
POST_SEARCH_QUERY = '/internal/search_playground/search',
}

export enum LLMs {
Expand Down Expand Up @@ -82,3 +83,9 @@ export interface ModelProvider {
promptTokenLimit: number;
provider: LLMs;
}

export interface Pagination {
from: number;
size: number;
total: number;
}
4 changes: 3 additions & 1 deletion x-pack/plugins/search_playground/kibana.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
],
"requiredPlugins": [
"actions",
"data",
"encryptedSavedObjects",
"navigation",
"share",
Expand All @@ -25,7 +26,8 @@
"usageCollection",
],
"requiredBundles": [
"kibanaReact"
"kibanaReact",
"unifiedDocViewer"
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export const Header: React.FC<HeaderProps> = ({
<EuiPageHeaderSection>
<EuiFlexGroup alignItems="center">
{showDocs && <PlaygroundHeaderDocs />}
<Toolbar />
<Toolbar selectedPageMode={selectedPageMode} />
</EuiFlexGroup>
</EuiPageHeaderSection>
</EuiPageTemplate.Header>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React from 'react';

import { EuiEmptyPrompt } from '@elastic/eui';
import { i18n } from '@kbn/i18n';

export interface EmptyResultsArgs {
query?: string;
}

export const EmptyResults: React.FC<EmptyResultsArgs> = ({ query }) => {
return (
<EuiEmptyPrompt
body={
<p>
{query
? i18n.translate('xpack.searchPlayground.resultList.emptyWithQuery.text', {
defaultMessage: 'No result found for: {query}',
values: { query },
})
: i18n.translate('xpack.searchPlayground.resultList.empty.text', {
defaultMessage: 'No results found',
})}
</p>
}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,54 +5,117 @@
* 2.0.
*/

import React, { useEffect, useState } from 'react';

import {
EuiFlexGroup,
EuiFlexItem,
EuiHorizontalRule,
EuiPagination,
EuiPanel,
EuiText,
EuiTitle,
} from '@elastic/eui';
import React from 'react';

const DEMO_DATA = [
{ id: '123321', name: 'John Doe', age: 25 },
{ id: '123321', name: 'John Doe', age: 25 },
{ id: '123321', name: 'John Doe', age: 25 },
{ id: '123321', name: 'John Doe', age: 25 },
{ id: '123321', name: 'John Doe', age: 25 },
{ id: '123321', name: 'John Doe', age: 25 },
{ id: '123321', name: 'John Doe', age: 25 },
{ id: '123321', name: 'John Doe', age: 25 },
{ id: '123321', name: 'John Doe', age: 25 },
{ id: '123321', name: 'John Doe', age: 25 },
];
import { UnifiedDocViewerFlyout } from '@kbn/unified-doc-viewer-plugin/public';

import type { DataView } from '@kbn/data-views-plugin/public';
import type { EsHitRecord } from '@kbn/discover-utils/types';
import type { SearchHit } from '@elastic/elasticsearch/lib/api/types';
import { buildDataTableRecord } from '@kbn/discover-utils';
import { i18n } from '@kbn/i18n';
import { Pagination } from '../../types';
import { getPageCounts } from '../../utils/pagination_helper';
import { EmptyResults } from './empty_results';
import { useKibana } from '../../hooks/use_kibana';

export interface ResultListArgs {
searchResults: SearchHit[];
pagination: Pagination;
onPaginationChange: (nextPage: number) => void;
searchQuery?: string;
}

export const ResultList: React.FC = () => {
export const ResultList: React.FC<ResultListArgs> = ({
searchResults,
pagination,
onPaginationChange,
searchQuery = '',
}) => {
const {
services: { data },
} = useKibana();
const [dataView, setDataView] = useState<DataView | null>(null);
useEffect(() => {
data.dataViews.getDefaultDataView().then((d) => setDataView(d));
}, [data]);
const [flyoutDocId, setFlyoutDocId] = useState<string | undefined>(undefined);
const { totalPage, page } = getPageCounts(pagination);
const hit =
flyoutDocId &&
buildDataTableRecord(searchResults.find((item) => item._id === flyoutDocId) as EsHitRecord);
return (
<EuiPanel grow={false}>
<EuiFlexGroup direction="column" gutterSize="none">
{DEMO_DATA.map((item, index) => {
return (
<>
<EuiFlexItem key={item.id + '-' + index} grow>
<EuiFlexGroup direction="column" gutterSize="xs">
<EuiFlexItem grow>
<EuiTitle size="xs">
<h2>{item.id}</h2>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow>
<EuiText size="s">
<p>{item.name}</p>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
{index !== DEMO_DATA.length - 1 && <EuiHorizontalRule margin="m" />}
</>
);
})}
{searchResults.length === 0 && (
<EuiFlexItem>
<EmptyResults query={searchQuery} />
</EuiFlexItem>
)}
{searchResults.length !== 0 &&
searchResults.map((item, index) => {
return (
<>
<EuiFlexItem
key={item._id + '-' + index}
onClick={() => setFlyoutDocId(item._id)}
grow
>
<EuiFlexGroup direction="column" gutterSize="xs">
<EuiFlexItem grow>
<EuiTitle size="xs">
<h3>ID:{item._id}</h3>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow>
<EuiText size="s">
<p>
{i18n.translate('xpack.searchPlayground.resultList.result.score', {
defaultMessage: 'Document score: {score}',
values: { score: item._score },
})}
</p>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
{index !== searchResults.length - 1 && <EuiHorizontalRule margin="m" />}
</>
);
})}
{searchResults.length !== 0 && (
<EuiFlexItem>
<EuiPagination
pageCount={totalPage}
activePage={page}
onPageClick={onPaginationChange}
/>
</EuiFlexItem>
)}
{flyoutDocId && dataView && hit && (
<UnifiedDocViewerFlyout
services={{}}
onClose={() => setFlyoutDocId(undefined)}
isEsqlQuery={false}
columns={[]}
hit={hit}
dataView={dataView}
onAddColumn={() => {}}
onRemoveColumn={() => {}}
setExpandedDoc={() => {}}
flyoutType="overlay"
/>
)}
</EuiFlexGroup>
</EuiPanel>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,50 @@
*/

import {
EuiButton,
EuiEmptyPrompt,
EuiFieldText,
EuiFlexGroup,
EuiFlexItem,
EuiSearchBar,
EuiForm,
useEuiTheme,
} from '@elastic/eui';
import React from 'react';
import { css } from '@emotion/react';
import { Controller, useController, useFormContext } from 'react-hook-form';
import { i18n } from '@kbn/i18n';
import { useQueryClient } from '@tanstack/react-query';
import { DEFAULT_PAGINATION } from '../../../common';
import { ResultList } from './result_list';
import { ChatForm, ChatFormFields, Pagination } from '../../types';
import { useSearchPreview } from '../../hooks/use_search_preview';
import { getPaginationFromPage } from '../../utils/pagination_helper';

export const SearchMode: React.FC = () => {
const { euiTheme } = useEuiTheme();
const showResults = true; // TODO demo
const { control, handleSubmit } = useFormContext();
const {
field: { value: searchBarValue },
formState: { isSubmitting },
} = useController<ChatForm, ChatFormFields.searchQuery>({
name: ChatFormFields.searchQuery,
});

const [searchQuery, setSearchQuery] = React.useState<{
query: string;
pagination: Pagination;
}>({ query: searchBarValue, pagination: DEFAULT_PAGINATION });

const { results, pagination } = useSearchPreview(searchQuery);

const queryClient = useQueryClient();
const handleSearch = async (query = searchBarValue, paginationParam = DEFAULT_PAGINATION) => {
queryClient.resetQueries({ queryKey: ['search-preview-results'] });
setSearchQuery({ query, pagination: paginationParam });
};

const onPagination = (page: number) => {
handleSearch(searchBarValue, getPaginationFromPage(page, pagination.size, pagination));
};

return (
<EuiFlexGroup direction="row" justifyContent="center">
Expand All @@ -31,25 +61,55 @@ export const SearchMode: React.FC = () => {
>
<EuiFlexGroup direction="column">
<EuiFlexItem grow={false}>
<EuiSearchBar />
<EuiForm component="form" onSubmit={handleSubmit(() => handleSearch())}>
<Controller
control={control}
name={ChatFormFields.searchQuery}
render={({ field }) => (
<EuiFieldText
{...field}
value={searchBarValue}
icon="search"
fullWidth
placeholder={i18n.translate(
'xpack.searchPlayground.searchMode.searchBar.placeholder',
{ defaultMessage: 'Search for documents' }
)}
isLoading={isSubmitting}
/>
)}
/>
</EuiForm>
</EuiFlexItem>
<EuiFlexItem className="eui-yScroll">
<EuiFlexGroup direction="column">
<EuiFlexItem>
{showResults ? (
<ResultList />
{searchQuery.query ? (
<ResultList
searchResults={results}
pagination={pagination}
onPaginationChange={onPagination}
searchQuery={searchQuery.query}
/>
) : (
<EuiEmptyPrompt
iconType={'checkInCircleFilled'}
iconColor="success"
title={<h2>Ready to search</h2>}
title={
<h2>
{i18n.translate('xpack.searchPlayground.searchMode.readyToSearch', {
defaultMessage: 'Ready to search',
})}
</h2>
}
body={
<p>
Type in a query in the search bar above or view the query we automatically
created for you.
{i18n.translate('xpack.searchPlayground.searchMode.searchPrompt', {
defaultMessage:
'Type in a query in the search bar above or view the query we automatically created for you.',
})}
</p>
}
actions={<EuiButton>View the query</EuiButton>}
/>
)}
</EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ import { EuiFlexGroup } from '@elastic/eui';
import React from 'react';
import { DataActionButton } from './data_action_button';
import { ViewCodeAction } from './view_code/view_code_action';
import { PlaygroundPageMode } from '../types';

export const Toolbar: React.FC = () => {
export const Toolbar: React.FC<{ selectedPageMode: PlaygroundPageMode }> = ({
selectedPageMode = PlaygroundPageMode.chat,
}) => {
return (
<EuiFlexGroup gutterSize="s" alignItems="center" data-test-subj="playground-header-actions">
<DataActionButton />
<ViewCodeAction />
<ViewCodeAction selectedPageMode={selectedPageMode} />
</EuiFlexGroup>
);
};
Loading

0 comments on commit 8fa5ce5

Please sign in to comment.