mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
137988 browser fields UI (#140516)
* first commit * first commit * get auth index and try field caps * use esClient * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' * wait for promise to finish * logs for debugging * format field capabilities * add simplier browserFields mapper * update response and remove width * update response * refactor * types and refactor * update api response * fix column ids * add columns toggle and reset * sort visible columns id on toggle on * merging info * call api * load info on browser field loaded * remove logs * add useColumns hook * remove browser fields dependency * update fn name * update types * update imported type package * update mock object * error message for no o11y alert indices * add endpoint integration test * activate commented tests * add unit test * comment uncommented tests * fix tests * review by Xavier * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' * remove unnecessary api calls * update types * update param names + right type * update types * update id and index to be same type as rest * update timelines id and index format * add schema update on load * add functional test * fix tests * reactivate skipped test * update row action types to work with new api * rollback basic fields as array update o11y render cell fn * update cell render fn to handle strings too * update column recovery on update * recover previous deleted column stats * add browser fields error handling * add toast on error and avoid calling field api when in siem * remove spread operator * add toast mock * update render cell cb when value is an object * remove not needed prop * fix browser field types * fix reset fields action * add missing hook dependency * [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' * fix browser field modal types * update browser field types * update render cell * update export type * fix default columns * remove description column in browser field modal * fix populate default fields on reset * delete description field in triggers_actions_ui * avoid to refetch the data because all the data is already there * remove description tests * insert new column in first pos + minor fixes * update onToggleColumn callback to avoid innecesary calls Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Xavier Mouligneau <xavier.mouligneau@elastic.co>
This commit is contained in:
parent
0af26e2ab9
commit
0f7cfd16f7
35 changed files with 571 additions and 136 deletions
|
@ -142,7 +142,12 @@ export class TriggersActionsUiExamplePlugin
|
|||
useInternalFlyout,
|
||||
getRenderCellValue: () => (props: any) => {
|
||||
const value = props.data.find((d: any) => d.field === props.columnId)?.value ?? [];
|
||||
return <>{value.length ? value.join() : '--'}</>;
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return <>{value.length ? value.join() : '--'}</>;
|
||||
}
|
||||
|
||||
return <>{value}</>;
|
||||
},
|
||||
sort,
|
||||
};
|
||||
|
|
|
@ -427,6 +427,11 @@ describe('CaseViewPage', () => {
|
|||
}),
|
||||
},
|
||||
},
|
||||
notifications: {
|
||||
toasts: {
|
||||
addDanger: () => {},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
TIMESTAMP,
|
||||
} from '@kbn/rule-data-utils';
|
||||
import type { CellValueElementProps, TimelineNonEcsData } from '@kbn/timelines-plugin/common';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { AlertStatusIndicator } from '../../../../components/shared/alert_status_indicator';
|
||||
import { TimestampTooltip } from '../../../../components/shared/timestamp_tooltip';
|
||||
import { asDuration } from '../../../../../common/utils/formatters';
|
||||
|
@ -38,6 +39,20 @@ export const getMappedNonEcsValue = ({
|
|||
return undefined;
|
||||
};
|
||||
|
||||
const getRenderValue = (mappedNonEcsValue: any) => {
|
||||
// can be updated when working on https://github.com/elastic/kibana/issues/140819
|
||||
const value = Array.isArray(mappedNonEcsValue) ? mappedNonEcsValue.join() : mappedNonEcsValue;
|
||||
|
||||
if (!isEmpty(value)) {
|
||||
if (typeof value === 'object') {
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
return '—';
|
||||
};
|
||||
|
||||
/**
|
||||
* This implementation of `EuiDataGrid`'s `renderCellValue`
|
||||
* accepts `EuiDataGridCellValueElementProps`, plus `data`
|
||||
|
@ -53,10 +68,12 @@ export const getRenderCellValue = ({
|
|||
}) => {
|
||||
return ({ columnId, data }: CellValueElementProps) => {
|
||||
if (!data) return null;
|
||||
const value = getMappedNonEcsValue({
|
||||
const mappedNonEcsValue = getMappedNonEcsValue({
|
||||
data,
|
||||
fieldName: columnId,
|
||||
})?.reduce((x) => x[0]);
|
||||
});
|
||||
|
||||
const value = getRenderValue(mappedNonEcsValue);
|
||||
|
||||
switch (columnId) {
|
||||
case ALERT_STATUS:
|
||||
|
|
|
@ -11,3 +11,4 @@ export type {
|
|||
RuleRegistrySearchRequestPagination,
|
||||
} from './search_strategy';
|
||||
export { BASE_RAC_ALERTS_API_PATH } from './constants';
|
||||
export type { BrowserFields, BrowserField } from './types';
|
||||
|
|
|
@ -9,6 +9,9 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
|||
|
||||
import * as t from 'io-ts';
|
||||
|
||||
import type { IFieldSubType } from '@kbn/es-query';
|
||||
import type { RuntimeField } from '@kbn/data-views-plugin/common';
|
||||
|
||||
// note: these schemas are not exhaustive. See the `Sort` type of `@elastic/elasticsearch` if you need to enhance it.
|
||||
const fieldSchema = t.string;
|
||||
export const sortOrderSchema = t.union([t.literal('asc'), t.literal('desc'), t.literal('_doc')]);
|
||||
|
@ -302,3 +305,21 @@ export interface ClusterPutComponentTemplateBody {
|
|||
mappings: estypes.MappingTypeMapping;
|
||||
};
|
||||
}
|
||||
|
||||
export interface BrowserField {
|
||||
aggregatable: boolean;
|
||||
category: string;
|
||||
description?: string | null;
|
||||
example?: string | number | null;
|
||||
fields: Readonly<Record<string, Partial<BrowserField>>>;
|
||||
format?: string;
|
||||
indexes: string[];
|
||||
name: string;
|
||||
searchable: boolean;
|
||||
type: string;
|
||||
subType?: IFieldSubType;
|
||||
readFromDocValues: boolean;
|
||||
runtimeField?: RuntimeField;
|
||||
}
|
||||
|
||||
export type BrowserFields = Record<string, Partial<BrowserField>>;
|
||||
|
|
|
@ -31,6 +31,7 @@ import {
|
|||
import { Logger, ElasticsearchClient, EcsEventOutcome } from '@kbn/core/server';
|
||||
import { AuditLogger } from '@kbn/security-plugin/server';
|
||||
import { IndexPatternsFetcher } from '@kbn/data-plugin/server';
|
||||
import { BrowserFields } from '../../common';
|
||||
import { alertAuditEvent, operationAlertAuditActionMap } from './audit_events';
|
||||
import {
|
||||
ALERT_WORKFLOW_STATUS,
|
||||
|
@ -42,7 +43,6 @@ import { ParsedTechnicalFields } from '../../common/parse_technical_fields';
|
|||
import { Dataset, IRuleDataService } from '../rule_data_plugin_service';
|
||||
import { getAuthzFilter, getSpacesFilter } from '../lib';
|
||||
import { fieldDescriptorToBrowserFieldMapper } from './browser_fields';
|
||||
import { BrowserFields } from '../types';
|
||||
|
||||
// TODO: Fix typings https://github.com/elastic/kibana/issues/101776
|
||||
type NonNullableProps<Obj extends {}, Props extends keyof Obj> = Omit<Obj, Props> & {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { FieldDescriptor } from '@kbn/data-views-plugin/server';
|
||||
import { BrowserField, BrowserFields } from '../../types';
|
||||
import { BrowserFields, BrowserField } from '../../../common';
|
||||
|
||||
const getFieldCategory = (fieldCapability: FieldDescriptor) => {
|
||||
const name = fieldCapability.name.split('.');
|
||||
|
@ -21,7 +21,7 @@ const getFieldCategory = (fieldCapability: FieldDescriptor) => {
|
|||
const browserFieldFactory = (
|
||||
fieldCapability: FieldDescriptor,
|
||||
category: string
|
||||
): { [fieldName in string]: BrowserField } => {
|
||||
): Readonly<Record<string, Partial<BrowserField>>> => {
|
||||
return {
|
||||
[fieldCapability.name]: {
|
||||
...fieldCapability,
|
||||
|
|
|
@ -9,6 +9,7 @@ import { IRouter } from '@kbn/core/server';
|
|||
import { transformError } from '@kbn/securitysolution-es-utils';
|
||||
import * as t from 'io-ts';
|
||||
|
||||
import { BrowserFields } from '../../common';
|
||||
import { RacRequestHandlerContext } from '../types';
|
||||
import { BASE_RAC_ALERTS_API_PATH } from '../../common/constants';
|
||||
import { buildRouteValidation } from './utils/route_validation';
|
||||
|
@ -50,7 +51,7 @@ export const getBrowserFieldsByFeatureId = (router: IRouter<RacRequestHandlerCon
|
|||
});
|
||||
}
|
||||
|
||||
const browserFields = await alertsClient.getBrowserFields({
|
||||
const browserFields: BrowserFields = await alertsClient.getBrowserFields({
|
||||
indices: o11yIndices,
|
||||
metaFields: ['_id', '_index'],
|
||||
allowNoIndex: true,
|
||||
|
|
|
@ -13,7 +13,6 @@ import {
|
|||
RuleTypeState,
|
||||
} from '@kbn/alerting-plugin/common';
|
||||
import { RuleExecutorOptions, RuleExecutorServices, RuleType } from '@kbn/alerting-plugin/server';
|
||||
import { FieldSpec } from '@kbn/data-plugin/common';
|
||||
import { AlertsClient } from './alert_data_client/alerts_client';
|
||||
|
||||
type SimpleAlertType<
|
||||
|
@ -72,11 +71,3 @@ export interface RacApiRequestHandlerContext {
|
|||
export type RacRequestHandlerContext = CustomRequestHandlerContext<{
|
||||
rac: RacApiRequestHandlerContext;
|
||||
}>;
|
||||
|
||||
export type BrowserField = FieldSpec & {
|
||||
category: string;
|
||||
};
|
||||
|
||||
export type BrowserFields = {
|
||||
[category in string]: { fields: { [fieldName in string]: BrowserField } };
|
||||
};
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { BrowserField } from '@kbn/triggers-actions-ui-plugin/public/application/sections/field_browser/types';
|
||||
import { BrowserField } from '@kbn/rule-registry-plugin/common';
|
||||
import { VFC } from 'react';
|
||||
import { useKibana } from '../../../../hooks/use_kibana';
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import React from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { EuiDataGridColumn, EuiText } from '@elastic/eui';
|
||||
import { BrowserField } from '@kbn/triggers-actions-ui-plugin/public/application/sections/field_browser/types';
|
||||
import { BrowserField } from '@kbn/rule-registry-plugin/common';
|
||||
import { IndicatorsFieldBrowser } from '../../indicators_field_browser';
|
||||
|
||||
export const useToolbarOptions = ({
|
||||
|
|
|
@ -16,8 +16,8 @@ import {
|
|||
import { Storage } from '@kbn/kibana-utils-plugin/public';
|
||||
import { TimelinesUIStart } from '@kbn/timelines-plugin/public';
|
||||
import type { TriggersAndActionsUIPublicPluginStart as TriggersActionsStart } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { BrowserField } from '@kbn/triggers-actions-ui-plugin/public/application/sections/field_browser/types';
|
||||
import { DataViewBase } from '@kbn/es-query';
|
||||
import { BrowserField } from '@kbn/rule-registry-plugin/common';
|
||||
import { Store } from 'redux';
|
||||
import { DataProvider } from '@kbn/timelines-plugin/common';
|
||||
|
||||
|
|
|
@ -92,6 +92,11 @@ describe('AlertsTable', () => {
|
|||
visibleColumns: columns.map((c) => c.id),
|
||||
'data-test-subj': 'testTable',
|
||||
updatedAt: Date.now(),
|
||||
onToggleColumn: () => {},
|
||||
onResetColumns: () => {},
|
||||
onColumnsChange: () => {},
|
||||
onChangeVisibleColumns: () => {},
|
||||
browserFields: {},
|
||||
};
|
||||
|
||||
const AlertsTableWithLocale: React.FunctionComponent<AlertsTableProps> = (props) => (
|
||||
|
|
|
@ -44,7 +44,6 @@ const AlertsTable: React.FunctionComponent<AlertsTableProps> = (props: AlertsTab
|
|||
alerts,
|
||||
alertsCount,
|
||||
isLoading,
|
||||
onColumnsChange,
|
||||
onPageChange,
|
||||
onSortChange,
|
||||
sort: sortingFields,
|
||||
|
@ -66,18 +65,6 @@ const AlertsTable: React.FunctionComponent<AlertsTableProps> = (props: AlertsTab
|
|||
useBulkActionsConfig: props.alertsTableConfiguration.useBulkActions,
|
||||
});
|
||||
|
||||
const toolbarVisibility = useCallback(() => {
|
||||
const { rowSelection } = bulkActionsState;
|
||||
return getToolbarVisibility({
|
||||
bulkActions,
|
||||
alertsCount,
|
||||
rowSelection,
|
||||
alerts: alertsData.alerts,
|
||||
updatedAt: props.updatedAt,
|
||||
isLoading,
|
||||
});
|
||||
}, [bulkActionsState, bulkActions, alertsCount, alertsData.alerts, props.updatedAt, isLoading])();
|
||||
|
||||
const {
|
||||
pagination,
|
||||
onChangePageSize,
|
||||
|
@ -91,7 +78,14 @@ const AlertsTable: React.FunctionComponent<AlertsTableProps> = (props: AlertsTab
|
|||
pageSize: props.pageSize,
|
||||
});
|
||||
|
||||
const [visibleColumns, setVisibleColumns] = useState(props.visibleColumns);
|
||||
const {
|
||||
visibleColumns,
|
||||
onToggleColumn,
|
||||
onResetColumns,
|
||||
updatedAt,
|
||||
browserFields,
|
||||
onChangeVisibleColumns,
|
||||
} = props;
|
||||
|
||||
// TODO when every solution is using this table, we will be able to simplify it by just passing the alert index
|
||||
const handleFlyoutAlert = useCallback(
|
||||
|
@ -104,16 +98,32 @@ const AlertsTable: React.FunctionComponent<AlertsTableProps> = (props: AlertsTab
|
|||
[alerts, setFlyoutAlertIndex]
|
||||
);
|
||||
|
||||
const onChangeVisibleColumns = useCallback(
|
||||
(newColumns: string[]) => {
|
||||
setVisibleColumns(newColumns);
|
||||
onColumnsChange(
|
||||
props.columns.sort((a, b) => newColumns.indexOf(a.id) - newColumns.indexOf(b.id)),
|
||||
newColumns
|
||||
);
|
||||
},
|
||||
[onColumnsChange, props.columns]
|
||||
);
|
||||
const toolbarVisibility = useCallback(() => {
|
||||
const { rowSelection } = bulkActionsState;
|
||||
return getToolbarVisibility({
|
||||
bulkActions,
|
||||
alertsCount,
|
||||
rowSelection,
|
||||
alerts: alertsData.alerts,
|
||||
updatedAt,
|
||||
isLoading,
|
||||
columnIds: visibleColumns,
|
||||
onToggleColumn,
|
||||
onResetColumns,
|
||||
browserFields,
|
||||
});
|
||||
}, [
|
||||
bulkActionsState,
|
||||
bulkActions,
|
||||
alertsCount,
|
||||
alertsData.alerts,
|
||||
updatedAt,
|
||||
browserFields,
|
||||
isLoading,
|
||||
visibleColumns,
|
||||
onToggleColumn,
|
||||
onResetColumns,
|
||||
])();
|
||||
|
||||
const leadingControlColumns = useMemo(() => {
|
||||
const isActionButtonsColumnActive =
|
||||
|
@ -203,7 +213,10 @@ const AlertsTable: React.FunctionComponent<AlertsTableProps> = (props: AlertsTab
|
|||
columnId: string;
|
||||
}) => {
|
||||
const value = data.find((d) => d.field === columnId)?.value ?? [];
|
||||
return <>{value.length ? value.join() : '--'}</>;
|
||||
if (Array.isArray(value)) {
|
||||
return <>{value.length ? value.join() : '--'}</>;
|
||||
}
|
||||
return <>{value}</>;
|
||||
};
|
||||
|
||||
const renderCellValue = useCallback(
|
||||
|
|
|
@ -21,10 +21,12 @@ import { PLUGIN_ID } from '../../../common/constants';
|
|||
import { TypeRegistry } from '../../type_registry';
|
||||
import AlertsTableState, { AlertsTableStateProps } from './alerts_table_state';
|
||||
import { useFetchAlerts } from './hooks/use_fetch_alerts';
|
||||
import { useFetchBrowserFieldCapabilities } from './hooks/use_fetch_browser_fields_capabilities';
|
||||
import { DefaultSort } from './hooks';
|
||||
import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
|
||||
|
||||
jest.mock('./hooks/use_fetch_alerts');
|
||||
jest.mock('./hooks/use_fetch_browser_fields_capabilities');
|
||||
jest.mock('@kbn/kibana-utils-plugin/public');
|
||||
jest.mock('@kbn/kibana-react-plugin/public', () => ({
|
||||
useKibana: () => ({
|
||||
|
@ -55,6 +57,11 @@ jest.mock('@kbn/kibana-react-plugin/public', () => ({
|
|||
}),
|
||||
},
|
||||
},
|
||||
notifications: {
|
||||
toasts: {
|
||||
addDanger: () => {},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
@ -137,6 +144,9 @@ hookUseFetchAlerts.mockImplementation(() => [
|
|||
},
|
||||
]);
|
||||
|
||||
const hookUseFetchBrowserFieldCapabilities = useFetchBrowserFieldCapabilities as jest.Mock;
|
||||
hookUseFetchBrowserFieldCapabilities.mockImplementation(() => [false, {}]);
|
||||
|
||||
const AlertsTableWithLocale: React.FunctionComponent<AlertsTableStateProps> = (props) => (
|
||||
<IntlProvider locale="en">
|
||||
<AlertsTableState {...props} />
|
||||
|
|
|
@ -35,6 +35,7 @@ import { ALERTS_TABLE_CONF_ERROR_MESSAGE, ALERTS_TABLE_CONF_ERROR_TITLE } from '
|
|||
import { TypeRegistry } from '../../type_registry';
|
||||
import { bulkActionsReducer } from './bulk_actions/reducer';
|
||||
import { useGetUserCasesPermissions } from './hooks/use_get_user_cases_permissions';
|
||||
import { useColumns } from './hooks/use_columns';
|
||||
|
||||
const DefaultPagination = {
|
||||
pageSize: 10,
|
||||
|
@ -59,7 +60,7 @@ export interface AlertsTableStateProps {
|
|||
showExpandToDetails: boolean;
|
||||
}
|
||||
|
||||
interface AlertsTableStorage {
|
||||
export interface AlertsTableStorage {
|
||||
columns: EuiDataGridColumn[];
|
||||
visibleColumns?: string[];
|
||||
sort: SortCombinations[];
|
||||
|
@ -93,6 +94,7 @@ const AlertsTableWithBulkActionsContextComponent: React.FunctionComponent<{
|
|||
);
|
||||
|
||||
const AlertsTableWithBulkActionsContext = React.memo(AlertsTableWithBulkActionsContextComponent);
|
||||
const EMPTY_FIELDS = [{ field: '*', include_unmapped: true }];
|
||||
|
||||
const AlertsTableState = ({
|
||||
alertsTableConfigurationRegistry,
|
||||
|
@ -106,6 +108,7 @@ const AlertsTableState = ({
|
|||
showExpandToDetails,
|
||||
}: AlertsTableStateProps) => {
|
||||
const { cases } = useKibana<{ cases: CaseUi }>().services;
|
||||
|
||||
const hasAlertsTableConfiguration =
|
||||
alertsTableConfigurationRegistry?.has(configurationId) ?? false;
|
||||
const alertsTableConfiguration = hasAlertsTableConfiguration
|
||||
|
@ -143,7 +146,23 @@ const AlertsTableState = ({
|
|||
...DefaultPagination,
|
||||
pageSize: pageSize ?? DefaultPagination.pageSize,
|
||||
});
|
||||
const [columns, setColumns] = useState<EuiDataGridColumn[]>(storageAlertsTable.current.columns);
|
||||
|
||||
const {
|
||||
columns,
|
||||
onColumnsChange,
|
||||
browserFields,
|
||||
isBrowserFieldDataLoading,
|
||||
onToggleColumn,
|
||||
onResetColumns,
|
||||
visibleColumns,
|
||||
onChangeVisibleColumns,
|
||||
} = useColumns({
|
||||
featureIds,
|
||||
storageAlertsTable,
|
||||
storage,
|
||||
id,
|
||||
defaultColumns: (alertsTableConfiguration && alertsTableConfiguration.columns) ?? [],
|
||||
});
|
||||
|
||||
const [
|
||||
isLoading,
|
||||
|
@ -156,7 +175,7 @@ const AlertsTableState = ({
|
|||
updatedAt,
|
||||
},
|
||||
] = useFetchAlerts({
|
||||
fields: columns.map((col) => ({ field: col.id, include_unmapped: true })),
|
||||
fields: EMPTY_FIELDS,
|
||||
featureIds,
|
||||
query,
|
||||
pagination,
|
||||
|
@ -194,18 +213,6 @@ const AlertsTableState = ({
|
|||
},
|
||||
[id]
|
||||
);
|
||||
const onColumnsChange = useCallback(
|
||||
(newColumns: EuiDataGridColumn[], visibleColumns: string[]) => {
|
||||
setColumns(newColumns);
|
||||
storageAlertsTable.current = {
|
||||
...storageAlertsTable.current,
|
||||
columns: newColumns,
|
||||
visibleColumns,
|
||||
};
|
||||
storage.current.set(id, storageAlertsTable.current);
|
||||
},
|
||||
[id, storage]
|
||||
);
|
||||
|
||||
const useFetchAlertsData = useCallback(() => {
|
||||
return {
|
||||
|
@ -215,7 +222,6 @@ const AlertsTableState = ({
|
|||
isInitializing,
|
||||
isLoading,
|
||||
getInspectQuery,
|
||||
onColumnsChange,
|
||||
onPageChange,
|
||||
onSortChange,
|
||||
refresh,
|
||||
|
@ -228,7 +234,6 @@ const AlertsTableState = ({
|
|||
getInspectQuery,
|
||||
isInitializing,
|
||||
isLoading,
|
||||
onColumnsChange,
|
||||
onPageChange,
|
||||
onSortChange,
|
||||
pagination.pageIndex,
|
||||
|
@ -252,9 +257,14 @@ const AlertsTableState = ({
|
|||
showExpandToDetails,
|
||||
trailingControlColumns: [],
|
||||
useFetchAlertsData,
|
||||
visibleColumns: storageAlertsTable.current.visibleColumns ?? [],
|
||||
visibleColumns,
|
||||
'data-test-subj': 'internalAlertsState',
|
||||
updatedAt,
|
||||
browserFields,
|
||||
onToggleColumn,
|
||||
onResetColumns,
|
||||
onColumnsChange,
|
||||
onChangeVisibleColumns,
|
||||
}),
|
||||
[
|
||||
alertsTableConfiguration,
|
||||
|
@ -264,7 +274,13 @@ const AlertsTableState = ({
|
|||
id,
|
||||
showExpandToDetails,
|
||||
useFetchAlertsData,
|
||||
visibleColumns,
|
||||
updatedAt,
|
||||
browserFields,
|
||||
onToggleColumn,
|
||||
onResetColumns,
|
||||
onColumnsChange,
|
||||
onChangeVisibleColumns,
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -281,7 +297,7 @@ const AlertsTableState = ({
|
|||
return hasAlertsTableConfiguration ? (
|
||||
<>
|
||||
{!isLoading && alertsCount === 0 && <EmptyState />}
|
||||
{isLoading && (
|
||||
{(isLoading || isBrowserFieldDataLoading) && (
|
||||
<EuiProgress size="xs" color="accent" data-test-subj="internalAlertsPageLoading" />
|
||||
)}
|
||||
{alertsCount !== 0 && CasesContext && cases && (
|
||||
|
|
|
@ -96,6 +96,11 @@ describe('AlertsTable.BulkActions', () => {
|
|||
visibleColumns: columns.map((c) => c.id),
|
||||
'data-test-subj': 'testTable',
|
||||
updatedAt: Date.now(),
|
||||
onToggleColumn: () => {},
|
||||
onResetColumns: () => {},
|
||||
onColumnsChange: () => {},
|
||||
onChangeVisibleColumns: () => {},
|
||||
browserFields: {},
|
||||
};
|
||||
|
||||
const tablePropsWithBulkActions = {
|
||||
|
|
|
@ -13,3 +13,10 @@ export const ERROR_FETCH_ALERTS = i18n.translate(
|
|||
defaultMessage: `An error has occurred on alerts search`,
|
||||
}
|
||||
);
|
||||
|
||||
export const ERROR_FETCH_BROWSER_FIELDS = i18n.translate(
|
||||
'xpack.triggersActionsUI.components.alertTable.useFetchBrowserFieldsCapabilities.errorMessageText',
|
||||
{
|
||||
defaultMessage: 'An error has occurred loading browser fields',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -0,0 +1,235 @@
|
|||
/*
|
||||
* 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 { EuiDataGridColumn } from '@elastic/eui';
|
||||
import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public';
|
||||
import { AlertConsumers } from '@kbn/rule-data-utils';
|
||||
import { BrowserField, BrowserFields } from '@kbn/rule-registry-plugin/common';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { AlertsTableStorage } from '../alerts_table_state';
|
||||
import { useFetchBrowserFieldCapabilities } from './use_fetch_browser_fields_capabilities';
|
||||
|
||||
interface UseColumnsArgs {
|
||||
featureIds: AlertConsumers[];
|
||||
storageAlertsTable: React.MutableRefObject<AlertsTableStorage>;
|
||||
storage: React.MutableRefObject<IStorageWrapper>;
|
||||
id: string;
|
||||
defaultColumns: EuiDataGridColumn[];
|
||||
}
|
||||
|
||||
const fieldTypeToDataGridColumnTypeMapper = (fieldType: string | undefined) => {
|
||||
if (fieldType === 'date') return 'datetime';
|
||||
if (fieldType === 'number') return 'numeric';
|
||||
if (fieldType === 'object') return 'json';
|
||||
return fieldType;
|
||||
};
|
||||
|
||||
/**
|
||||
* EUI Data Grid expects the columns to have a property 'schema' defined for proper sorting
|
||||
* this schema as its own types as can be check out in the docs so we add it here manually
|
||||
* https://eui.elastic.co/#/tabular-content/data-grid-schema-columns
|
||||
*/
|
||||
const euiColumnFactory = (
|
||||
column: EuiDataGridColumn,
|
||||
browserFields: BrowserFields
|
||||
): EuiDataGridColumn => {
|
||||
const browserFieldsProps = getBrowserFieldProps(column.id, browserFields);
|
||||
return {
|
||||
...column,
|
||||
schema: fieldTypeToDataGridColumnTypeMapper(browserFieldsProps.type),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Searches in browser fields object for a specific field
|
||||
*/
|
||||
const getBrowserFieldProps = (
|
||||
columnId: string,
|
||||
browserFields: BrowserFields
|
||||
): Partial<BrowserField> => {
|
||||
for (const [, categoryDescriptor] of Object.entries(browserFields)) {
|
||||
if (!categoryDescriptor.fields) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const [fieldName, fieldDescriptor] of Object.entries(categoryDescriptor.fields)) {
|
||||
if (fieldName === columnId) {
|
||||
return fieldDescriptor;
|
||||
}
|
||||
}
|
||||
}
|
||||
return { type: 'string' };
|
||||
};
|
||||
|
||||
/**
|
||||
* @param columns Columns to be considered in the alerts table
|
||||
* @param browserFields constant object with all field capabilities
|
||||
* @returns columns but with the info needed by the data grid to work as expected, e.g sorting
|
||||
*/
|
||||
const populateColumns = (
|
||||
columns: EuiDataGridColumn[],
|
||||
browserFields: BrowserFields
|
||||
): EuiDataGridColumn[] => {
|
||||
return columns.map((column: EuiDataGridColumn) => {
|
||||
return euiColumnFactory(column, browserFields);
|
||||
});
|
||||
};
|
||||
|
||||
const getColumnByColumnId = (columns: EuiDataGridColumn[], columnId: string) => {
|
||||
return columns.find(({ id }: { id: string }) => id === columnId);
|
||||
};
|
||||
|
||||
const persist = ({
|
||||
id,
|
||||
storageAlertsTable,
|
||||
columns,
|
||||
visibleColumns,
|
||||
storage,
|
||||
}: {
|
||||
id: string;
|
||||
storageAlertsTable: React.MutableRefObject<AlertsTableStorage>;
|
||||
storage: React.MutableRefObject<IStorageWrapper>;
|
||||
columns: EuiDataGridColumn[];
|
||||
visibleColumns: string[];
|
||||
}) => {
|
||||
storageAlertsTable.current = {
|
||||
...storageAlertsTable.current,
|
||||
columns,
|
||||
visibleColumns,
|
||||
};
|
||||
storage.current.set(id, storageAlertsTable.current);
|
||||
};
|
||||
|
||||
export const useColumns = ({
|
||||
featureIds,
|
||||
storageAlertsTable,
|
||||
storage,
|
||||
id,
|
||||
defaultColumns,
|
||||
}: UseColumnsArgs) => {
|
||||
const [isBrowserFieldDataLoading, browserFields] = useFetchBrowserFieldCapabilities({
|
||||
featureIds,
|
||||
});
|
||||
const [columns, setColumns] = useState<EuiDataGridColumn[]>(storageAlertsTable.current.columns);
|
||||
const [isColumnsPopulated, setColumnsPopulated] = useState<boolean>(false);
|
||||
const [visibleColumns, setVisibleColumns] = useState(
|
||||
storageAlertsTable.current.visibleColumns ?? []
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isBrowserFieldDataLoading !== false || isColumnsPopulated) return;
|
||||
|
||||
const populatedColumns = populateColumns(columns, browserFields);
|
||||
setColumnsPopulated(true);
|
||||
setColumns(populatedColumns);
|
||||
}, [browserFields, columns, isBrowserFieldDataLoading, isColumnsPopulated]);
|
||||
|
||||
const onColumnsChange = useCallback(
|
||||
(newColumns: EuiDataGridColumn[], newVisibleColumns: string[]) => {
|
||||
setColumns(newColumns);
|
||||
persist({
|
||||
id,
|
||||
storage,
|
||||
storageAlertsTable,
|
||||
columns: newColumns,
|
||||
visibleColumns: newVisibleColumns,
|
||||
});
|
||||
},
|
||||
[id, storage, storageAlertsTable]
|
||||
);
|
||||
|
||||
const onChangeVisibleColumns = useCallback(
|
||||
(newColumns: string[]) => {
|
||||
setVisibleColumns(newColumns);
|
||||
onColumnsChange(
|
||||
columns.sort((a, b) => newColumns.indexOf(a.id) - newColumns.indexOf(b.id)),
|
||||
newColumns
|
||||
);
|
||||
},
|
||||
[onColumnsChange, columns]
|
||||
);
|
||||
|
||||
const onToggleColumn = useCallback(
|
||||
(columnId: string): void => {
|
||||
const visibleIndex = visibleColumns.indexOf(columnId);
|
||||
const defaultIndex = defaultColumns.findIndex(
|
||||
(column: EuiDataGridColumn) => column.id === columnId
|
||||
);
|
||||
|
||||
const isVisible = visibleIndex >= 0;
|
||||
const isInDefaultConfig = defaultIndex >= 0;
|
||||
|
||||
let newColumnIds: string[] = [];
|
||||
|
||||
// if the column is shown, remove it
|
||||
if (isVisible) {
|
||||
newColumnIds = [
|
||||
...visibleColumns.slice(0, visibleIndex),
|
||||
...visibleColumns.slice(visibleIndex + 1),
|
||||
];
|
||||
}
|
||||
|
||||
// if the column isn't shown but it's part of the default config
|
||||
// insert into the same position as in the default config
|
||||
if (!isVisible && isInDefaultConfig) {
|
||||
newColumnIds = [
|
||||
...visibleColumns.slice(0, defaultIndex),
|
||||
columnId,
|
||||
...visibleColumns.slice(defaultIndex),
|
||||
];
|
||||
}
|
||||
|
||||
// if the column isn't shown and it's not part of the default config
|
||||
// push it into the second position. Behaviour copied by t_grid, security
|
||||
// does this to insert right after the timestamp column
|
||||
if (!isVisible && !isInDefaultConfig) {
|
||||
newColumnIds = [visibleColumns[0], columnId, ...visibleColumns.slice(1)];
|
||||
}
|
||||
|
||||
const newColumns = newColumnIds.map((_columnId) => {
|
||||
const column = getColumnByColumnId(defaultColumns, _columnId);
|
||||
return euiColumnFactory(column ? column : { id: _columnId }, browserFields);
|
||||
});
|
||||
|
||||
setVisibleColumns(newColumnIds);
|
||||
setColumns(newColumns);
|
||||
persist({
|
||||
id,
|
||||
storage,
|
||||
storageAlertsTable,
|
||||
columns: newColumns,
|
||||
visibleColumns: newColumnIds,
|
||||
});
|
||||
},
|
||||
[browserFields, defaultColumns, id, storage, storageAlertsTable, visibleColumns]
|
||||
);
|
||||
|
||||
const onResetColumns = useCallback(() => {
|
||||
const newVisibleColumns = defaultColumns.map((column) => column.id);
|
||||
const populatedDefaultColumns = populateColumns(defaultColumns, browserFields);
|
||||
setVisibleColumns(newVisibleColumns);
|
||||
setColumns(populatedDefaultColumns);
|
||||
persist({
|
||||
id,
|
||||
storage,
|
||||
storageAlertsTable,
|
||||
columns: populatedDefaultColumns,
|
||||
visibleColumns: newVisibleColumns,
|
||||
});
|
||||
}, [browserFields, defaultColumns, id, storage, storageAlertsTable]);
|
||||
|
||||
return {
|
||||
columns,
|
||||
isBrowserFieldDataLoading,
|
||||
browserFields,
|
||||
visibleColumns,
|
||||
onColumnsChange,
|
||||
onToggleColumn,
|
||||
onResetColumns,
|
||||
onChangeVisibleColumns,
|
||||
};
|
||||
};
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* 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 type { ValidFeatureId } from '@kbn/rule-data-utils';
|
||||
import type { EcsFieldsResponse } from '@kbn/rule-registry-plugin/common/search_strategy';
|
||||
import { BASE_RAC_ALERTS_API_PATH, BrowserFields } from '@kbn/rule-registry-plugin/common';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
import { ERROR_FETCH_BROWSER_FIELDS } from './translations';
|
||||
|
||||
export interface FetchAlertsArgs {
|
||||
featureIds: ValidFeatureId[];
|
||||
}
|
||||
|
||||
export interface FetchAlertResp {
|
||||
alerts: EcsFieldsResponse[];
|
||||
}
|
||||
|
||||
export type UseFetchAlerts = ({ featureIds }: FetchAlertsArgs) => [boolean, FetchAlertResp];
|
||||
|
||||
const INVALID_FEATURE_ID = 'siem';
|
||||
|
||||
export const useFetchBrowserFieldCapabilities = ({
|
||||
featureIds,
|
||||
}: FetchAlertsArgs): [boolean | undefined, BrowserFields] => {
|
||||
const {
|
||||
http,
|
||||
notifications: { toasts },
|
||||
} = useKibana().services;
|
||||
|
||||
const [isLoading, setIsLoading] = useState<boolean | undefined>(undefined);
|
||||
const [browserFields, setBrowserFields] = useState<BrowserFields>(() => ({}));
|
||||
|
||||
const getBrowserFieldInfo = useCallback(async () => {
|
||||
if (!http) return Promise.resolve({});
|
||||
|
||||
try {
|
||||
return await http.get<BrowserFields>(`${BASE_RAC_ALERTS_API_PATH}/browser_fields`, {
|
||||
query: { featureIds },
|
||||
});
|
||||
} catch (e) {
|
||||
toasts.addDanger(ERROR_FETCH_BROWSER_FIELDS);
|
||||
return {};
|
||||
}
|
||||
}, [featureIds, http, toasts]);
|
||||
|
||||
useEffect(() => {
|
||||
if (featureIds.includes(INVALID_FEATURE_ID)) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [featureIds]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoading !== undefined) return;
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
const callApi = async () => {
|
||||
const browserFieldsInfo = await getBrowserFieldInfo();
|
||||
|
||||
setBrowserFields(browserFieldsInfo);
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
callApi();
|
||||
}, [getBrowserFieldInfo, isLoading]);
|
||||
|
||||
return [isLoading, browserFields];
|
||||
};
|
|
@ -5,5 +5,4 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export { getToolbarVisibility } from './toolbar_visibility';
|
||||
export { AlertsCount } from './components/alerts_count/alerts_count';
|
||||
export * from './toolbar_visibility';
|
||||
|
|
|
@ -8,22 +8,47 @@
|
|||
import { EuiDataGridToolBarVisibilityOptions } from '@elastic/eui';
|
||||
import { EcsFieldsResponse } from '@kbn/rule-registry-plugin/common/search_strategy';
|
||||
import React, { lazy, Suspense } from 'react';
|
||||
import { BrowserFields } from '@kbn/rule-registry-plugin/common';
|
||||
import { AlertsCount } from './components/alerts_count/alerts_count';
|
||||
import { BulkActionsConfig } from '../../../../types';
|
||||
import { LastUpdatedAt } from './components/last_updated_at';
|
||||
import { FieldBrowser } from '../../field_browser';
|
||||
|
||||
const BulkActionsToolbar = lazy(() => import('../bulk_actions/components/toolbar'));
|
||||
|
||||
const getDefaultVisibility = ({
|
||||
alertsCount,
|
||||
updatedAt,
|
||||
columnIds,
|
||||
onToggleColumn,
|
||||
onResetColumns,
|
||||
browserFields,
|
||||
}: {
|
||||
alertsCount: number;
|
||||
updatedAt: number;
|
||||
}) => {
|
||||
columnIds: string[];
|
||||
onToggleColumn: (columnId: string) => void;
|
||||
onResetColumns: () => void;
|
||||
browserFields: BrowserFields;
|
||||
}): EuiDataGridToolBarVisibilityOptions => {
|
||||
const hasBrowserFields = Object.keys(browserFields).length > 0;
|
||||
const additionalControls = {
|
||||
right: <LastUpdatedAt updatedAt={updatedAt} />,
|
||||
left: { append: <AlertsCount count={alertsCount} /> },
|
||||
left: {
|
||||
append: (
|
||||
<>
|
||||
<AlertsCount count={alertsCount} />
|
||||
{hasBrowserFields ? (
|
||||
<FieldBrowser
|
||||
columnIds={columnIds}
|
||||
browserFields={browserFields}
|
||||
onResetColumns={onResetColumns}
|
||||
onToggleColumn={onToggleColumn}
|
||||
/>
|
||||
) : undefined}
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
|
@ -40,6 +65,10 @@ export const getToolbarVisibility = ({
|
|||
alerts,
|
||||
isLoading,
|
||||
updatedAt,
|
||||
columnIds,
|
||||
onToggleColumn,
|
||||
onResetColumns,
|
||||
browserFields,
|
||||
}: {
|
||||
bulkActions: BulkActionsConfig[];
|
||||
alertsCount: number;
|
||||
|
@ -47,18 +76,30 @@ export const getToolbarVisibility = ({
|
|||
alerts: EcsFieldsResponse[];
|
||||
isLoading: boolean;
|
||||
updatedAt: number;
|
||||
columnIds: string[];
|
||||
onToggleColumn: (columnId: string) => void;
|
||||
onResetColumns: () => void;
|
||||
browserFields: any;
|
||||
}): EuiDataGridToolBarVisibilityOptions => {
|
||||
const selectedRowsCount = rowSelection.size;
|
||||
const defaultVisibility = getDefaultVisibility({ alertsCount, updatedAt });
|
||||
const defaultVisibility = getDefaultVisibility({
|
||||
alertsCount,
|
||||
updatedAt,
|
||||
columnIds,
|
||||
onToggleColumn,
|
||||
onResetColumns,
|
||||
browserFields,
|
||||
});
|
||||
const isBulkActionsActive =
|
||||
selectedRowsCount === 0 || selectedRowsCount === undefined || bulkActions.length === 0;
|
||||
|
||||
if (selectedRowsCount === 0 || selectedRowsCount === undefined || bulkActions.length === 0)
|
||||
return defaultVisibility;
|
||||
if (isBulkActionsActive) return defaultVisibility;
|
||||
|
||||
const options = {
|
||||
showColumnSelector: false,
|
||||
showSortSelector: false,
|
||||
additionalControls: {
|
||||
...defaultVisibility.additionalControls,
|
||||
right: <LastUpdatedAt updatedAt={updatedAt} />,
|
||||
left: {
|
||||
append: (
|
||||
<>
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
EuiSelectable,
|
||||
FilterChecked,
|
||||
} from '@elastic/eui';
|
||||
import type { BrowserFields } from '../../types';
|
||||
import { BrowserFields } from '@kbn/rule-registry-plugin/common';
|
||||
import * as i18n from '../../translations';
|
||||
import { getFieldCount, isEscape } from '../../helpers';
|
||||
import { styles } from './categories_selector.styles';
|
||||
|
|
|
@ -127,12 +127,6 @@ describe('field_items', () => {
|
|||
sortable: true,
|
||||
width: '225px',
|
||||
},
|
||||
{
|
||||
field: 'description',
|
||||
name: 'Description',
|
||||
sortable: true,
|
||||
width: '400px',
|
||||
},
|
||||
{
|
||||
field: 'category',
|
||||
name: 'Category',
|
||||
|
@ -188,12 +182,10 @@ describe('field_items', () => {
|
|||
);
|
||||
|
||||
expect(getAllByText('Name').at(0)).toBeInTheDocument();
|
||||
expect(getAllByText('Description').at(0)).toBeInTheDocument();
|
||||
expect(getAllByText('Category').at(0)).toBeInTheDocument();
|
||||
|
||||
expect(getByTestId(`field-${timestampFieldId}-checkbox`)).toBeInTheDocument();
|
||||
expect(getByTestId(`field-${timestampFieldId}-name`)).toBeInTheDocument();
|
||||
expect(getByTestId(`field-${timestampFieldId}-description`)).toBeInTheDocument();
|
||||
expect(getByTestId(`field-${timestampFieldId}-category`)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
|
|
|
@ -11,20 +11,15 @@ import {
|
|||
EuiToolTip,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiScreenReaderOnly,
|
||||
EuiBadge,
|
||||
EuiBasicTableColumn,
|
||||
EuiTableActionsColumnType,
|
||||
} from '@elastic/eui';
|
||||
import { uniqBy } from 'lodash/fp';
|
||||
|
||||
import { getEmptyValue, getExampleText, getIconFromType } from '../../helpers';
|
||||
import type {
|
||||
BrowserFields,
|
||||
BrowserFieldItem,
|
||||
FieldTableColumns,
|
||||
GetFieldTableColumns,
|
||||
} from '../../types';
|
||||
import { BrowserFields } from '@kbn/rule-registry-plugin/common';
|
||||
import { getIconFromType } from '../../helpers';
|
||||
import type { BrowserFieldItem, FieldTableColumns, GetFieldTableColumns } from '../../types';
|
||||
import { FieldName } from '../field_name';
|
||||
import * as i18n from '../../translations';
|
||||
import { styles } from './field_items.style';
|
||||
|
@ -93,26 +88,6 @@ const getDefaultFieldTableColumns = (highlight: string): FieldTableColumns => [
|
|||
sortable: true,
|
||||
width: '225px',
|
||||
},
|
||||
{
|
||||
field: 'description',
|
||||
name: i18n.DESCRIPTION,
|
||||
render: (description: string, { name, example }) => (
|
||||
<EuiToolTip content={description}>
|
||||
<>
|
||||
<EuiScreenReaderOnly data-test-subj="descriptionForScreenReaderOnly">
|
||||
<p>{i18n.DESCRIPTION_FOR_FIELD(name)}</p>
|
||||
</EuiScreenReaderOnly>
|
||||
<span css={styles.truncatable}>
|
||||
<span css={styles.description} data-test-subj={`field-${name}-description`}>
|
||||
{`${description ?? getEmptyValue()} ${getExampleText(example)}`}
|
||||
</span>
|
||||
</span>
|
||||
</>
|
||||
</EuiToolTip>
|
||||
),
|
||||
sortable: true,
|
||||
width: '400px',
|
||||
},
|
||||
{
|
||||
field: 'category',
|
||||
name: i18n.CATEGORY,
|
||||
|
|
|
@ -6,9 +6,10 @@
|
|||
*/
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { EuiInMemoryTable, Pagination, Direction, useEuiTheme } from '@elastic/eui';
|
||||
import { BrowserFields } from '@kbn/rule-registry-plugin/common';
|
||||
import { getFieldColumns, getFieldItems, isActionsColumn } from '../field_items';
|
||||
import { CATEGORY_TABLE_CLASS_NAME, TABLE_HEIGHT } from '../../helpers';
|
||||
import type { BrowserFields, FieldBrowserProps, GetFieldTableColumns } from '../../types';
|
||||
import type { FieldBrowserProps, GetFieldTableColumns } from '../../types';
|
||||
import { FieldTableHeader } from './field_table_header';
|
||||
import { styles } from './field_table.styles';
|
||||
|
||||
|
|
|
@ -8,7 +8,8 @@ import { EuiButtonEmpty, EuiToolTip } from '@elastic/eui';
|
|||
import { debounce } from 'lodash';
|
||||
import React, { useEffect, useRef, useState, useCallback, useMemo } from 'react';
|
||||
|
||||
import type { FieldBrowserProps, BrowserFields } from './types';
|
||||
import { BrowserFields } from '@kbn/rule-registry-plugin/common';
|
||||
import type { FieldBrowserProps } from './types';
|
||||
import { FieldBrowserModal } from './field_browser_modal';
|
||||
import { filterBrowserFieldsByFieldName, filterSelectedBrowserFields } from './helpers';
|
||||
import * as i18n from './translations';
|
||||
|
|
|
@ -18,7 +18,8 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import type { FieldBrowserProps, BrowserFields } from './types';
|
||||
import { BrowserFields } from '@kbn/rule-registry-plugin/common';
|
||||
import type { FieldBrowserProps } from './types';
|
||||
import { Search } from './components/search';
|
||||
|
||||
import { CLOSE_BUTTON_CLASS_NAME, FIELD_BROWSER_WIDTH, RESET_FIELDS_CLASS_NAME } from './helpers';
|
||||
|
|
|
@ -13,7 +13,7 @@ import {
|
|||
filterBrowserFieldsByFieldName,
|
||||
filterSelectedBrowserFields,
|
||||
} from './helpers';
|
||||
import type { BrowserFields } from './types';
|
||||
import { BrowserFields } from '@kbn/rule-registry-plugin/common';
|
||||
|
||||
describe('helpers', () => {
|
||||
describe('categoryHasFields', () => {
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { BrowserField, BrowserFields } from '@kbn/rule-registry-plugin/common';
|
||||
import { isEmpty } from 'lodash/fp';
|
||||
import { BrowserField, BrowserFields } from './types';
|
||||
|
||||
export const FIELD_BROWSER_WIDTH = 925;
|
||||
export const TABLE_HEIGHT = 260;
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { BrowserFields } from './types';
|
||||
import { BrowserFields } from '@kbn/rule-registry-plugin/common';
|
||||
|
||||
const DEFAULT_INDEX_PATTERN = [
|
||||
'apm-*-transaction*',
|
||||
|
|
|
@ -6,26 +6,8 @@
|
|||
*/
|
||||
|
||||
import type { EuiBasicTableColumn } from '@elastic/eui';
|
||||
import type { IFieldSubType } from '@kbn/es-query';
|
||||
import type { RuntimeField } from '@kbn/data-views-plugin/common';
|
||||
import { BrowserFields } from '@kbn/rule-registry-plugin/common';
|
||||
|
||||
export interface BrowserField {
|
||||
aggregatable: boolean;
|
||||
category: string;
|
||||
description: string | null;
|
||||
example: string | number | null;
|
||||
fields: Readonly<Record<string, Partial<BrowserField>>>;
|
||||
format: string;
|
||||
indexes: string[];
|
||||
name: string;
|
||||
searchable: boolean;
|
||||
type: string;
|
||||
subType?: IFieldSubType;
|
||||
readFromDocValues: boolean;
|
||||
runtimeField?: RuntimeField;
|
||||
}
|
||||
|
||||
export type BrowserFields = Readonly<Record<string, Partial<BrowserField>>>;
|
||||
/**
|
||||
* An item rendered in the table
|
||||
*/
|
||||
|
|
|
@ -410,7 +410,6 @@ export interface FetchAlertData {
|
|||
isInitializing: boolean;
|
||||
isLoading: boolean;
|
||||
getInspectQuery: () => { request: {}; response: {} };
|
||||
onColumnsChange: (columns: EuiDataGridColumn[], visibleColumns: string[]) => void;
|
||||
onPageChange: (pagination: RuleRegistrySearchRequestPagination) => void;
|
||||
onSortChange: (sort: EuiDataGridSorting['columns']) => void;
|
||||
refresh: () => void;
|
||||
|
@ -434,6 +433,11 @@ export interface AlertsTableProps {
|
|||
visibleColumns: string[];
|
||||
'data-test-subj': string;
|
||||
updatedAt: number;
|
||||
browserFields: any;
|
||||
onToggleColumn: (columnId: string) => void;
|
||||
onResetColumns: () => void;
|
||||
onColumnsChange: (columns: EuiDataGridColumn[], visibleColumns: string[]) => void;
|
||||
onChangeVisibleColumns: (newColumns: string[]) => void;
|
||||
}
|
||||
|
||||
// TODO We need to create generic type between our plugin, right now we have different one because of the old alerts table
|
||||
|
|
|
@ -46,6 +46,32 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
);
|
||||
});
|
||||
|
||||
it('should let the user choose between fields', async () => {
|
||||
await waitAndClickByTestId('show-field-browser');
|
||||
await waitAndClickByTestId('categories-filter-button');
|
||||
await waitAndClickByTestId('categories-selector-option-name-base');
|
||||
await find.clickByCssSelector('#_id');
|
||||
await waitAndClickByTestId('close');
|
||||
|
||||
const headers = await find.allByCssSelector('.euiDataGridHeaderCell');
|
||||
expect(headers.length).to.be(6);
|
||||
});
|
||||
|
||||
it('should take into account the column type when sorting', async () => {
|
||||
const sortElQuery =
|
||||
'[data-test-subj="dataGridHeaderCellActionGroup-kibana.alert.duration.us"] > li:nth-child(2)';
|
||||
|
||||
await waitAndClickByTestId('dataGridHeaderCell-kibana.alert.duration.us');
|
||||
|
||||
await retry.try(async () => {
|
||||
const exists = await find.byCssSelector(sortElQuery);
|
||||
if (!exists) throw new Error('Still loading...');
|
||||
});
|
||||
|
||||
const sortItem = await find.byCssSelector(sortElQuery);
|
||||
expect(await sortItem.getVisibleText()).to.be('Sort Low-High');
|
||||
});
|
||||
|
||||
it('should sort properly', async () => {
|
||||
await find.clickDisplayedByCssSelector(
|
||||
'[data-test-subj="dataGridHeaderCell-event.action"] .euiDataGridHeaderCell__button'
|
||||
|
@ -161,4 +187,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
return rows;
|
||||
}
|
||||
});
|
||||
|
||||
const waitAndClickByTestId = async (testId: string) => {
|
||||
retry.try(async () => {
|
||||
const exists = await testSubjects.exists(testId);
|
||||
if (!exists) throw new Error('Still loading...');
|
||||
});
|
||||
|
||||
return find.clickDisplayedByCssSelector(`[data-test-subj="${testId}"]`);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -49,8 +49,7 @@ export default ({ getService, getPageObject }: FtrProviderContext) => {
|
|||
expect(durationColumnExists).to.be(false);
|
||||
});
|
||||
|
||||
// TODO Enable this test after fixing: https://github.com/elastic/kibana/issues/137988
|
||||
it.skip('remembers sorting changes', async () => {
|
||||
it('remembers sorting changes', async () => {
|
||||
const timestampColumnButton = await testSubjects.find(
|
||||
'dataGridHeaderCellActionButton-@timestamp'
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue