mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
# Backport This will backport the following commits from `main` to `8.11`: - [[Index Management] Fix small edit lifecycle bugs (#168560)](https://github.com/elastic/kibana/pull/168560) <!--- Backport version: 8.9.8 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Ignacio Rivas","email":"rivasign@gmail.com"},"sourceCommit":{"committedDate":"2023-10-16T09:51:50Z","message":"[Index Management] Fix small edit lifecycle bugs (#168560)","sha":"3488c83dc5abef2741cabfce277202a7f24ff951","branchLabelMapping":{"^v8.12.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["Feature:Index Management","Team:Deployment Management","release_note:skip","backport:skip","v8.11.0","v8.12.0"],"number":168560,"url":"https://github.com/elastic/kibana/pull/168560","mergeCommit":{"message":"[Index Management] Fix small edit lifecycle bugs (#168560)","sha":"3488c83dc5abef2741cabfce277202a7f24ff951"}},"sourceBranch":"main","suggestedTargetBranches":["8.11"],"targetPullRequestStates":[{"branch":"8.11","label":"v8.11.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.12.0","labelRegex":"^v8.12.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/168560","number":168560,"mergeCommit":{"message":"[Index Management] Fix small edit lifecycle bugs (#168560)","sha":"3488c83dc5abef2741cabfce277202a7f24ff951"}}]}] BACKPORT-->
This commit is contained in:
parent
9659b2bdec
commit
70b88bed7b
11 changed files with 194 additions and 60 deletions
|
@ -102,4 +102,5 @@ export type TestSubjects =
|
|||
| 'filter-option-h'
|
||||
| 'infiniteRetentionPeriod.input'
|
||||
| 'saveButton'
|
||||
| 'dataRetentionDetail'
|
||||
| 'createIndexSaveButton';
|
||||
|
|
|
@ -278,6 +278,7 @@ export const createDataStreamPayload = (dataStream: Partial<DataStream>): DataSt
|
|||
},
|
||||
hidden: false,
|
||||
lifecycle: {
|
||||
enabled: true,
|
||||
data_retention: '7d',
|
||||
},
|
||||
...dataStream,
|
||||
|
|
|
@ -338,6 +338,55 @@ describe('Data Streams tab', () => {
|
|||
});
|
||||
|
||||
describe('update data retention', () => {
|
||||
test('Should show disabled or infinite retention period accordingly in table and flyout', async () => {
|
||||
const { setLoadDataStreamsResponse, setLoadDataStreamResponse } = httpRequestsMockHelpers;
|
||||
|
||||
const ds1 = createDataStreamPayload({
|
||||
name: 'dataStream1',
|
||||
lifecycle: {
|
||||
enabled: false,
|
||||
},
|
||||
});
|
||||
const ds2 = createDataStreamPayload({
|
||||
name: 'dataStream2',
|
||||
lifecycle: {
|
||||
enabled: true,
|
||||
},
|
||||
});
|
||||
|
||||
setLoadDataStreamsResponse([ds1, ds2]);
|
||||
setLoadDataStreamResponse(ds1.name, ds1);
|
||||
|
||||
testBed = await setup(httpSetup, {
|
||||
history: createMemoryHistory(),
|
||||
url: urlServiceMock,
|
||||
});
|
||||
await act(async () => {
|
||||
testBed.actions.goToDataStreamsList();
|
||||
});
|
||||
testBed.component.update();
|
||||
|
||||
const { actions, find, table } = testBed;
|
||||
const { tableCellsValues } = table.getMetaData('dataStreamTable');
|
||||
|
||||
expect(tableCellsValues).toEqual([
|
||||
['', 'dataStream1', 'green', '1', 'Disabled', 'Delete'],
|
||||
['', 'dataStream2', 'green', '1', '', 'Delete'],
|
||||
]);
|
||||
|
||||
await actions.clickNameAt(0);
|
||||
expect(find('dataRetentionDetail').text()).toBe('Disabled');
|
||||
|
||||
await act(async () => {
|
||||
testBed.find('closeDetailsButton').simulate('click');
|
||||
});
|
||||
testBed.component.update();
|
||||
|
||||
setLoadDataStreamResponse(ds2.name, ds2);
|
||||
await actions.clickNameAt(1);
|
||||
expect(find('dataRetentionDetail').text()).toBe('Keep data indefinitely');
|
||||
});
|
||||
|
||||
test('can set data retention period', async () => {
|
||||
const {
|
||||
actions: { clickNameAt, clickEditDataRetentionButton },
|
||||
|
|
|
@ -59,7 +59,9 @@ export interface DataStream {
|
|||
_meta?: Metadata;
|
||||
privileges: Privileges;
|
||||
hidden: boolean;
|
||||
lifecycle?: IndicesDataLifecycleWithRollover;
|
||||
lifecycle?: IndicesDataLifecycleWithRollover & {
|
||||
enabled?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface DataStreamIndex {
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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 { getLifecycleValue } from './data_streams';
|
||||
|
||||
describe('Data stream helpers', () => {
|
||||
describe('getLifecycleValue', () => {
|
||||
it('Knows when it should be marked as disabled', () => {
|
||||
expect(
|
||||
getLifecycleValue({
|
||||
enabled: false,
|
||||
})
|
||||
).toBe('Disabled');
|
||||
|
||||
expect(getLifecycleValue()).toBe('Disabled');
|
||||
});
|
||||
|
||||
it('knows when it should be marked as infinite', () => {
|
||||
expect(
|
||||
getLifecycleValue({
|
||||
enabled: true,
|
||||
})
|
||||
).toBe('Keep data indefinitely');
|
||||
});
|
||||
|
||||
it('knows when it has a defined data retention period', () => {
|
||||
expect(
|
||||
getLifecycleValue({
|
||||
enabled: true,
|
||||
data_retention: '2d',
|
||||
})
|
||||
).toBe('2d');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -5,6 +5,10 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiIcon, EuiToolTip } from '@elastic/eui';
|
||||
|
||||
import { DataStream } from '../../../common';
|
||||
|
||||
export const isFleetManaged = (dataStream: DataStream): boolean => {
|
||||
|
@ -38,3 +42,37 @@ export const isSelectedDataStreamHidden = (
|
|||
?.hidden
|
||||
);
|
||||
};
|
||||
|
||||
export const getLifecycleValue = (
|
||||
lifecycle?: DataStream['lifecycle'],
|
||||
inifniteAsIcon?: boolean
|
||||
) => {
|
||||
if (!lifecycle?.enabled) {
|
||||
return i18n.translate('xpack.idxMgmt.dataStreamList.dataRetentionDisabled', {
|
||||
defaultMessage: 'Disabled',
|
||||
});
|
||||
} else if (!lifecycle?.data_retention) {
|
||||
const infiniteDataRetention = i18n.translate(
|
||||
'xpack.idxMgmt.dataStreamList.dataRetentionInfinite',
|
||||
{
|
||||
defaultMessage: 'Keep data indefinitely',
|
||||
}
|
||||
);
|
||||
|
||||
if (inifniteAsIcon) {
|
||||
return (
|
||||
<EuiToolTip
|
||||
data-test-subj="infiniteRetention"
|
||||
position="top"
|
||||
content={infiniteDataRetention}
|
||||
>
|
||||
<EuiIcon type="infinity" />
|
||||
</EuiToolTip>
|
||||
);
|
||||
}
|
||||
|
||||
return infiniteDataRetention;
|
||||
}
|
||||
|
||||
return lifecycle?.data_retention;
|
||||
};
|
||||
|
|
|
@ -30,6 +30,7 @@ import {
|
|||
} from '@elastic/eui';
|
||||
|
||||
import { DiscoverLink } from '../../../../lib/discover_link';
|
||||
import { getLifecycleValue } from '../../../../lib/data_streams';
|
||||
import { SectionLoading, reactRouterNavigate } from '../../../../../shared_imports';
|
||||
import { SectionError, Error, DataHealth } from '../../../../components';
|
||||
import { useLoadDataStream } from '../../../../services/api';
|
||||
|
@ -147,19 +148,6 @@ export const DataStreamDetailPanel: React.FunctionComponent<Props> = ({
|
|||
const getManagementDetails = () => {
|
||||
const managementDetails = [];
|
||||
|
||||
if (lifecycle?.data_retention) {
|
||||
managementDetails.push({
|
||||
name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.dataRetentionTitle', {
|
||||
defaultMessage: 'Data retention',
|
||||
}),
|
||||
toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.dataRetentionToolTip', {
|
||||
defaultMessage: 'The amount of time to retain the data in the data stream.',
|
||||
}),
|
||||
content: lifecycle.data_retention,
|
||||
dataTestSubj: 'dataRetentionDetail',
|
||||
});
|
||||
}
|
||||
|
||||
if (ilmPolicyName) {
|
||||
managementDetails.push({
|
||||
name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyTitle', {
|
||||
|
@ -278,6 +266,16 @@ export const DataStreamDetailPanel: React.FunctionComponent<Props> = ({
|
|||
),
|
||||
dataTestSubj: 'indexTemplateDetail',
|
||||
},
|
||||
{
|
||||
name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.dataRetentionTitle', {
|
||||
defaultMessage: 'Data retention',
|
||||
}),
|
||||
toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.dataRetentionToolTip', {
|
||||
defaultMessage: 'The amount of time to retain the data in the data stream.',
|
||||
}),
|
||||
content: getLifecycleValue(lifecycle),
|
||||
dataTestSubj: 'dataRetentionDetail',
|
||||
},
|
||||
];
|
||||
|
||||
const managementDetails = getManagementDetails();
|
||||
|
@ -376,7 +374,7 @@ export const DataStreamDetailPanel: React.FunctionComponent<Props> = ({
|
|||
}
|
||||
}}
|
||||
dataStreamName={dataStreamName}
|
||||
dataRetention={dataStream?.lifecycle?.data_retention as string}
|
||||
lifecycle={dataStream?.lifecycle}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ import {
|
|||
import { ScopedHistory } from '@kbn/core/public';
|
||||
|
||||
import { DataStream } from '../../../../../../common/types';
|
||||
import { getLifecycleValue } from '../../../../lib/data_streams';
|
||||
import { UseRequestResponse, reactRouterNavigate } from '../../../../../shared_imports';
|
||||
import { getDataStreamDetailsLink, getIndexListUri } from '../../../../services/routing';
|
||||
import { DataHealth } from '../../../../components';
|
||||
|
@ -34,6 +35,8 @@ interface Props {
|
|||
filters?: string;
|
||||
}
|
||||
|
||||
const INFINITE_AS_ICON = true;
|
||||
|
||||
export const DataStreamTable: React.FunctionComponent<Props> = ({
|
||||
dataStreams,
|
||||
reload,
|
||||
|
@ -144,7 +147,7 @@ export const DataStreamTable: React.FunctionComponent<Props> = ({
|
|||
),
|
||||
truncateText: true,
|
||||
sortable: true,
|
||||
render: (lifecycle: DataStream['lifecycle']) => lifecycle?.data_retention,
|
||||
render: (lifecycle: DataStream['lifecycle']) => getLifecycleValue(lifecycle, INFINITE_AS_ICON),
|
||||
});
|
||||
|
||||
columns.push({
|
||||
|
|
|
@ -35,13 +35,13 @@ import {
|
|||
} from '../../../../../shared_imports';
|
||||
|
||||
import { documentationService } from '../../../../services/documentation';
|
||||
import { splitSizeAndUnits } from '../../../../../../common';
|
||||
import { splitSizeAndUnits, DataStream } from '../../../../../../common';
|
||||
import { useAppContext } from '../../../../app_context';
|
||||
import { UnitField } from './unit_field';
|
||||
import { updateDataRetention } from '../../../../services/api';
|
||||
|
||||
interface Props {
|
||||
dataRetention: string;
|
||||
lifecycle: DataStream['lifecycle'];
|
||||
dataStreamName: string;
|
||||
onClose: (data?: { hasUpdatedDataRetention: boolean }) => void;
|
||||
}
|
||||
|
@ -83,33 +83,6 @@ export const timeUnits = [
|
|||
}
|
||||
),
|
||||
},
|
||||
{
|
||||
value: 'ms',
|
||||
text: i18n.translate(
|
||||
'xpack.idxMgmt.dataStreamsDetailsPanel.editDataRetentionModal.timeUnits.millisecondsLabel',
|
||||
{
|
||||
defaultMessage: 'milliseconds',
|
||||
}
|
||||
),
|
||||
},
|
||||
{
|
||||
value: 'micros',
|
||||
text: i18n.translate(
|
||||
'xpack.idxMgmt.dataStreamsDetailsPanel.editDataRetentionModal.timeUnits.microsecondsLabel',
|
||||
{
|
||||
defaultMessage: 'microseconds',
|
||||
}
|
||||
),
|
||||
},
|
||||
{
|
||||
value: 'nanos',
|
||||
text: i18n.translate(
|
||||
'xpack.idxMgmt.dataStreamsDetailsPanel.editDataRetentionModal.timeUnits.nanosecondsLabel',
|
||||
{
|
||||
defaultMessage: 'nanoseconds',
|
||||
}
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const configurationFormSchema: FormSchema = {
|
||||
|
@ -124,7 +97,12 @@ const configurationFormSchema: FormSchema = {
|
|||
formatters: [fieldFormatters.toInt],
|
||||
validations: [
|
||||
{
|
||||
validator: ({ value }) => {
|
||||
validator: ({ value, formData }) => {
|
||||
// If infiniteRetentionPeriod is set, we dont need to validate the data retention field
|
||||
if (formData.infiniteRetentionPeriod) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
return {
|
||||
message: i18n.translate(
|
||||
|
@ -171,11 +149,11 @@ const configurationFormSchema: FormSchema = {
|
|||
};
|
||||
|
||||
export const EditDataRetentionModal: React.FunctionComponent<Props> = ({
|
||||
dataRetention,
|
||||
lifecycle,
|
||||
dataStreamName,
|
||||
onClose,
|
||||
}) => {
|
||||
const { size, unit } = splitSizeAndUnits(dataRetention);
|
||||
const { size, unit } = splitSizeAndUnits(lifecycle?.data_retention as string);
|
||||
const {
|
||||
services: { notificationService },
|
||||
} = useAppContext();
|
||||
|
@ -184,7 +162,10 @@ export const EditDataRetentionModal: React.FunctionComponent<Props> = ({
|
|||
defaultValue: {
|
||||
dataRetention: size,
|
||||
timeUnit: unit || 'd',
|
||||
infiniteRetentionPeriod: !dataRetention,
|
||||
// When data retention is not set and lifecycle is enabled, is the only scenario in
|
||||
// which data retention will be infinite. If lifecycle isnt set or is not enabled, we
|
||||
// dont have inifinite data retention.
|
||||
infiniteRetentionPeriod: lifecycle?.enabled && !lifecycle?.data_retention,
|
||||
},
|
||||
schema: configurationFormSchema,
|
||||
id: 'editDataRetentionForm',
|
||||
|
|
|
@ -31,6 +31,10 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
},
|
||||
},
|
||||
},
|
||||
lifecycle: {
|
||||
// @ts-expect-error @elastic/elasticsearch enabled prop is not typed yet
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
data_stream: {},
|
||||
},
|
||||
|
@ -85,8 +89,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
expect(typeof storageSizeBytes).to.be('number');
|
||||
};
|
||||
|
||||
// FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/168021
|
||||
describe.skip('Data streams', function () {
|
||||
describe('Data streams', function () {
|
||||
describe('Get', () => {
|
||||
const testDataStreamName = 'test-data-stream';
|
||||
|
||||
|
|
|
@ -12,8 +12,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
const pageObjects = getPageObjects(['common', 'indexManagement', 'header']);
|
||||
const toasts = getService('toasts');
|
||||
const log = getService('log');
|
||||
const dataStreams = getService('dataStreams');
|
||||
const browser = getService('browser');
|
||||
const es = getService('es');
|
||||
const security = getService('security');
|
||||
const testSubjects = getService('testSubjects');
|
||||
|
||||
|
@ -23,15 +23,32 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
before(async () => {
|
||||
await log.debug('Creating required data stream');
|
||||
try {
|
||||
await dataStreams.createDataStream(
|
||||
TEST_DS_NAME,
|
||||
{
|
||||
'@timestamp': {
|
||||
type: 'date',
|
||||
await es.indices.putIndexTemplate({
|
||||
name: `${TEST_DS_NAME}_index_template`,
|
||||
index_patterns: [TEST_DS_NAME],
|
||||
data_stream: {},
|
||||
_meta: {
|
||||
description: `Template for ${TEST_DS_NAME} testing index`,
|
||||
},
|
||||
template: {
|
||||
settings: { mode: undefined },
|
||||
mappings: {
|
||||
properties: {
|
||||
'@timestamp': {
|
||||
type: 'date',
|
||||
},
|
||||
},
|
||||
},
|
||||
lifecycle: {
|
||||
// @ts-expect-error @elastic/elasticsearch enabled prop is not typed yet
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
await es.indices.createDataStream({
|
||||
name: TEST_DS_NAME,
|
||||
});
|
||||
} catch (e) {
|
||||
log.debug('[Setup error] Error creating test data stream');
|
||||
throw e;
|
||||
|
@ -49,7 +66,10 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
await log.debug('Cleaning up created data stream');
|
||||
|
||||
try {
|
||||
await dataStreams.deleteDataStream(TEST_DS_NAME);
|
||||
await es.indices.deleteDataStream({ name: TEST_DS_NAME });
|
||||
await es.indices.deleteIndexTemplate({
|
||||
name: `${TEST_DS_NAME}_index_template`,
|
||||
});
|
||||
} catch (e) {
|
||||
log.debug('[Teardown error] Error deleting test data stream');
|
||||
throw e;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue