mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[BeatsCM] Update table controls for better discoverability (#27043)
* working new control layout # Conflicts: # x-pack/plugins/beats_management/public/components/table/table.tsx * Finish new table controls * check for tags * update type to match reality * when all beats are removed, no more error * update label for uniformity
This commit is contained in:
parent
5421473a1c
commit
d7a5e631d5
16 changed files with 235 additions and 495 deletions
|
@ -1,65 +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 { EuiButton, EuiContextMenu, EuiPopover } from '@elastic/eui';
|
||||
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
|
||||
import React from 'react';
|
||||
import { ActionDefinition } from './table_type_configs';
|
||||
|
||||
interface ActionButtonProps {
|
||||
itemName: 'Beats' | 'Tags';
|
||||
actions: ActionDefinition[];
|
||||
intl: InjectedIntl;
|
||||
isPopoverVisible: boolean;
|
||||
actionHandler(action: string, payload?: any): void;
|
||||
hidePopover(): void;
|
||||
showPopover(): void;
|
||||
}
|
||||
|
||||
export const ActionButton = injectI18n((props: ActionButtonProps) => {
|
||||
const { actions, actionHandler, hidePopover, isPopoverVisible, showPopover, intl } = props;
|
||||
if (actions.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<EuiPopover
|
||||
anchorPosition="downLeft"
|
||||
button={
|
||||
<EuiButton iconSide="right" iconType="arrowDown" onClick={showPopover}>
|
||||
<FormattedMessage
|
||||
id="xpack.beatsManagement.table.bulkActionButtonLabel"
|
||||
defaultMessage="Bulk Action"
|
||||
/>
|
||||
</EuiButton>
|
||||
}
|
||||
closePopover={hidePopover}
|
||||
id="contextMenu"
|
||||
isOpen={isPopoverVisible}
|
||||
panelPaddingSize="none"
|
||||
withTitle
|
||||
>
|
||||
<EuiContextMenu
|
||||
initialPanelId={0}
|
||||
panels={[
|
||||
{
|
||||
id: 0,
|
||||
title: intl.formatMessage(
|
||||
{
|
||||
id: 'xpack.beatsManagement.table.bulkActionMenuLabel',
|
||||
defaultMessage: 'Manage {itemName}',
|
||||
},
|
||||
{ itemName: props.itemName }
|
||||
),
|
||||
items: actions.map(action => ({
|
||||
...action,
|
||||
onClick: () => actionHandler(action.action),
|
||||
})),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</EuiPopover>
|
||||
);
|
||||
});
|
|
@ -7,26 +7,34 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { AssignmentActionType } from './table';
|
||||
|
||||
export interface AssignmentControlSchema {
|
||||
export enum ActionComponentType {
|
||||
Action,
|
||||
Popover,
|
||||
SelectionCount,
|
||||
TagBadgeList,
|
||||
}
|
||||
export interface ControlSchema {
|
||||
id?: number;
|
||||
name: string;
|
||||
danger?: boolean;
|
||||
type: ActionComponentType;
|
||||
action?: AssignmentActionType;
|
||||
actionDataKey?: string;
|
||||
showWarning?: boolean;
|
||||
warningHeading?: string;
|
||||
warningMessage?: string;
|
||||
lazyLoad?: boolean;
|
||||
panel?: AssignmentControlSchema;
|
||||
grow?: boolean;
|
||||
}
|
||||
|
||||
export const beatsListAssignmentOptions: AssignmentControlSchema[] = [
|
||||
export const beatsListActions: ControlSchema[] = [
|
||||
{
|
||||
grow: false,
|
||||
name: i18n.translate('xpack.beatsManagement.beatsListAssignmentOptions.unenrollButtonLabel', {
|
||||
defaultMessage: 'Unenroll selected',
|
||||
}),
|
||||
showWarning: true,
|
||||
type: ActionComponentType.Action,
|
||||
warningHeading: i18n.translate(
|
||||
'xpack.beatsManagement.beatsListAssignmentOptions.unenrollBeatsWarninigTitle',
|
||||
{ defaultMessage: 'Unenroll selected beats?' }
|
||||
|
@ -43,23 +51,20 @@ export const beatsListAssignmentOptions: AssignmentControlSchema[] = [
|
|||
defaultMessage: 'Set tags',
|
||||
}),
|
||||
grow: false,
|
||||
type: ActionComponentType.TagBadgeList,
|
||||
actionDataKey: 'tags',
|
||||
lazyLoad: true,
|
||||
panel: {
|
||||
id: 1,
|
||||
name: i18n.translate('xpack.beatsManagement.beatsListAssignmentOptions.assignTagsName', {
|
||||
defaultMessage: 'Assign tags',
|
||||
}),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const tagListAssignmentOptions: AssignmentControlSchema[] = [
|
||||
export const tagListActions: ControlSchema[] = [
|
||||
{
|
||||
danger: true,
|
||||
grow: false,
|
||||
name: i18n.translate('xpack.beatsManagement.tagListAssignmentOptions.removeTagsButtonLabel', {
|
||||
defaultMessage: 'Remove tag(s)',
|
||||
defaultMessage: 'Remove selected',
|
||||
}),
|
||||
type: ActionComponentType.Action,
|
||||
showWarning: true,
|
||||
warningHeading: i18n.translate(
|
||||
'xpack.beatsManagement.tagListAssignmentOptions.removeTagsWarninigTitle',
|
||||
|
@ -73,13 +78,14 @@ export const tagListAssignmentOptions: AssignmentControlSchema[] = [
|
|||
},
|
||||
];
|
||||
|
||||
export const tagConfigAssignmentOptions: AssignmentControlSchema[] = [
|
||||
export const tagConfigActions: ControlSchema[] = [
|
||||
{
|
||||
danger: true,
|
||||
grow: false,
|
||||
name: i18n.translate('xpack.beatsManagement.tagConfigAssignmentOptions.removeTagsButtonLabel', {
|
||||
defaultMessage: 'Remove tag(s)',
|
||||
}),
|
||||
type: ActionComponentType.Action,
|
||||
showWarning: true,
|
||||
warningHeading: i18n.translate(
|
||||
'xpack.beatsManagement.tagConfigAssignmentOptions.removeTagsWarninigTitle',
|
|
@ -1,60 +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 { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { InjectedIntl, injectI18n } from '@kbn/i18n/react';
|
||||
import React from 'react';
|
||||
import { AutocompleteField } from '../autocomplete_field/index';
|
||||
import { OptionControl } from './controls/index';
|
||||
import { AssignmentOptions as AssignmentOptionsType, KueryBarProps } from './table';
|
||||
|
||||
interface ControlBarProps {
|
||||
itemType: string;
|
||||
assignmentOptions: AssignmentOptionsType;
|
||||
kueryBarProps?: KueryBarProps;
|
||||
selectionCount: number;
|
||||
intl: InjectedIntl;
|
||||
}
|
||||
|
||||
function ControlBarUi(props: ControlBarProps) {
|
||||
const {
|
||||
assignmentOptions: { actionHandler, items, schema, type },
|
||||
kueryBarProps,
|
||||
selectionCount,
|
||||
intl,
|
||||
} = props;
|
||||
|
||||
if (type === 'none') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<OptionControl
|
||||
itemType={props.itemType}
|
||||
schema={schema}
|
||||
selectionCount={selectionCount}
|
||||
actionHandler={actionHandler}
|
||||
items={items}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
{kueryBarProps && (
|
||||
<EuiFlexItem>
|
||||
<AutocompleteField
|
||||
{...kueryBarProps}
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'xpack.beatsManagement.table.filterResultsPlaceholder',
|
||||
defaultMessage: 'Filter results',
|
||||
})}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
export const ControlBar = injectI18n(ControlBarUi);
|
|
@ -4,18 +4,14 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
// @ts-ignore EuiConfirmModal typings not included in current EUI
|
||||
EuiConfirmModal,
|
||||
EuiOverlayMask,
|
||||
EuiTextColor,
|
||||
} from '@elastic/eui';
|
||||
import { EuiButton, EuiConfirmModal, EuiOverlayMask } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React from 'react';
|
||||
import { AssignmentActionType } from '../table';
|
||||
|
||||
interface ActionControlProps {
|
||||
action: AssignmentActionType;
|
||||
disabled: boolean;
|
||||
danger?: boolean;
|
||||
name: string;
|
||||
showWarning?: boolean;
|
||||
|
@ -47,16 +43,19 @@ export class ActionControl extends React.PureComponent<ActionControlProps, Actio
|
|||
warningHeading,
|
||||
warningMessage,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<EuiTextColor
|
||||
color={danger ? 'danger' : 'default'}
|
||||
<EuiButton
|
||||
size="s"
|
||||
color={danger ? 'danger' : 'primary'}
|
||||
disabled={this.props.disabled}
|
||||
onClick={
|
||||
showWarning ? () => this.setState({ showModal: true }) : () => actionHandler(action)
|
||||
}
|
||||
>
|
||||
{name}
|
||||
</EuiTextColor>
|
||||
</EuiButton>
|
||||
{this.state.showModal && (
|
||||
<EuiOverlayMask>
|
||||
<EuiConfirmModal
|
||||
|
|
|
@ -4,193 +4,54 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
EuiButton,
|
||||
// @ts-ignore
|
||||
EuiCard,
|
||||
EuiContextMenu,
|
||||
EuiPanel,
|
||||
EuiPopover,
|
||||
EuiTextColor,
|
||||
EuiToolTip,
|
||||
EuiToolTipProps,
|
||||
} from '@elastic/eui';
|
||||
import { EuiIcon } from '@elastic/eui';
|
||||
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
|
||||
import { isArray } from 'lodash';
|
||||
import React from 'react';
|
||||
import { AssignmentControlSchema } from '../index';
|
||||
import { ActionComponentType, ControlSchema } from '../action_schema';
|
||||
import { AssignmentActionType } from '../table';
|
||||
import { ActionControl } from './action_control';
|
||||
import { TagBadgeList } from './tag_badge_list';
|
||||
|
||||
interface ComponentProps {
|
||||
intl: InjectedIntl;
|
||||
itemType: string;
|
||||
items?: any[];
|
||||
schema: AssignmentControlSchema[];
|
||||
selectionCount: number;
|
||||
interface ComponentProps extends ControlSchema {
|
||||
actionData?: {
|
||||
[key: string]: any;
|
||||
};
|
||||
disabled: boolean;
|
||||
actionHandler(action: AssignmentActionType, payload?: any): void;
|
||||
}
|
||||
|
||||
interface ComponentState {
|
||||
showPopover: boolean;
|
||||
}
|
||||
|
||||
interface FixedEuiToolTipProps extends EuiToolTipProps {
|
||||
delay: 'regular' | 'long';
|
||||
}
|
||||
const FixedEuiToolTip = (EuiToolTip as any) as React.SFC<FixedEuiToolTipProps>;
|
||||
|
||||
class OptionControlUi extends React.PureComponent<ComponentProps, ComponentState> {
|
||||
constructor(props: ComponentProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
showPopover: false,
|
||||
};
|
||||
}
|
||||
|
||||
public schemaToPanelTree(
|
||||
schemaOrArray: AssignmentControlSchema | AssignmentControlSchema[],
|
||||
panels: any = []
|
||||
) {
|
||||
const { items, actionHandler, intl } = this.props;
|
||||
|
||||
let schema: AssignmentControlSchema | null = null;
|
||||
let schemaArray: AssignmentControlSchema[] | null = null;
|
||||
|
||||
if (isArray(schemaOrArray)) {
|
||||
schemaArray = schemaOrArray as AssignmentControlSchema[];
|
||||
} else {
|
||||
schema = schemaOrArray as AssignmentControlSchema;
|
||||
}
|
||||
|
||||
const panel: any = {
|
||||
title: schema ? schema.name : undefined,
|
||||
id: panels.length,
|
||||
};
|
||||
|
||||
if (schemaArray) {
|
||||
panel.items = schemaArray.map(def => {
|
||||
return {
|
||||
onClick: def.lazyLoad ? () => actionHandler(AssignmentActionType.Reload) : undefined,
|
||||
panel: def.panel ? def.panel.id : undefined,
|
||||
name: def.action ? (
|
||||
<ActionControl
|
||||
actionHandler={actionHandler}
|
||||
action={def.action}
|
||||
danger={def.danger}
|
||||
name={def.name}
|
||||
showWarning={def.showWarning}
|
||||
warningHeading={def.warningHeading}
|
||||
warningMessage={def.warningMessage}
|
||||
/>
|
||||
) : (
|
||||
<EuiTextColor color={def.danger ? 'danger' : 'default'}>{def.name}</EuiTextColor>
|
||||
),
|
||||
};
|
||||
});
|
||||
} else {
|
||||
if (items === undefined) {
|
||||
panel.content = intl.formatMessage({
|
||||
id: 'xpack.beatsManagement.tableControls.unknownErrorMessage',
|
||||
defaultMessage: 'Unknown Error.',
|
||||
});
|
||||
} else if (items.length === 0) {
|
||||
panel.content = (
|
||||
<EuiPanel>
|
||||
<EuiCard
|
||||
icon={<EuiIcon size="l" type="bolt" />}
|
||||
title={intl.formatMessage({
|
||||
id: 'xpack.beatsManagement.tableControls.noTagsFoundTitle',
|
||||
defaultMessage: 'No tags found.',
|
||||
})}
|
||||
description={intl.formatMessage({
|
||||
id: 'xpack.beatsManagement.tableControls.noTagsFoundDescription',
|
||||
defaultMessage: 'Please create a new configuration tag.',
|
||||
})}
|
||||
/>
|
||||
</EuiPanel>
|
||||
);
|
||||
} else {
|
||||
panel.content = <TagBadgeList items={items} actionHandler={actionHandler} />;
|
||||
export const OptionControl: React.SFC<ComponentProps> = (props: ComponentProps) => {
|
||||
switch (props.type) {
|
||||
case ActionComponentType.Action:
|
||||
if (!props.action) {
|
||||
throw Error('Action cannot be undefined');
|
||||
}
|
||||
}
|
||||
|
||||
panels.push(panel);
|
||||
|
||||
if (schemaArray !== null) {
|
||||
schemaArray.forEach((item: AssignmentControlSchema) => {
|
||||
if (item.panel) {
|
||||
this.schemaToPanelTree(item.panel, panels);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return panels;
|
||||
return (
|
||||
<ActionControl
|
||||
actionHandler={props.actionHandler}
|
||||
action={props.action}
|
||||
danger={props.danger}
|
||||
name={props.name}
|
||||
showWarning={props.showWarning}
|
||||
warningHeading={props.warningHeading}
|
||||
warningMessage={props.warningMessage}
|
||||
disabled={props.disabled}
|
||||
/>
|
||||
);
|
||||
case ActionComponentType.TagBadgeList:
|
||||
if (!props.actionDataKey) {
|
||||
throw Error('actionDataKey cannot be undefined');
|
||||
}
|
||||
if (!props.actionData) {
|
||||
throw Error('actionData cannot be undefined');
|
||||
}
|
||||
return (
|
||||
<TagBadgeList
|
||||
actionHandler={props.actionHandler}
|
||||
action={props.action}
|
||||
name={props.name}
|
||||
items={props.actionData[props.actionDataKey]}
|
||||
disabled={props.disabled}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { itemType, selectionCount, schema, intl } = this.props;
|
||||
|
||||
return (
|
||||
<EuiPopover
|
||||
button={
|
||||
<FixedEuiToolTip
|
||||
position="top"
|
||||
delay="long"
|
||||
content={
|
||||
selectionCount === 0
|
||||
? intl.formatMessage(
|
||||
{
|
||||
id: 'xpack.beatsManagement.tableControls.selectItemDescription',
|
||||
defaultMessage:
|
||||
'Select {itemType} to perform operations such as setting tags and unenrolling Beats.',
|
||||
},
|
||||
{ itemType }
|
||||
)
|
||||
: intl.formatMessage(
|
||||
{
|
||||
id: 'xpack.beatsManagement.tableControls.manageSelectedItemDescription',
|
||||
defaultMessage: 'Manage your selected {itemType}',
|
||||
},
|
||||
{ itemType }
|
||||
)
|
||||
}
|
||||
>
|
||||
<EuiButton
|
||||
color="primary"
|
||||
iconSide="right"
|
||||
disabled={selectionCount === 0}
|
||||
iconType="arrowDown"
|
||||
onClick={() => {
|
||||
this.setState({
|
||||
showPopover: true,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.beatsManagement.tableControls.manageItemButtonLabel"
|
||||
defaultMessage="Manage {itemType}"
|
||||
values={{ itemType }}
|
||||
/>
|
||||
</EuiButton>
|
||||
</FixedEuiToolTip>
|
||||
}
|
||||
closePopover={() => {
|
||||
this.setState({ showPopover: false });
|
||||
}}
|
||||
id="assignmentList"
|
||||
isOpen={this.state.showPopover}
|
||||
panelPaddingSize="none"
|
||||
anchorPosition="downLeft"
|
||||
withTitle
|
||||
>
|
||||
<EuiContextMenu initialPanelId={0} panels={this.schemaToPanelTree(schema)} />
|
||||
</EuiPopover>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const OptionControl = injectI18n(OptionControlUi);
|
||||
return <div>Invalid config</div>;
|
||||
};
|
||||
|
|
|
@ -1,55 +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 { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { TABLE_CONFIG } from '../../../../common/constants';
|
||||
import { TagBadge } from '../../tag/tag_badge';
|
||||
|
||||
interface TagAssignmentProps {
|
||||
tag: any;
|
||||
assignTag(id: string): void;
|
||||
}
|
||||
|
||||
interface TagAssignmentState {
|
||||
isFetchingTags: boolean;
|
||||
}
|
||||
|
||||
export class TagAssignment extends React.PureComponent<TagAssignmentProps, TagAssignmentState> {
|
||||
constructor(props: TagAssignmentProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isFetchingTags: false,
|
||||
};
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {
|
||||
assignTag,
|
||||
tag,
|
||||
tag: { id },
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="xs" key={id}>
|
||||
{this.state.isFetchingTags && (
|
||||
<EuiFlexItem>
|
||||
<EuiLoadingSpinner size="m" />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem>
|
||||
<TagBadge
|
||||
maxIdRenderSize={TABLE_CONFIG.TRUNCATE_TAG_LENGTH_SMALL}
|
||||
onClick={() => assignTag(id)}
|
||||
onClickAriaLabel={id}
|
||||
tag={tag}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -4,26 +4,91 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import {
|
||||
EuiButton,
|
||||
EuiContextMenuPanel,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiPopover,
|
||||
} from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { TABLE_CONFIG } from '../../../../common/constants/table';
|
||||
import { TagBadge } from '../../tag/tag_badge';
|
||||
import { AssignmentActionType } from '../index';
|
||||
import { TagAssignment } from './tag_assignment';
|
||||
|
||||
interface TagBadgeListProps {
|
||||
items: any[];
|
||||
disabled: boolean;
|
||||
name: string;
|
||||
action?: AssignmentActionType;
|
||||
actionHandler(action: AssignmentActionType, payload?: any): void;
|
||||
}
|
||||
|
||||
export const TagBadgeList = (props: TagBadgeListProps) => (
|
||||
// @ts-ignore direction prop type "column" not defined in current EUI version
|
||||
<EuiFlexGroup direction="column" gutterSize="xs" style={{ margin: 10 }}>
|
||||
{props.items.map((item: any) => (
|
||||
<EuiFlexItem key={`${item.id}`}>
|
||||
<TagAssignment
|
||||
tag={item}
|
||||
assignTag={(id: string) => props.actionHandler(AssignmentActionType.Assign, id)}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
interface ComponentState {
|
||||
isPopoverOpen: boolean;
|
||||
}
|
||||
|
||||
export class TagBadgeList extends React.Component<TagBadgeListProps, ComponentState> {
|
||||
constructor(props: TagBadgeListProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isPopoverOpen: false,
|
||||
};
|
||||
}
|
||||
|
||||
public render() {
|
||||
const button = (
|
||||
<EuiButton
|
||||
size="s"
|
||||
iconType="arrowDown"
|
||||
iconSide="right"
|
||||
onClick={this.onButtonClick}
|
||||
disabled={this.props.disabled}
|
||||
>
|
||||
{this.props.name}
|
||||
</EuiButton>
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiPopover
|
||||
id="contentPanel"
|
||||
button={button}
|
||||
isOpen={this.state.isPopoverOpen}
|
||||
closePopover={this.closePopover}
|
||||
panelPaddingSize="none"
|
||||
anchorPosition="downLeft"
|
||||
>
|
||||
<EuiContextMenuPanel>
|
||||
<EuiFlexGroup direction="column" gutterSize="xs" style={{ margin: 10 }}>
|
||||
{this.props.items.map((tag: any) => (
|
||||
<EuiFlexItem key={`${tag.id}`}>
|
||||
<EuiFlexGroup gutterSize="xs">
|
||||
<EuiFlexItem>
|
||||
<TagBadge
|
||||
maxIdRenderSize={TABLE_CONFIG.TRUNCATE_TAG_LENGTH_SMALL}
|
||||
onClick={() => this.props.actionHandler(AssignmentActionType.Assign, tag.id)}
|
||||
onClickAriaLabel={tag.id}
|
||||
tag={tag}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
</EuiFlexGroup>
|
||||
</EuiContextMenuPanel>
|
||||
</EuiPopover>
|
||||
);
|
||||
}
|
||||
private onButtonClick = () => {
|
||||
this.setState(prevState => ({
|
||||
isPopoverOpen: !prevState.isPopoverOpen,
|
||||
}));
|
||||
};
|
||||
|
||||
private closePopover = () => {
|
||||
this.setState({
|
||||
isPopoverOpen: false,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -4,13 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { AssignmentActionType, AssignmentOptions, KueryBarProps, Table } from './table';
|
||||
export {
|
||||
AssignmentControlSchema,
|
||||
beatsListAssignmentOptions,
|
||||
tagConfigAssignmentOptions,
|
||||
} from './assignment_schema';
|
||||
export { ControlBar } from './controls';
|
||||
export { beatsListActions, tagConfigActions } from './action_schema';
|
||||
export { AssignmentActionType, KueryBarProps, Table } from './table';
|
||||
export {
|
||||
ActionDefinition,
|
||||
BeatDetailTagsTable,
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
// @ts-ignore no typings for EuiInMemoryTable in EUI
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
// @ts-ignore
|
||||
EuiInMemoryTable,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
|
@ -14,8 +16,9 @@ import React from 'react';
|
|||
import styled from 'styled-components';
|
||||
import { AutocompleteSuggestion } from 'ui/autocomplete_providers';
|
||||
import { TABLE_CONFIG } from '../../../common/constants';
|
||||
import { AssignmentControlSchema } from './assignment_schema';
|
||||
import { ControlBar } from './controls';
|
||||
import { AutocompleteField } from '../autocomplete_field/index';
|
||||
import { ControlSchema } from './action_schema';
|
||||
import { OptionControl } from './controls/option_control';
|
||||
import { TableType } from './table_type_configs';
|
||||
|
||||
export enum AssignmentActionType {
|
||||
|
@ -27,13 +30,6 @@ export enum AssignmentActionType {
|
|||
Search,
|
||||
}
|
||||
|
||||
export interface AssignmentOptions {
|
||||
schema: AssignmentControlSchema[];
|
||||
items: any[];
|
||||
type?: 'none' | 'primary' | 'assignment';
|
||||
actionHandler(action: AssignmentActionType, payload?: any): void;
|
||||
}
|
||||
|
||||
export interface KueryBarProps {
|
||||
filterQueryDraft: string;
|
||||
isLoadingSuggestions: boolean;
|
||||
|
@ -46,11 +42,15 @@ export interface KueryBarProps {
|
|||
}
|
||||
|
||||
interface TableProps {
|
||||
assignmentOptions?: AssignmentOptions;
|
||||
actions?: ControlSchema[];
|
||||
actionData?: {
|
||||
[key: string]: any;
|
||||
};
|
||||
hideTableControls?: boolean;
|
||||
kueryBarProps?: KueryBarProps;
|
||||
items: any[];
|
||||
type: TableType;
|
||||
actionHandler?(action: AssignmentActionType, payload?: any): void;
|
||||
}
|
||||
|
||||
interface TableState {
|
||||
|
@ -80,8 +80,14 @@ export class Table extends React.Component<TableProps, TableState> {
|
|||
});
|
||||
};
|
||||
|
||||
public actionHandler = (action: AssignmentActionType, payload?: any): void => {
|
||||
if (this.props.actionHandler) {
|
||||
this.props.actionHandler(action, payload);
|
||||
}
|
||||
};
|
||||
|
||||
public render() {
|
||||
const { assignmentOptions, hideTableControls, items, kueryBarProps, type } = this.props;
|
||||
const { actionData, actions, hideTableControls, items, kueryBarProps, type } = this.props;
|
||||
|
||||
const pagination = {
|
||||
initialPageSize: TABLE_CONFIG.INITIAL_ROW_SIZE,
|
||||
|
@ -102,14 +108,33 @@ export class Table extends React.Component<TableProps, TableState> {
|
|||
|
||||
return (
|
||||
<TableContainer>
|
||||
{!hideTableControls && assignmentOptions && (
|
||||
<ControlBar
|
||||
itemType={type.itemType}
|
||||
assignmentOptions={assignmentOptions}
|
||||
kueryBarProps={kueryBarProps}
|
||||
selectionCount={this.state.selection.length}
|
||||
/>
|
||||
)}
|
||||
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween">
|
||||
{actions &&
|
||||
actions.map(action => (
|
||||
<EuiFlexItem grow={false} key={action.name}>
|
||||
<OptionControl
|
||||
{...action}
|
||||
actionData={actionData}
|
||||
actionHandler={this.actionHandler}
|
||||
disabled={this.state.selection.length === 0}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
|
||||
{kueryBarProps && (
|
||||
<EuiFlexItem>
|
||||
<AutocompleteField
|
||||
{...kueryBarProps}
|
||||
placeholder={i18n.translate(
|
||||
'xpack.beatsManagement.table.filterResultsPlaceholder',
|
||||
{
|
||||
defaultMessage: 'Filter results',
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiInMemoryTable
|
||||
columns={type.columnDefinitions}
|
||||
|
|
|
@ -1,29 +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 {
|
||||
// @ts-ignore typings for EuiSearchar not included in EUI
|
||||
EuiSearchBar,
|
||||
} from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { FilterDefinition } from '../table';
|
||||
import { AssignmentActionType } from './table';
|
||||
|
||||
interface TableSearchControlProps {
|
||||
filters?: FilterDefinition[];
|
||||
actionHandler(action: AssignmentActionType, payload?: any): void;
|
||||
}
|
||||
|
||||
export const TableSearchControl = (props: TableSearchControlProps) => {
|
||||
const { actionHandler, filters } = props;
|
||||
return (
|
||||
<EuiSearchBar
|
||||
box={{ incremental: true }}
|
||||
filters={filters}
|
||||
onChange={(query: any) => actionHandler(AssignmentActionType.Search, query)}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -164,8 +164,8 @@ export const BeatsTableType: TableType = {
|
|||
name: i18n.translate('xpack.beatsManagement.beatsTable.lastConfigUpdateTitle', {
|
||||
defaultMessage: 'Last config update',
|
||||
}),
|
||||
render: (tags: BeatTag[]) =>
|
||||
tags.length ? (
|
||||
render: (tags?: BeatTag[]) =>
|
||||
tags && tags.length ? (
|
||||
<span>
|
||||
{moment(first(sortByOrder(tags, ['last_updated'], ['desc'])).last_updated).fromNow()}
|
||||
</span>
|
||||
|
|
|
@ -24,7 +24,6 @@ export const TagBadge = (props: TagBadgeProps) => {
|
|||
onClickAriaLabel,
|
||||
tag: { color, disabled, id },
|
||||
} = props;
|
||||
|
||||
const maxIdRenderSize = props.maxIdRenderSize || TABLE_CONFIG.TRUNCATE_TAG_LENGTH;
|
||||
const idToRender = id.length > maxIdRenderSize ? `${id.substring(0, maxIdRenderSize)}...` : id;
|
||||
return disabled ? (
|
||||
|
|
|
@ -27,7 +27,7 @@ import { isEqual } from 'lodash';
|
|||
import React from 'react';
|
||||
import { BeatTag, CMBeat, ConfigurationBlock } from '../../../common/domain_types';
|
||||
import { ConfigList } from '../config_list';
|
||||
import { AssignmentActionType, BeatsTableType, Table, tagConfigAssignmentOptions } from '../table';
|
||||
import { AssignmentActionType, BeatsTableType, Table, tagConfigActions } from '../table';
|
||||
import { ConfigView } from './config_view';
|
||||
import { TagBadge } from './tag_badge';
|
||||
|
||||
|
@ -190,12 +190,8 @@ export class TagEdit extends React.PureComponent<TagEditProps, TagEditState> {
|
|||
</h3>
|
||||
</EuiTitle>
|
||||
<Table
|
||||
assignmentOptions={{
|
||||
schema: tagConfigAssignmentOptions,
|
||||
items: [],
|
||||
type: 'primary',
|
||||
actionHandler: this.handleAssignmentActions,
|
||||
}}
|
||||
actions={tagConfigActions}
|
||||
actionHandler={this.handleAssignmentActions}
|
||||
items={attachedBeats}
|
||||
ref={this.state.tableRef}
|
||||
type={BeatsTableType}
|
||||
|
|
|
@ -73,7 +73,10 @@ export class BeatsContainer extends Container<ContainerState> {
|
|||
}
|
||||
const assignments = createBeatTagAssignments(beats, tagId);
|
||||
await this.libs.beats.removeTagsFromBeats(assignments);
|
||||
await this.reload(this.query);
|
||||
// ES responds incorrectly when we call too soon
|
||||
setTimeout(async () => {
|
||||
await this.reload(this.query);
|
||||
}, 150);
|
||||
};
|
||||
|
||||
public assignTagsToBeats = async (beats: CMPopulatedBeat[] | string[], tagId: string) => {
|
||||
|
@ -82,7 +85,10 @@ export class BeatsContainer extends Container<ContainerState> {
|
|||
}
|
||||
const assignments = createBeatTagAssignments(beats, tagId);
|
||||
await this.libs.beats.assignTagsToBeats(assignments);
|
||||
await this.reload(this.query);
|
||||
// ES responds incorrectly when we call too soon
|
||||
setTimeout(async () => {
|
||||
await this.reload(this.query);
|
||||
}, 150);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import React from 'react';
|
|||
import { BeatTag } from '../../../common/domain_types';
|
||||
import { Breadcrumb } from '../../components/navigation/breadcrumb';
|
||||
import { AssignmentActionType, Table, TagsTableType } from '../../components/table';
|
||||
import { tagListAssignmentOptions } from '../../components/table/assignment_schema';
|
||||
import { tagListActions } from '../../components/table/action_schema';
|
||||
import { WithKueryAutocompletion } from '../../containers/with_kuery_autocompletion';
|
||||
import { AppPageProps } from '../../frontend_types';
|
||||
|
||||
|
@ -79,12 +79,8 @@ class TagsPageComponent extends React.PureComponent<PageProps, PageState> {
|
|||
onSubmit: () => null, // todo
|
||||
value: this.props.urlState.tagsKBar || '',
|
||||
}}
|
||||
assignmentOptions={{
|
||||
schema: tagListAssignmentOptions,
|
||||
type: 'primary',
|
||||
items: [],
|
||||
actionHandler: this.handleTagsAction,
|
||||
}}
|
||||
actions={tagListActions}
|
||||
actionHandler={this.handleTagsAction}
|
||||
ref={this.state.tableRef}
|
||||
items={this.props.containers.tags.state.list}
|
||||
type={TagsTableType}
|
||||
|
|
|
@ -24,7 +24,7 @@ import { BeatTag, CMPopulatedBeat, ConfigurationBlock } from '../../../common/do
|
|||
import { EnrollBeat } from '../../components/enroll_beats';
|
||||
import { Breadcrumb } from '../../components/navigation/breadcrumb';
|
||||
import { BeatsTableType, Table } from '../../components/table';
|
||||
import { beatsListAssignmentOptions } from '../../components/table/assignment_schema';
|
||||
import { beatsListActions } from '../../components/table/action_schema';
|
||||
import { AssignmentActionType } from '../../components/table/table';
|
||||
import { WithKueryAutocompletion } from '../../containers/with_kuery_autocompletion';
|
||||
import { AppPageProps } from '../../frontend_types';
|
||||
|
@ -167,28 +167,27 @@ class BeatsPageComponent extends React.PureComponent<PageProps, PageState> {
|
|||
onSubmit: () => null, // todo
|
||||
value: this.props.urlState.beatsKBar || '',
|
||||
}}
|
||||
assignmentOptions={{
|
||||
items: this.filterTags(this.props.containers.tags.state.list),
|
||||
schema: beatsListAssignmentOptions,
|
||||
type: 'assignment',
|
||||
actionHandler: async (action: AssignmentActionType, payload: any) => {
|
||||
switch (action) {
|
||||
case AssignmentActionType.Assign:
|
||||
const status = await this.props.containers.beats.toggleTagAssignment(
|
||||
payload,
|
||||
this.getSelectedBeats()
|
||||
);
|
||||
this.notifyUpdatedTagAssociation(status, this.getSelectedBeats(), payload);
|
||||
break;
|
||||
case AssignmentActionType.Delete:
|
||||
this.props.containers.beats.deactivate(this.getSelectedBeats());
|
||||
this.notifyBeatDisenrolled(this.getSelectedBeats());
|
||||
break;
|
||||
case AssignmentActionType.Reload:
|
||||
this.props.containers.tags.reload();
|
||||
break;
|
||||
}
|
||||
},
|
||||
actions={beatsListActions}
|
||||
actionData={{
|
||||
tags: this.filterTags(this.props.containers.tags.state.list),
|
||||
}}
|
||||
actionHandler={async (action: AssignmentActionType, payload: any) => {
|
||||
switch (action) {
|
||||
case AssignmentActionType.Assign:
|
||||
const status = await this.props.containers.beats.toggleTagAssignment(
|
||||
payload,
|
||||
this.getSelectedBeats()
|
||||
);
|
||||
this.notifyUpdatedTagAssociation(status, this.getSelectedBeats(), payload);
|
||||
break;
|
||||
case AssignmentActionType.Delete:
|
||||
this.props.containers.beats.deactivate(this.getSelectedBeats());
|
||||
this.notifyBeatDisenrolled(this.getSelectedBeats());
|
||||
break;
|
||||
case AssignmentActionType.Reload:
|
||||
this.props.containers.tags.reload();
|
||||
break;
|
||||
}
|
||||
}}
|
||||
items={sortBy(this.props.containers.beats.state.list, 'id') || []}
|
||||
ref={this.tableRef}
|
||||
|
@ -349,7 +348,9 @@ class BeatsPageComponent extends React.PureComponent<PageProps, PageState> {
|
|||
// union beat tags
|
||||
flatten(this.getSelectedBeats().map(({ full_tags }) => full_tags))
|
||||
// map tag list to bool
|
||||
.map(({ configuration_blocks }) => this.configBlocksRequireUniqueness(configuration_blocks))
|
||||
.map(tag => {
|
||||
return this.configBlocksRequireUniqueness(tag ? tag.configuration_blocks : []);
|
||||
})
|
||||
// reduce to result
|
||||
.reduce((acc, cur) => acc || cur, false);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue