Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Managed label to data streams and a view switch for the table #83049

Merged
merged 11 commits into from
Nov 18, 2020
Merged
Next Next commit
Add Managed label to data streams and a view switch for the table
yuliacech committed Nov 10, 2020
commit b669bca28800019fe8087e202cc23e54c9ab95b3
Original file line number Diff line number Diff line change
@@ -21,6 +21,7 @@ export type TestSubjects =
| 'filterList.filterItem'
| 'ilmPolicyLink'
| 'includeStatsSwitch'
| 'viewManagedSwitch'
| 'indexTable'
| 'indexTableIncludeHiddenIndicesToggle'
| 'indexTableIndexNameLink'
Original file line number Diff line number Diff line change
@@ -24,6 +24,7 @@ export interface DataStreamsTabTestBed extends TestBed<TestSubjects> {
goToDataStreamsList: () => void;
clickEmptyPromptIndexTemplateLink: () => void;
clickIncludeStatsSwitch: () => void;
clickViewManagedSwitch: () => void;
clickReloadButton: () => void;
clickNameAt: (index: number) => void;
clickIndicesAt: (index: number) => void;
@@ -83,6 +84,11 @@ export const setup = async (overridingDependencies: any = {}): Promise<DataStrea
find('includeStatsSwitch').simulate('click');
};

const clickViewManagedSwitch = () => {
const { find } = testBed;
find('viewManagedSwitch').simulate('click');
};

const clickReloadButton = () => {
const { find } = testBed;
find('reloadButton').simulate('click');
@@ -170,6 +176,7 @@ export const setup = async (overridingDependencies: any = {}): Promise<DataStrea
goToDataStreamsList,
clickEmptyPromptIndexTemplateLink,
clickIncludeStatsSwitch,
clickViewManagedSwitch,
clickReloadButton,
clickNameAt,
clickIndicesAt,
@@ -187,8 +194,8 @@ export const setup = async (overridingDependencies: any = {}): Promise<DataStrea
};
};

