diff --git a/x-pack/plugins/beats_management/common/domain_types.ts b/x-pack/plugins/beats_management/common/domain_types.ts
index 3299457da019b..a553f987a233e 100644
--- a/x-pack/plugins/beats_management/common/domain_types.ts
+++ b/x-pack/plugins/beats_management/common/domain_types.ts
@@ -28,7 +28,7 @@ export interface CMBeat {
}
export interface CMPopulatedBeat extends CMBeat {
- fullTags: BeatTag[];
+ full_tags: BeatTag[];
}
export interface BeatTag {
diff --git a/x-pack/plugins/beats_management/public/components/__snapshots__/beats_table.test.tsx.snap b/x-pack/plugins/beats_management/public/components/__snapshots__/beats_table.test.tsx.snap
deleted file mode 100644
index b4d041382b005..0000000000000
--- a/x-pack/plugins/beats_management/public/components/__snapshots__/beats_table.test.tsx.snap
+++ /dev/null
@@ -1,19 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`BeatsTable component matches snapshot 1`] = `
-
-`;
diff --git a/x-pack/plugins/beats_management/public/components/beats_table.tsx b/x-pack/plugins/beats_management/public/components/beats_table.tsx
deleted file mode 100644
index 944a38003ea70..0000000000000
--- a/x-pack/plugins/beats_management/public/components/beats_table.tsx
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
- * 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 {
- EuiBadge,
- EuiButton,
- EuiContextMenu,
- EuiFlexGroup,
- EuiIcon,
- // @ts-ignore
- EuiInMemoryTable,
- EuiLink,
- EuiPopover,
-} from '@elastic/eui';
-import React from 'react';
-import styled from 'styled-components';
-import { CMPopulatedBeat } from '../../common/domain_types';
-
-const columns = [
- {
- field: 'id',
- name: 'Beat name',
- render: (id: string) => {id},
- sortable: true,
- },
- {
- field: 'type',
- name: 'Type',
- sortable: true,
- },
- {
- field: 'tags',
- name: 'Tags',
- render: (value: string, beat: CMPopulatedBeat) => (
-
- {beat.fullTags.map(tag => (
-
- {tag.id}
-
- ))}
-
- ),
- sortable: true,
- },
- {
- // TODO: update to use actual metadata field
- field: 'event_rate',
- name: 'Event rate',
- sortable: true,
- },
- {
- // TODO: update to use actual metadata field
- field: 'last_updated',
- name: 'Last config update',
- sortable: true,
- },
-];
-
-interface BeatsTableProps {
- items: CMPopulatedBeat[];
- onBulkEdit: any;
- onBulkDelete: any;
- onBulkAssignTags: any;
-}
-
-interface BeatsTableState {
- pageIndex: number;
- pageSize: number;
- selection: CMPopulatedBeat[];
- isBulkPopoverOpen: boolean;
-}
-
-const TableContainer = styled.div`
- padding: 16px;
-`;
-
-export class BeatsTable extends React.Component {
- constructor(props: BeatsTableProps) {
- super(props);
-
- this.state = {
- pageIndex: 0,
- pageSize: 5,
- selection: [],
- isBulkPopoverOpen: false,
- };
- }
-
- public render() {
- const { items, onBulkEdit, onBulkDelete, onBulkAssignTags } = this.props;
- const { pageIndex, pageSize, isBulkPopoverOpen, selection } = this.state;
-
- const bulkActionButton = (
-
- Bulk Action
-
- );
-
- const panels = [
- {
- id: 0,
- title: 'Bulk Action',
- items: [
- {
- name: 'Bulk Edit',
- icon: ,
- onClick: () => onBulkEdit(selection),
- },
- {
- name: 'Bulk Delete',
- icon: ,
- onClick: () => onBulkDelete(selection),
- },
- {
- name: 'Bulk Assign Tags',
- icon: ,
- onClick: () => onBulkAssignTags(selection),
- },
- ],
- },
- ];
-
- const toolsLeft = (
-
- );
-
- const search = {
- box: { incremental: true },
- filters: [
- {
- type: 'field_value_selection',
- field: 'type',
- name: 'Type',
- multiSelect: true,
- options: items.map(({ type }) => {
- return {
- value: type,
- name: 'Type',
- view: type,
- };
- }),
- },
- ],
- toolsLeft,
- };
-
- const pagination = {
- initialPageSize: 5,
- pageIndex,
- pageSize,
- totalItemCount: items.length,
- pageSizeOptions: [3, 5, 8],
- };
-
- const selectionOptions = {
- onSelectionChange: this.setSelection,
- selectable: () => true,
- selectableMessage: () => null,
- };
-
- return (
-
- {
- return {
- ...beat,
- key: `beat${index}`,
- };
- })}
- itemId="id"
- isSelectable={true}
- pagination={pagination}
- responsive={true}
- search={search}
- selection={selectionOptions}
- sorting={true}
- />
-
- );
- }
-
- private showBulkPopover = () => {
- const { isBulkPopoverOpen } = this.state;
- this.setState({
- isBulkPopoverOpen: !isBulkPopoverOpen,
- });
- };
-
- private hideBulkPopover = () => {
- this.setState({
- isBulkPopoverOpen: false,
- });
- };
-
- private setSelection = (selection: any) => {
- this.setState({
- selection,
- });
- };
-}
diff --git a/x-pack/plugins/beats_management/public/components/table/__snapshots__/beats_table.test.tsx.snap b/x-pack/plugins/beats_management/public/components/table/__snapshots__/beats_table.test.tsx.snap
new file mode 100644
index 0000000000000..fbf2e4fe03255
--- /dev/null
+++ b/x-pack/plugins/beats_management/public/components/table/__snapshots__/beats_table.test.tsx.snap
@@ -0,0 +1,97 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`BeatsTable component matches snapshot 1`] = `
+
+
+
+
+`;
diff --git a/x-pack/plugins/beats_management/public/components/beats_table.test.tsx b/x-pack/plugins/beats_management/public/components/table/beats_table.test.tsx
similarity index 64%
rename from x-pack/plugins/beats_management/public/components/beats_table.test.tsx
rename to x-pack/plugins/beats_management/public/components/table/beats_table.test.tsx
index 9e29fa87d7ad2..b241e04da1915 100644
--- a/x-pack/plugins/beats_management/public/components/beats_table.test.tsx
+++ b/x-pack/plugins/beats_management/public/components/table/beats_table.test.tsx
@@ -6,15 +6,13 @@
import { shallow } from 'enzyme';
import React from 'react';
-import { CMPopulatedBeat } from '../../common/domain_types';
+import { CMPopulatedBeat } from '../../../common/domain_types';
import { BeatsTable } from './beats_table';
describe('BeatsTable component', () => {
let items: CMPopulatedBeat[];
let beat;
- let onBulkEdit: any;
- let onBulkDelete: any;
- let onBulkAssignTags: any;
+ let onBulkAction: any;
beforeEach(() => {
beat = {
@@ -23,7 +21,7 @@ describe('BeatsTable component', () => {
type: 'type',
host_ip: 'ip',
host_name: 'name',
- fullTags: [
+ full_tags: [
{
id: 'Production',
configuration_blocks: [],
@@ -31,20 +29,11 @@ describe('BeatsTable component', () => {
],
};
items = [beat];
- onBulkEdit = jest.fn();
- onBulkDelete = jest.fn();
- onBulkAssignTags = jest.fn();
+ onBulkAction = jest.fn();
});
it('matches snapshot', () => {
- const wrapper = shallow(
-
- );
+ const wrapper = shallow();
expect(wrapper).toMatchSnapshot();
});
});
diff --git a/x-pack/plugins/beats_management/public/components/table/beats_table.tsx b/x-pack/plugins/beats_management/public/components/table/beats_table.tsx
new file mode 100644
index 0000000000000..1ac8cbbea3171
--- /dev/null
+++ b/x-pack/plugins/beats_management/public/components/table/beats_table.tsx
@@ -0,0 +1,185 @@
+/*
+ * 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 {
+ EuiBadge,
+ EuiFlexGroup,
+ // @ts-ignore
+ EuiInMemoryTable,
+ EuiLink,
+} from '@elastic/eui';
+import { flatten, uniq } from 'lodash';
+import React from 'react';
+import styled from 'styled-components';
+import { CMPopulatedBeat } from '../../../common/domain_types';
+import { BulkActionControlBar } from './controls';
+
+const columns = [
+ {
+ field: 'id',
+ name: 'Beat name',
+ render: (id: string) => {id},
+ sortable: true,
+ },
+ {
+ field: 'type',
+ name: 'Type',
+ sortable: true,
+ },
+ {
+ field: 'full_tags',
+ name: 'Tags',
+ render: (value: string, beat: CMPopulatedBeat) => (
+
+ {beat.full_tags.map(tag => (
+
+ {tag.id}
+
+ ))}
+
+ ),
+ sortable: false,
+ },
+ {
+ // TODO: update to use actual metadata field
+ field: 'event_rate',
+ name: 'Event rate',
+ sortable: true,
+ },
+ {
+ // TODO: update to use actual metadata field
+ field: 'last_updated',
+ name: 'Last config update',
+ sortable: true,
+ },
+];
+
+interface BeatsTableProps {
+ items: CMPopulatedBeat[];
+ onBulkAction: any;
+}
+
+interface BeatsTableState {
+ itemsToRender: CMPopulatedBeat[];
+ pageIndex: number;
+ pageSize: number;
+ search?: any;
+ selection: CMPopulatedBeat[];
+}
+
+const TableContainer = styled.div`
+ padding: 16px;
+`;
+
+export class BeatsTable extends React.Component {
+ constructor(props: BeatsTableProps) {
+ super(props);
+
+ this.state = {
+ itemsToRender: props.items,
+ pageIndex: 0,
+ pageSize: 5,
+ selection: [],
+ };
+ }
+
+ public render() {
+ const { onBulkAction } = this.props;
+ const { itemsToRender, pageIndex, pageSize } = this.state;
+
+ const pagination = {
+ pageIndex,
+ pageSize,
+ totalItemCount: itemsToRender.length,
+ pageSizeOptions: [3, 5, 8],
+ };
+
+ const selectionOptions = {
+ onSelectionChange: this.setSelection,
+ selectable: () => true,
+ selectableMessage: () => null,
+ };
+
+ const tagOptions = this.getTagsOptions();
+ const typeOptions = this.getTypeOptions();
+
+ return (
+
+ {
+ const { selection } = this.state;
+ onBulkAction(action, selection);
+ }}
+ onSearchQueryChange={this.onQueryChange}
+ tagOptions={tagOptions}
+ typeOptions={typeOptions}
+ />
+
+
+ );
+ }
+
+ private getClauseValuesForField = (ast: any, fieldName: string) => {
+ const clauses = ast.getFieldClauses(fieldName);
+ return clauses ? clauses.map((clause: any) => clause.value) : [];
+ };
+
+ private onQueryChange = (search: any) => {
+ const { items } = this.props;
+ let itemsToRender = items;
+
+ if (search && !search.error) {
+ const { query } = search;
+ const types = this.getClauseValuesForField(query.ast, 'type');
+ const tags = this.getClauseValuesForField(query.ast, 'tag');
+ const terms = query.ast.getTermClauses().map((clause: any) => clause.value);
+ if (types.length) {
+ itemsToRender = itemsToRender.filter(item => types.includes(item.type));
+ }
+ if (tags.length) {
+ itemsToRender = itemsToRender.filter(item =>
+ item.full_tags.some(({ id }) => tags.includes(id))
+ );
+ }
+ if (terms.length) {
+ itemsToRender = itemsToRender.filter(item =>
+ terms.some((term: string) => item.id.includes(term))
+ );
+ }
+ }
+
+ this.setState({
+ itemsToRender,
+ });
+ };
+
+ private getTagsOptions = () => {
+ const { items } = this.props;
+ const ids = flatten(items.map(({ full_tags }) => full_tags.map(({ id }) => id)));
+ return uniq(ids).map(id => ({
+ value: id,
+ }));
+ };
+
+ private getTypeOptions = () => {
+ const { items } = this.props;
+ return uniq(items.map(({ type }) => type)).map(type => ({ value: type }));
+ };
+
+ private setSelection = (selection: any) => {
+ this.setState({
+ selection,
+ });
+ };
+}
diff --git a/x-pack/plugins/beats_management/public/components/table/controls.tsx b/x-pack/plugins/beats_management/public/components/table/controls.tsx
new file mode 100644
index 0000000000000..2602da4be6221
--- /dev/null
+++ b/x-pack/plugins/beats_management/public/components/table/controls.tsx
@@ -0,0 +1,136 @@
+/*
+ * 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 {
+ EuiButton,
+ EuiContextMenu,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiIcon,
+ EuiPopover,
+ // @ts-ignore
+ EuiSearchBar,
+} from '@elastic/eui';
+import React from 'react';
+
+// TODO: move to constants
+const BULK_ASSIGN_TAG = 'BULK_ASSIGN_TAG';
+const BULK_DELETE = 'BULK_DELETE';
+const BULK_EDIT = 'BULK_EDIT';
+
+interface BulkActionControlBarState {
+ isPopoverVisible: boolean;
+}
+
+interface FilterOption {
+ value: string;
+}
+
+interface BulkActionControlBarProps {
+ onBulkAction: any;
+ onSearchQueryChange: any;
+ tagOptions: FilterOption[];
+ typeOptions: FilterOption[];
+}
+
+export class BulkActionControlBar extends React.Component<
+ BulkActionControlBarProps,
+ BulkActionControlBarState
+> {
+ constructor(props: BulkActionControlBarProps) {
+ super(props);
+
+ this.state = {
+ isPopoverVisible: false,
+ };
+ }
+
+ public render() {
+ const { isPopoverVisible } = this.state;
+
+ const bulkActionButton = (
+
+ Bulk Action
+
+ );
+ const { onBulkAction, onSearchQueryChange, tagOptions, typeOptions } = this.props;
+ const panels = [
+ {
+ id: 0,
+ title: 'Bulk Action',
+ items: [
+ {
+ name: 'Bulk Edit',
+ icon: ,
+ onClick: () => onBulkAction(BULK_EDIT),
+ },
+ {
+ name: 'Bulk Delete',
+ icon: ,
+ onClick: () => onBulkAction(BULK_DELETE),
+ },
+ {
+ name: 'Bulk Assign Tags',
+ icon: ,
+ onClick: () => onBulkAction(BULK_ASSIGN_TAG),
+ },
+ ],
+ },
+ ];
+
+ return (
+
+
+
+
+
+
+
+
+ );
+ }
+
+ private showPopover = () => {
+ this.setState({
+ isPopoverVisible: true,
+ });
+ };
+
+ private hidePopover = () => {
+ this.setState({
+ isPopoverVisible: false,
+ });
+ };
+}
diff --git a/x-pack/plugins/beats_management/public/components/table/index.ts b/x-pack/plugins/beats_management/public/components/table/index.ts
new file mode 100644
index 0000000000000..73facdbdb2ca0
--- /dev/null
+++ b/x-pack/plugins/beats_management/public/components/table/index.ts
@@ -0,0 +1,7 @@
+/*
+ * 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.
+ */
+
+export { BeatsTable } from './beats_table';
diff --git a/x-pack/yarn.lock b/x-pack/yarn.lock
index 61c89996ebfa3..5712838a9e070 100644
--- a/x-pack/yarn.lock
+++ b/x-pack/yarn.lock
@@ -2344,25 +2344,15 @@ ecc-jsbn@~0.1.1:
dependencies:
jsbn "~0.1.0"
-ee-first@1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
ecdsa-sig-formatter@1.0.10:
version "1.0.10"
resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz#1c595000f04a8897dfb85000892a0f4c33af86c3"
dependencies:
safe-buffer "^5.0.1"
-elasticsearch@13.0.1:
- version "13.0.1"
- resolved "https://registry.yarnpkg.com/elasticsearch/-/elasticsearch-13.0.1.tgz#fa58204233052c4cd221e8721e48f3906b385b32"
- dependencies:
- agentkeepalive "^2.2.0"
- chalk "^1.0.0"
- lodash "2.4.2"
- lodash.get "^4.4.2"
- lodash.isempty "^4.4.0"
- lodash.trimend "^4.5.1"
+ee-first@1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
elasticsearch@^14.1.0:
version "14.2.2"