[Index Management] Add data retention support to index template (#170609)

This commit is contained in:
Ignacio Rivas 2023-11-10 16:12:31 +01:00 committed by GitHub
parent 65e65a834e
commit f7ab883319
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 476 additions and 19 deletions

View file

@ -12,7 +12,7 @@ import { API_BASE_PATH } from '../../../common/constants';
import { getComposableTemplate } from '../../../test/fixtures';
import { setupEnvironment } from '../helpers';
import { TEMPLATE_NAME, INDEX_PATTERNS as DEFAULT_INDEX_PATTERNS, MAPPINGS } from './constants';
import { TEMPLATE_NAME, INDEX_PATTERNS as DEFAULT_INDEX_PATTERNS } from './constants';
import { setup } from './template_clone.helpers';
import { TemplateFormTestBed } from './template_form.helpers';
@ -37,9 +37,7 @@ jest.mock('@elastic/eui', () => {
const templateToClone = getComposableTemplate({
name: TEMPLATE_NAME,
indexPatterns: ['indexPattern1'],
template: {
mappings: MAPPINGS,
},
template: {},
allowAutoCreate: true,
});
@ -98,7 +96,7 @@ describe('<TemplateClone />', () => {
actions.clickNextButton();
});
const { priority, version, _kbnMeta, allowAutoCreate } = templateToClone;
const { template, priority, version, _kbnMeta, allowAutoCreate } = templateToClone;
expect(httpSetup.post).toHaveBeenLastCalledWith(
`${API_BASE_PATH}/index_templates`,
expect.objectContaining({
@ -109,6 +107,7 @@ describe('<TemplateClone />', () => {
version,
allowAutoCreate,
_kbnMeta,
template,
}),
})
);

View file

@ -532,6 +532,12 @@ describe('<TemplateCreate />', () => {
await actions.completeStepOne({
name: TEMPLATE_NAME,
indexPatterns: DEFAULT_INDEX_PATTERNS,
dataStream: {},
lifecycle: {
enabled: true,
value: 1,
unit: 'd',
},
allowAutoCreate: true,
});
// Component templates
@ -560,6 +566,7 @@ describe('<TemplateCreate />', () => {
name: TEMPLATE_NAME,
indexPatterns: DEFAULT_INDEX_PATTERNS,
allowAutoCreate: true,
dataStream: {},
_kbnMeta: {
type: 'default',
hasDatastream: false,
@ -582,6 +589,10 @@ describe('<TemplateCreate />', () => {
},
},
aliases: ALIASES,
lifecycle: {
enabled: true,
data_retention: '1d',
},
},
}),
})
@ -621,6 +632,11 @@ describe('<TemplateCreate />', () => {
name: TEMPLATE_NAME,
indexPatterns: DEFAULT_INDEX_PATTERNS,
dataStream: {},
lifecycle: {
enabled: true,
value: 1,
unit: 'd',
},
});
await act(async () => {
@ -631,6 +647,12 @@ describe('<TemplateCreate />', () => {
`${API_BASE_PATH}/index_templates/simulate`,
expect.objectContaining({
body: JSON.stringify({
template: {
lifecycle: {
enabled: true,
data_retention: '1d',
},
},
index_patterns: DEFAULT_INDEX_PATTERNS,
data_stream: {},
allow_auto_create: false,

View file

@ -119,6 +119,11 @@ describe('<TemplateEdit />', () => {
name: 'test',
indexPatterns: ['myPattern*'],
version: 1,
lifecycle: {
enabled: true,
value: 1,
unit: 'd',
},
});
// Component templates
await actions.completeStepTwo();
@ -150,6 +155,12 @@ describe('<TemplateEdit />', () => {
hasDatastream: true,
isLegacy: false,
},
template: {
lifecycle: {
enabled: true,
data_retention: '1d',
},
},
}),
})
);

View file

@ -146,6 +146,7 @@ export const formSetup = async (initTestBed: SetupFunc<TestSubjects>) => {
priority,
version,
dataStream,
lifecycle,
allowAutoCreate,
}: Partial<TemplateDeserialized> = {}) => {
const { component, form, find } = testBed;
@ -184,12 +185,27 @@ export const formSetup = async (initTestBed: SetupFunc<TestSubjects>) => {
if (version) {
form.setInputValue('versionField.input', JSON.stringify(version));
}
});
component.update();
if (lifecycle && lifecycle.enabled) {
act(() => {
form.toggleEuiSwitch('dataRetentionToggle.input');
});
component.update();
act(() => {
form.setInputValue('valueDataRetentionField', String(lifecycle.value));
});
}
await act(async () => {
if (allowAutoCreate) {
form.toggleEuiSwitch('allowAutoCreateField.input');
}
clickNextButton();
jest.advanceTimersByTime(0);
});
component.update();
@ -215,7 +231,6 @@ export const formSetup = async (initTestBed: SetupFunc<TestSubjects>) => {
await act(async () => {
clickNextButton();
jest.advanceTimersByTime(0);
});
component.update();
@ -337,6 +352,7 @@ export type TestSubjects =
| 'orderField.input'
| 'priorityField.input'
| 'dataStreamField.input'
| 'dataRetentionToggle.input'
| 'allowAutoCreateField.input'
| 'pageTitle'
| 'previewTab'
@ -361,6 +377,7 @@ export type TestSubjects =
| 'aliasesEditor'
| 'settingsEditor'
| 'versionField.input'
| 'valueDataRetentionField'
| 'mappingsEditor.formTab'
| 'mappingsEditor.advancedConfiguration.sizeEnabledToggle'
| 'previewIndexTemplate';

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { DataStream, EnhancedDataStreamFromEs, Health } from '../types';
import { DataStream, EnhancedDataStreamFromEs, Health, DataRetention } from '../types';
export function deserializeDataStream(dataStreamFromEs: EnhancedDataStreamFromEs): DataStream {
const {
@ -83,3 +83,43 @@ export const splitSizeAndUnits = (field: string): { size: string; unit: string }
unit,
};
};
export const serializeAsESLifecycle = (lifecycle?: DataRetention): DataStream['lifecycle'] => {
if (!lifecycle || !lifecycle?.enabled) {
return undefined;
}
const { infiniteDataRetention, value, unit } = lifecycle;
if (infiniteDataRetention) {
return {
enabled: true,
};
}
return {
enabled: true,
data_retention: `${value}${unit}`,
};
};
export const deserializeESLifecycle = (lifecycle?: DataStream['lifecycle']): DataRetention => {
if (!lifecycle || !lifecycle?.enabled) {
return { enabled: false };
}
if (!lifecycle.data_retention) {
return {
enabled: true,
infiniteDataRetention: true,
};
}
const { size, unit } = splitSizeAndUnits(lifecycle.data_retention as string);
return {
enabled: true,
value: Number(size),
unit,
};
};

View file

@ -12,6 +12,7 @@ import {
TemplateListItem,
TemplateType,
} from '../types';
import { deserializeESLifecycle } from './data_stream_serialization';
const hasEntries = (data: object = {}) => Object.entries(data).length > 0;
@ -69,6 +70,7 @@ export function deserializeTemplate(
name,
version,
priority,
...(template.lifecycle ? { lifecycle: deserializeESLifecycle(template.lifecycle) } : {}),
indexPatterns: indexPatterns.sort(),
template,
ilmPolicy: settings?.index?.lifecycle,

View file

@ -75,3 +75,10 @@ export interface DataStreamIndex {
preferILM: boolean;
managedBy: string;
}
export interface DataRetention {
enabled: boolean;
infiniteDataRetention?: boolean;
value?: number;
unit?: string;
}

View file

@ -13,7 +13,13 @@ export * from './mappings';
export * from './templates';
export type { EnhancedDataStreamFromEs, Health, DataStream, DataStreamIndex } from './data_streams';
export type {
EnhancedDataStreamFromEs,
Health,
DataStream,
DataStreamIndex,
DataRetention,
} from './data_streams';
export * from './component_templates';

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import { DataRetention, DataStream } from './data_streams';
import { IndexSettings } from './indices';
import { Aliases } from './aliases';
import { Mappings } from './mappings';
@ -18,6 +19,7 @@ export interface TemplateSerialized {
settings?: IndexSettings;
aliases?: Aliases;
mappings?: Mappings;
lifecycle?: DataStream['lifecycle'];
};
composed_of?: string[];
version?: number;
@ -40,6 +42,7 @@ export interface TemplateDeserialized {
aliases?: Aliases;
mappings?: Mappings;
};
lifecycle?: DataRetention;
composedOf?: string[]; // Composable template only
version?: number;
priority?: number; // Composable template only

View file

@ -7,7 +7,7 @@
import React, { FunctionComponent, useState } from 'react';
import { EuiFilterSelectItem, EuiPopover, EuiButtonEmpty } from '@elastic/eui';
import { UseField } from '../../../../../shared_imports';
import { UseField } from '../../../../shared_imports';
interface Props {
path: string;

View file

@ -6,6 +6,7 @@
*/
export type { CommonWizardSteps } from './components';
export {
TabAliases,
TabMappings,
@ -15,3 +16,6 @@ export {
StepSettingsContainer,
TemplateContentIndicator,
} from './components';
export { UnitField } from './fields/unit_field';
export { timeUnits } from '../../constants/time_units';

View file

@ -26,7 +26,10 @@ import {
Field,
Forms,
JsonEditorField,
NumericField,
} from '../../../../shared_imports';
import { UnitField, timeUnits } from '../../shared';
import { DataRetention } from '../../../../../common';
import { documentationService } from '../../../services/documentation';
import { schemas, nameConfig, nameConfigWithoutValidations } from '../template_form_schemas';
@ -112,6 +115,19 @@ function getFieldsMeta(esDocsBase: string) {
}),
testSubject: 'versionField',
},
dataRetention: {
title: i18n.translate('xpack.idxMgmt.templateForm.stepLogistics.dataRetentionTitle', {
defaultMessage: 'Data retention',
}),
description: i18n.translate(
'xpack.idxMgmt.templateForm.stepLogistics.dataRetentionDescription',
{
defaultMessage:
'Data will be kept at least this long before being automatically deleted.',
}
),
unitTestSubject: 'unitDataRetentionField',
},
allowAutoCreate: {
title: i18n.translate('xpack.idxMgmt.templateForm.stepLogistics.allowAutoCreateTitle', {
defaultMessage: 'Allow auto create',
@ -177,9 +193,18 @@ export const StepLogistics: React.FunctionComponent<Props> = React.memo(
getFormData,
} = form;
const [{ addMeta }] = useFormData<{ addMeta: boolean }>({
const [{ addMeta, doCreateDataStream, lifecycle }] = useFormData<{
addMeta: boolean;
lifecycle: DataRetention;
doCreateDataStream: boolean;
}>({
form,
watch: 'addMeta',
watch: [
'addMeta',
'lifecycle.enabled',
'lifecycle.infiniteDataRetention',
'doCreateDataStream',
],
});
/**
@ -198,8 +223,16 @@ export const StepLogistics: React.FunctionComponent<Props> = React.memo(
});
}, [onChange, isFormValid, validate, getFormData]);
const { name, indexPatterns, createDataStream, order, priority, version, allowAutoCreate } =
getFieldsMeta(documentationService.getEsDocsBase());
const {
name,
indexPatterns,
createDataStream,
order,
priority,
version,
dataRetention,
allowAutoCreate,
} = getFieldsMeta(documentationService.getEsDocsBase());
return (
<>
@ -272,6 +305,61 @@ export const StepLogistics: React.FunctionComponent<Props> = React.memo(
</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.
*/}
{doCreateDataStream && (
<FormRow
title={dataRetention.title}
description={
<>
{dataRetention.description}
<EuiSpacer size="m" />
<UseField
path="lifecycle.enabled"
componentProps={{ 'data-test-subj': 'dataRetentionToggle' }}
/>
</>
}
>
{lifecycle?.enabled && (
<UseField
path="lifecycle.value"
component={NumericField}
labelAppend={
<UseField
path="lifecycle.infiniteDataRetention"
data-test-subj="infiniteDataRetentionToggle"
componentProps={{
euiFieldProps: {
compressed: true,
},
}}
/>
}
componentProps={{
euiFieldProps: {
disabled: lifecycle?.infiniteDataRetention,
'data-test-subj': 'valueDataRetentionField',
min: 1,
append: (
<UnitField
path="lifecycle.unit"
options={timeUnits}
disabled={lifecycle?.infiniteDataRetention}
euiFieldProps={{
'data-test-subj': 'unitDataRetentionField',
}}
/>
),
},
}}
/>
)}
</FormRow>
)}
{/* Order */}
{isLegacy && (
<FormRow title={order.title} description={order.description}>

View file

@ -27,9 +27,11 @@ import { serializers } from '../../../../shared_imports';
import { serializeLegacyTemplate, serializeTemplate } from '../../../../../common/lib';
import { TemplateDeserialized, getTemplateParameter, Aliases } from '../../../../../common';
import { SimulateTemplate } from '../../index_templates';
import { getLifecycleValue } from '../../../lib/data_streams';
import { WizardSection } from '../template_form';
const { stripEmptyFields } = serializers;
const INFINITE_AS_ICON = true;
const NoneDescriptionText = () => (
<FormattedMessage
@ -87,6 +89,7 @@ export const StepReview: React.FunctionComponent<Props> = React.memo(
indexPatterns,
version,
order,
template: indexTemplate,
priority,
allowAutoCreate,
composedOf,
@ -109,6 +112,7 @@ export const StepReview: React.FunctionComponent<Props> = React.memo(
const serializedMappings = getTemplateParameter(serializedTemplate, 'mappings');
const serializedSettings = getTemplateParameter(serializedTemplate, 'settings');
const serializedAliases = getTemplateParameter(serializedTemplate, 'aliases');
const serializedLifecycle = (indexTemplate as TemplateDeserialized)?.lifecycle;
const numIndexPatterns = indexPatterns!.length;
@ -274,6 +278,20 @@ export const StepReview: React.FunctionComponent<Props> = React.memo(
{getDescriptionText(serializedAliases)}
</EuiDescriptionListDescription>
{isLegacy !== true && serializedLifecycle?.enabled && (
<>
<EuiDescriptionListTitle data-test-subj="lifecycleTitle">
<FormattedMessage
id="xpack.idxMgmt.templateForm.stepReview.summaryTab.lifecycleLabel"
defaultMessage="Data retention"
/>
</EuiDescriptionListTitle>
<EuiDescriptionListDescription data-test-subj="lifecycleValue">
{getLifecycleValue(serializedLifecycle, INFINITE_AS_ICON)}
</EuiDescriptionListDescription>
</>
)}
{/* Metadata (optional) */}
{isLegacy !== true && _meta && (
<>

View file

@ -21,6 +21,7 @@ import {
} from '../shared';
import { documentationService } from '../../services/documentation';
import { SectionError } from '../section_error';
import { serializeAsESLifecycle } from '../../../../common/lib/data_stream_serialization';
import {
SimulateTemplateFlyoutContent,
SimulateTemplateProps,
@ -190,6 +191,9 @@ export const TemplateForm = ({
if (Object.keys(outputTemplate.template).length === 0) {
delete outputTemplate.template;
}
if (outputTemplate.lifecycle) {
delete outputTemplate.lifecycle;
}
}
return outputTemplate;
@ -206,10 +210,13 @@ export const TemplateForm = ({
settings: wizardData.settings,
mappings: wizardData.mappings,
aliases: wizardData.aliases,
lifecycle: wizardData.logistics.lifecycle
? serializeAsESLifecycle(wizardData.logistics.lifecycle)
: undefined,
},
};
return cleanupTemplateObject(outputTemplate);
return cleanupTemplateObject(outputTemplate as TemplateDeserialized);
},
[]
);

View file

@ -158,6 +158,85 @@ export const schemas: Record<string, FormSchema> = {
}),
formatters: [toInt],
},
'lifecycle.enabled': {
type: FIELD_TYPES.TOGGLE,
label: i18n.translate('xpack.idxMgmt.templateForm.stepLogistics.enableDataRetentionLabel', {
defaultMessage: 'Enable data retention',
}),
defaultValue: false,
},
'lifecycle.infiniteDataRetention': {
type: FIELD_TYPES.TOGGLE,
label: i18n.translate('xpack.idxMgmt.templateForm.stepLogistics.infiniteDataRetentionLabel', {
defaultMessage: 'Keep data indefinitely',
}),
defaultValue: false,
},
'lifecycle.value': {
type: FIELD_TYPES.TEXT,
label: i18n.translate(
'xpack.idxMgmt.templateForm.stepLogistics.fieldDataRetentionValueLabel',
{
defaultMessage: 'Data Retention',
}
),
formatters: [toInt],
validations: [
{
validator: ({ value, formData }) => {
// If infiniteRetentionPeriod is set, we dont need to validate the data retention field
if (formData['lifecycle.infiniteDataRetention']) {
return undefined;
}
if (!value) {
return {
message: i18n.translate(
'xpack.idxMgmt.templateForm.stepLogistics.dataRetentionFieldRequiredError',
{
defaultMessage: 'A data retention value is required.',
}
),
};
}
if (value <= 0) {
return {
message: i18n.translate(
'xpack.idxMgmt.templateForm.stepLogistics.dataRetentionFieldNonNegativeError',
{
defaultMessage: `A positive value is required.`,
}
),
};
}
if (value % 1 !== 0) {
return {
message: i18n.translate(
'xpack.idxMgmt.templateForm.stepLogistics.dataRetentionFieldDecimalError',
{
defaultMessage: `The value should be an integer number.`,
}
),
};
}
},
},
],
},
'lifecycle.unit': {
type: FIELD_TYPES.TEXT,
label: i18n.translate(
'xpack.idxMgmt.templateForm.stepLogistics.fieldDataRetentionUnitLabel',
{
defaultMessage: 'Time unit',
}
),
defaultValue: 'd',
},
allowAutoCreate: {
type: FIELD_TYPES.TOGGLE,
label: i18n.translate('xpack.idxMgmt.templateForm.stepLogistics.fieldAllowAutoCreateLabel', {

View file

@ -42,7 +42,7 @@ import { splitSizeAndUnits, DataStream } from '../../../../../../common';
import { timeUnits } from '../../../../constants/time_units';
import { isDSLWithILMIndices } from '../../../../lib/data_streams';
import { useAppContext } from '../../../../app_context';
import { UnitField } from './unit_field';
import { UnitField } from '../../../../components/shared';
import { updateDataRetention } from '../../../../services/api';
interface Props {

View file

@ -20,6 +20,8 @@ import {
EuiCodeBlock,
EuiSpacer,
} from '@elastic/eui';
import { serializeAsESLifecycle } from '../../../../../../../common/lib/data_stream_serialization';
import { getLifecycleValue } from '../../../../../lib/data_streams';
import { TemplateDeserialized } from '../../../../../../../common';
import { ILM_PAGES_POLICY_EDIT } from '../../../../../constants';
import { useIlmLocator } from '../../../../../services/use_ilm_locator';
@ -28,6 +30,7 @@ interface Props {
templateDetails: TemplateDeserialized;
}
const INFINITE_AS_ICON = true;
const i18nTexts = {
yes: i18n.translate('xpack.idxMgmt.templateDetails.summaryTab.yesDescriptionText', {
defaultMessage: 'Yes',
@ -194,6 +197,24 @@ export const TabSummary: React.FunctionComponent<Props> = ({ templateDetails })
{version || version === 0 ? version : i18nTexts.none}
</EuiDescriptionListDescription>
{/* Data retention */}
{hasDatastream && templateDetails?.lifecycle && (
<>
<EuiDescriptionListTitle>
<FormattedMessage
id="xpack.idxMgmt.templateDetails.summaryTab.lifecycleDescriptionListTitle"
defaultMessage="Data retention"
/>
</EuiDescriptionListTitle>
<EuiDescriptionListDescription>
{getLifecycleValue(
serializeAsESLifecycle(templateDetails.lifecycle),
INFINITE_AS_ICON
)}
</EuiDescriptionListDescription>
</>
)}
{/* Allow auto create */}
{isLegacy !== true && (
<>

View file

@ -19,6 +19,12 @@ export const templateSchema = schema.object({
settings: schema.maybe(schema.object({}, { unknowns: 'allow' })),
aliases: schema.maybe(schema.object({}, { unknowns: 'allow' })),
mappings: schema.maybe(schema.object({}, { unknowns: 'allow' })),
lifecycle: schema.maybe(
schema.object({
enabled: schema.boolean(),
data_retention: schema.maybe(schema.string()),
})
),
})
),
composedOf: schema.maybe(schema.arrayOf(schema.string())),

View file

@ -7,6 +7,7 @@
import { getRandomString, getRandomNumber } from '@kbn/test-jest-helpers';
import { TemplateDeserialized, TemplateType, TemplateListItem } from '../../common';
import { IndexSettings, Aliases, Mappings, DataStream } from '../../common/types';
const objHasProperties = (obj?: Record<string, any>): boolean => {
return obj === undefined || Object.keys(obj).length === 0 ? false : true;
@ -17,7 +18,7 @@ export const getComposableTemplate = ({
version = getRandomNumber(),
priority = getRandomNumber(),
indexPatterns = [],
template: { settings, aliases, mappings } = {},
template: { settings, aliases, mappings, lifecycle } = {},
hasDatastream = false,
isLegacy = false,
type = 'default',
@ -27,6 +28,12 @@ export const getComposableTemplate = ({
isLegacy?: boolean;
type?: TemplateType;
hasDatastream: boolean;
template?: {
settings?: IndexSettings;
aliases?: Aliases;
mappings?: Mappings;
lifecycle?: DataStream['lifecycle'];
};
}
> = {}): TemplateDeserialized => {
const indexTemplate = {
@ -39,6 +46,7 @@ export const getComposableTemplate = ({
aliases,
mappings,
settings,
lifecycle,
},
_kbnMeta: {
type,

View file

@ -30,9 +30,26 @@ export default function ({ getService }) {
after(() => Promise.all([cleanUpEsResources(), cleanUpTemplates()]));
describe('get all', () => {
const templateName = `template-${getRandomString()}`;
const indexTemplate = getTemplatePayload(templateName, [getRandomString()]);
const legacyTemplate = getTemplatePayload(templateName, [getRandomString()], true);
const indexTemplate = getTemplatePayload(`template-${getRandomString()}`, [
getRandomString(),
]);
const legacyTemplate = getTemplatePayload(
`template-${getRandomString()}`,
[getRandomString()],
true
);
const tmpTemplate = getTemplatePayload(`template-${getRandomString()}`, [getRandomString()]);
const indexTemplateWithLifecycle = {
...tmpTemplate,
dataStream: {},
template: {
...tmpTemplate.template,
lifecycle: {
enabled: true,
data_retention: '10d',
},
},
};
beforeEach(async () => {
const res1 = await createTemplate(indexTemplate);
@ -44,6 +61,11 @@ export default function ({ getService }) {
if (res2.status !== 200) {
throw new Error(res2.body.message);
}
const res3 = await createTemplate(indexTemplateWithLifecycle);
if (res3.status !== 200) {
throw new Error(res3.body.message);
}
});
it('should list all the index templates with the expected parameters', async () => {
@ -107,6 +129,36 @@ export default function ({ getService }) {
].sort();
expect(Object.keys(legacyTemplateFound).sort()).to.eql(expectedLegacyKeys);
// Index template with lifecycle
const templateWithLifecycle = allTemplates.templates.find(
(template) => template.name === indexTemplateWithLifecycle.name
);
if (!templateWithLifecycle) {
throw new Error(
`Index template with lifecycle "${
indexTemplateWithLifecycle.name
}" not found in ${JSON.stringify(allTemplates.templates, null, 2)}`
);
}
const expectedWithLifecycleKeys = [
'name',
'indexPatterns',
'lifecycle',
'hasSettings',
'hasAliases',
'hasMappings',
'ilmPolicy',
'priority',
'composedOf',
'dataStream',
'version',
'_kbnMeta',
].sort();
expect(Object.keys(templateWithLifecycle).sort()).to.eql(expectedWithLifecycleKeys);
});
});

View file

@ -0,0 +1,53 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../../ftr_provider_context';
export default ({ getPageObjects, getService }: FtrProviderContext) => {
const pageObjects = getPageObjects(['common', 'indexManagement', 'header']);
const log = getService('log');
const security = getService('security');
const testSubjects = getService('testSubjects');
const INDEX_TEMPLATE_NAME = `test-index-template`;
describe('Create index template', function () {
before(async () => {
await log.debug('Navigating to the index templates tab');
await security.testUser.setRoles(['index_management_user']);
await pageObjects.common.navigateToApp('indexManagement');
// Navigate to the data streams tab
await pageObjects.indexManagement.changeTabs('templatesTab');
await pageObjects.header.waitUntilLoadingHasFinished();
// Click create template button
await testSubjects.click('createTemplateButton');
});
it('can create an index template with data retention', async () => {
// Complete required fields from step 1
await testSubjects.setValue('nameField', INDEX_TEMPLATE_NAME);
await testSubjects.setValue('indexPatternsField', 'test-1');
// Enable data stream
await testSubjects.click('dataStreamField > input');
// Enable data retention
await testSubjects.click('dataRetentionToggle > input');
// Set the retention to 7 hours
await testSubjects.setValue('valueDataRetentionField', '7');
await testSubjects.click('show-filters-button');
await testSubjects.click('filter-option-h');
// Navigate to the last step of the wizard
await testSubjects.click('nextButton');
await testSubjects.click('nextButton');
await testSubjects.click('nextButton');
await testSubjects.click('nextButton');
await testSubjects.click('nextButton');
expect(await testSubjects.getVisibleText('lifecycleValue')).to.be('7 hours');
});
});
};

View file

@ -0,0 +1,14 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../../../ftr_provider_context';
export default ({ loadTestFile }: FtrProviderContext) => {
describe('Index Management: index templates tab', function () {
loadTestFile(require.resolve('./create_index_template'));
});
};