Skip to content

Commit

Permalink
[Beats Management] Add Tags List (#21274)
Browse files Browse the repository at this point in the history
* Add BeatsTable and control bar components.

* Clean yarn.lock.

* Move raw numbers/strings to constants. Remove obsolete state/props.

* Update/add tests.

* Change prop name from "items" to "beats".

* Add TagsTable component and associated search/action bar.

* Rename some variables.

* Add constant after forgetting to save file.

* Fix design mistake in table component.

* Disable delete button when no tags selected.

* Export tags table from index.ts.

* Move search bar filter definitions to table render.

* Update table to support assignment options.

* Update action control position.

* Refactor split render function into custom components.

* Add assignment options to Tags List.

* Remove obsolete code.

* Move tooltips for tag icons to top position.
  • Loading branch information
justinkambic authored and mattapperson committed Aug 27, 2018
1 parent 2089fe8 commit 264a566
Show file tree
Hide file tree
Showing 9 changed files with 196 additions and 13 deletions.
1 change: 1 addition & 0 deletions x-pack/plugins/beats_management/common/constants/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@
export const TABLE_CONFIG = {
INITIAL_ROW_SIZE: 5,
PAGE_SIZE_OPTIONS: [3, 5, 10, 20],
TRUNCATE_TAG_LENGTH: 33,
};
1 change: 1 addition & 0 deletions x-pack/plugins/beats_management/common/domain_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@ export interface BeatTag {
id: string;
configuration_blocks: ConfigurationBlock[];
color?: string;
last_updated: Date;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 ? (
<AssignmentOptions
actionHandler={actionHandler}
Expand All @@ -41,7 +42,7 @@ export function ControlBar(props: ControlBarProps) {
) : (
<EuiSearchBar
box={{ incremental: true }}
filters={controlDefinitions.filters}
filters={filters}
onChange={(query: any) => actionHandler('search', query)}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
12 changes: 10 additions & 2 deletions x-pack/plugins/beats_management/public/components/table/table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ interface BeatsTableProps {
assignmentOptions: any[] | null;
assignmentTitle: string | null;
items: any[];
showAssignmentOptions: boolean;
type: TableType;
actionHandler(action: string, payload?: any): void;
}
Expand All @@ -42,7 +43,14 @@ export class Table extends React.Component<BeatsTableProps, BeatsTableState> {
}

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,
Expand All @@ -64,7 +72,7 @@ export class Table extends React.Component<BeatsTableProps, BeatsTableState> {
assignmentTitle={assignmentTitle}
controlDefinitions={type.controlDefinitions(items)}
selectionCount={this.state.selection.length}
showAssignmentOptions={true}
showAssignmentOptions={showAssignmentOptions}
/>
<EuiSpacer size="m" />
<EuiInMemoryTable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ import { EuiBadge, EuiFlexGroup, EuiIcon, EuiLink } from '@elastic/eui';
import { flatten, uniq } from 'lodash';
import moment from 'moment';
import React from 'react';
import { CMPopulatedBeat } from '../../../common/domain_types';
import { TABLE_CONFIG } from '../../../common/constants';
import { BeatTag, CMPopulatedBeat, ConfigurationBlock } from '../../../common/domain_types';

export interface ColumnDefinition {
align?: string;
field: string;
name: string;
sortable?: boolean;
width?: string;
render?(value: any, object?: any): any;
}

Expand Down Expand Up @@ -116,3 +119,41 @@ export const BeatsTableType: TableType = {
],
}),
};

export const TagsTableType: TableType = {
columnDefinitions: [
{
field: 'id',
name: 'Tag name',
render: (id: string, tag: BeatTag) => (
<EuiBadge color={tag.color ? tag.color : 'primary'}>
{tag.id.length > TABLE_CONFIG.TRUNCATE_TAG_LENGTH
? `${tag.id.substring(0, TABLE_CONFIG.TRUNCATE_TAG_LENGTH)}...`
: tag.id}
</EuiBadge>
),
sortable: true,
width: '45%',
},
{
align: 'right',
field: 'configuration_blocks',
name: 'Configurations',
render: (configurationBlocks: ConfigurationBlock[]) => (
<div>{configurationBlocks.length}</div>
),
sortable: false,
},
{
align: 'right',
field: 'last_updated',
name: 'Last update',
render: (lastUpdate: Date) => <div>{moment(lastUpdate).fromNow()}</div>,
sortable: true,
},
],
controlDefinitions: (data: any) => ({
actions: [],
filters: [],
}),
};
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,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;
});
Expand Down
3 changes: 2 additions & 1 deletion x-pack/plugins/beats_management/public/pages/main/beats.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ interface BeatsPageProps {

interface BeatsPageState {
beats: CMBeat[];
tags: any[] | null;
tableRef: any;
tags: any[] | null;
}

export class BeatsPage extends React.PureComponent<BeatsPageProps, BeatsPageState> {
Expand Down Expand Up @@ -54,6 +54,7 @@ export class BeatsPage extends React.PureComponent<BeatsPageProps, BeatsPageStat
assignmentTitle="Set tags"
items={this.state.beats || []}
ref={this.state.tableRef}
showAssignmentOptions={true}
type={BeatsTableType}
/>
);
Expand Down
132 changes: 130 additions & 2 deletions x-pack/plugins/beats_management/public/pages/main/tags.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<TagsPageProps, TagsPageState> {
constructor(props: TagsPageProps) {
super(props);

this.state = {
beats: [],
tableRef: React.createRef(),
tags: [],
};

this.loadTags();
}

export class TagsPage extends React.PureComponent {
public render() {
return <div>tags table and stuff</div>;
return (
<Table
actionHandler={this.handleTagsAction}
assignmentOptions={this.state.beats}
assignmentTitle={'Assign Beats'}
items={this.state.tags}
ref={this.state.tableRef}
showAssignmentOptions={true}
type={TagsTableType}
/>
);
}

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 (
<EuiToolTip title={tagId}>
<EuiIcon
key={tagId}
type="stopFilled"
// @ts-ignore color typing does not allow for any string
color={associatedTag.color || 'primary'}
/>
</EuiToolTip>
);
});

return (
<EuiFlexItem key={beat.id}>
<EuiFlexGroup alignItems="center" gutterSize="none">
{tagIcons.map(icon => (
<EuiFlexItem component="span" grow={false}>
{icon}
</EuiFlexItem>
))}
<EuiFlexItem>
<EuiButtonEmpty
onClick={() => {
this.assignTagsToBeats(beat, tagsToAdd);
this.removeTagsFromBeats(beat, tagsToRemove);
this.loadBeats();
}}
>
{beat.id}
</EuiButtonEmpty>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
);
});

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;
};
}

0 comments on commit 264a566

Please sign in to comment.