Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
2e1512a
WIP
Feb 17, 2022
aef22e5
Merge remote-tracking branch 'elastic/main' into rops/alerts_table
Feb 17, 2022
ef19b7d
Fix some issues
Feb 17, 2022
3ec7e09
Merge remote-tracking branch 'elastic/main' into rops/alerts_table
Feb 21, 2022
5583997
WIP
Feb 21, 2022
37a1afd
Merge remote-tracking branch 'elastic/main' into rops/alerts_table
Feb 22, 2022
02d050d
Revert "WIP"
Feb 22, 2022
94d5a61
WIP
Feb 22, 2022
0032a43
Merge remote-tracking branch 'elastic/main' into rops/alerts_table
Feb 23, 2022
8101279
Mock data
Feb 23, 2022
4f0bc7c
Merge remote-tracking branch 'elastic/main' into rops/alerts_table
Feb 24, 2022
892dd88
WIP
Feb 24, 2022
3858ad8
Merge remote-tracking branch 'elastic/main' into rops/alerts_table
Feb 24, 2022
db39f46
Merge remote-tracking branch 'elastic/main' into rops/alerts_table
Feb 25, 2022
f610853
Merge remote-tracking branch 'elastic/main' into rops/alerts_table
Feb 28, 2022
fddce1b
WIP
Mar 1, 2022
4891226
Merge remote-tracking branch 'elastic/main' into rops/alerts_table
Mar 2, 2022
ab75bd2
Merge remote-tracking branch 'elastic/main' into rops/alerts_table
Mar 7, 2022
c72bd30
Remove unnecessary logic
Mar 8, 2022
8ebb013
Merge remote-tracking branch 'elastic/main' into rops/alerts_table
Mar 8, 2022
c49f03f
Add some basic tests
Mar 8, 2022
0c9d22f
Merge remote-tracking branch 'elastic/main' into rops/alerts_table
Mar 9, 2022
5461b86
Merge remote-tracking branch 'elastic/main' into rops/alerts_table
Mar 10, 2022
bc8c4a2
Merge remote-tracking branch 'elastic/main' into rops/alerts_table
Mar 14, 2022
48ef702
Slight rename
Mar 14, 2022
06c6b2f
Fix unnecessary export
Mar 14, 2022
d92f2df
Fix types
Mar 14, 2022
7a6a61c
Add ability to use externally
Mar 14, 2022
b2b5609
Add test data
Mar 14, 2022
d64c00c
Fix types and tests
Mar 14, 2022
801fb46
Merge remote-tracking branch 'elastic/main' into rops/alerts_table
Mar 14, 2022
19d7503
LAZZYYYYYYY load
Mar 14, 2022
72399e3
Merge remote-tracking branch 'elastic/main' into rops/alerts_table
Mar 15, 2022
4053a67
Merge remote-tracking branch 'elastic/main' into rops/alerts_table
Mar 18, 2022
e45630e
Merge remote-tracking branch 'elastic/main' into rops/alerts_table
Mar 21, 2022
49893fb
Improvements based on o11y integrations
Mar 21, 2022
f054aaf
Fix types and tests
Mar 22, 2022
7002999
Merge branch 'main' into rops/alerts_table
kibanamachine Mar 22, 2022
e9276e9
Cleanup and unit tests
Mar 22, 2022
9f6bd7c
Better integration with o11y alerts
Mar 22, 2022
fdb6159
Use experimental flag
Mar 22, 2022
0bf897f
Add functional tests
Mar 22, 2022
93fe07e
Merge remote-tracking branch 'elastic/main' into rops/alerts_table
Mar 23, 2022
efb0af0
Fix tests
Mar 23, 2022
9aba43b
Add unit test for home page, and stop using internal because we have …
Mar 23, 2022
4b49e3b
Merge remote-tracking branch 'elastic/main' into rops/alerts_table
Mar 23, 2022
134fd00
Fix bad label
Mar 23, 2022
f3de9b4
Merge remote-tracking branch 'elastic/main' into rops/alerts_table
chrisronline Mar 28, 2022
a97c5f6
Update strategy name
Mar 28, 2022
db15142
Merge remote-tracking branch 'elastic/main' into rops/alerts_table
Mar 29, 2022
96887a0
Fix test
Mar 29, 2022
0da303d
Fix test, part 2
Mar 29, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion x-pack/plugins/rule_registry/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,9 @@
* 2.0.
*/
export { parseTechnicalFields, type ParsedTechnicalFields } from './parse_technical_fields';
export type { RuleRegistrySearchRequest, RuleRegistrySearchResponse } from './search_strategy';
export type {
RuleRegistrySearchRequest,
RuleRegistrySearchResponse,
RuleRegistrySearchRequestPagination,
} from './search_strategy';
export { BASE_RAC_ALERTS_API_PATH } from './constants';
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export type ExperimentalFeatures = typeof allowedExperimentalValues;
*/
export const allowedExperimentalValues = Object.freeze({
rulesListDatagrid: true,
internalAlertsTable: false,
rulesDetailLogs: true,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export const renderApp = (deps: TriggersAndActionsUiServices) => {

export const App = ({ deps }: { deps: TriggersAndActionsUiServices }) => {
const { savedObjects, uiSettings, theme$ } = deps;
const sections: Section[] = ['rules', 'connectors'];
const sections: Section[] = ['rules', 'connectors', 'alerts'];
const isDarkMode = useObservable<boolean>(uiSettings.get$('theme:darkMode'));

const sectionsRegex = sections.join('|');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ export {
} from '../../../../alerting/common';
export { BASE_ACTION_API_PATH, INTERNAL_BASE_ACTION_API_PATH } from '../../../../actions/common';

export type Section = 'connectors' | 'rules';
export type Section = 'connectors' | 'rules' | 'alerts';

export const routeToHome = `/`;
export const routeToConnectors = `/connectors`;
export const routeToRules = `/rules`;
export const routeToRuleDetails = `/rule/:ruleId`;
export const routeToInternalAlerts = `/alerts`;
export const legacyRouteToRules = `/alerts`;
export const legacyRouteToRuleDetails = `/alert/:alertId`;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,20 @@ import { RouteComponentProps, Router } from 'react-router-dom';
import { createMemoryHistory, createLocation } from 'history';
import { mountWithIntl } from '@kbn/test-jest-helpers';
import TriggersActionsUIHome, { MatchParams } from './home';
import { hasShowActionsCapability } from './lib/capabilities';
import { useKibana } from '../common/lib/kibana';
import { getIsExperimentalFeatureEnabled } from '../common/get_experimental_features';
jest.mock('../common/lib/kibana');
jest.mock('../common/get_experimental_features');
jest.mock('./lib/capabilities');
const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>;

describe('home', () => {
beforeEach(() => {
(hasShowActionsCapability as jest.Mock).mockClear();
(getIsExperimentalFeatureEnabled as jest.Mock).mockClear();
});

it('renders the documentation link', async () => {
const props: RouteComponentProps<MatchParams> = {
history: createMemoryHistory(),
Expand All @@ -40,4 +49,38 @@ describe('home', () => {
'https://www.elastic.co/guide/en/kibana/mocked-test-branch/create-and-manage-rules.html'
);
});

it('hides the internal alerts table route if the config is not set', async () => {
(hasShowActionsCapability as jest.Mock).mockImplementation(() => {
return true;
});
const props: RouteComponentProps<MatchParams> = {
history: createMemoryHistory(),
location: createLocation('/'),
match: {
isExact: true,
path: `/connectorss`,
url: '',
params: {
section: 'connectors',
},
},
};

let home = mountWithIntl(<TriggersActionsUIHome {...props} />);

// Just rules/connectors
expect(home.find('.euiTab__content').length).toBe(2);

(getIsExperimentalFeatureEnabled as jest.Mock).mockImplementation((feature: string) => {
if (feature === 'internalAlertsTable') {
return true;
}
return false;
});

home = mountWithIntl(<TriggersActionsUIHome {...props} />);
// alerts now too!
expect(home.find('.euiTab__content').length).toBe(3);
});
});
29 changes: 27 additions & 2 deletions x-pack/plugins/triggers_actions_ui/public/application/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
*/

import React, { lazy, useEffect } from 'react';
import { Route, RouteComponentProps, Switch } from 'react-router-dom';
import { Route, RouteComponentProps, Switch, Redirect } from 'react-router-dom';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiSpacer, EuiButtonEmpty, EuiPageHeader } from '@elastic/eui';

import { Section, routeToConnectors, routeToRules } from './constants';
import { getIsExperimentalFeatureEnabled } from '../common/get_experimental_features';
import { Section, routeToConnectors, routeToRules, routeToInternalAlerts } from './constants';
import { getAlertingSectionBreadcrumb } from './lib/breadcrumb';
import { getCurrentDocTitle } from './lib/doc_title';
import { hasShowActionsCapability } from './lib/capabilities';
Expand All @@ -24,6 +25,7 @@ const ActionsConnectorsList = lazy(
() => import('./sections/actions_connectors_list/components/actions_connectors_list')
);
const RulesList = lazy(() => import('./sections/rules_list/components/rules_list'));
const AlertsPage = lazy(() => import('./sections/alerts_table/alerts_page'));

export interface MatchParams {
section: Section;
Expand All @@ -38,9 +40,11 @@ export const TriggersActionsUIHome: React.FunctionComponent<RouteComponentProps<
const {
chrome,
application: { capabilities },

setBreadcrumbs,
docLinks,
} = useKibana().services;
const isInternalAlertsTableEnabled = getIsExperimentalFeatureEnabled('internalAlertsTable');

const canShowActions = hasShowActionsCapability(capabilities);
const tabs: Array<{
Expand All @@ -67,6 +71,18 @@ export const TriggersActionsUIHome: React.FunctionComponent<RouteComponentProps<
});
}

if (isInternalAlertsTableEnabled) {
tabs.push({
id: 'alerts',
name: (
<FormattedMessage
id="xpack.triggersActionsUI.home.TabTitle"
defaultMessage="Alerts (Internal use only)"
/>
),
});
}

const onSectionChange = (newSection: Section) => {
history.push(`/${newSection}`);
};
Expand Down Expand Up @@ -134,6 +150,15 @@ export const TriggersActionsUIHome: React.FunctionComponent<RouteComponentProps<
path={routeToRules}
component={suspendedComponentWithProps(RulesList, 'xl')}
/>
{isInternalAlertsTableEnabled ? (
<Route
exact
path={routeToInternalAlerts}
component={suspendedComponentWithProps(AlertsPage, 'xl')}
/>
) : (
<Redirect to={routeToRules} />
)}
</Switch>
</HealthCheck>
</HealthContextProvider>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Note: This entire folder is meant for internal, testing purposes and is not exposed to users.
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
/*
* 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, { useState, useCallback, useEffect } from 'react';
import { get } from 'lodash';
import {
EuiDataGridCellValueElementProps,
EuiDataGridControlColumn,
EuiFlexItem,
EuiFlexGroup,
EuiSpacer,
EuiProgress,
} from '@elastic/eui';
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { AlertConsumers } from '@kbn/rule-data-utils';
import {
RuleRegistrySearchRequest,
RuleRegistrySearchResponse,
RuleRegistrySearchRequestPagination,
} from '../../../../../../rule_registry/common';
import { AlertsTable } from '../alerts_table';
import { useKibana } from '../../../../common/lib/kibana';
import { AbortError } from '../../../../../../../../src/plugins/kibana_utils/common';
import { AlertsData } from '../../../../types';

const consumers = [
AlertConsumers.APM,
AlertConsumers.LOGS,
AlertConsumers.UPTIME,
AlertConsumers.INFRASTRUCTURE,
];

const defaultPagination = {
pageSize: 10,
pageIndex: 0,
};

const defaultSort: estypes.SortCombinations[] = [
{
'event.action': {
order: 'asc',
},
},
];

const AlertsPage: React.FunctionComponent = () => {
const { data, notifications } = useKibana().services;
const [showCheckboxes] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [isInitializing, setIsInitializing] = useState(true);
const [alertsCount, setAlertsCount] = useState(0);
const [alerts, setAlerts] = useState<AlertsData[]>([]);
const [sort, setSort] = useState<estypes.SortCombinations[]>(defaultSort);
const [pagination, setPagination] = useState(defaultPagination);

const onPageChange = (_pagination: RuleRegistrySearchRequestPagination) => {
setPagination(_pagination);
};
const onSortChange = (_sort: Array<{ id: string; direction: 'asc' | 'desc' }>) => {
setSort(
_sort.map(({ id, direction }) => {
return {
[id]: {
order: direction,
},
};
})
);
};

const asyncSearch = useCallback(() => {
setIsLoading(true);
const abortController = new AbortController();
const request: RuleRegistrySearchRequest = {
featureIds: consumers,
sort,
pagination,
};
data.search
.search<RuleRegistrySearchRequest, RuleRegistrySearchResponse>(request, {
strategy: 'privateRuleRegistryAlertsSearchStrategy',
abortSignal: abortController.signal,
})
.subscribe({
next: (res) => {
const alertsResponse = res.rawResponse.hits.hits.map(
(hit) => hit.fields as unknown as AlertsData
) as AlertsData[];
setAlerts(alertsResponse);
const total = !isNaN(res.rawResponse.hits.total as number)
? (res.rawResponse.hits.total as number)
: (res.rawResponse.hits.total as estypes.SearchTotalHits).value ?? 0;
setAlertsCount(total);
setIsLoading(false);
},
error: (e) => {
if (e instanceof AbortError) {
notifications.toasts.addWarning({
title: e.message,
});
} else {
notifications.toasts.addDanger({
title: 'Failed to run search',
text: e.message,
});
}
setIsLoading(false);
},
});
setIsInitializing(false);
}, [data.search, notifications.toasts, sort, pagination]);

useEffect(() => {
asyncSearch();
}, [asyncSearch]);

const useFetchAlertsData = () => {
return {
activePage: pagination.pageIndex,
alerts,
alertsCount,
isInitializing,
isLoading,
getInspectQuery: () => ({ request: {}, response: {} }),
onColumnsChange: (columns: EuiDataGridControlColumn[]) => {},
onPageChange,
onSortChange,
refresh: () => {
asyncSearch();
},
};
};

const tableProps = {
consumers,
bulkActions: [],
columns: [
{
id: 'event.action',
displayAsText: 'Alert status',
initialWidth: 150,
},
{
id: '@timestamp',
displayAsText: 'Last updated',
initialWidth: 250,
},
{
id: 'kibana.alert.duration.us',
displayAsText: 'Duration',
initialWidth: 150,
},
{
id: 'kibana.alert.reason',
displayAsText: 'Reason',
},
],
deletedEventIds: [],
disabledCellActions: [],
pageSize: defaultPagination.pageSize,
pageSizeOptions: [2, 5, 10, 20, 50, 100],
leadingControlColumns: [],
renderCellValue: (rcvProps: EuiDataGridCellValueElementProps) => {
const { columnId, visibleRowIndex } = rcvProps as EuiDataGridCellValueElementProps & {
visibleRowIndex: number;
};
const value = (get(alerts[visibleRowIndex], columnId) ?? [])[0];
return value ?? 'N/A';
},
showCheckboxes,
trailingControlColumns: [],
useFetchAlertsData,
'data-test-subj': 'internalAlertsPage',
};

return (
<section>
<h1>THIS IS AN INTERNAL TEST PAGE</h1>
<EuiSpacer />
<EuiFlexGroup>
<EuiFlexItem grow={true}>
{isLoading && (
<EuiProgress size="xs" color="accent" data-test-subj="internalAlertsPageLoading" />
)}
<AlertsTable {...tableProps} />
</EuiFlexItem>
</EuiFlexGroup>
</section>
);
};

export { AlertsPage };
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* 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 { AlertsPage } from './alerts_page';
// eslint-disable-next-line import/no-default-export
export { AlertsPage as default };
Loading