[Discover] Add Actions header to unified data table (#214366) (#220824)

## Summary

For accesibility reasons we need to label all the headers. At this point
in time the actions from the unified data table have an empty header,
for that we are grouping them in a column and labeling them as
"Actions".

| Scenario | Before | After (only icon) |
|----------|--------|-----------------|
| 1 icon | <img width="1035" alt="image"
src="https://github.com/user-attachments/assets/2884904b-42ce-432f-9cf7-90140c349ca2"
/> | <img width="1036" alt="image"
src="https://github.com/user-attachments/assets/84947522-0426-4317-a642-1fb6cab27306"
/> |
| Multiple icons | <img width="558" alt="image"
src="https://github.com/user-attachments/assets/fe4be017-116d-4586-888d-0e81b0b0395e"
/> | <img width="557" alt="image"
src="https://github.com/user-attachments/assets/58ea3261-703e-4d17-906c-29ee5a40569f"
/> |


### Checklist

Check the PR satisfies following conditions. 

Reviewers should verify this PR satisfies this list as well.

- [ ] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [ ] If a plugin configuration key changed, check if it needs to be
allowlisted in the cloud and added to the [docker
list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)
- [ ] This was checked for breaking HTTP API changes, and any breaking
changes have been approved by the breaking-change committee. The
`release_note:breaking` label should be applied in these situations.
- [ ] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
- [ ] The PR description includes the appropriate Release Notes section,
and the correct `release_note:*` label is applied per the
[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

### Identify risks

Does this PR introduce any risks? For example, consider risks like hard
to test bugs, performance regression, potential of data loss.

Describe the risk, its severity, and mitigation for each identified
risk. Invite stakeholders and evaluate how to proceed before merging.

- [ ] [See some risk
examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx)
- [ ] ...

---------

Co-authored-by: Matthias Wilhelm <matthias.wilhelm@elastic.co>
This commit is contained in:
Alejandro García Parrondo 2025-06-03 12:06:52 +02:00 committed by GitHub
parent 26891242ee
commit b4b49acb68
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 730 additions and 181 deletions

View file

@ -30,8 +30,7 @@ interface DegradedDocsControlProps extends Partial<RowControlProps> {
*/
export const createDegradedDocsControl = (props?: DegradedDocsControlProps): RowControlColumn => ({
id: 'connectedDegradedDocs',
headerAriaLabel: actionsHeaderAriaLabelDegradedAction,
renderControl: (Control, rowProps) => {
render: (Control, rowProps) => {
return <DegradedDocs Control={Control} rowProps={rowProps} {...props} />;
},
});

View file

@ -24,17 +24,11 @@ import { getStacktraceFields } from '../../utils/get_stack_trace_fields';
*/
export const createStacktraceControl = (props?: Partial<RowControlProps>): RowControlColumn => ({
id: 'connectedStacktraceDocs',
headerAriaLabel: actionsHeaderAriaLabelStacktraceAction,
renderControl: (Control, rowProps) => {
render: (Control, rowProps) => {
return <Stacktrace Control={Control} rowProps={rowProps} {...props} />;
},
});
const actionsHeaderAriaLabelStacktraceAction = i18n.translate(
'discover.customControl.stacktraceArialLabel',
{ defaultMessage: 'Access to available stacktraces' }
);
const stacktraceAvailableControlButton = i18n.translate(
'discover.customControl.stacktrace.available',
{ defaultMessage: 'Stacktraces available' }

View file

@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { EuiButtonIconProps, EuiDataGridControlColumn, IconType } from '@elastic/eui';
import { EuiButtonIconProps, IconType } from '@elastic/eui';
import type { Interpolation, Theme } from '@emotion/react';
import React, { FC, ReactElement } from 'react';
import { DataTableRecord } from '../../types';
@ -32,7 +32,5 @@ export type RowControlComponent = FC<RowControlProps>;
export interface RowControlColumn {
id: string;
headerAriaLabel: string;
headerCellRender?: EuiDataGridControlColumn['headerCellRender'];
renderControl: (Control: RowControlComponent, props: RowControlRowProps) => ReactElement;
render: (Control: RowControlComponent, props: RowControlRowProps) => ReactElement;
}

View file

@ -120,10 +120,9 @@ export const testLeadingControlColumn: EuiDataGridControlColumn = {
};
export const mockRowAdditionalLeadingControls = ['visBarVerticalStacked', 'heart', 'inspect'].map(
(iconType, index): RowControlColumn => ({
id: `exampleControl_${iconType}`,
headerAriaLabel: `Example Row Control ${iconType}`,
renderControl: (Control, rowProps) => {
(iconType): RowControlColumn => ({
id: `exampleRowControl-${iconType}`,
render: (Control, rowProps) => {
return (
<Control
data-test-subj={`exampleRowControl-${iconType}`}

View file

@ -0,0 +1,381 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import React from 'react';
import { getActionsColumn } from './actions_column';
import { RowControlColumn } from '@kbn/discover-utils';
import { render, within, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import * as actionsHeader from './actions_header';
import { UnifiedDataTableContext } from '../../../table_context';
import { dataTableContextComplexMock } from '../../../../__mocks__/table_context';
describe('getActionsColumn', () => {
describe('given no columns', () => {
it('returns null', () => {
const result = getActionsColumn({
baseColumns: [],
externalControlColumns: undefined,
rowAdditionalLeadingControls: undefined,
});
expect(result).toBeNull();
});
});
describe.each([
{
baseColumns: [() => <div>Base column</div>],
rowAdditionalLeadingControls: [],
externalControlColumns: [],
expectedWidth: 24,
expectedTexts: ['Base column'],
description: '1 base column',
},
{
baseColumns: [() => <div>Base column 1</div>, () => <div>Base column 2</div>],
rowAdditionalLeadingControls: [],
externalControlColumns: [],
expectedWidth: 48,
expectedTexts: ['Base column 1', 'Base column 2'],
description: '2 base columns',
},
{
baseColumns: [],
rowAdditionalLeadingControls: [
{
id: 'row-control-column',
render: () => <div>Row control column</div>,
},
],
externalControlColumns: [],
expectedWidth: 24,
expectedTexts: ['Row control column'],
description: '1 row additional leading control column',
},
{
baseColumns: [],
rowAdditionalLeadingControls: [
{
id: 'row-control-column-1',
render: () => <div>Row control column 1</div>,
},
{
id: 'row-control-column-2',
render: () => <div>Row control column 2</div>,
},
],
externalControlColumns: [],
expectedWidth: 48,
expectedTexts: ['Row control column 1', 'Row control column 2'],
description: '2 row additional leading control columns',
},
{
baseColumns: [],
rowAdditionalLeadingControls: [],
externalControlColumns: [
{
id: 'external-1',
width: 80,
rowCellRender: () => <div>External control column</div>,
headerCellRender: () => <div>External control column</div>,
},
],
expectedWidth: 80,
expectedTexts: ['External control column'],
description: '1 external control column',
},
{
baseColumns: [],
rowAdditionalLeadingControls: [],
externalControlColumns: [
{
id: 'external-1',
width: 80,
rowCellRender: () => <div>External control column 1</div>,
headerCellRender: () => <div>External control column 1</div>,
},
{
id: 'external-2',
width: 90,
rowCellRender: () => <div>External control column 2</div>,
headerCellRender: () => <div>External control column 2</div>,
},
],
expectedWidth: 170,
expectedTexts: ['External control column 1', 'External control column 2'],
description: '2 external control columns',
},
{
baseColumns: [() => <div>Base column 1</div>, () => <div>Base column 2</div>],
rowAdditionalLeadingControls: [
{
id: 'row-control-column-1',
render: () => <div>Row control column 1</div>,
},
{
id: 'row-control-column-2',
render: () => <div>Row control column 2</div>,
},
],
externalControlColumns: [],
expectedWidth: 96,
expectedTexts: [
'Base column 1',
'Base column 2',
'Row control column 1',
'Row control column 2',
],
description: '2 base columns and 2 row additional leading control columns',
},
{
baseColumns: [() => <div>Base column 1</div>, () => <div>Base column 2</div>],
rowAdditionalLeadingControls: [],
externalControlColumns: [
{
id: 'external-1',
width: 80,
rowCellRender: () => <div>External control column 1</div>,
headerCellRender: () => <div>External control column 1</div>,
},
{
id: 'external-2',
width: 90,
rowCellRender: () => <div>External control column 2</div>,
headerCellRender: () => <div>External control column 2</div>,
},
],
expectedWidth: 222,
expectedTexts: [
'Base column 1',
'Base column 2',
'External control column 1',
'External control column 2',
],
description: '2 base columns and 2 external control columns',
},
{
baseColumns: [],
rowAdditionalLeadingControls: [
{
id: 'row-control-column-1',
render: () => <div>Row control column 1</div>,
},
{
id: 'row-control-column-2',
render: () => <div>Row control column 2</div>,
},
],
externalControlColumns: [
{
id: 'external-1',
width: 80,
rowCellRender: () => <div>External control column 1</div>,
headerCellRender: () => <div>External control column 1</div>,
},
{
id: 'external-2',
width: 90,
rowCellRender: () => <div>External control column 2</div>,
headerCellRender: () => <div>External control column 2</div>,
},
],
expectedWidth: 218,
expectedTexts: [
'Row control column 1',
'Row control column 2',
'External control column 1',
'External control column 2',
],
description: '2 row additional leading columns and 2 external control columns',
},
{
baseColumns: [() => <div>Base column 1</div>, () => <div>Base column 2</div>],
rowAdditionalLeadingControls: [
{
id: 'row-control-column-1',
render: () => <div>Row control column 1</div>,
},
{
id: 'row-control-column-2',
render: () => <div>Row control column 2</div>,
},
],
externalControlColumns: [
{
id: 'external-1',
width: 80,
rowCellRender: () => <div>External control column 1</div>,
headerCellRender: () => <div>External control column 1</div>,
},
{
id: 'external-2',
width: 90,
rowCellRender: () => <div>External control column 2</div>,
headerCellRender: () => <div>External control column 2</div>,
},
],
expectedWidth: 270,
expectedTexts: [
'Base column 1',
'Base column 2',
'Row control column 1',
'Row control column 2',
'External control column 1',
'External control column 2',
],
description: '2 of each column type',
},
])(
'given $description',
({
expectedWidth,
expectedTexts,
baseColumns,
rowAdditionalLeadingControls,
externalControlColumns,
}) => {
it('returns a column with the correct width', () => {
const result = getActionsColumn({
baseColumns,
rowAdditionalLeadingControls:
rowAdditionalLeadingControls as unknown as RowControlColumn[],
externalControlColumns,
});
expect(result).toEqual(
expect.objectContaining({
width: expectedWidth,
})
);
});
it('should return the header cell render function', () => {
// Given
const actionsHeaderSpy = jest.spyOn(actionsHeader, 'ActionsHeader');
// When
const result = getActionsColumn({
baseColumns,
rowAdditionalLeadingControls:
rowAdditionalLeadingControls as unknown as RowControlColumn[],
externalControlColumns,
});
expect(result?.headerCellRender).toBeInstanceOf(Function);
// Then
render(result?.headerCellRender());
expect(actionsHeaderSpy).toHaveBeenCalledWith(
{
maxWidth: expectedWidth,
},
{}
);
});
it('should return the row cell render function', () => {
// Given
const result = getActionsColumn({
baseColumns,
rowAdditionalLeadingControls,
externalControlColumns,
});
expect(result?.rowCellRender).toBeInstanceOf(Function);
// When
render(
<UnifiedDataTableContext.Provider value={dataTableContextComplexMock}>
{result?.rowCellRender({
setCellProps: jest.fn(),
rowIndex: 0,
colIndex: 0,
columnId: 'actions',
isExpandable: false,
isExpanded: false,
isDetails: false,
})}
</UnifiedDataTableContext.Provider>
);
// Then
expectedTexts.forEach((text) => {
within(screen.getByTestId('unifiedDataTable_actionsColumnCell')).getByText(text);
});
});
}
);
describe('given 4 row additional leading control columns', () => {
const rowAdditionalLeadingControls: RowControlColumn[] = [
{
id: 'row-control-column-1',
render: (Control) => (
<Control iconType="empty" label="Row control column 1" onClick={jest.fn()} />
),
},
{
id: 'row-control-column-2',
render: (Control) => (
<Control iconType="empty" label="Row control column 2" onClick={jest.fn()} />
),
},
{
id: 'row-control-column-3',
render: (Control) => (
<Control iconType="empty" label="Row control column 3" onClick={jest.fn()} />
),
},
{
id: 'row-control-column-4',
render: (Control) => (
<Control iconType="empty" label="Row control column 4" onClick={jest.fn()} />
),
},
];
it('should return a menu control column', async () => {
// Given
const user = userEvent.setup();
// When
const result = getActionsColumn({
baseColumns: [],
rowAdditionalLeadingControls,
externalControlColumns: [],
});
render(
<UnifiedDataTableContext.Provider value={dataTableContextComplexMock}>
{result?.rowCellRender({
setCellProps: jest.fn(),
rowIndex: 0,
colIndex: 0,
columnId: 'actions',
isExpandable: false,
isExpanded: false,
isDetails: false,
})}
</UnifiedDataTableContext.Provider>
);
// Then
// The first item appears by itself
expect(
screen.getByTestId(`unifiedDataTable_rowControl_${rowAdditionalLeadingControls[0].id}`)
);
// The rest of the items are in a menu
expect(screen.getByLabelText('Additional actions')).toBeVisible();
await user.click(screen.getByLabelText('Additional actions'));
rowAdditionalLeadingControls.slice(1).forEach((control) => {
expect(screen.getByTestId(`unifiedDataTable_rowMenu_${control.id}`)).toBeVisible();
});
});
});
});

View file

@ -0,0 +1,78 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import {
EuiDataGridCellValueElementProps,
EuiDataGridControlColumn,
EuiFlexGroup,
RenderCellValue,
} from '@elastic/eui';
import React from 'react';
import type { RowControlColumn } from '@kbn/discover-utils';
import { DEFAULT_CONTROL_COLUMN_WIDTH } from '../../../constants';
import { getAdditionalRowControlColumns } from '../additional_row_control';
import { ActionsHeader } from './actions_header';
const COLUMN_ID = 'actions';
const EXTERNAL_CONTROL_COLUMNS_SPACING = 4;
const HorizontalSpacer = () => <div css={{ paddingLeft: EXTERNAL_CONTROL_COLUMNS_SPACING }} />;
export const getActionsColumn = ({
baseColumns,
externalControlColumns,
rowAdditionalLeadingControls,
}: {
baseColumns: RenderCellValue[];
rowAdditionalLeadingControls?: RowControlColumn[];
externalControlColumns?: EuiDataGridControlColumn[];
}) => {
if (
!baseColumns.length &&
!externalControlColumns?.length &&
!rowAdditionalLeadingControls?.length
) {
return null;
}
let columnWidth = baseColumns.length * DEFAULT_CONTROL_COLUMN_WIDTH;
const actions = [...baseColumns];
if (externalControlColumns?.length) {
if (actions.length > 0) {
actions.push(HorizontalSpacer);
columnWidth += EXTERNAL_CONTROL_COLUMNS_SPACING;
}
actions.push(...externalControlColumns.map((column) => column.rowCellRender));
columnWidth += externalControlColumns.reduce((acc, column) => acc + column.width, 0);
}
if (rowAdditionalLeadingControls?.length) {
const additionalRowControColumns = getAdditionalRowControlColumns(rowAdditionalLeadingControls);
actions.push(...additionalRowControColumns);
columnWidth += DEFAULT_CONTROL_COLUMN_WIDTH * additionalRowControColumns.length;
}
return {
id: COLUMN_ID,
width: columnWidth,
rowCellRender: (props: EuiDataGridCellValueElementProps) => (
<EuiFlexGroup
data-test-subj="unifiedDataTable_actionsColumnCell"
responsive={false}
alignItems="center"
gutterSize="none"
>
{actions.map((Action, idx) => (
<Action key={idx} {...props} />
))}
</EuiFlexGroup>
),
headerCellRender: () => <ActionsHeader maxWidth={columnWidth} />,
};
};

View file

@ -0,0 +1,54 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import React from 'react';
import { render, screen } from '@testing-library/react';
import { ActionsHeader } from './actions_header';
const setup = (props: React.ComponentProps<typeof ActionsHeader>) => {
render(<ActionsHeader {...props} />);
};
describe('<ActionsHeader />', () => {
describe('when the maxWidth is greater than the text width', () => {
it('should show the text', () => {
const maxWidth = 500;
setup({ maxWidth });
expect(screen.getByTestId('unifiedDataTable_actionsColumnHeaderText')).toBeVisible();
});
it('should NOT show the icon', () => {
const maxWidth = 500;
setup({ maxWidth });
expect(
screen.queryByTestId('unifiedDataTable_actionsColumnHeaderIcon')
).not.toBeInTheDocument();
});
});
describe('when the maxWidth is less than the text width', () => {
it('should NOT show the text', () => {
const maxWidth = 0;
setup({ maxWidth });
expect(
screen.queryByTestId('unifiedDataTable_actionsColumnHeaderText')
).not.toBeInTheDocument();
});
it('should show the icon', () => {
const maxWidth = 0;
setup({ maxWidth });
expect(screen.getByTestId('unifiedDataTable_actionsColumnHeaderIcon')).toBeVisible();
});
});
});

View file

@ -0,0 +1,67 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import React, { useLayoutEffect, useRef, useState } from 'react';
import { EuiIconTip, EuiScreenReaderOnly, useEuiTheme } from '@elastic/eui';
import { css } from '@emotion/react';
import { i18n } from '@kbn/i18n';
import ColumnHeaderTruncateContainer from '../../column_header_truncate_container';
export const ActionsHeader = ({ maxWidth }: { maxWidth: number }) => {
const textRef = useRef<HTMLSpanElement>(null);
const [showText, setShowText] = useState(false);
const { euiTheme } = useEuiTheme();
useLayoutEffect(() => {
if (!textRef.current) return;
const textWidth = textRef.current.getBoundingClientRect().width;
setShowText(textWidth < maxWidth);
}, [textRef, maxWidth, setShowText]);
const actionsText = i18n.translate('unifiedDataTable.controlColumnsActionHeader', {
defaultMessage: 'Actions',
});
return (
<div css={{ padding: euiTheme.size.xs }}>
<ColumnHeaderTruncateContainer>
<EuiScreenReaderOnly>
<span>
{i18n.translate('unifiedDataTable.actionsColumnHeader', {
defaultMessage: 'Actions column',
})}
</span>
</EuiScreenReaderOnly>
{showText ? (
<span data-test-subj="unifiedDataTable_actionsColumnHeaderText">{actionsText}</span>
) : (
<EuiIconTip
iconProps={{
'data-test-subj': 'unifiedDataTable_actionsColumnHeaderIcon',
}}
type="iInCircle"
content={actionsText}
/>
)}
{/* Hidden measurement span */}
<span
ref={textRef}
css={css`
position: absolute;
visibility: hidden;
white-space: nowrap;
pointer-events: none;
`}
>
{actionsText}
</span>
</ColumnHeaderTruncateContainer>
</div>
);
};

View file

@ -0,0 +1,10 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
export { getActionsColumn } from './actions_column';

View file

@ -9,6 +9,33 @@
import { getAdditionalRowControlColumns } from './get_additional_row_control_columns';
import { mockRowAdditionalLeadingControls } from '../../../../__mocks__/external_control_columns';
import { render, screen } from '@testing-library/react';
import React from 'react';
import { UnifiedDataTableContext } from '../../../table_context';
import { dataTableContextComplexMock } from '../../../../__mocks__/table_context';
import { userEvent } from '@testing-library/user-event';
import { RowControlColumn } from '@kbn/discover-utils';
const setup = (rowControlColumns: RowControlColumn[]) => {
const columns = getAdditionalRowControlColumns(rowControlColumns);
render(
<UnifiedDataTableContext.Provider value={dataTableContextComplexMock}>
{columns.map((Column, idx) => (
<Column
key={idx}
setCellProps={jest.fn()}
rowIndex={0}
colIndex={0}
columnId="actions"
isExpandable
isExpanded
isDetails
/>
))}
</UnifiedDataTableContext.Provider>
);
};
describe('getAdditionalRowControlColumns', () => {
it('should work correctly for 0 controls', () => {
@ -18,35 +45,46 @@ describe('getAdditionalRowControlColumns', () => {
});
it('should work correctly for 1 control', () => {
const columns = getAdditionalRowControlColumns([mockRowAdditionalLeadingControls[0]]);
// Given
const rowControlColumnMock = mockRowAdditionalLeadingControls[0];
expect(columns.map((column) => column.id)).toEqual([
`additionalRowControl_${mockRowAdditionalLeadingControls[0].id}`,
]);
// When
setup([rowControlColumnMock]);
// Then
expect(screen.getByTestId(rowControlColumnMock.id)).toBeVisible();
});
it('should work correctly for 2 controls', () => {
const columns = getAdditionalRowControlColumns([
mockRowAdditionalLeadingControls[0],
mockRowAdditionalLeadingControls[1],
]);
// Given
const mocks = [mockRowAdditionalLeadingControls[0], mockRowAdditionalLeadingControls[1]];
expect(columns.map((column) => column.id)).toEqual([
`additionalRowControl_${mockRowAdditionalLeadingControls[0].id}`,
`additionalRowControl_${mockRowAdditionalLeadingControls[1].id}`,
]);
// When
setup(mocks);
// Then
expect(screen.getByTestId(mocks[0].id)).toBeVisible();
expect(screen.getByTestId(mocks[1].id)).toBeVisible();
});
it('should work correctly for 3 and more controls', () => {
const columns = getAdditionalRowControlColumns([
it('should work correctly for 3 and more controls', async () => {
// Given
const user = userEvent.setup();
const mocks = [
mockRowAdditionalLeadingControls[0],
mockRowAdditionalLeadingControls[1],
mockRowAdditionalLeadingControls[2],
]);
];
expect(columns.map((column) => column.id)).toEqual([
`additionalRowControl_${mockRowAdditionalLeadingControls[0].id}`,
`additionalRowControl_menuControl`,
]);
// When
setup(mocks);
// Then
expect(screen.getByTestId(mocks[0].id)).toBeVisible();
// The other elements are hidden under the menu button
await user.click(screen.getByTestId('unifiedDataTable_additionalRowControl_actionsMenu'));
expect(screen.getByTestId(mocks[1].id)).toBeVisible();
expect(screen.getByTestId(mocks[2].id)).toBeVisible();
});
});

View file

@ -7,14 +7,11 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import type { EuiDataGridControlColumn } from '@elastic/eui';
import { RowControlColumn } from '@kbn/discover-utils';
import { getRowControlColumn } from './row_control_column';
import { getRowMenuControlColumn } from './row_menu_control_column';
export const getAdditionalRowControlColumns = (
rowControlColumns: RowControlColumn[]
): EuiDataGridControlColumn[] => {
export const getAdditionalRowControlColumns = (rowControlColumns: RowControlColumn[]) => {
if (rowControlColumns.length <= 2) {
return rowControlColumns.map(getRowControlColumn);
}

View file

@ -8,7 +8,6 @@
*/
import React from 'react';
import type { EuiDataGridCellValueElementProps } from '@elastic/eui';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { getRowControlColumn } from './row_control_column';
@ -24,14 +23,11 @@ describe('getRowControlColumn', () => {
const mockClick = jest.fn();
const props = {
id: 'test_row_control',
headerAriaLabel: 'row control',
renderControl: jest.fn((Control, rowProps) => (
render: jest.fn((Control, rowProps) => (
<Control label={`test-${rowProps.rowIndex}`} iconType="heart" onClick={mockClick} />
)),
};
const rowControlColumn = getRowControlColumn(props);
const RowControlColumn =
rowControlColumn.rowCellRender as React.FC<EuiDataGridCellValueElementProps>;
const RowControlColumn = getRowControlColumn(props);
render(
<UnifiedDataTableContext.Provider value={contextMock}>
<RowControlColumn
@ -56,8 +52,7 @@ describe('getRowControlColumn', () => {
it('should wrap the Control button with a tooltip when tooltipContent is passed', async () => {
const props = {
id: 'test_row_control',
headerAriaLabel: 'row control',
renderControl: jest.fn((Control, rowProps) => (
render: jest.fn((Control, rowProps) => (
<Control
label={`test-${rowProps.rowIndex}`}
tooltipContent="Control tooltip text!"
@ -66,9 +61,7 @@ describe('getRowControlColumn', () => {
/>
)),
};
const rowControlColumn = getRowControlColumn(props);
const RowControlColumn =
rowControlColumn.rowCellRender as React.FC<EuiDataGridCellValueElementProps>;
const RowControlColumn = getRowControlColumn(props);
render(
<UnifiedDataTableContext.Provider value={contextMock}>
<RowControlColumn

View file

@ -8,22 +8,15 @@
*/
import React, { useMemo } from 'react';
import {
EuiButtonIcon,
EuiDataGridCellValueElementProps,
EuiDataGridControlColumn,
EuiScreenReaderOnly,
EuiToolTip,
} from '@elastic/eui';
import { EuiButtonIcon, EuiDataGridCellValueElementProps, EuiToolTip } from '@elastic/eui';
import { RowControlColumn, RowControlProps } from '@kbn/discover-utils';
import { DEFAULT_CONTROL_COLUMN_WIDTH } from '../../../constants';
import { useControlColumn } from '../../../hooks/use_control_column';
export const RowControlCell = ({
renderControl,
rowControlColumn,
...props
}: EuiDataGridCellValueElementProps & {
renderControl: RowControlColumn['renderControl'];
rowControlColumn: RowControlColumn;
}) => {
const { record, rowIndex } = useControlColumn(props);
@ -47,7 +40,7 @@ export const RowControlCell = ({
<EuiButtonIcon
aria-label={label}
color={color ?? 'text'}
data-test-subj={dataTestSubj ?? `unifiedDataTable_rowControl_${props.columnId}`}
data-test-subj={dataTestSubj ?? `unifiedDataTable_rowControl_${rowControlColumn.id}`}
disabled={disabled}
iconSize="s"
iconType={iconType}
@ -75,29 +68,14 @@ export const RowControlCell = ({
return control;
},
[props.columnId, record, rowIndex]
[rowControlColumn.id, record, rowIndex]
);
return record ? renderControl(Control, { record, rowIndex }) : null;
return record ? rowControlColumn.render(Control, { record, rowIndex }) : null;
};
export const getRowControlColumn = (
rowControlColumn: RowControlColumn
): EuiDataGridControlColumn => {
const { id, headerAriaLabel, headerCellRender, renderControl } = rowControlColumn;
return {
id: `additionalRowControl_${id}`,
width: DEFAULT_CONTROL_COLUMN_WIDTH,
headerCellRender:
headerCellRender ??
(() => (
<EuiScreenReaderOnly>
<span>{headerAriaLabel}</span>
</EuiScreenReaderOnly>
)),
rowCellRender: (props) => {
return <RowControlCell {...props} renderControl={renderControl} />;
},
export const getRowControlColumn = (rowControlColumn: RowControlColumn) => {
return (props: EuiDataGridCellValueElementProps) => {
return <RowControlCell {...props} rowControlColumn={rowControlColumn} />;
};
};

View file

@ -8,7 +8,6 @@
*/
import React from 'react';
import type { EuiDataGridCellValueElementProps } from '@elastic/eui';
import { render, screen } from '@testing-library/react';
import { getRowMenuControlColumn } from './row_menu_control_column';
import { dataTableContextMock } from '../../../../__mocks__/table_context';
@ -25,8 +24,7 @@ describe('getRowMenuControlColumn', () => {
const mockClick = jest.fn();
const props = {
id: 'test_row_menu_control',
headerAriaLabel: 'row control',
renderControl: jest.fn((Control, rowProps) => (
render: jest.fn((Control, rowProps) => (
<Control
label={`test-${rowProps.rowIndex}`}
tooltipContent={`test-${rowProps.rowIndex}`}
@ -35,13 +33,11 @@ describe('getRowMenuControlColumn', () => {
/>
)),
};
const rowMenuControlColumn = getRowMenuControlColumn([
const RowMenuControlColumn = getRowMenuControlColumn([
props,
mockRowAdditionalLeadingControls[0],
mockRowAdditionalLeadingControls[1],
]);
const RowMenuControlColumn =
rowMenuControlColumn.rowCellRender as React.FC<EuiDataGridCellValueElementProps>;
render(
<UnifiedDataTableContext.Provider value={contextMock}>
<RowMenuControlColumn
@ -55,7 +51,9 @@ describe('getRowMenuControlColumn', () => {
/>
</UnifiedDataTableContext.Provider>
);
const menuButton = screen.getByTestId('unifiedDataTable_test_row_menu_control');
const menuButton = screen.getByTestId(
'unifiedDataTable_additionalRowControl_test_row_menu_controlMenu'
);
expect(menuButton).toBeInTheDocument();
await userEvent.click(menuButton);

View file

@ -13,14 +13,11 @@ import {
EuiContextMenuItem,
EuiContextMenuPanel,
EuiDataGridCellValueElementProps,
EuiDataGridControlColumn,
EuiPopover,
EuiScreenReaderOnly,
EuiToolTip,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { RowControlColumn, RowControlProps } from '@kbn/discover-utils';
import { DEFAULT_CONTROL_COLUMN_WIDTH } from '../../../constants';
import { useControlColumn } from '../../../hooks/use_control_column';
/**
@ -68,7 +65,7 @@ export const RowMenuControlCell = ({
const Control = getControlComponent(rowControlColumn.id);
return (
<Fragment key={rowControlColumn.id}>
{record ? rowControlColumn.renderControl(Control, { record, rowIndex }) : null}
{record ? rowControlColumn.render(Control, { record, rowIndex }) : null}
</Fragment>
);
}),
@ -82,7 +79,7 @@ export const RowMenuControlCell = ({
button={
<EuiToolTip content={buttonLabel} delay="long">
<EuiButtonIcon
data-test-subj={`unifiedDataTable_${props.columnId}`}
data-test-subj={`unifiedDataTable_additionalRowControl_${props.columnId}Menu`}
iconSize="s"
iconType="boxesVertical"
color="text"
@ -103,23 +100,8 @@ export const RowMenuControlCell = ({
);
};
export const getRowMenuControlColumn = (
rowControlColumns: RowControlColumn[]
): EuiDataGridControlColumn => {
return {
id: 'additionalRowControl_menuControl',
width: DEFAULT_CONTROL_COLUMN_WIDTH,
headerCellRender: () => (
<EuiScreenReaderOnly>
<span>
{i18n.translate('unifiedDataTable.additionalActionsColumnHeader', {
defaultMessage: 'Additional actions column',
})}
</span>
</EuiScreenReaderOnly>
),
rowCellRender: (props) => {
return <RowMenuControlCell {...props} rowControlColumns={rowControlColumns} />;
},
export const getRowMenuControlColumn = (rowControlColumns: RowControlColumn[]) => {
return (props: EuiDataGridCellValueElementProps) => {
return <RowMenuControlCell {...props} rowControlColumns={rowControlColumns} />;
};
};

View file

@ -13,3 +13,5 @@ export {
} from './color_indicator';
export { getAdditionalRowControlColumns } from './additional_row_control';
export { getActionsColumn } from './actions_column';

View file

@ -797,9 +797,13 @@ describe('UnifiedDataTable', () => {
expect(
findTestSubject(component, 'exampleRowControl-visBarVerticalStacked').exists()
).toBeTruthy();
expect(
findTestSubject(component, 'unifiedDataTable_additionalRowControl_menuControl').exists()
).toBeTruthy();
// The other actions are within the popover
findTestSubject(component, 'unifiedDataTable_additionalRowControl_actionsMenu')
.first()
.simulate('click');
expect(findTestSubject(component, 'exampleRowControl-heart').exists()).toBeTruthy();
expect(findTestSubject(component, 'exampleRowControl-inspect').exists()).toBeTruthy();
},
EXTENDED_JEST_TIMEOUT
);

View file

@ -91,8 +91,8 @@ import { getCustomCellPopoverRenderer } from '../utils/get_render_cell_popover';
import { useSelectedDocs } from '../hooks/use_selected_docs';
import {
getColorIndicatorControlColumn,
getActionsColumn,
type ColorIndicatorControlColumnParams,
getAdditionalRowControlColumns,
} from './custom_control_columns';
import { useSorting } from '../hooks/use_sorting';
@ -949,34 +949,32 @@ export const UnifiedDataTable = ({
const canSetExpandedDoc = Boolean(setExpandedDoc && !!renderDocumentView);
const leadingControlColumns: EuiDataGridControlColumn[] = useMemo(() => {
const defaultControlColumns = getLeadControlColumns({ rows: displayedRows, canSetExpandedDoc });
const internalControlColumns = controlColumnIds
? // reorder the default controls as per controlColumnIds
controlColumnIds.reduce((acc, id) => {
const controlColumn = defaultControlColumns.find((col) => col.id === id);
if (controlColumn) {
acc.push(controlColumn);
}
return acc;
}, [] as EuiDataGridControlColumn[])
: defaultControlColumns;
const { leadColumns, leadColumnsExtraContent } = getLeadControlColumns({
rows: displayedRows,
canSetExpandedDoc,
});
const leadingColumns: EuiDataGridControlColumn[] = externalControlColumns
? [...internalControlColumns, ...externalControlColumns]
: internalControlColumns;
const filteredLeadColumns = leadColumns.filter((column) =>
controlColumnIds.includes(column.id)
);
if (getRowIndicator) {
const colorIndicatorControlColumn = getColorIndicatorControlColumn({
getRowIndicator,
});
leadingColumns.unshift(colorIndicatorControlColumn);
filteredLeadColumns.unshift(colorIndicatorControlColumn);
}
if (rowAdditionalLeadingControls?.length) {
leadingColumns.push(...getAdditionalRowControlColumns(rowAdditionalLeadingControls));
const actionsColumn = getActionsColumn({
baseColumns: leadColumnsExtraContent,
rowAdditionalLeadingControls,
externalControlColumns,
});
if (actionsColumn) {
filteredLeadColumns.push(actionsColumn);
}
return leadingColumns;
return filteredLeadColumns;
}, [
canSetExpandedDoc,
controlColumnIds,

View file

@ -12,9 +12,10 @@ import { i18n } from '@kbn/i18n';
import {
type EuiDataGridColumn,
type EuiDataGridColumnCellAction,
EuiScreenReaderOnly,
type EuiDataGridControlColumn,
EuiListGroupItemProps,
type EuiDataGridColumnSortingConfig,
RenderCellValue,
} from '@elastic/eui';
import type { DataView } from '@kbn/data-views-plugin/public';
import { getDataViewFieldOrCreateFromColumnMeta } from '@kbn/data-view-utils';
@ -67,21 +68,6 @@ const DataTableScoreColumnHeaderMemoized = React.memo(DataTableScoreColumnHeader
export const OPEN_DETAILS = 'openDetails';
export const SELECT_ROW = 'select';
const openDetails = {
id: OPEN_DETAILS,
width: DEFAULT_CONTROL_COLUMN_WIDTH,
headerCellRender: () => (
<EuiScreenReaderOnly>
<span>
{i18n.translate('unifiedDataTable.controlColumnHeader', {
defaultMessage: 'Control column',
})}
</span>
</EuiScreenReaderOnly>
),
rowCellRender: ExpandButton,
};
const getSelect = (rows: DataTableRecord[]) => ({
id: SELECT_ROW,
width: DEFAULT_CONTROL_COLUMN_WIDTH,
@ -95,11 +81,14 @@ export function getLeadControlColumns({
}: {
rows: DataTableRecord[];
canSetExpandedDoc: boolean;
}) {
if (!canSetExpandedDoc) {
return [getSelect(rows)];
}
return [openDetails, getSelect(rows)];
}): {
leadColumns: EuiDataGridControlColumn[];
leadColumnsExtraContent: RenderCellValue[];
} {
return {
leadColumns: [getSelect(rows)],
leadColumnsExtraContent: canSetExpandedDoc ? [ExpandButton] : [],
};
}
function buildEuiGridColumn({

View file

@ -197,10 +197,9 @@ export const createExampleDataSourceProfileProvider = (): DataSourceProfileProvi
return [
...additionalControls,
...['visBarVerticalStacked', 'heart', 'inspect'].map(
(iconType, index): RowControlColumn => ({
(iconType): RowControlColumn => ({
id: `exampleControl_${iconType}`,
headerAriaLabel: `Example Row Control ${iconType}`,
renderControl: (Control, rowProps) => {
render: (Control, rowProps) => {
return (
<Control
data-test-subj={`exampleLogsControl_${iconType}`}

View file

@ -29,7 +29,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});
await discover.waitUntilSearchingHasFinished();
await testSubjects.existOrFail('exampleLogsControl_visBarVerticalStacked');
await testSubjects.existOrFail('unifiedDataTable_additionalRowControl_menuControl');
await testSubjects.existOrFail('unifiedDataTable_additionalRowControl_actionsMenu');
});
it('should not render logs controls for non-logs data source', async () => {
@ -42,7 +42,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});
await discover.waitUntilSearchingHasFinished();
await testSubjects.missingOrFail('exampleLogsControl_visBarVerticalStacked');
await testSubjects.missingOrFail('unifiedDataTable_additionalRowControl_menuControl');
await testSubjects.missingOrFail('unifiedDataTable_additionalRowControl_actionsMenu');
});
});
@ -52,7 +52,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await dataViews.switchTo('my-example-logs');
await discover.waitUntilSearchingHasFinished();
await testSubjects.existOrFail('exampleLogsControl_visBarVerticalStacked');
await testSubjects.existOrFail('unifiedDataTable_additionalRowControl_menuControl');
await testSubjects.existOrFail('unifiedDataTable_additionalRowControl_actionsMenu');
// check Surrounding docs page
await dataGrid.clickRowToggle();
@ -63,7 +63,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await header.waitUntilLoadingHasFinished();
await testSubjects.existOrFail('exampleLogsControl_visBarVerticalStacked');
await testSubjects.existOrFail('unifiedDataTable_additionalRowControl_menuControl');
await testSubjects.existOrFail('unifiedDataTable_additionalRowControl_actionsMenu');
});
it('should not render logs controls for non-logs data source', async () => {
@ -71,7 +71,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await dataViews.switchTo('my-example-metrics');
await discover.waitUntilSearchingHasFinished();
await testSubjects.missingOrFail('exampleLogsControl_visBarVerticalStacked');
await testSubjects.missingOrFail('unifiedDataTable_additionalRowControl_menuControl');
await testSubjects.missingOrFail('unifiedDataTable_additionalRowControl_actionsMenu');
// check Surrounding docs page
await dataGrid.clickRowToggle();
@ -82,7 +82,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await header.waitUntilLoadingHasFinished();
await testSubjects.missingOrFail('exampleLogsControl_visBarVerticalStacked');
await testSubjects.missingOrFail('unifiedDataTable_additionalRowControl_menuControl');
await testSubjects.missingOrFail('unifiedDataTable_additionalRowControl_actionsMenu');
});
});
});

View file

@ -2683,7 +2683,6 @@
"discover.customControl.degradedDocPresent": "Ce document n'a pas pu être analysé correctement. Tous les champs n'ont pas été remplis correctement.",
"discover.customControl.stacktrace.available": "Traces d'appel disponibles",
"discover.customControl.stacktrace.notAvailable": "Traces d'appel indisponibles",
"discover.customControl.stacktraceArialLabel": "Accès aux traces d'appel disponibles",
"discover.discoverBreadcrumbTitle": "Discover",
"discover.discoverDefaultSearchSessionName": "Discover",
"discover.discoverDescription": "Explorez vos données de manière interactive en interrogeant et en filtrant des documents bruts.",
@ -8256,7 +8255,6 @@
"uiActionsEnhanced.drilldowns.urlDrilldownCollectConfig.urlTemplateVariablesHelpLinkText": "Aide",
"uiActionsEnhanced.drilldowns.urlDrilldownValidation.urlCompileErrorMessage": "Le modèle d'URL n'est pas valide dans le contexte donné. {message}.",
"uiActionsEnhanced.drilldowns.urlDrilldownValidation.urlFormatGeneralErrorMessage": "Format dURL non valide.",
"unifiedDataTable.additionalActionsColumnHeader": "Colonne d'actions supplémentaires",
"unifiedDataTable.advancedDiffModesTooltip": "Les modes avancés offrent des capacités de diffraction améliorées, mais ils fonctionnent sur des documents bruts et ne prennent donc pas en charge le formatage des champs.",
"unifiedDataTable.clearSelection": "Effacer la sélection",
"unifiedDataTable.compareSelectedRowsButtonDisabledTooltip": "La comparaison est limitée à {limit} lignes",
@ -8270,7 +8268,6 @@
"unifiedDataTable.comparisonColumnTooltip": "Document de comparaison : {documentId}",
"unifiedDataTable.comparisonMaxFieldsCallout": "La comparaison est limitée aux {comparisonFields} de {totalFields} champs.",
"unifiedDataTable.comparisonSettings": "Paramètres de comparaison",
"unifiedDataTable.controlColumnHeader": "Colonne de commande",
"unifiedDataTable.copyColumnNameToClipboard.toastTitle": "Copié dans le presse-papiers",
"unifiedDataTable.copyColumnValuesToClipboard.toastTitle": "Valeurs de la colonne \"{column}\" copiées dans le presse-papiers",
"unifiedDataTable.copyDocsToClipboardJSON": "Copier des documents au format JSON",

View file

@ -2682,7 +2682,6 @@
"discover.customControl.degradedDocPresent": "このドキュメントを正しく解析できませんでした。一部のフィールドが正しく入力されていません。",
"discover.customControl.stacktrace.available": "スタックトレースがあります",
"discover.customControl.stacktrace.notAvailable": "スタックトレースがありません",
"discover.customControl.stacktraceArialLabel": "使用可能なスタックトレースにアクセス",
"discover.discoverBreadcrumbTitle": "Discover",
"discover.discoverDefaultSearchSessionName": "Discover",
"discover.discoverDescription": "ドキュメントにクエリーをかけたりフィルターを適用することで、データをインタラクティブに閲覧できます。",
@ -8247,7 +8246,6 @@
"uiActionsEnhanced.drilldowns.urlDrilldownCollectConfig.urlTemplateVariablesHelpLinkText": "ヘルプ",
"uiActionsEnhanced.drilldowns.urlDrilldownValidation.urlCompileErrorMessage": "このURLテンプレートは指定されたコンテキストで無効です。{message}。",
"uiActionsEnhanced.drilldowns.urlDrilldownValidation.urlFormatGeneralErrorMessage": "無効なURL形式です。",
"unifiedDataTable.additionalActionsColumnHeader": "追加のアクション列",
"unifiedDataTable.advancedDiffModesTooltip": "高度なモードでは、拡張差異機能を利用できますが、未加工ドキュメントで動作するため、フィールド書式設定はサポートされません。",
"unifiedDataTable.clearSelection": "選択した項目をクリア",
"unifiedDataTable.compareSelectedRowsButtonDisabledTooltip": "比較は{limit}行に制限されます",
@ -8261,7 +8259,6 @@
"unifiedDataTable.comparisonColumnTooltip": "比較ドキュメント:{documentId}",
"unifiedDataTable.comparisonMaxFieldsCallout": "比較は、{totalFields}フィールド中{comparisonFields}フィールドに制限されます。",
"unifiedDataTable.comparisonSettings": "比較設定",
"unifiedDataTable.controlColumnHeader": "列の制御",
"unifiedDataTable.copyColumnNameToClipboard.toastTitle": "クリップボードにコピーされました",
"unifiedDataTable.copyColumnValuesToClipboard.toastTitle": "\"{column}\"列の値がクリップボードにコピーされました",
"unifiedDataTable.copyDocsToClipboardJSON": "ドキュメントをJSONとしてコピー",

View file

@ -2683,7 +2683,6 @@
"discover.customControl.degradedDocPresent": "无法正确解析此文档。并非所有字段都进行了正确填充。",
"discover.customControl.stacktrace.available": "堆栈跟踪可用",
"discover.customControl.stacktrace.notAvailable": "堆栈跟踪不可用",
"discover.customControl.stacktraceArialLabel": "访问可用堆栈跟踪",
"discover.discoverBreadcrumbTitle": "Discover",
"discover.discoverDefaultSearchSessionName": "发现",
"discover.discoverDescription": "通过查询和筛选原始文档来以交互方式浏览您的数据。",
@ -8262,7 +8261,6 @@
"uiActionsEnhanced.drilldowns.urlDrilldownCollectConfig.urlTemplateVariablesHelpLinkText": "帮助",
"uiActionsEnhanced.drilldowns.urlDrilldownValidation.urlCompileErrorMessage": "URL 模板在给定上下文中无效。{message}。",
"uiActionsEnhanced.drilldowns.urlDrilldownValidation.urlFormatGeneralErrorMessage": "URL 格式无效。",
"unifiedDataTable.additionalActionsColumnHeader": "其他操作列",
"unifiedDataTable.advancedDiffModesTooltip": "高级模式提供了增强型差异功能,但在原始文档上运行,因此不支持字段格式化。",
"unifiedDataTable.clearSelection": "清除所选内容",
"unifiedDataTable.compareSelectedRowsButtonDisabledTooltip": "对比限定为 {limit} 行",
@ -8276,7 +8274,6 @@
"unifiedDataTable.comparisonColumnTooltip": "比较文档:{documentId}",
"unifiedDataTable.comparisonMaxFieldsCallout": "比较仅限于 {comparisonFields} 个(共 {totalFields} 个)字段。",
"unifiedDataTable.comparisonSettings": "对比设置",
"unifiedDataTable.controlColumnHeader": "控制列",
"unifiedDataTable.copyColumnNameToClipboard.toastTitle": "已复制到剪贴板",
"unifiedDataTable.copyColumnValuesToClipboard.toastTitle": "“{column}”列的值已复制到剪贴板",
"unifiedDataTable.copyDocsToClipboardJSON": "将文档复制为 JSON",

View file

@ -559,7 +559,7 @@ describe('query tab with unified timeline', () => {
const messageColumnIndex =
customColumnOrder.findIndex((header) => header.id === 'message') +
// offset for additional leading columns on left
4;
3;
expect(container.querySelector('[data-gridcell-column-id="message"]')).toHaveAttribute(
'data-gridcell-column-index',

View file

@ -104,7 +104,7 @@ export const StyledTimelineUnifiedDataTable = styled.div.attrs(({ className = ''
/* auto row height */
.euiDataGridRowCell__content--autoHeight {
margin-top: 6px;
margin-top: 3px;
}
/* single row height */

View file

@ -30,7 +30,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});
await PageObjects.discover.waitUntilSearchingHasFinished();
await testSubjects.existOrFail('exampleLogsControl_visBarVerticalStacked');
await testSubjects.existOrFail('unifiedDataTable_additionalRowControl_menuControl');
await testSubjects.existOrFail('unifiedDataTable_additionalRowControl_actionsMenu');
});
it('should not render logs controls for non-logs data source', async () => {
@ -43,7 +43,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});
await PageObjects.discover.waitUntilSearchingHasFinished();
await testSubjects.missingOrFail('exampleLogsControl_visBarVerticalStacked');
await testSubjects.missingOrFail('unifiedDataTable_additionalRowControl_menuControl');
await testSubjects.missingOrFail('unifiedDataTable_additionalRowControl_actionsMenu');
});
});
@ -53,7 +53,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await dataViews.switchTo('my-example-logs');
await PageObjects.discover.waitUntilSearchingHasFinished();
await testSubjects.existOrFail('exampleLogsControl_visBarVerticalStacked');
await testSubjects.existOrFail('unifiedDataTable_additionalRowControl_menuControl');
await testSubjects.existOrFail('unifiedDataTable_additionalRowControl_actionsMenu');
// check Surrounding docs page
await dataGrid.clickRowToggle();
@ -64,7 +64,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.header.waitUntilLoadingHasFinished();
await testSubjects.existOrFail('exampleLogsControl_visBarVerticalStacked');
await testSubjects.existOrFail('unifiedDataTable_additionalRowControl_menuControl');
await testSubjects.existOrFail('unifiedDataTable_additionalRowControl_actionsMenu');
});
it('should not render logs controls for non-logs data source', async () => {
@ -72,7 +72,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await dataViews.switchTo('my-example-metrics');
await PageObjects.discover.waitUntilSearchingHasFinished();
await testSubjects.missingOrFail('exampleLogsControl_visBarVerticalStacked');
await testSubjects.missingOrFail('unifiedDataTable_additionalRowControl_menuControl');
await testSubjects.missingOrFail('unifiedDataTable_additionalRowControl_actionsMenu');
// check Surrounding docs page
await dataGrid.clickRowToggle();
@ -83,7 +83,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.header.waitUntilLoadingHasFinished();
await testSubjects.missingOrFail('exampleLogsControl_visBarVerticalStacked');
await testSubjects.missingOrFail('unifiedDataTable_additionalRowControl_menuControl');
await testSubjects.missingOrFail('unifiedDataTable_additionalRowControl_actionsMenu');
});
});
});