[8.x] [Index Management] Add index mode field in index template form (#199521) (#200244)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[Index Management] Add index mode field in index template form
(#199521)](https://github.com/elastic/kibana/pull/199521)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Elena
Stoeva","email":"59341489+ElenaStoeva@users.noreply.github.com"},"sourceCommit":{"committedDate":"2024-11-14T18:58:42Z","message":"[Index
Management] Add index mode field in index template form
(#199521)\n\nCloses
https://github.com/elastic/kibana/issues/198620\r\n\r\n##
Summary\r\n\r\nThis PR adds a field for index mode setting in the
Logistics step in\r\nIndex Template
form.\r\n\r\n\r\n\r\nhttps://github.com/user-attachments/assets/ee38bdec-66ff-468d-a55e-abf5354c3da2\r\n\r\n\r\n**How
to test:**\r\n1. Go to Index Management -> Index Templates and start
creating an index\r\ntemplate\r\n2. Verify that the index mode is only
enabled if the data stream toggle\r\nis on.\r\n3. Verify that typing the
`logs-*-*` index pattern sets the index mode\r\nto
\"LogsDB\":\r\n\r\n<img width=\"1401\" alt=\"Screenshot 2024-11-13 at 13
00
10\"\r\nsrc=\"https://github.com/user-attachments/assets/d1825d08-5039-4c43-80a8-653233e0b677\">\r\n\r\n\r\n\r\n4.
Go to the Settings step and verify that the index mode callout
is\r\ndisplayed correctly.\r\n5. Go to Review step and verify that Index
mode is displayed correctly\r\nin both the summary and the preview
request.\r\n6. Save the template and verify that the template details
tab correctly\r\ndisplays the index mode.\r\n\r\n<img width=\"1565\"
alt=\"Screenshot 2024-11-13 at 17 22
54\"\r\nsrc=\"https://github.com/user-attachments/assets/2055501b-32c9-463c-b61d-541b9687b459\">\r\n\r\n<img
width=\"1565\" alt=\"Screenshot 2024-11-13 at 17 22
31\"\r\nsrc=\"https://github.com/user-attachments/assets/21c9cf9e-5858-4403-9106-57ed8ccf3639\">\r\n\r\n\r\n\r\n\r\n\r\n###
Checklist\r\n\r\nDelete any items that are not applicable to this
PR.\r\n\r\n- [x] Any text added follows [EUI's
writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\r\nsentence case text and includes
[i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n-
[x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n- [x] Any UI
touched in this PR is usable by keyboard only (learn more\r\nabout
[keyboard accessibility](https://webaim.org/techniques/keyboard/))\r\n-
[x] Any UI touched in this PR does not create any new axe
failures\r\n(run axe in
browser:\r\n[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),\r\n[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))\r\n-
[x] This renders correctly on smaller devices using a
responsive\r\nlayout. (You can test this [in
your\r\nbrowser](https://www.browserstack.com/guide/responsive-testing-on-local-server))\r\n-
[x] This was checked for
[cross-browser\r\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)","sha":"16127fcc8faf128cfd9d2feffc6086eb6330c11f","branchLabelMapping":{"^v9.0.0$":"main","^v8.17.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["Feature:Index
Management","Team:Kibana
Management","release_note:skip","v9.0.0","backport:prev-minor","v8.17.0"],"title":"[Index
Management] Add index mode field in index template
form","number":199521,"url":"https://github.com/elastic/kibana/pull/199521","mergeCommit":{"message":"[Index
Management] Add index mode field in index template form
(#199521)\n\nCloses
https://github.com/elastic/kibana/issues/198620\r\n\r\n##
Summary\r\n\r\nThis PR adds a field for index mode setting in the
Logistics step in\r\nIndex Template
form.\r\n\r\n\r\n\r\nhttps://github.com/user-attachments/assets/ee38bdec-66ff-468d-a55e-abf5354c3da2\r\n\r\n\r\n**How
to test:**\r\n1. Go to Index Management -> Index Templates and start
creating an index\r\ntemplate\r\n2. Verify that the index mode is only
enabled if the data stream toggle\r\nis on.\r\n3. Verify that typing the
`logs-*-*` index pattern sets the index mode\r\nto
\"LogsDB\":\r\n\r\n<img width=\"1401\" alt=\"Screenshot 2024-11-13 at 13
00
10\"\r\nsrc=\"https://github.com/user-attachments/assets/d1825d08-5039-4c43-80a8-653233e0b677\">\r\n\r\n\r\n\r\n4.
Go to the Settings step and verify that the index mode callout
is\r\ndisplayed correctly.\r\n5. Go to Review step and verify that Index
mode is displayed correctly\r\nin both the summary and the preview
request.\r\n6. Save the template and verify that the template details
tab correctly\r\ndisplays the index mode.\r\n\r\n<img width=\"1565\"
alt=\"Screenshot 2024-11-13 at 17 22
54\"\r\nsrc=\"https://github.com/user-attachments/assets/2055501b-32c9-463c-b61d-541b9687b459\">\r\n\r\n<img
width=\"1565\" alt=\"Screenshot 2024-11-13 at 17 22
31\"\r\nsrc=\"https://github.com/user-attachments/assets/21c9cf9e-5858-4403-9106-57ed8ccf3639\">\r\n\r\n\r\n\r\n\r\n\r\n###
Checklist\r\n\r\nDelete any items that are not applicable to this
PR.\r\n\r\n- [x] Any text added follows [EUI's
writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\r\nsentence case text and includes
[i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n-
[x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n- [x] Any UI
touched in this PR is usable by keyboard only (learn more\r\nabout
[keyboard accessibility](https://webaim.org/techniques/keyboard/))\r\n-
[x] Any UI touched in this PR does not create any new axe
failures\r\n(run axe in
browser:\r\n[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),\r\n[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))\r\n-
[x] This renders correctly on smaller devices using a
responsive\r\nlayout. (You can test this [in
your\r\nbrowser](https://www.browserstack.com/guide/responsive-testing-on-local-server))\r\n-
[x] This was checked for
[cross-browser\r\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)","sha":"16127fcc8faf128cfd9d2feffc6086eb6330c11f"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/199521","number":199521,"mergeCommit":{"message":"[Index
Management] Add index mode field in index template form
(#199521)\n\nCloses
https://github.com/elastic/kibana/issues/198620\r\n\r\n##
Summary\r\n\r\nThis PR adds a field for index mode setting in the
Logistics step in\r\nIndex Template
form.\r\n\r\n\r\n\r\nhttps://github.com/user-attachments/assets/ee38bdec-66ff-468d-a55e-abf5354c3da2\r\n\r\n\r\n**How
to test:**\r\n1. Go to Index Management -> Index Templates and start
creating an index\r\ntemplate\r\n2. Verify that the index mode is only
enabled if the data stream toggle\r\nis on.\r\n3. Verify that typing the
`logs-*-*` index pattern sets the index mode\r\nto
\"LogsDB\":\r\n\r\n<img width=\"1401\" alt=\"Screenshot 2024-11-13 at 13
00
10\"\r\nsrc=\"https://github.com/user-attachments/assets/d1825d08-5039-4c43-80a8-653233e0b677\">\r\n\r\n\r\n\r\n4.
Go to the Settings step and verify that the index mode callout
is\r\ndisplayed correctly.\r\n5. Go to Review step and verify that Index
mode is displayed correctly\r\nin both the summary and the preview
request.\r\n6. Save the template and verify that the template details
tab correctly\r\ndisplays the index mode.\r\n\r\n<img width=\"1565\"
alt=\"Screenshot 2024-11-13 at 17 22
54\"\r\nsrc=\"https://github.com/user-attachments/assets/2055501b-32c9-463c-b61d-541b9687b459\">\r\n\r\n<img
width=\"1565\" alt=\"Screenshot 2024-11-13 at 17 22
31\"\r\nsrc=\"https://github.com/user-attachments/assets/21c9cf9e-5858-4403-9106-57ed8ccf3639\">\r\n\r\n\r\n\r\n\r\n\r\n###
Checklist\r\n\r\nDelete any items that are not applicable to this
PR.\r\n\r\n- [x] Any text added follows [EUI's
writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\r\nsentence case text and includes
[i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n-
[x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n- [x] Any UI
touched in this PR is usable by keyboard only (learn more\r\nabout
[keyboard accessibility](https://webaim.org/techniques/keyboard/))\r\n-
[x] Any UI touched in this PR does not create any new axe
failures\r\n(run axe in
browser:\r\n[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),\r\n[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))\r\n-
[x] This renders correctly on smaller devices using a
responsive\r\nlayout. (You can test this [in
your\r\nbrowser](https://www.browserstack.com/guide/responsive-testing-on-local-server))\r\n-
[x] This was checked for
[cross-browser\r\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)","sha":"16127fcc8faf128cfd9d2feffc6086eb6330c11f"}},{"branch":"8.x","label":"v8.17.0","branchLabelMappingKey":"^v8.17.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Elena Stoeva <59341489+ElenaStoeva@users.noreply.github.com>
This commit is contained in:
Kibana Machine 2024-11-15 07:47:45 +11:00 committed by GitHub
parent 798e18cb18
commit 7ef99b3f6d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 403 additions and 56 deletions

View file

@ -52,6 +52,15 @@ jest.mock('@elastic/eui', () => {
}}
/>
),
EuiSuperSelect: (props: any) => (
<input
data-test-subj={props['data-test-subj'] || 'mockSuperSelect'}
value={props.valueOfSelected}
onChange={(e) => {
props.onChange(e.target.value);
}}
/>
),
};
});
@ -301,6 +310,15 @@ describe('<TemplateCreate />', () => {
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('<TemplateCreate />', () => {
});
});
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('<TemplateCreate />', () => {
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('<TemplateCreate />', () => {
name: TEMPLATE_NAME,
indexPatterns: DEFAULT_INDEX_PATTERNS,
allowAutoCreate: 'TRUE',
indexMode: 'time_series',
dataStream: {},
_kbnMeta: {
type: 'default',

View file

@ -169,6 +169,7 @@ describe('<TemplateEdit />', () => {
indexPatterns: ['myPattern*'],
version: 1,
allowAutoCreate: 'NO_OVERWRITE',
indexMode: 'standard',
dataStream: {
hidden: true,
anyUnknownKey: 'should_be_kept',

View file

@ -148,6 +148,7 @@ export const formSetup = async (initTestBed: SetupFunc<TestSubjects>) => {
enableDataStream,
lifecycle,
allowAutoCreate,
indexMode,
}: Partial<TemplateDeserialized> & { enableDataStream?: boolean } = {}) => {
const { component, form, find } = testBed;
@ -204,6 +205,10 @@ export const formSetup = async (initTestBed: SetupFunc<TestSubjects>) => {
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'

View file

@ -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

View file

@ -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';

View file

@ -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);
});
});
});
});
});

View file

@ -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 ?? [],

View file

@ -19,6 +19,7 @@ export type {
DataStream,
DataStreamIndex,
DataRetention,
IndexMode,
} from './data_streams';
export * from './component_templates';

View file

@ -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;
};

View file

@ -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<Props> = 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<Props> = React.memo(
<EuiSpacer size="l" />
{indexMode && (
<>
<EuiCallOut
title={
<FormattedMessage
id="xpack.idxMgmt.formWizard.stepSettings.indexModeCallout.title"
defaultMessage="The {settingName} setting has been set to {indexMode} within template {logisticsLink}. Any changes to {settingName} set on this page will be overwritten by the Logistics selection."
values={{
settingName: (
<EuiCode>
{i18n.translate(
'xpack.idxMgmt.formWizard.stepSettings.indexModeCallout.indexModeSettingLabel',
{
defaultMessage: 'index.mode',
}
)}
</EuiCode>
),
indexMode: indexModeLabels[indexMode],
logisticsLink: (
<EuiLink onClick={() => navigateToStep(0)}>
{i18n.translate(
'xpack.idxMgmt.formWizard.stepSettings.indexModeCallout.logisticsLinkLabel',
{
defaultMessage: 'Logistics',
}
)}
</EuiLink>
),
}}
/>
}
color="warning"
iconType="warning"
data-test-subj="indexModeCallout"
/>
<EuiSpacer size="l" />
</>
)}
{/* Settings code editor */}
<EuiFormRow
label={

View file

@ -8,19 +8,36 @@
import React from 'react';
import { Forms } from '../../../../../shared_imports';
import { TemplateDeserialized } from '../../../../../../common';
import { WizardContent } from '../../../template_form/template_form';
import { CommonWizardSteps } from './types';
import { StepSettings } from './step_settings';
interface Props {
esDocsBase: string;
getTemplateData?: (wizardContent: WizardContent) => TemplateDeserialized;
}
export const StepSettingsContainer = React.memo(({ esDocsBase }: Props) => {
export const StepSettingsContainer = React.memo(({ esDocsBase, getTemplateData }: Props) => {
const { defaultValue, updateContent } = Forms.useContent<CommonWizardSteps, 'settings'>(
'settings'
);
const { getData } = Forms.useMultiContentContext<WizardContent>();
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 (
<StepSettings defaultValue={defaultValue} onChange={updateContent} esDocsBase={esDocsBase} />
<StepSettings
defaultValue={defaultValue}
onChange={updateContent}
esDocsBase={esDocsBase}
indexMode={indexMode}
/>
);
});

View file

@ -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: (
<Fragment>
<strong>{indexModeLabels[STANDARD_INDEX_MODE]}</strong>
<EuiText size="s" color="subdued">
<p>{indexModeDescriptions[STANDARD_INDEX_MODE]}</p>
</EuiText>
</Fragment>
),
},
{
value: TIME_SERIES_MODE,
inputDisplay: indexModeLabels[TIME_SERIES_MODE],
dropdownDisplay: (
<Fragment>
<strong>{indexModeLabels[TIME_SERIES_MODE]}</strong>
<EuiText size="s" color="subdued">
<p>{indexModeDescriptions[TIME_SERIES_MODE]}</p>
</EuiText>
</Fragment>
),
},
{
value: LOGSDB_INDEX_MODE,
inputDisplay: indexModeLabels[LOGSDB_INDEX_MODE],
dropdownDisplay: (
<Fragment>
<strong>{indexModeLabels[LOGSDB_INDEX_MODE]}</strong>
<EuiText size="s" color="subdued">
<p>{indexModeDescriptions[LOGSDB_INDEX_MODE]}</p>
</EuiText>
</Fragment>
),
},
],
testSubject: 'indexModeField',
},
order: {
title: i18n.translate('xpack.idxMgmt.templateForm.stepLogistics.orderTitle', {
defaultMessage: 'Merge order',
@ -198,21 +253,37 @@ export const StepLogistics: React.FunctionComponent<Props> = 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<Props> = React.memo(
name,
indexPatterns,
createDataStream,
indexMode,
order,
priority,
version,
@ -312,6 +384,21 @@ export const StepLogistics: React.FunctionComponent<Props> = React.memo(
</FormRow>
)}
{doCreateDataStream && (
<FormRow title={indexMode.title} description={indexMode.description}>
<UseField
path="indexMode"
componentProps={{
euiFieldProps: {
hasDividers: true,
'data-test-subj': indexMode.testSubject,
options: indexMode.options,
},
}}
/>
</FormRow>
)}
{/*
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.

View file

@ -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<Props> = React.memo(
const {
name,
indexPatterns,
indexMode,
version,
order,
template: indexTemplate,
@ -277,9 +278,7 @@ export const StepReview: React.FunctionComponent<Props> = React.memo(
/>
</EuiDescriptionListTitle>
<EuiDescriptionListDescription data-test-subj="indexModeValue">
{getIndexModeLabel(
serializedSettings?.['index.mode'] ?? serializedSettings?.index?.mode
)}
{indexModeLabels[indexMode]}
</EuiDescriptionListDescription>
{/* Mappings */}

View file

@ -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 = ({
)}
<FormWizardStep id={wizardSections.settings.id} label={wizardSections.settings.label}>
<StepSettingsContainer esDocsBase={documentationService.getEsDocsBase()} />
<StepSettingsContainer
esDocsBase={documentationService.getEsDocsBase()}
getTemplateData={buildTemplateObject(indexTemplate)}
/>
</FormWizardStep>
<FormWizardStep id={wizardSections.mappings.id} label={wizardSections.mappings.label}>

View file

@ -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<string, FormSchema> = {
}),
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', {

View file

@ -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',
}
),
};

View file

@ -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<Props> = ({
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',
},
{

View file

@ -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<Props> = ({
}),
truncateText: true,
sortable: true,
render: (indexMode: DataStream['indexMode']) => getIndexModeLabel(indexMode),
render: (indexMode: DataStream['indexMode']) => indexModeLabels[indexMode],
});
columns.push({

View file

@ -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<Props> = ({ 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<Props> = ({ templateDetails })
/>
</EuiDescriptionListTitle>
<EuiDescriptionListDescription>
{getIndexModeLabel(template?.settings?.index?.mode)}
{indexModeLabels[indexMode]}
</EuiDescriptionListDescription>
{/* Allow auto create */}

View file

@ -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(

View file

@ -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,

View file

@ -59,6 +59,7 @@ export function templatesHelpers(getService: FtrProviderContext['getService']) {
name,
indexPatterns,
version: 1,
indexMode: 'standard',
template: { ...getTemplateMock(isMappingsSourceFieldEnabled) },
_kbnMeta: {
isLegacy,

View file

@ -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',

View file

@ -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();
});

View file

@ -56,6 +56,7 @@ export function SvlTemplatesHelpers({ getService }: FtrProviderContext) {
const baseTemplate: TemplateDeserialized = {
name,
indexPatterns,
indexMode: 'standard',
version: 1,
template: { ...getTemplateMock(isMappingsSourceFieldEnabled) },
_kbnMeta: {

View file

@ -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',