Skip to content
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

Merged

Conversation

kqualters-elastic
Copy link
Contributor

@kqualters-elastic kqualters-elastic commented Dec 7, 2021

Summary

This pr changes the controls on the alerts aggregation visualization to be an EuiComboBox where any aggregatable field can be used for showing aggregate statistics instead of just 10 static possible values in an EuiSelect.
combo_box_alerts
Updated styles: 400px fixed width to match max-width from eui, compressed=true
image

Checklist

Delete any items that are not applicable to this PR.

@kqualters-elastic kqualters-elastic added release_note:enhancement auto-backport Deprecated - use backport:version if exact versions are needed v8.1.0 Team:Threat Hunting:Investigations Security Solution Investigations Team labels Dec 7, 2021
@kqualters-elastic kqualters-elastic changed the title Visualization field selection [Security Solution] Alerts visualization free field selection Dec 7, 2021
@monina-n
Copy link

monina-n commented Dec 7, 2021

Hey Kevin! A few comments:

  • Using the EUIcombo box is good, can we use the compressed EUI combo box so it's more slim?
  • Can we use a static max-width for the combo box at all times? In the gif, there's a part where we select a long field name and it extends the width by around 30 px. Let's have it be the same width and truncate for longer fields instead of having it increase in size/decrease in size for every selection.
  • I'm not sure if this is a browser thing but the scroll bar should be the default EUI one that comes with the component (see example below)

Question:

  • what happens when a user presses the clear button on the stack by combo box but doesn't select a different option in the list?

Example:
Screen Shot 2021-12-07 at 11 11 22 AM

@@ -40,7 +39,7 @@ export const AlertsCountPanel = memo<AlertsCountPanelProps>(
// create a unique, but stable (across re-renders) query id
const uniqueQueryId = useMemo(() => `${DETECTIONS_ALERTS_COUNT_ID}-${uuid.v4()}`, []);
const [selectedStackByOption, setSelectedStackByOption] =
useState<AlertsStackByField>(DEFAULT_STACK_BY_FIELD);
useState<string>(DEFAULT_STACK_BY_FIELD);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
useState<string>(DEFAULT_STACK_BY_FIELD);
useState(DEFAULT_STACK_BY_FIELD);

💅 TS will automatically assume it's a string

@@ -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>(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can drop the <string> here as well, I think.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💅

aria-label={i18n.STACK_BY_ARIA_LABEL}
placeholder={i18n.STACK_BY_PLACEHOLDER}
prepend={i18n.STACK_BY_LABEL}
singleSelection={{ asPlainText: true }}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactoring { asPlainText: true } into a constant outside the component or into a ref inside the component will prevent rerenders of the EuiComboBox.

min-width: 350px;
`;

export const StackByComboBox: React.FC<StackedBySelectProps> = ({ selected, onSelect }) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More a question: I saw that a lot of other modules use React.memo here instead. Is this something that we should do here as well wrt to performance?

>(undefined);
const { pathname } = useLocation();

const { browserFields } = useSourcererDataView(getScopeFromPath(pathname));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does useSourcererDataView() update during runtime sometimes or can we assume browserFields is static at this point?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks :)

export const useStackByFields = () => {
const [stackByFieldOptions, setStackByFieldOptions] = useState<
undefined | EuiComboBoxOptionOption[]
>(undefined);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking about a way to get rid of undefined here. If this piece of state is initialized later in the function, we can eliminate it, I think. Something similar like this:

function fieldsToOptions(fields) {
  return Object.entries(allFields).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));

  const allFields = useMemo(() => getAllFieldsByName(browserFields), [browserFields]);

  const [stackByFieldOptions, setStackByFieldOptions] = useState(() => fieldsToOptions(allFields));

  useEffect(() => {
    setStackByFieldOptions(fieldsToOptions(allfields));
  }, [allFields]);

  return useMemo(() => stackByFieldOptions, [stackByFieldOptions]);
};

In case allFields does not change, we can even remove the useEffect in there. Wdyt?

(event: React.ChangeEvent<HTMLSelectElement>) => {
onSelect(event.target.value as AlertsStackByField);
export const StackByComboBoxWrapper = styled.div`
min-width: 350px;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉


export const getAlertsCountQuery = (
stackByField: AlertsStackByField,
stackByField: string,
Copy link
Contributor

@michaelolo24 michaelolo24 Dec 8, 2021

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.

Copy link
Contributor Author

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.


export const StackByComboBox: React.FC<StackedBySelectProps> = ({ selected, onSelect }) => {
const onChange = useCallback(
(options) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is options === selectedOption?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kqualters-elastic kqualters-elastic marked this pull request as ready for review December 13, 2021 19:04
@kqualters-elastic kqualters-elastic requested a review from a team as a code owner December 13, 2021 19:04
Copy link
Contributor

@janmonschke janmonschke left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

@kqualters-elastic
Copy link
Contributor Author

@monina-n updated the styles, please see the second screenshot. re: no value selected, both the table and the graph show the empty state in that case.

@@ -48,3 +54,28 @@ export const useInspectButton = ({
};
}, [setQuery, loading, response, request, refetch, uniqueQueryId, deleteQuery]);
};

function fieldsToOptions(fields: { [fieldName: string]: Partial<BrowserField> }) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rename this to getAggregatableFields

@kqualters-elastic kqualters-elastic enabled auto-merge (squash) December 15, 2021 20:11
@kqualters-elastic kqualters-elastic merged commit e768f1d into elastic:main Dec 15, 2021
@kibana-ci
Copy link
Collaborator

💛 Build succeeded, but was flaky

Test Failures

  • [job] [logs] Security Solution Tests / Row renderers Suricata Signature tooltips do not overlap

Metrics [docs]

Async chunks

Total size of all lazy-loaded chunks that will be downloaded as the user navigates the app

id before after diff
securitySolution 4.6MB 4.6MB +475.0B

History

To update your PR or re-run it, just comment with:
@elasticmachine merge upstream

cc @kqualters-elastic

@kibanamachine kibanamachine added the backport:skip This commit does not require backporting label Dec 15, 2021
@kibanamachine
Copy link
Contributor

💔 Backport failed

The backport operation could not be completed due to the following error:
There are no branches to backport to. Aborting.

The backport PRs will be merged automatically after passing CI.

To backport manually run:
node scripts/backport --pr 120610

TinLe pushed a commit to TinLe/kibana that referenced this pull request Dec 22, 2021
…c#120610)

* Use EuiComboBox in place of a select for alert page visuals

* Remove console.log

* Better variable name in reduce function

* Remove comment

* Remove typo

* Fix linting/tests

* PR feedback

* Add missing mocks

* Add router mocks

* Rename getAggregatableFields
@kqualters-elastic kqualters-elastic deleted the visualization-field-selection branch June 2, 2022 17:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
auto-backport Deprecated - use backport:version if exact versions are needed backport:skip This commit does not require backporting release_note:enhancement Team:Threat Hunting:Investigations Security Solution Investigations Team v8.1.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants