Skip to content

Commit

Permalink
[Serverless Search] Indexing API - Select index (#160454)
Browse files Browse the repository at this point in the history
## Summary

Adds fetching indices and showing a combox to select and search for
indices to the indexing API page.

## Notes:
- Doc links are currently placeholders and will need to be set (at some
point?)

### Screenshots
<img width="1840" alt="image"
src="https://github.com/elastic/kibana/assets/1972968/1b60f0a3-5098-43a0-a741-3f7ed18e5107">

---------

Co-authored-by: Sander Philipse <sander.philipse@elastic.co>
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
3 people authored Jun 29, 2023
1 parent eab8268 commit 0aa94ed
Show file tree
Hide file tree
Showing 17 changed files with 463 additions and 8 deletions.
9 changes: 9 additions & 0 deletions x-pack/plugins/serverless_search/common/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,12 @@ export interface CreateAPIKeyArgs {
name: string;
role_descriptors?: Record<string, any>;
}

export interface IndexData {
name: string;
count: number;
}

export interface FetchIndicesResult {
indices: IndexData[];
}
10 changes: 10 additions & 0 deletions x-pack/plugins/serverless_search/common/utils/is_not_nullish.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* 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.
*/

export function isNotNullish<T>(value: T | null | undefined): value is T {
return value !== null && value !== undefined;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,33 @@
* 2.0.
*/

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

import {
EuiCallOut,
EuiComboBox,
EuiComboBoxOptionOption,
EuiFlexGroup,
EuiFlexItem,
EuiFormRow,
EuiLink,
EuiPageTemplate,
EuiSpacer,
EuiStat,
EuiText,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { useQuery } from '@tanstack/react-query';

import { IndexData, FetchIndicesResult } from '../../../common/types';
import { FETCH_INDICES_PATH } from '../routes';
import { API_KEY_PLACEHOLDER, ELASTICSEARCH_URL_PLACEHOLDER } from '../constants';
import { useKibanaServices } from '../hooks/use_kibana';
import { CodeBox } from './code_box';
import { javascriptDefinition } from './languages/javascript';
import { languageDefinitions } from './languages/languages';
import { LanguageDefinition } from './languages/types';
import { LanguageDefinition, LanguageDefinitionSnippetArguments } from './languages/types';

import { OverviewPanel } from './overview_panels/overview_panel';
import { LanguageClientPanel } from './overview_panels/language_client_panel';
Expand All @@ -48,9 +58,88 @@ const NoIndicesContent = () => (
</>
);

interface IndicesContentProps {
indices: IndexData[];
isLoading: boolean;
onChange: (selectedOptions: Array<EuiComboBoxOptionOption<IndexData>>) => void;
selectedIndex?: IndexData;
setSearchValue: (searchValue?: string) => void;
}
const IndicesContent = ({
indices,
isLoading,
onChange,
selectedIndex,
setSearchValue,
}: IndicesContentProps) => {
const toOption = (index: IndexData) => ({ label: index.name, value: index });
const options: Array<EuiComboBoxOptionOption<IndexData>> = indices.map(toOption);
return (
<>
<EuiSpacer />
<EuiFlexGroup>
<EuiFlexItem>
<EuiFormRow
fullWidth
label={i18n.translate(
'xpack.serverlessSearch.content.indexingApi.index.comboBox.title',
{ defaultMessage: 'Index' }
)}
>
<EuiComboBox
async
fullWidth
isLoading={isLoading}
singleSelection={{ asPlainText: true }}
onChange={onChange}
onSearchChange={setSearchValue}
options={options}
selectedOptions={selectedIndex ? [toOption(selectedIndex)] : undefined}
/>
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiStat
// TODO: format count based on locale
title={selectedIndex ? selectedIndex.count.toLocaleString() : '--'}
titleColor="primary"
description={i18n.translate(
'xpack.serverlessSearch.content.indexingApi.index.documentCount.description',
{ defaultMessage: 'Documents' }
)}
/>
</EuiFlexItem>
</EuiFlexGroup>
</>
);
};

export const ElasticsearchIndexingApi = () => {
const { cloud, http } = useKibanaServices();
const [selectedLanguage, setSelectedLanguage] =
useState<LanguageDefinition>(javascriptDefinition);
const [indexSearchQuery, setIndexSearchQuery] = useState<string | undefined>(undefined);
const [selectedIndex, setSelectedIndex] = useState<IndexData | undefined>(undefined);
const elasticsearchURL = useMemo(() => {
return cloud?.elasticsearchUrl ?? ELASTICSEARCH_URL_PLACEHOLDER;
}, [cloud]);
const { data, isLoading, isError } = useQuery({
queryKey: ['indices', { searchQuery: indexSearchQuery }],
queryFn: async () => {
const query = {
search_query: indexSearchQuery || null,
};
const result = await http.get<FetchIndicesResult>(FETCH_INDICES_PATH, { query });
return result;
},
});

const codeSnippetArguments: LanguageDefinitionSnippetArguments = {
url: elasticsearchURL,
apiKey: API_KEY_PLACEHOLDER,
indexName: selectedIndex?.name,
};
const showNoIndices = !isLoading && data?.indices?.length === 0 && indexSearchQuery === undefined;

return (
<EuiPageTemplate offset={0} grow restrictWidth data-test-subj="svlSearchIndexingApiPage">
Expand All @@ -67,6 +156,17 @@ export const ElasticsearchIndexingApi = () => {
)}
bottomBorder="extended"
/>
{isError && (
<EuiPageTemplate.Section>
<EuiCallOut
color="danger"
title={i18n.translate(
'xpack.serverlessSearch.content.indexingApi.fetchIndices.error.title',
{ defaultMessage: 'Error fetching indices' }
)}
/>
</EuiPageTemplate.Section>
)}
<EuiPageTemplate.Section color="subdued" bottomBorder="extended">
<OverviewPanel
title={i18n.translate('xpack.serverlessSearch.content.indexingApi.clientPanel.title', {
Expand Down Expand Up @@ -106,16 +206,41 @@ export const ElasticsearchIndexingApi = () => {
</EuiFlexGroup>
<EuiSpacer />
<CodeBox
code="ingestData"
codeArgs={{ url: '', apiKey: '' }}
code="ingestDataIndex"
codeArgs={codeSnippetArguments}
languages={languageDefinitions}
selectedLanguage={selectedLanguage}
setSelectedLanguage={setSelectedLanguage}
/>
</>
}
links={
showNoIndices
? undefined
: [
{
label: i18n.translate(
'xpack.serverlessSearch.content.indexingApi.ingestDocsLink',
{ defaultMessage: 'Ingestion documentation' }
),
href: '#', // TODO: get doc links ?
},
]
}
>
<NoIndicesContent />
{showNoIndices ? (
<NoIndicesContent />
) : (
<IndicesContent
isLoading={isLoading}
indices={data?.indices ?? []}
onChange={(options) => {
setSelectedIndex(options?.[0]?.value);
}}
setSearchValue={setIndexSearchQuery}
selectedIndex={selectedIndex}
/>
)}
</OverviewPanel>
</EuiPageTemplate.Section>
</EuiPageTemplate>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* 2.0.
*/

import { INDEX_NAME_PLACEHOLDER } from '../../constants';
import { LanguageDefinition } from './types';

export const consoleDefinition: Partial<LanguageDefinition> = {
Expand All @@ -29,4 +30,8 @@ export const consoleDefinition: Partial<LanguageDefinition> = {
{"name": "Brave New World", "author": "Aldous Huxley", "release_date": "1932-06-01", "page_count": 268}
{ "index" : { "_index" : "books" } }
{"name": "The Handmaid's Tale", "author": "Margaret Atwood", "release_date": "1985-06-01", "page_count": 311}`,
ingestDataIndex: ({ indexName }) => `POST _bulk?pretty
{ "index" : { "_index" : "${indexName ?? INDEX_NAME_PLACEHOLDER}" } }
{"name": "foo", "title": "bar"}
`,
};
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ export API_KEY="${apiKey}"`,
{ "index" : { "_index" : "books" } }
{"name": "The Handmaid'"'"'s Tale", "author": "Margaret Atwood", "release_date": "1985-06-01", "page_count": 311}
'`,
ingestDataIndex: ({ apiKey, url, indexName }) => `curl -X POST ${url}/_bulk?pretty \\
-H "Authorization: ApiKey ${apiKey}" \\
-H "Content-Type: application/json" \\
-d'
{ "index" : { "_index" : "${indexName ?? 'index_name'}" } }
{"name": "foo", "title": "bar" }
`,
installClient: `# if cURL is not already installed on your system
# then install it with the package manager of your choice
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,30 @@ bytes: 293,
aborted: false
}
*/`,
ingestDataIndex: ({
apiKey,
url,
indexName,
}) => `const { Client } = require('@elastic/elasticsearch');
const client = new Client({
node: '${url}',
auth: {
apiKey: '${apiKey}'
}
});
const dataset = [
{'name': 'foo', 'title': 'bar'},
];
// Index with the bulk helper
const result = await client.helpers.bulk({
datasource: dataset,
onDocument (doc) {
return { index: { _index: '${indexName ?? 'index_name'}' }};
}
});
console.log(result);
`,
installClient: 'npm install @elastic/elasticsearch@8',
name: i18n.translate('xpack.serverlessSearch.languages.javascript', {
defaultMessage: 'JavaScript / Node.js',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import { i18n } from '@kbn/i18n';
import { docLinks } from '../../../../common/doc_links';
import { INDEX_NAME_PLACEHOLDER } from '../../constants';
import { LanguageDefinition, Languages } from './types';

export const rubyDefinition: LanguageDefinition = {
Expand All @@ -31,6 +32,18 @@ export const rubyDefinition: LanguageDefinition = {
{ index: { _index: 'books', data: {name: "The Handmaid's Tale", "author": "Margaret Atwood", "release_date": "1985-06-01", "page_count": 311} } }
]
client.bulk(body: documents)`,
ingestDataIndex: ({ apiKey, url, indexName }) => `client = ElasticsearchServerless::Client.new(
api_key: '${apiKey}',
url: '${url}'
)
documents = [
{ index: { _index: '${
indexName ?? INDEX_NAME_PLACEHOLDER
}', data: {name: "foo", "title": "bar"} } },
]
client.bulk(body: documents)
`,
installClient: `# Requires Ruby version 3.0 or higher
# From the project's root directory:$ gem build elasticsearch-serverless.gemspec
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export enum Languages {
export interface LanguageDefinitionSnippetArguments {
url: string;
apiKey: string;
indexName?: string;
}

type CodeSnippet = string | ((args: LanguageDefinitionSnippetArguments) => string);
Expand All @@ -33,6 +34,7 @@ export interface LanguageDefinition {
iconType: string;
id: Languages;
ingestData: CodeSnippet;
ingestDataIndex: CodeSnippet;
installClient: string;
languageStyling?: string;
name: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import React, { useMemo, useState } from 'react';
import { docLinks } from '../../../common/doc_links';
import { PLUGIN_ID } from '../../../common';
import { useKibanaServices } from '../hooks/use_kibana';
import { API_KEY_PLACEHOLDER, ELASTICSEARCH_URL_PLACEHOLDER } from '../constants';
import { CodeBox } from './code_box';
import { javascriptDefinition } from './languages/javascript';
import { languageDefinitions } from './languages/languages';
Expand All @@ -35,9 +36,6 @@ import { SelectClientPanel } from './overview_panels/select_client';
import { ApiKeyPanel } from './api_key/api_key';
import { LanguageClientPanel } from './overview_panels/language_client_panel';

const ELASTICSEARCH_URL_PLACEHOLDER = 'https://your_deployment_url';
const API_KEY_PLACEHOLDER = 'your_api_key';

export const ElasticsearchOverview = () => {
const [selectedLanguage, setSelectedLanguage] =
useState<LanguageDefinition>(javascriptDefinition);
Expand Down
10 changes: 10 additions & 0 deletions x-pack/plugins/serverless_search/public/application/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* 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.
*/

export const API_KEY_PLACEHOLDER = 'your_api_key';
export const ELASTICSEARCH_URL_PLACEHOLDER = 'https://your_deployment_url';
export const INDEX_NAME_PLACEHOLDER = 'index_name';
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export const MANAGEMENT_API_KEYS = '/app/management/security/api_keys';

// Server Routes
export const CREATE_API_KEY_PATH = '/internal/security/api_key';
export const FETCH_INDICES_PATH = '/internal/serverless_search/indices';
Loading

0 comments on commit 0aa94ed

Please sign in to comment.