Skip to content

Commit

Permalink
Draft basic global alerts page
Browse files Browse the repository at this point in the history
  • Loading branch information
umbopepato committed Jan 22, 2024
1 parent 1ca128d commit 5ca182e
Show file tree
Hide file tree
Showing 20 changed files with 1,099 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { i18n } from '@kbn/i18n';
import type { Filter, Query, TimeRange } from '@kbn/es-query';
import type { DataView } from '@kbn/data-views-plugin/public';
import type { SavedQueryService, SavedQuery } from '@kbn/data-plugin/public';
import { QuickFiltersMenuItem } from './quick_filters';
import { QueryBarMenuPanels, QueryBarMenuPanelsProps } from './query_bar_menu_panels';
import { FilterEditorWrapper } from './filter_editor_wrapper';
import { popoverDragAndDropCss } from './add_filter_popover.styles';
Expand Down Expand Up @@ -58,6 +59,7 @@ export interface QueryBarMenuProps extends WithCloseFilterEditorConfirmModalProp
hiddenPanelOptions?: QueryBarMenuPanelsProps['hiddenPanelOptions'];
onFiltersUpdated?: (filters: Filter[]) => void;
filters?: Filter[];
quickFilters: QuickFiltersMenuItem[];
query?: Query;
savedQuery?: SavedQuery;
onClearSavedQuery?: () => void;
Expand Down Expand Up @@ -89,6 +91,7 @@ function QueryBarMenuComponent({
toggleFilterBarMenuPopover,
onFiltersUpdated,
filters,
quickFilters,
query,
savedQuery,
onClearSavedQuery,
Expand Down Expand Up @@ -150,6 +153,7 @@ function QueryBarMenuComponent({

const panels = QueryBarMenuPanels({
filters,
quickFilters,
savedQuery,
language,
dateRangeFrom,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* Side Public License, v 1.
*/

import React, { useState, useRef, useEffect, useCallback } from 'react';
import React, { useState, useRef, useEffect, useCallback, useMemo } from 'react';
import { isEqual } from 'lodash';
import {
EuiContextMenuPanelDescriptor,
Expand All @@ -24,6 +24,7 @@ import {
toggleFilterNegated,
pinFilter,
unpinFilter,
compareFilters,
} from '@kbn/es-query';
import { i18n } from '@kbn/i18n';
import { METRIC_TYPE } from '@kbn/analytics';
Expand All @@ -34,6 +35,8 @@ import {
UI_SETTINGS,
} from '@kbn/data-plugin/common';
import type { SavedQueryService, SavedQuery } from '@kbn/data-plugin/public';
import { EuiContextMenuPanelItemDescriptor } from '@elastic/eui/src/components/context_menu/context_menu';
import { isQuickFiltersGroup, QuickFiltersMenuItem } from './quick_filters';
import type { IUnifiedSearchPluginServices } from '../types';
import { fromUser } from './from_user';
import { QueryLanguageSwitcher } from './language_switcher';
Expand Down Expand Up @@ -134,10 +137,21 @@ export const strings = {
i18n.translate('unifiedSearch.filter.options.filterLanguageLabel', {
defaultMessage: 'Filter language',
}),
getQuickFiltersLabel: () =>
i18n.translate('unifiedSearch.filter.options.quickFiltersLabel', {
defaultMessage: 'Quick filters',
}),
};

type ContextMenuItem =
| EuiContextMenuPanelDescriptor
| (EuiContextMenuPanelItemDescriptor & {
width?: number;
});

export interface QueryBarMenuPanelsProps {
filters?: Filter[];
quickFilters: QuickFiltersMenuItem[];
savedQuery?: SavedQuery;
language: string;
dateRangeFrom?: string;
Expand All @@ -162,6 +176,7 @@ export interface QueryBarMenuPanelsProps {

export function QueryBarMenuPanels({
filters,
quickFilters,
savedQuery,
language,
dateRangeFrom,
Expand Down Expand Up @@ -192,6 +207,52 @@ export function QueryBarMenuPanels({
const [hasFiltersOrQuery, setHasFiltersOrQuery] = useState(false);
const [savedQueryHasChanged, setSavedQueryHasChanged] = useState(false);

const applyQuickFilter = useCallback(
(filter: Filter) => {
if (!filters?.some((f) => compareFilters(f, filter))) {
onFiltersUpdated?.([...(filters ?? []), filter]);
}
closePopover();
},
[closePopover, filters, onFiltersUpdated]
);

const quickFiltersContextMenuData = useMemo(() => {
let items = [] as EuiContextMenuPanelItemDescriptor[];
const panels = [] as EuiContextMenuPanelDescriptor[];
if (showFilterBar && quickFilters.length > 0) {
let panelsCount = 0;
const quickFiltersItemToContextMenuItem = (qf: QuickFiltersMenuItem) => {
if (isQuickFiltersGroup(qf)) {
const panelId = `quick_filter_group_${panelsCount++}`;
panels.push({
id: panelId,
title: qf.groupName,
items: qf.items.map(quickFiltersItemToContextMenuItem),
});
return {
name: qf.groupName,
icon: qf.icon ?? 'filterInCircle',
panel: panelId,
};
} else {
return {
...qf,
icon: qf.icon ?? 'filterInCircle',
onClick: () => {
applyQuickFilter(qf.filter);
},
};
}
};
items = quickFilters.map(quickFiltersItemToContextMenuItem);
}
return {
items,
panels,
};
}, [applyQuickFilter, quickFilters, showFilterBar]);

useEffect(() => {
const fetchSavedQueries = async () => {
cancelPendingListingRequest.current();
Expand Down Expand Up @@ -320,7 +381,7 @@ export function QueryBarMenuPanels({
const luceneLabel = strings.getLuceneLanguageName();
const kqlLabel = strings.getKqlLanguageName();

const filtersRelatedPanels = [
const filtersRelatedPanels: ContextMenuItem[] = [
{
name: strings.getOptionsAddFilterButtonLabel(),
icon: 'plus',
Expand All @@ -337,7 +398,7 @@ export function QueryBarMenuPanels({
},
];

const queryAndFiltersRelatedPanels = [
const queryAndFiltersRelatedPanels: ContextMenuItem[] = [
{
name: savedQuery
? strings.getLoadOtherFilterSetLabel()
Expand All @@ -359,7 +420,7 @@ export function QueryBarMenuPanels({
{ isSeparator: true },
];

const items = [];
const items: ContextMenuItem[] = [];
// apply to all actions are only shown when there are filters
if (showFilterBar) {
items.push(...filtersRelatedPanels);
Expand All @@ -385,6 +446,11 @@ export function QueryBarMenuPanels({
{ isSeparator: true }
);
}

if (showFilterBar && quickFilters.length > 0) {
items.push(...[...quickFiltersContextMenuData.items, { isSeparator: true } as const]);
}

// saved queries actions are only shown when the showQueryInput and showFilterBar is true
if (showQueryInput && showFilterBar) {
items.push(...queryAndFiltersRelatedPanels);
Expand Down Expand Up @@ -513,6 +579,7 @@ export function QueryBarMenuPanels({
width: 400,
content: <div>{manageFilterSetComponent}</div>,
},
...quickFiltersContextMenuData.panels,
] as EuiContextMenuPanelDescriptor[];

if (hiddenPanelOptions && hiddenPanelOptions.length > 0) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { EuiIconType } from '@elastic/eui/src/components/icon/icon';
import { ComponentType } from 'react';
import { Filter } from '@kbn/es-query';

export interface QuickFilter {
name: string;
icon?: Exclude<EuiIconType, ComponentType>;
disabled?: boolean;
filter: Filter;
}

export interface QuickFiltersGroup {
groupName: string;
icon?: Exclude<EuiIconType, ComponentType>;
items: QuickFiltersMenuItem[];
}

export type QuickFiltersMenuItem = QuickFiltersGroup | QuickFilter;

export const isQuickFiltersGroup = (
quickFiltersMenuItem: QuickFiltersMenuItem
): quickFiltersMenuItem is QuickFiltersGroup => 'items' in quickFiltersMenuItem;
4 changes: 4 additions & 0 deletions src/plugins/unified_search/public/search_bar/search_bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import type {
SuggestionsListSize,
} from '../typeahead/suggestions_component';
import { searchBarStyles } from './search_bar.styles';
import { QuickFiltersMenuItem } from '../query_string_input/quick_filters';

export interface SearchBarInjectedDeps {
kibana: KibanaReactContextValue<IUnifiedSearchPluginServices>;
Expand All @@ -58,6 +59,7 @@ export interface SearchBarOwnProps<QT extends AggregateQuery | Query = Query> {
showDatePicker?: boolean;
showAutoRefreshOnly?: boolean;
filters?: Filter[];
quickFilters?: QuickFiltersMenuItem[];
filtersForSuggestions?: Filter[];
hiddenFilterPanelOptions?: QueryBarMenuProps['hiddenPanelOptions'];
prependFilterBar?: React.ReactNode;
Expand Down Expand Up @@ -146,6 +148,7 @@ class SearchBarUI<QT extends (Query | AggregateQuery) | Query = Query> extends C
showSubmitButton: true,
showAutoRefreshOnly: false,
filtersForSuggestions: [],
quickFilters: [],
};

private services = this.props.kibana.services;
Expand Down Expand Up @@ -502,6 +505,7 @@ class SearchBarUI<QT extends (Query | AggregateQuery) | Query = Query> extends C
openQueryBarMenu={this.state.openQueryBarMenu}
onFiltersUpdated={this.props.onFiltersUpdated}
filters={this.props.filters}
quickFilters={this.props.quickFilters ?? []}
hiddenPanelOptions={this.props.hiddenFilterPanelOptions}
query={this.state.query as Query}
savedQuery={this.props.savedQuery}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import {
import { setDataViewsService } from '../common/lib/data_apis';
import { KibanaContextProvider } from '../common/lib/kibana';

const Alerts = lazy(() => import('./sections/global_alerts'));
const GlobalAlerts = lazy(() => import('./sections/global_alerts'));

export interface TriggersAndActionsUiServices extends CoreStart {
actions: ActionsPublicPluginSetup;
Expand All @@ -60,7 +60,7 @@ export interface TriggersAndActionsUiServices extends CoreStart {
unifiedSearch: UnifiedSearchPublicPluginStart;
}

export const renderApp = (deps: TriggersAndActionsUiServices) => {
export const renderApp = (deps: Partial<TriggersAndActionsUiServices>) => {
const { element } = deps;
render(<App deps={deps} />, element);
return () => {
Expand All @@ -80,7 +80,7 @@ export const App = ({ deps }: { deps: TriggersAndActionsUiServices }) => {
<KibanaContextProvider services={{ ...deps }}>
<Router history={deps.history}>
<Routes>
<Route path={`/`} component={suspendedComponentWithProps(Alerts, 'xl')} />
<Route path={`/`} component={suspendedComponentWithProps(GlobalAlerts, 'xl')} />
</Routes>
</Router>
</KibanaContextProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { AlertConsumers } from '@kbn/rule-data-utils';
import { createStartServicesMock } from '../../common/lib/kibana/kibana_react.mock';
import type { ValidFeatureId } from '@kbn/rule-data-utils';
import { act, renderHook } from '@testing-library/react-hooks';
import { useAlertDataView } from './use_alert_data_view';
import { useAlertDataViews } from './use_alert_data_view';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import React from 'react';

Expand Down Expand Up @@ -76,7 +76,7 @@ describe('useAlertDataView', () => {
};

const { result, waitForNextUpdate } = renderHook(
() => useAlertDataView(observabilityAlertFeatureIds),
() => useAlertDataViews(observabilityAlertFeatureIds),
{
wrapper,
}
Expand All @@ -91,7 +91,7 @@ describe('useAlertDataView', () => {
it('fetch index names + fields for the provided o11y featureIds', async () => {
await act(async () => {
const { waitForNextUpdate } = renderHook(
() => useAlertDataView(observabilityAlertFeatureIds),
() => useAlertDataViews(observabilityAlertFeatureIds),
{
wrapper,
}
Expand All @@ -107,7 +107,7 @@ describe('useAlertDataView', () => {

it('only fetch index names for security featureId', async () => {
await act(async () => {
const { waitForNextUpdate } = renderHook(() => useAlertDataView([AlertConsumers.SIEM]), {
const { waitForNextUpdate } = renderHook(() => useAlertDataViews([AlertConsumers.SIEM]), {
wrapper,
});

Expand All @@ -122,7 +122,7 @@ describe('useAlertDataView', () => {
it('Do not fetch anything if security and o11y featureIds are mix together', async () => {
await act(async () => {
const { result, waitForNextUpdate } = renderHook(
() => useAlertDataView([AlertConsumers.SIEM, AlertConsumers.LOGS]),
() => useAlertDataViews([AlertConsumers.SIEM, AlertConsumers.LOGS]),
{
wrapper,
}
Expand All @@ -144,7 +144,7 @@ describe('useAlertDataView', () => {

await act(async () => {
const { result, waitForNextUpdate } = renderHook(
() => useAlertDataView(observabilityAlertFeatureIds),
() => useAlertDataViews(observabilityAlertFeatureIds),
{
wrapper,
}
Expand Down
Loading

0 comments on commit 5ca182e

Please sign in to comment.