mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Index Management] Support data retention on Data Streams tab (#165263)
This commit is contained in:
parent
a6c25b15aa
commit
be01217b19
13 changed files with 239 additions and 154 deletions
|
@ -49,11 +49,34 @@ POST %25%7B%5B%40metadata%5D%5Bbeat%5D%7D-%25%7B%5B%40metadata%5D%5Bversion%5D%7
|
|||
}
|
||||
```
|
||||
|
||||
Create a data stream configured with data stream lifecyle.
|
||||
|
||||
```
|
||||
PUT _index_template/my-index-template
|
||||
{
|
||||
"index_patterns": ["my-data-stream*"],
|
||||
"data_stream": { },
|
||||
"priority": 500,
|
||||
"template": {
|
||||
"lifecycle": {
|
||||
"data_retention": "7d"
|
||||
}
|
||||
},
|
||||
"_meta": {
|
||||
"description": "Template with data stream lifecycle"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
PUT _data_stream/my-data-stream
|
||||
```
|
||||
|
||||
## Index templates tab
|
||||
|
||||
### Quick steps for testing
|
||||
|
||||
**Legacy index templates** are only shown in the UI on stateful *and* if a user has existing legacy index templates. You can test this functionality by creating one in Console:
|
||||
**Legacy index templates** are only shown in the UI on stateful _and_ if a user has existing legacy index templates. You can test this functionality by creating one in Console:
|
||||
|
||||
```
|
||||
PUT _template/template_1
|
||||
|
@ -67,6 +90,7 @@ On serverless, Elasticsearch does not support legacy index templates and therefo
|
|||
To test **Cloud-managed templates**:
|
||||
|
||||
1. Add `cluster.metadata.managed_index_templates` setting via Dev Tools:
|
||||
|
||||
```
|
||||
PUT /_cluster/settings
|
||||
{
|
||||
|
@ -77,6 +101,7 @@ PUT /_cluster/settings
|
|||
```
|
||||
|
||||
2. Create a template with the format: `.cloud-<template_name>` via Dev Tools.
|
||||
|
||||
```
|
||||
PUT _template/.cloud-example
|
||||
{
|
||||
|
@ -101,4 +126,4 @@ In 7.x, the UI supports types defined as part of the mappings for legacy index t
|
|||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
```
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
import { act } from 'react-dom/test-utils';
|
||||
import { ReactWrapper } from 'enzyme';
|
||||
|
||||
import { EuiDescriptionListDescription } from '@elastic/eui';
|
||||
import {
|
||||
registerTestBed,
|
||||
TestBed,
|
||||
|
@ -43,8 +42,9 @@ export interface DataStreamsTabTestBed extends TestBed<TestSubjects> {
|
|||
findDetailPanelTitle: () => string;
|
||||
findEmptyPromptIndexTemplateLink: () => ReactWrapper;
|
||||
findDetailPanelIlmPolicyLink: () => ReactWrapper;
|
||||
findDetailPanelIlmPolicyName: () => ReactWrapper;
|
||||
findDetailPanelIlmPolicyDetail: () => ReactWrapper;
|
||||
findDetailPanelIndexTemplateLink: () => ReactWrapper;
|
||||
findDetailPanelDataRetentionDetail: () => ReactWrapper;
|
||||
}
|
||||
|
||||
export const setup = async (
|
||||
|
@ -211,10 +211,14 @@ export const setup = async (
|
|||
return find('indexTemplateLink');
|
||||
};
|
||||
|
||||
const findDetailPanelIlmPolicyName = () => {
|
||||
const descriptionList = testBed.component.find(EuiDescriptionListDescription);
|
||||
// ilm policy is the last in the details list
|
||||
return descriptionList.last();
|
||||
const findDetailPanelIlmPolicyDetail = () => {
|
||||
const { find } = testBed;
|
||||
return find('ilmPolicyDetail');
|
||||
};
|
||||
|
||||
const findDetailPanelDataRetentionDetail = () => {
|
||||
const { find } = testBed;
|
||||
return find('dataRetentionDetail');
|
||||
};
|
||||
|
||||
return {
|
||||
|
@ -240,8 +244,9 @@ export const setup = async (
|
|||
findDetailPanelTitle,
|
||||
findEmptyPromptIndexTemplateLink,
|
||||
findDetailPanelIlmPolicyLink,
|
||||
findDetailPanelIlmPolicyName,
|
||||
findDetailPanelIlmPolicyDetail,
|
||||
findDetailPanelIndexTemplateLink,
|
||||
findDetailPanelDataRetentionDetail,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -264,6 +269,9 @@ export const createDataStreamPayload = (dataStream: Partial<DataStream>): DataSt
|
|||
delete_index: true,
|
||||
},
|
||||
hidden: false,
|
||||
lifecycle: {
|
||||
data_retention: '7d',
|
||||
},
|
||||
...dataStream,
|
||||
});
|
||||
|
||||
|
|
|
@ -170,8 +170,8 @@ describe('Data Streams tab', () => {
|
|||
const { tableCellsValues } = table.getMetaData('dataStreamTable');
|
||||
|
||||
expect(tableCellsValues).toEqual([
|
||||
['', 'dataStream1', 'green', '1', 'Delete'],
|
||||
['', 'dataStream2', 'green', '1', 'Delete'],
|
||||
['', 'dataStream1', 'green', '1', '7d', 'Delete'],
|
||||
['', 'dataStream2', 'green', '1', '7d', 'Delete'],
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -209,8 +209,8 @@ describe('Data Streams tab', () => {
|
|||
// The table renders with the stats columns though.
|
||||
const { tableCellsValues } = table.getMetaData('dataStreamTable');
|
||||
expect(tableCellsValues).toEqual([
|
||||
['', 'dataStream1', 'green', 'December 31st, 1969 7:00:00 PM', '5b', '1', 'Delete'],
|
||||
['', 'dataStream2', 'green', 'December 31st, 1969 7:00:00 PM', '1kb', '1', 'Delete'],
|
||||
['', 'dataStream1', 'green', 'December 31st, 1969 7:00:00 PM', '5b', '1', '7d', 'Delete'],
|
||||
['', 'dataStream2', 'green', 'December 31st, 1969 7:00:00 PM', '1kb', '1', '7d', 'Delete'],
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -229,8 +229,8 @@ describe('Data Streams tab', () => {
|
|||
// the human-readable string values.
|
||||
const { tableCellsValues } = table.getMetaData('dataStreamTable');
|
||||
expect(tableCellsValues).toEqual([
|
||||
['', 'dataStream1', 'green', 'December 31st, 1969 7:00:00 PM', '5b', '1', 'Delete'],
|
||||
['', 'dataStream2', 'green', 'December 31st, 1969 7:00:00 PM', '1kb', '1', 'Delete'],
|
||||
['', 'dataStream1', 'green', 'December 31st, 1969 7:00:00 PM', '5b', '1', '7d', 'Delete'],
|
||||
['', 'dataStream2', 'green', 'December 31st, 1969 7:00:00 PM', '1kb', '1', '7d', 'Delete'],
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -335,6 +335,12 @@ describe('Data Streams tab', () => {
|
|||
expect(find('summaryTab').exists()).toBeTruthy();
|
||||
expect(find('title').text().trim()).toBe('indexTemplate');
|
||||
});
|
||||
|
||||
test('shows data retention detail when configured', async () => {
|
||||
const { actions, findDetailPanelDataRetentionDetail } = testBed;
|
||||
await actions.clickNameAt(0);
|
||||
expect(findDetailPanelDataRetentionDetail().exists()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -423,10 +429,10 @@ describe('Data Streams tab', () => {
|
|||
});
|
||||
testBed.component.update();
|
||||
|
||||
const { actions, findDetailPanelIlmPolicyLink, findDetailPanelIlmPolicyName } = testBed;
|
||||
const { actions, findDetailPanelIlmPolicyLink, findDetailPanelIlmPolicyDetail } = testBed;
|
||||
await actions.clickNameAt(0);
|
||||
expect(findDetailPanelIlmPolicyLink().exists()).toBeFalsy();
|
||||
expect(findDetailPanelIlmPolicyName().contains('None')).toBeTruthy();
|
||||
expect(findDetailPanelIlmPolicyDetail().exists()).toBeFalsy();
|
||||
});
|
||||
|
||||
test('without an ILM url locator and with an ILM policy', async () => {
|
||||
|
@ -453,10 +459,10 @@ describe('Data Streams tab', () => {
|
|||
});
|
||||
testBed.component.update();
|
||||
|
||||
const { actions, findDetailPanelIlmPolicyLink, findDetailPanelIlmPolicyName } = testBed;
|
||||
const { actions, findDetailPanelIlmPolicyLink, findDetailPanelIlmPolicyDetail } = testBed;
|
||||
await actions.clickNameAt(0);
|
||||
expect(findDetailPanelIlmPolicyLink().exists()).toBeFalsy();
|
||||
expect(findDetailPanelIlmPolicyName().contains('my_ilm_policy')).toBeTruthy();
|
||||
expect(findDetailPanelIlmPolicyDetail().contains('my_ilm_policy')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -489,8 +495,8 @@ describe('Data Streams tab', () => {
|
|||
const { tableCellsValues } = table.getMetaData('dataStreamTable');
|
||||
|
||||
expect(tableCellsValues).toEqual([
|
||||
['', `managed-data-stream${nonBreakingSpace}Fleet-managed`, 'green', '1', 'Delete'],
|
||||
['', 'non-managed-data-stream', 'green', '1', 'Delete'],
|
||||
['', `managed-data-stream${nonBreakingSpace}Fleet-managed`, 'green', '1', '7d', 'Delete'],
|
||||
['', 'non-managed-data-stream', 'green', '1', '7d', 'Delete'],
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -499,14 +505,16 @@ describe('Data Streams tab', () => {
|
|||
let { tableCellsValues } = table.getMetaData('dataStreamTable');
|
||||
|
||||
expect(tableCellsValues).toEqual([
|
||||
['', `managed-data-stream${nonBreakingSpace}Fleet-managed`, 'green', '1', 'Delete'],
|
||||
['', 'non-managed-data-stream', 'green', '1', 'Delete'],
|
||||
['', `managed-data-stream${nonBreakingSpace}Fleet-managed`, 'green', '1', '7d', 'Delete'],
|
||||
['', 'non-managed-data-stream', 'green', '1', '7d', 'Delete'],
|
||||
]);
|
||||
|
||||
actions.toggleViewFilterAt(0);
|
||||
|
||||
({ tableCellsValues } = table.getMetaData('dataStreamTable'));
|
||||
expect(tableCellsValues).toEqual([['', 'non-managed-data-stream', 'green', '1', 'Delete']]);
|
||||
expect(tableCellsValues).toEqual([
|
||||
['', 'non-managed-data-stream', 'green', '1', '7d', 'Delete'],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -537,7 +545,7 @@ describe('Data Streams tab', () => {
|
|||
const { tableCellsValues } = table.getMetaData('dataStreamTable');
|
||||
|
||||
expect(tableCellsValues).toEqual([
|
||||
['', `hidden-data-stream${nonBreakingSpace}Hidden`, 'green', '1', 'Delete'],
|
||||
['', `hidden-data-stream${nonBreakingSpace}Hidden`, 'green', '1', '7d', 'Delete'],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
@ -570,8 +578,8 @@ describe('Data Streams tab', () => {
|
|||
const { tableCellsValues } = table.getMetaData('dataStreamTable');
|
||||
|
||||
expect(tableCellsValues).toEqual([
|
||||
['', 'dataStreamNoDelete', 'green', '1', ''],
|
||||
['', 'dataStreamWithDelete', 'green', '1', 'Delete'],
|
||||
['', 'dataStreamNoDelete', 'green', '1', '7d', ''],
|
||||
['', 'dataStreamWithDelete', 'green', '1', '7d', 'Delete'],
|
||||
]);
|
||||
});
|
||||
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { DataStream, DataStreamFromEs, Health } from '../types';
|
||||
import { DataStream, EnhancedDataStreamFromEs, Health } from '../types';
|
||||
|
||||
export function deserializeDataStream(dataStreamFromEs: DataStreamFromEs): DataStream {
|
||||
export function deserializeDataStream(dataStreamFromEs: EnhancedDataStreamFromEs): DataStream {
|
||||
const {
|
||||
name,
|
||||
timestamp_field: timeStampField,
|
||||
|
@ -22,6 +22,7 @@ export function deserializeDataStream(dataStreamFromEs: DataStreamFromEs): DataS
|
|||
_meta,
|
||||
privileges,
|
||||
hidden,
|
||||
lifecycle,
|
||||
} = dataStreamFromEs;
|
||||
|
||||
return {
|
||||
|
@ -44,9 +45,12 @@ export function deserializeDataStream(dataStreamFromEs: DataStreamFromEs): DataS
|
|||
_meta,
|
||||
privileges,
|
||||
hidden,
|
||||
lifecycle,
|
||||
};
|
||||
}
|
||||
|
||||
export function deserializeDataStreamList(dataStreamsFromEs: DataStreamFromEs[]): DataStream[] {
|
||||
export function deserializeDataStreamList(
|
||||
dataStreamsFromEs: EnhancedDataStreamFromEs[]
|
||||
): DataStream[] {
|
||||
return dataStreamsFromEs.map((dataStream) => deserializeDataStream(dataStream));
|
||||
}
|
||||
|
|
|
@ -5,20 +5,20 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import {
|
||||
ByteSize,
|
||||
IndicesDataLifecycleWithRollover,
|
||||
IndicesDataStream,
|
||||
IndicesDataStreamsStatsDataStreamsStatsItem,
|
||||
Metadata,
|
||||
} from '@elastic/elasticsearch/lib/api/types';
|
||||
|
||||
interface TimestampFieldFromEs {
|
||||
name: string;
|
||||
}
|
||||
|
||||
type TimestampField = TimestampFieldFromEs;
|
||||
|
||||
interface MetaFromEs {
|
||||
managed_by: string;
|
||||
package: any;
|
||||
managed: boolean;
|
||||
}
|
||||
|
||||
type Meta = MetaFromEs;
|
||||
|
||||
interface PrivilegesFromEs {
|
||||
delete_index: boolean;
|
||||
}
|
||||
|
@ -27,20 +27,13 @@ type Privileges = PrivilegesFromEs;
|
|||
|
||||
export type HealthFromEs = 'GREEN' | 'YELLOW' | 'RED';
|
||||
|
||||
export interface DataStreamFromEs {
|
||||
name: string;
|
||||
timestamp_field: TimestampFieldFromEs;
|
||||
indices: DataStreamIndexFromEs[];
|
||||
generation: number;
|
||||
_meta?: MetaFromEs;
|
||||
status: HealthFromEs;
|
||||
template: string;
|
||||
ilm_policy?: string;
|
||||
store_size?: string;
|
||||
store_size_bytes?: number;
|
||||
maximum_timestamp?: number;
|
||||
privileges: PrivilegesFromEs;
|
||||
hidden: boolean;
|
||||
export interface EnhancedDataStreamFromEs extends IndicesDataStream {
|
||||
store_size?: IndicesDataStreamsStatsDataStreamsStatsItem['store_size'];
|
||||
store_size_bytes?: IndicesDataStreamsStatsDataStreamsStatsItem['store_size_bytes'];
|
||||
maximum_timestamp?: IndicesDataStreamsStatsDataStreamsStatsItem['maximum_timestamp'];
|
||||
privileges: {
|
||||
delete_index: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface DataStreamIndexFromEs {
|
||||
|
@ -58,12 +51,13 @@ export interface DataStream {
|
|||
health: Health;
|
||||
indexTemplateName: string;
|
||||
ilmPolicyName?: string;
|
||||
storageSize?: string;
|
||||
storageSize?: ByteSize;
|
||||
storageSizeBytes?: number;
|
||||
maxTimeStamp?: number;
|
||||
_meta?: Meta;
|
||||
_meta?: Metadata;
|
||||
privileges: Privileges;
|
||||
hidden: boolean;
|
||||
lifecycle?: IndicesDataLifecycleWithRollover;
|
||||
}
|
||||
|
||||
export interface DataStreamIndex {
|
||||
|
|
|
@ -13,7 +13,7 @@ export * from './mappings';
|
|||
|
||||
export * from './templates';
|
||||
|
||||
export type { DataStreamFromEs, Health, DataStream, DataStreamIndex } from './data_streams';
|
||||
export type { EnhancedDataStreamFromEs, Health, DataStream, DataStreamIndex } from './data_streams';
|
||||
|
||||
export * from './component_templates';
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, Fragment } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
EuiButton,
|
||||
|
@ -41,24 +41,16 @@ interface DetailsListProps {
|
|||
name: string;
|
||||
toolTip: string;
|
||||
content: any;
|
||||
dataTestSubj: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
const DetailsList: React.FunctionComponent<DetailsListProps> = ({ details }) => {
|
||||
const groups: any[] = [];
|
||||
let items: any[];
|
||||
const descriptionListItems = details.map((detail, index) => {
|
||||
const { name, toolTip, content, dataTestSubj } = detail;
|
||||
|
||||
details.forEach((detail, index) => {
|
||||
const { name, toolTip, content } = detail;
|
||||
|
||||
if (index % 2 === 0) {
|
||||
items = [];
|
||||
|
||||
groups.push(<EuiFlexGroup key={groups.length}>{items}</EuiFlexGroup>);
|
||||
}
|
||||
|
||||
items.push(
|
||||
<EuiFlexItem key={name}>
|
||||
return (
|
||||
<Fragment key={`${name}-${index}`}>
|
||||
<EuiDescriptionListTitle>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>{name}</EuiFlexItem>
|
||||
|
@ -69,12 +61,27 @@ const DetailsList: React.FunctionComponent<DetailsListProps> = ({ details }) =>
|
|||
</EuiFlexGroup>
|
||||
</EuiDescriptionListTitle>
|
||||
|
||||
<EuiDescriptionListDescription>{content}</EuiDescriptionListDescription>
|
||||
</EuiFlexItem>
|
||||
<EuiDescriptionListDescription data-test-subj={dataTestSubj}>
|
||||
{content}
|
||||
</EuiDescriptionListDescription>
|
||||
</Fragment>
|
||||
);
|
||||
});
|
||||
|
||||
return <EuiDescriptionList textStyle="reverse">{groups}</EuiDescriptionList>;
|
||||
const midpoint = Math.ceil(descriptionListItems.length / 2);
|
||||
const descriptionListColumnOne = descriptionListItems.slice(0, midpoint);
|
||||
const descriptionListColumnTwo = descriptionListItems.slice(-midpoint);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiDescriptionList textStyle="reverse">{descriptionListColumnOne}</EuiDescriptionList>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiDescriptionList textStyle="reverse">{descriptionListColumnTwo}</EuiDescriptionList>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
||||
interface Props {
|
||||
|
@ -123,23 +130,64 @@ export const DataStreamDetailPanel: React.FunctionComponent<Props> = ({
|
|||
ilmPolicyName,
|
||||
storageSize,
|
||||
maxTimeStamp,
|
||||
lifecycle,
|
||||
} = dataStream;
|
||||
const details = [
|
||||
|
||||
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', {
|
||||
defaultMessage: 'Index lifecycle policy',
|
||||
}),
|
||||
toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyToolTip', {
|
||||
defaultMessage: `The index lifecycle policy that manages the data in the data stream.`,
|
||||
}),
|
||||
content: ilmPolicyLink ? (
|
||||
<EuiLink data-test-subj={'ilmPolicyLink'} href={ilmPolicyLink}>
|
||||
{ilmPolicyName}
|
||||
</EuiLink>
|
||||
) : (
|
||||
ilmPolicyName
|
||||
),
|
||||
dataTestSubj: 'ilmPolicyDetail',
|
||||
});
|
||||
}
|
||||
|
||||
return managementDetails;
|
||||
};
|
||||
|
||||
const defaultDetails = [
|
||||
{
|
||||
name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.healthTitle', {
|
||||
defaultMessage: 'Health',
|
||||
}),
|
||||
toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.healthToolTip', {
|
||||
defaultMessage: `The health of the data stream's current backing indices`,
|
||||
defaultMessage: `The health of the data stream's current backing indices.`,
|
||||
}),
|
||||
content: <DataHealth health={health} />,
|
||||
dataTestSubj: 'healthDetail',
|
||||
},
|
||||
{
|
||||
name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.maxTimeStampTitle', {
|
||||
defaultMessage: 'Last updated',
|
||||
}),
|
||||
toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.maxTimeStampToolTip', {
|
||||
defaultMessage: 'The most recent document to be added to the data stream',
|
||||
defaultMessage: 'The most recent document to be added to the data stream.',
|
||||
}),
|
||||
content: maxTimeStamp ? (
|
||||
humanizeTimeStamp(maxTimeStamp)
|
||||
|
@ -150,22 +198,24 @@ export const DataStreamDetailPanel: React.FunctionComponent<Props> = ({
|
|||
})}
|
||||
</em>
|
||||
),
|
||||
dataTestSubj: 'lastUpdatedDetail',
|
||||
},
|
||||
{
|
||||
name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.storageSizeTitle', {
|
||||
defaultMessage: 'Storage size',
|
||||
}),
|
||||
toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.storageSizeToolTip', {
|
||||
defaultMessage: `Total size of all shards in the data stream’s backing indices`,
|
||||
defaultMessage: `The total size of all shards in the data stream’s backing indices.`,
|
||||
}),
|
||||
content: storageSize,
|
||||
dataTestSubj: 'storageSizeDetail',
|
||||
},
|
||||
{
|
||||
name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.indicesTitle', {
|
||||
defaultMessage: 'Indices',
|
||||
}),
|
||||
toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.indicesToolTip', {
|
||||
defaultMessage: `The data stream's current backing indices`,
|
||||
defaultMessage: `The data stream's current backing indices.`,
|
||||
}),
|
||||
content: (
|
||||
<EuiLink
|
||||
|
@ -177,24 +227,27 @@ export const DataStreamDetailPanel: React.FunctionComponent<Props> = ({
|
|||
{indices.length}
|
||||
</EuiLink>
|
||||
),
|
||||
dataTestSubj: 'indicesDetail',
|
||||
},
|
||||
{
|
||||
name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.timestampFieldTitle', {
|
||||
defaultMessage: 'Timestamp field',
|
||||
}),
|
||||
toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.timestampFieldToolTip', {
|
||||
defaultMessage: 'Timestamp field shared by all documents in the data stream',
|
||||
defaultMessage: 'The timestamp field shared by all documents in the data stream.',
|
||||
}),
|
||||
content: timeStampField.name,
|
||||
dataTestSubj: 'timestampDetail',
|
||||
},
|
||||
{
|
||||
name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.generationTitle', {
|
||||
defaultMessage: 'Generation',
|
||||
}),
|
||||
toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.generationToolTip', {
|
||||
defaultMessage: 'Cumulative count of backing indices created for the data stream',
|
||||
defaultMessage: 'The number of backing indices generated for the data stream.',
|
||||
}),
|
||||
content: generation,
|
||||
dataTestSubj: 'generationDetail',
|
||||
},
|
||||
{
|
||||
name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.indexTemplateTitle', {
|
||||
|
@ -202,7 +255,7 @@ export const DataStreamDetailPanel: React.FunctionComponent<Props> = ({
|
|||
}),
|
||||
toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.indexTemplateToolTip', {
|
||||
defaultMessage:
|
||||
'The index template that configured the data stream and configures its backing indices',
|
||||
'The index template that configured the data stream and configures its backing indices.',
|
||||
}),
|
||||
content: (
|
||||
<EuiLink
|
||||
|
@ -212,31 +265,13 @@ export const DataStreamDetailPanel: React.FunctionComponent<Props> = ({
|
|||
{indexTemplateName}
|
||||
</EuiLink>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyTitle', {
|
||||
defaultMessage: 'Index lifecycle policy',
|
||||
}),
|
||||
toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyToolTip', {
|
||||
defaultMessage: `The index lifecycle policy that manages the data stream's data`,
|
||||
}),
|
||||
content:
|
||||
ilmPolicyName && ilmPolicyLink ? (
|
||||
<EuiLink data-test-subj={'ilmPolicyLink'} href={ilmPolicyLink}>
|
||||
{ilmPolicyName}
|
||||
</EuiLink>
|
||||
) : (
|
||||
ilmPolicyName || (
|
||||
<em>
|
||||
{i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyContentNoneMessage', {
|
||||
defaultMessage: `None`,
|
||||
})}
|
||||
</em>
|
||||
)
|
||||
),
|
||||
dataTestSubj: 'indexTemplateDetail',
|
||||
},
|
||||
];
|
||||
|
||||
const managementDetails = getManagementDetails();
|
||||
const details = [...defaultDetails, ...managementDetails];
|
||||
|
||||
content = <DetailsList details={details} />;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,14 @@
|
|||
import React, { useState, Fragment } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiInMemoryTable, EuiBasicTableColumn, EuiButton, EuiLink } from '@elastic/eui';
|
||||
import {
|
||||
EuiInMemoryTable,
|
||||
EuiBasicTableColumn,
|
||||
EuiButton,
|
||||
EuiLink,
|
||||
EuiIcon,
|
||||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
import { ScopedHistory } from '@kbn/core/public';
|
||||
|
||||
import { DataStream } from '../../../../../../common/types';
|
||||
|
@ -71,7 +78,6 @@ export const DataStreamTable: React.FunctionComponent<Props> = ({
|
|||
render: (health: DataStream['health']) => {
|
||||
return <DataHealth health={health} />;
|
||||
},
|
||||
width: '100px',
|
||||
});
|
||||
|
||||
if (includeStats) {
|
||||
|
@ -80,7 +86,6 @@ export const DataStreamTable: React.FunctionComponent<Props> = ({
|
|||
name: i18n.translate('xpack.idxMgmt.dataStreamList.table.maxTimeStampColumnTitle', {
|
||||
defaultMessage: 'Last updated',
|
||||
}),
|
||||
width: '300px',
|
||||
truncateText: true,
|
||||
sortable: true,
|
||||
render: (maxTimeStamp: DataStream['maxTimeStamp']) =>
|
||||
|
@ -120,6 +125,28 @@ export const DataStreamTable: React.FunctionComponent<Props> = ({
|
|||
),
|
||||
});
|
||||
|
||||
columns.push({
|
||||
field: 'lifecycle',
|
||||
name: (
|
||||
<EuiToolTip
|
||||
content={i18n.translate('xpack.idxMgmt.dataStreamList.table.dataRetentionColumnTooltip', {
|
||||
defaultMessage:
|
||||
'Data will be be kept at least this long before it is automatically deleted. Only applies to data streams managed by a data stream lifecycle. This value might not apply to all data if the data stream also has an index lifecycle policy.',
|
||||
})}
|
||||
>
|
||||
<span>
|
||||
{i18n.translate('xpack.idxMgmt.dataStreamList.table.dataRetentionColumnTitle', {
|
||||
defaultMessage: 'Data retention',
|
||||
})}{' '}
|
||||
<EuiIcon size="s" color="subdued" type="questionInCircle" />
|
||||
</span>
|
||||
</EuiToolTip>
|
||||
),
|
||||
truncateText: true,
|
||||
sortable: true,
|
||||
render: (lifecycle: DataStream['lifecycle']) => lifecycle?.data_retention,
|
||||
});
|
||||
|
||||
columns.push({
|
||||
name: i18n.translate('xpack.idxMgmt.dataStreamList.table.actionColumnTitle', {
|
||||
defaultMessage: 'Actions',
|
||||
|
|
|
@ -8,55 +8,28 @@
|
|||
import { schema, TypeOf } from '@kbn/config-schema';
|
||||
|
||||
import { IScopedClusterClient } from '@kbn/core/server';
|
||||
import {
|
||||
IndicesDataStream,
|
||||
IndicesDataStreamsStatsDataStreamsStatsItem,
|
||||
SecurityHasPrivilegesResponse,
|
||||
} from '@elastic/elasticsearch/lib/api/types';
|
||||
import { deserializeDataStream, deserializeDataStreamList } from '../../../../common/lib';
|
||||
import { DataStreamFromEs } from '../../../../common/types';
|
||||
import { EnhancedDataStreamFromEs } from '../../../../common/types';
|
||||
import { RouteDependencies } from '../../../types';
|
||||
import { addBasePath } from '..';
|
||||
|
||||
interface PrivilegesFromEs {
|
||||
username: string;
|
||||
has_all_requested: boolean;
|
||||
cluster: Record<string, boolean>;
|
||||
index: Record<string, Record<string, boolean>>;
|
||||
application: Record<string, boolean>;
|
||||
}
|
||||
|
||||
interface StatsFromEs {
|
||||
data_stream: string;
|
||||
store_size: string;
|
||||
store_size_bytes: number;
|
||||
maximum_timestamp: number;
|
||||
}
|
||||
|
||||
const enhanceDataStreams = ({
|
||||
dataStreams,
|
||||
dataStreamsStats,
|
||||
dataStreamsPrivileges,
|
||||
}: {
|
||||
dataStreams: DataStreamFromEs[];
|
||||
dataStreamsStats?: StatsFromEs[];
|
||||
dataStreamsPrivileges?: PrivilegesFromEs;
|
||||
}): DataStreamFromEs[] => {
|
||||
return dataStreams.map((dataStream: DataStreamFromEs) => {
|
||||
let enhancedDataStream = { ...dataStream };
|
||||
|
||||
if (dataStreamsStats) {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const { store_size, store_size_bytes, maximum_timestamp } =
|
||||
dataStreamsStats.find(
|
||||
({ data_stream: statsName }: { data_stream: string }) => statsName === dataStream.name
|
||||
) || {};
|
||||
|
||||
enhancedDataStream = {
|
||||
...enhancedDataStream,
|
||||
store_size,
|
||||
store_size_bytes,
|
||||
maximum_timestamp,
|
||||
};
|
||||
}
|
||||
|
||||
enhancedDataStream = {
|
||||
...enhancedDataStream,
|
||||
dataStreams: IndicesDataStream[];
|
||||
dataStreamsStats?: IndicesDataStreamsStatsDataStreamsStatsItem[];
|
||||
dataStreamsPrivileges?: SecurityHasPrivilegesResponse;
|
||||
}): EnhancedDataStreamFromEs[] => {
|
||||
return dataStreams.map((dataStream) => {
|
||||
const enhancedDataStream: EnhancedDataStreamFromEs = {
|
||||
...dataStream,
|
||||
privileges: {
|
||||
delete_index: dataStreamsPrivileges
|
||||
? dataStreamsPrivileges.index[dataStream.name].delete_index
|
||||
|
@ -64,6 +37,17 @@ const enhanceDataStreams = ({
|
|||
},
|
||||
};
|
||||
|
||||
if (dataStreamsStats) {
|
||||
const currentDataStreamStats: IndicesDataStreamsStatsDataStreamsStatsItem | undefined =
|
||||
dataStreamsStats.find(({ data_stream: statsName }) => statsName === dataStream.name);
|
||||
|
||||
if (currentDataStreamStats) {
|
||||
enhancedDataStream.store_size = currentDataStreamStats.store_size;
|
||||
enhancedDataStream.store_size_bytes = currentDataStreamStats.store_size_bytes;
|
||||
enhancedDataStream.maximum_timestamp = currentDataStreamStats.maximum_timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
return enhancedDataStream;
|
||||
});
|
||||
};
|
||||
|
@ -125,11 +109,8 @@ export function registerGetAllRoute({ router, lib: { handleEsError }, config }:
|
|||
}
|
||||
|
||||
const enhancedDataStreams = enhanceDataStreams({
|
||||
// @ts-expect-error DataStreamFromEs conflicts with @elastic/elasticsearch IndicesGetDataStreamIndicesGetDataStreamItem
|
||||
dataStreams,
|
||||
// @ts-expect-error StatsFromEs conflicts with @elastic/elasticsearch IndicesDataStreamsStatsDataStreamsStatsItem
|
||||
dataStreamsStats,
|
||||
// @ts-expect-error PrivilegesFromEs conflicts with @elastic/elasticsearch ApplicationsPrivileges
|
||||
dataStreamsPrivileges,
|
||||
});
|
||||
|
||||
|
@ -164,11 +145,8 @@ export function registerGetOneRoute({ router, lib: { handleEsError }, config }:
|
|||
}
|
||||
|
||||
const enhancedDataStreams = enhanceDataStreams({
|
||||
// @ts-expect-error DataStreamFromEs conflicts with @elastic/elasticsearch IndicesGetDataStreamIndicesGetDataStreamItem
|
||||
dataStreams,
|
||||
// @ts-expect-error StatsFromEs conflicts with @elastic/elasticsearch IndicesDataStreamsStatsDataStreamsStatsItem
|
||||
dataStreamsStats,
|
||||
// @ts-expect-error PrivilegesFromEs conflicts with @elastic/elasticsearch ApplicationsPrivileges
|
||||
dataStreamsPrivileges,
|
||||
});
|
||||
const body = deserializeDataStream(enhancedDataStreams[0]);
|
||||
|
|
|
@ -17347,7 +17347,6 @@
|
|||
"xpack.idxMgmt.dataStreamDetailPanel.generationToolTip": "Nombre cumulatif d'index de sauvegarde créés pour le flux de données",
|
||||
"xpack.idxMgmt.dataStreamDetailPanel.healthTitle": "Intégrité",
|
||||
"xpack.idxMgmt.dataStreamDetailPanel.healthToolTip": "Intégrité des index de sauvegarde actuels du flux de données",
|
||||
"xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyContentNoneMessage": "Aucun",
|
||||
"xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyTitle": "Stratégie de cycle de vie des index",
|
||||
"xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyToolTip": "Stratégie de cycle de vie de l'index qui gère les données du flux de données",
|
||||
"xpack.idxMgmt.dataStreamDetailPanel.indexTemplateTitle": "Modèle d'index",
|
||||
|
|
|
@ -17361,7 +17361,6 @@
|
|||
"xpack.idxMgmt.dataStreamDetailPanel.generationToolTip": "データストリームに作成されたバッキングインデックスの累積数",
|
||||
"xpack.idxMgmt.dataStreamDetailPanel.healthTitle": "ヘルス",
|
||||
"xpack.idxMgmt.dataStreamDetailPanel.healthToolTip": "データストリームの現在のバッキングインデックスのヘルス",
|
||||
"xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyContentNoneMessage": "なし",
|
||||
"xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyTitle": "インデックスライフサイクルポリシー",
|
||||
"xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyToolTip": "データストリームのデータを管理するインデックスライフサイクルポリシー",
|
||||
"xpack.idxMgmt.dataStreamDetailPanel.indexTemplateTitle": "インデックステンプレート",
|
||||
|
|
|
@ -17361,7 +17361,6 @@
|
|||
"xpack.idxMgmt.dataStreamDetailPanel.generationToolTip": "为数据流创建的后备索引的累积计数",
|
||||
"xpack.idxMgmt.dataStreamDetailPanel.healthTitle": "运行状况",
|
||||
"xpack.idxMgmt.dataStreamDetailPanel.healthToolTip": "数据流的当前后备索引的运行状况",
|
||||
"xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyContentNoneMessage": "无",
|
||||
"xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyTitle": "索引生命周期策略",
|
||||
"xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyToolTip": "用于管理数据流数据的索引生命周期策略",
|
||||
"xpack.idxMgmt.dataStreamDetailPanel.indexTemplateTitle": "索引模板",
|
||||
|
|
|
@ -112,6 +112,9 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
|
||||
expect(testDataStream).to.eql({
|
||||
name: testDataStreamName,
|
||||
lifecycle: {
|
||||
enabled: true,
|
||||
},
|
||||
privileges: {
|
||||
delete_index: true,
|
||||
},
|
||||
|
@ -166,6 +169,9 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
indexTemplateName: testDataStreamName,
|
||||
maxTimeStamp: 0,
|
||||
hidden: false,
|
||||
lifecycle: {
|
||||
enabled: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -197,6 +203,9 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
indexTemplateName: testDataStreamName,
|
||||
maxTimeStamp: 0,
|
||||
hidden: false,
|
||||
lifecycle: {
|
||||
enabled: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue