mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Security Solution] Migrate Field Browser to TriggersActionsUi plugin (#135231)
* field browser migrated * fix tests, skip styled-components warnings * fix types and tests * more test fixes * styles migrated to emotion/react * use eui theme * cleaning * rename parameter fieldId to columnId * move files to components folder * fix lint error Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
5f753ac50c
commit
0573c83ebf
68 changed files with 1153 additions and 745 deletions
|
@ -40,6 +40,7 @@ import { MlLocatorDefinition } from '@kbn/ml-plugin/public';
|
|||
import { EuiTheme } from '@kbn/kibana-react-plugin/common';
|
||||
import { MockUrlService } from '@kbn/share-plugin/common/mocks';
|
||||
import { fleetMock } from '@kbn/fleet-plugin/public/mocks';
|
||||
import { triggersActionsUiMock } from '@kbn/triggers-actions-ui-plugin/public/mocks';
|
||||
|
||||
const mockUiSettings: Record<string, unknown> = {
|
||||
[DEFAULT_TIME_RANGE]: { from: 'now-15m', to: 'now', mode: 'quick' },
|
||||
|
@ -97,6 +98,7 @@ export const createStartServicesMock = (
|
|||
const locator = urlService.locators.create(new MlLocatorDefinition());
|
||||
const fleet = fleetMock.createStartMock();
|
||||
const unifiedSearch = unifiedSearchPluginMock.createStartContract();
|
||||
const triggersActionsUi = triggersActionsUiMock.createStart();
|
||||
|
||||
return {
|
||||
...core,
|
||||
|
@ -161,6 +163,7 @@ export const createStartServicesMock = (
|
|||
getLastUpdated: jest.fn(),
|
||||
getFieldBrowser: jest.fn(),
|
||||
},
|
||||
triggersActionsUi,
|
||||
} as unknown as StartServices;
|
||||
};
|
||||
|
||||
|
|
|
@ -4,12 +4,10 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import React from 'react';
|
||||
|
||||
export const mockTimelines = {
|
||||
getLastUpdated: jest.fn(),
|
||||
getLoadingPanel: jest.fn(),
|
||||
getFieldBrowser: jest.fn().mockReturnValue(<div data-test-subj="field-browser" />),
|
||||
getUseDraggableKeyboardWrapper: () =>
|
||||
jest.fn().mockReturnValue({
|
||||
onBlur: jest.fn(),
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* 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';
|
||||
|
||||
export const mockTriggersActionsUi = {
|
||||
getFieldBrowser: jest.fn().mockReturnValue(<div data-test-subj="field-browser" />),
|
||||
};
|
|
@ -10,7 +10,7 @@ import { render } from '@testing-library/react';
|
|||
|
||||
import { TestProviders, mockTimelineModel } from '../../../../../common/mock';
|
||||
import { HeaderActions } from './header_actions';
|
||||
import { mockTimelines } from '../../../../../common/mock/mock_timelines_plugin';
|
||||
import { mockTriggersActionsUi } from '../../../../../common/mock/mock_triggers_actions_ui_plugin';
|
||||
import {
|
||||
ColumnHeaderOptions,
|
||||
HeaderActionProps,
|
||||
|
@ -34,20 +34,20 @@ jest.mock('../../../../../common/hooks/use_selector', () => ({
|
|||
useShallowEqualSelector: jest.fn(),
|
||||
}));
|
||||
|
||||
const fieldId = 'test-field';
|
||||
const columnId = 'test-field';
|
||||
const timelineId = 'test-timeline';
|
||||
|
||||
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
||||
mockTimelines.getFieldBrowser.mockImplementation(
|
||||
mockTriggersActionsUi.getFieldBrowser.mockImplementation(
|
||||
({
|
||||
onToggleColumn,
|
||||
onResetColumns,
|
||||
}: {
|
||||
onToggleColumn: (field: string) => void;
|
||||
onToggleColumn: (columnId: string) => void;
|
||||
onResetColumns: () => void;
|
||||
}) => (
|
||||
<div data-test-subj="mock-field-browser">
|
||||
<div data-test-subj="mock-toggle-button" onClick={() => onToggleColumn(fieldId)} />
|
||||
<div data-test-subj="mock-toggle-button" onClick={() => onToggleColumn(columnId)} />
|
||||
<div data-test-subj="mock-reset-button" onClick={onResetColumns} />
|
||||
</div>
|
||||
)
|
||||
|
@ -56,7 +56,7 @@ mockTimelines.getFieldBrowser.mockImplementation(
|
|||
jest.mock('../../../../../common/lib/kibana', () => ({
|
||||
useKibana: () => ({
|
||||
services: {
|
||||
timelines: { ...mockTimelines },
|
||||
triggersActionsUi: { ...mockTriggersActionsUi },
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
@ -99,7 +99,7 @@ describe('HeaderActions', () => {
|
|||
|
||||
expect(mockDispatch).toHaveBeenCalledWith(
|
||||
timelineActions.upsertColumn({
|
||||
column: getColumnHeader(fieldId, []),
|
||||
column: getColumnHeader(columnId, []),
|
||||
id: timelineId,
|
||||
index: 1,
|
||||
})
|
||||
|
@ -111,7 +111,7 @@ describe('HeaderActions', () => {
|
|||
<TestProviders>
|
||||
<HeaderActions
|
||||
{...defaultProps}
|
||||
columnHeaders={[{ id: fieldId } as unknown as ColumnHeaderOptions]}
|
||||
columnHeaders={[{ id: columnId } as unknown as ColumnHeaderOptions]}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
@ -119,7 +119,7 @@ describe('HeaderActions', () => {
|
|||
|
||||
expect(mockDispatch).toHaveBeenCalledWith(
|
||||
timelineActions.removeColumn({
|
||||
columnId: fieldId,
|
||||
columnId,
|
||||
id: timelineId,
|
||||
})
|
||||
);
|
||||
|
|
|
@ -90,7 +90,7 @@ const HeaderActionsComponent: React.FC<HeaderActionProps> = ({
|
|||
timelineId,
|
||||
fieldBrowserOptions,
|
||||
}) => {
|
||||
const { timelines: timelinesUi } = useKibana().services;
|
||||
const { triggersActionsUi } = useKibana().services;
|
||||
const { globalFullScreen, setGlobalFullScreen } = useGlobalFullScreen();
|
||||
const { timelineFullScreen, setTimelineFullScreen } = useTimelineFullScreen();
|
||||
const dispatch = useDispatch();
|
||||
|
@ -179,18 +179,18 @@ const HeaderActionsComponent: React.FC<HeaderActionProps> = ({
|
|||
}, [defaultColumns, dispatch, timelineId]);
|
||||
|
||||
const onToggleColumn = useCallback(
|
||||
(fieldId: string) => {
|
||||
if (columnHeaders.some(({ id }) => id === fieldId)) {
|
||||
(columnId: string) => {
|
||||
if (columnHeaders.some(({ id }) => id === columnId)) {
|
||||
dispatch(
|
||||
timelineActions.removeColumn({
|
||||
columnId: fieldId,
|
||||
columnId,
|
||||
id: timelineId,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
dispatch(
|
||||
timelineActions.upsertColumn({
|
||||
column: getColumnHeader(fieldId, defaultColumns),
|
||||
column: getColumnHeader(columnId, defaultColumns),
|
||||
id: timelineId,
|
||||
index: 1,
|
||||
})
|
||||
|
@ -219,9 +219,9 @@ const HeaderActionsComponent: React.FC<HeaderActionProps> = ({
|
|||
|
||||
<EventsTh role="button">
|
||||
<FieldBrowserContainer>
|
||||
{timelinesUi.getFieldBrowser({
|
||||
{triggersActionsUi.getFieldBrowser({
|
||||
browserFields,
|
||||
columnHeaders,
|
||||
columnIds: columnHeaders.map(({ id }) => id),
|
||||
onResetColumns,
|
||||
onToggleColumn,
|
||||
options: fieldBrowserOptions,
|
||||
|
|
|
@ -10,7 +10,6 @@ import React from 'react';
|
|||
|
||||
import { TestProviders, mockTimelineModel, mockTimelineData } from '../../../../../common/mock';
|
||||
import { Actions, isAlert } from '.';
|
||||
import { mockTimelines } from '../../../../../common/mock/mock_timelines_plugin';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features';
|
||||
import { mockCasesContract } from '@kbn/cases-plugin/public/mocks';
|
||||
import { useShallowEqualSelector } from '../../../../../common/hooks/use_selector';
|
||||
|
@ -53,7 +52,6 @@ jest.mock('../../../../../common/lib/kibana', () => {
|
|||
savedObjects: {
|
||||
client: {},
|
||||
},
|
||||
timelines: { ...mockTimelines },
|
||||
},
|
||||
}),
|
||||
useToasts: jest.fn().mockReturnValue({
|
||||
|
|
|
@ -25,8 +25,17 @@ import { getDefaultControlColumn } from '../control_columns';
|
|||
import { testTrailingControlColumns } from '../../../../../common/mock/mock_timeline_control_columns';
|
||||
import { HeaderActions } from '../actions/header_actions';
|
||||
import { UseFieldBrowserOptionsProps } from '../../../fields_browser';
|
||||
import { mockTriggersActionsUi } from '../../../../../common/mock/mock_triggers_actions_ui_plugin';
|
||||
import { mockTimelines } from '../../../../../common/mock/mock_timelines_plugin';
|
||||
|
||||
jest.mock('../../../../../common/lib/kibana');
|
||||
jest.mock('../../../../../common/lib/kibana', () => ({
|
||||
useKibana: () => ({
|
||||
services: {
|
||||
timelines: mockTimelines,
|
||||
triggersActionsUi: mockTriggersActionsUi,
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
const mockUseFieldBrowserOptions = jest.fn();
|
||||
jest.mock('../../../fields_browser', () => ({
|
||||
|
|
|
@ -74,9 +74,11 @@ jest.mock('../../../../common/lib/kibana', () => {
|
|||
},
|
||||
timelines: {
|
||||
getLastUpdated: jest.fn(),
|
||||
getFieldBrowser: jest.fn(),
|
||||
getUseDraggableKeyboardWrapper: () => mockUseDraggableKeyboardWrapper,
|
||||
},
|
||||
triggersActionsUi: {
|
||||
getFieldBrowser: jest.fn(),
|
||||
},
|
||||
},
|
||||
}),
|
||||
useGetUserSavedObjectPermissions: jest.fn(),
|
||||
|
|
|
@ -71,6 +71,7 @@ jest.mock('../../../../common/lib/kibana', () => {
|
|||
getFieldBrowser: jest.fn(),
|
||||
getUseDraggableKeyboardWrapper: () => mockUseDraggableKeyboardWrapper,
|
||||
},
|
||||
triggersActionsUi: { getFieldBrowser: jest.fn() },
|
||||
},
|
||||
}),
|
||||
useGetUserSavedObjectPermissions: jest.fn(),
|
||||
|
|
|
@ -74,10 +74,12 @@ jest.mock('../../../../common/lib/kibana', () => {
|
|||
savedObjects: {
|
||||
client: {},
|
||||
},
|
||||
triggersActionsUi: {
|
||||
getFieldBrowser: jest.fn(),
|
||||
},
|
||||
timelines: {
|
||||
getLastUpdated: jest.fn(),
|
||||
getLoadingPanel: jest.fn(),
|
||||
getFieldBrowser: jest.fn(),
|
||||
getUseDraggableKeyboardWrapper: () =>
|
||||
jest.fn().mockReturnValue({
|
||||
onBlur: jest.fn(),
|
||||
|
|
|
@ -61,7 +61,6 @@ jest.mock('../../../../common/lib/kibana', () => {
|
|||
timelines: {
|
||||
getLastUpdated: jest.fn(),
|
||||
getLoadingPanel: jest.fn(),
|
||||
getFieldBrowser: jest.fn(),
|
||||
getUseDraggableKeyboardWrapper: () =>
|
||||
jest.fn().mockReturnValue({
|
||||
onBlur: jest.fn(),
|
||||
|
|
|
@ -27,7 +27,6 @@ export type {
|
|||
ControlColumnProps,
|
||||
DataProvidersAnd,
|
||||
DataProvider,
|
||||
FieldBrowserOptions,
|
||||
GenericActionRowCellRenderProps,
|
||||
HeaderActionProps,
|
||||
HeaderCellRender,
|
||||
|
|
|
@ -5,5 +5,4 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export * from './field_browser';
|
||||
export * from './timeline';
|
||||
|
|
|
@ -7,13 +7,29 @@
|
|||
import { ComponentType, JSXElementConstructor } from 'react';
|
||||
import { EuiDataGridControlColumn, EuiDataGridCellValueElementProps } from '@elastic/eui';
|
||||
|
||||
// Temporary import from triggers-actions-ui public types, it will not be needed after alerts table migrated
|
||||
import type {
|
||||
FieldBrowserOptions,
|
||||
CreateFieldComponent,
|
||||
GetFieldTableColumns,
|
||||
FieldBrowserProps,
|
||||
BrowserFieldItem,
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
} from '@kbn/triggers-actions-ui-plugin/public/types';
|
||||
|
||||
import { OnRowSelected, SortColumnTimeline, TimelineTabs } from '..';
|
||||
import { BrowserFields } from '../../../search_strategy/index_fields';
|
||||
import { ColumnHeaderOptions } from '../columns';
|
||||
import { TimelineItem, TimelineNonEcsData } from '../../../search_strategy';
|
||||
import { Ecs } from '../../../ecs';
|
||||
import { FieldBrowserOptions } from '../../field_browser';
|
||||
|
||||
export {
|
||||
FieldBrowserOptions,
|
||||
CreateFieldComponent,
|
||||
GetFieldTableColumns,
|
||||
FieldBrowserProps,
|
||||
BrowserFieldItem,
|
||||
};
|
||||
export interface ActionProps {
|
||||
action?: RowCellRender;
|
||||
ariaRowindex: number;
|
||||
|
@ -79,6 +95,7 @@ export interface BulkActionsProps {
|
|||
customBulkActions?: CustomBulkActionProp[];
|
||||
timelineId?: string;
|
||||
}
|
||||
|
||||
export interface HeaderActionProps {
|
||||
width: number;
|
||||
browserFields: BrowserFields;
|
||||
|
|
|
@ -10,6 +10,6 @@
|
|||
"extraPublicDirs": ["common"],
|
||||
"server": true,
|
||||
"ui": true,
|
||||
"requiredPlugins": ["alerting", "cases", "data", "kibanaReact", "kibanaUtils"],
|
||||
"requiredPlugins": ["alerting", "cases", "data", "kibanaReact", "kibanaUtils", "triggersActionsUi"],
|
||||
"optionalPlugins": ["security"]
|
||||
}
|
||||
|
|
|
@ -58,4 +58,3 @@ export { TGrid as default };
|
|||
export * from './drag_and_drop';
|
||||
export * from './last_updated';
|
||||
export * from './loading';
|
||||
export * from './field_browser';
|
||||
|
|
|
@ -39,6 +39,20 @@ jest.mock('react-redux', () => {
|
|||
};
|
||||
});
|
||||
|
||||
jest.mock('@kbn/kibana-react-plugin/public', () => {
|
||||
const originalModule = jest.requireActual('@kbn/kibana-react-plugin/public');
|
||||
return {
|
||||
...originalModule,
|
||||
useKibana: () => ({
|
||||
services: {
|
||||
triggersActionsUi: {
|
||||
getFieldBrowser: jest.fn(),
|
||||
},
|
||||
},
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('../../../hooks/use_selector', () => ({
|
||||
useShallowEqualSelector: () => mockGlobalState.timelineById.test,
|
||||
useDeepEqualSelector: () => mockGlobalState.timelineById.test,
|
||||
|
|
|
@ -37,6 +37,8 @@ import styled, { ThemeContext } from 'styled-components';
|
|||
import { ALERT_RULE_CONSUMER, ALERT_RULE_PRODUCER } from '@kbn/rule-data-utils';
|
||||
import { Filter } from '@kbn/es-query';
|
||||
import type { EuiTheme } from '@kbn/kibana-react-plugin/common';
|
||||
import { FieldBrowserOptions } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import {
|
||||
TGridCellAction,
|
||||
BulkActionsProp,
|
||||
|
@ -65,11 +67,9 @@ import {
|
|||
|
||||
import type { BrowserFields } from '../../../../common/search_strategy/index_fields';
|
||||
import type { OnRowSelected, OnSelectAll } from '../types';
|
||||
import type { FieldBrowserOptions } from '../../../../common/types';
|
||||
import type { Refetch } from '../../../store/t_grid/inputs';
|
||||
import { getPageRowIndex } from '../../../../common/utils/pagination';
|
||||
import { StatefulEventContext } from '../../stateful_event_context';
|
||||
import { FieldBrowser } from '../toolbar/field_browser';
|
||||
import { tGridActions, TGridModel, tGridSelectors, TimelineState } from '../../../store/t_grid';
|
||||
import { useDeepEqualSelector } from '../../../hooks/use_selector';
|
||||
import { RowAction } from './row_action';
|
||||
|
@ -79,6 +79,7 @@ import { checkBoxControlColumn } from './control_columns';
|
|||
import { ViewSelection } from '../event_rendered_view/selector';
|
||||
import { EventRenderedView } from '../event_rendered_view';
|
||||
import { REMOVE_COLUMN } from './column_headers/translations';
|
||||
import { TimelinesStartPlugins } from '../../../types';
|
||||
|
||||
const StatefulAlertBulkActions = lazy(() => import('../toolbar/bulk_actions/alert_bulk_actions'));
|
||||
|
||||
|
@ -337,6 +338,8 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
|
|||
trailingControlColumns = EMPTY_CONTROL_COLUMNS,
|
||||
unit = defaultUnit,
|
||||
}) => {
|
||||
const { triggersActionsUi } = useKibana<TimelinesStartPlugins>().services;
|
||||
|
||||
const dataGridRef = useRef<EuiDataGridRefProps>(null);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
@ -454,18 +457,18 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
|
|||
}, [defaultColumns, dispatch, id]);
|
||||
|
||||
const onToggleColumn = useCallback(
|
||||
(fieldId: string) => {
|
||||
if (columnHeaders.some(({ id: columnId }) => columnId === fieldId)) {
|
||||
(columnId: string) => {
|
||||
if (columnHeaders.some(({ id: columnHeaderId }) => columnId === columnHeaderId)) {
|
||||
dispatch(
|
||||
tGridActions.removeColumn({
|
||||
columnId: fieldId,
|
||||
columnId,
|
||||
id,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
dispatch(
|
||||
tGridActions.upsertColumn({
|
||||
column: getColumnHeader(fieldId, defaultColumns),
|
||||
column: getColumnHeader(columnId, defaultColumns),
|
||||
id,
|
||||
index: 1,
|
||||
})
|
||||
|
@ -545,14 +548,13 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
|
|||
) : (
|
||||
<>
|
||||
{additionalControls ?? null}
|
||||
<FieldBrowser
|
||||
data-test-subj="field-browser"
|
||||
browserFields={browserFields}
|
||||
options={fieldBrowserOptions}
|
||||
columnHeaders={columnHeaders}
|
||||
onResetColumns={onResetColumns}
|
||||
onToggleColumn={onToggleColumn}
|
||||
/>
|
||||
{triggersActionsUi.getFieldBrowser({
|
||||
browserFields,
|
||||
options: fieldBrowserOptions,
|
||||
columnIds: columnHeaders.map(({ id: columnId }) => columnId),
|
||||
onResetColumns,
|
||||
onToggleColumn,
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
@ -585,6 +587,7 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
|
|||
onAlertStatusActionFailure,
|
||||
onResetColumns,
|
||||
onToggleColumn,
|
||||
triggersActionsUi,
|
||||
additionalBulkActions,
|
||||
refetch,
|
||||
additionalControls,
|
||||
|
|
|
@ -23,6 +23,7 @@ import type { DocValueFields } from '../../../../common/search_strategy';
|
|||
import type { BrowserFields } from '../../../../common/search_strategy/index_fields';
|
||||
import {
|
||||
BulkActionsProp,
|
||||
FieldBrowserOptions,
|
||||
TGridCellAction,
|
||||
TimelineId,
|
||||
TimelineTabs,
|
||||
|
@ -42,7 +43,6 @@ import { defaultHeaders } from '../body/column_headers/default_headers';
|
|||
import { buildCombinedQuery, getCombinedFilterQuery, resolverIsShowing } from '../helpers';
|
||||
import { tGridActions, tGridSelectors } from '../../../store/t_grid';
|
||||
import { useTimelineEvents, InspectResponse, Refetch } from '../../../container';
|
||||
import { FieldBrowserOptions } from '../../field_browser';
|
||||
import { StatefulBody } from '../body';
|
||||
import { SELECTOR_TIMELINE_GLOBAL_CONTAINER, UpdatedFlexGroup, UpdatedFlexItem } from '../styles';
|
||||
import { Sort } from '../body/sort';
|
||||
|
|
|
@ -1,102 +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 { i18n } from '@kbn/i18n';
|
||||
|
||||
export const CATEGORY = i18n.translate('xpack.timelines.fieldBrowser.categoryLabel', {
|
||||
defaultMessage: 'Category',
|
||||
});
|
||||
|
||||
export const CATEGORIES = i18n.translate('xpack.timelines.fieldBrowser.categoriesTitle', {
|
||||
defaultMessage: 'Categories',
|
||||
});
|
||||
|
||||
export const CATEGORIES_COUNT = (totalCount: number) =>
|
||||
i18n.translate('xpack.timelines.fieldBrowser.categoriesCountTitle', {
|
||||
values: { totalCount },
|
||||
defaultMessage: '{totalCount} {totalCount, plural, =1 {category} other {categories}}',
|
||||
});
|
||||
|
||||
export const CLOSE = i18n.translate('xpack.timelines.fieldBrowser.closeButton', {
|
||||
defaultMessage: 'Close',
|
||||
});
|
||||
|
||||
export const FIELDS_BROWSER = i18n.translate('xpack.timelines.fieldBrowser.fieldBrowserTitle', {
|
||||
defaultMessage: 'Fields',
|
||||
});
|
||||
|
||||
export const DESCRIPTION = i18n.translate('xpack.timelines.fieldBrowser.descriptionLabel', {
|
||||
defaultMessage: 'Description',
|
||||
});
|
||||
|
||||
export const DESCRIPTION_FOR_FIELD = (field: string) =>
|
||||
i18n.translate('xpack.timelines.fieldBrowser.descriptionForScreenReaderOnly', {
|
||||
values: {
|
||||
field,
|
||||
},
|
||||
defaultMessage: 'Description for field {field}:',
|
||||
});
|
||||
|
||||
export const NAME = i18n.translate('xpack.timelines.fieldBrowser.fieldName', {
|
||||
defaultMessage: 'Name',
|
||||
});
|
||||
|
||||
export const FIELD = i18n.translate('xpack.timelines.fieldBrowser.fieldLabel', {
|
||||
defaultMessage: 'Field',
|
||||
});
|
||||
|
||||
export const FIELDS = i18n.translate('xpack.timelines.fieldBrowser.fieldsTitle', {
|
||||
defaultMessage: 'Fields',
|
||||
});
|
||||
|
||||
export const FIELDS_SHOWING = i18n.translate('xpack.timelines.fieldBrowser.fieldsCountShowing', {
|
||||
defaultMessage: 'Showing',
|
||||
});
|
||||
|
||||
export const FIELDS_COUNT = (totalCount: number) =>
|
||||
i18n.translate('xpack.timelines.fieldBrowser.fieldsCountTitle', {
|
||||
values: { totalCount },
|
||||
defaultMessage: '{totalCount, plural, =1 {field} other {fields}}',
|
||||
});
|
||||
|
||||
export const FILTER_PLACEHOLDER = i18n.translate('xpack.timelines.fieldBrowser.filterPlaceholder', {
|
||||
defaultMessage: 'Field name',
|
||||
});
|
||||
|
||||
export const NO_FIELDS_MATCH = i18n.translate('xpack.timelines.fieldBrowser.noFieldsMatchLabel', {
|
||||
defaultMessage: 'No fields match',
|
||||
});
|
||||
|
||||
export const NO_FIELDS_MATCH_INPUT = (searchInput: string) =>
|
||||
i18n.translate('xpack.timelines.fieldBrowser.noFieldsMatchInputLabel', {
|
||||
defaultMessage: 'No fields match {searchInput}',
|
||||
values: {
|
||||
searchInput,
|
||||
},
|
||||
});
|
||||
|
||||
export const RESET_FIELDS = i18n.translate('xpack.timelines.fieldBrowser.resetFieldsLink', {
|
||||
defaultMessage: 'Reset Fields',
|
||||
});
|
||||
|
||||
export const VIEW_COLUMN = (field: string) =>
|
||||
i18n.translate('xpack.timelines.fieldBrowser.viewColumnCheckboxAriaLabel', {
|
||||
values: { field },
|
||||
defaultMessage: 'View {field} column',
|
||||
});
|
||||
|
||||
export const VIEW_LABEL = i18n.translate('xpack.timelines.fieldBrowser.viewLabel', {
|
||||
defaultMessage: 'View',
|
||||
});
|
||||
|
||||
export const VIEW_VALUE_SELECTED = i18n.translate('xpack.timelines.fieldBrowser.viewSelected', {
|
||||
defaultMessage: 'selected',
|
||||
});
|
||||
|
||||
export const VIEW_VALUE_ALL = i18n.translate('xpack.timelines.fieldBrowser.viewAll', {
|
||||
defaultMessage: 'all',
|
||||
});
|
|
@ -11,7 +11,7 @@ import type { Store } from 'redux';
|
|||
import type { Storage } from '@kbn/kibana-utils-plugin/public';
|
||||
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import type { TGridProps } from '../types';
|
||||
import type { LastUpdatedAtProps, LoadingPanelProps, FieldBrowserProps } from '../components';
|
||||
import type { LastUpdatedAtProps, LoadingPanelProps } from '../components';
|
||||
import { initialTGridState } from '../store/t_grid/reducer';
|
||||
import { createStore } from '../store/t_grid';
|
||||
import { TGridLoading } from '../components/t_grid/shared';
|
||||
|
@ -72,12 +72,3 @@ export const getLoadingPanelLazy = (props: LoadingPanelProps) => {
|
|||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
const FieldBrowserLazy = lazy(() => import('../components/field_browser'));
|
||||
export const getFieldBrowserLazy = (props: FieldBrowserProps) => {
|
||||
return (
|
||||
<Suspense fallback={<EuiLoadingSpinner />}>
|
||||
<FieldBrowserLazy {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -18,7 +18,6 @@ import { mockHoverActions } from './mock_hover_actions';
|
|||
export const createTGridMocks = () => ({
|
||||
getHoverActions: () => mockHoverActions,
|
||||
getTGrid: () => <>{'hello grid'}</>,
|
||||
getFieldBrowser: () => <div data-test-subj="field-browser" />,
|
||||
getLastUpdated: (props: LastUpdatedAtProps) => <LastUpdatedAt {...props} />,
|
||||
getLoadingPanel: (props: LoadingPanelProps) => <LoadingPanel {...props} />,
|
||||
getUseAddToTimeline: () => useAddToTimeline,
|
||||
|
|
|
@ -10,13 +10,8 @@ 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, FieldBrowserProps } from './components';
|
||||
import {
|
||||
getLastUpdatedLazy,
|
||||
getLoadingPanelLazy,
|
||||
getTGridLazy,
|
||||
getFieldBrowserLazy,
|
||||
} from './methods';
|
||||
import type { LastUpdatedAtProps, LoadingPanelProps } from './components';
|
||||
import { getLastUpdatedLazy, getLoadingPanelLazy, getTGridLazy } from './methods';
|
||||
import type { TimelinesUIStart, TGridProps, TimelinesStartPlugins } from './types';
|
||||
import { tGridReducer } from './store/t_grid/reducer';
|
||||
import { useDraggableKeyboardWrapper } from './components/drag_and_drop/draggable_keyboard_wrapper_hook';
|
||||
|
@ -74,9 +69,6 @@ export class TimelinesPlugin implements Plugin<void, TimelinesUIStart> {
|
|||
getLastUpdated: (props: LastUpdatedAtProps) => {
|
||||
return getLastUpdatedLazy(props);
|
||||
},
|
||||
getFieldBrowser: (props: FieldBrowserProps) => {
|
||||
return getFieldBrowserLazy(props);
|
||||
},
|
||||
getUseAddToTimeline: () => {
|
||||
return useAddToTimeline;
|
||||
},
|
||||
|
|
|
@ -11,10 +11,10 @@ import { Store } from 'redux';
|
|||
import { CoreStart } from '@kbn/core/public';
|
||||
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import { CasesUiStart } from '@kbn/cases-plugin/public';
|
||||
import type { TriggersAndActionsUIPublicPluginStart as TriggersActionsStart } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import type {
|
||||
LastUpdatedAtProps,
|
||||
LoadingPanelProps,
|
||||
FieldBrowserProps,
|
||||
UseDraggableKeyboardWrapper,
|
||||
UseDraggableKeyboardWrapperProps,
|
||||
} from './components';
|
||||
|
@ -34,7 +34,6 @@ export interface TimelinesUIStart {
|
|||
getTGridReducer: () => any;
|
||||
getLoadingPanel: (props: LoadingPanelProps) => ReactElement<LoadingPanelProps>;
|
||||
getLastUpdated: (props: LastUpdatedAtProps) => ReactElement<LastUpdatedAtProps>;
|
||||
getFieldBrowser: (props: FieldBrowserProps) => ReactElement<FieldBrowserProps>;
|
||||
getUseAddToTimeline: () => (props: UseAddToTimelineProps) => UseAddToTimeline;
|
||||
getUseAddToTimelineSensor: () => (api: SensorAPI) => void;
|
||||
getUseDraggableKeyboardWrapper: () => (
|
||||
|
@ -46,6 +45,7 @@ export interface TimelinesUIStart {
|
|||
export interface TimelinesStartPlugins {
|
||||
data: DataPublicPluginStart;
|
||||
cases: CasesUiStart;
|
||||
triggersActionsUi: TriggersActionsStart;
|
||||
}
|
||||
|
||||
export type TimelinesStartServices = CoreStart & TimelinesStartPlugins;
|
||||
|
|
|
@ -29937,26 +29937,6 @@
|
|||
"xpack.timelines.clipboard.copy.to.the.clipboard": "Copier dans le presse-papiers",
|
||||
"xpack.timelines.clipboard.to.the.clipboard": "dans le presse-papiers",
|
||||
"xpack.timelines.copyToClipboardTooltip": "Copier dans le Presse-papiers",
|
||||
"xpack.timelines.fieldBrowser.categoriesCountTitle": "{totalCount} {totalCount, plural, =1 {catégorie} other {catégories}}",
|
||||
"xpack.timelines.fieldBrowser.categoriesTitle": "Catégories",
|
||||
"xpack.timelines.fieldBrowser.categoryLabel": "Catégorie",
|
||||
"xpack.timelines.fieldBrowser.closeButton": "Fermer",
|
||||
"xpack.timelines.fieldBrowser.descriptionForScreenReaderOnly": "Description pour le champ {field} :",
|
||||
"xpack.timelines.fieldBrowser.descriptionLabel": "Description",
|
||||
"xpack.timelines.fieldBrowser.fieldBrowserTitle": "Champs",
|
||||
"xpack.timelines.fieldBrowser.fieldLabel": "Champ",
|
||||
"xpack.timelines.fieldBrowser.fieldName": "Nom",
|
||||
"xpack.timelines.fieldBrowser.fieldsCountShowing": "Affichage",
|
||||
"xpack.timelines.fieldBrowser.fieldsCountTitle": "{totalCount, plural, =1 {champ} other {champs}}",
|
||||
"xpack.timelines.fieldBrowser.fieldsTitle": "Champs",
|
||||
"xpack.timelines.fieldBrowser.filterPlaceholder": "Nom du champ",
|
||||
"xpack.timelines.fieldBrowser.noFieldsMatchInputLabel": "Aucun champ ne correspond à {searchInput}",
|
||||
"xpack.timelines.fieldBrowser.noFieldsMatchLabel": "Aucun champ ne correspond",
|
||||
"xpack.timelines.fieldBrowser.resetFieldsLink": "Réinitialiser les champs",
|
||||
"xpack.timelines.fieldBrowser.viewAll": "tous",
|
||||
"xpack.timelines.fieldBrowser.viewColumnCheckboxAriaLabel": "Afficher la colonne {field}",
|
||||
"xpack.timelines.fieldBrowser.viewLabel": "Afficher",
|
||||
"xpack.timelines.fieldBrowser.viewSelected": "sélectionné",
|
||||
"xpack.timelines.footer.autoRefreshActiveDescription": "Actualisation automatique active",
|
||||
"xpack.timelines.footer.autoRefreshActiveTooltip": "Lorsque l'actualisation automatique est activée, la chronologie vous montrera les derniers événements {numberOfItems} correspondant à votre recherche.",
|
||||
"xpack.timelines.footer.events": "Événements",
|
||||
|
|
|
@ -29922,26 +29922,6 @@
|
|||
"xpack.timelines.clipboard.copy.to.the.clipboard": "クリップボードにコピー",
|
||||
"xpack.timelines.clipboard.to.the.clipboard": "クリップボードに",
|
||||
"xpack.timelines.copyToClipboardTooltip": "クリップボードにコピー",
|
||||
"xpack.timelines.fieldBrowser.categoriesCountTitle": "{totalCount} {totalCount, plural, other {カテゴリ}}",
|
||||
"xpack.timelines.fieldBrowser.categoriesTitle": "カテゴリー",
|
||||
"xpack.timelines.fieldBrowser.categoryLabel": "カテゴリー",
|
||||
"xpack.timelines.fieldBrowser.closeButton": "閉じる",
|
||||
"xpack.timelines.fieldBrowser.descriptionForScreenReaderOnly": "フィールド {field} の説明:",
|
||||
"xpack.timelines.fieldBrowser.descriptionLabel": "説明",
|
||||
"xpack.timelines.fieldBrowser.fieldBrowserTitle": "フィールド",
|
||||
"xpack.timelines.fieldBrowser.fieldLabel": "フィールド",
|
||||
"xpack.timelines.fieldBrowser.fieldName": "名前",
|
||||
"xpack.timelines.fieldBrowser.fieldsCountShowing": "表示中",
|
||||
"xpack.timelines.fieldBrowser.fieldsCountTitle": "{totalCount, plural, other {フィールド}}",
|
||||
"xpack.timelines.fieldBrowser.fieldsTitle": "フィールド",
|
||||
"xpack.timelines.fieldBrowser.filterPlaceholder": "フィールド名",
|
||||
"xpack.timelines.fieldBrowser.noFieldsMatchInputLabel": "{searchInput} に一致するフィールドがありません",
|
||||
"xpack.timelines.fieldBrowser.noFieldsMatchLabel": "一致するフィールドがありません",
|
||||
"xpack.timelines.fieldBrowser.resetFieldsLink": "フィールドをリセット",
|
||||
"xpack.timelines.fieldBrowser.viewAll": "すべて",
|
||||
"xpack.timelines.fieldBrowser.viewColumnCheckboxAriaLabel": "{field} 列を表示",
|
||||
"xpack.timelines.fieldBrowser.viewLabel": "表示",
|
||||
"xpack.timelines.fieldBrowser.viewSelected": "選択済み",
|
||||
"xpack.timelines.footer.autoRefreshActiveDescription": "自動更新アクション",
|
||||
"xpack.timelines.footer.autoRefreshActiveTooltip": "自動更新が有効な間、タイムラインはクエリに一致する最新の {numberOfItems} 件のイベントを表示します。",
|
||||
"xpack.timelines.footer.events": "イベント",
|
||||
|
|
|
@ -29949,26 +29949,6 @@
|
|||
"xpack.timelines.clipboard.copy.to.the.clipboard": "复制到剪贴板",
|
||||
"xpack.timelines.clipboard.to.the.clipboard": "至剪贴板",
|
||||
"xpack.timelines.copyToClipboardTooltip": "复制到剪贴板",
|
||||
"xpack.timelines.fieldBrowser.categoriesCountTitle": "{totalCount} {totalCount, plural, other {个类别}}",
|
||||
"xpack.timelines.fieldBrowser.categoriesTitle": "类别",
|
||||
"xpack.timelines.fieldBrowser.categoryLabel": "类别",
|
||||
"xpack.timelines.fieldBrowser.closeButton": "关闭",
|
||||
"xpack.timelines.fieldBrowser.descriptionForScreenReaderOnly": "{field} 字段的描述:",
|
||||
"xpack.timelines.fieldBrowser.descriptionLabel": "描述",
|
||||
"xpack.timelines.fieldBrowser.fieldBrowserTitle": "字段",
|
||||
"xpack.timelines.fieldBrowser.fieldLabel": "字段",
|
||||
"xpack.timelines.fieldBrowser.fieldName": "名称",
|
||||
"xpack.timelines.fieldBrowser.fieldsCountShowing": "正在显示",
|
||||
"xpack.timelines.fieldBrowser.fieldsCountTitle": "{totalCount, plural, other {字段}}",
|
||||
"xpack.timelines.fieldBrowser.fieldsTitle": "字段",
|
||||
"xpack.timelines.fieldBrowser.filterPlaceholder": "字段名称",
|
||||
"xpack.timelines.fieldBrowser.noFieldsMatchInputLabel": "没有字段匹配“{searchInput}”",
|
||||
"xpack.timelines.fieldBrowser.noFieldsMatchLabel": "没有字段匹配",
|
||||
"xpack.timelines.fieldBrowser.resetFieldsLink": "重置字段",
|
||||
"xpack.timelines.fieldBrowser.viewAll": "全部",
|
||||
"xpack.timelines.fieldBrowser.viewColumnCheckboxAriaLabel": "查看 {field} 列",
|
||||
"xpack.timelines.fieldBrowser.viewLabel": "查看",
|
||||
"xpack.timelines.fieldBrowser.viewSelected": "已选定",
|
||||
"xpack.timelines.footer.autoRefreshActiveDescription": "自动刷新已启用",
|
||||
"xpack.timelines.footer.autoRefreshActiveTooltip": "自动刷新已启用时,时间线将显示匹配查询的最近 {numberOfItems} 个事件。",
|
||||
"xpack.timelines.footer.events": "事件",
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* 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 { css } from '@emotion/react';
|
||||
import { UseEuiTheme } from '@elastic/eui';
|
||||
|
||||
export const styles = {
|
||||
badgesGroup: ({ euiTheme }: { euiTheme: UseEuiTheme['euiTheme'] }) => css`
|
||||
margin-top: ${euiTheme.size.xs};
|
||||
min-height: 24px;
|
||||
`,
|
||||
};
|
|
@ -7,9 +7,7 @@
|
|||
|
||||
import { render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { TestProviders } from '../../../../mock';
|
||||
|
||||
import { CategoriesBadges } from './categories_badges';
|
||||
import { CategoriesBadges, CategoriesBadgesProps } from './categories_badges';
|
||||
|
||||
const mockSetSelectedCategoryIds = jest.fn();
|
||||
const defaultProps = {
|
||||
|
@ -17,17 +15,16 @@ const defaultProps = {
|
|||
selectedCategoryIds: [],
|
||||
};
|
||||
|
||||
const renderComponent = (props: Partial<CategoriesBadgesProps> = {}) =>
|
||||
render(<CategoriesBadges {...{ ...defaultProps, ...props }} />);
|
||||
|
||||
describe('CategoriesBadges', () => {
|
||||
beforeEach(() => {
|
||||
mockSetSelectedCategoryIds.mockClear();
|
||||
});
|
||||
|
||||
it('should render empty badges', () => {
|
||||
const result = render(
|
||||
<TestProviders>
|
||||
<CategoriesBadges {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
const result = renderComponent();
|
||||
|
||||
const badges = result.getByTestId('category-badges');
|
||||
expect(badges).toBeInTheDocument();
|
||||
|
@ -35,11 +32,7 @@ describe('CategoriesBadges', () => {
|
|||
});
|
||||
|
||||
it('should render the selector button with selected categories', () => {
|
||||
const result = render(
|
||||
<TestProviders>
|
||||
<CategoriesBadges {...defaultProps} selectedCategoryIds={['base', 'event']} />
|
||||
</TestProviders>
|
||||
);
|
||||
const result = renderComponent({ selectedCategoryIds: ['base', 'event'] });
|
||||
|
||||
const badges = result.getByTestId('category-badges');
|
||||
expect(badges.childNodes.length).toBe(2);
|
||||
|
@ -48,11 +41,7 @@ describe('CategoriesBadges', () => {
|
|||
});
|
||||
|
||||
it('should call the set selected callback when badge unselect button clicked', () => {
|
||||
const result = render(
|
||||
<TestProviders>
|
||||
<CategoriesBadges {...defaultProps} selectedCategoryIds={['base', 'event']} />
|
||||
</TestProviders>
|
||||
);
|
||||
const result = renderComponent({ selectedCategoryIds: ['base', 'event'] });
|
||||
|
||||
result.getByTestId('category-badge-unselect-base').click();
|
||||
expect(mockSetSelectedCategoryIds).toHaveBeenCalledWith(['event']);
|
|
@ -4,26 +4,20 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { EuiBadge, EuiFlexGroup, EuiFlexItem, useEuiTheme } from '@elastic/eui';
|
||||
import { styles } from './categories_badges.styles';
|
||||
|
||||
interface CategoriesBadgesProps {
|
||||
export interface CategoriesBadgesProps {
|
||||
setSelectedCategoryIds: (categoryIds: string[]) => void;
|
||||
selectedCategoryIds: string[];
|
||||
}
|
||||
|
||||
const CategoriesBadgesGroup = styled(EuiFlexGroup)`
|
||||
margin-top: ${({ theme }) => theme.eui.euiSizeXS};
|
||||
min-height: 24px;
|
||||
`;
|
||||
CategoriesBadgesGroup.displayName = 'CategoriesBadgesGroup';
|
||||
|
||||
const CategoriesBadgesComponent: React.FC<CategoriesBadgesProps> = ({
|
||||
setSelectedCategoryIds,
|
||||
selectedCategoryIds,
|
||||
}) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const onUnselectCategory = useCallback(
|
||||
(categoryId: string) => {
|
||||
setSelectedCategoryIds(
|
||||
|
@ -34,7 +28,12 @@ const CategoriesBadgesComponent: React.FC<CategoriesBadgesProps> = ({
|
|||
);
|
||||
|
||||
return (
|
||||
<CategoriesBadgesGroup data-test-subj="category-badges" gutterSize="xs" wrap>
|
||||
<EuiFlexGroup
|
||||
css={styles.badgesGroup({ euiTheme })}
|
||||
data-test-subj="category-badges"
|
||||
gutterSize="xs"
|
||||
wrap
|
||||
>
|
||||
{selectedCategoryIds.map((categoryId) => (
|
||||
<EuiFlexItem grow={false} key={categoryId}>
|
||||
<EuiBadge
|
||||
|
@ -49,7 +48,7 @@ const CategoriesBadgesComponent: React.FC<CategoriesBadgesProps> = ({
|
|||
</EuiBadge>
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
</CategoriesBadgesGroup>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
|
@ -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.
|
||||
*/
|
||||
|
||||
export { CategoriesBadges } from './categories_badges';
|
||||
export type { CategoriesBadgesProps } from './categories_badges';
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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 { css } from '@emotion/react';
|
||||
|
||||
export const styles = {
|
||||
countBadge: css`
|
||||
margin-left: 5px;
|
||||
`,
|
||||
categoryName: ({ bold }: { bold: boolean }) => css`
|
||||
font-weight: ${bold ? 'bold' : 'normal'};
|
||||
`,
|
||||
selectableContainer: css`
|
||||
width: 300px;
|
||||
`,
|
||||
};
|
|
@ -4,11 +4,9 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { mockBrowserFields, TestProviders } from '../../../../mock';
|
||||
|
||||
import { render } from '@testing-library/react';
|
||||
import { mockBrowserFields } from '../../mock';
|
||||
import { CategoriesSelector } from './categories_selector';
|
||||
|
||||
const mockSetSelectedCategoryIds = jest.fn();
|
||||
|
@ -25,11 +23,7 @@ describe('CategoriesSelector', () => {
|
|||
|
||||
it('should render the default selector button', () => {
|
||||
const categoriesCount = Object.keys(mockBrowserFields).length;
|
||||
const result = render(
|
||||
<TestProviders>
|
||||
<CategoriesSelector {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
const result = render(<CategoriesSelector {...defaultProps} />);
|
||||
|
||||
expect(result.getByTestId('categories-filter-button')).toBeInTheDocument();
|
||||
expect(result.getByText('Categories')).toBeInTheDocument();
|
||||
|
@ -38,9 +32,7 @@ describe('CategoriesSelector', () => {
|
|||
|
||||
it('should render the selector button with selected categories', () => {
|
||||
const result = render(
|
||||
<TestProviders>
|
||||
<CategoriesSelector {...defaultProps} selectedCategoryIds={['base', 'event']} />
|
||||
</TestProviders>
|
||||
<CategoriesSelector {...defaultProps} selectedCategoryIds={['base', 'event']} />
|
||||
);
|
||||
|
||||
expect(result.getByTestId('categories-filter-button')).toBeInTheDocument();
|
||||
|
@ -49,11 +41,7 @@ describe('CategoriesSelector', () => {
|
|||
});
|
||||
|
||||
it('should open the category selector', () => {
|
||||
const result = render(
|
||||
<TestProviders>
|
||||
<CategoriesSelector {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
const result = render(<CategoriesSelector {...defaultProps} />);
|
||||
|
||||
result.getByTestId('categories-filter-button').click();
|
||||
|
||||
|
@ -63,27 +51,18 @@ describe('CategoriesSelector', () => {
|
|||
|
||||
it('should open the category selector with selected categories', () => {
|
||||
const result = render(
|
||||
<TestProviders>
|
||||
<CategoriesSelector {...defaultProps} selectedCategoryIds={['base', 'event']} />
|
||||
</TestProviders>
|
||||
<CategoriesSelector {...defaultProps} selectedCategoryIds={['base', 'event']} />
|
||||
);
|
||||
|
||||
result.getByTestId('categories-filter-button').click();
|
||||
|
||||
expect(result.getByTestId('categories-selector-search')).toBeInTheDocument();
|
||||
expect(result.getByTestId(`categories-selector-option-base`)).toBeInTheDocument();
|
||||
expect(result.getByTestId(`categories-selector-option-name-base`)).toHaveStyleRule(
|
||||
'font-weight',
|
||||
'bold'
|
||||
);
|
||||
expect(result.getByTestId(`categories-selector-option-name-base`)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call setSelectedCategoryIds when category selected', () => {
|
||||
const result = render(
|
||||
<TestProviders>
|
||||
<CategoriesSelector {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
const result = render(<CategoriesSelector {...defaultProps} />);
|
||||
|
||||
result.getByTestId('categories-filter-button').click();
|
||||
result.getByTestId(`categories-selector-option-base`).click();
|
|
@ -4,10 +4,10 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { omit } from 'lodash';
|
||||
import {
|
||||
EuiBadge,
|
||||
EuiFilterButton,
|
||||
EuiFilterGroup,
|
||||
EuiFlexGroup,
|
||||
|
@ -17,10 +17,10 @@ import {
|
|||
EuiSelectable,
|
||||
FilterChecked,
|
||||
} from '@elastic/eui';
|
||||
import { BrowserFields } from '../../../../../common';
|
||||
import * as i18n from './translations';
|
||||
import { CountBadge, getFieldCount, CategoryName, CategorySelectableContainer } from './helpers';
|
||||
import { isEscape } from '../../../../../common/utils/accessibility';
|
||||
import type { BrowserFields } from '../../types';
|
||||
import * as i18n from '../../translations';
|
||||
import { getFieldCount, isEscape } from '../../helpers';
|
||||
import { styles } from './categories_selector.styles';
|
||||
|
||||
interface CategoriesSelectorProps {
|
||||
/**
|
||||
|
@ -57,15 +57,15 @@ const renderOption = (option: CategoryOption, searchValue: string) => {
|
|||
justifyContent="spaceBetween"
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<CategoryName
|
||||
<span
|
||||
css={styles.categoryName({ bold: checked === 'on' })}
|
||||
data-test-subj={`categories-selector-option-name-${idAttr}`}
|
||||
bold={checked === 'on'}
|
||||
>
|
||||
<EuiHighlight search={searchValue}>{label}</EuiHighlight>
|
||||
</CategoryName>
|
||||
</span>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<CountBadge>{count}</CountBadge>
|
||||
<EuiBadge css={styles.countBadge}>{count}</EuiBadge>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
|
@ -143,7 +143,8 @@ const CategoriesSelectorComponent: React.FC<CategoriesSelectorProps> = ({
|
|||
closePopover={closePopover}
|
||||
panelPaddingSize="none"
|
||||
>
|
||||
<CategorySelectableContainer
|
||||
<div
|
||||
css={styles.selectableContainer}
|
||||
onKeyDown={onKeyDown}
|
||||
data-test-subj="categories-selector-container"
|
||||
>
|
||||
|
@ -164,7 +165,7 @@ const CategoriesSelectorComponent: React.FC<CategoriesSelectorProps> = ({
|
|||
</>
|
||||
)}
|
||||
</EuiSelectable>
|
||||
</CategorySelectableContainer>
|
||||
</div>
|
||||
</EuiPopover>
|
||||
</EuiFilterGroup>
|
||||
);
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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 { CategoriesSelector } from './categories_selector';
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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 { css } from '@emotion/react';
|
||||
|
||||
export const styles = {
|
||||
icon: css`
|
||||
margin: 0 4px;
|
||||
position: relative;
|
||||
top: -1px;
|
||||
`,
|
||||
truncatable: css`
|
||||
&,
|
||||
& * {
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: top;
|
||||
white-space: nowrap;
|
||||
}
|
||||
`,
|
||||
description: css`
|
||||
user-select: text;
|
||||
width: 400px;
|
||||
`,
|
||||
};
|
|
@ -4,32 +4,16 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { omit } from 'lodash/fp';
|
||||
import { render } from '@testing-library/react';
|
||||
import { EuiInMemoryTable } from '@elastic/eui';
|
||||
import { mockBrowserFields } from '../../../../mock';
|
||||
import { defaultColumnHeaderType } from '../../body/column_headers/default_headers';
|
||||
import { DEFAULT_DATE_COLUMN_MIN_WIDTH } from '../../body/constants';
|
||||
import { mockBrowserFields } from '../../mock';
|
||||
|
||||
import { getFieldColumns, getFieldItems } from './field_items';
|
||||
import { ColumnHeaderOptions } from '../../../../../common/types';
|
||||
|
||||
const timestampFieldId = '@timestamp';
|
||||
const columnHeaders: ColumnHeaderOptions[] = [
|
||||
{
|
||||
category: 'base',
|
||||
columnHeaderType: defaultColumnHeaderType,
|
||||
description:
|
||||
'Date/time when the event originated.\nFor log events this is the date/time when the event was generated, and not when it was read.\nRequired field for all events.',
|
||||
example: '2016-05-23T08:05:34.853Z',
|
||||
id: timestampFieldId,
|
||||
type: 'date',
|
||||
aggregatable: true,
|
||||
initialWidth: DEFAULT_DATE_COLUMN_MIN_WIDTH,
|
||||
},
|
||||
];
|
||||
const columnIds = [timestampFieldId];
|
||||
|
||||
describe('field_items', () => {
|
||||
describe('getFieldItems', () => {
|
||||
|
@ -39,7 +23,7 @@ describe('field_items', () => {
|
|||
const fieldItems = getFieldItems({
|
||||
selectedCategoryIds: ['base'],
|
||||
browserFields: { base: { fields: { [timestampFieldId]: timestampField } } },
|
||||
columnHeaders: [],
|
||||
columnIds: [],
|
||||
});
|
||||
|
||||
expect(fieldItems[0]).toEqual({
|
||||
|
@ -57,7 +41,7 @@ describe('field_items', () => {
|
|||
const fieldItems = getFieldItems({
|
||||
selectedCategoryIds: ['base'],
|
||||
browserFields: { base: { fields: { [timestampFieldId]: timestampField } } },
|
||||
columnHeaders,
|
||||
columnIds,
|
||||
});
|
||||
|
||||
expect(fieldItems[0]).toMatchObject({
|
||||
|
@ -78,7 +62,7 @@ describe('field_items', () => {
|
|||
},
|
||||
},
|
||||
},
|
||||
columnHeaders,
|
||||
columnIds,
|
||||
});
|
||||
|
||||
expect(fieldItems[0]).toMatchObject({
|
||||
|
@ -95,7 +79,7 @@ describe('field_items', () => {
|
|||
const fieldItems = getFieldItems({
|
||||
selectedCategoryIds: [],
|
||||
browserFields: mockBrowserFields,
|
||||
columnHeaders: [],
|
||||
columnIds: [],
|
||||
});
|
||||
|
||||
expect(fieldItems.length).toBe(fieldCount);
|
||||
|
@ -112,7 +96,7 @@ describe('field_items', () => {
|
|||
const fieldItems = getFieldItems({
|
||||
selectedCategoryIds,
|
||||
browserFields: mockBrowserFields,
|
||||
columnHeaders: [],
|
||||
columnIds: [],
|
||||
});
|
||||
|
||||
expect(fieldItems.length).toBe(fieldCount);
|
||||
|
@ -195,7 +179,7 @@ describe('field_items', () => {
|
|||
const fieldItems = getFieldItems({
|
||||
selectedCategoryIds: ['base'],
|
||||
browserFields: { base: { fields: { [timestampFieldId]: timestampField } } },
|
||||
columnHeaders: [],
|
||||
columnIds: [],
|
||||
});
|
||||
|
||||
const columns = getFieldColumns(getFieldColumnsParams);
|
||||
|
@ -218,7 +202,7 @@ describe('field_items', () => {
|
|||
const fieldItems = getFieldItems({
|
||||
selectedCategoryIds: ['base'],
|
||||
browserFields: { base: { fields: { [timestampFieldId]: timestampField } } },
|
||||
columnHeaders: [],
|
||||
columnIds: [],
|
||||
});
|
||||
|
||||
const columns = getFieldColumns(getFieldColumnsParams);
|
|
@ -4,7 +4,6 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
EuiCheckbox,
|
||||
|
@ -18,33 +17,17 @@ import {
|
|||
EuiTableActionsColumnType,
|
||||
} from '@elastic/eui';
|
||||
import { uniqBy } from 'lodash/fp';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { getEmptyValue } from '../../../empty_value';
|
||||
import { getExampleText, getIconFromType } from '../../../utils/helpers';
|
||||
import type { BrowserFields } from '../../../../../common/search_strategy';
|
||||
import { getEmptyValue, getExampleText, getIconFromType } from '../../helpers';
|
||||
import type {
|
||||
ColumnHeaderOptions,
|
||||
BrowserFields,
|
||||
BrowserFieldItem,
|
||||
FieldTableColumns,
|
||||
GetFieldTableColumns,
|
||||
} from '../../../../../common/types';
|
||||
import { TruncatableText } from '../../../truncatable_text';
|
||||
import { FieldName } from './field_name';
|
||||
import * as i18n from './translations';
|
||||
|
||||
const TypeIcon = styled(EuiIcon)`
|
||||
margin: 0 4px;
|
||||
position: relative;
|
||||
top: -1px;
|
||||
`;
|
||||
TypeIcon.displayName = 'TypeIcon';
|
||||
|
||||
export const Description = styled.span`
|
||||
user-select: text;
|
||||
width: 400px;
|
||||
`;
|
||||
Description.displayName = 'Description';
|
||||
} from '../../types';
|
||||
import { FieldName } from '../field_name';
|
||||
import * as i18n from '../../translations';
|
||||
import { styles } from './field_items.style';
|
||||
|
||||
/**
|
||||
* Returns the field items of all categories selected
|
||||
|
@ -52,15 +35,15 @@ Description.displayName = 'Description';
|
|||
export const getFieldItems = ({
|
||||
browserFields,
|
||||
selectedCategoryIds,
|
||||
columnHeaders,
|
||||
columnIds,
|
||||
}: {
|
||||
browserFields: BrowserFields;
|
||||
selectedCategoryIds: string[];
|
||||
columnHeaders: ColumnHeaderOptions[];
|
||||
columnIds: string[];
|
||||
}): BrowserFieldItem[] => {
|
||||
const categoryIds =
|
||||
selectedCategoryIds.length > 0 ? selectedCategoryIds : Object.keys(browserFields);
|
||||
const selectedFieldIds = new Set(columnHeaders.map(({ id }) => id));
|
||||
const selectedFieldIds = new Set(columnIds);
|
||||
|
||||
return uniqBy(
|
||||
'name',
|
||||
|
@ -93,8 +76,9 @@ const getDefaultFieldTableColumns = (highlight: string): FieldTableColumns => [
|
|||
<EuiFlexGroup alignItems="center" gutterSize="none">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip content={type}>
|
||||
<TypeIcon
|
||||
<EuiIcon
|
||||
data-test-subj={`field-${name}-icon`}
|
||||
css={styles.icon}
|
||||
type={getIconFromType(type ?? null)}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
|
@ -118,11 +102,11 @@ const getDefaultFieldTableColumns = (highlight: string): FieldTableColumns => [
|
|||
<EuiScreenReaderOnly data-test-subj="descriptionForScreenReaderOnly">
|
||||
<p>{i18n.DESCRIPTION_FOR_FIELD(name)}</p>
|
||||
</EuiScreenReaderOnly>
|
||||
<TruncatableText>
|
||||
<Description data-test-subj={`field-${name}-description`}>
|
||||
<span css={styles.truncatable}>
|
||||
<span css={styles.description} data-test-subj={`field-${name}-description`}>
|
||||
{`${description ?? getEmptyValue()} ${getExampleText(example)}`}
|
||||
</Description>
|
||||
</TruncatableText>
|
||||
</span>
|
||||
</span>
|
||||
</>
|
||||
</EuiToolTip>
|
||||
),
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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 { getFieldItems, getFieldColumns, isActionsColumn } from './field_items';
|
|
@ -7,27 +7,13 @@
|
|||
|
||||
import { mount } from 'enzyme';
|
||||
import React from 'react';
|
||||
import { mockBrowserFields, TestProviders } from '../../../../mock';
|
||||
import { getColumnsWithTimestamp } from '../../../utils/helpers';
|
||||
|
||||
import { FieldName } from './field_name';
|
||||
|
||||
const categoryId = 'base';
|
||||
const timestampFieldId = '@timestamp';
|
||||
|
||||
const defaultProps = {
|
||||
categoryId,
|
||||
categoryColumns: getColumnsWithTimestamp({
|
||||
browserFields: mockBrowserFields,
|
||||
category: categoryId,
|
||||
}),
|
||||
closePopOverTrigger: false,
|
||||
fieldId: timestampFieldId,
|
||||
handleClosePopOverTrigger: jest.fn(),
|
||||
hoverActionsOwnFocus: false,
|
||||
onCloseRequested: jest.fn(),
|
||||
onUpdateColumns: jest.fn(),
|
||||
setClosePopOverTrigger: jest.fn(),
|
||||
};
|
||||
|
||||
describe('FieldName', () => {
|
||||
|
@ -36,11 +22,7 @@ describe('FieldName', () => {
|
|||
});
|
||||
|
||||
test('it renders the field name', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<FieldName {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
const wrapper = mount(<FieldName {...defaultProps} />);
|
||||
|
||||
expect(
|
||||
wrapper.find(`[data-test-subj="field-${timestampFieldId}-name"]`).first().text()
|
||||
|
@ -50,11 +32,7 @@ describe('FieldName', () => {
|
|||
test('it highlights the text specified by the `highlight` prop', () => {
|
||||
const highlight = 'stamp';
|
||||
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<FieldName {...{ ...defaultProps, highlight }} />
|
||||
</TestProviders>
|
||||
);
|
||||
const wrapper = mount(<FieldName {...{ ...defaultProps, highlight }} />);
|
||||
|
||||
expect(wrapper.find('mark').first().text()).toEqual(highlight);
|
||||
});
|
|
@ -4,4 +4,5 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
export { FieldBrowser } from './field_browser';
|
||||
|
||||
export { FieldName } from './field_name';
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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 { UseEuiTheme } from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
|
||||
export const styles = {
|
||||
tableContainer: ({
|
||||
height,
|
||||
euiTheme,
|
||||
}: {
|
||||
height: number;
|
||||
euiTheme: UseEuiTheme['euiTheme'];
|
||||
}) => css`
|
||||
margin-top: ${euiTheme.size.xs};
|
||||
border-top: ${euiTheme.border.thin};
|
||||
height: ${height}px;
|
||||
overflow: hidden;
|
||||
`,
|
||||
};
|
|
@ -7,34 +7,19 @@
|
|||
|
||||
import React from 'react';
|
||||
import { render, RenderResult } from '@testing-library/react';
|
||||
import { mockBrowserFields, TestProviders } from '../../../../mock';
|
||||
import { defaultColumnHeaderType } from '../../body/column_headers/default_headers';
|
||||
import { DEFAULT_DATE_COLUMN_MIN_WIDTH } from '../../body/constants';
|
||||
import { mockBrowserFields } from '../../mock';
|
||||
|
||||
import { ColumnHeaderOptions } from '../../../../../common';
|
||||
import { FieldTable, FieldTableProps } from './field_table';
|
||||
|
||||
const timestampFieldId = '@timestamp';
|
||||
|
||||
const columnHeaders: ColumnHeaderOptions[] = [
|
||||
{
|
||||
category: 'base',
|
||||
columnHeaderType: defaultColumnHeaderType,
|
||||
description:
|
||||
'Date/time when the event originated.\nFor log events this is the date/time when the event was generated, and not when it was read.\nRequired field for all events.',
|
||||
example: '2016-05-23T08:05:34.853Z',
|
||||
id: timestampFieldId,
|
||||
type: 'date',
|
||||
aggregatable: true,
|
||||
initialWidth: DEFAULT_DATE_COLUMN_MIN_WIDTH,
|
||||
},
|
||||
];
|
||||
const columnIds = [timestampFieldId];
|
||||
|
||||
const mockOnToggleColumn = jest.fn();
|
||||
|
||||
const defaultProps: FieldTableProps = {
|
||||
selectedCategoryIds: [],
|
||||
columnHeaders: [],
|
||||
columnIds: [],
|
||||
filteredBrowserFields: {},
|
||||
searchInput: '',
|
||||
filterSelectedEnabled: false,
|
||||
|
@ -43,6 +28,11 @@ const defaultProps: FieldTableProps = {
|
|||
onToggleColumn: mockOnToggleColumn,
|
||||
};
|
||||
|
||||
const getComponent = (props: Partial<FieldTableProps> = {}) => (
|
||||
<FieldTable {...{ ...defaultProps, ...props }} />
|
||||
);
|
||||
const renderComponent = (props: Partial<FieldTableProps> = {}) => render(getComponent(props));
|
||||
|
||||
describe('FieldTable', () => {
|
||||
const timestampField = mockBrowserFields.base.fields![timestampFieldId];
|
||||
const defaultPageSize = 10;
|
||||
|
@ -52,21 +42,13 @@ describe('FieldTable', () => {
|
|||
});
|
||||
|
||||
it('should render empty field table', () => {
|
||||
const result = render(
|
||||
<TestProviders>
|
||||
<FieldTable {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
const result = renderComponent();
|
||||
|
||||
expect(result.getByText('No items found')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render field table with fields of all categories', () => {
|
||||
const result = render(
|
||||
<TestProviders>
|
||||
<FieldTable {...defaultProps} filteredBrowserFields={mockBrowserFields} />
|
||||
</TestProviders>
|
||||
);
|
||||
const result = renderComponent({ filteredBrowserFields: mockBrowserFields });
|
||||
|
||||
expect(result.container.getElementsByClassName('euiTableRow').length).toBe(defaultPageSize);
|
||||
});
|
||||
|
@ -80,15 +62,10 @@ describe('FieldTable', () => {
|
|||
0
|
||||
);
|
||||
|
||||
const result = render(
|
||||
<TestProviders>
|
||||
<FieldTable
|
||||
{...defaultProps}
|
||||
selectedCategoryIds={selectedCategoryIds}
|
||||
filteredBrowserFields={mockBrowserFields}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
const result = renderComponent({
|
||||
selectedCategoryIds,
|
||||
filteredBrowserFields: mockBrowserFields,
|
||||
});
|
||||
|
||||
expect(result.container.getElementsByClassName('euiTableRow').length).toBe(fieldCount);
|
||||
});
|
||||
|
@ -102,46 +79,31 @@ describe('FieldTable', () => {
|
|||
},
|
||||
];
|
||||
|
||||
const result = render(
|
||||
<TestProviders>
|
||||
<FieldTable
|
||||
{...defaultProps}
|
||||
getFieldTableColumns={() => fieldTableColumns}
|
||||
filteredBrowserFields={mockBrowserFields}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
const result = renderComponent({
|
||||
getFieldTableColumns: () => fieldTableColumns,
|
||||
filteredBrowserFields: mockBrowserFields,
|
||||
});
|
||||
|
||||
expect(result.getAllByText('Custom column').length).toBeGreaterThan(0);
|
||||
expect(result.getAllByTestId('customColumn').length).toEqual(defaultPageSize);
|
||||
});
|
||||
|
||||
it('should render field table with unchecked field', () => {
|
||||
const result = render(
|
||||
<TestProviders>
|
||||
<FieldTable
|
||||
{...defaultProps}
|
||||
selectedCategoryIds={['base']}
|
||||
filteredBrowserFields={{ base: { fields: { [timestampFieldId]: timestampField } } }}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
const result = renderComponent({
|
||||
selectedCategoryIds: ['base'],
|
||||
filteredBrowserFields: { base: { fields: { [timestampFieldId]: timestampField } } },
|
||||
});
|
||||
|
||||
const checkbox = result.getByTestId(`field-${timestampFieldId}-checkbox`);
|
||||
expect(checkbox).not.toHaveAttribute('checked');
|
||||
});
|
||||
|
||||
it('should render field table with checked field', () => {
|
||||
const result = render(
|
||||
<TestProviders>
|
||||
<FieldTable
|
||||
{...defaultProps}
|
||||
selectedCategoryIds={['base']}
|
||||
columnHeaders={columnHeaders}
|
||||
filteredBrowserFields={{ base: { fields: { [timestampFieldId]: timestampField } } }}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
const result = renderComponent({
|
||||
selectedCategoryIds: ['base'],
|
||||
columnIds,
|
||||
filteredBrowserFields: { base: { fields: { [timestampFieldId]: timestampField } } },
|
||||
});
|
||||
|
||||
const checkbox = result.getByTestId(`field-${timestampFieldId}-checkbox`);
|
||||
expect(checkbox).toHaveAttribute('checked');
|
||||
|
@ -149,16 +111,11 @@ describe('FieldTable', () => {
|
|||
|
||||
describe('selection', () => {
|
||||
it('should call onToggleColumn callback when field unchecked', () => {
|
||||
const result = render(
|
||||
<TestProviders>
|
||||
<FieldTable
|
||||
{...defaultProps}
|
||||
selectedCategoryIds={['base']}
|
||||
columnHeaders={columnHeaders}
|
||||
filteredBrowserFields={{ base: { fields: { [timestampFieldId]: timestampField } } }}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
const result = renderComponent({
|
||||
selectedCategoryIds: ['base'],
|
||||
columnIds,
|
||||
filteredBrowserFields: { base: { fields: { [timestampFieldId]: timestampField } } },
|
||||
});
|
||||
|
||||
result.getByTestId(`field-${timestampFieldId}-checkbox`).click();
|
||||
|
||||
|
@ -167,15 +124,10 @@ describe('FieldTable', () => {
|
|||
});
|
||||
|
||||
it('should call onToggleColumn callback when field checked', () => {
|
||||
const result = render(
|
||||
<TestProviders>
|
||||
<FieldTable
|
||||
{...defaultProps}
|
||||
selectedCategoryIds={['base']}
|
||||
filteredBrowserFields={{ base: { fields: { [timestampFieldId]: timestampField } } }}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
const result = renderComponent({
|
||||
selectedCategoryIds: ['base'],
|
||||
filteredBrowserFields: { base: { fields: { [timestampFieldId]: timestampField } } },
|
||||
});
|
||||
|
||||
result.getByTestId(`field-${timestampFieldId}-checkbox`).click();
|
||||
|
||||
|
@ -192,17 +144,12 @@ describe('FieldTable', () => {
|
|||
result.getByTestId('pagination-button-1').click();
|
||||
};
|
||||
|
||||
const defaultPaginationProps: FieldTableProps = {
|
||||
...defaultProps,
|
||||
const paginationProps = {
|
||||
filteredBrowserFields: mockBrowserFields,
|
||||
};
|
||||
|
||||
it('should paginate on page clicked', () => {
|
||||
const result = render(
|
||||
<TestProviders>
|
||||
<FieldTable {...defaultPaginationProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
const result = renderComponent(paginationProps);
|
||||
|
||||
expect(isAtFirstPage(result)).toBeTruthy();
|
||||
|
||||
|
@ -212,11 +159,7 @@ describe('FieldTable', () => {
|
|||
});
|
||||
|
||||
it('should not reset on field checked', () => {
|
||||
const result = render(
|
||||
<TestProviders>
|
||||
<FieldTable {...defaultPaginationProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
const result = renderComponent(paginationProps);
|
||||
|
||||
changePage(result);
|
||||
|
||||
|
@ -227,22 +170,19 @@ describe('FieldTable', () => {
|
|||
});
|
||||
|
||||
it('should reset on filter change', () => {
|
||||
const result = render(
|
||||
<FieldTable
|
||||
{...defaultPaginationProps}
|
||||
selectedCategoryIds={['destination', 'event', 'client', 'agent', 'host']}
|
||||
/>,
|
||||
{ wrapper: TestProviders }
|
||||
);
|
||||
const result = renderComponent({
|
||||
...paginationProps,
|
||||
selectedCategoryIds: ['destination', 'event', 'client', 'agent', 'host'],
|
||||
});
|
||||
|
||||
changePage(result);
|
||||
expect(isAtFirstPage(result)).toBeFalsy();
|
||||
|
||||
result.rerender(
|
||||
<FieldTable
|
||||
{...defaultPaginationProps}
|
||||
selectedCategoryIds={['destination', 'event', 'client', 'agent']}
|
||||
/>
|
||||
getComponent({
|
||||
...paginationProps,
|
||||
selectedCategoryIds: ['destination', 'event', 'client', 'agent'],
|
||||
})
|
||||
);
|
||||
|
||||
expect(isAtFirstPage(result)).toBeTruthy();
|
|
@ -4,23 +4,20 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { EuiInMemoryTable, Pagination, Direction } from '@elastic/eui';
|
||||
import { BrowserFields, ColumnHeaderOptions } from '../../../../../common';
|
||||
import { getFieldColumns, getFieldItems, isActionsColumn } from './field_items';
|
||||
import { CATEGORY_TABLE_CLASS_NAME, TABLE_HEIGHT } from './helpers';
|
||||
import type { GetFieldTableColumns } from '../../../../../common/types/field_browser';
|
||||
import { EuiInMemoryTable, Pagination, Direction, useEuiTheme } from '@elastic/eui';
|
||||
import { getFieldColumns, getFieldItems, isActionsColumn } from '../field_items';
|
||||
import { CATEGORY_TABLE_CLASS_NAME, TABLE_HEIGHT } from '../../helpers';
|
||||
import type { BrowserFields, FieldBrowserProps, GetFieldTableColumns } from '../../types';
|
||||
import { FieldTableHeader } from './field_table_header';
|
||||
import { styles } from './field_table.styles';
|
||||
|
||||
const DEFAULT_SORTING: { field: string; direction: Direction } = {
|
||||
field: '',
|
||||
direction: 'asc',
|
||||
} as const;
|
||||
|
||||
export interface FieldTableProps {
|
||||
columnHeaders: ColumnHeaderOptions[];
|
||||
export interface FieldTableProps extends Pick<FieldBrowserProps, 'columnIds' | 'onToggleColumn'> {
|
||||
/**
|
||||
* A map of categoryId -> metadata about the fields in that category,
|
||||
* filtered such that the name of every field in the category includes
|
||||
|
@ -30,7 +27,6 @@ export interface FieldTableProps {
|
|||
/** when true, show only the the selected field */
|
||||
filterSelectedEnabled: boolean;
|
||||
onFilterSelectedChange: (enabled: boolean) => void;
|
||||
onToggleColumn: (fieldId: string) => void;
|
||||
/**
|
||||
* Optional function to customize field table columns
|
||||
*/
|
||||
|
@ -48,21 +44,8 @@ export interface FieldTableProps {
|
|||
onHide: () => void;
|
||||
}
|
||||
|
||||
const TableContainer = styled.div<{ height: number }>`
|
||||
margin-top: ${({ theme }) => theme.eui.euiSizeXS};
|
||||
border-top: ${({ theme }) => theme.eui.euiBorderThin};
|
||||
${({ height }) => `height: ${height}px`};
|
||||
overflow: hidden;
|
||||
`;
|
||||
TableContainer.displayName = 'TableContainer';
|
||||
|
||||
const Count = styled.span`
|
||||
font-weight: bold;
|
||||
`;
|
||||
Count.displayName = 'Count';
|
||||
|
||||
const FieldTableComponent: React.FC<FieldTableProps> = ({
|
||||
columnHeaders,
|
||||
columnIds,
|
||||
filteredBrowserFields,
|
||||
filterSelectedEnabled,
|
||||
getFieldTableColumns,
|
||||
|
@ -72,6 +55,7 @@ const FieldTableComponent: React.FC<FieldTableProps> = ({
|
|||
onToggleColumn,
|
||||
onHide,
|
||||
}) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const [pageIndex, setPageIndex] = useState(0);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
|
||||
|
@ -83,9 +67,9 @@ const FieldTableComponent: React.FC<FieldTableProps> = ({
|
|||
getFieldItems({
|
||||
browserFields: filteredBrowserFields,
|
||||
selectedCategoryIds,
|
||||
columnHeaders,
|
||||
columnIds,
|
||||
}),
|
||||
[columnHeaders, filteredBrowserFields, selectedCategoryIds]
|
||||
[columnIds, filteredBrowserFields, selectedCategoryIds]
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -147,7 +131,7 @@ const FieldTableComponent: React.FC<FieldTableProps> = ({
|
|||
onFilterSelectedChange={onFilterSelectedChange}
|
||||
/>
|
||||
|
||||
<TableContainer height={TABLE_HEIGHT}>
|
||||
<div css={styles.tableContainer({ height: TABLE_HEIGHT, euiTheme })}>
|
||||
<EuiInMemoryTable
|
||||
data-test-subj="field-table"
|
||||
className={`${CATEGORY_TABLE_CLASS_NAME} eui-yScroll`}
|
||||
|
@ -160,9 +144,8 @@ const FieldTableComponent: React.FC<FieldTableProps> = ({
|
|||
onChange={onTableChange}
|
||||
compressed
|
||||
/>
|
||||
</TableContainer>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const FieldTable = React.memo(FieldTableComponent);
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* 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 { css } from '@emotion/react';
|
||||
|
||||
export const styles = {
|
||||
count: css`
|
||||
font-weight: bold;
|
||||
`,
|
||||
};
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { TestProviders } from '../../../../mock';
|
||||
import { FieldTableHeader, FieldTableHeaderProps } from './field_table_header';
|
||||
|
||||
const mockOnFilterSelectedChange = jest.fn();
|
||||
|
@ -20,30 +19,18 @@ const defaultProps: FieldTableHeaderProps = {
|
|||
describe('FieldTableHeader', () => {
|
||||
describe('FieldCount', () => {
|
||||
it('should render empty field table', () => {
|
||||
const result = render(
|
||||
<TestProviders>
|
||||
<FieldTableHeader {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
const result = render(<FieldTableHeader {...defaultProps} />);
|
||||
|
||||
expect(result.getByTestId('fields-showing').textContent).toBe('Showing 0 fields');
|
||||
});
|
||||
|
||||
it('should render field table with one singular field count value', () => {
|
||||
const result = render(
|
||||
<TestProviders>
|
||||
<FieldTableHeader {...defaultProps} fieldCount={1} />
|
||||
</TestProviders>
|
||||
);
|
||||
const result = render(<FieldTableHeader {...defaultProps} fieldCount={1} />);
|
||||
|
||||
expect(result.getByTestId('fields-showing').textContent).toBe('Showing 1 field');
|
||||
});
|
||||
it('should render field table with multiple fields count value', () => {
|
||||
const result = render(
|
||||
<TestProviders>
|
||||
<FieldTableHeader {...defaultProps} fieldCount={4} />
|
||||
</TestProviders>
|
||||
);
|
||||
const result = render(<FieldTableHeader {...defaultProps} fieldCount={4} />);
|
||||
|
||||
expect(result.getByTestId('fields-showing').textContent).toBe('Showing 4 fields');
|
||||
});
|
||||
|
@ -55,31 +42,19 @@ describe('FieldTableHeader', () => {
|
|||
});
|
||||
|
||||
it('should render "view all" option when filterSelected is not enabled', () => {
|
||||
const result = render(
|
||||
<TestProviders>
|
||||
<FieldTableHeader {...defaultProps} filterSelectedEnabled={false} />
|
||||
</TestProviders>
|
||||
);
|
||||
const result = render(<FieldTableHeader {...defaultProps} filterSelectedEnabled={false} />);
|
||||
|
||||
expect(result.getByTestId('viewSelectorButton').textContent).toBe('View: all');
|
||||
});
|
||||
|
||||
it('should render "view selected" option when filterSelected is not enabled', () => {
|
||||
const result = render(
|
||||
<TestProviders>
|
||||
<FieldTableHeader {...defaultProps} filterSelectedEnabled={true} />
|
||||
</TestProviders>
|
||||
);
|
||||
const result = render(<FieldTableHeader {...defaultProps} filterSelectedEnabled={true} />);
|
||||
|
||||
expect(result.getByTestId('viewSelectorButton').textContent).toBe('View: selected');
|
||||
});
|
||||
|
||||
it('should open the view selector with button click', async () => {
|
||||
const result = render(
|
||||
<TestProviders>
|
||||
<FieldTableHeader {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
const result = render(<FieldTableHeader {...defaultProps} />);
|
||||
|
||||
expect(result.queryByTestId('viewSelectorMenu')).not.toBeInTheDocument();
|
||||
expect(result.queryByTestId('viewSelectorOption-all')).not.toBeInTheDocument();
|
||||
|
@ -93,11 +68,7 @@ describe('FieldTableHeader', () => {
|
|||
});
|
||||
|
||||
it('should callback when "view all" option is clicked', () => {
|
||||
const result = render(
|
||||
<TestProviders>
|
||||
<FieldTableHeader {...defaultProps} filterSelectedEnabled={false} />
|
||||
</TestProviders>
|
||||
);
|
||||
const result = render(<FieldTableHeader {...defaultProps} filterSelectedEnabled={false} />);
|
||||
|
||||
result.getByTestId('viewSelectorButton').click();
|
||||
result.getByTestId('viewSelectorOption-all').click();
|
||||
|
@ -105,11 +76,7 @@ describe('FieldTableHeader', () => {
|
|||
});
|
||||
|
||||
it('should callback when "view selected" option is clicked', () => {
|
||||
const result = render(
|
||||
<TestProviders>
|
||||
<FieldTableHeader {...defaultProps} filterSelectedEnabled={false} />
|
||||
</TestProviders>
|
||||
);
|
||||
const result = render(<FieldTableHeader {...defaultProps} filterSelectedEnabled={false} />);
|
||||
|
||||
result.getByTestId('viewSelectorButton').click();
|
||||
result.getByTestId('viewSelectorOption-selected').click();
|
|
@ -4,9 +4,7 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import {
|
||||
EuiText,
|
||||
EuiPopover,
|
||||
|
@ -17,7 +15,8 @@ import {
|
|||
EuiContextMenuPanel,
|
||||
EuiHorizontalRule,
|
||||
} from '@elastic/eui';
|
||||
import * as i18n from './translations';
|
||||
import * as i18n from '../../translations';
|
||||
import { styles } from './field_table_header.styles';
|
||||
|
||||
export interface FieldTableHeaderProps {
|
||||
fieldCount: number;
|
||||
|
@ -25,11 +24,6 @@ export interface FieldTableHeaderProps {
|
|||
onFilterSelectedChange: (enabled: boolean) => void;
|
||||
}
|
||||
|
||||
const Count = styled.span`
|
||||
font-weight: bold;
|
||||
`;
|
||||
Count.displayName = 'Count';
|
||||
|
||||
const FieldTableHeaderComponent: React.FC<FieldTableHeaderProps> = ({
|
||||
fieldCount,
|
||||
filterSelectedEnabled,
|
||||
|
@ -50,7 +44,10 @@ const FieldTableHeaderComponent: React.FC<FieldTableHeaderProps> = ({
|
|||
<EuiFlexItem>
|
||||
<EuiText data-test-subj="fields-showing" size="xs">
|
||||
{i18n.FIELDS_SHOWING}
|
||||
<Count data-test-subj="fields-count"> {fieldCount} </Count>
|
||||
<span css={styles.count} data-test-subj="fields-count">
|
||||
{' '}
|
||||
{fieldCount}{' '}
|
||||
</span>
|
||||
{i18n.FIELDS_COUNT(fieldCount)}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
|
@ -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.
|
||||
*/
|
||||
|
||||
export { FieldTable } from './field_table';
|
||||
export type { FieldTableProps } from './field_table';
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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 { Search } from './search';
|
|
@ -7,15 +7,12 @@
|
|||
|
||||
import { mount } from 'enzyme';
|
||||
import React from 'react';
|
||||
import { TestProviders } from '../../../../mock';
|
||||
import { Search } from './search';
|
||||
|
||||
describe('Search', () => {
|
||||
test('it renders the field search input with the expected placeholder text when the searchInput prop is empty', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<Search isSearching={false} onSearchInputChange={jest.fn()} searchInput="" />
|
||||
</TestProviders>
|
||||
<Search isSearching={false} onSearchInputChange={jest.fn()} searchInput="" />
|
||||
);
|
||||
|
||||
expect(wrapper.find('[data-test-subj="field-search"]').first().props().placeholder).toEqual(
|
||||
|
@ -27,9 +24,7 @@ describe('Search', () => {
|
|||
const searchInput = 'aFieldName';
|
||||
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<Search isSearching={false} onSearchInputChange={jest.fn()} searchInput={searchInput} />
|
||||
</TestProviders>
|
||||
<Search isSearching={false} onSearchInputChange={jest.fn()} searchInput={searchInput} />
|
||||
);
|
||||
|
||||
expect(wrapper.find('input').props().value).toEqual(searchInput);
|
||||
|
@ -37,9 +32,7 @@ describe('Search', () => {
|
|||
|
||||
test('it renders the field search input with a spinner when isSearching is true', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<Search isSearching={true} onSearchInputChange={jest.fn()} searchInput="" />
|
||||
</TestProviders>
|
||||
<Search isSearching={true} onSearchInputChange={jest.fn()} searchInput="" />
|
||||
);
|
||||
|
||||
expect(wrapper.find('.euiLoadingSpinner').first().exists()).toBe(true);
|
||||
|
@ -49,9 +42,7 @@ describe('Search', () => {
|
|||
const onSearchInputChange = jest.fn();
|
||||
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<Search isSearching={false} onSearchInputChange={onSearchInputChange} searchInput="" />
|
||||
</TestProviders>
|
||||
<Search isSearching={false} onSearchInputChange={onSearchInputChange} searchInput="" />
|
||||
);
|
||||
|
||||
wrapper
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import { EuiFieldSearch } from '@elastic/eui';
|
||||
import * as i18n from './translations';
|
||||
import * as i18n from '../../translations';
|
||||
interface Props {
|
||||
isSearching: boolean;
|
||||
onSearchInputChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* 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 { css } from '@emotion/react';
|
||||
|
||||
export const styles = {
|
||||
buttonContainer: css`
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
`,
|
||||
};
|
|
@ -7,29 +7,22 @@
|
|||
|
||||
import React from 'react';
|
||||
import { act, fireEvent, render, waitFor } from '@testing-library/react';
|
||||
|
||||
import { mockBrowserFields, TestProviders } from '../../../../mock';
|
||||
|
||||
import { mockBrowserFields } from './mock';
|
||||
import { FIELD_BROWSER_WIDTH } from './helpers';
|
||||
|
||||
import { FieldBrowserComponent } from './field_browser';
|
||||
import { FieldBrowserProps } from '../../../field_browser';
|
||||
import type { FieldBrowserProps } from './types';
|
||||
|
||||
const defaultProps: FieldBrowserProps = {
|
||||
browserFields: mockBrowserFields,
|
||||
columnHeaders: [],
|
||||
columnIds: [],
|
||||
onToggleColumn: jest.fn(),
|
||||
onResetColumns: jest.fn(),
|
||||
};
|
||||
|
||||
const renderComponent = (props: Partial<FieldBrowserProps> = {}) =>
|
||||
render(
|
||||
<TestProviders>
|
||||
<FieldBrowserComponent {...{ ...defaultProps, ...props }} />
|
||||
</TestProviders>
|
||||
);
|
||||
render(<FieldBrowserComponent {...{ ...defaultProps, ...props }} />);
|
||||
|
||||
describe('StatefulFieldsBrowser', () => {
|
||||
describe('FieldsBrowser', () => {
|
||||
it('should render the Fields button, which displays the fields browser on click', () => {
|
||||
const result = renderComponent();
|
||||
|
|
@ -4,34 +4,26 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiButtonEmpty, EuiToolTip } from '@elastic/eui';
|
||||
import { debounce } from 'lodash';
|
||||
import React, { useEffect, useRef, useState, useCallback, useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import type { BrowserFields } from '../../../../../common/search_strategy/index_fields';
|
||||
import type { FieldBrowserProps } from '../../../../../common/types/field_browser';
|
||||
import type { FieldBrowserProps, BrowserFields } from './types';
|
||||
import { FieldBrowserModal } from './field_browser_modal';
|
||||
import { filterBrowserFieldsByFieldName, filterSelectedBrowserFields } from './helpers';
|
||||
import * as i18n from './translations';
|
||||
import { styles } from './field_browser.styles';
|
||||
|
||||
const FIELDS_BUTTON_CLASS_NAME = 'fields-button';
|
||||
|
||||
/** wait this many ms after the user completes typing before applying the filter input */
|
||||
export const INPUT_TIMEOUT = 250;
|
||||
|
||||
const FieldBrowserButtonContainer = styled.div`
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
FieldBrowserButtonContainer.displayName = 'FieldBrowserButtonContainer';
|
||||
/**
|
||||
* Manages the state of the field browser
|
||||
*/
|
||||
export const FieldBrowserComponent: React.FC<FieldBrowserProps> = ({
|
||||
columnHeaders,
|
||||
columnIds,
|
||||
browserFields,
|
||||
onResetColumns,
|
||||
onToggleColumn,
|
||||
|
@ -73,9 +65,9 @@ export const FieldBrowserComponent: React.FC<FieldBrowserProps> = ({
|
|||
const selectionFilteredBrowserFields = useMemo<BrowserFields>(
|
||||
() =>
|
||||
filterSelectedEnabled
|
||||
? filterSelectedBrowserFields({ browserFields, columnHeaders })
|
||||
? filterSelectedBrowserFields({ browserFields, columnIds })
|
||||
: browserFields,
|
||||
[browserFields, columnHeaders, filterSelectedEnabled]
|
||||
[browserFields, columnIds, filterSelectedEnabled]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -123,7 +115,7 @@ export const FieldBrowserComponent: React.FC<FieldBrowserProps> = ({
|
|||
);
|
||||
|
||||
return (
|
||||
<FieldBrowserButtonContainer data-test-subj="fields-browser-button-container">
|
||||
<div css={styles.buttonContainer} data-test-subj="fields-browser-button-container">
|
||||
<EuiToolTip content={i18n.FIELDS_BROWSER}>
|
||||
<EuiButtonEmpty
|
||||
aria-label={i18n.FIELDS_BROWSER}
|
||||
|
@ -141,7 +133,7 @@ export const FieldBrowserComponent: React.FC<FieldBrowserProps> = ({
|
|||
|
||||
{show && (
|
||||
<FieldBrowserModal
|
||||
columnHeaders={columnHeaders}
|
||||
columnIds={columnIds}
|
||||
filteredBrowserFields={
|
||||
filteredBrowserFields != null ? filteredBrowserFields : browserFields
|
||||
}
|
||||
|
@ -161,7 +153,7 @@ export const FieldBrowserComponent: React.FC<FieldBrowserProps> = ({
|
|||
width={width}
|
||||
/>
|
||||
)}
|
||||
</FieldBrowserButtonContainer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
import { mount } from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import { TestProviders, mockBrowserFields, defaultHeaders } from '../../../../mock';
|
||||
import { mockBrowserFields } from './mock';
|
||||
import { FieldBrowserModal, FieldBrowserModalProps } from './field_browser_modal';
|
||||
|
||||
const mockOnHide = jest.fn();
|
||||
|
@ -16,7 +16,7 @@ const mockOnToggleColumn = jest.fn();
|
|||
const mockOnResetColumns = jest.fn();
|
||||
|
||||
const testProps: FieldBrowserModalProps = {
|
||||
columnHeaders: [],
|
||||
columnIds: [],
|
||||
filteredBrowserFields: mockBrowserFields,
|
||||
searchInput: '',
|
||||
appliedFilterInput: '',
|
||||
|
@ -32,59 +32,42 @@ const testProps: FieldBrowserModalProps = {
|
|||
onFilterSelectedChange: jest.fn(),
|
||||
};
|
||||
|
||||
const mountComponent = (props: Partial<FieldBrowserModalProps> = {}) =>
|
||||
mount(<FieldBrowserModal {...{ ...testProps, ...props }} />);
|
||||
|
||||
describe('FieldBrowserModal', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test('it renders the Close button', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<FieldBrowserModal {...testProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
const wrapper = mountComponent();
|
||||
|
||||
expect(wrapper.find('[data-test-subj="close"]').first().text()).toEqual('Close');
|
||||
});
|
||||
|
||||
test('it invokes the Close button', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<FieldBrowserModal {...testProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
const wrapper = mountComponent();
|
||||
|
||||
wrapper.find('[data-test-subj="close"]').first().simulate('click');
|
||||
expect(mockOnHide).toBeCalled();
|
||||
});
|
||||
|
||||
test('it renders the Reset Fields button', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<FieldBrowserModal {...testProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
const wrapper = mountComponent();
|
||||
|
||||
expect(wrapper.find('[data-test-subj="reset-fields"]').first().text()).toEqual('Reset Fields');
|
||||
});
|
||||
|
||||
test('it invokes onResetColumns callback when the user clicks the Reset Fields button', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<FieldBrowserModal {...testProps} columnHeaders={defaultHeaders} />
|
||||
</TestProviders>
|
||||
);
|
||||
const wrapper = mountComponent({ columnIds: ['test'] });
|
||||
|
||||
wrapper.find('[data-test-subj="reset-fields"]').first().simulate('click');
|
||||
expect(mockOnResetColumns).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('it invokes onHide when the user clicks the Reset Fields button', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<FieldBrowserModal {...testProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
const wrapper = mountComponent();
|
||||
|
||||
wrapper.find('[data-test-subj="reset-fields"]').first().simulate('click');
|
||||
|
||||
|
@ -92,41 +75,25 @@ describe('FieldBrowserModal', () => {
|
|||
});
|
||||
|
||||
test('it renders the search', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<FieldBrowserModal {...testProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
const wrapper = mountComponent();
|
||||
|
||||
expect(wrapper.find('[data-test-subj="field-search"]').exists()).toBe(true);
|
||||
});
|
||||
|
||||
test('it renders the categories selector', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<FieldBrowserModal {...testProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
const wrapper = mountComponent();
|
||||
|
||||
expect(wrapper.find('[data-test-subj="categories-selector"]').exists()).toBe(true);
|
||||
});
|
||||
|
||||
test('it renders the fields table', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<FieldBrowserModal {...testProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
const wrapper = mountComponent();
|
||||
|
||||
expect(wrapper.find('[data-test-subj="field-table"]').exists()).toBe(true);
|
||||
});
|
||||
|
||||
test('focuses the search input when the component mounts', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<FieldBrowserModal {...testProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
const wrapper = mountComponent();
|
||||
|
||||
expect(
|
||||
wrapper.find('[data-test-subj="field-search"]').first().getDOMNode().id ===
|
||||
|
@ -138,14 +105,9 @@ describe('FieldBrowserModal', () => {
|
|||
const onSearchInputChange = jest.fn();
|
||||
const inputText = 'event.category';
|
||||
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<FieldBrowserModal {...testProps} onSearchInputChange={onSearchInputChange} />
|
||||
</TestProviders>
|
||||
);
|
||||
const wrapper = mountComponent({ onSearchInputChange });
|
||||
|
||||
const searchField = wrapper.find('[data-test-subj="field-search"]').first();
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const changeEvent: any = { target: { value: inputText } };
|
||||
const onChange = searchField.props().onChange;
|
||||
|
||||
|
@ -158,16 +120,7 @@ describe('FieldBrowserModal', () => {
|
|||
test('it renders the CreateFieldButton when it is provided', () => {
|
||||
const MyTestComponent = () => <div>{'test'}</div>;
|
||||
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<FieldBrowserModal
|
||||
{...testProps}
|
||||
options={{
|
||||
createFieldButton: MyTestComponent,
|
||||
}}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
const wrapper = mountComponent({ options: { createFieldButton: MyTestComponent } });
|
||||
|
||||
expect(wrapper.find(MyTestComponent).exists()).toBeTruthy();
|
||||
});
|
|
@ -4,7 +4,6 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
|
@ -19,16 +18,15 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import type { BrowserFields } from '../../../../../common/search_strategy';
|
||||
import type { FieldBrowserProps, ColumnHeaderOptions } from '../../../../../common/types';
|
||||
import { Search } from './search';
|
||||
import type { FieldBrowserProps, BrowserFields } from './types';
|
||||
import { Search } from './components/search';
|
||||
|
||||
import { CLOSE_BUTTON_CLASS_NAME, FIELD_BROWSER_WIDTH, RESET_FIELDS_CLASS_NAME } from './helpers';
|
||||
|
||||
import * as i18n from './translations';
|
||||
import { CategoriesSelector } from './categories_selector';
|
||||
import { FieldTable } from './field_table';
|
||||
import { CategoriesBadges } from './categories_badges';
|
||||
import { CategoriesSelector } from './components/categories_selector';
|
||||
import { CategoriesBadges } from './components/categories_badges';
|
||||
import { FieldTable } from './components/field_table';
|
||||
|
||||
export type FieldBrowserModalProps = Pick<
|
||||
FieldBrowserProps,
|
||||
|
@ -37,7 +35,7 @@ export type FieldBrowserModalProps = Pick<
|
|||
/**
|
||||
* The current timeline column headers
|
||||
*/
|
||||
columnHeaders: ColumnHeaderOptions[];
|
||||
columnIds: string[];
|
||||
/**
|
||||
* A map of categoryId -> metadata about the fields in that category,
|
||||
* filtered such that the name of every field in the category includes
|
||||
|
@ -88,7 +86,7 @@ export type FieldBrowserModalProps = Pick<
|
|||
*/
|
||||
const FieldBrowserModalComponent: React.FC<FieldBrowserModalProps> = ({
|
||||
appliedFilterInput,
|
||||
columnHeaders,
|
||||
columnIds,
|
||||
filteredBrowserFields,
|
||||
filterSelectedEnabled,
|
||||
isSearching,
|
||||
|
@ -169,7 +167,7 @@ const FieldBrowserModalComponent: React.FC<FieldBrowserModalProps> = ({
|
|||
<EuiSpacer size="l" />
|
||||
|
||||
<FieldTable
|
||||
columnHeaders={columnHeaders}
|
||||
columnIds={columnIds}
|
||||
filteredBrowserFields={filteredBrowserFields}
|
||||
filterSelectedEnabled={filterSelectedEnabled}
|
||||
searchInput={appliedFilterInput}
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { mockBrowserFields } from '../../../../mock';
|
||||
import { mockBrowserFields } from './mock';
|
||||
|
||||
import {
|
||||
categoryHasFields,
|
||||
|
@ -13,8 +13,7 @@ import {
|
|||
filterBrowserFieldsByFieldName,
|
||||
filterSelectedBrowserFields,
|
||||
} from './helpers';
|
||||
import { BrowserFields } from '../../../../../common/search_strategy';
|
||||
import { ColumnHeaderOptions } from '../../../../../common';
|
||||
import type { BrowserFields } from './types';
|
||||
|
||||
describe('helpers', () => {
|
||||
describe('categoryHasFields', () => {
|
||||
|
@ -257,25 +256,21 @@ describe('helpers', () => {
|
|||
});
|
||||
|
||||
describe('filterSelectedBrowserFields', () => {
|
||||
const columnHeaders = [
|
||||
{ id: 'agent.ephemeral_id' },
|
||||
{ id: 'agent.id' },
|
||||
{ id: 'container.id' },
|
||||
] as ColumnHeaderOptions[];
|
||||
const columnIds = ['agent.ephemeral_id', 'agent.id', 'container.id'];
|
||||
|
||||
test('it returns an empty collection when browserFields is empty', () => {
|
||||
expect(filterSelectedBrowserFields({ browserFields: {}, columnHeaders: [] })).toEqual({});
|
||||
expect(filterSelectedBrowserFields({ browserFields: {}, columnIds: [] })).toEqual({});
|
||||
});
|
||||
|
||||
test('it returns an empty collection when browserFields is empty and columnHeaders is non empty', () => {
|
||||
expect(filterSelectedBrowserFields({ browserFields: {}, columnHeaders })).toEqual({});
|
||||
test('it returns an empty collection when browserFields is empty and columnIds is non empty', () => {
|
||||
expect(filterSelectedBrowserFields({ browserFields: {}, columnIds })).toEqual({});
|
||||
});
|
||||
|
||||
test('it returns an empty collection when browserFields is NOT empty and columnHeaders is empty', () => {
|
||||
test('it returns an empty collection when browserFields is NOT empty and columnIds is empty', () => {
|
||||
expect(
|
||||
filterSelectedBrowserFields({
|
||||
browserFields: mockBrowserFields,
|
||||
columnHeaders: [],
|
||||
columnIds: [],
|
||||
})
|
||||
).toEqual({});
|
||||
});
|
||||
|
@ -330,7 +325,7 @@ describe('helpers', () => {
|
|||
expect(
|
||||
filterSelectedBrowserFields({
|
||||
browserFields: mockBrowserFields,
|
||||
columnHeaders,
|
||||
columnIds,
|
||||
})
|
||||
).toEqual(filtered);
|
||||
});
|
|
@ -5,19 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiBadge, EuiLoadingSpinner } from '@elastic/eui';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import type { BrowserField, BrowserFields } from '../../../../../common/search_strategy';
|
||||
import { ColumnHeaderOptions } from '../../../../../common';
|
||||
|
||||
export const LoadingSpinner = styled(EuiLoadingSpinner)`
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
top: 3px;
|
||||
`;
|
||||
|
||||
LoadingSpinner.displayName = 'LoadingSpinner';
|
||||
import { isEmpty } from 'lodash/fp';
|
||||
import { BrowserField, BrowserFields } from './types';
|
||||
|
||||
export const FIELD_BROWSER_WIDTH = 925;
|
||||
export const TABLE_HEIGHT = 260;
|
||||
|
@ -50,7 +39,6 @@ export function filterBrowserFieldsByFieldName({
|
|||
for (const [categoryName, categoryDescriptor] of Object.entries(browserFields)) {
|
||||
if (!categoryDescriptor.fields) {
|
||||
// ignore any category that is missing fields. This is not expected to happen.
|
||||
// eslint-disable-next-line no-continue
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -67,7 +55,6 @@ export function filterBrowserFieldsByFieldName({
|
|||
|
||||
if (!fieldNameFromDescriptor) {
|
||||
// Ignore any field that is missing a name in its descriptor. This is not expected to happen.
|
||||
// eslint-disable-next-line no-continue
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -94,23 +81,22 @@ export function filterBrowserFieldsByFieldName({
|
|||
|
||||
/**
|
||||
* Filters the selected `BrowserFields` to return a new collection where every
|
||||
* category contains at least one field that is present in the `columnHeaders`.
|
||||
* category contains at least one field that is present in the `columnIds`.
|
||||
*/
|
||||
export const filterSelectedBrowserFields = ({
|
||||
browserFields,
|
||||
columnHeaders,
|
||||
columnIds,
|
||||
}: {
|
||||
browserFields: BrowserFields;
|
||||
columnHeaders: ColumnHeaderOptions[];
|
||||
columnIds: string[];
|
||||
}): BrowserFields => {
|
||||
const selectedFieldIds = new Set(columnHeaders.map(({ id }) => id));
|
||||
const selectedFieldIds = new Set(columnIds);
|
||||
|
||||
const result: Record<string, Partial<BrowserField>> = {};
|
||||
|
||||
for (const [categoryName, categoryDescriptor] of Object.entries(browserFields)) {
|
||||
if (!categoryDescriptor.fields) {
|
||||
// ignore any category that is missing fields. This is not expected to happen.
|
||||
// eslint-disable-next-line no-continue
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -127,7 +113,6 @@ export const filterSelectedBrowserFields = ({
|
|||
|
||||
if (!fieldNameFromDescriptor) {
|
||||
// Ignore any field that is missing a name in its descriptor. This is not expected to happen.
|
||||
// eslint-disable-next-line no-continue
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -147,22 +132,37 @@ export const filterSelectedBrowserFields = ({
|
|||
return result;
|
||||
};
|
||||
|
||||
export const getIconFromType = (type: string | null | undefined) => {
|
||||
switch (type) {
|
||||
case 'string': // fall through
|
||||
case 'keyword':
|
||||
return 'string';
|
||||
case 'number': // fall through
|
||||
case 'long':
|
||||
return 'number';
|
||||
case 'date':
|
||||
return 'clock';
|
||||
case 'ip':
|
||||
case 'geo_point':
|
||||
return 'globe';
|
||||
case 'object':
|
||||
return 'questionInCircle';
|
||||
case 'float':
|
||||
return 'number';
|
||||
default:
|
||||
return 'questionInCircle';
|
||||
}
|
||||
};
|
||||
|
||||
export const getEmptyValue = () => '—';
|
||||
|
||||
/** Returns example text, or an empty string if the field does not have an example */
|
||||
export const getExampleText = (example: string | number | null | undefined): string =>
|
||||
!isEmpty(example) ? `Example: ${example}` : '';
|
||||
|
||||
/** Returns `true` if the escape key was pressed */
|
||||
export const isEscape = (event: React.KeyboardEvent): boolean => event.key === 'Escape';
|
||||
|
||||
export const CATEGORY_TABLE_CLASS_NAME = 'category-table';
|
||||
export const CLOSE_BUTTON_CLASS_NAME = 'close-button';
|
||||
export const RESET_FIELDS_CLASS_NAME = 'reset-fields';
|
||||
|
||||
export const CountBadge = styled(EuiBadge)`
|
||||
margin-left: 5px;
|
||||
` as unknown as typeof EuiBadge;
|
||||
|
||||
CountBadge.displayName = 'CountBadge';
|
||||
|
||||
export const CategoryName = styled.span<{ bold: boolean }>`
|
||||
font-weight: ${({ bold }) => (bold ? 'bold' : 'normal')};
|
||||
`;
|
||||
CategoryName.displayName = 'CategoryName';
|
||||
|
||||
export const CategorySelectableContainer = styled.div`
|
||||
width: 300px;
|
||||
`;
|
||||
CategorySelectableContainer.displayName = 'CategorySelectableContainer';
|
|
@ -4,14 +4,9 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { FieldBrowser } from './field_browser';
|
||||
export type { FieldBrowserProps } from './types';
|
||||
|
||||
import { FieldBrowser } from '../t_grid/toolbar/field_browser';
|
||||
export type {
|
||||
CreateFieldComponent,
|
||||
FieldBrowserOptions,
|
||||
FieldBrowserProps,
|
||||
GetFieldTableColumns,
|
||||
} from '../../../common/types/field_browser';
|
||||
|
||||
export { FieldBrowser };
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export { FieldBrowser as default };
|
|
@ -0,0 +1,476 @@
|
|||
/*
|
||||
* 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 { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { BrowserFields } from './types';
|
||||
|
||||
const DEFAULT_INDEX_PATTERN = [
|
||||
'apm-*-transaction*',
|
||||
'traces-apm*',
|
||||
'auditbeat-*',
|
||||
'endgame-*',
|
||||
'filebeat-*',
|
||||
'logs-*',
|
||||
'packetbeat-*',
|
||||
'winlogbeat-*',
|
||||
];
|
||||
|
||||
export const mockBrowserFields: BrowserFields = {
|
||||
agent: {
|
||||
fields: {
|
||||
'agent.ephemeral_id': {
|
||||
aggregatable: true,
|
||||
category: 'agent',
|
||||
description:
|
||||
'Ephemeral identifier of this agent (if one exists). This id normally changes across restarts, but `agent.id` does not.',
|
||||
example: '8a4f500f',
|
||||
format: '',
|
||||
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
|
||||
name: 'agent.ephemeral_id',
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
},
|
||||
'agent.hostname': {
|
||||
aggregatable: true,
|
||||
category: 'agent',
|
||||
description: null,
|
||||
example: null,
|
||||
format: '',
|
||||
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
|
||||
name: 'agent.hostname',
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
},
|
||||
'agent.id': {
|
||||
aggregatable: true,
|
||||
category: 'agent',
|
||||
description:
|
||||
'Unique identifier of this agent (if one exists). Example: For Beats this would be beat.id.',
|
||||
example: '8a4f500d',
|
||||
format: '',
|
||||
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
|
||||
name: 'agent.id',
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
},
|
||||
'agent.name': {
|
||||
aggregatable: true,
|
||||
category: 'agent',
|
||||
description:
|
||||
'Name of the agent. This is a name that can be given to an agent. This can be helpful if for example two Filebeat instances are running on the same host but a human readable separation is needed on which Filebeat instance data is coming from. If no name is given, the name is often left empty.',
|
||||
example: 'foo',
|
||||
format: '',
|
||||
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
|
||||
name: 'agent.name',
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
auditd: {
|
||||
fields: {
|
||||
'auditd.data.a0': {
|
||||
aggregatable: true,
|
||||
category: 'auditd',
|
||||
description: null,
|
||||
example: null,
|
||||
format: '',
|
||||
indexes: ['auditbeat'],
|
||||
name: 'auditd.data.a0',
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
},
|
||||
'auditd.data.a1': {
|
||||
aggregatable: true,
|
||||
category: 'auditd',
|
||||
description: null,
|
||||
example: null,
|
||||
format: '',
|
||||
indexes: ['auditbeat'],
|
||||
name: 'auditd.data.a1',
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
},
|
||||
'auditd.data.a2': {
|
||||
aggregatable: true,
|
||||
category: 'auditd',
|
||||
description: null,
|
||||
example: null,
|
||||
format: '',
|
||||
indexes: ['auditbeat'],
|
||||
name: 'auditd.data.a2',
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
base: {
|
||||
fields: {
|
||||
'@timestamp': {
|
||||
aggregatable: true,
|
||||
category: 'base',
|
||||
description:
|
||||
'Date/time when the event originated. For log events this is the date/time when the event was generated, and not when it was read. Required field for all events.',
|
||||
example: '2016-05-23T08:05:34.853Z',
|
||||
format: '',
|
||||
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
|
||||
name: '@timestamp',
|
||||
searchable: true,
|
||||
type: 'date',
|
||||
},
|
||||
_id: {
|
||||
category: 'base',
|
||||
description: 'Each document has an _id that uniquely identifies it',
|
||||
example: 'Y-6TfmcB0WOhS6qyMv3s',
|
||||
name: '_id',
|
||||
type: 'string',
|
||||
searchable: true,
|
||||
aggregatable: false,
|
||||
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
|
||||
},
|
||||
message: {
|
||||
category: 'base',
|
||||
description:
|
||||
'For log events the message field contains the log message, optimized for viewing in a log viewer. For structured logs without an original message field, other fields can be concatenated to form a human-readable summary of the event. If multiple messages exist, they can be combined into one message.',
|
||||
example: 'Hello World',
|
||||
name: 'message',
|
||||
type: 'string',
|
||||
searchable: true,
|
||||
aggregatable: false,
|
||||
format: 'string',
|
||||
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
|
||||
},
|
||||
},
|
||||
},
|
||||
client: {
|
||||
fields: {
|
||||
'client.address': {
|
||||
aggregatable: true,
|
||||
category: 'client',
|
||||
description:
|
||||
'Some event client addresses are defined ambiguously. The event will sometimes list an IP, a domain or a unix socket. You should always store the raw address in the `.address` field. Then it should be duplicated to `.ip` or `.domain`, depending on which one it is.',
|
||||
example: null,
|
||||
format: '',
|
||||
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
|
||||
name: 'client.address',
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
},
|
||||
'client.bytes': {
|
||||
aggregatable: true,
|
||||
category: 'client',
|
||||
description: 'Bytes sent from the client to the server.',
|
||||
example: '184',
|
||||
format: '',
|
||||
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
|
||||
name: 'client.bytes',
|
||||
searchable: true,
|
||||
type: 'number',
|
||||
},
|
||||
'client.domain': {
|
||||
aggregatable: true,
|
||||
category: 'client',
|
||||
description: 'Client domain.',
|
||||
example: null,
|
||||
format: '',
|
||||
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
|
||||
name: 'client.domain',
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
},
|
||||
'client.geo.country_iso_code': {
|
||||
aggregatable: true,
|
||||
category: 'client',
|
||||
description: 'Country ISO code.',
|
||||
example: 'CA',
|
||||
format: '',
|
||||
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
|
||||
name: 'client.geo.country_iso_code',
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
cloud: {
|
||||
fields: {
|
||||
'cloud.account.id': {
|
||||
aggregatable: true,
|
||||
category: 'cloud',
|
||||
description:
|
||||
'The cloud account or organization id used to identify different entities in a multi-tenant environment. Examples: AWS account id, Google Cloud ORG Id, or other unique identifier.',
|
||||
example: '666777888999',
|
||||
format: '',
|
||||
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
|
||||
name: 'cloud.account.id',
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
},
|
||||
'cloud.availability_zone': {
|
||||
aggregatable: true,
|
||||
category: 'cloud',
|
||||
description: 'Availability zone in which this host is running.',
|
||||
example: 'us-east-1c',
|
||||
format: '',
|
||||
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
|
||||
name: 'cloud.availability_zone',
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
container: {
|
||||
fields: {
|
||||
'container.id': {
|
||||
aggregatable: true,
|
||||
category: 'container',
|
||||
description: 'Unique container id.',
|
||||
example: null,
|
||||
format: '',
|
||||
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
|
||||
name: 'container.id',
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
},
|
||||
'container.image.name': {
|
||||
aggregatable: true,
|
||||
category: 'container',
|
||||
description: 'Name of the image the container was built on.',
|
||||
example: null,
|
||||
format: '',
|
||||
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
|
||||
name: 'container.image.name',
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
},
|
||||
'container.image.tag': {
|
||||
aggregatable: true,
|
||||
category: 'container',
|
||||
description: 'Container image tag.',
|
||||
example: null,
|
||||
format: '',
|
||||
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
|
||||
name: 'container.image.tag',
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
destination: {
|
||||
fields: {
|
||||
'destination.address': {
|
||||
aggregatable: true,
|
||||
category: 'destination',
|
||||
description:
|
||||
'Some event destination addresses are defined ambiguously. The event will sometimes list an IP, a domain or a unix socket. You should always store the raw address in the `.address` field. Then it should be duplicated to `.ip` or `.domain`, depending on which one it is.',
|
||||
example: null,
|
||||
format: '',
|
||||
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
|
||||
name: 'destination.address',
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
},
|
||||
'destination.bytes': {
|
||||
aggregatable: true,
|
||||
category: 'destination',
|
||||
description: 'Bytes sent from the destination to the source.',
|
||||
example: '184',
|
||||
format: '',
|
||||
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
|
||||
name: 'destination.bytes',
|
||||
searchable: true,
|
||||
type: 'number',
|
||||
},
|
||||
'destination.domain': {
|
||||
aggregatable: true,
|
||||
category: 'destination',
|
||||
description: 'Destination domain.',
|
||||
example: null,
|
||||
format: '',
|
||||
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
|
||||
name: 'destination.domain',
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
},
|
||||
'destination.ip': {
|
||||
aggregatable: true,
|
||||
category: 'destination',
|
||||
description:
|
||||
'IP address of the destination. Can be one or multiple IPv4 or IPv6 addresses.',
|
||||
example: '',
|
||||
format: '',
|
||||
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
|
||||
name: 'destination.ip',
|
||||
searchable: true,
|
||||
type: 'ip',
|
||||
},
|
||||
'destination.port': {
|
||||
aggregatable: true,
|
||||
category: 'destination',
|
||||
description: 'Port of the destination.',
|
||||
example: '',
|
||||
format: '',
|
||||
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
|
||||
name: 'destination.port',
|
||||
searchable: true,
|
||||
type: 'long',
|
||||
},
|
||||
},
|
||||
},
|
||||
event: {
|
||||
fields: {
|
||||
'event.end': {
|
||||
category: 'event',
|
||||
description:
|
||||
'event.end contains the date when the event ended or when the activity was last observed.',
|
||||
example: null,
|
||||
format: '',
|
||||
indexes: DEFAULT_INDEX_PATTERN,
|
||||
name: 'event.end',
|
||||
searchable: true,
|
||||
type: 'date',
|
||||
aggregatable: true,
|
||||
},
|
||||
'event.action': {
|
||||
category: 'event',
|
||||
description:
|
||||
'The action captured by the event. This describes the information in the event. It is more specific than `event.category`. Examples are `group-add`, `process-started`, `file-created`. The value is normally defined by the implementer.',
|
||||
example: 'user-password-change',
|
||||
name: 'event.action',
|
||||
type: 'string',
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
format: 'string',
|
||||
indexes: DEFAULT_INDEX_PATTERN,
|
||||
},
|
||||
'event.category': {
|
||||
category: 'event',
|
||||
description:
|
||||
'This is one of four ECS Categorization Fields, and indicates the second level in the ECS category hierarchy. `event.category` represents the "big buckets" of ECS categories. For example, filtering on `event.category:process` yields all events relating to process activity. This field is closely related to `event.type`, which is used as a subcategory. This field is an array. This will allow proper categorization of some events that fall in multiple categories.',
|
||||
example: 'authentication',
|
||||
name: 'event.category',
|
||||
type: 'string',
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
format: 'string',
|
||||
indexes: DEFAULT_INDEX_PATTERN,
|
||||
},
|
||||
'event.severity': {
|
||||
category: 'event',
|
||||
description:
|
||||
"The numeric severity of the event according to your event source. What the different severity values mean can be different between sources and use cases. It's up to the implementer to make sure severities are consistent across events from the same source. The Syslog severity belongs in `log.syslog.severity.code`. `event.severity` is meant to represent the severity according to the event source (e.g. firewall, IDS). If the event source does not publish its own severity, you may optionally copy the `log.syslog.severity.code` to `event.severity`.",
|
||||
example: 7,
|
||||
name: 'event.severity',
|
||||
type: 'number',
|
||||
format: 'number',
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
indexes: DEFAULT_INDEX_PATTERN,
|
||||
},
|
||||
},
|
||||
},
|
||||
host: {
|
||||
fields: {
|
||||
'host.name': {
|
||||
category: 'host',
|
||||
description:
|
||||
'Name of the host. It can contain what `hostname` returns on Unix systems, the fully qualified domain name, or a name specified by the user. The sender decides which value to use.',
|
||||
name: 'host.name',
|
||||
type: 'string',
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
format: 'string',
|
||||
indexes: DEFAULT_INDEX_PATTERN,
|
||||
},
|
||||
},
|
||||
},
|
||||
source: {
|
||||
fields: {
|
||||
'source.ip': {
|
||||
aggregatable: true,
|
||||
category: 'source',
|
||||
description: 'IP address of the source. Can be one or multiple IPv4 or IPv6 addresses.',
|
||||
example: '',
|
||||
format: '',
|
||||
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
|
||||
name: 'source.ip',
|
||||
searchable: true,
|
||||
type: 'ip',
|
||||
},
|
||||
'source.port': {
|
||||
aggregatable: true,
|
||||
category: 'source',
|
||||
description: 'Port of the source.',
|
||||
example: '',
|
||||
format: '',
|
||||
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
|
||||
name: 'source.port',
|
||||
searchable: true,
|
||||
type: 'long',
|
||||
},
|
||||
},
|
||||
},
|
||||
user: {
|
||||
fields: {
|
||||
'user.name': {
|
||||
category: 'user',
|
||||
description: 'Short name or login of the user.',
|
||||
example: 'albert',
|
||||
name: 'user.name',
|
||||
type: 'string',
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
format: 'string',
|
||||
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
|
||||
},
|
||||
},
|
||||
},
|
||||
nestedField: {
|
||||
fields: {
|
||||
'nestedField.firstAttributes': {
|
||||
aggregatable: false,
|
||||
category: 'nestedField',
|
||||
description: '',
|
||||
example: '',
|
||||
format: '',
|
||||
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
|
||||
name: 'nestedField.firstAttributes',
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
subType: {
|
||||
nested: {
|
||||
path: 'nestedField',
|
||||
},
|
||||
},
|
||||
},
|
||||
'nestedField.secondAttributes': {
|
||||
aggregatable: false,
|
||||
category: 'nestedField',
|
||||
description: '',
|
||||
example: '',
|
||||
format: '',
|
||||
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
|
||||
name: 'nestedField.secondAttributes',
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
subType: {
|
||||
nested: {
|
||||
path: 'nestedField',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const mockRuntimeMappings: MappingRuntimeFields = {
|
||||
'@a.runtime.field': {
|
||||
script: {
|
||||
source: 'emit("Radical dude: " + doc[\'host.name\'].value)',
|
||||
},
|
||||
type: 'keyword',
|
||||
},
|
||||
};
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
|
||||
export const CATEGORY = i18n.translate('xpack.triggersActionsUI.fieldBrowser.categoryLabel', {
|
||||
defaultMessage: 'Category',
|
||||
});
|
||||
|
||||
export const CATEGORIES = i18n.translate('xpack.triggersActionsUI.fieldBrowser.categoriesTitle', {
|
||||
defaultMessage: 'Categories',
|
||||
});
|
||||
|
||||
export const CATEGORIES_COUNT = (totalCount: number) =>
|
||||
i18n.translate('xpack.triggersActionsUI.fieldBrowser.categoriesCountTitle', {
|
||||
values: { totalCount },
|
||||
defaultMessage: '{totalCount} {totalCount, plural, =1 {category} other {categories}}',
|
||||
});
|
||||
|
||||
export const CLOSE = i18n.translate('xpack.triggersActionsUI.fieldBrowser.closeButton', {
|
||||
defaultMessage: 'Close',
|
||||
});
|
||||
|
||||
export const FIELDS_BROWSER = i18n.translate(
|
||||
'xpack.triggersActionsUI.fieldBrowser.fieldBrowserTitle',
|
||||
{
|
||||
defaultMessage: 'Fields',
|
||||
}
|
||||
);
|
||||
|
||||
export const DESCRIPTION = i18n.translate('xpack.triggersActionsUI.fieldBrowser.descriptionLabel', {
|
||||
defaultMessage: 'Description',
|
||||
});
|
||||
|
||||
export const DESCRIPTION_FOR_FIELD = (field: string) =>
|
||||
i18n.translate('xpack.triggersActionsUI.fieldBrowser.descriptionForScreenReaderOnly', {
|
||||
values: {
|
||||
field,
|
||||
},
|
||||
defaultMessage: 'Description for field {field}:',
|
||||
});
|
||||
|
||||
export const NAME = i18n.translate('xpack.triggersActionsUI.fieldBrowser.fieldName', {
|
||||
defaultMessage: 'Name',
|
||||
});
|
||||
|
||||
export const FIELD = i18n.translate('xpack.triggersActionsUI.fieldBrowser.fieldLabel', {
|
||||
defaultMessage: 'Field',
|
||||
});
|
||||
|
||||
export const FIELDS = i18n.translate('xpack.triggersActionsUI.fieldBrowser.fieldsTitle', {
|
||||
defaultMessage: 'Fields',
|
||||
});
|
||||
|
||||
export const FIELDS_SHOWING = i18n.translate(
|
||||
'xpack.triggersActionsUI.fieldBrowser.fieldsCountShowing',
|
||||
{
|
||||
defaultMessage: 'Showing',
|
||||
}
|
||||
);
|
||||
|
||||
export const FIELDS_COUNT = (totalCount: number) =>
|
||||
i18n.translate('xpack.triggersActionsUI.fieldBrowser.fieldsCountTitle', {
|
||||
values: { totalCount },
|
||||
defaultMessage: '{totalCount, plural, =1 {field} other {fields}}',
|
||||
});
|
||||
|
||||
export const FILTER_PLACEHOLDER = i18n.translate(
|
||||
'xpack.triggersActionsUI.fieldBrowser.filterPlaceholder',
|
||||
{
|
||||
defaultMessage: 'Field name',
|
||||
}
|
||||
);
|
||||
|
||||
export const NO_FIELDS_MATCH = i18n.translate(
|
||||
'xpack.triggersActionsUI.fieldBrowser.noFieldsMatchLabel',
|
||||
{
|
||||
defaultMessage: 'No fields match',
|
||||
}
|
||||
);
|
||||
|
||||
export const NO_FIELDS_MATCH_INPUT = (searchInput: string) =>
|
||||
i18n.translate('xpack.triggersActionsUI.fieldBrowser.noFieldsMatchInputLabel', {
|
||||
defaultMessage: 'No fields match {searchInput}',
|
||||
values: {
|
||||
searchInput,
|
||||
},
|
||||
});
|
||||
|
||||
export const RESET_FIELDS = i18n.translate('xpack.triggersActionsUI.fieldBrowser.resetFieldsLink', {
|
||||
defaultMessage: 'Reset Fields',
|
||||
});
|
||||
|
||||
export const VIEW_COLUMN = (field: string) =>
|
||||
i18n.translate('xpack.triggersActionsUI.fieldBrowser.viewColumnCheckboxAriaLabel', {
|
||||
values: { field },
|
||||
defaultMessage: 'View {field} column',
|
||||
});
|
||||
|
||||
export const VIEW_LABEL = i18n.translate('xpack.triggersActionsUI.fieldBrowser.viewLabel', {
|
||||
defaultMessage: 'View',
|
||||
});
|
||||
|
||||
export const VIEW_VALUE_SELECTED = i18n.translate(
|
||||
'xpack.triggersActionsUI.fieldBrowser.viewSelected',
|
||||
{
|
||||
defaultMessage: 'selected',
|
||||
}
|
||||
);
|
||||
|
||||
export const VIEW_VALUE_ALL = i18n.translate('xpack.triggersActionsUI.fieldBrowser.viewAll', {
|
||||
defaultMessage: 'all',
|
||||
});
|
|
@ -5,10 +5,27 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiBasicTableColumn } from '@elastic/eui';
|
||||
import { BrowserFields } from '../../search_strategy';
|
||||
import { ColumnHeaderOptions } from '../timeline/columns';
|
||||
import type { EuiBasicTableColumn } from '@elastic/eui';
|
||||
import type { IFieldSubType } from '@kbn/es-query';
|
||||
import type { RuntimeField } from '@kbn/data-views-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
|
||||
*/
|
||||
|
@ -39,7 +56,7 @@ export interface FieldBrowserOptions {
|
|||
|
||||
export interface FieldBrowserProps {
|
||||
/** The timeline's current column headers */
|
||||
columnHeaders: ColumnHeaderOptions[];
|
||||
columnIds: string[];
|
||||
/** A map of categoryId -> metadata about the fields in that category */
|
||||
browserFields: BrowserFields;
|
||||
/** When true, this Fields Browser is being used as an "events viewer" */
|
||||
|
@ -47,7 +64,7 @@ export interface FieldBrowserProps {
|
|||
/** Callback to reset the default columns */
|
||||
onResetColumns: () => void;
|
||||
/** Callback to toggle a field column */
|
||||
onToggleColumn: (fieldId: string) => void;
|
||||
onToggleColumn: (columnId: string) => void;
|
||||
/** The options to customize the field browser, supporting columns rendering and button to create fields */
|
||||
options?: FieldBrowserOptions;
|
||||
/** The width of the field browser */
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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, { lazy, Suspense } from 'react';
|
||||
import { EuiLoadingSpinner } from '@elastic/eui';
|
||||
|
||||
import type { FieldBrowserProps } from '../application/sections/field_browser';
|
||||
|
||||
const FieldBrowserLazy: React.FC<FieldBrowserProps> = lazy(
|
||||
() => import('../application/sections/field_browser')
|
||||
);
|
||||
|
||||
export const getFieldBrowserLazy = (props: FieldBrowserProps) => (
|
||||
<Suspense fallback={<EuiLoadingSpinner />}>
|
||||
<FieldBrowserLazy {...props} />
|
||||
</Suspense>
|
||||
);
|
|
@ -39,6 +39,8 @@ export type {
|
|||
RuleEventLogListProps,
|
||||
AlertTableFlyoutComponent,
|
||||
GetRenderCellValue,
|
||||
FieldBrowserOptions,
|
||||
FieldBrowserProps,
|
||||
RuleDefinitionProps,
|
||||
} from './types';
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import {
|
|||
RuleTypeModel,
|
||||
AlertsTableProps,
|
||||
AlertsTableConfigurationRegistry,
|
||||
FieldBrowserProps,
|
||||
RuleTagBadgeOptions,
|
||||
RuleTagBadgeProps,
|
||||
} from './types';
|
||||
|
@ -38,6 +39,7 @@ import { CreateConnectorFlyoutProps } from './application/sections/action_connec
|
|||
import { EditConnectorFlyoutProps } from './application/sections/action_connector_form/edit_connector_flyout';
|
||||
import { getActionFormLazy } from './common/get_action_form';
|
||||
import { ActionAccordionFormProps } from './application/sections/action_connector_form/action_form';
|
||||
import { getFieldBrowserLazy } from './common/get_field_browser';
|
||||
import { getRuleDefinitionLazy } from './common/get_rule_definition';
|
||||
import { getRuleStatusPanelLazy } from './common/get_rule_status_panel';
|
||||
|
||||
|
@ -85,6 +87,9 @@ function createStartMock(): TriggersAndActionsUIPublicPluginStart {
|
|||
getAlertsTable: (props: AlertsTableProps) => {
|
||||
return getAlertsTableLazy(props);
|
||||
},
|
||||
getFieldBrowser: (props: FieldBrowserProps) => {
|
||||
return getFieldBrowserLazy(props);
|
||||
},
|
||||
getRuleStatusDropdown: (props) => {
|
||||
return getRuleStatusDropdownLazy(props);
|
||||
},
|
||||
|
|
|
@ -31,6 +31,7 @@ import { getEditConnectorFlyoutLazy } from './common/get_edit_connector_flyout';
|
|||
import { getAddAlertFlyoutLazy } from './common/get_add_alert_flyout';
|
||||
import { getEditAlertFlyoutLazy } from './common/get_edit_alert_flyout';
|
||||
import { getAlertsTableLazy } from './common/get_alerts_table';
|
||||
import { getFieldBrowserLazy } from './common/get_field_browser';
|
||||
import { getRuleStatusDropdownLazy } from './common/get_rule_status_dropdown';
|
||||
import { getRuleTagFilterLazy } from './common/get_rule_tag_filter';
|
||||
import { getRuleStatusFilterLazy } from './common/get_rule_status_filter';
|
||||
|
@ -71,6 +72,7 @@ import { PLUGIN_ID } from './common/constants';
|
|||
import type { AlertsTableStateProps } from './application/sections/alerts_table/alerts_table_state';
|
||||
import { getAlertsTableStateLazy } from './common/get_alerts_table_state';
|
||||
import { ActionAccordionFormProps } from './application/sections/action_connector_form/action_form';
|
||||
import type { FieldBrowserProps } from './application/sections/field_browser/types';
|
||||
import { getRuleDefinitionLazy } from './common/get_rule_definition';
|
||||
import { RuleStatusPanelProps } from './application/sections/rule_details/components/rule_status_panel';
|
||||
|
||||
|
@ -101,6 +103,7 @@ export interface TriggersAndActionsUIPublicPluginStart {
|
|||
) => ReactElement<RuleEditProps>;
|
||||
getAlertsTable: (props: AlertsTableProps) => ReactElement<AlertsTableProps>;
|
||||
getAlertsStateTable: (props: AlertsTableStateProps) => ReactElement<AlertsTableStateProps>;
|
||||
getFieldBrowser: (props: FieldBrowserProps) => ReactElement<FieldBrowserProps>;
|
||||
getRuleStatusDropdown: (props: RuleStatusDropdownProps) => ReactElement<RuleStatusDropdownProps>;
|
||||
getRuleTagFilter: (props: RuleTagFilterProps) => ReactElement<RuleTagFilterProps>;
|
||||
getRuleStatusFilter: (props: RuleStatusFilterProps) => ReactElement<RuleStatusFilterProps>;
|
||||
|
@ -308,6 +311,9 @@ export class Plugin
|
|||
getAlertsTable: (props: AlertsTableProps) => {
|
||||
return getAlertsTableLazy(props);
|
||||
},
|
||||
getFieldBrowser: (props: FieldBrowserProps) => {
|
||||
return getFieldBrowserLazy(props);
|
||||
},
|
||||
getRuleStatusDropdown: (props: RuleStatusDropdownProps) => {
|
||||
return getRuleStatusDropdownLazy(props);
|
||||
},
|
||||
|
|
|
@ -58,6 +58,13 @@ import type { RuleEventLogListProps } from './application/sections/rule_details/
|
|||
import type { CreateConnectorFlyoutProps } from './application/sections/action_connector_form/create_connector_flyout';
|
||||
import type { EditConnectorFlyoutProps } from './application/sections/action_connector_form/edit_connector_flyout';
|
||||
import type { RulesListNotifyBadgeProps } from './application/sections/rules_list/components/rules_list_notify_badge';
|
||||
import type {
|
||||
FieldBrowserOptions,
|
||||
CreateFieldComponent,
|
||||
GetFieldTableColumns,
|
||||
FieldBrowserProps,
|
||||
BrowserFieldItem,
|
||||
} from './application/sections/field_browser/types';
|
||||
|
||||
// In Triggers and Actions we treat all `Alert`s as `SanitizedRule<RuleTypeParams>`
|
||||
// so the `Params` is a black-box of Record<string, unknown>
|
||||
|
@ -98,6 +105,11 @@ export type {
|
|||
CreateConnectorFlyoutProps,
|
||||
EditConnectorFlyoutProps,
|
||||
RulesListNotifyBadgeProps,
|
||||
FieldBrowserProps,
|
||||
FieldBrowserOptions,
|
||||
CreateFieldComponent,
|
||||
GetFieldTableColumns,
|
||||
BrowserFieldItem,
|
||||
};
|
||||
export type { ActionType, AsApiContract };
|
||||
export {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue