From 7b25b6634cc101664f0c35bcb31d79fa3608f06b Mon Sep 17 00:00:00 2001 From: Elena Stoeva <59341489+ElenaStoeva@users.noreply.github.com> Date: Thu, 14 Nov 2024 18:58:42 +0000 Subject: [PATCH] [Index Management] Add index mode field in index template form (#199521) Closes https://github.com/elastic/kibana/issues/198620 ## Summary This PR adds a field for index mode setting in the Logistics step in Index Template form. https://github.com/user-attachments/assets/ee38bdec-66ff-468d-a55e-abf5354c3da2 **How to test:** 1. Go to Index Management -> Index Templates and start creating an index template 2. Verify that the index mode is only enabled if the data stream toggle is on. 3. Verify that typing the `logs-*-*` index pattern sets the index mode to "LogsDB": Screenshot 2024-11-13 at 13 00 10 4. Go to the Settings step and verify that the index mode callout is displayed correctly. 5. Go to Review step and verify that Index mode is displayed correctly in both the summary and the preview request. 6. Save the template and verify that the template details tab correctly displays the index mode. Screenshot 2024-11-13 at 17 22 54 Screenshot 2024-11-13 at 17 22 31 ### Checklist Delete any items that are not applicable to this PR. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [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] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [x] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [x] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [x] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) (cherry picked from commit 16127fcc8faf128cfd9d2feffc6086eb6330c11f) --- .../template_create.test.tsx | 67 ++++++++++ .../template_edit.test.tsx | 1 + .../template_form.helpers.ts | 8 ++ .../common/constants/index.ts | 1 + .../common/constants/index_modes.ts | 10 ++ .../common/lib/template_serialization.test.ts | 39 +++++- .../common/lib/template_serialization.ts | 26 +++- .../index_management/common/types/index.ts | 1 + .../common/types/templates.ts | 3 +- .../components/wizard_steps/step_settings.tsx | 49 +++++++- .../wizard_steps/step_settings_container.tsx | 21 +++- .../template_form/steps/step_logistics.tsx | 117 +++++++++++++++--- .../template_form/steps/step_review.tsx | 7 +- .../template_form/template_form.tsx | 8 +- .../template_form/template_form_schemas.tsx | 8 ++ .../application/lib/index_mode_labels.ts | 57 ++++++--- .../data_stream_detail_panel.tsx | 4 +- .../data_stream_table/data_stream_table.tsx | 4 +- .../template_details/tabs/tab_summary.tsx | 6 +- .../routes/api/templates/validate_schemas.ts | 1 + .../test/fixtures/template.ts | 4 + .../index_management/lib/templates.helpers.ts | 1 + .../management/index_management/templates.ts | 7 ++ .../index_management/index_template_wizard.ts | 6 + .../index_management/svl_templates.helpers.ts | 1 + .../index_management/index_templates.ts | 2 + 26 files changed, 403 insertions(+), 56 deletions(-) create mode 100644 x-pack/plugins/index_management/common/constants/index_modes.ts diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx index a27fb82fb82b0..7ba6832b8833d 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx @@ -52,6 +52,15 @@ jest.mock('@elastic/eui', () => { }} /> ), + EuiSuperSelect: (props: any) => ( + { + props.onChange(e.target.value); + }} + /> + ), }; }); @@ -301,6 +310,15 @@ describe('', () => { expect(find('stepTitle').text()).toEqual('Index settings (optional)'); }); + it('should display a warning callout displaying the selected index mode', async () => { + const { exists, find } = testBed; + + expect(exists('indexModeCallout')).toBe(true); + expect(find('indexModeCallout').text()).toContain( + 'The index.mode setting has been set to Standard within template Logistics.' + ); + }); + it('should not allow invalid json', async () => { const { form, actions } = testBed; @@ -426,6 +444,53 @@ describe('', () => { }); }); + describe('logistics (step 1)', () => { + beforeEach(async () => { + await act(async () => { + testBed = await setup(httpSetup); + }); + testBed.component.update(); + }); + + it('setting index pattern to logs-*-* should set the index mode to logsdb', async () => { + const { component, actions } = testBed; + // Logistics + await actions.completeStepOne({ name: 'my_logs_template', indexPatterns: ['logs-*-*'] }); + // Component templates + await actions.completeStepTwo(); + // Index settings + await actions.completeStepThree('{}'); + // Mappings + await actions.completeStepFour(); + // Aliases + await actions.completeStepFive(); + + await act(async () => { + actions.clickNextButton(); + }); + component.update(); + + expect(httpSetup.post).toHaveBeenLastCalledWith( + `${API_BASE_PATH}/index_templates`, + expect.objectContaining({ + body: JSON.stringify({ + name: 'my_logs_template', + indexPatterns: ['logs-*-*'], + allowAutoCreate: 'NO_OVERWRITE', + indexMode: 'logsdb', + dataStream: {}, + _kbnMeta: { + type: 'default', + hasDatastream: false, + isLegacy: false, + }, + template: {}, + }), + }) + ); + }); + }); + describe('review (step 6)', () => { beforeEach(async () => { await act(async () => { @@ -529,6 +594,7 @@ describe('', () => { name: TEMPLATE_NAME, indexPatterns: DEFAULT_INDEX_PATTERNS, allowAutoCreate: 'TRUE', + indexMode: 'time_series', }); // Component templates await actions.completeStepTwo('test_component_template_1'); @@ -557,6 +623,7 @@ describe('', () => { name: TEMPLATE_NAME, indexPatterns: DEFAULT_INDEX_PATTERNS, allowAutoCreate: 'TRUE', + indexMode: 'time_series', dataStream: {}, _kbnMeta: { type: 'default', diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.test.tsx index dac7dadfa2557..bbd1d24c7906d 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.test.tsx @@ -169,6 +169,7 @@ describe('', () => { indexPatterns: ['myPattern*'], version: 1, allowAutoCreate: 'NO_OVERWRITE', + indexMode: 'standard', dataStream: { hidden: true, anyUnknownKey: 'should_be_kept', diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_form.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_form.helpers.ts index 7977c4373d765..7c02608d4e3f7 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_form.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_form.helpers.ts @@ -148,6 +148,7 @@ export const formSetup = async (initTestBed: SetupFunc) => { enableDataStream, lifecycle, allowAutoCreate, + indexMode, }: Partial & { enableDataStream?: boolean } = {}) => { const { component, form, find } = testBed; @@ -204,6 +205,10 @@ export const formSetup = async (initTestBed: SetupFunc) => { radioOption.simulate('change', { target: { checked: true } }); component.update(); } + + if (indexMode) { + form.setSelectValue('indexModeField', indexMode); + } }); component.update(); @@ -356,6 +361,7 @@ export type TestSubjects = | 'mappingsEditorFieldEdit' | 'mockCodeEditor' | 'mockComboBox' + | 'mockSuperSelect' | 'nameField' | 'nameField.input' | 'nameParameterInput' @@ -364,6 +370,8 @@ export type TestSubjects = | 'orderField.input' | 'priorityField.input' | 'dataStreamField.input' + | 'indexModeField' + | 'indexModeCallout' | 'dataRetentionToggle.input' | 'allowAutoCreateField.input' | 'pageTitle' diff --git a/x-pack/plugins/index_management/common/constants/index.ts b/x-pack/plugins/index_management/common/constants/index.ts index 49e2a7f9505a9..81e50fa8b75be 100644 --- a/x-pack/plugins/index_management/common/constants/index.ts +++ b/x-pack/plugins/index_management/common/constants/index.ts @@ -9,6 +9,7 @@ import { i18n } from '@kbn/i18n'; export { BASE_PATH } from './base_path'; export { API_BASE_PATH, INTERNAL_API_BASE_PATH } from './api_base_path'; export { INVALID_INDEX_PATTERN_CHARS, INVALID_TEMPLATE_NAME_CHARS } from './invalid_characters'; +export * from './index_modes'; export * from './index_statuses'; // Since each index can have a max length or 255 characters and the max length of diff --git a/x-pack/plugins/index_management/common/constants/index_modes.ts b/x-pack/plugins/index_management/common/constants/index_modes.ts new file mode 100644 index 0000000000000..65099cf4e4f8b --- /dev/null +++ b/x-pack/plugins/index_management/common/constants/index_modes.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const STANDARD_INDEX_MODE = 'standard'; +export const LOGSDB_INDEX_MODE = 'logsdb'; +export const TIME_SERIES_MODE = 'time_series'; diff --git a/x-pack/plugins/index_management/common/lib/template_serialization.test.ts b/x-pack/plugins/index_management/common/lib/template_serialization.test.ts index 8f9f73c334a9f..cb86de2660fd3 100644 --- a/x-pack/plugins/index_management/common/lib/template_serialization.test.ts +++ b/x-pack/plugins/index_management/common/lib/template_serialization.test.ts @@ -6,7 +6,8 @@ */ import { deserializeTemplate, serializeTemplate } from './template_serialization'; -import { TemplateDeserialized, TemplateSerialized } from '../types'; +import { TemplateDeserialized, TemplateSerialized, IndexMode } from '../types'; +import { STANDARD_INDEX_MODE, LOGSDB_INDEX_MODE, TIME_SERIES_MODE } from '../constants'; const defaultSerializedTemplate: TemplateSerialized = { template: {}, @@ -17,6 +18,7 @@ const defaultSerializedTemplate: TemplateSerialized = { const defaultDeserializedTemplate: TemplateDeserialized = { name: 'my_template', indexPatterns: ['test'], + indexMode: STANDARD_INDEX_MODE, _kbnMeta: { type: 'default', hasDatastream: true, @@ -26,12 +28,13 @@ const defaultDeserializedTemplate: TemplateDeserialized = { const allowAutoCreateRadioOptions = ['NO_OVERWRITE', 'TRUE', 'FALSE']; const allowAutoCreateSerializedValues = [undefined, true, false]; +const indexModeValues = [STANDARD_INDEX_MODE, LOGSDB_INDEX_MODE, TIME_SERIES_MODE, undefined]; describe('Template serialization', () => { describe('serialization of allow_auto_create parameter', () => { describe('deserializeTemplate()', () => { allowAutoCreateSerializedValues.forEach((value, index) => { - test(`correctly deserializes ${value} value`, () => { + test(`correctly deserializes ${value} allow_auto_create value`, () => { expect( deserializeTemplate({ ...defaultSerializedTemplate, @@ -41,11 +44,29 @@ describe('Template serialization', () => { ).toHaveProperty('allowAutoCreate', allowAutoCreateRadioOptions[index]); }); }); + + indexModeValues.forEach((value) => { + test(`correctly deserializes ${value} index mode settings value`, () => { + expect( + deserializeTemplate({ + ...defaultSerializedTemplate, + name: 'my_template', + template: { + settings: { + index: { + mode: value, + }, + }, + }, + }) + ).toHaveProperty('indexMode', value ?? STANDARD_INDEX_MODE); + }); + }); }); describe('serializeTemplate()', () => { allowAutoCreateRadioOptions.forEach((option, index) => { - test(`correctly serializes ${option} radio option`, () => { + test(`correctly serializes ${option} allowAutoCreate radio option`, () => { expect( serializeTemplate({ ...defaultDeserializedTemplate, @@ -54,6 +75,18 @@ describe('Template serialization', () => { ).toHaveProperty('allow_auto_create', allowAutoCreateSerializedValues[index]); }); }); + + // Only use the first three values (omit undefined) + indexModeValues.slice(0, 3).forEach((value) => { + test(`correctly serializes ${value} indexMode option`, () => { + expect( + serializeTemplate({ + ...defaultDeserializedTemplate, + indexMode: value as IndexMode, + }) + ).toHaveProperty('template.settings.index.mode', value); + }); + }); }); }); }); diff --git a/x-pack/plugins/index_management/common/lib/template_serialization.ts b/x-pack/plugins/index_management/common/lib/template_serialization.ts index 0ed52e3f04ba0..999023704559c 100644 --- a/x-pack/plugins/index_management/common/lib/template_serialization.ts +++ b/x-pack/plugins/index_management/common/lib/template_serialization.ts @@ -11,9 +11,15 @@ import { TemplateSerialized, TemplateListItem, TemplateType, + IndexMode, } from '../types'; import { deserializeESLifecycle } from './data_stream_utils'; -import { allowAutoCreateRadioValues, allowAutoCreateRadioIds } from '../constants'; +import { + allowAutoCreateRadioValues, + allowAutoCreateRadioIds, + STANDARD_INDEX_MODE, + LOGSDB_INDEX_MODE, +} from '../constants'; const hasEntries = (data: object = {}) => Object.entries(data).length > 0; @@ -26,6 +32,7 @@ export function serializeTemplate(templateDeserialized: TemplateDeserialized): T composedOf, ignoreMissingComponentTemplates, dataStream, + indexMode, _meta, allowAutoCreate, deprecated, @@ -34,7 +41,16 @@ export function serializeTemplate(templateDeserialized: TemplateDeserialized): T return { version, priority, - template, + template: { + ...template, + settings: { + ...template?.settings, + index: { + ...template?.settings?.index, + mode: indexMode, + }, + }, + }, index_patterns: indexPatterns, data_stream: dataStream, composed_of: composedOf, @@ -75,6 +91,11 @@ export function deserializeTemplate( const ilmPolicyName = settings?.index?.lifecycle?.name; + const indexMode = (settings?.index?.mode ?? + (indexPatterns.some((pattern) => pattern === 'logs-*-*') + ? LOGSDB_INDEX_MODE + : STANDARD_INDEX_MODE)) as IndexMode; + const deserializedTemplate: TemplateDeserialized = { name, version, @@ -82,6 +103,7 @@ export function deserializeTemplate( ...(template.lifecycle ? { lifecycle: deserializeESLifecycle(template.lifecycle) } : {}), indexPatterns: indexPatterns.sort(), template, + indexMode, ilmPolicy: ilmPolicyName ? { name: ilmPolicyName } : undefined, composedOf: composedOf ?? [], ignoreMissingComponentTemplates: ignoreMissingComponentTemplates ?? [], diff --git a/x-pack/plugins/index_management/common/types/index.ts b/x-pack/plugins/index_management/common/types/index.ts index ef2e8a389c079..7ec100bc1d366 100644 --- a/x-pack/plugins/index_management/common/types/index.ts +++ b/x-pack/plugins/index_management/common/types/index.ts @@ -19,6 +19,7 @@ export type { DataStream, DataStreamIndex, DataRetention, + IndexMode, } from './data_streams'; export * from './component_templates'; diff --git a/x-pack/plugins/index_management/common/types/templates.ts b/x-pack/plugins/index_management/common/types/templates.ts index b05a29a961a73..ab4614200c0b5 100644 --- a/x-pack/plugins/index_management/common/types/templates.ts +++ b/x-pack/plugins/index_management/common/types/templates.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { DataRetention, DataStream } from './data_streams'; +import { DataRetention, DataStream, IndexMode } from './data_streams'; import { IndexSettings } from './indices'; import { Aliases } from './aliases'; import { Mappings } from './mappings'; @@ -51,6 +51,7 @@ export interface TemplateDeserialized { priority?: number; // Composable template only allowAutoCreate: string; order?: number; // Legacy template only + indexMode: IndexMode; ilmPolicy?: { name: string; }; diff --git a/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_settings.tsx b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_settings.tsx index 9b26ed38223c4..38a11f03c7ee6 100644 --- a/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_settings.tsx +++ b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_settings.tsx @@ -16,6 +16,8 @@ import { EuiFormRow, EuiText, EuiCode, + EuiCallOut, + EuiLink, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { CodeEditor } from '@kbn/code-editor'; @@ -23,15 +25,19 @@ import { CodeEditor } from '@kbn/code-editor'; import { Forms } from '../../../../../shared_imports'; import { useJsonStep } from './use_json_step'; import { documentationService } from '../../../mappings_editor/shared_imports'; +import { indexModeLabels } from '../../../../lib/index_mode_labels'; +import { IndexMode } from '../../../../../../common/types'; interface Props { onChange: (content: Forms.Content) => void; esDocsBase: string; defaultValue?: { [key: string]: any }; + indexMode?: IndexMode; } export const StepSettings: React.FunctionComponent = React.memo( - ({ defaultValue = {}, onChange, esDocsBase }) => { + ({ defaultValue = {}, onChange, esDocsBase, indexMode }) => { + const { navigateToStep } = Forms.useFormWizardContext(); const { jsonContent, setJsonContent, error } = useJsonStep({ defaultValue, onChange, @@ -80,6 +86,47 @@ export const StepSettings: React.FunctionComponent = React.memo( + {indexMode && ( + <> + + {i18n.translate( + 'xpack.idxMgmt.formWizard.stepSettings.indexModeCallout.indexModeSettingLabel', + { + defaultMessage: 'index.mode', + } + )} + + ), + indexMode: indexModeLabels[indexMode], + logisticsLink: ( + navigateToStep(0)}> + {i18n.translate( + 'xpack.idxMgmt.formWizard.stepSettings.indexModeCallout.logisticsLinkLabel', + { + defaultMessage: 'Logistics', + } + )} + + ), + }} + /> + } + color="warning" + iconType="warning" + data-test-subj="indexModeCallout" + /> + + + + )} + {/* Settings code editor */} TemplateDeserialized; } -export const StepSettingsContainer = React.memo(({ esDocsBase }: Props) => { +export const StepSettingsContainer = React.memo(({ esDocsBase, getTemplateData }: Props) => { const { defaultValue, updateContent } = Forms.useContent( 'settings' ); + const { getData } = Forms.useMultiContentContext(); + + let indexMode; + if (getTemplateData) { + const wizardContent = getData(); + // Build the current template object, providing the wizard content data + const template = getTemplateData(wizardContent); + indexMode = template.indexMode; + } return ( - + ); }); diff --git a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_logistics.tsx b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_logistics.tsx index d73d95600e5b1..9742f2eec525f 100644 --- a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_logistics.tsx +++ b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_logistics.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useEffect, useCallback } from 'react'; +import React, { useEffect, useCallback, Fragment } from 'react'; import { EuiFlexGroup, EuiFlexItem, @@ -14,6 +14,7 @@ import { EuiSpacer, EuiLink, EuiCode, + EuiText, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; @@ -34,7 +35,13 @@ import { UnitField, timeUnits } from '../../shared'; import { DataRetention } from '../../../../../common'; import { documentationService } from '../../../services/documentation'; import { schemas, nameConfig, nameConfigWithoutValidations } from '../template_form_schemas'; -import { allowAutoCreateRadios } from '../../../../../common/constants'; +import { + allowAutoCreateRadios, + STANDARD_INDEX_MODE, + TIME_SERIES_MODE, + LOGSDB_INDEX_MODE, +} from '../../../../../common/constants'; +import { indexModeLabels, indexModeDescriptions } from '../../../lib/index_mode_labels'; // Create or Form components with partial props that are common to all instances const UseField = getUseField({ component: Field }); @@ -91,6 +98,54 @@ function getFieldsMeta(esDocsBase: string) { ), testSubject: 'dataStreamField', }, + indexMode: { + title: i18n.translate('xpack.idxMgmt.templateForm.stepLogistics.indexModeTitle', { + defaultMessage: 'Data stream index mode', + }), + description: i18n.translate('xpack.idxMgmt.templateForm.stepLogistics.indexModeDescription', { + defaultMessage: + 'The index.mode setting is used to control settings applied in specific domains like ingestions of time series data or logs.', + }), + options: [ + { + value: STANDARD_INDEX_MODE, + inputDisplay: indexModeLabels[STANDARD_INDEX_MODE], + dropdownDisplay: ( + + {indexModeLabels[STANDARD_INDEX_MODE]} + +

{indexModeDescriptions[STANDARD_INDEX_MODE]}

+
+
+ ), + }, + { + value: TIME_SERIES_MODE, + inputDisplay: indexModeLabels[TIME_SERIES_MODE], + dropdownDisplay: ( + + {indexModeLabels[TIME_SERIES_MODE]} + +

{indexModeDescriptions[TIME_SERIES_MODE]}

+
+
+ ), + }, + { + value: LOGSDB_INDEX_MODE, + inputDisplay: indexModeLabels[LOGSDB_INDEX_MODE], + dropdownDisplay: ( + + {indexModeLabels[LOGSDB_INDEX_MODE]} + +

{indexModeDescriptions[LOGSDB_INDEX_MODE]}

+
+
+ ), + }, + ], + testSubject: 'indexModeField', + }, order: { title: i18n.translate('xpack.idxMgmt.templateForm.stepLogistics.orderTitle', { defaultMessage: 'Merge order', @@ -198,21 +253,37 @@ export const StepLogistics: React.FunctionComponent = React.memo( isValid: isFormValid, getErrors: getFormErrors, getFormData, + setFieldValue, } = form; - const [{ addMeta, doCreateDataStream, lifecycle }] = useFormData<{ - addMeta: boolean; - lifecycle: DataRetention; - doCreateDataStream: boolean; - }>({ - form, - watch: [ - 'addMeta', - 'lifecycle.enabled', - 'lifecycle.infiniteDataRetention', - 'doCreateDataStream', - ], - }); + const [{ addMeta, doCreateDataStream, lifecycle, indexPatterns: indexPatternsField }] = + useFormData<{ + addMeta: boolean; + lifecycle: DataRetention; + doCreateDataStream: boolean; + indexPatterns: string[]; + }>({ + form, + watch: [ + 'addMeta', + 'lifecycle.enabled', + 'lifecycle.infiniteDataRetention', + 'doCreateDataStream', + 'indexPatterns', + ], + }); + + useEffect(() => { + if ( + indexPatternsField && + indexPatternsField.length === 1 && + indexPatternsField[0] === 'logs-*-*' && + // Only set index mode if index pattern was changed + defaultValue.indexPatterns !== indexPatternsField + ) { + setFieldValue('indexMode', LOGSDB_INDEX_MODE); + } + }, [defaultValue.indexPatterns, indexPatternsField, setFieldValue]); /** * When the consumer call validate() on this step, we submit the form so it enters the "isSubmitted" state @@ -234,6 +305,7 @@ export const StepLogistics: React.FunctionComponent = React.memo( name, indexPatterns, createDataStream, + indexMode, order, priority, version, @@ -312,6 +384,21 @@ export const StepLogistics: React.FunctionComponent = React.memo( )} + {doCreateDataStream && ( + + + + )} + {/* Since data stream and data retention are settings that are only allowed for non legacy, we only need to check if data stream is set to true to show the data retention. diff --git a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_review.tsx b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_review.tsx index 9cb5c481b6b50..593655da62fef 100644 --- a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_review.tsx +++ b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_review.tsx @@ -22,7 +22,7 @@ import { EuiCodeBlock, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import { getIndexModeLabel } from '../../../lib/index_mode_labels'; +import { indexModeLabels } from '../../../lib/index_mode_labels'; import { allowAutoCreateRadioIds } from '../../../../../common/constants'; import { serializers } from '../../../../shared_imports'; @@ -89,6 +89,7 @@ export const StepReview: React.FunctionComponent = React.memo( const { name, indexPatterns, + indexMode, version, order, template: indexTemplate, @@ -277,9 +278,7 @@ export const StepReview: React.FunctionComponent = React.memo( /> - {getIndexModeLabel( - serializedSettings?.['index.mode'] ?? serializedSettings?.index?.mode - )} + {indexModeLabels[indexMode]} {/* Mappings */} diff --git a/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx b/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx index 2da3eef609a65..53b53a6ebdeee 100644 --- a/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx +++ b/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx @@ -11,7 +11,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { EuiSpacer, EuiButton, EuiPageHeader } from '@elastic/eui'; import { ScopedHistory } from '@kbn/core/public'; -import { allowAutoCreateRadioIds } from '../../../../common/constants'; +import { allowAutoCreateRadioIds, STANDARD_INDEX_MODE } from '../../../../common/constants'; import { TemplateDeserialized } from '../../../../common'; import { serializers, Forms, GlobalFlyout } from '../../../shared_imports'; import { @@ -118,6 +118,7 @@ export const TemplateForm = ({ name: '', indexPatterns: [], dataStream: {}, + indexMode: STANDARD_INDEX_MODE, template: {}, _kbnMeta: { type: 'default', @@ -341,7 +342,10 @@ export const TemplateForm = ({ )} - + diff --git a/x-pack/plugins/index_management/public/application/components/template_form/template_form_schemas.tsx b/x-pack/plugins/index_management/public/application/components/template_form/template_form_schemas.tsx index dec31faad794a..513517e6be6cb 100644 --- a/x-pack/plugins/index_management/public/application/components/template_form/template_form_schemas.tsx +++ b/x-pack/plugins/index_management/public/application/components/template_form/template_form_schemas.tsx @@ -23,6 +23,7 @@ import { allowAutoCreateRadioIds, INVALID_INDEX_PATTERN_CHARS, INVALID_TEMPLATE_NAME_CHARS, + STANDARD_INDEX_MODE, } from '../../../../common/constants'; const { @@ -138,6 +139,13 @@ export const schemas: Record = { }), defaultValue: false, }, + indexMode: { + type: FIELD_TYPES.SUPER_SELECT, + defaultValue: STANDARD_INDEX_MODE, + label: i18n.translate('xpack.idxMgmt.templateForm.stepLogistics.fieldIndexModeLabel', { + defaultMessage: 'Index mode', + }), + }, order: { type: FIELD_TYPES.NUMBER, label: i18n.translate('xpack.idxMgmt.templateForm.stepLogistics.fieldOrderLabel', { diff --git a/x-pack/plugins/index_management/public/application/lib/index_mode_labels.ts b/x-pack/plugins/index_management/public/application/lib/index_mode_labels.ts index 409659b8133c3..dd7a712567e9f 100644 --- a/x-pack/plugins/index_management/public/application/lib/index_mode_labels.ts +++ b/x-pack/plugins/index_management/public/application/lib/index_mode_labels.ts @@ -6,24 +6,43 @@ */ import { i18n } from '@kbn/i18n'; +import { + STANDARD_INDEX_MODE, + LOGSDB_INDEX_MODE, + TIME_SERIES_MODE, +} from '../../../common/constants'; -export const getIndexModeLabel = (mode?: string | null) => { - switch (mode) { - case 'standard': - case null: - case undefined: - return i18n.translate('xpack.idxMgmt.indexModeLabels.standardModeLabel', { - defaultMessage: 'Standard', - }); - case 'logsdb': - return i18n.translate('xpack.idxMgmt.indexModeLabels.logsdbModeLabel', { - defaultMessage: 'LogsDB', - }); - case 'time_series': - return i18n.translate('xpack.idxMgmt.indexModeLabels.tsdbModeLabel', { - defaultMessage: 'Time series', - }); - default: - return mode; - } +export const indexModeLabels = { + [STANDARD_INDEX_MODE]: i18n.translate('xpack.idxMgmt.indexModeLabels.standardIndexModeLabel', { + defaultMessage: 'Standard', + }), + [LOGSDB_INDEX_MODE]: i18n.translate('xpack.idxMgmt.indexModeLabels.logsdbIndexModeLabel', { + defaultMessage: 'LogsDB', + }), + [TIME_SERIES_MODE]: i18n.translate('xpack.idxMgmt.indexModeLabels.timeSeriesIndexModeLabel', { + defaultMessage: 'Time series', + }), +}; + +export const indexModeDescriptions = { + [STANDARD_INDEX_MODE]: i18n.translate( + 'xpack.idxMgmt.indexModeDescriptions.standardIndexModeDescription', + { + defaultMessage: + 'Standard indexing with default settings, for data other than logs or metrics', + } + ), + [LOGSDB_INDEX_MODE]: i18n.translate( + 'xpack.idxMgmt.indexModeDescriptions.logsdbIndexModeDescription', + { + defaultMessage: + 'Optimized for storing logs data, with reduced storage and default settings that help reduce the chance of logs being rejected by Elasticsearch', + } + ), + [TIME_SERIES_MODE]: i18n.translate( + 'xpack.idxMgmt.indexModeDescriptions.timeSeriesIndexModeDescription', + { + defaultMessage: 'Optimized for metrics data with reduced storage', + } + ), }; diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx index 5b3bf0920c3b7..d962305a7147c 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx @@ -34,7 +34,7 @@ import { EuiSpacer, } from '@elastic/eui'; -import { getIndexModeLabel } from '../../../../lib/index_mode_labels'; +import { indexModeLabels } from '../../../../lib/index_mode_labels'; import { DiscoverLink } from '../../../../lib/discover_link'; import { getLifecycleValue } from '../../../../lib/data_streams'; import { SectionLoading, reactRouterNavigate } from '../../../../../shared_imports'; @@ -355,7 +355,7 @@ export const DataStreamDetailPanel: React.FunctionComponent = ({ defaultMessage: "The index mode applied to the data stream's backing indices, as defined in its associated index template.", }), - content: getIndexModeLabel(indexMode), + content: indexModeLabels[indexMode], dataTestSubj: 'indexModeDetail', }, { diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx index e91fd644f795c..59daae719bf47 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx @@ -36,7 +36,7 @@ import { humanizeTimeStamp } from '../humanize_time_stamp'; import { DataStreamsBadges } from '../data_stream_badges'; import { ConditionalWrap } from '../data_stream_detail_panel'; import { isDataStreamFullyManagedByILM } from '../../../../lib/data_streams'; -import { getIndexModeLabel } from '../../../../lib/index_mode_labels'; +import { indexModeLabels } from '../../../../lib/index_mode_labels'; import { FilterListButton, Filters } from '../../components'; import { type DataStreamFilterName } from '../data_stream_list'; @@ -192,7 +192,7 @@ export const DataStreamTable: React.FunctionComponent = ({ }), truncateText: true, sortable: true, - render: (indexMode: DataStream['indexMode']) => getIndexModeLabel(indexMode), + render: (indexMode: DataStream['indexMode']) => indexModeLabels[indexMode], }); columns.push({ diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_summary.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_summary.tsx index ff06a08014f61..2621f3ec483c1 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_summary.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_summary.tsx @@ -28,7 +28,7 @@ import { TemplateDeserialized } from '../../../../../../../common'; import { ILM_PAGES_POLICY_EDIT } from '../../../../../constants'; import { useIlmLocator } from '../../../../../services/use_ilm_locator'; import { allowAutoCreateRadioIds } from '../../../../../../../common/constants'; -import { getIndexModeLabel } from '../../../../../lib/index_mode_labels'; +import { indexModeLabels } from '../../../../../lib/index_mode_labels'; interface Props { templateDetails: TemplateDeserialized; @@ -54,11 +54,11 @@ export const TabSummary: React.FunctionComponent = ({ templateDetails }) composedOf, order, indexPatterns = [], + indexMode, ilmPolicy, _meta, _kbnMeta: { isLegacy, hasDatastream }, allowAutoCreate, - template, } = templateDetails; const numIndexPatterns = indexPatterns.length; @@ -231,7 +231,7 @@ export const TabSummary: React.FunctionComponent = ({ templateDetails }) /> - {getIndexModeLabel(template?.settings?.index?.mode)} + {indexModeLabels[indexMode]} {/* Allow auto create */} diff --git a/x-pack/plugins/index_management/server/routes/api/templates/validate_schemas.ts b/x-pack/plugins/index_management/server/routes/api/templates/validate_schemas.ts index 97cb0a1c1c69c..cfd9c1d18e610 100644 --- a/x-pack/plugins/index_management/server/routes/api/templates/validate_schemas.ts +++ b/x-pack/plugins/index_management/server/routes/api/templates/validate_schemas.ts @@ -13,6 +13,7 @@ export const templateSchema = schema.object({ version: schema.maybe(schema.number()), order: schema.maybe(schema.number()), priority: schema.maybe(schema.number()), + indexMode: schema.string(), // Not present for legacy templates allowAutoCreate: schema.maybe(schema.string()), template: schema.maybe( diff --git a/x-pack/plugins/index_management/test/fixtures/template.ts b/x-pack/plugins/index_management/test/fixtures/template.ts index 54df4410352b6..09895b550dc18 100644 --- a/x-pack/plugins/index_management/test/fixtures/template.ts +++ b/x-pack/plugins/index_management/test/fixtures/template.ts @@ -23,6 +23,7 @@ export const getComposableTemplate = ({ isLegacy = false, type = 'default', allowAutoCreate = 'NO_OVERWRITE', + indexMode = 'standard', composedOf = [], }: Partial< TemplateDeserialized & { @@ -42,6 +43,7 @@ export const getComposableTemplate = ({ version, priority, indexPatterns, + indexMode, allowAutoCreate, template: { aliases, @@ -66,6 +68,7 @@ export const getTemplate = ({ order = getRandomNumber(), indexPatterns = [], template: { settings, aliases, mappings } = {}, + indexMode = 'standard', dataStream, composedOf, ignoreMissingComponentTemplates, @@ -85,6 +88,7 @@ export const getTemplate = ({ version, order, indexPatterns, + indexMode, allowAutoCreate, template: { aliases, diff --git a/x-pack/test/api_integration/apis/management/index_management/lib/templates.helpers.ts b/x-pack/test/api_integration/apis/management/index_management/lib/templates.helpers.ts index ea1e9a2d83bb1..a342df2d6287e 100644 --- a/x-pack/test/api_integration/apis/management/index_management/lib/templates.helpers.ts +++ b/x-pack/test/api_integration/apis/management/index_management/lib/templates.helpers.ts @@ -59,6 +59,7 @@ export function templatesHelpers(getService: FtrProviderContext['getService']) { name, indexPatterns, version: 1, + indexMode: 'standard', template: { ...getTemplateMock(isMappingsSourceFieldEnabled) }, _kbnMeta: { isLegacy, diff --git a/x-pack/test/api_integration/apis/management/index_management/templates.ts b/x-pack/test/api_integration/apis/management/index_management/templates.ts index 1fe7e022bfc9a..066df3120be08 100644 --- a/x-pack/test/api_integration/apis/management/index_management/templates.ts +++ b/x-pack/test/api_integration/apis/management/index_management/templates.ts @@ -92,6 +92,7 @@ export default function ({ getService }: FtrProviderContext) { const expectedKeys = [ 'name', 'indexPatterns', + 'indexMode', 'hasSettings', 'hasAliases', 'hasMappings', @@ -115,6 +116,7 @@ export default function ({ getService }: FtrProviderContext) { const expectedLegacyKeys = [ 'name', 'indexPatterns', + 'indexMode', 'hasSettings', 'hasAliases', 'hasMappings', @@ -138,6 +140,7 @@ export default function ({ getService }: FtrProviderContext) { const expectedWithDSLKeys = [ 'name', 'indexPatterns', + 'indexMode', 'lifecycle', 'hasSettings', 'hasAliases', @@ -163,6 +166,7 @@ export default function ({ getService }: FtrProviderContext) { const expectedWithILMKeys = [ 'name', 'indexPatterns', + 'indexMode', 'ilmPolicy', 'hasSettings', 'hasAliases', @@ -190,6 +194,7 @@ export default function ({ getService }: FtrProviderContext) { const expectedKeys = [ 'name', 'indexPatterns', + 'indexMode', 'template', 'composedOf', 'ignoreMissingComponentTemplates', @@ -213,6 +218,7 @@ export default function ({ getService }: FtrProviderContext) { const expectedKeys = [ 'name', 'indexPatterns', + 'indexMode', 'template', 'order', 'version', @@ -375,6 +381,7 @@ export default function ({ getService }: FtrProviderContext) { _kbnMeta: { hasDatastream: false, type: 'default' }, name: templateName, indexPatterns: [getRandomString()], + indexMode: 'standard', template: {}, deprecated: true, allowAutoCreate: 'TRUE', diff --git a/x-pack/test/functional/apps/index_management/index_template_wizard.ts b/x-pack/test/functional/apps/index_management/index_template_wizard.ts index 581a0b2761644..d932b96d4f6a1 100644 --- a/x-pack/test/functional/apps/index_management/index_template_wizard.ts +++ b/x-pack/test/functional/apps/index_management/index_template_wizard.ts @@ -67,6 +67,12 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const stepTitle = await testSubjects.getVisibleText('stepTitle'); expect(stepTitle).to.be('Index settings (optional)'); + // Verify that index mode callout is displayed + const indexModeCalloutText = await testSubjects.getVisibleText('indexModeCallout'); + expect(indexModeCalloutText).to.be( + 'The index.mode setting has been set to Standard within template Logistics. Any changes to index.mode set on this page will be overwritten by the Logistics selection.' + ); + // Click Next button await pageObjects.indexManagement.clickNextButton(); }); diff --git a/x-pack/test_serverless/api_integration/services/index_management/svl_templates.helpers.ts b/x-pack/test_serverless/api_integration/services/index_management/svl_templates.helpers.ts index 0f6006fd91470..8c96011936743 100644 --- a/x-pack/test_serverless/api_integration/services/index_management/svl_templates.helpers.ts +++ b/x-pack/test_serverless/api_integration/services/index_management/svl_templates.helpers.ts @@ -56,6 +56,7 @@ export function SvlTemplatesHelpers({ getService }: FtrProviderContext) { const baseTemplate: TemplateDeserialized = { name, indexPatterns, + indexMode: 'standard', version: 1, template: { ...getTemplateMock(isMappingsSourceFieldEnabled) }, _kbnMeta: { diff --git a/x-pack/test_serverless/api_integration/test_suites/common/index_management/index_templates.ts b/x-pack/test_serverless/api_integration/test_suites/common/index_management/index_templates.ts index 1e0dff8bef632..1190a642a3518 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/index_management/index_templates.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/index_management/index_templates.ts @@ -90,6 +90,7 @@ export default function ({ getService }: FtrProviderContext) { const expectedKeys = [ 'name', 'indexPatterns', + 'indexMode', 'hasSettings', 'hasAliases', 'hasMappings', @@ -114,6 +115,7 @@ export default function ({ getService }: FtrProviderContext) { const expectedKeys = [ 'name', 'indexPatterns', + 'indexMode', 'template', '_kbnMeta', 'allowAutoCreate',