Skip to content

Commit

Permalink
[SecuritySolution] Fix bucket names in legend (#156764)
Browse files Browse the repository at this point in the history
## Summary

issue: #156636

This PR is to fix the bucket name in the legend.
When Stack by `@timestamp`, the bucket names in the legend were not
formatted.
In this PR, it parses it with users' `local` timezone and formate:

<img width="2549" alt="Screenshot 2023-05-05 at 00 01 16"
src="https://user-images.githubusercontent.com/6295984/236348010-6ebfae4f-6f0e-4e33-9f06-ebc887adb108.png">





https://user-images.githubusercontent.com/6295984/236355789-3438ad32-fb4f-4270-8b4e-11c56b1b5d02.mov

---------

Co-authored-by: kibanamachine <[email protected]>
Co-authored-by: PhilippeOberti <[email protected]>
  • Loading branch information
3 people authored May 5, 2023
1 parent 14f5b9e commit f2a268c
Show file tree
Hide file tree
Showing 18 changed files with 193 additions and 51 deletions.

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 @@ -55,7 +55,7 @@ const mockDateRange: TimeRangeBounds = {
max: moment(validDate).add(numberOfDays, 'days'),
};

const mockField: string = 'threat.indicator.ip';
const mockField = { label: 'threat.indicator.ip', value: 'ip' };

export default {
component: IndicatorsBarChart,
Expand All @@ -70,7 +70,7 @@ export const Default: Story<void> = () => (

export const NoData: Story<void> = () => (
<StoryProvidersComponent kibana={{ timelines: mockKibanaTimelinesService }}>
<IndicatorsBarChart indicators={[]} field={''} dateRange={mockDateRange} />
<IndicatorsBarChart indicators={[]} field={mockField} dateRange={mockDateRange} />
</StoryProvidersComponent>
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { TimeRangeBounds } from '@kbn/data-plugin/common';
import { TestProvidersComponent } from '../../../../../common/mocks/test_providers';
import { IndicatorsBarChart } from '.';
import { ChartSeries } from '../../../services';
import { EuiComboBoxOptionOption } from '@elastic/eui';

moment.suppressDeprecationWarnings = true;
moment.tz.setDefault('UTC');
Expand Down Expand Up @@ -45,7 +46,10 @@ describe('<IndicatorsBarChart />', () => {
min: moment(validDate),
max: moment(validDate).add(1, 'days'),
};
const mockField: string = 'threat.indicator.ip';
const mockField: EuiComboBoxOptionOption<string> = {
label: 'threat.indicator.ip',
value: 'ip',
};

const component = render(
<TestProvidersComponent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import React, { VFC } from 'react';
import { Axis, BarSeries, Chart, Position, ScaleType, Settings } from '@elastic/charts';
import { EuiThemeProvider } from '@elastic/eui';
import { EuiComboBoxOptionOption, EuiThemeProvider } from '@elastic/eui';
import { TimeRangeBounds } from '@kbn/data-plugin/common';
import { IndicatorBarchartLegendAction } from '../legend_action';
import { barChartTimeAxisLabelFormatter } from '../../../../../common/utils/dates';
Expand All @@ -30,7 +30,7 @@ export interface IndicatorsBarChartProps {
/**
* Indicator field selected in the IndicatorFieldSelector component, passed to AddToTimeline to populate the timeline.
*/
field: string;
field: EuiComboBoxOptionOption<string>;
/**
* Option height value to override the default {@link DEFAULT_CHART_HEIGHT} default barchart height.
*/
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 @@ -8,6 +8,7 @@
import React from 'react';
import { Story } from '@storybook/react';
import { DataView, DataViewField } from '@kbn/data-views-plugin/common';
import { EuiComboBoxOptionOption } from '@elastic/eui';
import { RawIndicatorFieldId } from '../../../../../../common/types/indicator';
import { IndicatorsFieldSelector } from '.';

Expand All @@ -33,7 +34,9 @@ export const Default: Story<void> = () => {
return (
<IndicatorsFieldSelector
indexPattern={mockIndexPattern}
valueChange={(value: string) => window.alert(`${value} selected`)}
valueChange={({ label }: EuiComboBoxOptionOption<string>) =>
window.alert(`${label} selected`)
}
/>
);
};
Expand All @@ -42,7 +45,9 @@ export const WithDefaultValue: Story<void> = () => {
return (
<IndicatorsFieldSelector
indexPattern={mockIndexPattern}
valueChange={(value: string) => window.alert(`${value} selected`)}
valueChange={({ label }: EuiComboBoxOptionOption<string>) =>
window.alert(`${label} selected`)
}
defaultStackByValue={RawIndicatorFieldId.LastSeen}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { render } from '@testing-library/react';
import { DataView, DataViewField } from '@kbn/data-views-plugin/common';
import { TestProvidersComponent } from '../../../../../common/mocks/test_providers';
import { IndicatorsFieldSelector } from '.';
import { EuiComboBoxOptionOption } from '@elastic/eui';

const mockIndexPattern: DataView = {
fields: [
Expand All @@ -31,7 +32,7 @@ describe('<IndicatorsFieldSelector />', () => {
<IndicatorsFieldSelector
indexPattern={{ fields: [] } as any}
// eslint-disable-next-line no-console
valueChange={(value: string) => console.log(value)}
valueChange={(value: EuiComboBoxOptionOption<string>) => console.log(value)}
/>
</TestProvidersComponent>
);
Expand All @@ -45,7 +46,7 @@ describe('<IndicatorsFieldSelector />', () => {
<IndicatorsFieldSelector
indexPattern={mockIndexPattern}
// eslint-disable-next-line no-console
valueChange={(value: string) => console.log(value)}
valueChange={(value: EuiComboBoxOptionOption<string>) => console.log(value)}
/>
</TestProvidersComponent>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { COMBOBOX_PREPEND_LABEL } from './translations';

export interface IndicatorsFieldSelectorProps {
indexPattern: SecuritySolutionDataViewBase;
valueChange: (value: string) => void;
valueChange: (value: EuiComboBoxOptionOption<string>) => void;
defaultStackByValue?: RawIndicatorFieldId;
}

Expand All @@ -27,18 +27,21 @@ const COMBOBOX_SINGLE_SELECTION = { asPlainText: true };
export const IndicatorsFieldSelector = memo<IndicatorsFieldSelectorProps>(
({ indexPattern, valueChange, defaultStackByValue = DEFAULT_STACK_BY_VALUE }) => {
const styles = useStyles();

const defaultStackByValueInfo = indexPattern.fields.find(
(f: DataViewField) => f.name === defaultStackByValue
);
const [selectedField, setSelectedField] = useState<Array<EuiComboBoxOptionOption<string>>>([
{
label: defaultStackByValue,
value: defaultStackByValueInfo?.type,
},
]);

const fields: Array<EuiComboBoxOptionOption<string>> = useMemo(
() =>
indexPattern
? indexPattern.fields.map((f: DataViewField) => ({
label: f.name,
value: f.type,
}))
: [],
[indexPattern]
Expand All @@ -47,7 +50,7 @@ export const IndicatorsFieldSelector = memo<IndicatorsFieldSelectorProps>(
const selectedFieldChange = useCallback(
(values: Array<EuiComboBoxOptionOption<string>>) => {
if (values && values.length > 0) {
valueChange(values[0].label);
valueChange(values[0]);
}
setSelectedField(values);
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* 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 { render } from '@testing-library/react';
import moment from 'moment';
import React from 'react';
import { IndicatorBarchartLegendAction } from './legend_action';

jest.mock('moment');

describe('IndicatorBarchartLegendAction', () => {
const mockDate = '14182940000';

const mockToIsoString = jest.fn();
beforeAll(() => {
(moment as unknown as jest.Mock).mockReturnValue({
toISOString: mockToIsoString,
});
});

beforeEach(() => {
jest.clearAllMocks();
});
it('should formate group name if it is a date type', () => {
const mockField = {
label: '@timestamp',
value: 'date',
};
render(<IndicatorBarchartLegendAction data={mockDate} field={mockField} />);
expect(mockToIsoString).toHaveBeenCalled();
});

it('should render group name without formation', () => {
const mockField = {
label: 'host.name',
value: 'string',
};
render(<IndicatorBarchartLegendAction data={mockDate} field={mockField} />);
expect(mockToIsoString).not.toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@
*/

import React, { useState, VFC } from 'react';
import { EuiButtonIcon, EuiContextMenuPanel, EuiPopover, EuiToolTip } from '@elastic/eui';
import {
EuiButtonIcon,
EuiComboBoxOptionOption,
EuiContextMenuPanel,
EuiPopover,
EuiToolTip,
} from '@elastic/eui';
import moment from 'moment';
import { CopyToClipboardContextMenu } from '../../copy_to_clipboard';
import { FilterInContextMenu, FilterOutContextMenu } from '../../../../query_bar';
import { AddToTimelineContextMenu } from '../../../../timeline';
Expand All @@ -27,7 +34,7 @@ export interface IndicatorBarchartLegendActionProps {
/**
* Indicator field selected in the IndicatorFieldSelector component, passed to the {@link AddToTimelineContextMenu} to populate the timeline.
*/
field: string;
field: EuiComboBoxOptionOption<string>;
}

export const IndicatorBarchartLegendAction: VFC<IndicatorBarchartLegendActionProps> = ({
Expand All @@ -36,11 +43,31 @@ export const IndicatorBarchartLegendAction: VFC<IndicatorBarchartLegendActionPro
}) => {
const [isPopoverOpen, setPopover] = useState(false);

const group = field.value === 'date' ? moment(data).toISOString() : data;
const popoverItems = [
<FilterInContextMenu data={data} field={field} data-test-subj={FILTER_IN_BUTTON_TEST_ID} />,
<FilterOutContextMenu data={data} field={field} data-test-subj={FILTER_OUT_BUTTON_TEST_ID} />,
<AddToTimelineContextMenu data={data} field={field} data-test-subj={TIMELINE_BUTTON_TEST_ID} />,
<CopyToClipboardContextMenu value={data} data-test-subj={COPY_TO_CLIPBOARD_BUTTON_TEST_ID} />,
<FilterInContextMenu
key={FILTER_IN_BUTTON_TEST_ID}
data={group}
field={field.label}
data-test-subj={FILTER_IN_BUTTON_TEST_ID}
/>,
<FilterOutContextMenu
key={FILTER_OUT_BUTTON_TEST_ID}
data={group}
field={field.label}
data-test-subj={FILTER_OUT_BUTTON_TEST_ID}
/>,
<AddToTimelineContextMenu
key={TIMELINE_BUTTON_TEST_ID}
data={group}
field={field.label}
data-test-subj={TIMELINE_BUTTON_TEST_ID}
/>,
<CopyToClipboardContextMenu
key={COPY_TO_CLIPBOARD_BUTTON_TEST_ID}
value={group}
data-test-subj={COPY_TO_CLIPBOARD_BUTTON_TEST_ID}
/>,
];

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { DataView, DataViewField } from '@kbn/data-views-plugin/common';
import { TimeRange } from '@kbn/es-query';
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { IUiSettingsClient } from '@kbn/core/public';
import { EuiComboBoxOptionOption } from '@elastic/eui';
import { BARCHART_AGGREGATION_NAME } from '../../../../../common/constants';
import { StoryProvidersComponent } from '../../../../common/mocks/story_providers';
import { mockKibanaTimelinesService } from '../../../../common/mocks/mock_kibana_timelines_service';
Expand Down Expand Up @@ -115,6 +116,12 @@ const uiSettingsMock = {

const timelinesMock = mockKibanaTimelinesService;

const mockField = { label: 'threat.indicator.ip', value: 'ip' };

const mockOnFieldChange = function (value: EuiComboBoxOptionOption<string>): void {
window.alert(value.label);
};

export const Default: Story<void> = () => {
return (
<StoryProvidersComponent
Expand All @@ -125,10 +132,8 @@ export const Default: Story<void> = () => {
timeRange={mockTimeRange}
indexPattern={mockIndexPattern}
series={[]}
field={''}
onFieldChange={function (value: string): void {
throw new Error('Function not implemented.');
}}
field={mockField}
onFieldChange={mockOnFieldChange}
/>
</StoryProvidersComponent>
);
Expand All @@ -148,10 +153,8 @@ export const InitialLoad: Story<void> = () => {
series={[]}
isLoading={true}
isFetching={false}
field={''}
onFieldChange={function (value: string): void {
throw new Error('Function not implemented.');
}}
field={mockField}
onFieldChange={mockOnFieldChange}
/>
</StoryProvidersComponent>
);
Expand Down Expand Up @@ -194,10 +197,8 @@ export const UpdatingData: Story<void> = () => {
series={mockIndicators}
isLoading={false}
isFetching={true}
field={''}
onFieldChange={function (value: string): void {
throw new Error('Function not implemented.');
}}
field={mockField}
onFieldChange={mockOnFieldChange}
/>
</StoryProvidersComponent>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const mockIndexPattern: DataView = {
} as DataView;

const mockTimeRange: TimeRange = { from: '', to: '' };
const mockField = { label: 'host.name', value: 'string' };

describe('<IndicatorsBarChartWrapper />', () => {
describe('when not loading or refetching', () => {
Expand All @@ -38,7 +39,7 @@ describe('<IndicatorsBarChartWrapper />', () => {
<IndicatorsBarChartWrapper
dateRange={{ max: moment(), min: moment() }}
series={[]}
field=""
field={mockField}
onFieldChange={jest.fn()}
indexPattern={mockIndexPattern}
timeRange={mockTimeRange}
Expand All @@ -59,7 +60,7 @@ describe('<IndicatorsBarChartWrapper />', () => {
<IndicatorsBarChartWrapper
dateRange={{ max: moment(), min: moment() }}
series={[]}
field=""
field={mockField}
onFieldChange={jest.fn()}
indexPattern={mockIndexPattern}
timeRange={mockTimeRange}
Expand All @@ -80,7 +81,7 @@ describe('<IndicatorsBarChartWrapper />', () => {
<IndicatorsBarChartWrapper
dateRange={{ max: moment(), min: moment() }}
series={[]}
field=""
field={mockField}
onFieldChange={jest.fn()}
indexPattern={mockIndexPattern}
timeRange={mockTimeRange}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import React, { memo } from 'react';
import {
EuiComboBoxOptionOption,
EuiFlexGroup,
EuiFlexItem,
EuiLoadingSpinner,
Expand Down Expand Up @@ -41,9 +42,9 @@ export interface IndicatorsBarChartWrapperProps {

dateRange: TimeRangeBounds;

field: string;
field: EuiComboBoxOptionOption<string>;

onFieldChange: (value: string) => void;
onFieldChange: (value: EuiComboBoxOptionOption<string>) => void;

/** Is initial load in progress? */
isLoading?: boolean;
Expand Down
Loading

0 comments on commit f2a268c

Please sign in to comment.