[Search] Add a search guide selector to index onboarding (#206810)

## Summary

This adds a guide selector to the Kibana index management onboarding
experience.

It also fixes a bug where useQuery was causing us to re-render the page
unnecessarily.

<img width="1284" alt="Screenshot 2025-01-15 at 16 11 48"
src="https://github.com/user-attachments/assets/19abe86f-3148-442a-8e1e-8b6b8eeb2ba1"
/>

### Checklist
Check the PR satisfies following conditions. 

Reviewers should verify this PR satisfies this list as well.

- [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/src/platform/packages/shared/kbn-i18n/README.md)
- [x] [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
- [x] The PR description includes the appropriate Release Notes section,
and the correct `release_note:*` label is applied per the
[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

---------

Co-authored-by: Rodney Norris <rodney@tattdcodemonkey.com>
This commit is contained in:
Sander Philipse 2025-01-17 19:30:50 +01:00 committed by GitHub
parent ba0aa3ff43
commit 63fc1eae9f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 663 additions and 235 deletions

View file

@ -11,12 +11,15 @@ export enum AnalyticsEvents {
startPageShowCreateIndexUIClick = 'start_page_show_create_index_ui',
startCreateIndexPageModifyIndexName = 'start_modify_index_name',
startCreateIndexClick = 'start_create_index',
startCreateIndexWorkflowSelect = 'start_workflow_select',
startCreateIndexLanguageSelect = 'start_code_lang_select',
startCreateIndexRunInConsole = 'start_cta_run_in_console',
startCreateIndexCodeCopyInstall = 'start_code_copy_install',
startCreateIndexCodeCopy = 'start_code_copy',
startCreateIndexCreatedRedirect = 'start_index_created_api',
startFileUploadClick = 'start_file_upload',
indexDetailsCodeLanguageSelect = 'index_details_code_lang_select',
indexDetailsWorkflowSelect = 'index_details_workflow_select',
indexDetailsInstallCodeCopy = 'index_details_code_copy_install',
indexDetailsAddMappingsCodeCopy = 'index_details_add_mappings_code_copy',
indexDetailsIngestDocumentsCodeCopy = 'index_details_ingest_documents_code_copy',
@ -29,6 +32,7 @@ export enum AnalyticsEvents {
createIndexPageModifyIndexName = 'create_index_modify_index_name',
createIndexCreateIndexClick = 'create_index_click_create',
createIndexLanguageSelect = 'create_index_code_lang_select',
createIndexWorkflowSelect = 'create_index_workflow_select',
createIndexRunInConsole = 'create_index_run_in_console',
createIndexCodeCopyInstall = 'create_index_copy_install',
createIndexCodeCopy = 'create_index_code_copy',

View file

@ -34,3 +34,31 @@ export const CONNECT_CREATE_VECTOR_INDEX_CMD_DESCRIPTION = i18n.translate(
defaultMessage: 'Use the Elasticsearch client to create an index with dense vector fields',
}
);
export const CONNECT_CREATE_DEFAULT_INDEX_CMD_TITLE = i18n.translate(
'xpack.searchIndices.code.createIndexCommand.title',
{
defaultMessage: 'Create an index with text fields',
}
);
export const CONNECT_CREATE_DEFAULT_INDEX_CMD_DESCRIPTION = i18n.translate(
'xpack.searchIndices.code.createIndexCommand.description',
{
defaultMessage: 'Use the Elasticsearch client to create an index with text fields',
}
);
export const CONNECT_CREATE_SEMANTIC_INDEX_CMD_TITLE = i18n.translate(
'xpack.searchIndices.code.createIndexCommand.title',
{
defaultMessage: 'Create an index with semantic fields',
}
);
export const CONNECT_CREATE_SEMANTIC_INDEX_CMD_DESCRIPTION = i18n.translate(
'xpack.searchIndices.code.createIndexCommand.description',
{
defaultMessage: 'Use the Elasticsearch client to create an index with semantic fields',
}
);

View file

@ -7,6 +7,10 @@
import { CreateIndexCodeExamples } from '../types';
import {
CONNECT_CREATE_DEFAULT_INDEX_CMD_DESCRIPTION,
CONNECT_CREATE_DEFAULT_INDEX_CMD_TITLE,
CONNECT_CREATE_SEMANTIC_INDEX_CMD_DESCRIPTION,
CONNECT_CREATE_SEMANTIC_INDEX_CMD_TITLE,
CONNECT_CREATE_VECTOR_INDEX_CMD_DESCRIPTION,
CONNECT_CREATE_VECTOR_INDEX_CMD_TITLE,
INSTALL_INSTRUCTIONS_DESCRIPTION,
@ -14,23 +18,23 @@ import {
} from './constants';
import { CurlCreateIndexExamples } from './curl';
import { JavascriptServerlessCreateIndexExamples } from './javascript';
import { PythonServerlessCreateIndexExamples } from './python';
import { JavascriptCreateIndexExamples } from './javascript';
import { PythonCreateIndexExamples } from './python';
import { ConsoleCreateIndexExamples } from './sense';
export const DefaultServerlessCodeExamples: CreateIndexCodeExamples = {
export const DefaultCodeExamples: CreateIndexCodeExamples = {
exampleType: 'search',
installTitle: INSTALL_INSTRUCTIONS_TITLE,
installDescription: INSTALL_INSTRUCTIONS_DESCRIPTION,
createIndexTitle: CONNECT_CREATE_VECTOR_INDEX_CMD_TITLE,
createIndexDescription: CONNECT_CREATE_VECTOR_INDEX_CMD_DESCRIPTION,
createIndexTitle: CONNECT_CREATE_DEFAULT_INDEX_CMD_TITLE,
createIndexDescription: CONNECT_CREATE_DEFAULT_INDEX_CMD_DESCRIPTION,
sense: ConsoleCreateIndexExamples.default,
curl: CurlCreateIndexExamples.default,
python: PythonServerlessCreateIndexExamples.default,
javascript: JavascriptServerlessCreateIndexExamples.default,
python: PythonCreateIndexExamples.default,
javascript: JavascriptCreateIndexExamples.default,
};
export const DenseVectorSeverlessCodeExamples: CreateIndexCodeExamples = {
export const DenseVectorCodeExamples: CreateIndexCodeExamples = {
exampleType: 'vector',
installTitle: INSTALL_INSTRUCTIONS_TITLE,
installDescription: INSTALL_INSTRUCTIONS_DESCRIPTION,
@ -38,6 +42,18 @@ export const DenseVectorSeverlessCodeExamples: CreateIndexCodeExamples = {
createIndexDescription: CONNECT_CREATE_VECTOR_INDEX_CMD_DESCRIPTION,
sense: ConsoleCreateIndexExamples.dense_vector,
curl: CurlCreateIndexExamples.dense_vector,
python: PythonServerlessCreateIndexExamples.dense_vector,
javascript: JavascriptServerlessCreateIndexExamples.dense_vector,
python: PythonCreateIndexExamples.dense_vector,
javascript: JavascriptCreateIndexExamples.dense_vector,
};
export const SemanticCodeExamples: CreateIndexCodeExamples = {
exampleType: 'semantic',
installTitle: INSTALL_INSTRUCTIONS_TITLE,
installDescription: INSTALL_INSTRUCTIONS_DESCRIPTION,
createIndexTitle: CONNECT_CREATE_SEMANTIC_INDEX_CMD_TITLE,
createIndexDescription: CONNECT_CREATE_SEMANTIC_INDEX_CMD_DESCRIPTION,
sense: ConsoleCreateIndexExamples.semantic,
curl: CurlCreateIndexExamples.semantic,
python: PythonCreateIndexExamples.semantic,
javascript: JavascriptCreateIndexExamples.semantic,
};

View file

@ -23,7 +23,16 @@ export const CurlCreateIndexExamples: CreateIndexLanguageExamples = {
indexName ?? INDEX_PLACEHOLDER
}' \
--header 'Authorization: ApiKey ${apiKey ?? API_KEY_PLACEHOLDER}' \
--header 'Content-Type: application/json'`,
--header 'Content-Type: application/json
--data-raw '{
"mappings": {
"properties":{
"text":{
"type":"text"
}
}
}
}'`,
},
dense_vector: {
createIndex: ({ elasticsearchURL, apiKey, indexName }) => `curl PUT '${elasticsearchURL}/${
@ -43,11 +52,27 @@ export const CurlCreateIndexExamples: CreateIndexLanguageExamples = {
}
}
}
}'`,
},
semantic: {
createIndex: ({ elasticsearchURL, apiKey, indexName }) => `curl PUT '${elasticsearchURL}/${
indexName ?? INDEX_PLACEHOLDER
}' \
--header 'Authorization: ApiKey ${apiKey ?? API_KEY_PLACEHOLDER}' \
--header 'Content-Type: application/json
--data-raw '{
"mappings": {
"properties":{
"text":{
"type":"semantic_text"
}
}
}
}'`,
},
};
export const CurlVectorsIngestDataExample: IngestDataCodeDefinition = {
export const CurlIngestDataExample: IngestDataCodeDefinition = {
ingestCommand: ({ elasticsearchURL, apiKey, indexName, sampleDocuments }) => {
let result = `curl -X POST "${elasticsearchURL}/_bulk?pretty" \
--header 'Authorization: ApiKey ${apiKey ?? API_KEY_PLACEHOLDER}' \

View file

@ -8,21 +8,43 @@
import { i18n } from '@kbn/i18n';
import { IngestDataCodeExamples } from '../types';
import { JSServerlessIngestVectorDataExample } from './javascript';
import { PythonServerlessVectorsIngestDataExample } from './python';
import { ConsoleVectorsIngestDataExample } from './sense';
import { CurlVectorsIngestDataExample } from './curl';
import { JSIngestDataExample } from './javascript';
import { PythonIngestDataExample } from './python';
import { ConsoleIngestDataExample } from './sense';
import { CurlIngestDataExample } from './curl';
import { INSTALL_INSTRUCTIONS_TITLE, INSTALL_INSTRUCTIONS_DESCRIPTION } from './constants';
export const DenseVectorServerlessCodeExamples: IngestDataCodeExamples = {
const addMappingsTitle = i18n.translate(
'xpack.searchIndices.codeExamples.serverless.denseVector.mappingsTitle',
{
defaultMessage: 'Define field mappings',
}
);
export const DefaultIngestDataCodeExamples: IngestDataCodeExamples = {
installTitle: INSTALL_INSTRUCTIONS_TITLE,
installDescription: INSTALL_INSTRUCTIONS_DESCRIPTION,
addMappingsTitle: i18n.translate(
'xpack.searchIndices.codeExamples.serverless.denseVector.mappingsTitle',
addMappingsTitle,
addMappingsDescription: i18n.translate(
'xpack.searchIndices.codeExamples.serverless.default.mappingsDescription',
{
defaultMessage: 'Define field mappings',
defaultMessage:
'This example defines one field: a text field that will provide full-text search capabilities. You can add more field types by modifying the mappings in your API call, or in the mappings tab.',
}
),
defaultMapping: {
text: { type: 'text' },
},
sense: ConsoleIngestDataExample,
curl: CurlIngestDataExample,
python: PythonIngestDataExample,
javascript: JSIngestDataExample,
};
export const DenseVectorIngestDataCodeExamples: IngestDataCodeExamples = {
installTitle: INSTALL_INSTRUCTIONS_TITLE,
installDescription: INSTALL_INSTRUCTIONS_DESCRIPTION,
addMappingsTitle,
addMappingsDescription: i18n.translate(
'xpack.searchIndices.codeExamples.serverless.denseVector.mappingsDescription',
{
@ -34,8 +56,29 @@ export const DenseVectorServerlessCodeExamples: IngestDataCodeExamples = {
vector: { type: 'dense_vector', dims: 3 },
text: { type: 'text' },
},
sense: ConsoleVectorsIngestDataExample,
curl: CurlVectorsIngestDataExample,
python: PythonServerlessVectorsIngestDataExample,
javascript: JSServerlessIngestVectorDataExample,
sense: ConsoleIngestDataExample,
curl: CurlIngestDataExample,
python: PythonIngestDataExample,
javascript: JSIngestDataExample,
};
export const SemanticIngestDataCodeExamples: IngestDataCodeExamples = {
installTitle: INSTALL_INSTRUCTIONS_TITLE,
installDescription: INSTALL_INSTRUCTIONS_DESCRIPTION,
addMappingsTitle,
addMappingsDescription: i18n.translate(
'xpack.searchIndices.codeExamples.serverless.denseVector.mappingsDescription',
{
defaultMessage:
'This example defines one field: a semantic text field that will provide vector search capabilities using the default ELSER model. You can add more field types by modifying the mappings in your API call, or in the mappings tab.',
}
),
defaultMapping: {
// @ts-expect-error - our types don't understand yet that we can have semantic_text fields without inference ids
text: { type: 'semantic_text' },
},
sense: ConsoleIngestDataExample,
curl: CurlIngestDataExample,
python: PythonIngestDataExample,
javascript: JSIngestDataExample,
};

View file

@ -19,11 +19,11 @@ export const JAVASCRIPT_INFO: CodeLanguage = {
codeBlockLanguage: 'javascript',
};
const SERVERLESS_INSTALL_CMD = `npm install @elastic/elasticsearch`;
const INSTALL_CMD = `npm install @elastic/elasticsearch`;
export const JavascriptServerlessCreateIndexExamples: CreateIndexLanguageExamples = {
export const JavascriptCreateIndexExamples: CreateIndexLanguageExamples = {
default: {
installCommand: SERVERLESS_INSTALL_CMD,
installCommand: INSTALL_CMD,
createIndex: ({
elasticsearchURL,
apiKey,
@ -39,10 +39,15 @@ const client = new Client({
client.indices.create({
index: "${indexName ?? INDEX_PLACEHOLDER}",
mappings: {
properties: {
text: { type: "text"}
},
},
});`,
},
dense_vector: {
installCommand: SERVERLESS_INSTALL_CMD,
installCommand: INSTALL_CMD,
createIndex: ({
elasticsearchURL,
apiKey,
@ -64,12 +69,36 @@ client.indices.create({
text: { type: "text"}
},
},
});`,
},
semantic: {
installCommand: INSTALL_CMD,
createIndex: ({
elasticsearchURL,
apiKey,
indexName,
}) => `import { Client } from "@elastic/elasticsearch"
const client = new Client({
node: '${elasticsearchURL}',
auth: {
apiKey: "${apiKey ?? API_KEY_PLACEHOLDER}"
}
});
client.indices.create({
index: "${indexName ?? INDEX_PLACEHOLDER}",
mappings: {
properties: {
text: { type: "semantic_text"}
},
},
});`,
},
};
export const JSServerlessIngestVectorDataExample: IngestDataCodeDefinition = {
installCommand: SERVERLESS_INSTALL_CMD,
export const JSIngestDataExample: IngestDataCodeDefinition = {
installCommand: INSTALL_CMD,
ingestCommand: ({
apiKey,
elasticsearchURL,

View file

@ -23,11 +23,11 @@ export const PYTHON_INFO: CodeLanguage = {
codeBlockLanguage: 'python',
};
const SERVERLESS_PYTHON_INSTALL_CMD = 'pip install elasticsearch';
const PYTHON_INSTALL_CMD = 'pip install elasticsearch';
export const PythonServerlessCreateIndexExamples: CreateIndexLanguageExamples = {
export const PythonCreateIndexExamples: CreateIndexLanguageExamples = {
default: {
installCommand: SERVERLESS_PYTHON_INSTALL_CMD,
installCommand: PYTHON_INSTALL_CMD,
createIndex: ({
elasticsearchURL,
apiKey,
@ -40,11 +40,16 @@ client = Elasticsearch(
)
client.indices.create(
index="${indexName ?? INDEX_PLACEHOLDER}"
index="${indexName ?? INDEX_PLACEHOLDER}",
mappings={
"properties": {
"text": {"type": "text"}
}
}
)`,
},
dense_vector: {
installCommand: SERVERLESS_PYTHON_INSTALL_CMD,
installCommand: PYTHON_INSTALL_CMD,
createIndex: ({
elasticsearchURL,
apiKey,
@ -64,10 +69,32 @@ client.indices.create(
"text": {"type": "text"}
}
}
)`,
},
semantic: {
installCommand: PYTHON_INSTALL_CMD,
createIndex: ({
elasticsearchURL,
apiKey,
indexName,
}: CodeSnippetParameters) => `from elasticsearch import Elasticsearch
client = Elasticsearch(
"${elasticsearchURL}",
api_key="${apiKey ?? API_KEY_PLACEHOLDER}"
)
client.indices.create(
index="${indexName ?? INDEX_PLACEHOLDER}",
mappings={
"properties": {
"text": {"type": "semantic_text"}
}
}
)`,
},
};
const serverlessIngestionCommand: IngestCodeSnippetFunction = ({
const ingestionCommand: IngestCodeSnippetFunction = ({
elasticsearchURL,
apiKey,
indexName,
@ -86,7 +113,7 @@ docs = ${JSON.stringify(sampleDocuments, null, 4)}
bulk_response = helpers.bulk(client, docs, index=index_name)
print(bulk_response)`;
const serverlessUpdateMappingsCommand: IngestCodeSnippetFunction = ({
const updateMappingsCommand: IngestCodeSnippetFunction = ({
elasticsearchURL,
apiKey,
indexName,
@ -106,8 +133,8 @@ mapping_response = client.indices.put_mapping(index=index_name, body=mappings)
print(mapping_response)
`;
export const PythonServerlessVectorsIngestDataExample: IngestDataCodeDefinition = {
installCommand: SERVERLESS_PYTHON_INSTALL_CMD,
ingestCommand: serverlessIngestionCommand,
updateMappingsCommand: serverlessUpdateMappingsCommand,
export const PythonIngestDataExample: IngestDataCodeDefinition = {
installCommand: PYTHON_INSTALL_CMD,
ingestCommand: ingestionCommand,
updateMappingsCommand,
};

View file

@ -11,7 +11,16 @@ import { CreateIndexLanguageExamples } from './types';
export const ConsoleCreateIndexExamples: CreateIndexLanguageExamples = {
default: {
createIndex: ({ indexName }) => `PUT /${indexName ?? INDEX_PLACEHOLDER}`,
createIndex: ({ indexName }) => `PUT /${indexName ?? INDEX_PLACEHOLDER}
{
"mappings": {
"properties":{
"text":{
"type":"text"
}
}
}
}`,
},
dense_vector: {
createIndex: ({ indexName }) => `PUT /${indexName ?? INDEX_PLACEHOLDER}
@ -27,11 +36,23 @@ export const ConsoleCreateIndexExamples: CreateIndexLanguageExamples = {
}
}
}
}`,
},
semantic: {
createIndex: ({ indexName }) => `PUT /${indexName ?? INDEX_PLACEHOLDER}
{
"mappings": {
"properties":{
"text":{
"type":"semantic_text"
}
}
}
}`,
},
};
export const ConsoleVectorsIngestDataExample: IngestDataCodeDefinition = {
export const ConsoleIngestDataExample: IngestDataCodeDefinition = {
ingestCommand: ({ indexName, sampleDocuments }) => {
let result = 'POST /_bulk?pretty\n';
sampleDocuments.forEach((document) => {

View file

@ -10,8 +10,11 @@ import { CreateIndexCodeDefinition, IngestDataCodeDefinition } from '../types';
export interface CreateIndexLanguageExamples {
default: CreateIndexCodeDefinition;
dense_vector: CreateIndexCodeDefinition;
semantic: CreateIndexCodeDefinition;
}
export interface IngestDataLanguageExamples {
default: IngestDataCodeDefinition;
dense_vector: IngestDataCodeDefinition;
semantic: IngestDataCodeDefinition;
}

View file

@ -0,0 +1,47 @@
/*
* 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 { i18n } from '@kbn/i18n';
export type WorkflowId = 'default' | 'vector' | 'semantic';
export interface Workflow {
title: string;
id: WorkflowId;
summary: string;
}
export const workflows: Workflow[] = [
{
title: i18n.translate('xpack.searchIndices.workflows.default', {
defaultMessage: 'Keyword Search',
}),
id: 'default',
summary: i18n.translate('xpack.searchIndices.workflows.defaultSummary', {
defaultMessage: 'Set up an index in Elasticsearch using the text field mapping.',
}),
},
{
title: i18n.translate('xpack.searchIndices.workflows.vector', {
defaultMessage: 'Vector Search',
}),
id: 'vector',
summary: i18n.translate('xpack.searchIndices.workflows.vectorSummary', {
defaultMessage: 'Set up an index in Elasticsearch using the dense_vector field mapping.',
}),
},
{
title: i18n.translate('xpack.searchIndices.workflows.semantic', {
defaultMessage: 'Semantic Search',
}),
id: 'semantic',
summary: i18n.translate('xpack.searchIndices.workflows.semanticSummary', {
defaultMessage:
"Semantic search in Elasticsearch is now simpler with the new semantic_text field type. This example walks through setting up your index with a semantic_text field, which uses Elastic's built-in ELSER machine learning model. If the model is not running, a new deployment will start once the mappings are defined.",
}),
},
];

View file

@ -22,6 +22,8 @@ import { CreateIndexPanel } from '../shared/create_index_panel';
import { CreateIndexCodeView } from './create_index_code_view';
import { CreateIndexUIView } from './create_index_ui_view';
import { WorkflowId } from '../../code_examples/workflows';
import { useWorkflow } from '../shared/hooks/use_workflow';
function initCreateIndexState() {
const defaultIndexName = generateRandomIndexName();
@ -50,6 +52,7 @@ export const CreateIndex = ({ indicesData }: CreateIndexProps) => {
? CreateIndexViewMode.Code
: CreateIndexViewMode.UI
);
const { workflow, setSelectedWorkflowId } = useWorkflow();
const usageTracker = useUsageTracker();
const onChangeView = useCallback(
(id: string) => {
@ -102,6 +105,14 @@ export const CreateIndex = ({ indicesData }: CreateIndexProps) => {
selectedLanguage={formState.codingLanguage}
indexName={formState.indexName}
changeCodingLanguage={onChangeCodingLanguage}
changeWorkflowId={(workflowId: WorkflowId) => {
setSelectedWorkflowId(workflowId);
usageTracker.click([
AnalyticsEvents.startCreateIndexWorkflowSelect,
`${AnalyticsEvents.startCreateIndexWorkflowSelect}_${workflowId}`,
]);
}}
selectedWorkflow={workflow}
canCreateApiKey={userPrivileges?.privileges.canCreateApiKeys}
analyticsEvents={{
runInConsole: AnalyticsEvents.createIndexRunInConsole,

View file

@ -7,11 +7,7 @@
import React from 'react';
import { render } from '@testing-library/react';
import {
AddDocumentsCodeExample,
basicExampleTexts,
exampleTextsWithCustomMapping,
} from './add_documents_code_example';
import { AddDocumentsCodeExample, exampleTexts } from './add_documents_code_example';
import { generateSampleDocument } from '../../utils/document_generation';
import { MappingProperty } from '@elastic/elasticsearch/lib/api/types';
@ -71,7 +67,7 @@ describe('AddDocumentsCodeExample', () => {
expect(generateSampleDocument).toHaveBeenCalledTimes(3);
basicExampleTexts.forEach((text, index) => {
exampleTexts.forEach((text, index) => {
expect(generateSampleDocument).toHaveBeenNthCalledWith(index + 1, mappingProperties, text);
});
});
@ -84,16 +80,15 @@ describe('AddDocumentsCodeExample', () => {
expect(generateSampleDocument).toHaveBeenCalledTimes(3);
const mappingProperties: Record<string, MappingProperty> = {
vector: { type: 'dense_vector', dims: 3 },
text: { type: 'text' },
};
basicExampleTexts.forEach((text, index) => {
exampleTexts.forEach((text, index) => {
expect(generateSampleDocument).toHaveBeenNthCalledWith(index + 1, mappingProperties, text);
});
});
it('pass basic examples when mapping is default with extra vector fields', () => {
it('pass examples when mapping is default with extra vector fields', () => {
const indexName = 'test-index';
const mappingProperties: Record<string, MappingProperty> = {
vector: { type: 'dense_vector', dims: 3, similarity: 'extra' },
@ -106,25 +101,7 @@ describe('AddDocumentsCodeExample', () => {
expect(generateSampleDocument).toHaveBeenCalledTimes(3);
basicExampleTexts.forEach((text, index) => {
expect(generateSampleDocument).toHaveBeenNthCalledWith(index + 1, mappingProperties, text);
});
});
it('pass examples text when mapping is custom', () => {
const indexName = 'test-index';
const mappingProperties: Record<string, MappingProperty> = {
text: { type: 'text' },
test: { type: 'boolean' },
};
render(
<AddDocumentsCodeExample indexName={indexName} mappingProperties={mappingProperties} />
);
expect(generateSampleDocument).toHaveBeenCalledTimes(3);
exampleTextsWithCustomMapping.forEach((text, index) => {
exampleTexts.forEach((text, index) => {
expect(generateSampleDocument).toHaveBeenNthCalledWith(index + 1, mappingProperties, text);
});
});

View file

@ -6,17 +6,15 @@
*/
import React, { useCallback, useMemo, useState } from 'react';
import { MappingDenseVectorProperty, MappingProperty } from '@elastic/elasticsearch/lib/api/types';
import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui';
import { MappingProperty } from '@elastic/elasticsearch/lib/api/types';
import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { TryInConsoleButton } from '@kbn/try-in-console';
import { isEqual } from 'lodash';
import { useSearchApiKey } from '@kbn/search-api-keys-components';
import { useKibana } from '../../hooks/use_kibana';
import { IngestCodeSnippetParameters } from '../../types';
import { LanguageSelector } from '../shared/language_selector';
import { useIngestCodeExamples } from './hooks/use_ingest_code_examples';
import { useElasticsearchUrl } from '../../hooks/use_elasticsearch_url';
import { useUsageTracker } from '../../contexts/usage_tracker_context';
import { AvailableLanguages, LanguageOptions, Languages } from '../../code_examples';
@ -24,13 +22,15 @@ import { AnalyticsEvents } from '../../analytics/constants';
import { CodeSample } from '../shared/code_sample';
import { generateSampleDocument } from '../../utils/document_generation';
import { getDefaultCodingLanguage } from '../../utils/language';
import { GuideSelector } from '../shared/guide_selector';
import { useWorkflow } from '../shared/hooks/use_workflow';
import { WorkflowId } from '../../code_examples/workflows';
export const basicExampleTexts = [
'Yellowstone National Park',
'Yosemite National Park',
'Rocky Mountain National Park',
export const exampleTexts = [
'Yellowstone National Park is one of the largest national parks in the United States. It ranges from the Wyoming to Montana and Idaho, and contains an area of 2,219,791 acress across three different states. Its most famous for hosting the geyser Old Faithful and is centered on the Yellowstone Caldera, the largest super volcano on the American continent. Yellowstone is host to hundreds of species of animal, many of which are endangered or threatened. Most notably, it contains free-ranging herds of bison and elk, alongside bears, cougars and wolves. The national park receives over 4.5 million visitors annually and is a UNESCO World Heritage Site.',
'Yosemite National Park is a United States National Park, covering over 750,000 acres of land in California. A UNESCO World Heritage Site, the park is best known for its granite cliffs, waterfalls and giant sequoia trees. Yosemite hosts over four million visitors in most years, with a peak of five million visitors in 2016. The park is home to a diverse range of wildlife, including mule deer, black bears, and the endangered Sierra Nevada bighorn sheep. The park has 1,200 square miles of wilderness, and is a popular destination for rock climbers, with over 3,000 feet of vertical granite to climb. Its most famous and cliff is the El Capitan, a 3,000 feet monolith along its tallest face.',
'Rocky Mountain National Park is one of the most popular national parks in the United States. It receives over 4.5 million visitors annually, and is known for its mountainous terrain, including Longs Peak, which is the highest peak in the park. The park is home to a variety of wildlife, including elk, mule deer, moose, and bighorn sheep. The park is also home to a variety of ecosystems, including montane, subalpine, and alpine tundra. The park is a popular destination for hiking, camping, and wildlife viewing, and is a UNESCO World Heritage Site.',
];
export const exampleTextsWithCustomMapping = [1, 2, 3].map((num) => `Example text ${num}`);
export interface AddDocumentsCodeExampleProps {
indexName: string;
@ -42,41 +42,28 @@ export const AddDocumentsCodeExample = ({
mappingProperties,
}: AddDocumentsCodeExampleProps) => {
const { application, share, console: consolePlugin } = useKibana().services;
const ingestCodeExamples = useIngestCodeExamples();
const elasticsearchUrl = useElasticsearchUrl();
const usageTracker = useUsageTracker();
const indexHasMappings = Object.keys(mappingProperties).length > 0;
const [selectedLanguage, setSelectedLanguage] =
useState<AvailableLanguages>(getDefaultCodingLanguage);
const selectedCodeExamples = ingestCodeExamples[selectedLanguage];
const codeSampleMappings = indexHasMappings
? mappingProperties
: ingestCodeExamples.defaultMapping;
const { selectedWorkflowId, setSelectedWorkflowId, ingestExamples, workflow } = useWorkflow();
const selectedCodeExamples = ingestExamples[selectedLanguage];
const codeSampleMappings = indexHasMappings ? mappingProperties : ingestExamples.defaultMapping;
const onSelectLanguage = useCallback(
(value: AvailableLanguages) => {
setSelectedLanguage(value);
usageTracker.count([
AnalyticsEvents.startCreateIndexLanguageSelect,
`${AnalyticsEvents.startCreateIndexLanguageSelect}_${value}`,
AnalyticsEvents.indexDetailsCodeLanguageSelect,
`${AnalyticsEvents.indexDetailsCodeLanguageSelect}_${value}`,
]);
},
[usageTracker]
);
const sampleDocuments = useMemo(() => {
// If the default mapping was used, we need to exclude generated vector fields
const copyCodeSampleMappings = {
...codeSampleMappings,
vector: {
type: codeSampleMappings.vector?.type,
dims: (codeSampleMappings.vector as MappingDenseVectorProperty)?.dims,
},
};
const isDefaultMapping = isEqual(copyCodeSampleMappings, ingestCodeExamples.defaultMapping);
const sampleTexts = isDefaultMapping ? basicExampleTexts : exampleTextsWithCustomMapping;
return sampleTexts.map((text) => generateSampleDocument(codeSampleMappings, text));
}, [codeSampleMappings, ingestCodeExamples.defaultMapping]);
return exampleTexts.map((text) => generateSampleDocument(codeSampleMappings, text));
}, [codeSampleMappings]);
const { apiKey } = useSearchApiKey();
const codeParams: IngestCodeSnippetParameters = useMemo(() => {
return {
@ -88,6 +75,7 @@ export const AddDocumentsCodeExample = ({
apiKey: apiKey || undefined,
};
}, [indexName, elasticsearchUrl, sampleDocuments, codeSampleMappings, indexHasMappings, apiKey]);
const [panelRef, setPanelRef] = useState<HTMLDivElement | null>(null);
return (
<EuiPanel
@ -95,24 +83,34 @@ export const AddDocumentsCodeExample = ({
hasShadow={false}
paddingSize="m"
data-test-subj="SearchIndicesAddDocumentsCode"
panelRef={setPanelRef}
>
<EuiFlexGroup direction="column">
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
<EuiFlexItem css={{ maxWidth: '300px' }}>
<LanguageSelector
options={LanguageOptions}
selectedLanguage={selectedLanguage}
onSelectLanguage={onSelectLanguage}
/>
</EuiFlexItem>
{!indexHasMappings && (
<EuiFlexItem css={{ maxWidth: '300px' }} grow={false}>
<GuideSelector
selectedWorkflowId={selectedWorkflowId}
onChange={(workflowId: WorkflowId) => {
setSelectedWorkflowId(workflowId);
usageTracker.click([
AnalyticsEvents.indexDetailsCodeLanguageSelect,
`${AnalyticsEvents.indexDetailsCodeLanguageSelect}_${workflowId}`,
]);
}}
showTour
container={panelRef}
/>
</EuiFlexItem>
)}
<EuiFlexItem grow={false}>
<TryInConsoleButton
request={
!indexHasMappings
? `${ingestCodeExamples.sense.updateMappingsCommand(
? `${ingestExamples.sense.updateMappingsCommand(
codeParams
)}\n\n${ingestCodeExamples.sense.ingestCommand(codeParams)}`
: ingestCodeExamples.sense.ingestCommand(codeParams)
)}\n\n${ingestExamples.sense.ingestCommand(codeParams)}`
: ingestExamples.sense.ingestCommand(codeParams)
}
application={application}
sharePlugin={share}
@ -120,12 +118,31 @@ export const AddDocumentsCodeExample = ({
/>
</EuiFlexItem>
</EuiFlexGroup>
{!!workflow && (
<EuiFlexItem>
<EuiTitle>
<h3>{workflow.title}</h3>
</EuiTitle>
<EuiSpacer size="s" />
<EuiText>
<p>{workflow.summary}</p>
</EuiText>
</EuiFlexItem>
)}
<EuiFlexItem css={{ maxWidth: '300px' }} grow={false}>
<LanguageSelector
options={LanguageOptions}
selectedLanguage={selectedLanguage}
onSelectLanguage={onSelectLanguage}
showLabel
/>
</EuiFlexItem>
{selectedCodeExamples.installCommand && (
<EuiFlexItem>
<CodeSample
id="installCodeExample"
title={ingestCodeExamples.installTitle}
description={ingestCodeExamples.installDescription}
title={ingestExamples.installTitle}
description={ingestExamples.installDescription}
language="shell"
code={selectedCodeExamples.installCommand}
onCodeCopyClick={() => {
@ -141,8 +158,8 @@ export const AddDocumentsCodeExample = ({
<EuiFlexItem>
<CodeSample
id="addMappingsCodeExample"
title={ingestCodeExamples.addMappingsTitle}
description={ingestCodeExamples.addMappingsDescription}
title={ingestExamples.addMappingsTitle}
description={ingestExamples.addMappingsDescription}
language={Languages[selectedLanguage].codeBlockLanguage}
code={selectedCodeExamples.updateMappingsCommand(codeParams)}
onCodeCopyClick={() => {

View file

@ -1,13 +0,0 @@
/*
* 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 * as IngestCodeExamples from '../../../code_examples/ingest_data';
export const useIngestCodeExamples = () => {
// TODO: Choose code examples based on onboarding token, stack vs es3, or project type
return IngestCodeExamples.DenseVectorServerlessCodeExamples;
};

View file

@ -79,12 +79,7 @@ export const SearchIndexDetailsPage = () => {
}, [share, index]);
const navigateToDiscover = useNavigateToDiscover(indexName);
const [hasDocuments, setHasDocuments] = useState<boolean>(false);
const [isDocumentsLoading, setDocumentsLoading] = useState<boolean>(true);
useEffect(() => {
setDocumentsLoading(isInitialLoading);
setHasDocuments(!(!isInitialLoading && indexDocuments?.results?.data.length === 0));
}, [indexDocuments, isInitialLoading, setHasDocuments, setDocumentsLoading]);
const hasDocuments = Boolean(isInitialLoading || indexDocuments?.results?.data.length);
usePageChrome(indexName, [
...IndexManagementBreadcrumbs,
@ -225,7 +220,7 @@ export const SearchIndexDetailsPage = () => {
<>
<EuiFlexItem>
<EuiButtonEmpty
isLoading={isDocumentsLoading}
isLoading={isInitialLoading}
data-test-subj="viewInDiscoverLink"
onClick={navigateToDiscover}
>
@ -237,7 +232,7 @@ export const SearchIndexDetailsPage = () => {
</EuiFlexItem>
<EuiFlexItem>
<EuiButton
isLoading={isDocumentsLoading}
isLoading={isInitialLoading}
data-test-subj="useInPlaygroundLink"
onClick={navigateToPlayground}
iconType="launch"
@ -255,7 +250,7 @@ export const SearchIndexDetailsPage = () => {
<EuiButtonEmpty
href={docLinks.links.apiReference}
target="_blank"
isLoading={isDocumentsLoading}
isLoading={isInitialLoading}
iconType="documentation"
data-test-subj="ApiReferenceDoc"
>

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import React, { useMemo } from 'react';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui';
import { TryInConsoleButton } from '@kbn/try-in-console';
import { useSearchApiKey } from '@kbn/search-api-keys-components';
@ -17,13 +17,17 @@ import { useElasticsearchUrl } from '../../hooks/use_elasticsearch_url';
import { APIKeyCallout } from './api_key_callout';
import { CodeSample } from './code_sample';
import { useCreateIndexCodingExamples } from './hooks/use_create_index_coding_examples';
import { useWorkflow } from './hooks/use_workflow';
import { LanguageSelector } from './language_selector';
import { GuideSelector } from './guide_selector';
import { Workflow, WorkflowId } from '../../code_examples/workflows';
export interface CreateIndexCodeViewProps {
selectedLanguage: AvailableLanguages;
indexName: string;
changeCodingLanguage: (language: AvailableLanguages) => void;
changeWorkflowId: (workflowId: WorkflowId) => void;
selectedWorkflow?: Workflow;
canCreateApiKey?: boolean;
analyticsEvents: {
runInConsole: string;
@ -36,12 +40,14 @@ export const CreateIndexCodeView = ({
analyticsEvents,
canCreateApiKey,
changeCodingLanguage,
changeWorkflowId,
selectedWorkflow,
indexName,
selectedLanguage,
}: CreateIndexCodeViewProps) => {
const { application, share, console: consolePlugin } = useKibana().services;
const usageTracker = useUsageTracker();
const selectedCodeExamples = useCreateIndexCodingExamples();
const { createIndexExamples: selectedCodeExamples } = useWorkflow();
const elasticsearchUrl = useElasticsearchUrl();
const { apiKey } = useSearchApiKey();
@ -64,30 +70,52 @@ export const CreateIndexCodeView = ({
<APIKeyCallout apiKey={apiKey} />
</EuiFlexItem>
)}
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
<EuiFlexItem css={{ maxWidth: '300px' }}>
<LanguageSelector
options={LanguageOptions}
selectedLanguage={selectedLanguage}
onSelectLanguage={changeCodingLanguage}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<TryInConsoleButton
request={selectedCodeExamples.sense.createIndex(codeParams)}
application={application}
sharePlugin={share}
consolePlugin={consolePlugin}
telemetryId={`${selectedLanguage}_create_index`}
onClick={() => {
usageTracker.click([
analyticsEvents.runInConsole,
`${analyticsEvents.runInConsole}_${selectedLanguage}`,
]);
}}
/>
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexItem>
<EuiFlexGroup justifyContent="spaceBetween" alignItems="flexStart">
<EuiFlexItem grow={false}>
<GuideSelector
selectedWorkflowId={selectedWorkflow?.id || 'default'}
onChange={changeWorkflowId}
showTour={false}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<TryInConsoleButton
request={selectedCodeExamples.sense.createIndex(codeParams)}
application={application}
sharePlugin={share}
consolePlugin={consolePlugin}
telemetryId={`${selectedLanguage}_create_index`}
onClick={() => {
usageTracker.click([
analyticsEvents.runInConsole,
`${analyticsEvents.runInConsole}_${selectedLanguage}`,
]);
}}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
{!!selectedWorkflow && (
<>
<EuiFlexItem>
<EuiTitle size="xs">
<h4>{selectedWorkflow?.title}</h4>
</EuiTitle>
<EuiSpacer size="s" />
<EuiText color="subdued" size="s">
<p>{selectedWorkflow?.summary}</p>
</EuiText>
</EuiFlexItem>
</>
)}
<EuiFlexItem css={{ maxWidth: '300px' }}>
<LanguageSelector
options={LanguageOptions}
selectedLanguage={selectedLanguage}
onSelectLanguage={changeCodingLanguage}
/>
</EuiFlexItem>
{selectedCodeExample.installCommand && (
<CodeSample
title={selectedCodeExamples.installTitle}

View file

@ -0,0 +1,137 @@
/*
* 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, { useState, useEffect } from 'react';
import {
EuiButton,
EuiCard,
EuiText,
EuiFlexGroup,
EuiFlexItem,
EuiPopover,
EuiTourStep,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { Workflow, WorkflowId, workflows } from '../../code_examples/workflows';
import { useGuideTour } from './hooks/use_guide_tour';
interface PopoverCardProps {
workflow: Workflow;
isSelected: boolean;
onChange: (workflowId: WorkflowId) => void;
}
const PopoverCard: React.FC<PopoverCardProps> = ({ workflow, onChange, isSelected }) => (
<EuiCard
title={workflow.title}
hasBorder
titleSize="xs"
description={
<EuiText color="subdued" size="s">
{workflow.summary}
</EuiText>
}
selectable={{
onClick: () => onChange(workflow.id),
isSelected,
}}
/>
);
interface GuideSelectorProps {
selectedWorkflowId: WorkflowId;
onChange: (workflow: WorkflowId) => void;
showTour: boolean;
container?: HTMLElement | null;
}
export const GuideSelector: React.FC<GuideSelectorProps> = ({
selectedWorkflowId,
onChange,
showTour,
container,
}) => {
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const { tourIsOpen, setTourIsOpen } = useGuideTour();
const onPopoverClick = () => {
setIsPopoverOpen(() => !isPopoverOpen);
};
useEffect(() => {
closePopover();
}, [selectedWorkflowId]);
const closePopover = () => setIsPopoverOpen(false);
const PopoverButton = (
<EuiButton
color="text"
iconType="arrowDown"
iconSide="right"
onClick={onPopoverClick}
data-test-subj="workflowSelectorButton"
>
{i18n.translate('xpack.searchIndices.guideSelector.selectWorkflow', {
defaultMessage: 'Select a guide',
})}
</EuiButton>
);
const Popover = () => (
<EuiPopover
anchorPosition="downRight"
button={PopoverButton}
isOpen={isPopoverOpen}
closePopover={closePopover}
title="Select a workflow"
container={container || undefined}
>
<>
<EuiFlexGroup gutterSize="m" style={{ maxWidth: '960px' }}>
{workflows.map((workflow) => (
<EuiFlexItem key={workflow.id}>
<PopoverCard
workflow={workflow}
isSelected={workflow.id === selectedWorkflowId}
onChange={(value) => onChange(value)}
/>
</EuiFlexItem>
))}
</EuiFlexGroup>
</>
</EuiPopover>
);
return showTour ? (
<EuiTourStep
content={
<EuiText>
<p>
{i18n.translate('xpack.searchIndices.tourDescription', {
defaultMessage: 'Explore additional guides for setting up your Elasticsearch index.',
})}
</p>
</EuiText>
}
isStepOpen={tourIsOpen}
minWidth={300}
onFinish={() => setTourIsOpen(false)}
step={1}
stepsTotal={1}
title={i18n.translate('xpack.searchIndices.touTitle', {
defaultMessage: 'New guides available!',
})}
anchorPosition="rightUp"
>
<Popover />
</EuiTourStep>
) : (
<Popover />
);
};

View file

@ -1,15 +0,0 @@
/*
* 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 { CreateIndexCodeExamples } from '../../../types';
import { DenseVectorSeverlessCodeExamples } from '../../../code_examples/create_index';
export const useCreateIndexCodingExamples = (): CreateIndexCodeExamples => {
// TODO: in the future this will be dynamic based on the onboarding token
// or project sub-type
return DenseVectorSeverlessCodeExamples;
};

View file

@ -0,0 +1,22 @@
/*
* 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 { useState } from 'react';
const GUIDE_TOUR_KEY = 'searchIndicesIngestDataGuideTour';
export const useGuideTour = () => {
const hasDismissedGuide = localStorage.getItem(GUIDE_TOUR_KEY) === 'dismissed';
const [tourIsOpen, setTourIsOpen] = useState(!hasDismissedGuide);
return {
tourIsOpen,
setTourIsOpen: (isOpen: boolean) => {
setTourIsOpen(isOpen);
localStorage.setItem(GUIDE_TOUR_KEY, isOpen ? '' : 'dismissed');
},
};
};

View file

@ -0,0 +1,54 @@
/*
* 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 { useState } from 'react';
import {
DenseVectorIngestDataCodeExamples,
SemanticIngestDataCodeExamples,
DefaultIngestDataCodeExamples,
} from '../../../code_examples/ingest_data';
import { WorkflowId, workflows } from '../../../code_examples/workflows';
import {
DefaultCodeExamples,
DenseVectorCodeExamples,
SemanticCodeExamples,
} from '../../../code_examples/create_index';
const workflowIdToCreateIndexExamples = (type: WorkflowId) => {
switch (type) {
case 'vector':
return DenseVectorCodeExamples;
case 'semantic':
return SemanticCodeExamples;
default:
return DefaultCodeExamples;
}
};
const workflowIdToIngestDataExamples = (type: WorkflowId) => {
switch (type) {
case 'vector':
return DenseVectorIngestDataCodeExamples;
case 'semantic':
return SemanticIngestDataCodeExamples;
default:
return DefaultIngestDataCodeExamples;
}
};
export const useWorkflow = () => {
// TODO: in the future this will be dynamic based on the onboarding token
// or project sub-type
const [selectedWorkflowId, setSelectedWorkflowId] = useState<WorkflowId>('default');
return {
selectedWorkflowId,
setSelectedWorkflowId,
workflow: workflows.find((workflow) => workflow.id === selectedWorkflowId),
createIndexExamples: workflowIdToCreateIndexExamples(selectedWorkflowId),
ingestExamples: workflowIdToIngestDataExamples(selectedWorkflowId),
};
};

View file

@ -17,12 +17,14 @@ export interface LanguageSelectorProps {
selectedLanguage: AvailableLanguages;
options: CodeLanguage[];
onSelectLanguage: (value: AvailableLanguages) => void;
showLabel?: boolean;
}
export const LanguageSelector = ({
selectedLanguage,
options,
onSelectLanguage,
showLabel = false,
}: LanguageSelectorProps) => {
const assetBasePath = useAssetBasePath();
const languageOptions = useMemo(
@ -48,6 +50,16 @@ export const LanguageSelector = ({
);
return (
<EuiSuperSelect<AvailableLanguages>
prepend={
showLabel
? i18n.translate('xpack.searchIndices.codeLanguage.selectLabel', {
defaultMessage: 'Language',
})
: undefined
}
aria-label={i18n.translate('xpack.searchIndices.codeLanguage.selectLabel', {
defaultMessage: 'Select a programming language for the code examples',
})}
options={languageOptions}
valueOfSelected={selectedLanguage}
onChange={(value) => onSelectLanguage(value)}

View file

@ -23,6 +23,8 @@ import { CreateIndexFormState, CreateIndexViewMode } from '../../types';
import { CreateIndexPanel } from '../shared/create_index_panel';
import { useKibana } from '../../hooks/use_kibana';
import { useUserPrivilegesQuery } from '../../hooks/api/use_user_permissions';
import { WorkflowId } from '../../code_examples/workflows';
import { useWorkflow } from '../shared/hooks/use_workflow';
function initCreateIndexState(): CreateIndexFormState {
const defaultIndexName = generateRandomIndexName();
@ -48,6 +50,7 @@ export const ElasticsearchStart: React.FC<ElasticsearchStartProps> = () => {
: CreateIndexViewMode.UI
);
const usageTracker = useUsageTracker();
const { workflow, setSelectedWorkflowId } = useWorkflow();
useEffect(() => {
usageTracker.load(AnalyticsEvents.startPageOpened);
@ -114,6 +117,14 @@ export const ElasticsearchStart: React.FC<ElasticsearchStartProps> = () => {
selectedLanguage={formState.codingLanguage}
indexName={formState.indexName}
changeCodingLanguage={onChangeCodingLanguage}
changeWorkflowId={(workflowId: WorkflowId) => {
setSelectedWorkflowId(workflowId);
usageTracker.click([
AnalyticsEvents.startCreateIndexWorkflowSelect,
`${AnalyticsEvents.startCreateIndexWorkflowSelect}_${workflowId}`,
]);
}}
selectedWorkflow={workflow}
canCreateApiKey={userPrivileges?.privileges.canCreateApiKeys}
analyticsEvents={{
runInConsole: AnalyticsEvents.startCreateIndexRunInConsole,

View file

@ -27,7 +27,7 @@ export const useIndexDocumentSearch = (indexName: string) => {
const {
services: { http },
} = useKibana();
const response = useQuery({
const { data, isInitialLoading } = useQuery({
queryKey: [QueryKeys.SearchDocuments, indexName],
refetchInterval: INDEX_SEARCH_POLLING,
refetchIntervalInBackground: true,
@ -46,7 +46,8 @@ export const useIndexDocumentSearch = (indexName: string) => {
}),
});
return {
...response,
meta: pageToPagination(response?.data?.results?._meta?.page ?? DEFAULT_PAGINATION),
data,
isInitialLoading,
meta: pageToPagination(data?.results?._meta?.page ?? DEFAULT_PAGINATION),
};
};

View file

@ -14,7 +14,7 @@ const POLLING_INTERVAL = 15 * 1000;
export const useIndex = (indexName: string) => {
const { http } = useKibana().services;
const queryKey = [QueryKeys.FetchIndex, indexName];
const result = useQuery<Index, { body: { statusCode: number; message: string; error: string } }>({
return useQuery<Index, { body: { statusCode: number; message: string; error: string } }>({
queryKey,
refetchInterval: POLLING_INTERVAL,
refetchIntervalInBackground: true,
@ -25,5 +25,4 @@ export const useIndex = (indexName: string) => {
queryFn: () =>
http.fetch<Index>(`/internal/index_management/indices/${encodeURIComponent(indexName)}`),
});
return { queryKey, ...result };
};

View file

@ -14,7 +14,7 @@ const POLLING_INTERVAL = 15 * 1000;
export const useIndexMapping = (indexName: string) => {
const { http } = useKibana().services;
const queryKey = [QueryKeys.FetchMapping, indexName];
const result = useQuery<Mappings, { body: { message: string; error: string } }>({
return useQuery<Mappings, { body: { message: string; error: string } }>({
queryKey,
refetchInterval: POLLING_INTERVAL,
refetchIntervalInBackground: true,
@ -22,5 +22,4 @@ export const useIndexMapping = (indexName: string) => {
queryFn: () =>
http.fetch<Mappings>(`/api/index_management/mapping/${encodeURIComponent(indexName)}`),
});
return { queryKey, ...result };
};

View file

@ -233,20 +233,6 @@ export function SearchIndexDetailPageProvider({ getService }: FtrProviderContext
);
},
async expectSampleDocumentsWithCustomMappings() {
await browser.refresh();
await testSubjects.existOrFail('ingestDataCodeExample-code-block');
expect(await testSubjects.getVisibleText('ingestDataCodeExample-code-block')).to.contain(
'Example text 1'
);
expect(await testSubjects.getVisibleText('ingestDataCodeExample-code-block')).to.contain(
'Example text 2'
);
expect(await testSubjects.getVisibleText('ingestDataCodeExample-code-block')).to.contain(
'Example text 3'
);
},
async clickFirstDocumentDeleteAction() {
await testSubjects.existOrFail('documentMetadataButton');
await testSubjects.click('documentMetadataButton');

View file

@ -88,17 +88,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
it('should have basic example texts', async () => {
await pageObjects.searchIndexDetailsPage.expectHasSampleDocuments();
});
it('should have other example texts when mapping changed', async () => {
await es.indices.putMapping({
index: indexNameCodeExample,
properties: {
text: { type: 'text' },
number: { type: 'integer' },
},
});
await pageObjects.searchIndexDetailsPage.expectSampleDocumentsWithCustomMappings();
});
});
describe('API key details', () => {

View file

@ -221,20 +221,6 @@ export function SvlSearchIndexDetailPageProvider({ getService }: FtrProviderCont
);
},
async expectSampleDocumentsWithCustomMappings() {
await browser.refresh();
await testSubjects.existOrFail('ingestDataCodeExample-code-block');
expect(await testSubjects.getVisibleText('ingestDataCodeExample-code-block')).to.contain(
'Example text 1'
);
expect(await testSubjects.getVisibleText('ingestDataCodeExample-code-block')).to.contain(
'Example text 2'
);
expect(await testSubjects.getVisibleText('ingestDataCodeExample-code-block')).to.contain(
'Example text 3'
);
},
async clickFirstDocumentDeleteAction() {
await testSubjects.existOrFail('documentMetadataButton');
await testSubjects.click('documentMetadataButton');

View file

@ -70,17 +70,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
it('should have basic example texts', async () => {
await pageObjects.svlSearchIndexDetailPage.expectHasSampleDocuments();
});
it('should have other example texts when mapping changed', async () => {
await es.indices.putMapping({
index: indexNameCodeExample,
properties: {
text: { type: 'text' },
number: { type: 'integer' },
},
});
await pageObjects.svlSearchIndexDetailPage.expectSampleDocumentsWithCustomMappings();
});
});
describe('API key details', () => {