Skip to content

Commit 8e38a06

Browse files
committed
Unifying alert, document and event tables
1 parent 3790a2e commit 8e38a06

26 files changed

Lines changed: 2050 additions & 898 deletions

src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/test_utils/workflow_form_test_setup.tsx

Lines changed: 82 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*/
99

1010
import React from 'react';
11+
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
1112

1213
interface MockQuery {
1314
query: string;
@@ -101,6 +102,22 @@ export const MockDataViewPicker = ({ onChangeDataView }: MockDataViewPickerProps
101102
</div>
102103
);
103104

105+
const createMockDataQueryServices = () => {
106+
const { query } = dataPluginMock.createStartContract();
107+
return { query };
108+
};
109+
110+
const createMockSearchHitsResponse = (
111+
hits: Array<{ _id: string; _index: string; _source: Record<string, unknown> }>
112+
) => ({
113+
rawResponse: {
114+
hits: {
115+
total: { value: hits.length, relation: 'eq' as const },
116+
hits,
117+
},
118+
},
119+
});
120+
104121
/**
105122
* Creates mock Kibana services for event form tests
106123
*/
@@ -109,41 +126,57 @@ export const createEventFormKibanaMocks = () => {
109126
getActiveSpace: jest.fn().mockResolvedValue({ id: 'default' }),
110127
};
111128

129+
const mockFormatter = {
130+
convert: jest.fn((value: unknown) => ({ text: String(value ?? '') })),
131+
};
132+
112133
const mockDataView = {
113134
id: 'test-data-view',
114135
title: '.alerts-*-default',
115136
timeFieldName: '@timestamp',
116137
refreshFields: jest.fn().mockResolvedValue(undefined),
117-
getFieldByName: jest.fn().mockReturnValue(null),
138+
getFormatterForField: jest.fn().mockReturnValue(mockFormatter),
139+
getFieldByName: jest.fn((name: string) => ({
140+
name,
141+
type: name === '@timestamp' ? 'date' : 'string',
142+
esTypes: name === '@timestamp' ? ['date'] : ['keyword'],
143+
})),
144+
fields: {
145+
replaceAll: jest.fn(),
146+
getByName: jest.fn().mockReturnValue(null),
147+
getAll: jest.fn().mockReturnValue([]),
148+
create: jest.fn(),
149+
add: jest.fn(),
150+
remove: jest.fn(),
151+
update: jest.fn(),
152+
filter: jest.fn().mockReturnValue([]),
153+
},
118154
};
119155

156+
const mockAlertHits = [
157+
{
158+
_id: '1',
159+
_index: '.alerts-default',
160+
_source: {
161+
'@timestamp': '2024-01-01T00:00:00Z',
162+
'kibana.alert.rule.name': 'Test Rule',
163+
'kibana.alert.reason': 'test event created',
164+
message: 'Test message',
165+
},
166+
},
167+
];
168+
120169
const mockSearchSource = {
121170
setField: jest.fn(),
122171
fetch$: jest.fn().mockReturnValue({
123172
pipe: jest.fn().mockReturnValue({
124-
toPromise: jest.fn().mockResolvedValue({
125-
rawResponse: {
126-
hits: {
127-
hits: [
128-
{
129-
_id: '1',
130-
_index: '.alerts-default',
131-
_source: {
132-
'@timestamp': '2024-01-01T00:00:00Z',
133-
'kibana.alert.rule.name': 'Test Rule',
134-
'kibana.alert.reason': 'test event created',
135-
message: 'Test message',
136-
},
137-
},
138-
],
139-
},
140-
},
141-
}),
173+
toPromise: jest.fn().mockResolvedValue(createMockSearchHitsResponse(mockAlertHits)),
142174
}),
143175
}),
144176
};
145177

146178
const mockData = {
179+
...createMockDataQueryServices(),
147180
dataViews: {
148181
find: jest.fn().mockResolvedValue([]),
149182
create: jest.fn().mockResolvedValue(mockDataView),
@@ -155,24 +188,7 @@ export const createEventFormKibanaMocks = () => {
155188
},
156189
search: jest.fn().mockReturnValue({
157190
pipe: jest.fn().mockReturnValue({
158-
toPromise: jest.fn().mockResolvedValue({
159-
rawResponse: {
160-
hits: {
161-
hits: [
162-
{
163-
_id: '1',
164-
_index: '.alerts-default',
165-
_source: {
166-
'@timestamp': '2024-01-01T00:00:00Z',
167-
'kibana.alert.rule.name': 'Test Rule',
168-
'kibana.alert.reason': 'test event created',
169-
message: 'Test message',
170-
},
171-
},
172-
],
173-
},
174-
},
175-
}),
191+
toPromise: jest.fn().mockResolvedValue(createMockSearchHitsResponse(mockAlertHits)),
176192
}),
177193
}),
178194
},
@@ -195,16 +211,31 @@ export const createEventFormKibanaMocks = () => {
195211
* Creates mock Kibana services for index form tests
196212
*/
197213
export const createIndexFormKibanaMocks = () => {
214+
const mockFormatter = {
215+
convert: jest.fn((value: unknown) => ({ text: String(value ?? '') })),
216+
};
217+
198218
const createMockDataView = () => ({
199219
id: 'test-data-view-id',
200220
title: 'logs-*',
201221
name: 'logs-*',
222+
timeFieldName: '@timestamp',
202223
getIndexPattern: jest.fn().mockReturnValue('logs-*'),
203224
refreshFields: jest.fn().mockResolvedValue(undefined),
204-
getFieldByName: jest.fn().mockReturnValue(null),
225+
getFormatterForField: jest.fn().mockReturnValue(mockFormatter),
226+
getFieldByName: jest.fn((name: string) => ({
227+
name,
228+
type: name === '@timestamp' ? 'date' : 'string',
229+
esTypes: name === '@timestamp' ? ['date'] : ['keyword'],
230+
})),
205231
fields: {
232+
replaceAll: jest.fn(),
206233
getByName: jest.fn().mockReturnValue(null),
207234
getAll: jest.fn().mockReturnValue([]),
235+
create: jest.fn((spec: { name: string }) => ({ name: spec.name, type: 'string' })),
236+
add: jest.fn(),
237+
remove: jest.fn(),
238+
update: jest.fn(),
208239
length: 0,
209240
filter: jest.fn().mockReturnValue([]),
210241
},
@@ -217,27 +248,24 @@ export const createIndexFormKibanaMocks = () => {
217248
clearInstanceCache: jest.fn(),
218249
};
219250

251+
const mockDocumentHits = [
252+
{
253+
_id: '1',
254+
_index: 'logs-*',
255+
_source: {
256+
'@timestamp': '2024-01-01T00:00:00Z',
257+
message: 'Test log message',
258+
},
259+
},
260+
];
261+
220262
const mockData = {
263+
...createMockDataQueryServices(),
221264
search: {
222265
search: jest.fn().mockReturnValue({
223266
pipe: jest.fn().mockReturnValue({
224267
subscribe: jest.fn(({ next, complete }) => {
225-
next({
226-
rawResponse: {
227-
hits: {
228-
hits: [
229-
{
230-
_id: '1',
231-
_index: 'logs-*',
232-
_source: {
233-
'@timestamp': '2024-01-01T00:00:00Z',
234-
message: 'Test log message',
235-
},
236-
},
237-
],
238-
},
239-
},
240-
});
268+
next(createMockSearchHitsResponse(mockDocumentHits));
241269
complete();
242270
return { unsubscribe: jest.fn() };
243271
}),

src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/trigger_event_table_config.tsx

Lines changed: 27 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,20 @@
1010
import {
1111
EuiCodeBlock,
1212
type EuiDataGridCellPopoverElementProps,
13-
euiFontSize,
1413
EuiIconTip,
1514
EuiText,
1615
useEuiTheme,
1716
} from '@elastic/eui';
1817
import { css } from '@emotion/react';
19-
import React, { useCallback, useMemo, useState } from 'react';
18+
import React, { useCallback, useMemo } from 'react';
2019
import type { DataTableRecord } from '@kbn/discover-utils/types';
2120
import { i18n } from '@kbn/i18n';
2221
import { FormattedMessage, FormattedNumber } from '@kbn/i18n-react';
23-
import type { UiActionsStart } from '@kbn/ui-actions-plugin/public';
2422
import {
2523
type CustomCellRenderer,
2624
type CustomGridColumnsConfiguration,
2725
type DataTableColumnsMeta,
2826
getRenderCustomToolbarWithElements,
29-
type SortOrder,
3027
type UnifiedDataTableRenderCustomToolbar,
3128
type UnifiedDataTableRenderCustomToolbarProps,
3229
} from '@kbn/unified-data-table';
@@ -47,7 +44,13 @@ import {
4744
TriggerEventRunPayloadSelectionSync,
4845
TriggerEventTableSelectionCountSync,
4946
} from './workflow_execute_event_selection_sync';
50-
import { WorkflowTriggerEventDataGridCellPopover } from './workflow_trigger_event_data_grid_cell_popover';
47+
import { WorkflowExecuteDataGridCellPopover } from './workflow_execute_data_grid_cell_popover';
48+
import {
49+
buildUnifiedDataTableServices,
50+
getNoUnifiedDataTableCellActions,
51+
useUnifiedDataTableColumnState,
52+
useUnifiedDataTableTimestampTypography,
53+
} from './workflow_execute_unified_table_base';
5154

5255
export type {
5356
UseTriggerEventTableConfigOptions,
@@ -88,14 +91,22 @@ export function useTriggerEventTableConfig(
8891
onTriggerEventTableSelectionCountChange,
8992
} = options;
9093

91-
const euiThemeContext = useEuiTheme();
92-
const { euiTheme } = euiThemeContext;
94+
const { euiTheme } = useEuiTheme();
9395

94-
const [visibleTableColumns, setVisibleTableColumns] = useState<string[]>(() => [
95-
...DEFAULT_TRIGGER_EVENT_TABLE_COLUMNS,
96-
]);
97-
const [showTimeColumn, setShowTimeColumn] = useState(true);
98-
const [sort, setSort] = useState<SortOrder[]>([['@timestamp', 'desc']]);
96+
const {
97+
visibleTableColumns,
98+
showTimeColumn,
99+
sort,
100+
handleSortChange,
101+
handleUnifiedDataTableSetColumns,
102+
} = useUnifiedDataTableColumnState({
103+
defaultColumns: [...DEFAULT_TRIGGER_EVENT_TABLE_COLUMNS],
104+
dataView,
105+
fallbackColumns: VISIBLE_COLUMNS_FALLBACK,
106+
ensureColumnWhenOnlyTimeField: 'summary',
107+
});
108+
109+
const timestampCellTypography = useUnifiedDataTableTimestampTypography();
99110

100111
const dataTableRows = useMemo<DataTableRecord[]>(
101112
() =>
@@ -216,63 +227,8 @@ export function useTriggerEventTableConfig(
216227
);
217228

218229
const unifiedDataTableServices = useMemo(
219-
() => ({
220-
theme: services.theme,
221-
fieldFormats: services.fieldFormats,
222-
uiSettings: services.uiSettings,
223-
toastNotifications: services.notifications.toasts,
224-
storage: services.storage,
225-
data: {
226-
...services.data,
227-
dataViews: services.dataViews,
228-
},
229-
}),
230-
[
231-
services.data,
232-
services.dataViews,
233-
services.fieldFormats,
234-
services.notifications.toasts,
235-
services.storage,
236-
services.theme,
237-
services.uiSettings,
238-
]
239-
);
240-
241-
const getNoCellActions = useCallback<UiActionsStart['getTriggerCompatibleActions']>(
242-
async () => [],
243-
[]
244-
);
245-
246-
const handleSortChange = useCallback((nextSort: string[][]) => {
247-
setSort(
248-
nextSort
249-
.filter(
250-
(value): value is [string, 'asc' | 'desc'] =>
251-
Array.isArray(value) &&
252-
value.length === 2 &&
253-
typeof value[0] === 'string' &&
254-
(value[1] === 'asc' || value[1] === 'desc')
255-
)
256-
.map(([field, direction]) => [field, direction])
257-
);
258-
}, []);
259-
260-
const handleUnifiedDataTableSetColumns = useCallback(
261-
(columns: string[], hideTimeColumn: boolean) => {
262-
let nextColumns = columns.length > 0 ? [...columns] : [...VISIBLE_COLUMNS_FALLBACK];
263-
const timeFieldName = dataView?.timeFieldName;
264-
if (timeFieldName && nextColumns.length === 1 && nextColumns[0] === timeFieldName) {
265-
nextColumns = [...nextColumns, 'summary'];
266-
}
267-
setVisibleTableColumns(nextColumns);
268-
setShowTimeColumn(!hideTimeColumn);
269-
},
270-
[dataView]
271-
);
272-
273-
const timestampCellTypography = useMemo(
274-
() => euiFontSize(euiThemeContext, 'xs'),
275-
[euiThemeContext]
230+
() => buildUnifiedDataTableServices(services),
231+
[services]
276232
);
277233

278234
const leftToolbarContent = useMemo(() => {
@@ -365,7 +321,7 @@ export function useTriggerEventTableConfig(
365321
]);
366322

367323
const renderCellPopover = useCallback((popoverProps: EuiDataGridCellPopoverElementProps) => {
368-
return <WorkflowTriggerEventDataGridCellPopover {...popoverProps} />;
324+
return <WorkflowExecuteDataGridCellPopover {...popoverProps} />;
369325
}, []);
370326

371327
return {
@@ -377,7 +333,7 @@ export function useTriggerEventTableConfig(
377333
externalCustomRenderers,
378334
customGridColumnsConfiguration,
379335
unifiedDataTableServices,
380-
getNoCellActions,
336+
getNoCellActions: getNoUnifiedDataTableCellActions,
381337
handleSortChange,
382338
handleUnifiedDataTableSetColumns,
383339
timestampCellTypography,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
import { useWorkflowExecuteHitTableConfigImpl } from './use_workflow_execute_hit_table_config_impl';
11+
import type {
12+
UseWorkflowExecuteHitTableConfigOptions,
13+
UseWorkflowExecuteHitTableConfigResult,
14+
} from './use_workflow_execute_hit_table_config_types';
15+
16+
export type {
17+
UseWorkflowExecuteHitTableConfigOptions,
18+
UseWorkflowExecuteHitTableConfigResult,
19+
} from './use_workflow_execute_hit_table_config_types';
20+
21+
export type UseWorkflowExecuteHitTableConfigFn = (
22+
options: UseWorkflowExecuteHitTableConfigOptions
23+
) => UseWorkflowExecuteHitTableConfigResult;
24+
25+
export const useWorkflowExecuteHitTableConfig =
26+
useWorkflowExecuteHitTableConfigImpl as UseWorkflowExecuteHitTableConfigFn;

0 commit comments

Comments
 (0)