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 5448c932d65e0..2695da7adb813 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 { @@ -150,6 +151,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',