Skip to content

Commit

Permalink
[SecuritySolution] Render histograms with Lens (#147261)
Browse files Browse the repository at this point in the history
## Summary


Relevant issue: #136409

These are all behind feature flag `chartEmbeddablesEnabled`

---
### Changes:
1. Legends are all moved to the left side of the chart to avoid
overlapping with chart actions.
2. The second `group by` of Alerts Trend Chart is removed (as it is
always disabled).


---

### Events:
<img width="2545" alt="Screenshot 2023-01-25 at 15 33 27"
src="https://user-images.githubusercontent.com/6295984/214605761-2e08d848-feb3-42e3-9850-08cd7f8b9c99.png">

---
### Top N

<img width="2552" alt="Screenshot 2023-01-25 at 15 34 21"
src="https://user-images.githubusercontent.com/6295984/214605889-91f74c33-e8c4-43ac-b137-6098f4764a6d.png">

---

### No indices:
<img width="1671" alt="Screenshot 2022-12-21 at 17 03 05"
src="https://user-images.githubusercontent.com/6295984/208963467-ccbb6bf4-11b3-4bc8-b568-208dd0791828.png">

---

### Alerts - Trend
<img width="2548" alt="Screenshot 2023-01-25 at 15 34 52"
src="https://user-images.githubusercontent.com/6295984/214605993-ea1a2fe5-6f44-4c70-9152-79f76ed9e48f.png">

---

### ~Alerts - Treemap~ (Not included in this PR -
#149592)
**Big Difference after converting to Lens. Likely to have a redesign:**

Known issues:
1. Alerts tree map: Is not rendered exactly the same due to the limits
of dimension of Lens.
3. No value display in each legend item for alerts tree map
4. Background color cannot be decided by risk score


Before:
<img width="1661" alt="Screenshot 2023-01-10 at 12 00 51"
src="https://user-images.githubusercontent.com/6295984/211546444-f98b0f4a-0666-493c-b57e-934b3d154211.png">

After:
<img width="2550" alt="Screenshot 2023-01-25 at 15 35 16"
src="https://user-images.githubusercontent.com/6295984/214606117-7a1e78a4-8947-4d95-9994-2acb106ca3af.png">

---
### ~Alerts - Charts~ (Not included in this PR -
#149592)
**Lens does not support the
[design](elastic/security-team#5599). Likely
to have a redesign:**


<img width="2536" alt="Screenshot 2023-01-25 at 15 35 55"
src="https://user-images.githubusercontent.com/6295984/214606298-bcc97920-bb52-4367-901f-102a778799b6.png">

---

### Alerts - Table
<img width="1666" alt="Screenshot 2022-12-21 at 17 12 25"
src="https://user-images.githubusercontent.com/6295984/208964514-b39e40ae-ecb4-4e06-8cc5-32d63e28823a.png">

Known issues:
1. #149828
2. Unable to restore a column after hiding it.
3. #150048
4. #150158


---
### Alerts - Preview

Known issue:
There's no legend in alerts preview as its legend action, filter in,
filter out are not useful on rule creation page. -
#149220

<img width="2543" alt="Screenshot 2023-01-26 at 13 32 00"
src="https://user-images.githubusercontent.com/6295984/214848163-437ca866-46ee-47ee-a550-aa3b9c97eef6.png">





### Checklist

- [x] [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
angorayc and kibanamachine authored Feb 2, 2023
1 parent 3c14f00 commit 9cfec58
Show file tree
Hide file tree
Showing 55 changed files with 2,782 additions and 270 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ jest.mock('react-router-dom', () => {
useLocation: jest.fn().mockReturnValue({ pathname: '/test' }),
};
});
jest.mock('../../common/components/visualization_actions');

const casesService = {
ui: { getCasesContext: () => mockCasesContext },
Expand Down
5 changes: 4 additions & 1 deletion x-pack/plugins/security_solution/public/app/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,8 @@ export const renderApp = ({
</SecurityApp>,
element
);
return () => unmountComponentAtNode(element);
return () => {
services.data.search.session.clear();
unmountComponentAtNode(element);
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ export interface DonutChartProps {
data: DonutChartData[] | null | undefined;
fillColor: FillColor;
height?: number;
isChartEmbeddablesEnabled?: boolean;
label: React.ReactElement | string;
legendItems?: LegendItem[] | null | undefined;
onElementClick?: ElementClickListener;
Expand All @@ -67,10 +66,10 @@ export interface DonutChartWrapperProps {
/* Make this position absolute in order to overlap the text onto the donut */
export const DonutTextWrapper = styled(EuiFlexGroup)<
EuiFlexGroupProps & {
$isChartEmbeddablesEnabled?: boolean;
$dataExists?: boolean;
$donutTextWrapperStyles?: FlattenSimpleInterpolation;
$isChartEmbeddablesEnabled?: boolean;
className?: string;
donutTextWrapperStyles?: FlattenSimpleInterpolation;
}
>`
top: ${({ $isChartEmbeddablesEnabled, $dataExists }) =>
Expand All @@ -80,8 +79,8 @@ export const DonutTextWrapper = styled(EuiFlexGroup)<
position: absolute;
z-index: 1;
${({ className, donutTextWrapperStyles }) =>
className && donutTextWrapperStyles ? `&.${className} {${donutTextWrapperStyles}}` : ''}
${({ className, $donutTextWrapperStyles }) =>
className && $donutTextWrapperStyles ? `&.${className} {${$donutTextWrapperStyles}}` : ''}
`;

export const StyledEuiFlexItem = styled(EuiFlexItem)`
Expand Down Expand Up @@ -117,11 +116,11 @@ const DonutChartWrapperComponent: React.FC<DonutChartWrapperProps> = ({
<StyledEuiFlexItem grow={isChartEmbeddablesEnabled}>
<DonutTextWrapper
$dataExists={dataExists}
$donutTextWrapperStyles={donutTextWrapperStyles}
$isChartEmbeddablesEnabled={isChartEmbeddablesEnabled}
alignItems="center"
className={donutTextWrapperClassName}
direction="column"
donutTextWrapperStyles={donutTextWrapperStyles}
gutterSize="none"
justifyContent="center"
>
Expand Down Expand Up @@ -151,7 +150,6 @@ export const DonutChart = ({
data,
fillColor,
height = 90,
isChartEmbeddablesEnabled,
label,
legendItems,
onElementClick,
Expand All @@ -165,7 +163,7 @@ export const DonutChart = ({
dataExists={data != null && data.length > 0}
label={label}
title={title}
isChartEmbeddablesEnabled={isChartEmbeddablesEnabled}
isChartEmbeddablesEnabled={false}
>
<>
{data == null || totalCount == null || totalCount === 0 ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ jest.mock('../../lib/kibana', () => {
};
});

jest.mock('../visualization_actions');
jest.mock('../visualization_actions/lens_embeddable');

jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useHistory: () => mockHistory,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* 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 type { BarChartComponentProps } from '../charts/barchart';
import { BarChart } from '../charts/barchart';
import { MatrixLoader } from './matrix_loader';

const MatrixHistogramChartContentComponent = ({
isInitialLoading,
barChart,
configs,
stackByField,
scopeId,
}: BarChartComponentProps & { isInitialLoading: boolean }) => {
return isInitialLoading ? (
<MatrixLoader />
) : (
<BarChart barChart={barChart} configs={configs} stackByField={stackByField} scopeId={scopeId} />
);
};

export const MatrixHistogramChartContent = React.memo(MatrixHistogramChartContentComponent);

MatrixHistogramChartContentComponent.displayName = 'MatrixHistogramChartContentComponent';
Original file line number Diff line number Diff line change
Expand Up @@ -176,22 +176,7 @@ describe('Matrix Histogram Component', () => {
});

describe('Inspect button', () => {
test("it doesn't render Inspect button by default on Host page", () => {
mockLocation.mockReturnValue({ pathname: '/hosts' });

const testProps = {
...mockMatrixOverTimeHistogramProps,
lensAttributes: dnsTopDomainsLensAttributes,
};
wrapper = mount(<MatrixHistogram {...testProps} />, {
wrappingComponent: TestProviders,
});
expect(wrapper.find('[data-test-subj="inspect-icon-button"]').exists()).toBe(false);
});

test("it doesn't render Inspect button by default on Network page", () => {
mockLocation.mockReturnValue({ pathname: '/network' });

test("it doesn't render Inspect button by default", () => {
const testProps = {
...mockMatrixOverTimeHistogramProps,
lensAttributes: dnsTopDomainsLensAttributes,
Expand All @@ -201,41 +186,10 @@ describe('Matrix Histogram Component', () => {
});
expect(wrapper.find('[data-test-subj="inspect-icon-button"]').exists()).toBe(false);
});

test('it render Inspect button by default on other pages', () => {
mockLocation.mockReturnValue({ pathname: '/overview' });

const testProps = {
...mockMatrixOverTimeHistogramProps,
lensAttributes: dnsTopDomainsLensAttributes,
};
wrapper = mount(<MatrixHistogram {...testProps} />, {
wrappingComponent: TestProviders,
});
expect(wrapper.find('[data-test-subj="inspect-icon-button"]').exists()).toBe(true);
});
});

describe('VisualizationActions', () => {
test('it renders VisualizationActions on Host page if lensAttributes is provided', () => {
mockLocation.mockReturnValue({ pathname: '/hosts' });

const testProps = {
...mockMatrixOverTimeHistogramProps,
lensAttributes: dnsTopDomainsLensAttributes,
};
wrapper = mount(<MatrixHistogram {...testProps} />, {
wrappingComponent: TestProviders,
});
expect(wrapper.find('[data-test-subj="mock-viz-actions"]').exists()).toBe(true);
expect(wrapper.find('[data-test-subj="mock-viz-actions"]').prop('className')).toEqual(
'histogram-viz-actions'
);
});

test('it renders VisualizationActions on Network page if lensAttributes is provided', () => {
mockLocation.mockReturnValue({ pathname: '/network' });

test('it renders VisualizationActions if lensAttributes is provided', () => {
const testProps = {
...mockMatrixOverTimeHistogramProps,
lensAttributes: dnsTopDomainsLensAttributes,
Expand All @@ -248,20 +202,6 @@ describe('Matrix Histogram Component', () => {
'histogram-viz-actions'
);
});

test("it doesn't renders VisualizationActions except Host / Network pages", () => {
const testProps = {
...mockMatrixOverTimeHistogramProps,
lensAttributes: dnsTopDomainsLensAttributes,
};

mockLocation.mockReturnValue({ pathname: '/overview' });

wrapper = mount(<MatrixHistogram {...testProps} />, {
wrappingComponent: TestProviders,
});
expect(wrapper.find('[data-test-subj="mock-viz-actions"]').exists()).toBe(false);
});
});

describe('toggle query', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,8 @@ import styled from 'styled-components';

import { EuiFlexGroup, EuiFlexItem, EuiProgress, EuiSelect, EuiSpacer } from '@elastic/eui';
import { useDispatch } from 'react-redux';
import { useLocation } from 'react-router-dom';
import * as i18n from './translations';
import { BarChart } from '../charts/barchart';
import { HeaderSection } from '../header_section';
import { MatrixLoader } from './matrix_loader';
import { Panel } from '../panel';
import { getBarchartConfigs, getCustomChartData } from './utils';
import { useMatrixHistogramCombined } from '../../containers/matrix_histogram';
Expand All @@ -35,8 +32,10 @@ import { HoverVisibilityContainer } from '../hover_visibility_container';
import { VisualizationActions } from '../visualization_actions';
import type { GetLensAttributes, LensAttributes } from '../visualization_actions/types';
import { useQueryToggle } from '../../containers/query_toggle';
import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features';
import { VISUALIZATION_ACTIONS_BUTTON_CLASS } from '../visualization_actions/utils';
import { isExplorePage } from '../../../helpers';
import { VisualizationEmbeddable } from '../visualization_actions/visualization_embeddable';
import { MatrixHistogramChartContent } from './chart_content';

export type MatrixHistogramComponentProps = MatrixHistogramProps &
Omit<MatrixHistogramQueryProps, 'stackByField'> & {
Expand Down Expand Up @@ -71,6 +70,8 @@ const HistogramPanel = styled(Panel)<{ height?: number }>`
${({ height }) => (height != null ? `min-height: ${height}px;` : '')}
`;

const CHART_HEIGHT = '150px';

export const MatrixHistogramComponent: React.FC<MatrixHistogramComponentProps> = ({
chartHeight,
defaultStackByOption,
Expand Down Expand Up @@ -107,7 +108,6 @@ export const MatrixHistogramComponent: React.FC<MatrixHistogramComponentProps> =
hideQueryToggle = false,
}) => {
const dispatch = useDispatch();
const { pathname } = useLocation();

const handleBrushEnd = useCallback(
({ x }) => {
Expand Down Expand Up @@ -169,6 +169,8 @@ export const MatrixHistogramComponent: React.FC<MatrixHistogramComponentProps> =
[setQuerySkip, setToggleStatus]
);

const isChartEmbeddablesEnabled = useIsExperimentalFeatureEnabled('chartEmbeddablesEnabled');

const matrixHistogramRequest = {
endDate,
errorMessage,
Expand All @@ -180,11 +182,10 @@ export const MatrixHistogramComponent: React.FC<MatrixHistogramComponentProps> =
stackByField: selectedStackByOption.value,
runtimeMappings,
isPtrIncluded,
skip: querySkip,
skip: querySkip || isChartEmbeddablesEnabled,
};
const [loading, { data, inspect, totalCount, refetch }] =
useMatrixHistogramCombined(matrixHistogramRequest);
const onExplorePage = isExplorePage(pathname);

const titleWithStackByField = useMemo(
() => (title != null && typeof title === 'function' ? title(selectedStackByOption) : title),
Expand All @@ -209,22 +210,28 @@ export const MatrixHistogramComponent: React.FC<MatrixHistogramComponentProps> =

useEffect(() => {
if (!loading && !isInitialLoading) {
setQuery({ id, inspect, loading, refetch });
setQuery({
id,
inspect,
loading,
refetch,
});
}

if (isInitialLoading && !!barChartData && data) {
setIsInitialLoading(false);
}
}, [
setQuery,
barChartData,
data,
id,
inspect,
isChartEmbeddablesEnabled,
isInitialLoading,
loading,
refetch,
isInitialLoading,
barChartData,
data,
setIsInitialLoading,
setQuery,
]);

const timerange = useMemo(() => ({ from: startDate, to: endDate }), [startDate, endDate]);
Expand Down Expand Up @@ -261,11 +268,11 @@ export const MatrixHistogramComponent: React.FC<MatrixHistogramComponentProps> =
toggleQuery={hideQueryToggle ? undefined : toggleQuery}
subtitle={subtitleWithCounts}
inspectMultiple
showInspectButton={showInspectButton || !onExplorePage}
showInspectButton={showInspectButton && !isChartEmbeddablesEnabled}
isInspectDisabled={filterQuery === undefined}
>
<EuiFlexGroup alignItems="center" gutterSize="none">
{onExplorePage && (getLensAttributes || lensAttributes) && timerange && (
{(getLensAttributes || lensAttributes) && timerange && (
<EuiFlexItem grow={false}>
<VisualizationActions
className="histogram-viz-actions"
Expand Down Expand Up @@ -293,10 +300,20 @@ export const MatrixHistogramComponent: React.FC<MatrixHistogramComponentProps> =
</EuiFlexGroup>
</HeaderSection>
{toggleStatus ? (
isInitialLoading ? (
<MatrixLoader />
isChartEmbeddablesEnabled ? (
<VisualizationEmbeddable
data-test-subj="embeddable-matrix-histogram"
getLensAttributes={getLensAttributes}
height={CHART_HEIGHT}
id={`${id}-embeddable`}
inspectTitle={title as string}
lensAttributes={lensAttributes}
stackByField={selectedStackByOption.value}
timerange={timerange}
/>
) : (
<BarChart
<MatrixHistogramChartContent
isInitialLoading={isInitialLoading}
barChart={barChartData}
configs={barchartConfigs}
stackByField={selectedStackByOption.value}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* 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.
*/

export const useRefetchByRestartingSession = jest.fn().mockReturnValue({
session: {
current: {
start: jest
.fn()
.mockReturnValueOnce('mockSearchSessionId')
.mockReturnValue('mockSearchSessionIdDefault'),
},
},
refetchByRestartingSession: jest.fn(),
});
Loading

0 comments on commit 9cfec58

Please sign in to comment.