mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[SIEM] Apply highlighting to the Timeline data providers drop area and flyout button (#45173)
## Summary To indicate they accept droppable data providers, this PR applies highlighting to the Timeline flyout badge and data providers drop area. Tested in dark and light mode in: - Chrome `76.0.3809.132` - Firefox `69.0` - Safari `12.1.2` ## Timeline data providers (drop area) highlighting behavior - While a data provider is being dragged, in a page or from within the timeline itself, apply `euiColorSuccess` to the timeline data providers drop area's dashed borders and "plain" text, but NOT to the other primitives, i.e. data provider badges, `AND` / `OR` circular badges, lines, etc - While a data provider is being dragged, in a page or from within the timeline itself, but NOT hovering over the data providers drop area, apply the `euiColorSuccess` color with `10%` alpha channel to the background of the data providers drop area - While a data provider is being dragged over the drop target area inside the data providers drop zone, apply the `euiColorSuccess` color with `20%` alpha channel to the background of the data providers drop area - While a data provider is being dragged over the drop target area inside the data providers drop zone that will add the data provider as an `AND` clause, apply `euiColorSuccess` color to the dashed border, and fill the background with the `euiColorSuccess` color with `30%` alpha channel to the background of the data providers drop area - Do NOT apply highlighting styles when a column or field (non-data provider) is dragged from the timeline or the `Events` widget ### Dark mode  ### Light mode  ## Flyout button highlighting behavior - The (default) timeline flyout button is styled as non-filled EUI button - The button text reads `Timeline ^` (with a trailing caret), instead of `T I M E L I N E` - Hovering over the flyout button while NOT dragging underlines the flyout button text, but does not fill the background - When a user starts dragging a data provider, apply `euiColorSuccess` to all visible flyout button properties (e.g. border, text) - While a data provider is being dragged, in a page or from within the timeline itself, but NOT hovering over the flyout, apply the `euiColorSuccess` color with `10%` alpha channel to the background of the flyout button - While a data provider is being dragged, in a page or from within the timeline itself, AND hovering over the flyout such that it's in a droppable state, apply the `euiColorSuccess` color with `20%` alpha channel to the background of the flyout button - While a data provider is being dragged, in a page or from within the timeline itself, AND hovering over the flyout such that it's in a droppable state, replace the `Timeline ^` with a circle containing a `+` plus sign - Apply `EuiNotificationBadge` styling to the data providers badge count, and position it in the upper left-hand corner - Do NOT apply highlighting styles when a column or field (non-data provider) is dragged from the timeline or the `Events` widget ### Dark mode  ### Light mode  Resolves https://github.com/elastic/siem-team/issues/457
This commit is contained in:
parent
9a109f2170
commit
40147fa83f
13 changed files with 427 additions and 127 deletions
|
@ -37,3 +37,10 @@ export const drop = (dropTarget: JQuery<HTMLElement>) => {
|
|||
.trigger('mousemove', { button: primaryButton })
|
||||
.trigger('mouseup');
|
||||
};
|
||||
|
||||
/** Drags the subject being dragged on the specified drop target, but does not drop it */
|
||||
export const dragWithoutDrop = (dropTarget: JQuery<HTMLElement>) => {
|
||||
cy.wrap(dropTarget).trigger('mousemove', 'center', {
|
||||
button: primaryButton,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -10,12 +10,20 @@ export const DATA_PROVIDER = '[data-test-subj="providerContainer"]';
|
|||
/** Data providers are dropped and rendered in this area of the timeline */
|
||||
export const TIMELINE_DATA_PROVIDERS = '[data-test-subj="dataProviders"]';
|
||||
|
||||
/** The empty data providers area when no data providers have been added to the timeline */
|
||||
export const TIMELINE_DATA_PROVIDERS_EMPTY =
|
||||
'[data-test-subj="dataProviders"] [data-test-subj="empty"]';
|
||||
|
||||
/** Data providers that were dropped on a timeline */
|
||||
export const TIMELINE_DROPPED_DATA_PROVIDERS = `${TIMELINE_DATA_PROVIDERS} ${DATA_PROVIDER}`;
|
||||
|
||||
/** The `T I M E L I N E` button that toggles visibility of the Timeline */
|
||||
/** The `Timeline ^` button that toggles visibility of the Timeline */
|
||||
export const TIMELINE_TOGGLE_BUTTON = '[data-test-subj="flyoutOverlay"]';
|
||||
|
||||
/** The flyout button shown when a data provider is not ready to be dropped on the timeline */
|
||||
export const TIMELINE_NOT_READY_TO_DROP_BUTTON =
|
||||
'[data-test-subj="flyout-button-not-ready-to-drop"]';
|
||||
|
||||
/** Contains the KQL bar for searching or filtering in the timeline */
|
||||
export const SEARCH_OR_FILTER_CONTAINER =
|
||||
'[data-test-subj="timeline-search-or-filter-search-container"]';
|
||||
|
@ -26,3 +34,6 @@ export const SERVER_SIDE_EVENT_COUNT = '[data-test-subj="server-side-event-count
|
|||
/** Expands or collapses an event in the timeline */
|
||||
export const TOGGLE_TIMELINE_EXPAND_EVENT =
|
||||
'[data-test-subj="timeline"] [data-test-subj="expand-event"]';
|
||||
|
||||
/** The body of the timeline flyout */
|
||||
export const TIMELINE_FLYOUT_BODY = '[data-test-subj="eui-flyout-body"]';
|
||||
|
|
|
@ -5,12 +5,17 @@
|
|||
*/
|
||||
|
||||
import { logout } from '../../lib/logout';
|
||||
import { TIMELINE_DROPPED_DATA_PROVIDERS } from '../../lib/timeline/selectors';
|
||||
import {
|
||||
TIMELINE_DATA_PROVIDERS,
|
||||
TIMELINE_DROPPED_DATA_PROVIDERS,
|
||||
TIMELINE_DATA_PROVIDERS_EMPTY,
|
||||
} from '../../lib/timeline/selectors';
|
||||
import { dragFromAllHostsToTimeline, toggleTimelineVisibility } from '../../lib/timeline/helpers';
|
||||
import { ALL_HOSTS_WIDGET_DRAGGABLE_HOSTS } from '../../lib/hosts/selectors';
|
||||
import { HOSTS_PAGE } from '../../lib/urls';
|
||||
import { waitForAllHostsWidget } from '../../lib/hosts/helpers';
|
||||
import { DEFAULT_TIMEOUT, loginAndWaitForPage } from '../../lib/util/helpers';
|
||||
import { drag, dragWithoutDrop } from '../../lib/drag_n_drop/helpers';
|
||||
|
||||
describe('timeline data providers', () => {
|
||||
beforeEach(() => {
|
||||
|
@ -43,4 +48,60 @@ describe('timeline data providers', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('sets the background to euiColorSuccess with a 10% alpha channel when the user starts dragging a host, but is not hovering over the data providers', () => {
|
||||
waitForAllHostsWidget();
|
||||
|
||||
toggleTimelineVisibility();
|
||||
|
||||
cy.get(ALL_HOSTS_WIDGET_DRAGGABLE_HOSTS)
|
||||
.first()
|
||||
.then(host => drag(host));
|
||||
|
||||
cy.get(TIMELINE_DATA_PROVIDERS).should(
|
||||
'have.css',
|
||||
'background',
|
||||
'rgba(125, 226, 209, 0.1) none repeat scroll 0% 0% / auto padding-box border-box'
|
||||
);
|
||||
});
|
||||
|
||||
it('sets the background to euiColorSuccess with a 20% alpha channel when the user starts dragging a host AND is hovering over the data providers', () => {
|
||||
waitForAllHostsWidget();
|
||||
|
||||
toggleTimelineVisibility();
|
||||
|
||||
cy.get(ALL_HOSTS_WIDGET_DRAGGABLE_HOSTS)
|
||||
.first()
|
||||
.then(host => drag(host));
|
||||
|
||||
cy.get(TIMELINE_DATA_PROVIDERS_EMPTY).then(dataProvidersDropArea =>
|
||||
dragWithoutDrop(dataProvidersDropArea)
|
||||
);
|
||||
|
||||
cy.get(TIMELINE_DATA_PROVIDERS_EMPTY).should(
|
||||
'have.css',
|
||||
'background',
|
||||
'rgba(125, 226, 209, 0.2) none repeat scroll 0% 0% / auto padding-box border-box'
|
||||
);
|
||||
});
|
||||
|
||||
it('renders the dashed border color as euiColorSuccess when hovering over the data providers', () => {
|
||||
waitForAllHostsWidget();
|
||||
|
||||
toggleTimelineVisibility();
|
||||
|
||||
cy.get(ALL_HOSTS_WIDGET_DRAGGABLE_HOSTS)
|
||||
.first()
|
||||
.then(host => drag(host));
|
||||
|
||||
cy.get(TIMELINE_DATA_PROVIDERS_EMPTY).then(dataProvidersDropArea =>
|
||||
dragWithoutDrop(dataProvidersDropArea)
|
||||
);
|
||||
|
||||
cy.get(TIMELINE_DATA_PROVIDERS).should(
|
||||
'have.css',
|
||||
'border',
|
||||
'3.1875px dashed rgb(125, 226, 209)'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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 { logout } from '../../lib/logout';
|
||||
import {
|
||||
TIMELINE_FLYOUT_BODY,
|
||||
TIMELINE_NOT_READY_TO_DROP_BUTTON,
|
||||
} from '../../lib/timeline/selectors';
|
||||
import { ALL_HOSTS_WIDGET_DRAGGABLE_HOSTS } from '../../lib/hosts/selectors';
|
||||
import { HOSTS_PAGE } from '../../lib/urls';
|
||||
import { waitForAllHostsWidget } from '../../lib/hosts/helpers';
|
||||
import { loginAndWaitForPage } from '../../lib/util/helpers';
|
||||
import { drag } from '../../lib/drag_n_drop/helpers';
|
||||
import { toggleTimelineVisibility } from '../../lib/timeline/helpers';
|
||||
|
||||
describe('timeline flyout button', () => {
|
||||
beforeEach(() => {
|
||||
loginAndWaitForPage(HOSTS_PAGE);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
return logout();
|
||||
});
|
||||
|
||||
it('toggles open the timeline', () => {
|
||||
toggleTimelineVisibility();
|
||||
|
||||
cy.get(TIMELINE_FLYOUT_BODY).should('have.css', 'visibility', 'visible');
|
||||
});
|
||||
|
||||
it('sets the flyout button background to euiColorSuccess with a 10% alpha channel when the user starts dragging a host, but is not hovering over the flyout button', () => {
|
||||
waitForAllHostsWidget();
|
||||
|
||||
cy.get(ALL_HOSTS_WIDGET_DRAGGABLE_HOSTS)
|
||||
.first()
|
||||
.then(host => drag(host));
|
||||
|
||||
cy.get(TIMELINE_NOT_READY_TO_DROP_BUTTON).should(
|
||||
'have.css',
|
||||
'background',
|
||||
'rgba(125, 226, 209, 0.1) none repeat scroll 0% 0% / auto padding-box border-box'
|
||||
);
|
||||
});
|
||||
});
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import { defaultTo, noop } from 'lodash/fp';
|
||||
import * as React from 'react';
|
||||
import { DragDropContext, DropResult, ResponderProvided } from 'react-beautiful-dnd';
|
||||
import { DragDropContext, DropResult, ResponderProvided, DragStart } from 'react-beautiful-dnd';
|
||||
import { connect } from 'react-redux';
|
||||
import { Dispatch } from 'redux';
|
||||
|
||||
|
@ -19,8 +19,10 @@ import {
|
|||
addFieldToTimelineColumns,
|
||||
addProviderToTimeline,
|
||||
fieldWasDroppedOnTimelineColumns,
|
||||
IS_DRAGGING_CLASS_NAME,
|
||||
providerWasDroppedOnTimeline,
|
||||
providerWasDroppedOnTimelineButton,
|
||||
draggableIsField,
|
||||
} from './helpers';
|
||||
|
||||
interface Props {
|
||||
|
@ -65,7 +67,7 @@ export class DragDropContextWrapperComponent extends React.Component<Props> {
|
|||
const { children } = this.props;
|
||||
|
||||
return (
|
||||
<DragDropContext onDragEnd={this.onDragEnd} onDragStart={disableScrolling}>
|
||||
<DragDropContext onDragEnd={this.onDragEnd} onDragStart={onDragStart}>
|
||||
{children}
|
||||
</DragDropContext>
|
||||
);
|
||||
|
@ -86,6 +88,10 @@ export class DragDropContextWrapperComponent extends React.Component<Props> {
|
|||
dispatch,
|
||||
});
|
||||
}
|
||||
|
||||
if (!draggableIsField(result)) {
|
||||
document.body.classList.remove(IS_DRAGGING_CLASS_NAME);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -102,7 +108,7 @@ const mapStateToProps = (state: State) => {
|
|||
|
||||
export const DragDropContextWrapper = connect(mapStateToProps)(DragDropContextWrapperComponent);
|
||||
|
||||
const disableScrolling = () => {
|
||||
const onDragStart = (initial: DragStart) => {
|
||||
const x =
|
||||
window.pageXOffset !== undefined
|
||||
? window.pageXOffset
|
||||
|
@ -114,6 +120,10 @@ const disableScrolling = () => {
|
|||
: (document.documentElement || document.body.parentNode || document.body).scrollTop;
|
||||
|
||||
window.onscroll = () => window.scrollTo(x, y);
|
||||
|
||||
if (!draggableIsField(initial)) {
|
||||
document.body.classList.add(IS_DRAGGING_CLASS_NAME);
|
||||
}
|
||||
};
|
||||
|
||||
const enableScrolling = () => (window.onscroll = () => noop);
|
||||
|
|
|
@ -33,7 +33,7 @@ describe('DroppableWrapper', () => {
|
|||
expect(toJson(wrapper)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('it renders the children', () => {
|
||||
test('it renders the children when a render prop is not provided', () => {
|
||||
const message = 'draggable wrapper content';
|
||||
|
||||
const wrapper = mount(
|
||||
|
@ -48,5 +48,40 @@ describe('DroppableWrapper', () => {
|
|||
|
||||
expect(wrapper.text()).toEqual(message);
|
||||
});
|
||||
|
||||
test('it does NOT render the children if a render method is provided', () => {
|
||||
const message = 'draggable wrapper content';
|
||||
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<MockedProvider mocks={mocksSource} addTypename={false}>
|
||||
<DragDropContextWrapper browserFields={mockBrowserFields}>
|
||||
<DroppableWrapper render={() => null} droppableId="testing">
|
||||
<div data-test-subj="this-should-not-render">{message}</div>
|
||||
</DroppableWrapper>
|
||||
</DragDropContextWrapper>
|
||||
</MockedProvider>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(wrapper.find('[data-test-subj="this-should-not-render"]').exists()).toBe(false);
|
||||
});
|
||||
|
||||
test('it renders the render prop contents when a render prop is provided', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<MockedProvider mocks={mocksSource} addTypename={false}>
|
||||
<DragDropContextWrapper browserFields={mockBrowserFields}>
|
||||
<DroppableWrapper
|
||||
render={({ isDraggingOver }) => <div>{`isDraggingOver is: ${isDraggingOver}`}</div>}
|
||||
droppableId="testing"
|
||||
/>
|
||||
</DragDropContextWrapper>
|
||||
</MockedProvider>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(wrapper.text()).toEqual('isDraggingOver is: false');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,11 +9,15 @@ import { Droppable } from 'react-beautiful-dnd';
|
|||
import { pure } from 'recompose';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { THIRTY_PERCENT_ALPHA_HEX_SUFFIX, TWENTY_PERCENT_ALPHA_HEX_SUFFIX } from './helpers';
|
||||
|
||||
interface Props {
|
||||
children?: React.ReactNode;
|
||||
droppableId: string;
|
||||
height?: string;
|
||||
isDropDisabled?: boolean;
|
||||
type?: string;
|
||||
render?: ({ isDraggingOver }: { isDraggingOver: boolean }) => React.ReactNode;
|
||||
}
|
||||
|
||||
const ReactDndDropTarget = styled.div<{ isDraggingOver: boolean; height: string }>`
|
||||
|
@ -30,14 +34,22 @@ const ReactDndDropTarget = styled.div<{ isDraggingOver: boolean; height: string
|
|||
? `
|
||||
.drop-and-provider-timeline {
|
||||
&:hover {
|
||||
background-color: ${props.theme.eui.euiColorEmptyShade};
|
||||
background-color: ${props.theme.eui.euiColorSuccess}${THIRTY_PERCENT_ALPHA_HEX_SUFFIX};
|
||||
}
|
||||
}
|
||||
.drop-and-provider-timeline:hover {
|
||||
background-color: ${props.theme.eui.euiColorSuccess}${THIRTY_PERCENT_ALPHA_HEX_SUFFIX};
|
||||
}
|
||||
> div.timeline-drop-area-empty {
|
||||
background-color: ${props.theme.eui.euiColorLightShade};
|
||||
color: ${props.theme.eui.euiColorSuccess}
|
||||
background-color: ${props.theme.eui.euiColorSuccess}${TWENTY_PERCENT_ALPHA_HEX_SUFFIX};
|
||||
|
||||
& .euiTextColor--subdued {
|
||||
color: ${props.theme.eui.euiColorSuccess};
|
||||
}
|
||||
}
|
||||
> div.timeline-drop-area {
|
||||
background-color: ${props.theme.eui.euiColorLightShade};
|
||||
background-color: ${props.theme.eui.euiColorSuccess}${TWENTY_PERCENT_ALPHA_HEX_SUFFIX};
|
||||
.provider-item-filter-container div:first-child{
|
||||
// Override dragNdrop beautiful so we do not have our droppable moving around for no good reason
|
||||
transform: none !important;
|
||||
|
@ -48,6 +60,10 @@ const ReactDndDropTarget = styled.div<{ isDraggingOver: boolean; height: string
|
|||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
& .euiFormHelpText {
|
||||
color: ${props.theme.eui.euiColorSuccess};
|
||||
}
|
||||
}
|
||||
.flyout-overlay {
|
||||
.euiPanel {
|
||||
|
@ -74,7 +90,14 @@ const ReactDndDropTarget = styled.div<{ isDraggingOver: boolean; height: string
|
|||
ReactDndDropTarget.displayName = 'ReactDndDropTarget';
|
||||
|
||||
export const DroppableWrapper = pure<Props>(
|
||||
({ children, droppableId, height = '100%', isDropDisabled = false, type }) => (
|
||||
({
|
||||
children = null,
|
||||
droppableId,
|
||||
height = '100%',
|
||||
isDropDisabled = false,
|
||||
type,
|
||||
render = null,
|
||||
}) => (
|
||||
<Droppable
|
||||
isDropDisabled={isDropDisabled}
|
||||
droppableId={droppableId}
|
||||
|
@ -88,7 +111,7 @@ export const DroppableWrapper = pure<Props>(
|
|||
{...provided.droppableProps}
|
||||
isDraggingOver={snapshot.isDraggingOver}
|
||||
>
|
||||
{children}
|
||||
{render == null ? children : render({ isDraggingOver: snapshot.isDraggingOver })}
|
||||
{provided.placeholder}
|
||||
</ReactDndDropTarget>
|
||||
)}
|
||||
|
|
|
@ -52,10 +52,10 @@ export const getDroppableId = (visualizationPlaceholderId: string): string =>
|
|||
export const sourceIsContent = (result: DropResult): boolean =>
|
||||
result.source.droppableId.startsWith(droppableContentPrefix);
|
||||
|
||||
export const draggableIsContent = (result: DropResult): boolean =>
|
||||
export const draggableIsContent = (result: DropResult | { draggableId: string }): boolean =>
|
||||
result.draggableId.startsWith(draggableContentPrefix);
|
||||
|
||||
export const draggableIsField = (result: DropResult): boolean =>
|
||||
export const draggableIsField = (result: DropResult | { draggableId: string }): boolean =>
|
||||
result.draggableId.startsWith(draggableFieldPrefix);
|
||||
|
||||
export const reasonIsDrop = (result: DropResult): boolean => result.reason === 'DROP';
|
||||
|
@ -221,3 +221,15 @@ export const updateShowTimeline = ({
|
|||
* header drop zone in the timeline
|
||||
*/
|
||||
export const DRAG_TYPE_FIELD = 'drag-type-field';
|
||||
|
||||
/** This class is added to the document body while dragging */
|
||||
export const IS_DRAGGING_CLASS_NAME = 'is-dragging';
|
||||
|
||||
/** A hex alpha channel suffix representing 10% for the `AA` in `#RRGGBBAA` */
|
||||
export const TEN_PERCENT_ALPHA_HEX_SUFFIX = '1A';
|
||||
|
||||
/** A hex alpha channel suffix representing 20% for the `AA` in `#RRGGBBAA` */
|
||||
export const TWENTY_PERCENT_ALPHA_HEX_SUFFIX = '33';
|
||||
|
||||
/** A hex alpha channel suffix representing 30% for the `AA` in `#RRGGBBAA` */
|
||||
export const THIRTY_PERCENT_ALPHA_HEX_SUFFIX = '4d';
|
||||
|
|
|
@ -4,88 +4,88 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiBadge, EuiBadgeProps, EuiPanel, EuiText } from '@elastic/eui';
|
||||
import { EuiNotificationBadge, EuiIcon, EuiButton } from '@elastic/eui';
|
||||
import * as React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { DroppableWrapper } from '../../drag_and_drop/droppable_wrapper';
|
||||
import { droppableTimelineFlyoutButtonPrefix } from '../../drag_and_drop/helpers';
|
||||
import {
|
||||
droppableTimelineFlyoutButtonPrefix,
|
||||
IS_DRAGGING_CLASS_NAME,
|
||||
TEN_PERCENT_ALPHA_HEX_SUFFIX,
|
||||
TWENTY_PERCENT_ALPHA_HEX_SUFFIX,
|
||||
} from '../../drag_and_drop/helpers';
|
||||
import { DataProvider } from '../../timeline/data_providers/data_provider';
|
||||
|
||||
import * as i18n from './translations';
|
||||
|
||||
export const NOT_READY_TO_DROP_CLASS_NAME = 'not-ready-to-drop';
|
||||
export const READY_TO_DROP_CLASS_NAME = 'ready-to-drop';
|
||||
|
||||
const Container = styled.div`
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
padding-top: 8px;
|
||||
position: fixed;
|
||||
top: 40%;
|
||||
right: -3px;
|
||||
min-width: 50px;
|
||||
max-width: 80px;
|
||||
right: -49px;
|
||||
z-index: 9;
|
||||
height: 240px;
|
||||
max-height: 240px;
|
||||
transform: rotate(-90deg);
|
||||
user-select: none;
|
||||
|
||||
button {
|
||||
border-bottom: none;
|
||||
border-radius: 4px 4px 0 0;
|
||||
box-shadow: none;
|
||||
height: 46px;
|
||||
margin: 1px 0 1px 1px;
|
||||
width: 136px;
|
||||
}
|
||||
|
||||
.euiButton:hover:not(:disabled) {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.euiButton--primary:enabled {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.euiButton--primary:enabled:hover,
|
||||
.euiButton--primary:enabled:focus {
|
||||
animation: none;
|
||||
background-color: inherit;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.${IS_DRAGGING_CLASS_NAME} & .${NOT_READY_TO_DROP_CLASS_NAME} {
|
||||
color: ${({ theme }) => theme.eui.euiColorSuccess};
|
||||
background: ${({ theme }) =>
|
||||
`${theme.eui.euiColorSuccess}${TEN_PERCENT_ALPHA_HEX_SUFFIX} !important`};
|
||||
border: 1px solid ${({ theme }) => theme.eui.euiColorSuccess};
|
||||
border-bottom: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.${READY_TO_DROP_CLASS_NAME} {
|
||||
color: ${({ theme }) => theme.eui.euiColorSuccess};
|
||||
background: ${({ theme }) =>
|
||||
`${theme.eui.euiColorSuccess}${TWENTY_PERCENT_ALPHA_HEX_SUFFIX} !important`};
|
||||
border: 1px solid ${({ theme }) => theme.eui.euiColorSuccess};
|
||||
border-bottom: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
`;
|
||||
|
||||
Container.displayName = 'Container';
|
||||
|
||||
const BadgeButtonContainer = styled.div`
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-direction: row;
|
||||
`;
|
||||
|
||||
BadgeButtonContainer.displayName = 'BadgeButtonContainer';
|
||||
|
||||
export const Button = styled(EuiPanel)`
|
||||
display: flex;
|
||||
z-index: 9;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
border-top: 1px solid ${({ theme }) => theme.eui.euiColorLightShade};
|
||||
border-bottom: 1px solid ${({ theme }) => theme.eui.euiColorLightShade};
|
||||
border-left: 1px solid ${({ theme }) => theme.eui.euiColorLightShade};
|
||||
border-radius: 6px 0 0 6px;
|
||||
box-shadow: ${({ theme }) =>
|
||||
`0 3px 3px -1px ${theme.eui.euiColorLightestShade}, 0 5px 7px -2px ${theme.eui.euiColorLightestShade}`};
|
||||
background-color: inherit;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
Button.displayName = 'Button';
|
||||
|
||||
export const Text = styled(EuiText)`
|
||||
width: 12px;
|
||||
z-index: 10;
|
||||
user-select: none;
|
||||
`;
|
||||
|
||||
Text.displayName = 'Text';
|
||||
|
||||
// Ref: https://github.com/elastic/eui/issues/1655
|
||||
// export const Badge = styled(EuiBadge)`
|
||||
// border-radius: 5px;
|
||||
// min-width: 25px;
|
||||
// padding: 0px;
|
||||
// transform: translateY(10px);
|
||||
// z-index: 10;
|
||||
// `;
|
||||
export const Badge = (props: EuiBadgeProps) => (
|
||||
<EuiBadge
|
||||
{...props}
|
||||
style={{
|
||||
borderRadius: '5px',
|
||||
minWidth: '25px',
|
||||
padding: '0px',
|
||||
textAlign: 'center',
|
||||
transform: 'translateY(10px)',
|
||||
zIndex: 10,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
Badge.displayName = 'Badge';
|
||||
|
||||
interface FlyoutButtonProps {
|
||||
dataProviders: DataProvider[];
|
||||
onOpen: () => void;
|
||||
|
@ -96,27 +96,53 @@ interface FlyoutButtonProps {
|
|||
export const FlyoutButton = React.memo<FlyoutButtonProps>(
|
||||
({ onOpen, show, dataProviders, timelineId }) =>
|
||||
show ? (
|
||||
<Container>
|
||||
<DroppableWrapper droppableId={`${droppableTimelineFlyoutButtonPrefix}${timelineId}`}>
|
||||
<BadgeButtonContainer
|
||||
className="flyout-overlay"
|
||||
data-test-subj="flyoutOverlay"
|
||||
onClick={onOpen}
|
||||
>
|
||||
{dataProviders.length !== 0 && (
|
||||
<Badge data-test-subj="badge" color="primary">
|
||||
<Container onClick={onOpen}>
|
||||
<DroppableWrapper
|
||||
data-test-subj="flyout-droppable-wrapper"
|
||||
droppableId={`${droppableTimelineFlyoutButtonPrefix}${timelineId}`}
|
||||
render={({ isDraggingOver }) => (
|
||||
<BadgeButtonContainer
|
||||
className="flyout-overlay"
|
||||
data-test-subj="flyoutOverlay"
|
||||
onClick={onOpen}
|
||||
>
|
||||
{!isDraggingOver ? (
|
||||
<EuiButton
|
||||
className={NOT_READY_TO_DROP_CLASS_NAME}
|
||||
data-test-subj="flyout-button-not-ready-to-drop"
|
||||
fill={false}
|
||||
iconSide="right"
|
||||
iconType="arrowUp"
|
||||
>
|
||||
{i18n.FLYOUT_BUTTON}
|
||||
</EuiButton>
|
||||
) : (
|
||||
<EuiButton
|
||||
className={READY_TO_DROP_CLASS_NAME}
|
||||
data-test-subj="flyout-button-ready-to-drop"
|
||||
fill={false}
|
||||
>
|
||||
<EuiIcon data-test-subj="flyout-button-plus-icon" type="plusInCircleFilled" />
|
||||
</EuiButton>
|
||||
)}
|
||||
|
||||
<EuiNotificationBadge
|
||||
color="accent"
|
||||
data-test-subj="badge"
|
||||
style={{
|
||||
left: '-9px',
|
||||
position: 'relative',
|
||||
top: '-6px',
|
||||
transform: 'rotate(90deg)',
|
||||
visibility: dataProviders.length !== 0 ? 'inherit' : 'hidden',
|
||||
zIndex: 10,
|
||||
}}
|
||||
>
|
||||
{dataProviders.length}
|
||||
</Badge>
|
||||
)}
|
||||
<Button>
|
||||
<Text data-test-subj="flyoutButton" size="s">
|
||||
{i18n.TIMELINE.toLocaleUpperCase()
|
||||
.split('')
|
||||
.join(' ')}
|
||||
</Text>
|
||||
</Button>
|
||||
</BadgeButtonContainer>
|
||||
</DroppableWrapper>
|
||||
</EuiNotificationBadge>
|
||||
</BadgeButtonContainer>
|
||||
)}
|
||||
/>
|
||||
</Container>
|
||||
) : null,
|
||||
(prevProps, nextProps) =>
|
||||
|
|
|
@ -9,3 +9,7 @@ import { i18n } from '@kbn/i18n';
|
|||
export const TIMELINE = i18n.translate('xpack.siem.flyout.button.timeline', {
|
||||
defaultMessage: 'timeline',
|
||||
});
|
||||
|
||||
export const FLYOUT_BUTTON = i18n.translate('xpack.siem.flyout.button.text', {
|
||||
defaultMessage: 'Timeline',
|
||||
});
|
||||
|
|
|
@ -52,10 +52,10 @@ describe('Flyout', () => {
|
|||
|
||||
expect(
|
||||
wrapper
|
||||
.find('[data-test-subj="flyoutButton"]')
|
||||
.find('[data-test-subj="flyout-button-not-ready-to-drop"]')
|
||||
.first()
|
||||
.text()
|
||||
).toContain('T I M E L I N E');
|
||||
).toContain('Timeline');
|
||||
});
|
||||
|
||||
test('it renders the title field when its state is set to flyout is true', () => {
|
||||
|
@ -96,7 +96,9 @@ describe('Flyout', () => {
|
|||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(wrapper.find('[data-test-subj="flyoutButton"]').exists()).toEqual(false);
|
||||
expect(wrapper.find('[data-test-subj="flyout-button-not-ready-to-drop"]').exists()).toEqual(
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
test('it renders the flyout body', () => {
|
||||
|
@ -173,7 +175,7 @@ describe('Flyout', () => {
|
|||
).toContain('10');
|
||||
});
|
||||
|
||||
test('it does NOT render the data providers badge when the number is equal to 0', () => {
|
||||
test('it hides the data providers badge when the timeline does NOT have data providers', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<Flyout
|
||||
|
@ -185,7 +187,39 @@ describe('Flyout', () => {
|
|||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(wrapper.find('[data-test-subj="badge"]').exists()).toEqual(false);
|
||||
expect(
|
||||
wrapper
|
||||
.find('[data-test-subj="badge"]')
|
||||
.first()
|
||||
.props().style!.visibility
|
||||
).toEqual('hidden');
|
||||
});
|
||||
|
||||
test('it does NOT hide the data providers badge when the timeline has data providers', () => {
|
||||
const stateWithDataProviders = set(
|
||||
'timeline.timelineById.test.dataProviders',
|
||||
mockDataProviders,
|
||||
state
|
||||
);
|
||||
const storeWithDataProviders = createStore(stateWithDataProviders, apolloClientObservable);
|
||||
|
||||
const wrapper = mount(
|
||||
<TestProviders store={storeWithDataProviders}>
|
||||
<Flyout
|
||||
flyoutHeight={testFlyoutHeight}
|
||||
headerHeight={flyoutHeaderHeight}
|
||||
timelineId="test"
|
||||
usersViewing={usersViewing}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find('[data-test-subj="badge"]')
|
||||
.first()
|
||||
.props().style!.visibility
|
||||
).toEqual('inherit');
|
||||
});
|
||||
|
||||
test('should call the onOpen when the mouse is clicked for rendering', () => {
|
||||
|
@ -255,7 +289,9 @@ describe('Flyout', () => {
|
|||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(wrapper.find('[data-test-subj="flyoutButton"]').exists()).toEqual(true);
|
||||
expect(wrapper.find('[data-test-subj="flyout-button-not-ready-to-drop"]').exists()).toEqual(
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
test('should NOT show the flyout button when show is false', () => {
|
||||
|
@ -270,7 +306,9 @@ describe('Flyout', () => {
|
|||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(wrapper.find('[data-test-subj="flyoutButton"]').exists()).toEqual(false);
|
||||
expect(wrapper.find('[data-test-subj="flyout-button-not-ready-to-drop"]').exists()).toEqual(
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
test('should return the flyout button with text', () => {
|
||||
|
@ -287,10 +325,10 @@ describe('Flyout', () => {
|
|||
);
|
||||
expect(
|
||||
wrapper
|
||||
.find('[data-test-subj="flyoutButton"]')
|
||||
.find('[data-test-subj="flyout-button-not-ready-to-drop"]')
|
||||
.first()
|
||||
.text()
|
||||
).toContain('T I M E L I N E');
|
||||
).toContain('Timeline');
|
||||
});
|
||||
|
||||
test('should call the onOpen when it is clicked', () => {
|
||||
|
|
|
@ -10,7 +10,11 @@ import styled from 'styled-components';
|
|||
|
||||
import { BrowserFields } from '../../../containers/source';
|
||||
import { DroppableWrapper } from '../../drag_and_drop/droppable_wrapper';
|
||||
import { droppableTimelineProvidersPrefix } from '../../drag_and_drop/helpers';
|
||||
import {
|
||||
droppableTimelineProvidersPrefix,
|
||||
IS_DRAGGING_CLASS_NAME,
|
||||
TEN_PERCENT_ALPHA_HEX_SUFFIX,
|
||||
} from '../../drag_and_drop/helpers';
|
||||
import {
|
||||
OnChangeDataProviderKqlQuery,
|
||||
OnChangeDroppableAndProvider,
|
||||
|
@ -38,6 +42,21 @@ interface Props {
|
|||
show: boolean;
|
||||
}
|
||||
|
||||
const DropTargetDataProvidersContainer = styled.div`
|
||||
.${IS_DRAGGING_CLASS_NAME} & .drop-target-data-providers {
|
||||
background: ${({ theme }) => `${theme.eui.euiColorSuccess}${TEN_PERCENT_ALPHA_HEX_SUFFIX}`};
|
||||
border: 0.2rem dashed ${({ theme }) => theme.eui.euiColorSuccess};
|
||||
|
||||
& .euiTextColor--subdued {
|
||||
color: ${({ theme }) => theme.eui.euiColorSuccess};
|
||||
}
|
||||
|
||||
& .euiFormHelpText {
|
||||
color: ${({ theme }) => theme.eui.euiColorSuccess};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const DropTargetDataProviders = styled.div`
|
||||
position: relative;
|
||||
border: 0.2rem dashed ${props => props.theme.eui.euiColorMediumShade};
|
||||
|
@ -85,29 +104,34 @@ export const DataProviders = pure<Props>(
|
|||
onToggleDataProviderExcluded,
|
||||
show,
|
||||
}) => (
|
||||
<DropTargetDataProviders data-test-subj="dataProviders">
|
||||
<TimelineContext.Consumer>
|
||||
{isLoading => (
|
||||
<DroppableWrapper isDropDisabled={!show || isLoading} droppableId={getDroppableId(id)}>
|
||||
{dataProviders != null && dataProviders.length ? (
|
||||
<Providers
|
||||
browserFields={browserFields}
|
||||
id={id}
|
||||
dataProviders={dataProviders}
|
||||
onChangeDataProviderKqlQuery={onChangeDataProviderKqlQuery}
|
||||
onChangeDroppableAndProvider={onChangeDroppableAndProvider}
|
||||
onDataProviderEdited={onDataProviderEdited}
|
||||
onDataProviderRemoved={onDataProviderRemoved}
|
||||
onToggleDataProviderEnabled={onToggleDataProviderEnabled}
|
||||
onToggleDataProviderExcluded={onToggleDataProviderExcluded}
|
||||
/>
|
||||
) : (
|
||||
<Empty />
|
||||
)}
|
||||
</DroppableWrapper>
|
||||
)}
|
||||
</TimelineContext.Consumer>
|
||||
</DropTargetDataProviders>
|
||||
<DropTargetDataProvidersContainer className="drop-target-data-providers-container">
|
||||
<DropTargetDataProviders
|
||||
className="drop-target-data-providers"
|
||||
data-test-subj="dataProviders"
|
||||
>
|
||||
<TimelineContext.Consumer>
|
||||
{isLoading => (
|
||||
<DroppableWrapper isDropDisabled={!show || isLoading} droppableId={getDroppableId(id)}>
|
||||
{dataProviders != null && dataProviders.length ? (
|
||||
<Providers
|
||||
browserFields={browserFields}
|
||||
id={id}
|
||||
dataProviders={dataProviders}
|
||||
onChangeDataProviderKqlQuery={onChangeDataProviderKqlQuery}
|
||||
onChangeDroppableAndProvider={onChangeDroppableAndProvider}
|
||||
onDataProviderEdited={onDataProviderEdited}
|
||||
onDataProviderRemoved={onDataProviderRemoved}
|
||||
onToggleDataProviderEnabled={onToggleDataProviderEnabled}
|
||||
onToggleDataProviderExcluded={onToggleDataProviderExcluded}
|
||||
/>
|
||||
) : (
|
||||
<Empty />
|
||||
)}
|
||||
</DroppableWrapper>
|
||||
)}
|
||||
</TimelineContext.Consumer>
|
||||
</DropTargetDataProviders>
|
||||
</DropTargetDataProvidersContainer>
|
||||
)
|
||||
);
|
||||
|
||||
|
|
|
@ -19,11 +19,13 @@ import {
|
|||
OnToggleDataProviderExcluded,
|
||||
} from '../events';
|
||||
|
||||
import { BrowserFields } from '../../../containers/source';
|
||||
import { TWENTY_PERCENT_ALPHA_HEX_SUFFIX } from '../../drag_and_drop/helpers';
|
||||
|
||||
import { DataProvider } from './data_provider';
|
||||
import { ProviderItemAnd } from './provider_item_and';
|
||||
|
||||
import * as i18n from './translations';
|
||||
import { BrowserFields } from '../../../containers/source';
|
||||
|
||||
const DropAndTargetDataProvidersContainer = styled(EuiFlexItem)`
|
||||
margin: 0px 8px;
|
||||
|
@ -34,7 +36,7 @@ DropAndTargetDataProvidersContainer.displayName = 'DropAndTargetDataProvidersCon
|
|||
const DropAndTargetDataProviders = styled.div<{ hasAndItem: boolean }>`
|
||||
min-width: 230px;
|
||||
width: auto;
|
||||
border: 0.1rem dashed ${props => props.theme.eui.euiColorMediumShade};
|
||||
border: 0.1rem dashed ${props => props.theme.eui.euiColorSuccess};
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
padding: 3px 10px;
|
||||
|
@ -45,7 +47,7 @@ const DropAndTargetDataProviders = styled.div<{ hasAndItem: boolean }>`
|
|||
props.hasAndItem
|
||||
? `&:hover {
|
||||
transition: background-color 0.7s ease;
|
||||
background-color: ${props.theme.eui.euiColorEmptyShade};
|
||||
background-color: ${() => props.theme.eui.euiColorSuccess}${TWENTY_PERCENT_ALPHA_HEX_SUFFIX};
|
||||
}`
|
||||
: ''};
|
||||
cursor: ${({ hasAndItem }) => (!hasAndItem ? `default` : 'inherit')};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue