diff --git a/x-pack/solutions/search/plugins/search_indices/public/analytics/constants.ts b/x-pack/solutions/search/plugins/search_indices/public/analytics/constants.ts index 0da7aedf19328..fe7c19eeb708c 100644 --- a/x-pack/solutions/search/plugins/search_indices/public/analytics/constants.ts +++ b/x-pack/solutions/search/plugins/search_indices/public/analytics/constants.ts @@ -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', diff --git a/x-pack/solutions/search/plugins/search_indices/public/code_examples/constants.ts b/x-pack/solutions/search/plugins/search_indices/public/code_examples/constants.ts index 3ba23d0fae222..aca4c4c5e4bb2 100644 --- a/x-pack/solutions/search/plugins/search_indices/public/code_examples/constants.ts +++ b/x-pack/solutions/search/plugins/search_indices/public/code_examples/constants.ts @@ -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', + } +); diff --git a/x-pack/solutions/search/plugins/search_indices/public/code_examples/create_index.ts b/x-pack/solutions/search/plugins/search_indices/public/code_examples/create_index.ts index d462c2310ab4c..d77ac7ce49416 100644 --- a/x-pack/solutions/search/plugins/search_indices/public/code_examples/create_index.ts +++ b/x-pack/solutions/search/plugins/search_indices/public/code_examples/create_index.ts @@ -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, }; diff --git a/x-pack/solutions/search/plugins/search_indices/public/code_examples/curl.ts b/x-pack/solutions/search/plugins/search_indices/public/code_examples/curl.ts index a73d5a7cfe617..23fe480f216dc 100644 --- a/x-pack/solutions/search/plugins/search_indices/public/code_examples/curl.ts +++ b/x-pack/solutions/search/plugins/search_indices/public/code_examples/curl.ts @@ -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}' \ diff --git a/x-pack/solutions/search/plugins/search_indices/public/code_examples/ingest_data.ts b/x-pack/solutions/search/plugins/search_indices/public/code_examples/ingest_data.ts index f2eb019bda3e9..3f60109dd32e1 100644 --- a/x-pack/solutions/search/plugins/search_indices/public/code_examples/ingest_data.ts +++ b/x-pack/solutions/search/plugins/search_indices/public/code_examples/ingest_data.ts @@ -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, }; diff --git a/x-pack/solutions/search/plugins/search_indices/public/code_examples/javascript.ts b/x-pack/solutions/search/plugins/search_indices/public/code_examples/javascript.ts index 75de93e485742..4da25de849068 100644 --- a/x-pack/solutions/search/plugins/search_indices/public/code_examples/javascript.ts +++ b/x-pack/solutions/search/plugins/search_indices/public/code_examples/javascript.ts @@ -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, diff --git a/x-pack/solutions/search/plugins/search_indices/public/code_examples/python.ts b/x-pack/solutions/search/plugins/search_indices/public/code_examples/python.ts index cf2b06603c381..326e3259663be 100644 --- a/x-pack/solutions/search/plugins/search_indices/public/code_examples/python.ts +++ b/x-pack/solutions/search/plugins/search_indices/public/code_examples/python.ts @@ -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, }; diff --git a/x-pack/solutions/search/plugins/search_indices/public/code_examples/sense.ts b/x-pack/solutions/search/plugins/search_indices/public/code_examples/sense.ts index f54071003df64..8d6a01ff7e34c 100644 --- a/x-pack/solutions/search/plugins/search_indices/public/code_examples/sense.ts +++ b/x-pack/solutions/search/plugins/search_indices/public/code_examples/sense.ts @@ -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) => { diff --git a/x-pack/solutions/search/plugins/search_indices/public/code_examples/types.ts b/x-pack/solutions/search/plugins/search_indices/public/code_examples/types.ts index dc8f877f565d5..cc99baae41a8c 100644 --- a/x-pack/solutions/search/plugins/search_indices/public/code_examples/types.ts +++ b/x-pack/solutions/search/plugins/search_indices/public/code_examples/types.ts @@ -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; } diff --git a/x-pack/solutions/search/plugins/search_indices/public/code_examples/workflows.ts b/x-pack/solutions/search/plugins/search_indices/public/code_examples/workflows.ts new file mode 100644 index 0000000000000..29d2a64254028 --- /dev/null +++ b/x-pack/solutions/search/plugins/search_indices/public/code_examples/workflows.ts @@ -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.", + }), + }, +]; diff --git a/x-pack/solutions/search/plugins/search_indices/public/components/create_index/create_index.tsx b/x-pack/solutions/search/plugins/search_indices/public/components/create_index/create_index.tsx index f09ae3856c097..2f4081b99d486 100644 --- a/x-pack/solutions/search/plugins/search_indices/public/components/create_index/create_index.tsx +++ b/x-pack/solutions/search/plugins/search_indices/public/components/create_index/create_index.tsx @@ -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, diff --git a/x-pack/solutions/search/plugins/search_indices/public/components/index_documents/add_documents_code_example.test.tsx b/x-pack/solutions/search/plugins/search_indices/public/components/index_documents/add_documents_code_example.test.tsx index c5fdc7428e690..e19ece4162dcb 100644 --- a/x-pack/solutions/search/plugins/search_indices/public/components/index_documents/add_documents_code_example.test.tsx +++ b/x-pack/solutions/search/plugins/search_indices/public/components/index_documents/add_documents_code_example.test.tsx @@ -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 = { - 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 = { 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 = { - text: { type: 'text' }, - test: { type: 'boolean' }, - }; - - render( - - ); - - expect(generateSampleDocument).toHaveBeenCalledTimes(3); - - exampleTextsWithCustomMapping.forEach((text, index) => { + exampleTexts.forEach((text, index) => { expect(generateSampleDocument).toHaveBeenNthCalledWith(index + 1, mappingProperties, text); }); }); diff --git a/x-pack/solutions/search/plugins/search_indices/public/components/index_documents/add_documents_code_example.tsx b/x-pack/solutions/search/plugins/search_indices/public/components/index_documents/add_documents_code_example.tsx index d96055b5dc184..e4655f6a858b5 100644 --- a/x-pack/solutions/search/plugins/search_indices/public/components/index_documents/add_documents_code_example.tsx +++ b/x-pack/solutions/search/plugins/search_indices/public/components/index_documents/add_documents_code_example.tsx @@ -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(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(null); return ( - - - + {!indexHasMappings && ( + + { + setSelectedWorkflowId(workflowId); + usageTracker.click([ + AnalyticsEvents.indexDetailsCodeLanguageSelect, + `${AnalyticsEvents.indexDetailsCodeLanguageSelect}_${workflowId}`, + ]); + }} + showTour + container={panelRef} + /> + + )} + {!!workflow && ( + + +

{workflow.title}

+
+ + +

{workflow.summary}

+
+
+ )} + + + {selectedCodeExamples.installCommand && ( { @@ -141,8 +158,8 @@ export const AddDocumentsCodeExample = ({ { diff --git a/x-pack/solutions/search/plugins/search_indices/public/components/index_documents/hooks/use_ingest_code_examples.tsx b/x-pack/solutions/search/plugins/search_indices/public/components/index_documents/hooks/use_ingest_code_examples.tsx deleted file mode 100644 index 7eb84e6f62933..0000000000000 --- a/x-pack/solutions/search/plugins/search_indices/public/components/index_documents/hooks/use_ingest_code_examples.tsx +++ /dev/null @@ -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; -}; diff --git a/x-pack/solutions/search/plugins/search_indices/public/components/indices/details_page.tsx b/x-pack/solutions/search/plugins/search_indices/public/components/indices/details_page.tsx index e7d9eb96bb660..16b355955c2b2 100644 --- a/x-pack/solutions/search/plugins/search_indices/public/components/indices/details_page.tsx +++ b/x-pack/solutions/search/plugins/search_indices/public/components/indices/details_page.tsx @@ -79,12 +79,7 @@ export const SearchIndexDetailsPage = () => { }, [share, index]); const navigateToDiscover = useNavigateToDiscover(indexName); - const [hasDocuments, setHasDocuments] = useState(false); - const [isDocumentsLoading, setDocumentsLoading] = useState(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 = () => { <> @@ -237,7 +232,7 @@ export const SearchIndexDetailsPage = () => { { diff --git a/x-pack/solutions/search/plugins/search_indices/public/components/shared/create_index_code_view.tsx b/x-pack/solutions/search/plugins/search_indices/public/components/shared/create_index_code_view.tsx index c169179bf2982..389e5dcd0b989 100644 --- a/x-pack/solutions/search/plugins/search_indices/public/components/shared/create_index_code_view.tsx +++ b/x-pack/solutions/search/plugins/search_indices/public/components/shared/create_index_code_view.tsx @@ -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 = ({ )} - - - - - - { - usageTracker.click([ - analyticsEvents.runInConsole, - `${analyticsEvents.runInConsole}_${selectedLanguage}`, - ]); - }} - /> - - + + + + + + + { + usageTracker.click([ + analyticsEvents.runInConsole, + `${analyticsEvents.runInConsole}_${selectedLanguage}`, + ]); + }} + /> + + + + {!!selectedWorkflow && ( + <> + + +

{selectedWorkflow?.title}

+
+ + +

{selectedWorkflow?.summary}

+
+
+ + )} + + + {selectedCodeExample.installCommand && ( void; +} + +const PopoverCard: React.FC = ({ workflow, onChange, isSelected }) => ( + + {workflow.summary} + + } + selectable={{ + onClick: () => onChange(workflow.id), + isSelected, + }} + /> +); + +interface GuideSelectorProps { + selectedWorkflowId: WorkflowId; + onChange: (workflow: WorkflowId) => void; + showTour: boolean; + container?: HTMLElement | null; +} + +export const GuideSelector: React.FC = ({ + 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 = ( + + {i18n.translate('xpack.searchIndices.guideSelector.selectWorkflow', { + defaultMessage: 'Select a guide', + })} + + ); + + const Popover = () => ( + + <> + + {workflows.map((workflow) => ( + + onChange(value)} + /> + + ))} + + + + ); + + return showTour ? ( + +

+ {i18n.translate('xpack.searchIndices.tourDescription', { + defaultMessage: 'Explore additional guides for setting up your Elasticsearch index.', + })} +

+ + } + isStepOpen={tourIsOpen} + minWidth={300} + onFinish={() => setTourIsOpen(false)} + step={1} + stepsTotal={1} + title={i18n.translate('xpack.searchIndices.touTitle', { + defaultMessage: 'New guides available!', + })} + anchorPosition="rightUp" + > + +
+ ) : ( + + ); +}; diff --git a/x-pack/solutions/search/plugins/search_indices/public/components/shared/hooks/use_create_index_coding_examples.tsx b/x-pack/solutions/search/plugins/search_indices/public/components/shared/hooks/use_create_index_coding_examples.tsx deleted file mode 100644 index fb1cb6a7eab52..0000000000000 --- a/x-pack/solutions/search/plugins/search_indices/public/components/shared/hooks/use_create_index_coding_examples.tsx +++ /dev/null @@ -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; -}; diff --git a/x-pack/solutions/search/plugins/search_indices/public/components/shared/hooks/use_guide_tour.tsx b/x-pack/solutions/search/plugins/search_indices/public/components/shared/hooks/use_guide_tour.tsx new file mode 100644 index 0000000000000..fee2fc156a914 --- /dev/null +++ b/x-pack/solutions/search/plugins/search_indices/public/components/shared/hooks/use_guide_tour.tsx @@ -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'); + }, + }; +}; diff --git a/x-pack/solutions/search/plugins/search_indices/public/components/shared/hooks/use_workflow.tsx b/x-pack/solutions/search/plugins/search_indices/public/components/shared/hooks/use_workflow.tsx new file mode 100644 index 0000000000000..0db49738aed8d --- /dev/null +++ b/x-pack/solutions/search/plugins/search_indices/public/components/shared/hooks/use_workflow.tsx @@ -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('default'); + return { + selectedWorkflowId, + setSelectedWorkflowId, + workflow: workflows.find((workflow) => workflow.id === selectedWorkflowId), + createIndexExamples: workflowIdToCreateIndexExamples(selectedWorkflowId), + ingestExamples: workflowIdToIngestDataExamples(selectedWorkflowId), + }; +}; diff --git a/x-pack/solutions/search/plugins/search_indices/public/components/shared/language_selector.tsx b/x-pack/solutions/search/plugins/search_indices/public/components/shared/language_selector.tsx index ce31e4c38e0fe..406b658cb181d 100644 --- a/x-pack/solutions/search/plugins/search_indices/public/components/shared/language_selector.tsx +++ b/x-pack/solutions/search/plugins/search_indices/public/components/shared/language_selector.tsx @@ -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 ( + 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)} diff --git a/x-pack/solutions/search/plugins/search_indices/public/components/start/elasticsearch_start.tsx b/x-pack/solutions/search/plugins/search_indices/public/components/start/elasticsearch_start.tsx index 7b525250ff493..0b359688353a5 100644 --- a/x-pack/solutions/search/plugins/search_indices/public/components/start/elasticsearch_start.tsx +++ b/x-pack/solutions/search/plugins/search_indices/public/components/start/elasticsearch_start.tsx @@ -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 = () => { : CreateIndexViewMode.UI ); const usageTracker = useUsageTracker(); + const { workflow, setSelectedWorkflowId } = useWorkflow(); useEffect(() => { usageTracker.load(AnalyticsEvents.startPageOpened); @@ -114,6 +117,14 @@ export const ElasticsearchStart: React.FC = () => { 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, diff --git a/x-pack/solutions/search/plugins/search_indices/public/hooks/api/use_document_search.ts b/x-pack/solutions/search/plugins/search_indices/public/hooks/api/use_document_search.ts index a8afd69385623..db0e9bac718c8 100644 --- a/x-pack/solutions/search/plugins/search_indices/public/hooks/api/use_document_search.ts +++ b/x-pack/solutions/search/plugins/search_indices/public/hooks/api/use_document_search.ts @@ -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), }; }; diff --git a/x-pack/solutions/search/plugins/search_indices/public/hooks/api/use_index.ts b/x-pack/solutions/search/plugins/search_indices/public/hooks/api/use_index.ts index e91e4c9d06f5f..1f0ad428a33b9 100644 --- a/x-pack/solutions/search/plugins/search_indices/public/hooks/api/use_index.ts +++ b/x-pack/solutions/search/plugins/search_indices/public/hooks/api/use_index.ts @@ -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({ + return useQuery({ queryKey, refetchInterval: POLLING_INTERVAL, refetchIntervalInBackground: true, @@ -25,5 +25,4 @@ export const useIndex = (indexName: string) => { queryFn: () => http.fetch(`/internal/index_management/indices/${encodeURIComponent(indexName)}`), }); - return { queryKey, ...result }; }; diff --git a/x-pack/solutions/search/plugins/search_indices/public/hooks/api/use_index_mappings.ts b/x-pack/solutions/search/plugins/search_indices/public/hooks/api/use_index_mappings.ts index 1d5a83aa920ed..b09cf79f4aba4 100644 --- a/x-pack/solutions/search/plugins/search_indices/public/hooks/api/use_index_mappings.ts +++ b/x-pack/solutions/search/plugins/search_indices/public/hooks/api/use_index_mappings.ts @@ -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({ + return useQuery({ queryKey, refetchInterval: POLLING_INTERVAL, refetchIntervalInBackground: true, @@ -22,5 +22,4 @@ export const useIndexMapping = (indexName: string) => { queryFn: () => http.fetch(`/api/index_management/mapping/${encodeURIComponent(indexName)}`), }); - return { queryKey, ...result }; }; diff --git a/x-pack/test/functional/page_objects/search_index_details_page.ts b/x-pack/test/functional/page_objects/search_index_details_page.ts index feef7eb157172..c6afabfa25569 100644 --- a/x-pack/test/functional/page_objects/search_index_details_page.ts +++ b/x-pack/test/functional/page_objects/search_index_details_page.ts @@ -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'); diff --git a/x-pack/test/functional_search/tests/search_index_details.ts b/x-pack/test/functional_search/tests/search_index_details.ts index fccac0d31a145..65cd8b5ffac24 100644 --- a/x-pack/test/functional_search/tests/search_index_details.ts +++ b/x-pack/test/functional_search/tests/search_index_details.ts @@ -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', () => { diff --git a/x-pack/test_serverless/functional/page_objects/svl_search_index_detail_page.ts b/x-pack/test_serverless/functional/page_objects/svl_search_index_detail_page.ts index 17821c7dfe73b..3e0a9da0744a9 100644 --- a/x-pack/test_serverless/functional/page_objects/svl_search_index_detail_page.ts +++ b/x-pack/test_serverless/functional/page_objects/svl_search_index_detail_page.ts @@ -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'); diff --git a/x-pack/test_serverless/functional/test_suites/search/search_index_detail.ts b/x-pack/test_serverless/functional/test_suites/search/search_index_detail.ts index 0dda7789b6f93..59621ca8512a0 100644 --- a/x-pack/test_serverless/functional/test_suites/search/search_index_detail.ts +++ b/x-pack/test_serverless/functional/test_suites/search/search_index_detail.ts @@ -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', () => {