export const createDataStreamPayload = (name: string): DataStream => ({
name,
export const createDataStreamPayload = (dataStream: Partial<DataStream>): DataStream => ({
name: 'my-data-stream',
timeStampField: { name: '@timestamp' },
indices: [
{
@@ -201,6 +208,7 @@ export const createDataStreamPayload = (name: string): DataStream => ({
indexTemplateName: 'indexTemplate',
storageSize: '1b',
maxTimeStamp: 420,
...dataStream,
});

export const createDataStreamBackingIndex = (indexName: string, dataStreamName: string) => ({
Original file line number Diff line number Diff line change
@@ -96,10 +96,10 @@ describe('Data Streams tab', () => {
createNonDataStreamIndex('non-data-stream-index'),
]);

const dataStreamForDetailPanel = createDataStreamPayload('dataStream1');
const dataStreamForDetailPanel = createDataStreamPayload({ name: 'dataStream1' });
setLoadDataStreamsResponse([
dataStreamForDetailPanel,
createDataStreamPayload('dataStream2'),
createDataStreamPayload({ name: 'dataStream2' }),
]);
setLoadDataStreamResponse(dataStreamForDetailPanel);

@@ -260,9 +260,9 @@ describe('Data Streams tab', () => {
createDataStreamBackingIndex('data-stream-index2', 'dataStream2'),
]);

const dataStreamDollarSign = createDataStreamPayload('%dataStream');
setLoadDataStreamsResponse([dataStreamDollarSign]);
setLoadDataStreamResponse(dataStreamDollarSign);
const dataStreamPercentSign = createDataStreamPayload({ name: '%dataStream' });
setLoadDataStreamsResponse([dataStreamPercentSign]);
setLoadDataStreamResponse(dataStreamPercentSign);

testBed = await setup({
history: createMemoryHistory(),
@@ -300,10 +300,10 @@ describe('Data Streams tab', () => {
test('with an ILM url generator and an ILM policy', async () => {
const { setLoadDataStreamsResponse, setLoadDataStreamResponse } = httpRequestsMockHelpers;

const dataStreamForDetailPanel = {
...createDataStreamPayload('dataStream1'),
const dataStreamForDetailPanel = createDataStreamPayload({
name: 'dataStream1',
ilmPolicyName: 'my_ilm_policy',
};
});
setLoadDataStreamsResponse([dataStreamForDetailPanel]);
setLoadDataStreamResponse(dataStreamForDetailPanel);

@@ -324,7 +324,7 @@ describe('Data Streams tab', () => {
test('with an ILM url generator and no ILM policy', async () => {
const { setLoadDataStreamsResponse, setLoadDataStreamResponse } = httpRequestsMockHelpers;

const dataStreamForDetailPanel = createDataStreamPayload('dataStream1');
const dataStreamForDetailPanel = createDataStreamPayload({ name: 'dataStream1' });
setLoadDataStreamsResponse([dataStreamForDetailPanel]);
setLoadDataStreamResponse(dataStreamForDetailPanel);

@@ -346,10 +346,10 @@ describe('Data Streams tab', () => {
test('without an ILM url generator and with an ILM policy', async () => {
const { setLoadDataStreamsResponse, setLoadDataStreamResponse } = httpRequestsMockHelpers;

const dataStreamForDetailPanel = {
...createDataStreamPayload('dataStream1'),
const dataStreamForDetailPanel = createDataStreamPayload({
name: 'dataStream1',
ilmPolicyName: 'my_ilm_policy',
};
});
setLoadDataStreamsResponse([dataStreamForDetailPanel]);
setLoadDataStreamResponse(dataStreamForDetailPanel);

@@ -368,4 +368,58 @@ describe('Data Streams tab', () => {
expect(findDetailPanelIlmPolicyName().contains('my_ilm_policy')).toBeTruthy();
});
});

describe('managed data streams', () => {
const nonBreakingSpace = ' ';
beforeEach(async () => {
const managedDataStream = createDataStreamPayload({
name: 'managed-data-stream',
_meta: {
package: 'test',
managed: true,
managed_by: 'ingest-manager',
},
});
const nonManagedDataStream = createDataStreamPayload({ name: 'non-managed-data-stream' });
httpRequestsMockHelpers.setLoadDataStreamsResponse([managedDataStream, nonManagedDataStream]);

testBed = await setup({
history: createMemoryHistory(),
});
await act(async () => {
testBed.actions.goToDataStreamsList();
});
testBed.component.update();
});

test('listed in the table with Managed label', () => {
const { table } = testBed;
const { tableCellsValues } = table.getMetaData('dataStreamTable');

expect(tableCellsValues).toEqual([
['', `managed-data-stream${nonBreakingSpace}Managed`, 'green', '1', 'Delete'],
['', 'non-managed-data-stream', 'green', '1', 'Delete'],
]);
});

test('turning of "View managed" switch hides managed data streams', async () => {
yuliacech marked this conversation as resolved.
Show resolved Hide resolved
const { exists, actions, component, table } = testBed;
let { tableCellsValues } = table.getMetaData('dataStreamTable');

expect(tableCellsValues).toEqual([
['', `managed-data-stream${nonBreakingSpace}Managed`, 'green', '1', 'Delete'],
['', 'non-managed-data-stream', 'green', '1', 'Delete'],
]);

expect(exists('viewManagedSwitch')).toBe(true);

await act(async () => {
actions.clickViewManagedSwitch();
});
component.update();

({ tableCellsValues } = table.getMetaData('dataStreamTable'));
expect(tableCellsValues).toEqual([['', 'non-managed-data-stream', 'green', '1', 'Delete']]);
});
});
});
Original file line number Diff line number Diff line change
@@ -74,7 +74,9 @@ describe('<IndexManagementHome />', () => {
// The detail panel should still appear even if there are no data streams.
httpRequestsMockHelpers.setLoadDataStreamsResponse([]);

httpRequestsMockHelpers.setLoadDataStreamResponse(createDataStreamPayload('dataStream1'));
httpRequestsMockHelpers.setLoadDataStreamResponse(
createDataStreamPayload({ name: 'dataStream1' })
);

testBed = await setup({
history: createMemoryHistory(),
Original file line number Diff line number Diff line change
@@ -17,6 +17,7 @@ export function deserializeDataStream(dataStreamFromEs: DataStreamFromEs): DataS
ilm_policy: ilmPolicyName,
store_size: storageSize,
maximum_timestamp: maxTimeStamp,
_meta,
} = dataStreamFromEs;

return {
@@ -35,6 +36,7 @@ export function deserializeDataStream(dataStreamFromEs: DataStreamFromEs): DataS
ilmPolicyName,
storageSize,
maxTimeStamp,
_meta,
};
}

10 changes: 10 additions & 0 deletions x-pack/plugins/index_management/common/types/data_streams.ts
Original file line number Diff line number Diff line change
@@ -10,13 +10,22 @@ interface TimestampFieldFromEs {

type TimestampField = TimestampFieldFromEs;

interface MetaFieldFromEs {
managed_by: string;
package: any;
managed: boolean;
}

type MetaField = MetaFieldFromEs;

export type HealthFromEs = 'GREEN' | 'YELLOW' | 'RED';

export interface DataStreamFromEs {
name: string;
timestamp_field: TimestampFieldFromEs;
indices: DataStreamIndexFromEs[];
generation: number;
_meta?: MetaFieldFromEs;
status: HealthFromEs;
template: string;
ilm_policy?: string;
@@ -41,6 +50,7 @@ export interface DataStream {
ilmPolicyName?: string;
storageSize?: string;
maxTimeStamp?: number;
_meta?: MetaField;
}

export interface DataStreamIndex {
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { DataStream } from '../../../common';

export const isManagedByIngestManager = (dataStream: DataStream): boolean => {
return Boolean(dataStream._meta?.managed && dataStream._meta?.managed_by === 'ingest-manager');
};

export const filterDataStreams = (dataStreams: DataStream[], managed: boolean): DataStream[] => {
return dataStreams.filter((dataStream: DataStream) =>
managed ? isManagedByIngestManager(dataStream) : !isManagedByIngestManager(dataStream)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a little hard for me to parse. If I'm understanding correctly, it looks like managed is only ever going to be false.

const filteredDataStreams = isIncludeManagedChecked
      ? dataStreams
      : filterDataStreams(dataStreams, isIncludeManagedChecked);

If that's correct, could the filter be simplified to this?

return dataStreams.filter((dataStream: DataStream) => isManagedByIngestManager(dataStream) === false)

);
};
Original file line number Diff line number Diff line change
@@ -32,6 +32,7 @@ import { documentationService } from '../../../services/documentation';
import { Section } from '../home';
import { DataStreamTable } from './data_stream_table';
import { DataStreamDetailPanel } from './data_stream_detail_panel';
import { filterDataStreams } from '../../../lib/data_streams';

interface MatchParams {
dataStreamName?: string;
@@ -52,6 +53,7 @@ export const DataStreamList: React.FunctionComponent<RouteComponentProps<MatchPa
} = useAppContext();

const [isIncludeStatsChecked, setIsIncludeStatsChecked] = useState(false);
const [isIncludeManagedChecked, setIsIncludeManagedChecked] = useState(true);
const { error, isLoading, data: dataStreams, resendRequest: reload } = useLoadDataStreams({
includeStats: isIncludeStatsChecked,
});
@@ -147,11 +149,13 @@ export const DataStreamList: React.FunctionComponent<RouteComponentProps<MatchPa
/>
);
} else if (Array.isArray(dataStreams) && dataStreams.length > 0) {
const filteredDataStreams = isIncludeManagedChecked
? dataStreams
: filterDataStreams(dataStreams, isIncludeManagedChecked);
content = (
<>
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween">
<EuiFlexItem>
{/* TODO: Add a switch for toggling on data streams created by Ingest Manager */}
<EuiText color="subdued">
<FormattedMessage
id="xpack.idxMgmt.dataStreamList.dataStreamsDescription"
@@ -202,6 +206,16 @@ export const DataStreamList: React.FunctionComponent<RouteComponentProps<MatchPa
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiSwitch
label={i18n.translate('xpack.idxMgmt.dataStreamListControls.viewManagedSwitchLabel', {
defaultMessage: 'View managed',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screen Shot 2020-11-12 at 1 03 02 PM

WDYT about changing this to "Include managed" to align with the other toggle?

})}
checked={isIncludeManagedChecked}
onChange={(e) => setIsIncludeManagedChecked(e.target.checked)}
data-test-subj="viewManagedSwitch"
/>
</EuiFlexItem>
</EuiFlexGroup>

<EuiSpacer size="l" />
@@ -212,7 +226,7 @@ export const DataStreamList: React.FunctionComponent<RouteComponentProps<MatchPa
? `name="${attemptToURIDecode(dataStreamName)}"`
: ''
}
dataStreams={dataStreams}
dataStreams={filteredDataStreams}
reload={reload}
history={history as ScopedHistory}
includeStats={isIncludeStatsChecked}
Original file line number Diff line number Diff line change
@@ -4,10 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/

import React, { useState } from 'react';
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, EuiBadge } from '@elastic/eui';
import { ScopedHistory } from 'kibana/public';

import { DataStream } from '../../../../../../common/types';
@@ -16,6 +16,7 @@ import { getDataStreamDetailsLink, getIndexListUri } from '../../../../services/
import { DataHealth } from '../../../../components';
import { DeleteDataStreamConfirmationModal } from '../delete_data_stream_confirmation_modal';
import { humanizeTimeStamp } from '../humanize_time_stamp';
import { isManagedByIngestManager } from '../../../../lib/data_streams';

interface Props {
dataStreams?: DataStream[];
@@ -44,14 +45,27 @@ export const DataStreamTable: React.FunctionComponent<Props> = ({
}),
truncateText: true,
sortable: true,
render: (name: DataStream['name']) => {
render: (name: DataStream['name'], dataStream: DataStream) => {
return (
<EuiLink
data-test-subj="nameLink"
{...reactRouterNavigate(history, getDataStreamDetailsLink(name))}
>
{name}
</EuiLink>
<Fragment>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(nit) you can use shorthand <></> for fragments.

<EuiLink
data-test-subj="nameLink"
{...reactRouterNavigate(history, getDataStreamDetailsLink(name))}
>
{name}
</EuiLink>
{isManagedByIngestManager(dataStream) ? (
<Fragment>
&nbsp;
<EuiBadge color="hollow">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about adding a tooltip or title to the badge to explain that "managed" means managed by Ingest Manager, specifically? Or maybe add this to the switch?

<FormattedMessage
id="xpack.idxMgmt.dataStreamList.table.managedDataStreamBadge"
defaultMessage="Managed"
/>
</EuiBadge>
</Fragment>
) : null}
</Fragment>
);
},
});
@@ -121,7 +135,7 @@ export const DataStreamTable: React.FunctionComponent<Props> = ({
name: i18n.translate('xpack.idxMgmt.dataStreamList.table.actionDeleteText', {
defaultMessage: 'Delete',
}),
description: i18n.translate('xpack.idxMgmt.dataStreamList.table.actionDeleteDecription', {
description: i18n.translate('xpack.idxMgmt.dataStreamList.table.actionDeleteDescription', {
defaultMessage: 'Delete this data stream',
}),
icon: 'trash',