Skip to content

Commit

Permalink
[Security Solution] - Timeline UI refactor (#168230)
Browse files Browse the repository at this point in the history
## Summary

This PR implements many small refactors in the Timeline UI. I have
listed all the changes below which can help you while you are desk
testing.

### EQL Bar

|Before|After|
|--|--|

|![image](https://github.com/elastic/kibana/assets/7485038/6fbe05cf-5dab-4912-a1a8-2f5465c67c99)|![image](https://github.com/elastic/kibana/assets/7485038/b989e2e5-d124-400c-b12e-24c306d38561)|

### Timeline Title Bar / Bottom Bar

Below screenshots show how timeline bottom bar has changed. Things to
note:

- Favorite button is now just an icon with the title. User can simply
click on it to favorite /un-favorite a timeline

|Before | After|
|---|---|

|![image](https://github.com/elastic/kibana/assets/7485038/4c96e17a-ab7e-4bf4-8df2-e5d5f1f33bde)|![image](https://github.com/elastic/kibana/assets/7485038/c2f0ea5b-bf7b-48e2-b14f-43b9afee16bc)|

Below screenshots show how timeline title bar has changed. Things to
note :
- A new timeline action menu has been added to right to timeline title
bar.
- All actions such as create a new timeline, a new timeline template.
adding timeline to case, etc can be performed from here.

|Before|After|
|---|---|

|![image](https://github.com/elastic/kibana/assets/7485038/293d105e-a03b-4191-a73a-69e78cfdeb2c)|![image](https://github.com/elastic/kibana/assets/7485038/44ac7f00-3897-4c64-86f9-161376290b2e)|


- On the left side of the Timeline Header below are the changes.
- Timeline Title is not longer a button/link, so timeline cannot be
closed by clicking on that.
- ⊕ action menu is not longer available and corresponding actions are
available in above screenshots.

|Before|After|
|--|--|

|![image](https://github.com/elastic/kibana/assets/7485038/f76691e9-3f6a-46bf-9720-a10bcd428f97)|![image](https://github.com/elastic/kibana/assets/7485038/fee4e846-f0d3-45bf-9139-d3b21d93c567)|


### Timeline Header Panel

Below timeline header panel has been completely removed.


![image](https://github.com/elastic/kibana/assets/7485038/c7eb27a2-0314-49e6-8e6d-8db11badd4a8)
 

### Changes on how Data provider works

1. Data provider is by-default hidden in normal timeline but visible in
template timeline.
2. Data provider can be toggled by the user on-demand.
3. Data Provider will automatically become visible if user wants to put
a data grid column value in data provider and stars dragging it. Below
videos shows how that interaction works.


https://github.com/elastic/kibana/assets/7485038/c7232596-40aa-4687-9fcf-e4a707be8a76

### KPI

This PR also changes how KPIs are visible in empty and populated state.

|Before|After|
|---|---|

|![image](https://github.com/elastic/kibana/assets/7485038/0c5c24d8-d9cc-4554-b9ba-768be69a0eb5)|![image](https://github.com/elastic/kibana/assets/7485038/173debdb-cdae-4547-a5f2-913c1b4561aa)|

KPI bar has been completely removed till this issue resolves:
#171569

### Query Bar

In contrast to current layout of the query bar, DataView picker, Query
bar and Date Picker has been brought in the same line. This was done in
an effort to make it uniform in looks w.r.t the global query bar.

---------------
#### Before

![Screenshot 2023-11-21 at 11 58
31](https://github.com/elastic/kibana/assets/7485038/9e62491e-a500-4a94-9421-cb2fdcb7eb7c)

--------------
#### After
All the highlighted components are in the same line now + A button to
toggle Data Provider ( as explained in Data Porvider/QueryBuilder
Section) has also been added.


![image](https://github.com/elastic/kibana/assets/7485038/d2df322b-23dc-4f1b-9167-ece32ca70947)


### Spacing Uniformity

In the existing version of timeline, spacing is different at many
places. This PR aims to bring some uniformity to those spacing decisions
( primarily in EQL and Query Tab). The changes are very minor visually,
please feel free to find and report any discrepancies.

### Checklist

Delete any items that are not applicable to this PR.

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

---------

Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
logeekal and kibanamachine authored Nov 27, 2023
1 parent f7fa846 commit 72d2457
Show file tree
Hide file tree
Showing 108 changed files with 3,229 additions and 2,142 deletions.
2 changes: 2 additions & 0 deletions src/plugins/unified_search/public/filter_bar/filter_bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ const FilterBarUI = React.memo(function FilterBarUI(props: Props) {
gutterSize="none" // We use `gap` in the styles instead for better truncation of badges
alignItems="center"
tabIndex={-1}
data-test-subj="filter-items-group"
className={`filter-items-group ${props.className ?? ''}`}
>
{props.prepend}
<FilterItems
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ const RoundBadge = styled(EuiBadge)`
margin: 0 5px 0 5px;
padding: 7px 6px 4px 6px;
user-select: none;
width: 34px;
width: 40px;
height: 40px;
.euiBadge__content {
position: relative;
top: -1px;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,13 @@

import { noop, pick } from 'lodash/fp';
import React, { useCallback, useMemo } from 'react';
import type { DropResult } from '@hello-pangea/dnd';
import type { DragStart, DropResult } from '@hello-pangea/dnd';
import { DragDropContext } from '@hello-pangea/dnd';
import { useDispatch } from 'react-redux';
import type { Dispatch } from 'redux';
import deepEqual from 'fast-deep-equal';
import { IS_DRAGGING_CLASS_NAME } from '@kbn/securitysolution-t-grid';

import type { BeforeCapture } from './drag_drop_context';
import type { BrowserFields } from '../../containers/source';
import { dragAndDropSelectors } from '../../store';
import { timelineSelectors } from '../../../timelines/store/timeline';
Expand Down Expand Up @@ -151,8 +150,9 @@ export const DragDropContextWrapperComponent: React.FC<Props> = ({ browserFields
},
[activeTimelineDataProviders, browserFields, dataProviders, dispatch, onAddedToTimeline]
);

return (
<DragDropContext onDragEnd={onDragEnd} onBeforeCapture={onBeforeCapture} sensors={sensors}>
<DragDropContext onBeforeDragStart={onBeforeDragStart} onDragEnd={onDragEnd} sensors={sensors}>
{children}
</DragDropContext>
);
Expand All @@ -168,12 +168,12 @@ export const DragDropContextWrapper = React.memo(

DragDropContextWrapper.displayName = 'DragDropContextWrapper';

const onBeforeCapture = (before: BeforeCapture) => {
if (!draggableIsField(before)) {
const onBeforeDragStart = (start: DragStart) => {
if (!draggableIsField(start)) {
document.body.classList.add(IS_DRAGGING_CLASS_NAME);
}

if (draggableIsField(before)) {
if (draggableIsField(start)) {
document.body.classList.add(IS_TIMELINE_FIELD_DRAGGING_CLASS_NAME);
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,11 @@

import { EuiButton, EuiWindowEvent } from '@elastic/eui';
import React, { useCallback } from 'react';
import styled from 'styled-components';

import * as i18n from './translations';

export const EXIT_FULL_SCREEN_CLASS_NAME = 'exit-full-screen';

const StyledEuiButton = styled(EuiButton)`
margin: ${({ theme }) => theme.eui.euiSizeS};
`;

interface Props {
fullScreen: boolean;
setFullScreen: (fullScreen: boolean) => void;
Expand Down Expand Up @@ -45,16 +40,17 @@ const ExitFullScreenComponent: React.FC<Props> = ({ fullScreen, setFullScreen })
return (
<>
<EuiWindowEvent event="keydown" handler={onKeyDown} />
<StyledEuiButton
<EuiButton
className={EXIT_FULL_SCREEN_CLASS_NAME}
data-test-subj="exit-full-screen"
fullWidth={false}
iconType="fullScreen"
fill
isDisabled={!fullScreen}
onClick={exitFullScreen}
>
{i18n.EXIT_FULL_SCREEN}
</StyledEuiButton>
</EuiButton>
</>
);
};
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ const HeaderSectionComponent: React.FC<HeaderSectionProps> = ({
stackHeader,
subtitle,
title,
titleSize = 'm',
titleSize = 'l',
toggleQuery,
toggleStatus = true,
tooltip,
Expand Down Expand Up @@ -173,7 +173,6 @@ const HeaderSectionComponent: React.FC<HeaderSectionProps> = ({
<span className="eui-textBreakNormal">{title}</span>
{tooltip && (
<>
{' '}
<EuiIconTip
color="subdued"
title={tooltipTitle}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ interface InspectButtonProps {
onCloseInspect?: () => void;
queryId: string;
showInspectButton?: boolean;
title: string | React.ReactElement | React.ReactNode;
title?: string | React.ReactElement | React.ReactNode;
}

const InspectButtonComponent: React.FC<InspectButtonProps> = ({
Expand Down Expand Up @@ -80,9 +80,6 @@ const InspectButtonComponent: React.FC<InspectButtonProps> = ({
className={BUTTON_CLASS}
aria-label={i18n.INSPECT}
data-test-subj="inspect-empty-button"
color="text"
iconSide="left"
iconType="inspect"
isDisabled={isButtonDisabled}
isLoading={loading}
onClick={handleClick}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,6 @@ export const QueryBar = memo<QueryBarComponentProps>(
savedQuery={savedQuery}
displayStyle={displayStyle}
isDisabled={isDisabled}
hideTextBasedRunQueryLabel
/>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React from 'react';

import { Sourcerer } from '.';
import { sourcererModel } from '../../store/sourcerer';
import {
createSecuritySolutionStorageMock,
kibanaObservable,
mockGlobalState,
SUB_PLUGINS_REDUCER,
TestProviders,
} from '../../mock';
import { createStore } from '../../store';
import { useSourcererDataView } from '../../containers/sourcerer';
import { useSignalHelpers } from '../../containers/sourcerer/use_signal_helpers';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';

const mockDispatch = jest.fn();

jest.mock('../../containers/sourcerer');
jest.mock('../../containers/sourcerer/use_signal_helpers');
const mockUseUpdateDataView = jest.fn().mockReturnValue(() => true);
jest.mock('./use_update_data_view', () => ({
useUpdateDataView: () => mockUseUpdateDataView,
}));
jest.mock('react-redux', () => {
const original = jest.requireActual('react-redux');

return {
...original,
useDispatch: () => mockDispatch,
};
});

jest.mock('@kbn/kibana-react-plugin/public', () => {
const original = jest.requireActual('@kbn/kibana-react-plugin/public');

return {
...original,
toMountPoint: jest.fn(),
};
});

const mockUpdateUrlParam = jest.fn();
jest.mock('../../utils/global_query_string', () => {
const original = jest.requireActual('../../utils/global_query_string');

return {
...original,
useUpdateUrlParam: () => mockUpdateUrlParam,
};
});

let store: ReturnType<typeof createStore>;
const sourcererDataView = {
indicesExist: true,
loading: false,
};
describe('sourcerer on alerts page or rules details page', () => {
const { storage } = createSecuritySolutionStorageMock();
store = createStore(mockGlobalState, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
const testProps = {
scope: sourcererModel.SourcererScopeName.detections,
};

const pollForSignalIndexMock = jest.fn();

beforeEach(async () => {
jest.clearAllMocks();

(useSignalHelpers as jest.Mock).mockReturnValue({
pollForSignalIndex: pollForSignalIndexMock,
signalIndexNeedsInit: false,
});

(useSourcererDataView as jest.Mock).mockReturnValue({
...sourcererDataView,
indicesExist: true,
});

render(
<TestProviders store={store}>
<Sourcerer {...testProps} />
</TestProviders>
);

fireEvent.click(screen.getByTestId('sourcerer-trigger'));
await waitFor(() => {
expect(screen.getByTestId('sourcerer-advanced-options-toggle')).toBeVisible();
});
fireEvent.click(screen.getByTestId('sourcerer-advanced-options-toggle'));
});

it('renders an alerts badge in sourcerer button', () => {
expect(screen.getByTestId('sourcerer-advanced-options-toggle')).toHaveTextContent(
/Advanced options/
);
});

it('renders a callout', () => {
expect(screen.getByTestId('sourcerer-callout')).toHaveTextContent(
'Data view cannot be modified on this page'
);
});

it('disable data view selector', () => {
expect(screen.getByTestId('sourcerer-select')).toBeDisabled();
});

it('data view selector is default to Security Data View', () => {
expect(screen.getByTestId('sourcerer-select')).toHaveTextContent(/security data view/i);
});

it('renders an alert badge in data view selector', () => {
expect(screen.getByTestId('security-alerts-option-badge')).toHaveTextContent('Alerts');
});

it('disable index pattern selector', () => {
expect(screen.getByTestId('sourcerer-combo-box')).toHaveAttribute('disabled');
});

it('shows signal index as index pattern option', () => {
expect(screen.getByTestId('euiComboBoxPill')).toHaveTextContent('.siem-signals-spacename');
});

it('does not render reset button', () => {
expect(screen.queryByTestId('sourcerer-reset')).toBeFalsy();
});

it('does not render save button', () => {
expect(screen.queryByTestId('sourcerer-save')).toBeFalsy();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type { EuiSuperSelectOption, EuiFormRowProps } from '@elastic/eui';
import { EuiIcon, EuiBadge, EuiButtonEmpty, EuiFormRow } from '@elastic/eui';
import styled, { css } from 'styled-components';

import { euiThemeVars } from '@kbn/ui-theme';
import type { sourcererModel } from '../../store/sourcerer';

import * as i18n from './translations';
Expand All @@ -23,7 +24,7 @@ export const StyledFormRow = styled(EuiFormRow)`
max-width: none;
`;

export const StyledButton = styled(EuiButtonEmpty)`
export const StyledButtonEmpty = styled(EuiButtonEmpty)`
&:enabled:focus,
&:focus {
background-color: transparent;
Expand All @@ -43,7 +44,7 @@ export const PopoverContent = styled.div`
`;

export const StyledBadge = styled(EuiBadge)`
margin-left: 8px;
margin-left: ${euiThemeVars.euiSizeXS};
&,
.euiBadge__text {
cursor: pointer;
Expand Down
Loading

0 comments on commit 72d2457

Please sign in to comment.