diff --git a/x-pack/plugins/beats_management/common/constants/table.ts b/x-pack/plugins/beats_management/common/constants/table.ts index 801a60082d1b8..e5c2f6a029d67 100644 --- a/x-pack/plugins/beats_management/common/constants/table.ts +++ b/x-pack/plugins/beats_management/common/constants/table.ts @@ -7,4 +7,5 @@ export const TABLE_CONFIG = { INITIAL_ROW_SIZE: 5, PAGE_SIZE_OPTIONS: [3, 5, 10, 20], + TRUNCATE_TAG_LENGTH: 33, }; diff --git a/x-pack/plugins/beats_management/common/domain_types.ts b/x-pack/plugins/beats_management/common/domain_types.ts index a553f987a233e..59110741c0ac0 100644 --- a/x-pack/plugins/beats_management/common/domain_types.ts +++ b/x-pack/plugins/beats_management/common/domain_types.ts @@ -35,4 +35,5 @@ export interface BeatTag { id: string; configuration_blocks: ConfigurationBlock[]; color?: string; + last_updated: Date; } diff --git a/x-pack/plugins/beats_management/public/components/table/controls.tsx b/x-pack/plugins/beats_management/public/components/table/controls.tsx index 5182c10c71722..730c73a37d5e1 100644 --- a/x-pack/plugins/beats_management/public/components/table/controls.tsx +++ b/x-pack/plugins/beats_management/public/components/table/controls.tsx @@ -30,6 +30,7 @@ export function ControlBar(props: ControlBarProps) { selectionCount, showAssignmentOptions, } = props; + const filters = controlDefinitions.filters.length === 0 ? null : controlDefinitions.filters; return selectionCount !== 0 && showAssignmentOptions ? ( actionHandler('search', query)} /> ); diff --git a/x-pack/plugins/beats_management/public/components/table/index.ts b/x-pack/plugins/beats_management/public/components/table/index.ts index 0789cad5c3022..459002dd49b82 100644 --- a/x-pack/plugins/beats_management/public/components/table/index.ts +++ b/x-pack/plugins/beats_management/public/components/table/index.ts @@ -6,4 +6,4 @@ export { Table } from './table'; export { ControlBar } from './controls'; -export { BeatsTableType } from './table_type_configs'; +export { BeatsTableType, TagsTableType } from './table_type_configs'; diff --git a/x-pack/plugins/beats_management/public/components/table/table.tsx b/x-pack/plugins/beats_management/public/components/table/table.tsx index c66c290b2f5f9..dd84facd75704 100644 --- a/x-pack/plugins/beats_management/public/components/table/table.tsx +++ b/x-pack/plugins/beats_management/public/components/table/table.tsx @@ -20,6 +20,7 @@ interface BeatsTableProps { assignmentOptions: any[] | null; assignmentTitle: string | null; items: any[]; + showAssignmentOptions: boolean; type: TableType; actionHandler(action: string, payload?: any): void; } @@ -42,7 +43,14 @@ export class Table extends React.Component { } public render() { - const { actionHandler, assignmentOptions, assignmentTitle, items, type } = this.props; + const { + actionHandler, + assignmentOptions, + assignmentTitle, + items, + showAssignmentOptions, + type, + } = this.props; const pagination = { initialPageSize: TABLE_CONFIG.INITIAL_ROW_SIZE, @@ -64,7 +72,7 @@ export class Table extends React.Component { assignmentTitle={assignmentTitle} controlDefinitions={type.controlDefinitions(items)} selectionCount={this.state.selection.length} - showAssignmentOptions={true} + showAssignmentOptions={showAssignmentOptions} /> ( + + {tag.id.length > TABLE_CONFIG.TRUNCATE_TAG_LENGTH + ? `${tag.id.substring(0, TABLE_CONFIG.TRUNCATE_TAG_LENGTH)}...` + : tag.id} + + ), + sortable: true, + width: '45%', + }, + { + align: 'right', + field: 'configuration_blocks', + name: 'Configurations', + render: (configurationBlocks: ConfigurationBlock[]) => ( +
{configurationBlocks.length}
+ ), + sortable: false, + }, + { + align: 'right', + field: 'last_updated', + name: 'Last update', + render: (lastUpdate: Date) =>
{moment(lastUpdate).fromNow()}
, + sortable: true, + }, + ], + controlDefinitions: (data: any) => ({ + actions: [], + filters: [], + }), +}; diff --git a/x-pack/plugins/beats_management/public/lib/adapters/beats/memory_beats_adapter.ts b/x-pack/plugins/beats_management/public/lib/adapters/beats/memory_beats_adapter.ts index 1940ec2ada4b0..9606835337af3 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/beats/memory_beats_adapter.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/beats/memory_beats_adapter.ts @@ -33,11 +33,13 @@ export class MemoryBeatsAdapter implements CMBeatsAdapter { const beatIds = removals.map(r => r.beatId); const response = this.beatsDB.filter(beat => beatIds.includes(beat.id)).map(beat => { - const tagData = removals.find(r => r.beatId === beat.id); - if (tagData) { - if (beat.tags) { - beat.tags = beat.tags.filter(tag => tag !== tagData.tag); - } + const removalsForBeat = removals.filter(r => r.beatId === beat.id); + if (removalsForBeat.length) { + removalsForBeat.forEach((assignment: BeatsTagAssignment) => { + if (beat.tags) { + beat.tags = beat.tags.filter(tag => tag !== assignment.tag); + } + }); } return beat; }); diff --git a/x-pack/plugins/beats_management/public/pages/main/beats.tsx b/x-pack/plugins/beats_management/public/pages/main/beats.tsx index 1d8a9e6717605..1fa7784107fce 100644 --- a/x-pack/plugins/beats_management/public/pages/main/beats.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/beats.tsx @@ -30,8 +30,8 @@ interface BeatsPageProps { interface BeatsPageState { beats: CMBeat[]; - tags: any[] | null; tableRef: any; + tags: any[] | null; } export class BeatsPage extends React.PureComponent { @@ -93,6 +93,7 @@ export class BeatsPage extends React.PureComponent ); diff --git a/x-pack/plugins/beats_management/public/pages/main/tags.tsx b/x-pack/plugins/beats_management/public/pages/main/tags.tsx index 66dab3c1b3550..d7ed848fd10c3 100644 --- a/x-pack/plugins/beats_management/public/pages/main/tags.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/tags.tsx @@ -4,10 +4,138 @@ * you may not use this file except in compliance with the Elastic License. */ +// @ts-ignore EuiToolTip has no typings in current version +import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiIcon, EuiToolTip } from '@elastic/eui'; import React from 'react'; +import { BeatTag, CMBeat } from '../../../common/domain_types'; +import { BeatsTagAssignment } from '../../../server/lib/adapters/beats/adapter_types'; +import { Table, TagsTableType } from '../../components/table'; +import { FrontendLibs } from '../../lib/lib'; + +interface TagsPageProps { + libs: FrontendLibs; +} + +interface TagsPageState { + beats: any; + tableRef: any; + tags: BeatTag[]; +} + +export class TagsPage extends React.PureComponent { + constructor(props: TagsPageProps) { + super(props); + + this.state = { + beats: [], + tableRef: React.createRef(), + tags: [], + }; + + this.loadTags(); + } -export class TagsPage extends React.PureComponent { public render() { - return
tags table and stuff
; + return ( + + ); } + + private handleTagsAction = (action: string, payload: any) => { + switch (action) { + case 'loadAssignmentOptions': + this.loadBeats(); + break; + } + + this.loadTags(); + }; + + private async loadTags() { + const tags = await this.props.libs.tags.getAll(); + this.setState({ + tags, + }); + } + + private async loadBeats() { + const beats = await this.props.libs.beats.getAll(); + const selectedTags = this.getSelectedTags(); + const renderedBeats = beats.map((beat: CMBeat) => { + const tagsToRemove: BeatTag[] = []; + const tagsToAdd: BeatTag[] = []; + const tags = beat.tags || []; + selectedTags.forEach((tag: BeatTag) => { + tags.some((tagId: string) => tagId === tag.id) + ? tagsToRemove.push(tag) + : tagsToAdd.push(tag); + }); + + const tagIcons = tags.map((tagId: string) => { + const associatedTag = this.state.tags.find(tag => tag.id === tagId); + return ( + + + + ); + }); + + return ( + + + {tagIcons.map(icon => ( + + {icon} + + ))} + + { + this.assignTagsToBeats(beat, tagsToAdd); + this.removeTagsFromBeats(beat, tagsToRemove); + this.loadBeats(); + }} + > + {beat.id} + + + + + ); + }); + + this.setState({ + beats: renderedBeats, + }); + } + + private createBeatTagAssignments = (beat: CMBeat, tags: BeatTag[]): BeatsTagAssignment[] => + tags.map(({ id }) => ({ tag: id, beatId: beat.id })); + + private removeTagsFromBeats = async (beat: CMBeat, tags: BeatTag[]) => { + const assignments = this.createBeatTagAssignments(beat, tags); + await this.props.libs.beats.removeTagsFromBeats(assignments); + }; + + private assignTagsToBeats = async (beat: CMBeat, tags: BeatTag[]) => { + const assignments = this.createBeatTagAssignments(beat, tags); + await this.props.libs.beats.assignTagsToBeats(assignments); + }; + + private getSelectedTags = () => { + return this.state.tableRef.current.state.selection; + }; }