Cleaned up Observability plugin from timelines unused dependencies on TGrid usage (#143607)

* Clean Observability plugin from timelines unused deps on TGrid usage

* -

* End of standalone version of TGrid

* fixed unused deps

* -

* Clean up variables

* Fixed tests

* FIxed tests

* Removed unused tests

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Yuliia Naumenko 2022-10-19 15:52:39 -07:00 committed by GitHub
parent c25eedc402
commit 64b5efebdd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 67 additions and 1065 deletions

View file

@ -475,8 +475,6 @@
"@kbn/global-search-test-plugin/*": ["x-pack/test/plugin_functional/plugins/global_search_test/*"],
"@kbn/resolver-test-plugin": ["x-pack/test/plugin_functional/plugins/resolver_test"],
"@kbn/resolver-test-plugin/*": ["x-pack/test/plugin_functional/plugins/resolver_test/*"],
"@kbn/timelines-test-plugin": ["x-pack/test/plugin_functional/plugins/timelines_test"],
"@kbn/timelines-test-plugin/*": ["x-pack/test/plugin_functional/plugins/timelines_test/*"],
"@kbn/security-test-endpoints-plugin": ["x-pack/test/security_functional/fixtures/common/test_endpoints"],
"@kbn/security-test-endpoints-plugin/*": ["x-pack/test/security_functional/fixtures/common/test_endpoints/*"],
"@kbn/application-usage-test-plugin": ["x-pack/test/usage_collection/plugins/application_usage_test"],

View file

@ -27,7 +27,6 @@
"features",
"inspector",
"ruleRegistry",
"timelines",
"triggersActionsUi",
"inspector",
"unifiedSearch",

View file

@ -24,8 +24,6 @@ import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
import { LensPublicStart } from '@kbn/lens-plugin/public';
import { TriggersAndActionsUIPublicPluginStart } from '@kbn/triggers-actions-ui-plugin/public';
import { CasesUiStart } from '@kbn/cases-plugin/public';
import { TimelinesUIStart } from '@kbn/timelines-plugin/public';
export interface ObservabilityAppServices {
application: ApplicationStart;
cases: CasesUiStart;
@ -42,7 +40,6 @@ export interface ObservabilityAppServices {
stateTransfer: EmbeddableStateTransfer;
storage: IStorageWrapper;
theme: ThemeServiceStart;
timelines: TimelinesUIStart;
triggersActionsUi: TriggersAndActionsUIPublicPluginStart;
uiSettings: IUiSettingsClient;
isDev?: boolean;

View file

@ -12,9 +12,9 @@ import { casesFeatureId, observabilityFeatureId } from '../../common';
import { useBulkAddToCaseActions } from '../hooks/use_alert_bulk_case_actions';
import { TopAlert, useToGetInternalFlyout } from '../pages/alerts';
import { getRenderCellValue } from '../pages/alerts/components/render_cell_value';
import { addDisplayNames } from '../pages/alerts/containers/alerts_table_t_grid/add_display_names';
import { columns as alertO11yColumns } from '../pages/alerts/containers/alerts_table_t_grid/alerts_table_t_grid';
import { getRowActions } from '../pages/alerts/containers/alerts_table_t_grid/get_row_actions';
import { addDisplayNames } from '../pages/alerts/containers/alerts_table/add_display_names';
import { columns as alertO11yColumns } from '../pages/alerts/containers/alerts_table/default_columns';
import { getRowActions } from '../pages/alerts/containers/alerts_table/get_row_actions';
import type { ObservabilityRuleTypeRegistry } from '../rules/create_observability_rule_type_registry';
import type { ConfigSchema } from '../plugin';

View file

@ -12,7 +12,7 @@ import {
ADD_TO_CASE_DISABLED,
ADD_TO_EXISTING_CASE,
ADD_TO_NEW_CASE,
} from '../pages/alerts/containers/alerts_table_t_grid/translations';
} from '../pages/alerts/containers/alerts_table/translations';
import { useGetUserCasesPermissions } from './use_get_user_cases_permissions';
import { ObservabilityAppServices } from '../application/types';

View file

@ -1,42 +0,0 @@
/*
* 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 from 'react';
import { i18n } from '@kbn/i18n';
import { TimelineNonEcsData } from '@kbn/timelines-plugin/common/search_strategy';
import { TGridCellAction } from '@kbn/timelines-plugin/common/types/timeline';
import { getPageRowIndex } from '@kbn/timelines-plugin/public';
import FilterForValueButton from './filter_for_value';
import { getMappedNonEcsValue } from './render_cell_value';
export const FILTER_FOR_VALUE = i18n.translate('xpack.observability.hoverActions.filterForValue', {
defaultMessage: 'Filter for value',
});
/** actions for adding filters to the search bar */
const buildFilterCellActions = (addToQuery: (value: string) => void): TGridCellAction[] => [
({ data, pageSize }: { data: TimelineNonEcsData[][]; pageSize: number }) =>
({ rowIndex, columnId, Component }) => {
const value = getMappedNonEcsValue({
data: data[getPageRowIndex(rowIndex, pageSize)],
fieldName: columnId,
});
return (
<FilterForValueButton
Component={Component}
field={columnId}
value={value}
addToQuery={addToQuery}
/>
);
},
];
/** returns the default actions shown in `EuiDataGrid` cells */
export const getDefaultCellActions = ({ addToQuery }: { addToQuery: (value: string) => void }) =>
buildFilterCellActions(addToQuery);

View file

@ -10,7 +10,6 @@ export * from './render_cell_value';
export * from './severity_badge';
export * from './workflow_status_filter';
export * from './alerts_search_bar';
export * from './default_cell_actions';
export * from './filter_for_value';
export * from './parse_alert';
export * from './alerts_status_filter';

View file

@ -24,10 +24,7 @@ import { useKibana } from '../../../utils/kibana_react';
import { useGetUserCasesPermissions } from '../../../hooks/use_get_user_cases_permissions';
import { parseAlert } from './parse_alert';
import { translations, paths } from '../../../config';
import {
ADD_TO_EXISTING_CASE,
ADD_TO_NEW_CASE,
} from '../containers/alerts_table_t_grid/translations';
import { ADD_TO_EXISTING_CASE, ADD_TO_NEW_CASE } from '../containers/alerts_table/translations';
import { ObservabilityAppServices } from '../../../application/types';
import { RULE_DETAILS_PAGE_ID } from '../../rule_details/types';
import type { TopAlert } from '../containers/alerts_page/types';

View file

@ -5,4 +5,4 @@
* 2.0.
*/
export { getRenderCellValue, getMappedNonEcsValue } from './render_cell_value';
export { getRenderCellValue } from './render_cell_value';

View file

@ -6,12 +6,10 @@
*/
import { ALERT_DURATION, ALERT_REASON, ALERT_STATUS, TIMESTAMP } from '@kbn/rule-data-utils';
import { EuiDataGridColumn } from '@elastic/eui';
import type { ColumnHeaderOptions } from '@kbn/timelines-plugin/common';
import { translations } from '../../../../config';
export const addDisplayNames = (
column: Pick<EuiDataGridColumn, 'display' | 'displayAsText' | 'id' | 'initialWidth'> &
ColumnHeaderOptions
column: Pick<EuiDataGridColumn, 'display' | 'displayAsText' | 'id' | 'initialWidth'>
) => {
if (column.id === ALERT_REASON) {
return { ...column, displayAsText: translations.alertsTable.reasonColumnDescription };

View file

@ -0,0 +1,52 @@
/*
* 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.
*/
/**
* We need to produce types and code transpilation at different folders during the build of the package.
* We have types and code at different imports because we don't want to import the whole package in the resulting webpack bundle for the plugin.
* This way plugins can do targeted imports to reduce the final code bundle
*/
import { ALERT_DURATION, ALERT_REASON, ALERT_STATUS, TIMESTAMP } from '@kbn/rule-data-utils';
import { EuiDataGridColumn } from '@elastic/eui';
import type { ColumnHeaderOptions } from '@kbn/timelines-plugin/common';
import { translations } from '../../../../config';
/**
* columns implements a subset of `EuiDataGrid`'s `EuiDataGridColumn` interface,
* plus additional TGrid column properties
*/
export const columns: Array<
Pick<EuiDataGridColumn, 'display' | 'displayAsText' | 'id' | 'initialWidth'> & ColumnHeaderOptions
> = [
{
columnHeaderType: 'not-filtered',
displayAsText: translations.alertsTable.statusColumnDescription,
id: ALERT_STATUS,
initialWidth: 110,
},
{
columnHeaderType: 'not-filtered',
displayAsText: translations.alertsTable.lastUpdatedColumnDescription,
id: TIMESTAMP,
initialWidth: 230,
},
{
columnHeaderType: 'not-filtered',
displayAsText: translations.alertsTable.durationColumnDescription,
id: ALERT_DURATION,
initialWidth: 116,
},
{
columnHeaderType: 'not-filtered',
displayAsText: translations.alertsTable.reasonColumnDescription,
id: ALERT_REASON,
linkField: '*',
},
];

View file

@ -1,331 +0,0 @@
/*
* 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.
*/
/**
* We need to produce types and code transpilation at different folders during the build of the package.
* We have types and code at different imports because we don't want to import the whole package in the resulting webpack bundle for the plugin.
* This way plugins can do targeted imports to reduce the final code bundle
*/
import {
ALERT_DURATION,
ALERT_EVALUATION_THRESHOLD,
ALERT_EVALUATION_VALUE,
ALERT_REASON,
ALERT_RULE_CATEGORY,
ALERT_RULE_NAME,
ALERT_STATUS,
ALERT_UUID,
TIMESTAMP,
ALERT_START,
} from '@kbn/rule-data-utils';
import { EuiDataGridColumn, EuiFlexGroup } from '@elastic/eui';
import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public';
import styled from 'styled-components';
import React, { Suspense, useMemo, useState, useCallback, useEffect } from 'react';
import { pick } from 'lodash';
import type {
TGridType,
TGridState,
TGridModel,
SortDirection,
} from '@kbn/timelines-plugin/public';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import type {
ActionProps,
ColumnHeaderOptions,
ControlColumnProps,
RowRenderer,
} from '@kbn/timelines-plugin/common';
import { getAlertsPermissions } from '../../../../hooks/use_alert_permission';
import type { TopAlert } from '../alerts_page/types';
import { getRenderCellValue } from '../../components/render_cell_value';
import { observabilityAppId, observabilityFeatureId } from '../../../../../common';
import { useGetUserCasesPermissions } from '../../../../hooks/use_get_user_cases_permissions';
import { usePluginContext } from '../../../../hooks/use_plugin_context';
import { LazyAlertsFlyout } from '../../../..';
import { translations } from '../../../../config';
import { addDisplayNames } from './add_display_names';
import { ObservabilityAppServices } from '../../../../application/types';
import { useBulkAddToCaseActions } from '../../../../hooks/use_alert_bulk_case_actions';
import {
ObservabilityActions,
ObservabilityActionsProps,
} from '../../components/observability_actions';
interface AlertsTableTGridProps {
indexNames: string[];
rangeFrom: string;
rangeTo: string;
kuery?: string;
stateStorageKey: string;
storage: IStorageWrapper;
setRefetch: (ref: () => void) => void;
itemsPerPage?: number;
}
const EventsThContent = styled.div.attrs(({ className = '' }) => ({
className: `siemEventsTable__thContent ${className}`,
}))<{ textAlign?: string; width?: number }>`
font-size: ${({ theme }) => theme.eui.euiFontSizeXS};
font-weight: ${({ theme }) => theme.eui.euiFontWeightBold};
line-height: ${({ theme }) => theme.eui.euiLineHeight};
min-width: 0;
padding: ${({ theme }) => theme.eui.euiSizeXS};
text-align: ${({ textAlign }) => textAlign};
width: ${({ width }) =>
width != null
? `${width}px`
: '100%'}; /* Using width: 100% instead of flex: 1 and max-width: 100% for IE11 */
> button.euiButtonIcon,
> .euiToolTipAnchor > button.euiButtonIcon {
margin-left: ${({ theme }) => `-${theme.eui.euiSizeXS}`};
}
`;
/**
* columns implements a subset of `EuiDataGrid`'s `EuiDataGridColumn` interface,
* plus additional TGrid column properties
*/
export const columns: Array<
Pick<EuiDataGridColumn, 'display' | 'displayAsText' | 'id' | 'initialWidth'> & ColumnHeaderOptions
> = [
{
columnHeaderType: 'not-filtered',
displayAsText: translations.alertsTable.statusColumnDescription,
id: ALERT_STATUS,
initialWidth: 110,
},
{
columnHeaderType: 'not-filtered',
displayAsText: translations.alertsTable.lastUpdatedColumnDescription,
id: TIMESTAMP,
initialWidth: 230,
},
{
columnHeaderType: 'not-filtered',
displayAsText: translations.alertsTable.durationColumnDescription,
id: ALERT_DURATION,
initialWidth: 116,
},
{
columnHeaderType: 'not-filtered',
displayAsText: translations.alertsTable.reasonColumnDescription,
id: ALERT_REASON,
linkField: '*',
},
];
const NO_ROW_RENDER: RowRenderer[] = [];
const trailingControlColumns: never[] = [];
const FIELDS_WITHOUT_CELL_ACTIONS = [
'@timestamp',
'signal.rule.risk_score',
'signal.reason',
'kibana.alert.duration.us',
'kibana.alert.reason',
];
export function AlertsTableTGrid(props: AlertsTableTGridProps) {
const {
indexNames,
rangeFrom,
rangeTo,
kuery,
setRefetch,
stateStorageKey,
storage,
itemsPerPage,
} = props;
const {
timelines,
application: { capabilities },
} = useKibana<ObservabilityAppServices>().services;
const { observabilityRuleTypeRegistry, config } = usePluginContext();
const [flyoutAlert, setFlyoutAlert] = useState<TopAlert | undefined>(undefined);
const [tGridState, setTGridState] = useState<Partial<TGridModel> | null>(
storage.get(stateStorageKey)
);
const userCasesPermissions = useGetUserCasesPermissions();
const hasAlertsCrudPermissions = useCallback(
({ ruleConsumer, ruleProducer }: { ruleConsumer: string; ruleProducer?: string }) => {
if (ruleConsumer === 'alerts' && ruleProducer) {
return getAlertsPermissions(capabilities, ruleProducer).crud;
}
return getAlertsPermissions(capabilities, ruleConsumer).crud;
},
[capabilities]
);
const [deletedEventIds, setDeletedEventIds] = useState<string[]>([]);
useEffect(() => {
if (tGridState) {
const newState = {
...tGridState,
columns: tGridState.columns?.map((c) =>
pick(c, ['columnHeaderType', 'displayAsText', 'id', 'initialWidth', 'linkField'])
),
};
if (newState !== storage.get(stateStorageKey)) {
storage.set(stateStorageKey, newState);
}
}
}, [tGridState, stateStorageKey, storage]);
const setEventsDeleted = useCallback<ObservabilityActionsProps['setEventsDeleted']>((action) => {
if (action.isDeleted) {
setDeletedEventIds((ids) => [...ids, ...action.eventIds]);
}
}, []);
const leadingControlColumns: ControlColumnProps[] = useMemo(() => {
return [
{
id: 'expand',
width: 120,
headerCellRender: () => {
return <EventsThContent>{translations.alertsTable.actionsTextLabel}</EventsThContent>;
},
rowCellRender: (actionProps: ActionProps) => {
return (
<EuiFlexGroup gutterSize="none" responsive={false}>
<ObservabilityActions
{...actionProps}
setEventsDeleted={setEventsDeleted}
setFlyoutAlert={setFlyoutAlert}
observabilityRuleTypeRegistry={observabilityRuleTypeRegistry}
config={config}
/>
</EuiFlexGroup>
);
},
},
];
}, [setEventsDeleted, observabilityRuleTypeRegistry, config]);
const onStateChange = useCallback(
(state: TGridState) => {
const pickedState = pick(state.tableById['standalone-t-grid'], [
'columns',
'sort',
'selectedEventIds',
]);
if (JSON.stringify(pickedState) !== JSON.stringify(tGridState)) {
setTGridState(pickedState);
}
},
[tGridState]
);
const addToCaseBulkActions = useBulkAddToCaseActions();
const bulkActions = useMemo(
() => ({
alertStatusActions: false,
customBulkActions: addToCaseBulkActions,
}),
[addToCaseBulkActions]
);
const tGridProps = useMemo(() => {
const type: TGridType = 'standalone';
const sortDirection: SortDirection = 'desc';
return {
appId: observabilityAppId,
casesOwner: observabilityFeatureId,
casePermissions: userCasesPermissions,
type,
columns: (tGridState?.columns ?? columns).map(addDisplayNames),
deletedEventIds,
disabledCellActions: FIELDS_WITHOUT_CELL_ACTIONS,
end: rangeTo,
filters: [],
hasAlertsCrudPermissions,
indexNames,
itemsPerPage,
itemsPerPageOptions: [10, 25, 50],
loadingText: translations.alertsTable.loadingTextLabel,
onStateChange,
query: {
query: kuery ?? '',
language: 'kuery',
},
renderCellValue: getRenderCellValue({ setFlyoutAlert, observabilityRuleTypeRegistry }),
rowRenderers: NO_ROW_RENDER,
// TODO: implement Kibana data view runtime fields in observability
runtimeMappings: {},
start: rangeFrom,
setRefetch,
bulkActions,
sort: tGridState?.sort ?? [
{
columnId: '@timestamp',
columnType: 'date',
sortDirection,
},
],
queryFields: [
ALERT_DURATION,
ALERT_EVALUATION_THRESHOLD,
ALERT_EVALUATION_VALUE,
ALERT_REASON,
ALERT_RULE_CATEGORY,
ALERT_RULE_NAME,
ALERT_STATUS,
ALERT_UUID,
ALERT_START,
TIMESTAMP,
],
leadingControlColumns,
trailingControlColumns,
unit: (totalAlerts: number) => translations.alertsTable.showingAlertsTitle(totalAlerts),
};
}, [
userCasesPermissions,
tGridState?.columns,
tGridState?.sort,
deletedEventIds,
rangeTo,
hasAlertsCrudPermissions,
indexNames,
itemsPerPage,
observabilityRuleTypeRegistry,
onStateChange,
kuery,
rangeFrom,
setRefetch,
bulkActions,
leadingControlColumns,
]);
const handleFlyoutClose = () => setFlyoutAlert(undefined);
return (
<>
{flyoutAlert && (
<Suspense fallback={null}>
<LazyAlertsFlyout
alert={flyoutAlert}
observabilityRuleTypeRegistry={observabilityRuleTypeRegistry}
onClose={handleFlyoutClose}
/>
</Suspense>
)}
{timelines.getTGrid<'standalone'>(tGridProps)}
</>
);
}

View file

@ -1,8 +0,0 @@
/*
* 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.
*/
export { AlertsTableTGrid } from './alerts_table_t_grid';

View file

@ -6,5 +6,4 @@
*/
export * from './alerts_page';
export * from './alerts_table_t_grid';
export * from './state_container';

View file

@ -67,7 +67,6 @@ export const useAlertsActions = ({
setEventsDeleted,
onUpdateSuccess: onStatusUpdate,
onUpdateFailure: onStatusUpdate,
scopeId,
});
return {

View file

@ -92,7 +92,6 @@ export interface BulkActionsProps {
onUpdateSuccess?: OnUpdateAlertStatusSuccess;
onUpdateFailure?: OnUpdateAlertStatusError;
customBulkActions?: CustomBulkActionProp[];
scopeId?: string;
}
export interface HeaderActionProps {

View file

@ -6,18 +6,16 @@
*/
import React from 'react';
import { Provider } from 'react-redux';
import { I18nProvider } from '@kbn/i18n-react';
import type { Store } from 'redux';
import { Storage } from '@kbn/kibana-utils-plugin/public';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { createStore } from '../store/t_grid';
import { Provider } from 'react-redux';
import { TGrid as TGridComponent } from './t_grid';
import type { TGridProps } from '../types';
import { DragDropContextWrapper } from './drag_and_drop';
import { initialTGridState } from '../store/t_grid/reducer';
import type { TGridIntegratedProps } from './t_grid/integrated';
const EMPTY_BROWSER_FIELDS = {};
@ -31,18 +29,13 @@ type TGridComponent = TGridProps & {
export const TGrid = (props: TGridComponent) => {
const { store, storage, setStore, ...tGridProps } = props;
let tGridStore = store;
if (!tGridStore && props.type === 'standalone') {
tGridStore = createStore(initialTGridState, storage);
setStore(tGridStore);
}
let browserFields = EMPTY_BROWSER_FIELDS;
if ((tGridProps as TGridIntegratedProps).browserFields != null) {
browserFields = (tGridProps as TGridIntegratedProps).browserFields;
}
return (
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
<Provider store={tGridStore!}>
<Provider store={store!}>
<I18nProvider>
<DragDropContextWrapper browserFields={browserFields} defaultsHeader={props.columns}>
<TGridComponent {...tGridProps} />

View file

@ -9,13 +9,10 @@ import React from 'react';
import type { TGridProps } from '../../types';
import { TGridIntegrated, TGridIntegratedProps } from './integrated';
import { TGridStandalone, TGridStandaloneProps } from './standalone';
export const TGrid = (props: TGridProps) => {
const { type, ...componentsProps } = props;
if (type === 'standalone') {
return <TGridStandalone {...(componentsProps as unknown as TGridStandaloneProps)} />;
} else if (type === 'embedded') {
if (type === 'embedded') {
return <TGridIntegrated {...(componentsProps as unknown as TGridIntegratedProps)} />;
}
return null;

View file

@ -1,394 +0,0 @@
/*
* 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 { EuiFlexItem, EuiFlexGroup } from '@elastic/eui';
import { isEmpty } from 'lodash/fp';
import React, { useEffect, useMemo } from 'react';
import styled from 'styled-components';
import { useDispatch } from 'react-redux';
import { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import type { Filter, Query } from '@kbn/es-query';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import type { CoreStart } from '@kbn/core/public';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { getEsQueryConfig } from '@kbn/data-plugin/common';
import type { Ecs } from '../../../../common/ecs';
import { Direction, EntityType } from '../../../../common/search_strategy';
import { TGridCellAction } from '../../../../common/types/timeline';
import type {
CellValueElementProps,
ColumnHeaderOptions,
ControlColumnProps,
DataProvider,
RowRenderer,
SortColumnTable,
BulkActionsProp,
AlertStatus,
} from '../../../../common/types/timeline';
import { useDeepEqualSelector } from '../../../hooks/use_selector';
import { defaultHeaders } from '../body/column_headers/default_headers';
import { getCombinedFilterQuery } from '../helpers';
import { tGridActions, tGridSelectors } from '../../../store/t_grid';
import type { State } from '../../../store/t_grid';
import { useTimelineEvents } from '../../../container';
import { StatefulBody } from '../body';
import { LastUpdatedAt } from '../..';
import { SELECTOR_TIMELINE_GLOBAL_CONTAINER, UpdatedFlexItem, UpdatedFlexGroup } from '../styles';
import { InspectButton, InspectButtonContainer } from '../../inspect';
import { useFetchIndex } from '../../../container/source';
import { TGridLoading, TGridEmpty, TableContext } from '../shared';
const FullWidthFlexGroup = styled(EuiFlexGroup)<{ $visible: boolean }>`
overflow: hidden;
margin: 0;
display: ${({ $visible }) => ($visible ? 'flex' : 'none')};
`;
export const EVENTS_VIEWER_HEADER_HEIGHT = 90; // px
export const STANDALONE_ID = 'standalone-t-grid';
const EMPTY_DATA_PROVIDERS: DataProvider[] = [];
const TitleText = styled.span`
margin-right: 12px;
`;
const AlertsTableWrapper = styled.div`
width: 100%;
height: 100%;
`;
const EventsContainerLoading = styled.div.attrs(({ className = '' }) => ({
className: `${SELECTOR_TIMELINE_GLOBAL_CONTAINER} ${className}`,
}))`
position: relative;
width: 100%;
overflow: hidden;
flex: 1;
display: flex;
flex-direction: column;
`;
const ScrollableFlexItem = styled(EuiFlexItem)`
overflow: auto;
`;
export interface TGridStandaloneProps {
columns: ColumnHeaderOptions[];
dataViewId?: string | null;
defaultCellActions?: TGridCellAction[];
deletedEventIds: Readonly<string[]>;
disabledCellActions: string[];
end: string;
entityType?: EntityType;
loadingText: React.ReactNode;
filters: Filter[];
filterStatus?: AlertStatus;
getRowRenderer?: ({
data,
rowRenderers,
}: {
data: Ecs;
rowRenderers: RowRenderer[];
}) => RowRenderer | null;
hasAlertsCrudPermissions: ({
ruleConsumer,
ruleProducer,
}: {
ruleConsumer: string;
ruleProducer?: string;
}) => boolean;
height?: number;
indexNames: string[];
itemsPerPage?: number;
itemsPerPageOptions: number[];
query: Query;
onRuleChange?: () => void;
onStateChange?: (state: State) => void;
renderCellValue: (props: CellValueElementProps) => React.ReactNode;
rowRenderers: RowRenderer[];
runtimeMappings: MappingRuntimeFields;
setRefetch: (ref: () => void) => void;
start: string;
sort: SortColumnTable[];
graphEventId?: string;
leadingControlColumns: ControlColumnProps[];
trailingControlColumns: ControlColumnProps[];
bulkActions?: BulkActionsProp;
data?: DataPublicPluginStart;
unit?: (total: number) => React.ReactNode;
showCheckboxes?: boolean;
queryFields?: string[];
}
const TGridStandaloneComponent: React.FC<TGridStandaloneProps> = ({
columns,
dataViewId = null,
defaultCellActions,
deletedEventIds,
disabledCellActions,
end,
entityType = 'alerts',
loadingText,
filters,
filterStatus,
getRowRenderer,
hasAlertsCrudPermissions,
indexNames,
itemsPerPage,
itemsPerPageOptions,
onRuleChange,
query,
renderCellValue,
rowRenderers,
runtimeMappings,
setRefetch,
start,
sort,
graphEventId,
leadingControlColumns,
trailingControlColumns,
data,
unit,
showCheckboxes = true,
bulkActions = {},
queryFields = [],
}) => {
const dispatch = useDispatch();
const columnsHeader = isEmpty(columns) ? defaultHeaders : columns;
const { uiSettings } = useKibana<CoreStart>().services;
const [indexPatternsLoading, { browserFields, indexPatterns }] = useFetchIndex(indexNames);
const getTGrid = useMemo(() => tGridSelectors.getTGridByIdSelector(), []);
const {
itemsPerPage: itemsPerPageStore,
itemsPerPageOptions: itemsPerPageOptionsStore,
queryFields: queryFieldsFromState,
sort: sortStore,
title,
} = useDeepEqualSelector((state) => getTGrid(state, STANDALONE_ID ?? ''));
const justTitle = useMemo(() => <TitleText data-test-subj="title">{title}</TitleText>, [title]);
const esQueryConfig = getEsQueryConfig(uiSettings);
const filterQuery = useMemo(
() =>
getCombinedFilterQuery({
config: esQueryConfig,
browserFields,
dataProviders: EMPTY_DATA_PROVIDERS,
filters,
from: start,
indexPattern: indexPatterns,
kqlMode: 'search',
kqlQuery: query,
to: end,
}),
[esQueryConfig, indexPatterns, browserFields, filters, start, end, query]
);
const canQueryTimeline = useMemo(
() =>
filterQuery != null &&
indexPatternsLoading != null &&
!indexPatternsLoading &&
!isEmpty(start) &&
!isEmpty(end),
[indexPatternsLoading, filterQuery, start, end]
);
const fields = useMemo(
() => [
...columnsHeader.reduce<string[]>(
(acc, c) => (c.linkField != null ? [...acc, c.id, c.linkField] : [...acc, c.id]),
[]
),
...(queryFieldsFromState ?? []),
],
[columnsHeader, queryFieldsFromState]
);
const sortField = useMemo(
() =>
sortStore.map(({ columnId, columnType, esTypes, sortDirection }) => ({
field: columnId,
type: columnType,
direction: sortDirection as Direction,
esTypes: esTypes ?? [],
})),
[sortStore]
);
const [
loading,
{ consumers, events, updatedAt, loadPage, pageInfo, refetch, totalCount = 0, inspect },
] = useTimelineEvents({
dataViewId,
entityType,
excludeEcsData: true,
fields,
filterQuery,
id: STANDALONE_ID,
indexNames,
limit: itemsPerPageStore,
runtimeMappings,
sort: sortField,
startDate: start,
endDate: end,
skip: !canQueryTimeline,
data,
});
setRefetch(refetch);
useEffect(() => {
dispatch(tGridActions.updateIsLoading({ id: STANDALONE_ID, isLoading: loading }));
}, [dispatch, loading]);
const { hasAlertsCrud, totalSelectAllAlerts } = useMemo(() => {
return Object.entries(consumers).reduce<{
hasAlertsCrud: boolean;
totalSelectAllAlerts: number;
}>(
(acc, [ruleConsumer, nbrAlerts]) => {
const featureHasPermission = hasAlertsCrudPermissions({ ruleConsumer });
return {
hasAlertsCrud: featureHasPermission || acc.hasAlertsCrud,
totalSelectAllAlerts: featureHasPermission
? nbrAlerts + acc.totalSelectAllAlerts
: acc.totalSelectAllAlerts,
};
},
{
hasAlertsCrud: false,
totalSelectAllAlerts: 0,
}
);
}, [consumers, hasAlertsCrudPermissions]);
const totalCountMinusDeleted = useMemo(
() => (totalCount > 0 ? totalCount - deletedEventIds.length : 0),
[deletedEventIds.length, totalCount]
);
const hasAlerts = totalCountMinusDeleted > 0;
// Only show the table-spanning loading indicator when the query is loading and we
// don't have data (e.g. for the initial fetch).
// Subsequent fetches (e.g. for pagination) will show a small loading indicator on
// top of the table and the table will display the current page until the next page
// is fetched. This prevents a flicker when paginating.
const showFullLoading = loading && !hasAlerts;
const nonDeletedEvents = useMemo(
() => events.filter((e) => !deletedEventIds.includes(e._id)),
[deletedEventIds, events]
);
useEffect(() => {
dispatch(
tGridActions.createTGrid({
id: STANDALONE_ID,
columns,
indexNames,
itemsPerPage: itemsPerPage || itemsPerPageStore,
itemsPerPageOptions,
showCheckboxes,
defaultColumns: columns,
sort,
})
);
dispatch(
tGridActions.initializeTGridSettings({
id: STANDALONE_ID,
defaultColumns: columns,
sort,
loadingText,
unit,
queryFields,
})
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const tableContext = { tableId: STANDALONE_ID };
// Clear checkbox selection when new events are fetched
useEffect(() => {
dispatch(tGridActions.clearSelected({ id: STANDALONE_ID }));
dispatch(
tGridActions.setTGridSelectAll({
id: STANDALONE_ID,
selectAll: false,
})
);
}, [nonDeletedEvents, dispatch]);
return (
<InspectButtonContainer data-test-subj="events-viewer-panel">
<AlertsTableWrapper>
{showFullLoading && <TGridLoading />}
{canQueryTimeline ? (
<TableContext.Provider value={tableContext}>
<EventsContainerLoading
data-timeline-id={STANDALONE_ID}
data-test-subj={`events-container-loading-${loading}`}
>
<UpdatedFlexGroup gutterSize="s" justifyContent="flexEnd" alignItems="center">
<UpdatedFlexItem grow={false} $show={!loading}>
<InspectButton title={justTitle} inspect={inspect} loading={loading} />
</UpdatedFlexItem>
<UpdatedFlexItem grow={false} $show={!loading}>
<LastUpdatedAt updatedAt={updatedAt} />
</UpdatedFlexItem>
</UpdatedFlexGroup>
{!hasAlerts && !loading && <TGridEmpty />}
{hasAlerts && (
<FullWidthFlexGroup direction="row" $visible={!graphEventId} gutterSize="none">
<ScrollableFlexItem grow={1}>
<StatefulBody
activePage={pageInfo.activePage}
browserFields={browserFields}
data={nonDeletedEvents}
defaultCellActions={defaultCellActions}
disabledCellActions={disabledCellActions}
filterQuery={filterQuery}
getRowRenderer={getRowRenderer}
hasAlertsCrud={hasAlertsCrud}
hasAlertsCrudPermissions={hasAlertsCrudPermissions}
id={STANDALONE_ID}
indexNames={indexNames}
isEventViewer={true}
itemsPerPageOptions={itemsPerPageOptionsStore}
leadingControlColumns={leadingControlColumns}
loadPage={loadPage}
refetch={refetch}
renderCellValue={renderCellValue}
rowRenderers={rowRenderers}
onRuleChange={onRuleChange}
pageSize={itemsPerPageStore}
tabType={'query'}
tableView="gridView"
totalItems={totalCountMinusDeleted}
totalSelectAllAlerts={totalSelectAllAlerts}
unit={unit}
filterStatus={filterStatus}
trailingControlColumns={trailingControlColumns}
showCheckboxes={showCheckboxes}
bulkActions={bulkActions}
/>
</ScrollableFlexItem>
</FullWidthFlexGroup>
)}
</EventsContainerLoading>
</TableContext.Provider>
) : null}
</AlertsTableWrapper>
</InspectButtonContainer>
);
};
export const TGridStandalone = React.memo(TGridStandaloneComponent);

View file

@ -127,7 +127,6 @@ export const AlertBulkActionsComponent = React.memo<StatefulAlertBulkActionsProp
onUpdateSuccess,
onUpdateFailure,
customBulkActions,
scopeId: id,
});
return (

View file

@ -12,7 +12,6 @@ import * as i18n from '../components/t_grid/translations';
import type { AlertStatus, BulkActionsProps } from '../../common/types/timeline';
import { useUpdateAlertsStatus } from '../container/use_update_alerts';
import { useAppToasts } from './use_app_toasts';
import { STANDALONE_ID } from '../components/t_grid/standalone';
import { useStartTransaction } from '../lib/apm/use_start_transaction';
import { APM_USER_INTERACTIONS } from '../lib/apm/constants';
@ -31,9 +30,8 @@ export const useBulkActionItems = ({
onUpdateSuccess,
onUpdateFailure,
customBulkActions,
scopeId,
}: BulkActionsProps) => {
const { updateAlertStatus } = useUpdateAlertsStatus(scopeId !== STANDALONE_ID);
const { updateAlertStatus } = useUpdateAlertsStatus(true);
const { addSuccess, addError, addWarning } = useAppToasts();
const { startTransaction } = useStartTransaction();

View file

@ -49,7 +49,7 @@ export const getTGridLazy = (
) => {
initializeStore({ store, storage, setStore });
return (
<Suspense fallback={<TGridLoading height={props.type === 'standalone' ? 'tall' : 'short'} />}>
<Suspense fallback={<TGridLoading height={'short'} />}>
<TimelineLazy {...props} store={store} storage={storage} data={data} setStore={setStore} />
</Suspense>
);

View file

@ -6,8 +6,6 @@
*/
import { Store, Unsubscribe } from 'redux';
import { throttle } from 'lodash';
import { Storage } from '@kbn/kibana-utils-plugin/public';
import type { CoreSetup, Plugin, CoreStart } from '@kbn/core/public';
import type { LastUpdatedAtProps, LoadingPanelProps } from './components';
@ -40,20 +38,6 @@ export class TimelinesPlugin implements Plugin<void, TimelinesUIStart> {
}
},
getTGrid: (props: TGridProps) => {
if (props.type === 'standalone' && this._store) {
const { getState } = this._store;
const state = getState();
if (state && state.app) {
this._store = undefined;
} else {
if (props.onStateChange) {
this._storeUnsubscribe = this._store.subscribe(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
throttle(() => props.onStateChange!(getState()), 500)
);
}
}
}
return getTGridLazy(props, {
store: this._store,
storage: this._storage,

View file

@ -21,7 +21,6 @@ import type {
} from './components';
export type { SortDirection } from '../common/types';
import type { TGridIntegratedProps } from './components/t_grid/integrated';
import type { TGridStandaloneProps } from './components/t_grid/standalone';
import type { UseAddToTimelineProps, UseAddToTimeline } from './hooks/use_add_to_timeline';
import { HoverActionsConfig } from './components/hover_actions';
export * from './store/t_grid';
@ -52,19 +51,14 @@ export interface TimelinesStartPlugins {
}
export type TimelinesStartServices = CoreStart & TimelinesStartPlugins;
interface TGridStandaloneCompProps extends TGridStandaloneProps {
type: 'standalone';
}
interface TGridIntegratedCompProps extends TGridIntegratedProps {
type: 'embedded';
}
export type TGridType = 'standalone' | 'embedded';
export type GetTGridProps<T extends TGridType> = T extends 'standalone'
? TGridStandaloneCompProps
: T extends 'embedded'
export type TGridType = 'embedded';
export type GetTGridProps<T extends TGridType> = T extends 'embedded'
? TGridIntegratedCompProps
: TGridIntegratedCompProps;
export type TGridProps = TGridStandaloneCompProps | TGridIntegratedCompProps;
export type TGridProps = TGridIntegratedCompProps;
export interface StatefulEventContextType {
tabType: string | undefined;

View file

@ -23587,7 +23587,6 @@
"xpack.observability.formatters.secondsTimeUnitLabel": "s",
"xpack.observability.formatters.secondsTimeUnitLabelExtended": "secondes",
"xpack.observability.home.addData": "Ajouter des intégrations",
"xpack.observability.hoverActions.filterForValue": "Filtrer sur la valeur",
"xpack.observability.hoverActions.filterForValueButtonLabel": "Inclure",
"xpack.observability.inspector.stats.dataViewDescription": "La vue de données qui se connecte aux index Elasticsearch.",
"xpack.observability.inspector.stats.dataViewLabel": "Vue de données",

View file

@ -23566,7 +23566,6 @@
"xpack.observability.formatters.secondsTimeUnitLabel": "s",
"xpack.observability.formatters.secondsTimeUnitLabelExtended": "秒",
"xpack.observability.home.addData": "統合の追加",
"xpack.observability.hoverActions.filterForValue": "値でフィルター",
"xpack.observability.hoverActions.filterForValueButtonLabel": "フィルタリング",
"xpack.observability.inspector.stats.dataViewDescription": "Elasticsearchインデックスに接続したデータビューです。",
"xpack.observability.inspector.stats.dataViewLabel": "データビュー",

View file

@ -23597,7 +23597,6 @@
"xpack.observability.formatters.secondsTimeUnitLabel": "s",
"xpack.observability.formatters.secondsTimeUnitLabelExtended": "秒",
"xpack.observability.home.addData": "添加集成",
"xpack.observability.hoverActions.filterForValue": "筛留值",
"xpack.observability.hoverActions.filterForValueButtonLabel": "筛选范围",
"xpack.observability.inspector.stats.dataViewDescription": "连接到 Elasticsearch 索引的数据视图。",
"xpack.observability.inspector.stats.dataViewLabel": "数据视图",

View file

@ -231,15 +231,6 @@ export default ({ getService }: FtrProviderContext) => {
});
});
/*
* ATTENTION FUTURE DEVELOPER
*
* These tests should only be valid for 7.17.x
* You can run this test if you go to this file:
* x-pack/plugins/observability/public/pages/alerts/containers/alerts_table_t_grid/alerts_table_t_grid.tsx
* and at line 397 and change showCheckboxes to true
*
*/
describe.skip('Bulk Actions', () => {
before(async () => {
await security.testUser.setRoles(['global_alerts_logs_all_else_read']);

View file

@ -31,7 +31,6 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
testFiles: [
resolve(__dirname, './test_suites/resolver'),
resolve(__dirname, './test_suites/global_search'),
resolve(__dirname, './test_suites/timelines'),
],
services,
@ -62,9 +61,6 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
resolverTest: {
pathname: '/app/resolverTest',
},
timelineTest: {
pathname: '/app/timelinesTest',
},
},
// choose where screenshots should be saved

View file

@ -1,11 +0,0 @@
{
"id": "timelinesTest",
"owner": { "name": "Security solution", "githubTeam": "security-solution" },
"version": "1.0.0",
"kibanaVersion": "kibana",
"configPath": ["xpack", "timelinesTest"],
"requiredPlugins": ["timelines", "data"],
"requiredBundles": ["kibanaReact"],
"server": false,
"ui": true
}

View file

@ -1,100 +0,0 @@
/*
* 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 { Router } from 'react-router-dom';
import React, { useCallback, useRef } from 'react';
import ReactDOM from 'react-dom';
import { AppMountParameters, CoreStart } from '@kbn/core/public';
import { I18nProvider } from '@kbn/i18n-react';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common';
import { TimelinesUIStart } from '@kbn/timelines-plugin/public';
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
type CoreStartTimelines = CoreStart & { data: DataPublicPluginStart };
/**
* Render the Timeline Test app. Returns a cleanup function.
*/
export function renderApp(
coreStart: CoreStartTimelines,
parameters: AppMountParameters,
timelinesPluginSetup: TimelinesUIStart | null
) {
ReactDOM.render(
<AppRoot
coreStart={coreStart}
parameters={parameters}
timelinesPluginSetup={timelinesPluginSetup}
/>,
parameters.element
);
return () => {
ReactDOM.unmountComponentAtNode(parameters.element);
};
}
const AppRoot = React.memo(
({
coreStart,
parameters,
timelinesPluginSetup,
}: {
coreStart: CoreStartTimelines;
parameters: AppMountParameters;
timelinesPluginSetup: TimelinesUIStart | null;
}) => {
const refetch = useRef();
const setRefetch = useCallback((_refetch) => {
refetch.current = _refetch;
}, []);
const hasAlertsCrudPermissions = useCallback(() => true, []);
return (
<I18nProvider>
<Router history={parameters.history}>
<KibanaContextProvider services={coreStart}>
<EuiThemeProvider>
{(timelinesPluginSetup &&
timelinesPluginSetup.getTGrid &&
timelinesPluginSetup.getTGrid<'standalone'>({
type: 'standalone',
columns: [],
indexNames: [],
deletedEventIds: [],
disabledCellActions: [],
end: '',
filters: [],
hasAlertsCrudPermissions,
itemsPerPageOptions: [1, 2, 3],
loadingText: 'Loading events',
renderCellValue: () => <div data-test-subj="timeline-wrapper">test</div>,
sort: [],
leadingControlColumns: [],
trailingControlColumns: [],
query: {
query: '',
language: 'kuery',
},
setRefetch,
start: '',
rowRenderers: [],
runtimeMappings: {},
filterStatus: 'open',
unit: (n: number) => `${n}`,
})) ??
null}
</EuiThemeProvider>
</KibanaContextProvider>
</Router>
</I18nProvider>
);
}
);

View file

@ -1,20 +0,0 @@
/*
* 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 { PluginInitializer } from '@kbn/core/public';
import {
TimelinesTestPlugin,
TimelinesTestPluginSetupDependencies,
TimelinesTestPluginStartDependencies,
} from './plugin';
export const plugin: PluginInitializer<
void,
void,
TimelinesTestPluginSetupDependencies,
TimelinesTestPluginStartDependencies
> = () => new TimelinesTestPlugin();

View file

@ -1,54 +0,0 @@
/*
* 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 { Plugin, CoreStart, CoreSetup, AppMountParameters } from '@kbn/core/public';
import { i18n } from '@kbn/i18n';
import { TimelinesUIStart } from '@kbn/timelines-plugin/public';
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { renderApp } from './applications/timelines_test';
export type TimelinesTestPluginSetup = void;
export type TimelinesTestPluginStart = void;
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface TimelinesTestPluginSetupDependencies {}
export interface TimelinesTestPluginStartDependencies {
timelines: TimelinesUIStart;
data: DataPublicPluginStart;
}
export class TimelinesTestPlugin
implements
Plugin<
TimelinesTestPluginSetup,
void,
TimelinesTestPluginSetupDependencies,
TimelinesTestPluginStartDependencies
>
{
private timelinesPlugin: TimelinesUIStart | null = null;
public setup(
core: CoreSetup<TimelinesTestPluginStartDependencies, TimelinesTestPluginStart>,
setupDependencies: TimelinesTestPluginSetupDependencies
) {
core.application.register({
id: 'timelinesTest',
title: i18n.translate('xpack.timelinesTest.pluginTitle', {
defaultMessage: 'Timelines Test',
}),
mount: async (params: AppMountParameters<unknown>) => {
const startServices = await core.getStartServices();
const [coreStart, { data }] = startServices;
return renderApp({ ...coreStart, data }, params, this.timelinesPlugin);
},
});
}
public start(core: CoreStart, { timelines }: TimelinesTestPluginStartDependencies) {
this.timelinesPlugin = timelines;
}
}

View file

@ -1,24 +0,0 @@
/*
* 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 { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ getPageObjects, getService }: FtrProviderContext) {
describe('Timelines plugin API', function () {
const pageObjects = getPageObjects(['common']);
const testSubjects = getService('testSubjects');
describe('timelines plugin rendering', function () {
before(async () => {
await pageObjects.common.navigateToApp('timelineTest');
});
it('shows the timeline component on navigation', async () => {
await testSubjects.existOrFail('events-viewer-panel');
});
});
});
}