-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Security Solution] Alerts visualization free field selection #120610
Changes from 11 commits
5f746d6
01a25ab
2664701
204fd69
7b0b609
c2bc60c
7fbba7c
e3dd0f3
51c260a
b9e89b6
7dc7636
285fa10
7167204
f004914
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -41,7 +41,7 @@ import { LinkButton } from '../../../../common/components/links'; | |
import { SecurityPageName } from '../../../../app/types'; | ||
import { DEFAULT_STACK_BY_FIELD, PANEL_HEIGHT } from '../common/config'; | ||
import type { AlertsStackByField } from '../common/types'; | ||
import { KpiPanel, StackBySelect } from '../common/components'; | ||
import { KpiPanel, StackByComboBox } from '../common/components'; | ||
|
||
import { useInspectButton } from '../common/hooks'; | ||
|
||
|
@@ -109,7 +109,7 @@ export const AlertsHistogramPanel = memo<AlertsHistogramPanelProps>( | |
const [isInspectDisabled, setIsInspectDisabled] = useState(false); | ||
const [defaultNumberFormat] = useUiSetting$<string>(DEFAULT_NUMBER_FORMAT); | ||
const [totalAlertsObj, setTotalAlertsObj] = useState<AlertsTotal>(defaultTotalAlertsObj); | ||
const [selectedStackByOption, setSelectedStackByOption] = useState<AlertsStackByField>( | ||
const [selectedStackByOption, setSelectedStackByOption] = useState<string>( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can drop the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💅 |
||
onlyField == null ? defaultStackByOption : onlyField | ||
); | ||
|
||
|
@@ -276,10 +276,12 @@ export const AlertsHistogramPanel = memo<AlertsHistogramPanelProps>( | |
<EuiFlexGroup alignItems="center" gutterSize="none"> | ||
<EuiFlexItem grow={false}> | ||
{showStackBy && ( | ||
<StackBySelect | ||
selected={selectedStackByOption} | ||
onSelect={setSelectedStackByOption} | ||
/> | ||
<> | ||
<StackByComboBox | ||
selected={selectedStackByOption} | ||
onSelect={setSelectedStackByOption} | ||
/> | ||
</> | ||
)} | ||
{headerChildren != null && headerChildren} | ||
</EuiFlexItem> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,11 +5,11 @@ | |
* 2.0. | ||
*/ | ||
|
||
import { EuiPanel, EuiSelect } from '@elastic/eui'; | ||
import { EuiPanel, EuiComboBox } from '@elastic/eui'; | ||
import styled from 'styled-components'; | ||
import React, { useCallback } from 'react'; | ||
import { PANEL_HEIGHT, MOBILE_PANEL_HEIGHT, alertsStackByOptions } from './config'; | ||
import type { AlertsStackByField } from './types'; | ||
import React, { useCallback, useMemo } from 'react'; | ||
import { PANEL_HEIGHT, MOBILE_PANEL_HEIGHT } from './config'; | ||
import { useStackByFields } from './hooks'; | ||
import * as i18n from './translations'; | ||
|
||
export const KpiPanel = styled(EuiPanel)<{ height?: number }>` | ||
|
@@ -25,24 +25,45 @@ export const KpiPanel = styled(EuiPanel)<{ height?: number }>` | |
} | ||
`; | ||
interface StackedBySelectProps { | ||
selected: AlertsStackByField; | ||
onSelect: (selected: AlertsStackByField) => void; | ||
selected: string; | ||
onSelect: (selected: string) => void; | ||
} | ||
|
||
export const StackBySelect: React.FC<StackedBySelectProps> = ({ selected, onSelect }) => { | ||
const setSelectedOptionCallback = useCallback( | ||
(event: React.ChangeEvent<HTMLSelectElement>) => { | ||
onSelect(event.target.value as AlertsStackByField); | ||
export const StackByComboBoxWrapper = styled.div` | ||
width: 400px; | ||
`; | ||
|
||
export const StackByComboBox: React.FC<StackedBySelectProps> = ({ selected, onSelect }) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. More a question: I saw that a lot of other modules use |
||
const onChange = useCallback( | ||
(options) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no, it's defined by eui here https://github.com/elastic/eui/blob/main/src/components/combo_box/combo_box.tsx#L102 |
||
if (options && options.length > 0) { | ||
onSelect(options[0].value); | ||
} else { | ||
onSelect(''); | ||
} | ||
}, | ||
[onSelect] | ||
); | ||
|
||
const selectedOptions = useMemo(() => { | ||
return [{ label: selected, value: selected }]; | ||
}, [selected]); | ||
const stackOptions = useStackByFields(); | ||
const singleSelection = useMemo(() => { | ||
return { asPlainText: true }; | ||
}, []); | ||
return ( | ||
<EuiSelect | ||
onChange={setSelectedOptionCallback} | ||
options={alertsStackByOptions} | ||
prepend={i18n.STACK_BY_LABEL} | ||
value={selected} | ||
/> | ||
<StackByComboBoxWrapper> | ||
<EuiComboBox | ||
aria-label={i18n.STACK_BY_ARIA_LABEL} | ||
placeholder={i18n.STACK_BY_PLACEHOLDER} | ||
prepend={i18n.STACK_BY_LABEL} | ||
singleSelection={singleSelection} | ||
sortMatchesBy="startsWith" | ||
options={stackOptions} | ||
selectedOptions={selectedOptions} | ||
compressed | ||
onChange={onChange} | ||
/> | ||
</StackByComboBoxWrapper> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,8 +5,13 @@ | |
* 2.0. | ||
*/ | ||
|
||
import { useEffect } from 'react'; | ||
import { useEffect, useState, useMemo } from 'react'; | ||
import { useLocation } from 'react-router-dom'; | ||
import type { EuiComboBoxOptionOption } from '@elastic/eui'; | ||
import type { BrowserField } from '../../../../../../timelines/common'; | ||
import type { GlobalTimeArgs } from '../../../../common/containers/use_global_time'; | ||
import { getScopeFromPath, useSourcererDataView } from '../../../../common/containers/sourcerer'; | ||
import { getAllFieldsByName } from '../../../../common/containers/source'; | ||
|
||
export interface UseInspectButtonParams extends Pick<GlobalTimeArgs, 'setQuery' | 'deleteQuery'> { | ||
response: string; | ||
|
@@ -15,6 +20,7 @@ export interface UseInspectButtonParams extends Pick<GlobalTimeArgs, 'setQuery' | |
uniqueQueryId: string; | ||
loading: boolean; | ||
} | ||
|
||
/** | ||
* * Add query to inspect button utility. | ||
* * Delete query from inspect button utility when component unmounts | ||
|
@@ -48,3 +54,28 @@ export const useInspectButton = ({ | |
}; | ||
}, [setQuery, loading, response, request, refetch, uniqueQueryId, deleteQuery]); | ||
}; | ||
|
||
function fieldsToOptions(fields: { [fieldName: string]: Partial<BrowserField> }) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would rename this to |
||
return Object.entries(fields).reduce<EuiComboBoxOptionOption[]>( | ||
(filteredOptions: EuiComboBoxOptionOption[], [key, field]) => { | ||
if (field.aggregatable === true) { | ||
return [...filteredOptions, { label: key, value: key }]; | ||
} else { | ||
return filteredOptions; | ||
} | ||
}, | ||
[] | ||
); | ||
} | ||
|
||
export const useStackByFields = () => { | ||
const { pathname } = useLocation(); | ||
|
||
const { browserFields } = useSourcererDataView(getScopeFromPath(pathname)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes I believe it can be changed by a form control present on most pages that allows a user to change the index patter...data views that are being used to generate the data on a page, this hook will be re-evaluated then. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks :) |
||
const allFields = useMemo(() => getAllFieldsByName(browserFields), [browserFields]); | ||
const [stackByFieldOptions, setStackByFieldOptions] = useState(() => fieldsToOptions(allFields)); | ||
useEffect(() => { | ||
setStackByFieldOptions(fieldsToOptions(allFields)); | ||
}, [allFields]); | ||
return useMemo(() => stackByFieldOptions, [stackByFieldOptions]); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have an ECS fields type?Ignore, this can accept any aggregate-able field.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe we do, but this field can be any arbitrary field present in the documents, so I think string is as specific as we can be. The types used to derive this prop support that as well.