Extracted DiscoverGrid to a package named @kbn/unified-data-table as UnifiedDataTable component (#163211)

## Summary

Current PR includes the next set of changes:

1. Moved `DiscoverGrid` component to a package `@kbn/unified-data-table`
and added `@elastic/kibana-data-discovery` as code owners.
2. Changed `@kbn/unified-data-table` package naming for data grid
related components and methods to correspond `UnifiedDataTable` instead
of `Discover`.

3. Moved hooks `useColumns` and `useRowHeightsOptions` to a package as
its logic belongs to `UnifiedDataTable`.
4. Renamed `DiscoverGridContext` to `UnifiedDataTableContext`.
5. Extended `UnifiedDataTable` interface and functionality with the next
customization options:
- `renderDocumentView?: (displayedRows:
DataTableRecord[],displayedColumns: string[]) => JSX.Element |
undefined;` - callback to render DocumentView when the document is
expanded
- `configRowHeight?: number;` - optional value for providing
configuration setting for UnifiedDataTable rows height
- `showMultiFields?: boolean;` - optional value for providing
configuration setting for enabling to display the complex fields in the
table. Default is true.
- `maxDocFieldsDisplayed?: number;` - optional value for providing
configuration setting for maximum number of document fields to display
in the table. Default is 50.
- `externalControlColumns?: EuiDataGridControlColumn[];` - optional
value for providing EuiDataGridControlColumn list of the additional
leading control columns. UnifiedDataTable includes two control columns:
Open Details and Select.
<img width="522" alt="Screenshot 2023-08-22 at 2 26 57 PM"
src="d796b9c8-2fef-4bcc-a3c9-9f5cc6349ab9">

- `externalAdditionalControls?: React.ReactNode;` - optional value for
providing the additional controls available in the UnifiedDataTable
toolbar to manage it's records or state. UnifiedDataTable includes
Columns, Sorting and Bulk Actions.
<img width="673" alt="Screenshot 2023-08-22 at 2 40 28 PM"
src="f7ac0c87-5310-49dd-9084-1ce01ca0f366">

- `rowsPerPageOptions?: number[];` - optional list of number type values
to set custom UnifiedDataTable paging options to display the records per
page.
- `renderCustomGridBody?: (args: EuiDataGridCustomBodyProps) =>
React.ReactNode;` - An optional function called to completely customize
and control the rendering of EuiDataGrid's body and cell placement.
<img width="1658" alt="Screenshot 2023-08-22 at 2 50 27 PM"
src="14adc18d-73af-40f5-9859-b3c708e265b1">

- `trailingControlColumns?: EuiDataGridControlColumn[];` - optional list
of the `EuiDataGridControlColumn` type for setting trailing control
columns standard for `EuiDataGrid`.
- `visibleCellActions?: number;` - optional value for a custom number of
the visible cell actions in the table
<img width="497" alt="Screenshot 2023-08-22 at 2 45 49 PM"
src="57ef3ad9-7401-46bb-9b38-cc8cca2e6a24">

- `externalCustomRenderers?: Record<string,(props:
EuiDataGridCellValueElementProps) => React.ReactNode>;` - an optional
settings for a specified fields rendering like links. Applied only for
the listed fields rendering:
<img width="1121" alt="Screenshot 2023-08-22 at 2 51 07 PM"
src="77501eae-3046-4a2c-90e1-2db487c21e2c">

- `consumer` - optional string value for the name of the
`UnifiedDataTable` consumer component or application.
6. Extended `UnifiedDataGrid` services with the two additional: 
    `storage: Storage;`
    `data: DataPublicPluginStart; `
replaced `core: CoreStart;` with `theme: ThemeServiceStart;`, because
`core` is used only to get `theme`
7. Replaced `DocumentView` property with `renderDocumentView?:
(displayedRows: DataTableRecord[],displayedColumns: string[]) =>
JSX.Element | undefined;` callback function, which allows not to use
`DiscoverGridFlyout` definition for the documents rendering.
```
    /**
   * Document detail view component
   */
  DocumentView?: typeof DiscoverGridFlyout;
```
8. Removed the next properties from the data table interface, because it
was used to render DiscoverGridFlyout:
```
   /**
   * Filters applied by saved search embeddable
   */
  filters?: Filter[];
  /**
   * Query applied by KQL bar or text based editor
   */
  query?: Query | AggregateQuery;
  /**
   * Saved search id used for links to single doc and surrounding docs in the flyout
   */
  savedSearchId?: string;
```
9. Added usage examples and interface description to README file.
10. Changed grid styles naming from `.dscDiscoverGrid*` to
`.udtDataTable*`
11. Migrated discover plugin to use `UnifiedDataTable` instead of
`DiscoverGrid`

Extra changes were needed to avoid the circular dependancies:
- moved `DocViewFilterFn` and `FieldMapping` from discover plugin to
`packages/kbn-discover-utils/src/types.ts`
- added own `export type SortOrder = [string, string];` to avoid deps
for saved-search plugin

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Yuliia Naumenko 2023-09-01 23:02:53 -07:00 committed by GitHub
parent b838cd638a
commit 8fb5a651a0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
97 changed files with 2957 additions and 1226 deletions

1
.github/CODEOWNERS vendored
View file

@ -751,6 +751,7 @@ test/plugin_functional/plugins/ui_settings_plugin @elastic/kibana-core
packages/kbn-ui-shared-deps-npm @elastic/kibana-operations
packages/kbn-ui-shared-deps-src @elastic/kibana-operations
packages/kbn-ui-theme @elastic/kibana-operations
packages/kbn-unified-data-table @elastic/kibana-data-discovery
packages/kbn-unified-doc-viewer @elastic/kibana-data-discovery
examples/unified_doc_viewer @elastic/kibana-core
src/plugins/unified_doc_viewer @elastic/kibana-data-discovery

View file

@ -125,7 +125,8 @@
"unifiedDocViewer": ["src/plugins/unified_doc_viewer", "packages/kbn-unified-doc-viewer"],
"unifiedSearch": "src/plugins/unified_search",
"unifiedFieldList": "packages/kbn-unified-field-list",
"unifiedHistogram": "src/plugins/unified_histogram"
"unifiedHistogram": "src/plugins/unified_histogram",
"unifiedDataTable": "packages/kbn-unified-data-table"
},
"translations": []
}

View file

@ -743,6 +743,7 @@
"@kbn/ui-shared-deps-npm": "link:packages/kbn-ui-shared-deps-npm",
"@kbn/ui-shared-deps-src": "link:packages/kbn-ui-shared-deps-src",
"@kbn/ui-theme": "link:packages/kbn-ui-theme",
"@kbn/unified-data-table": "link:packages/kbn-unified-data-table",
"@kbn/unified-doc-viewer": "link:packages/kbn-unified-doc-viewer",
"@kbn/unified-doc-viewer-examples": "link:examples/unified_doc_viewer",
"@kbn/unified-doc-viewer-plugin": "link:src/plugins/unified_doc_viewer",

View file

@ -0,0 +1,241 @@
# @kbn/unified-data-table
This package contains components and services for the unified data table UI (as used in Discover).
## UnifiedDataTable Component
Props description:
| Property | Type | Description |
| :--- | :--- | :--- |
| **ariaLabelledBy** | string | Determines which element labels the grid for ARIA. |
| **className** | (optional) string | Optional class name to apply. |
| **columns** | string[] | Determines ids of the columns which are displayed. |
| **expandedDoc** | (optional) DataTableRecord | If set, the given document is displayed in a flyout. |
| **dataView** | DataView | The used data view. |
| **loadingState** | DataLoadingState | Determines if data is currently loaded. |
| **onFilter** | DocViewFilterFn | Function to add a filter in the grid cell or document flyout. |
| **onResize** | (optional)(colSettings: { columnId: string; width: number }) => void; | Function triggered when a column is resized by the user. |
| **onSetColumns** | (columns: string[], hideTimeColumn: boolean) => void; | Function to set all columns. |
| **onSort** | (optional)(sort: string[][]) => void; | Function to change sorting of the documents, skipped when isSortEnabled is set to false. |
| **rows** | (optional)DataTableRecord[] | Array of documents provided by Elasticsearch. |
| **sampleSize** | number | The max size of the documents returned by Elasticsearch. |
| **setExpandedDoc** | (optional)(doc?: DataTableRecord) => void; | Function to set the expanded document, which is displayed in a flyout. |
| **settings** | (optional)UnifiedDataTableSettings | Grid display settings persisted in Elasticsearch (e.g. column width). |
| **searchDescription** | (optional)string | Search description. |
| **searchTitle** | (optional)string | Search title. |
| **showTimeCol** | boolean | Determines whether the time columns should be displayed (legacy settings). |
| **showFullScreenButton** | (optional)boolean | Determines whether the full screen button should be displayed. |
| **isSortEnabled** | (optional)boolean | Manage user sorting control. |
| **sort** | SortOrder[] | Current sort setting. |
| **useNewFieldsApi** | boolean | How the data is fetched. |
| **isPaginationEnabled** | (optional)boolean | Manage pagination control. |
| **controlColumnIds** | (optional)string[] | List of used control columns (available: 'openDetails', 'select'). |
| **rowHeightState** | (optional)number | Row height from state. |
| **onUpdateRowHeight** | (optional)(rowHeight: number) => void; | Update row height state. |
| **isPlainRecord** | (optional)boolean | Is text base lang mode enabled. |
| **rowsPerPageState** | (optional)number | Current state value for rowsPerPage. |
| **onUpdateRowsPerPage** | (optional)(rowsPerPage: number) => void; | Update rows per page state. |
| **onFieldEdited** | (optional)() => void; | Callback to execute on edit runtime field. |
| **cellActionsTriggerId** | (optional)string | Optional triggerId to retrieve the column cell actions that will override the default ones. |
| **services** | See Required **services** list below | Service dependencies. |
| **renderDocumentView** | (optional)(hit: DataTableRecord,displayedRows: DataTableRecord[],displayedColumns: string[]) => JSX.Element | undefined; | Callback to render DocumentView when the document is expanded. |
| **configRowHeight** | (optional)number | Optional value for providing configuration setting for UnifiedDataTable rows height. |
| **showMultiFields** | (optional)boolean | Optional value for providing configuration setting for enabling to display the complex fields in the table. Default is true. |
| **maxDocFieldsDisplayed** | (optional)number | Optional value for providing configuration setting for maximum number of document fields to display in the table. Default is 50. |
| **externalControlColumns** | (optional)EuiDataGridControlColumn[] | Optional value for providing EuiDataGridControlColumn list of the additional leading control columns. UnifiedDataTable includes two control columns: Open Details and Select. |
| **totalHits** | (optional)number | Number total hits from ES. |
| **onFetchMoreRecords** | (optional)() => void | To fetch more. |
| **externalAdditionalControls** | (optional)React.ReactNode | Optional value for providing the additional controls available in the UnifiedDataTable toolbar to manage it's records or state. UnifiedDataTable includes Columns, Sorting and Bulk Actions. |
| **rowsPerPageOptions** | (optional)number[] | Optional list of number type values to set custom UnifiedDataTable paging options to display the records per page. |
| **renderCustomGridBody** | (optional)(args: EuiDataGridCustomBodyProps) => React.ReactNode; | An optional function called to completely customize and control the rendering of EuiDataGrid's body and cell placement. |
| **trailingControlColumns** | (optional)EuiDataGridControlColumn[] | An optional list of the EuiDataGridControlColumn type for setting trailing control columns standard for EuiDataGrid. |
| **visibleCellActions** | (optional)number | An optional value for a custom number of the visible cell actions in the table. By default is up to 3. |
| **externalCustomRenderers** | (optional)Record<string,(props: EuiDataGridCellValueElementProps) => React.ReactNode>; | An optional settings for a specified fields rendering like links. Applied only for the listed fields rendering. |
| **consumer** | (optional)string | Name of the UnifiedDataTable consumer component or application. |
| **componentsTourSteps** | (optional)Record<string,string> | Optional key/value pairs to set guided onboarding steps ids for a data table components included to guided tour. |
*Required **services** list:
```
theme: ThemeServiceStart;
fieldFormats: FieldFormatsStart;
uiSettings: IUiSettingsClient;
dataViewFieldEditor: DataViewFieldEditorStart;
toastNotifications: ToastsStart;
storage: Storage;
data: DataPublicPluginStart;
```
Usage example:
```
// Memoize unified data table to avoid the unnecessary re-renderings
const DataTableMemoized = React.memo(UnifiedDataTable);
// Add memoized component with all needed props
<DataTableMemoized
ariaLabelledBy="timelineDocumentsAriaLabel"
className={'unifiedDataTableTimeline'}
columns={['event.category', 'event.action', 'host.name', 'user.name']}
expandedDoc={expandedDoc as DataTableRecord}
dataView={dataView}
loadingState={isQueryLoading ? DataLoadingState.loading : DataLoadingState.loaded}
onFilter={() => {
// Add logic to refetch the data when the filter by field was added/removed. Refetch data.
}}
onResize={(colSettings: { columnId: string; width: number }) => {
// Update the table state with the new width for the column
}}
onSetColumns={(columns: string[], hideTimeColumn: boolean) => {
// Update table state with the new columns. Refetch data.
}}
onSort={!isTextBasedQuery ? onSort : undefined
// Update table state with the new sorting settings. Refetch data.
}
rows={searchResultRows}
sampleSize={500}
setExpandedDoc={() => {
// Callback function to do the logic when the document is expanded
}}
settings={tableSettings}
showTimeCol={true}
isSortEnabled={true}
sort={sortingColumns}
rowHeightState={3}
onUpdateRowHeight={(rowHeight: number) => {
// Do the state update with the new setting of the row height
}}
isPlainRecord={isTextBasedQuery}
rowsPerPageState={50}
onUpdateRowsPerPage={(rowHeight: number) => {
// Do the state update with the new number of the rows per page
}
onFieldEdited={() =>
// Callback to execute on edit runtime field. Refetch data.
}
cellActionsTriggerId={SecurityCellActionsTrigger.DEFAULT}
services={{
theme,
fieldFormats,
storage,
toastNotifications: toastsService,
uiSettings,
dataViewFieldEditor,
data: dataPluginContract,
}}
visibleCellActions={3}
externalCustomRenderers={{
// Set the record style definition for the specific fields rendering Record<string,(props: EuiDataGridCellValueElementProps) => React.ReactNode>
}}
renderDocumentView={() =>
// Implement similar callback to render the Document flyout
const renderDetailsPanel = useCallback(
() => (
<DetailsPanel
browserFields={browserFields}
handleOnPanelClosed={handleOnPanelClosed}
runtimeMappings={runtimeMappings}
tabType={TimelineTabs.query}
scopeId={timelineId}
isFlyoutView
/>
),
[browserFields, handleOnPanelClosed, runtimeMappings, timelineId]
);
}
externalControlColumns={leadingControlColumns}
externalAdditionalControls={additionalControls}
trailingControlColumns={trailingControlColumns}
renderCustomGridBody={renderCustomGridBody}
rowsPerPageOptions={[10, 30, 40, 100]}
showFullScreenButton={false}
useNewFieldsApi={true}
maxDocFieldsDisplayed={50}
consumer="timeline"
totalHits={
// total number of the documents in the search query result. For example: 1200
}
onFetchMoreRecords={() => {
// Do some data fetch to get more data
}}
configRowHeight={3}
showMultiFields={true}
componentsTourSteps={'expandButton': DISCOVER_TOUR_STEP_ANCHOR_IDS.expandDocument}
/>
```
## JsonCodeEditorCommon Component
Props description:
| Property | Type | Description |
| :--- | :--- | :--- |
| **width** | (optional) string or number | Editor component width. |
| **height** | (optional) string or number | Editor component height. |
| **hasLineNumbers** | (optional) boolean | Define if the editor component has line numbers style. |
| **hideCopyButton** | (optional) boolean | Show/hide setting for Copy button. |
| **onEditorDidMount** | (editor: monaco.editor.IStandaloneCodeEditor) => void | Do some logic to update the state with the edotor component value. |
Usage example:
```
<JsonCodeEditorCommon
jsonValue={jsonValue}
width={100}
height={400}
hasLineNumbers={true}
onEditorDidMount={(editorNode: monaco.editor.IStandaloneCodeEditor) => setEditor(editorNode)}
/>
```
## Utils
* `getRowsPerPageOptions(currentRowsPerPage)` - gets list of the table defaults for perPage options.
* `getDisplayedColumns(currentRowsPerPage)` - gets list of the table columns with the logic to define the empty list with _source column.
* `popularizeField(...)` - helper function to define the dataView persistance and save indexPattern update capabilities.
## Hooks
* `useColumns(...)` - this hook define the state update for the columns event handlers and allows to use them for external components outside the UnifiedDataTable.
An example of using hooks for defining event handlers for columns management with setting the consumer specific setAppState:
```
const {
columns: currentColumns,
onAddColumn,
onRemoveColumn,
onMoveColumn,
onSetColumns,
} = useColumns({
capabilities,
defaultOrder: uiSettings.get(SORT_DEFAULT_ORDER_SETTING),
dataView,
dataViews,
setAppState: stateContainer.appState.update,
useNewFieldsApi,
columns,
sort,
});
// Use onAddColumn, onRemoveColumn handlers in the DocumentView
const renderDocumentView = useCallback(
(hit: DataTableRecord, displayedRows: DataTableRecord[], displayedColumns: string[]) => (
<DiscoverGridFlyout
dataView={dataView}
hit={hit}
hits={displayedRows}
// if default columns are used, dont make them part of the URL - the context state handling will take care to restore them
columns={displayedColumns}
savedSearchId={savedSearch.id}
onFilter={onAddFilter}
onRemoveColumn={onRemoveColumn}
onAddColumn={onAddColumn}
onClose={() => setExpandedDoc(undefined)}
setExpandedDoc={setExpandedDoc}
query={query}
/>
),
[dataView, onAddColumn, onAddFilter, onRemoveColumn, query, savedSearch.id, setExpandedDoc]
);
```

View file

@ -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 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 or the Server
* Side Public License, v 1.
*/
import type { Config } from '@kbn/config';
export type ConfigMock = jest.Mocked<Config>;
const createConfigMock = (): ConfigMock => ({
has: jest.fn(),
get: jest.fn(),
set: jest.fn(),
getFlattenedPaths: jest.fn(),
toRaw: jest.fn(),
});
export const configMock = {
create: createConfigMock,
};

View file

@ -0,0 +1,419 @@
/*
* 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 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 or the Server
* Side Public License, v 1.
*/
import { DataView } from '@kbn/data-views-plugin/public';
import { buildDataViewMock } from '@kbn/discover-utils/src/__mocks__';
const fields = [
{
count: 0,
name: '_id',
type: 'string',
esTypes: ['_id'],
scripted: false,
searchable: true,
aggregatable: false,
readFromDocValues: false,
},
{
count: 0,
name: '_index',
type: 'string',
esTypes: ['_index'],
scripted: false,
searchable: true,
aggregatable: true,
readFromDocValues: false,
},
{
count: 0,
name: '_score',
type: 'number',
scripted: false,
searchable: false,
aggregatable: false,
readFromDocValues: false,
},
{
count: 0,
name: '_source',
type: '_source',
esTypes: ['_source'],
scripted: false,
searchable: false,
aggregatable: false,
readFromDocValues: false,
},
{
count: 0,
name: '_type',
type: 'string',
scripted: false,
searchable: false,
aggregatable: false,
readFromDocValues: false,
},
{
count: 2,
name: 'array_objects.description',
type: 'string',
esTypes: ['text'],
scripted: false,
searchable: true,
aggregatable: false,
readFromDocValues: false,
},
{
count: 0,
name: 'array_objects.description.keyword',
type: 'string',
esTypes: ['keyword'],
scripted: false,
searchable: true,
aggregatable: true,
readFromDocValues: true,
subType: {
multi: {
parent: 'array_objects.description',
},
},
},
{
count: 0,
name: 'array_objects.name',
type: 'string',
esTypes: ['text'],
scripted: false,
searchable: true,
aggregatable: false,
readFromDocValues: false,
},
{
count: 0,
name: 'array_objects.name.keyword',
type: 'string',
esTypes: ['keyword'],
scripted: false,
searchable: true,
aggregatable: true,
readFromDocValues: true,
subType: {
multi: {
parent: 'array_objects.name',
},
},
},
{
count: 0,
name: 'array_tags',
type: 'string',
esTypes: ['text'],
scripted: false,
searchable: true,
aggregatable: false,
readFromDocValues: false,
},
{
count: 0,
name: 'array_tags.keyword',
type: 'string',
esTypes: ['keyword'],
scripted: false,
searchable: true,
aggregatable: true,
readFromDocValues: true,
subType: {
multi: {
parent: 'array_tags',
},
},
},
{
count: 0,
name: 'binary_blob',
type: 'unknown',
esTypes: ['binary'],
scripted: false,
searchable: false,
aggregatable: false,
readFromDocValues: false,
},
{
count: 0,
name: 'bool_enabled',
type: 'boolean',
esTypes: ['boolean'],
scripted: false,
searchable: true,
aggregatable: true,
readFromDocValues: true,
},
{
count: 0,
name: 'date',
type: 'date',
esTypes: ['date'],
scripted: false,
searchable: true,
aggregatable: true,
readFromDocValues: true,
},
{
count: 1,
name: 'date_nanos',
type: 'date',
esTypes: ['date_nanos'],
scripted: false,
searchable: true,
aggregatable: true,
readFromDocValues: true,
},
{
count: 0,
name: 'flattened_labels',
type: 'unknown',
esTypes: ['flattened'],
scripted: false,
searchable: true,
aggregatable: true,
readFromDocValues: true,
},
{
count: 0,
name: 'geo_point',
type: 'geo_point',
esTypes: ['geo_point'],
scripted: false,
searchable: true,
aggregatable: true,
readFromDocValues: true,
},
{
count: 1,
name: 'geometry',
type: 'unknown',
esTypes: ['shape'],
scripted: false,
searchable: true,
aggregatable: false,
readFromDocValues: false,
},
{
count: 1,
name: 'histogram',
type: 'histogram',
esTypes: ['histogram'],
scripted: false,
searchable: false,
aggregatable: true,
readFromDocValues: true,
},
{
count: 0,
name: 'ip_addr',
type: 'ip',
esTypes: ['ip'],
scripted: false,
searchable: true,
aggregatable: true,
readFromDocValues: true,
},
{
count: 4,
name: 'keyword_key',
type: 'string',
esTypes: ['keyword'],
scripted: false,
searchable: true,
aggregatable: true,
readFromDocValues: true,
},
{
count: 0,
name: 'nested_user.first',
type: 'string',
esTypes: ['text'],
scripted: false,
searchable: true,
aggregatable: false,
readFromDocValues: false,
subType: {
nested: {
path: 'nested_user',
},
},
},
{
count: 0,
name: 'nested_user.first.keyword',
type: 'string',
esTypes: ['keyword'],
scripted: false,
searchable: true,
aggregatable: true,
readFromDocValues: true,
subType: {
multi: {
parent: 'nested_user.first',
},
nested: {
path: 'nested_user',
},
},
},
{
count: 0,
name: 'nested_user.last',
type: 'string',
esTypes: ['text'],
scripted: false,
searchable: true,
aggregatable: false,
readFromDocValues: false,
subType: {
nested: {
path: 'nested_user',
},
},
},
{
count: 0,
name: 'nested_user.last.keyword',
type: 'string',
esTypes: ['keyword'],
scripted: false,
searchable: true,
aggregatable: true,
readFromDocValues: true,
subType: {
multi: {
parent: 'nested_user.last',
},
nested: {
path: 'nested_user',
},
},
},
{
count: 3,
name: 'number_amount',
type: 'number',
esTypes: ['long'],
scripted: false,
searchable: true,
aggregatable: true,
readFromDocValues: true,
},
{
count: 3,
name: 'number_price',
type: 'number',
esTypes: ['float'],
scripted: false,
searchable: true,
aggregatable: true,
readFromDocValues: true,
},
{
count: 0,
name: 'object_user.first',
type: 'string',
esTypes: ['text'],
scripted: false,
searchable: true,
aggregatable: false,
readFromDocValues: false,
},
{
count: 0,
name: 'object_user.last',
type: 'string',
esTypes: ['text'],
scripted: false,
searchable: true,
aggregatable: false,
readFromDocValues: false,
},
{
count: 0,
name: 'range_time_frame',
type: 'date_range',
esTypes: ['date_range'],
scripted: false,
searchable: true,
aggregatable: true,
readFromDocValues: true,
},
{
count: 1,
name: 'rank_features',
type: 'unknown',
esTypes: ['rank_features'],
scripted: false,
searchable: false,
aggregatable: false,
readFromDocValues: false,
},
{
count: 5,
name: 'text_message',
type: 'string',
esTypes: ['text'],
scripted: false,
searchable: true,
aggregatable: false,
readFromDocValues: false,
},
{
count: 0,
name: 'vector',
type: 'unknown',
esTypes: ['dense_vector'],
scripted: false,
searchable: false,
aggregatable: false,
readFromDocValues: false,
},
{
count: 0,
name: 'version',
type: 'string',
esTypes: ['version'],
scripted: false,
searchable: true,
aggregatable: true,
readFromDocValues: true,
},
{
count: 1,
script: 'return "hi there"',
lang: 'painless',
name: 'scripted_string',
type: 'string',
scripted: true,
searchable: true,
aggregatable: true,
readFromDocValues: false,
},
{
count: 0,
name: 'runtime_number',
type: 'number',
esTypes: ['double'],
scripted: false,
searchable: true,
aggregatable: true,
readFromDocValues: false,
},
] as DataView['fields'];
export const dataViewComplexMock = buildDataViewMock({
name: 'data-view-with-various-field-types',
fields,
timeFieldName: 'data',
});

View file

@ -0,0 +1,64 @@
/*
* 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 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 or the Server
* Side Public License, v 1.
*/
import { DataView } from '@kbn/data-views-plugin/public';
import { buildDataViewMock } from '@kbn/discover-utils/src/__mocks__';
const fields = [
{
name: '_index',
type: 'string',
scripted: false,
filterable: true,
},
{
name: 'timestamp',
displayName: 'timestamp',
type: 'date',
scripted: false,
filterable: true,
aggregatable: true,
sortable: true,
},
{
name: 'message',
displayName: 'message',
type: 'string',
scripted: false,
filterable: false,
},
{
name: 'extension',
displayName: 'extension',
type: 'string',
scripted: false,
filterable: true,
aggregatable: true,
},
{
name: 'bytes',
displayName: 'bytes',
type: 'number',
scripted: false,
filterable: true,
aggregatable: true,
},
{
name: 'scripted',
displayName: 'scripted',
type: 'number',
scripted: true,
filterable: false,
},
] as DataView['fields'];
export const dataViewWithTimefieldMock = buildDataViewMock({
name: 'index-pattern-with-timefield',
fields,
timeFieldName: 'timestamp',
});

View file

@ -0,0 +1,45 @@
/*
* 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 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 or the Server
* Side Public License, v 1.
*/
import { DataViewsContract } from '@kbn/data-views-plugin/public';
import { dataViewMock } from '@kbn/discover-utils/src/__mocks__';
import { dataViewComplexMock } from './data_view_complex';
import { dataViewWithTimefieldMock } from './data_view_with_timefield';
export const dataViewMockList = [dataViewMock, dataViewComplexMock, dataViewWithTimefieldMock];
export function createDataViewsMock() {
return {
getCache: async () => {
return [dataViewMock];
},
get: async (id: string) => {
if (id === 'invalid-data-view-id') {
return Promise.reject('Invalid');
}
const dataView = dataViewMockList.find((dv) => dv.id === id);
if (dataView) {
return Promise.resolve(dataView);
} else {
return Promise.reject(`DataView ${id} not found`);
}
},
getDefaultDataView: jest.fn(() => dataViewMock),
updateSavedObject: jest.fn(),
getIdsWithTitle: jest.fn(() => {
return Promise.resolve(dataViewMockList);
}),
createFilter: jest.fn(),
create: jest.fn(),
clearInstanceCache: jest.fn(),
getFieldsForIndexPattern: jest.fn((dataView) => dataView.fields),
refreshFields: jest.fn(),
} as unknown as jest.Mocked<DataViewsContract>;
}
export const dataViewsMock = createDataViewsMock();

View file

@ -0,0 +1,118 @@
/*
* 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 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 or the Server
* Side Public License, v 1.
*/
import React, { useState } from 'react';
import {
EuiCheckbox,
EuiButtonIcon,
EuiPopover,
EuiFlexGroup,
EuiFlexItem,
EuiPopoverTitle,
EuiSpacer,
EuiDataGridControlColumn,
} from '@elastic/eui';
const SelectionHeaderCell = () => {
return (
<div data-test-subj="test-header-control-column-cell">
<EuiCheckbox id="selection-toggle" aria-label="Select all rows" onChange={() => null} />
</div>
);
};
const SimpleHeaderCell = () => {
return (
<div
style={{
fontSize: '12px',
fontWeight: 600,
lineHeight: 1.5,
minWidth: 0,
padding: '4px',
width: '100%',
display: 'flex',
alignItems: 'center',
}}
data-test-subj="test-header-action-cell"
>
{'Additional Actions'}
</div>
);
};
const SelectionRowCell = ({ rowIndex }: { rowIndex: number }) => {
return (
<div data-test-subj="test-body-control-column-cell">
<EuiCheckbox
id={`${rowIndex}`}
aria-label={`Select row test`}
checked={false}
onChange={() => null}
/>
</div>
);
};
const TestTrailingColumn = () => {
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
return (
<EuiPopover
isOpen={isPopoverOpen}
anchorPosition="upCenter"
panelPaddingSize="s"
button={
<EuiButtonIcon
aria-label="show actions"
iconType="boxesHorizontal"
color="text"
onClick={() => setIsPopoverOpen(!isPopoverOpen)}
/>
}
data-test-subj="test-trailing-column-popover-button"
closePopover={() => setIsPopoverOpen(false)}
>
<EuiPopoverTitle>{'Actions'}</EuiPopoverTitle>
<div style={{ width: 150 }}>
<button type="button" onClick={() => {}}>
<EuiFlexGroup alignItems="center" component="span" gutterSize="s">
<EuiFlexItem grow={false}>
<EuiButtonIcon aria-label="Pin selected items" iconType="pin" color="text" />
</EuiFlexItem>
<EuiFlexItem>{'Pin'}</EuiFlexItem>
</EuiFlexGroup>
</button>
<EuiSpacer size="s" />
<button type="button" onClick={() => {}}>
<EuiFlexGroup alignItems="center" component="span" gutterSize="s">
<EuiFlexItem grow={false}>
<EuiButtonIcon aria-label="Delete selected items" iconType="trash" color="text" />
</EuiFlexItem>
<EuiFlexItem>{'Delete'}</EuiFlexItem>
</EuiFlexGroup>
</button>
</div>
</EuiPopover>
);
};
export const testTrailingControlColumns = [
{
id: 'actions',
width: 96,
headerCellRender: SimpleHeaderCell,
rowCellRender: TestTrailingColumn,
},
];
export const testLeadingControlColumn: EuiDataGridControlColumn = {
id: 'test-leading-control',
headerCellRender: SelectionHeaderCell,
rowCellRender: SelectionRowCell,
width: 100,
};

View file

@ -0,0 +1,26 @@
/*
* 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 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 or the Server
* Side Public License, v 1.
*/
export class LocalStorageMock {
private store: Record<string, unknown>;
constructor(defaultStore: Record<string, unknown>) {
this.store = defaultStore;
}
clear() {
this.store = {};
}
get(key: string) {
return this.store[key] || null;
}
set(key: string, value: unknown) {
this.store[key] = String(value);
}
remove(key: string) {
delete this.store[key];
}
}

View file

@ -0,0 +1,116 @@
/*
* 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 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 or the Server
* Side Public License, v 1.
*/
import { of } from 'rxjs';
import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks';
import { expressionsPluginMock } from '@kbn/expressions-plugin/public/mocks';
import { chromeServiceMock, coreMock } from '@kbn/core/public/mocks';
import { chartPluginMock } from '@kbn/charts-plugin/public/mocks';
import { fieldFormatsMock } from '@kbn/field-formats-plugin/common/mocks';
import { IUiSettingsClient, ToastsStart } from '@kbn/core/public';
import { DataViewFieldEditorStart } from '@kbn/data-view-field-editor-plugin/public';
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
export function createServicesMock() {
const expressionsPlugin = expressionsPluginMock.createStartContract();
expressionsPlugin.run = jest.fn(() =>
of({
partial: false,
result: {
rows: [],
},
})
) as unknown as typeof expressionsPlugin.run;
const corePluginMock = coreMock.createStart();
const uiSettingsMock: Partial<typeof corePluginMock.uiSettings> = {
get: jest.fn(),
isDefault: jest.fn((key: string) => {
return true;
}),
};
corePluginMock.uiSettings = {
...corePluginMock.uiSettings,
...uiSettingsMock,
};
const theme = {
theme$: of({ darkMode: false }),
};
corePluginMock.theme = theme;
const dataPlugin = dataPluginMock.createStartContract();
return {
core: corePluginMock,
charts: chartPluginMock.createSetupContract(),
chrome: chromeServiceMock.createStartContract(),
history: () => ({
location: {
search: '',
},
listen: jest.fn(),
}),
fieldFormats: fieldFormatsMock,
filterManager: jest.fn(),
inspector: {
open: jest.fn(),
},
uiActions: uiActionsPluginMock.createStartContract(),
uiSettings: uiSettingsMock as IUiSettingsClient,
http: {
basePath: '/',
},
dataViewEditor: {
openEditor: jest.fn(),
userPermissions: {
editDataView: jest.fn(() => true),
},
},
dataViewFieldEditor: {
openEditor: jest.fn(),
userPermissions: {
editIndexPattern: jest.fn(() => true),
},
} as unknown as DataViewFieldEditorStart,
theme,
storage: {
clear: jest.fn(),
get: jest.fn(),
set: jest.fn(),
remove: jest.fn(),
},
toastNotifications: {
addInfo: jest.fn(),
addWarning: jest.fn(),
addDanger: jest.fn(),
addSuccess: jest.fn(),
} as unknown as ToastsStart,
expressions: expressionsPlugin,
savedObjectsTagging: {
ui: {
getTagIdsFromReferences: jest.fn().mockResolvedValue([]),
updateTagsReferences: jest.fn(),
},
},
dataViews: jest.fn(),
locator: {
useUrl: jest.fn(() => ''),
navigate: jest.fn(),
getUrl: jest.fn(() => Promise.resolve('')),
},
contextLocator: { getRedirectUrl: jest.fn(() => '') },
singleDocLocator: { getRedirectUrl: jest.fn(() => '') },
data: dataPlugin,
};
}
export const servicesMock = createServicesMock();

View file

@ -10,13 +10,13 @@ import type { DataView } from '@kbn/data-views-plugin/public';
import { dataViewMock, esHitsMock } from '@kbn/discover-utils/src/__mocks__';
import { dataViewComplexMock } from './data_view_complex';
import { esHitsComplex } from './es_hits_complex';
import { discoverServiceMock } from './services';
import { GridContext } from '../components/discover_grid/discover_grid_context';
import { convertValueToString } from '../utils/convert_value_to_string';
import { servicesMock } from './services';
import { DataTableContext } from '../src/table_context';
import { convertValueToString } from '../src/utils/convert_value_to_string';
import { buildDataTableRecord } from '@kbn/discover-utils';
import type { EsHitRecord } from '@kbn/discover-utils/types';
const buildGridContext = (dataView: DataView, rows: EsHitRecord[]): GridContext => {
const buildTableContext = (dataView: DataView, rows: EsHitRecord[]): DataTableContext => {
const usedRows = rows.map((row) => {
return buildDataTableRecord(row, dataView);
});
@ -34,7 +34,7 @@ const buildGridContext = (dataView: DataView, rows: EsHitRecord[]): GridContext
convertValueToString({
rowIndex,
columnId,
fieldFormats: discoverServiceMock.fieldFormats,
fieldFormats: servicesMock.fieldFormats,
rows: usedRows,
dataView,
options,
@ -42,6 +42,6 @@ const buildGridContext = (dataView: DataView, rows: EsHitRecord[]): GridContext
};
};
export const discoverGridContextMock = buildGridContext(dataViewMock, esHitsMock);
export const dataTableContextMock = buildTableContext(dataViewMock, esHitsMock);
export const discoverGridContextComplexMock = buildGridContext(dataViewComplexMock, esHitsComplex);
export const dataTableContextComplexMock = buildTableContext(dataViewComplexMock, esHitsComplex);

View file

@ -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 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 or the Server
* Side Public License, v 1.
*/
export { UnifiedDataTable, DataLoadingState } from './src/components/data_table';
export type { UnifiedDataTableProps } from './src/components/data_table';
export { getDisplayedColumns } from './src/utils/columns';
export { JSONCodeEditorCommonMemoized } from './src/components/json_code_editor/json_code_editor_common';
export * from './src/types';
export * as columnActions from './src/components/actions/columns';
export { getRowsPerPageOptions } from './src/utils/rows_per_page';
export { popularizeField } from './src/utils/popularize_field';
export { useColumns } from './src/hooks/use_data_grid_columns';

View file

@ -6,13 +6,8 @@
* Side Public License, v 1.
*/
/**
* User configurable state of data grid, persisted in saved search
*/
export interface DiscoverGridSettings {
columns?: Record<string, DiscoverGridSettingsColumn>;
}
export interface DiscoverGridSettingsColumn {
width?: number;
}
module.exports = {
preset: '@kbn/test',
rootDir: '../..',
roots: ['<rootDir>/packages/kbn-unified-data-table'],
};

View file

@ -0,0 +1,6 @@
{
"type": "shared-common",
"id": "@kbn/unified-data-table",
"description": "Contains functionality for the unified data table which can be integrated into apps",
"owner": "@elastic/kibana-data-discovery"
}

View file

@ -0,0 +1,10 @@
{
"name": "@kbn/unified-data-table",
"private": true,
"version": "1.0.0",
"license": "SSPL-1.0 OR Elastic License 2.0",
"sideEffects": [
"*.css",
"*.scss"
]
}

View file

@ -7,15 +7,13 @@
*/
import { getStateColumnActions } from './columns';
import { configMock } from '../../../__mocks__/config';
import { dataViewMock } from '@kbn/discover-utils/src/__mocks__';
import { dataViewsMock } from '../../../__mocks__/data_views';
import { Capabilities } from '@kbn/core/types';
import { DiscoverAppState } from '../../../application/main/services/discover_app_state_container';
import { dataViewsMock } from '../../../__mocks__/data_views';
function getStateColumnAction(
state: DiscoverAppState,
setAppState: (state: Partial<DiscoverAppState>) => void
state: { columns?: string[]; sort?: string[][] },
setAppState: (state: { columns: string[]; sort?: string[][] }) => void
) {
return getStateColumnActions({
capabilities: {
@ -23,13 +21,13 @@ function getStateColumnAction(
save: false,
},
} as unknown as Capabilities,
config: configMock,
dataView: dataViewMock,
dataViews: dataViewsMock,
useNewFieldsApi: true,
setAppState,
columns: state.columns,
sort: state.sort,
defaultOrder: 'desc',
});
}

View file

@ -5,13 +5,10 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { Capabilities, IUiSettingsClient } from '@kbn/core/public';
import { DataViewsContract } from '@kbn/data-plugin/public';
import { DataView } from '@kbn/data-views-plugin/public';
import { SORT_DEFAULT_ORDER_SETTING } from '@kbn/discover-utils';
import { DiscoverAppStateContainer } from '../../../application/main/services/discover_app_state_container';
import { GetStateReturn as ContextGetStateReturn } from '../../../application/context/services/context_state';
import { popularizeField } from '../../../utils/popularize_field';
import { Capabilities } from '@kbn/core/public';
import type { DataViewsContract } from '@kbn/data-plugin/public';
import type { DataView } from '@kbn/data-views-plugin/public';
import { popularizeField } from '../../utils/popularize_field';
/**
* Helper function to provide a fallback to a single _source column if the given array of columns
@ -57,27 +54,26 @@ export function moveColumn(columns: string[], columnName: string, newIndex: numb
export function getStateColumnActions({
capabilities,
config,
dataView,
dataViews,
useNewFieldsApi,
setAppState,
columns,
sort,
defaultOrder,
}: {
capabilities: Capabilities;
config: IUiSettingsClient;
dataView: DataView;
dataViews: DataViewsContract;
useNewFieldsApi: boolean;
setAppState: DiscoverAppStateContainer['update'] | ContextGetStateReturn['setAppState'];
setAppState: (state: { columns: string[]; sort?: string[][] }) => void;
columns?: string[];
sort: string[][] | undefined;
defaultOrder: string;
}) {
function onAddColumn(columnName: string) {
popularizeField(dataView, columnName, dataViews, capabilities);
const nextColumns = addColumn(columns || [], columnName, useNewFieldsApi);
const defaultOrder = config.get(SORT_DEFAULT_ORDER_SETTING);
const nextSort = columnName === '_score' && !sort?.length ? [['_score', defaultOrder]] : sort;
setAppState({ columns: nextColumns, sort: nextSort });
}

View file

@ -9,8 +9,8 @@
import React from 'react';
import { EuiButton } from '@elastic/eui';
import { mountWithIntl } from '@kbn/test-jest-helpers';
import { discoverServiceMock } from '../../__mocks__/services';
import { discoverGridContextMock } from '../../__mocks__/grid_context';
import { servicesMock } from '../../__mocks__/services';
import { dataTableContextMock } from '../../__mocks__/table_context';
import { buildCopyColumnNameButton, buildCopyColumnValuesButton } from './build_copy_column_button';
const execCommandMock = (global.document.execCommand = jest.fn());
@ -20,7 +20,7 @@ describe('Build a column button to copy to clipboard', () => {
it('should copy a column name to clipboard on click', () => {
const { label, iconType, onClick } = buildCopyColumnNameButton({
columnDisplayName: 'test-field-name',
toastNotifications: discoverServiceMock.toastNotifications,
toastNotifications: servicesMock.toastNotifications,
});
execCommandMock.mockImplementationOnce(() => true);
@ -49,9 +49,9 @@ describe('Build a column button to copy to clipboard', () => {
const { label, iconType, onClick } = buildCopyColumnValuesButton({
columnId: 'extension',
columnDisplayName: 'custom_extension',
toastNotifications: discoverServiceMock.toastNotifications,
toastNotifications: servicesMock.toastNotifications,
rowsCount: 3,
valueToStringConverter: discoverGridContextMock.valueToStringConverter,
valueToStringConverter: dataTableContextMock.valueToStringConverter,
});
const wrapper = mountWithIntl(
@ -72,8 +72,8 @@ describe('Build a column button to copy to clipboard', () => {
} = buildCopyColumnValuesButton({
columnId: '_source',
columnDisplayName: 'Document',
toastNotifications: discoverServiceMock.toastNotifications,
valueToStringConverter: discoverGridContextMock.valueToStringConverter,
toastNotifications: servicesMock.toastNotifications,
valueToStringConverter: dataTableContextMock.valueToStringConverter,
rowsCount: 3,
});
@ -101,7 +101,7 @@ describe('Build a column button to copy to clipboard', () => {
it('should not copy to clipboard on click', () => {
const { label, iconType, onClick } = buildCopyColumnNameButton({
columnDisplayName: 'test-field-name',
toastNotifications: discoverServiceMock.toastNotifications,
toastNotifications: servicesMock.toastNotifications,
});
execCommandMock.mockImplementationOnce(() => false);

View file

@ -13,8 +13,8 @@ import type { ToastsStart } from '@kbn/core/public';
import {
copyColumnValuesToClipboard,
copyColumnNameToClipboard,
} from '../../utils/copy_value_to_clipboard';
import type { ValueToStringConverter } from '../../types';
} from '../utils/copy_value_to_clipboard';
import type { ValueToStringConverter } from '../types';
function buildCopyColumnButton({
label,
@ -47,7 +47,7 @@ export function buildCopyColumnNameButton({
return buildCopyColumnButton({
label: (
<FormattedMessage
id="discover.grid.copyColumnNameToClipBoardButton"
id="unifiedDataTable.grid.copyColumnNameToClipBoardButton"
defaultMessage="Copy name"
/>
),
@ -72,7 +72,7 @@ export function buildCopyColumnValuesButton({
return buildCopyColumnButton({
label: (
<FormattedMessage
id="discover.grid.copyColumnValuesToClipBoardButton"
id="unifiedDataTable.grid.copyColumnValuesToClipBoardButton"
defaultMessage="Copy column"
/>
),

View file

@ -11,7 +11,7 @@ import { DataView, DataViewField } from '@kbn/data-views-plugin/common';
import { mountWithIntl } from '@kbn/test-jest-helpers';
import React from 'react';
import { buildDataViewMock } from '@kbn/discover-utils/src/__mocks__';
import { discoverServiceMock } from '../../__mocks__/services';
import { servicesMock } from '../../__mocks__/services';
import { buildEditFieldButton } from './build_edit_field_button';
const dataView = buildDataViewMock({
@ -50,8 +50,7 @@ describe('buildEditFieldButton', () => {
it('should return null if the field is not editable', () => {
const field = dataView.getFieldByName('unknown_field') as DataViewField;
const button = buildEditFieldButton({
hasEditDataViewPermission: () =>
discoverServiceMock.dataViewEditor.userPermissions.editDataView(),
hasEditDataViewPermission: () => servicesMock.dataViewEditor.userPermissions.editDataView(),
dataView,
field,
editField: jest.fn(),
@ -61,12 +60,11 @@ describe('buildEditFieldButton', () => {
it('should return null if the data view is not editable', () => {
jest
.spyOn(discoverServiceMock.dataViewEditor.userPermissions, 'editDataView')
.spyOn(servicesMock.dataViewEditor.userPermissions, 'editDataView')
.mockReturnValueOnce(false);
const field = dataView.getFieldByName('bytes') as DataViewField;
const button = buildEditFieldButton({
hasEditDataViewPermission: () =>
discoverServiceMock.dataViewEditor.userPermissions.editDataView(),
hasEditDataViewPermission: () => servicesMock.dataViewEditor.userPermissions.editDataView(),
dataView,
field,
editField: jest.fn(),
@ -77,8 +75,7 @@ describe('buildEditFieldButton', () => {
it('should return null if passed the _source field', () => {
const field = dataView.getFieldByName('_source') as DataViewField;
const button = buildEditFieldButton({
hasEditDataViewPermission: () =>
discoverServiceMock.dataViewEditor.userPermissions.editDataView(),
hasEditDataViewPermission: () => servicesMock.dataViewEditor.userPermissions.editDataView(),
dataView,
field,
editField: jest.fn(),
@ -89,8 +86,7 @@ describe('buildEditFieldButton', () => {
it('should return EuiListGroupItemProps if the field and data view are editable', () => {
const field = dataView.getFieldByName('bytes') as DataViewField;
const button = buildEditFieldButton({
hasEditDataViewPermission: () =>
discoverServiceMock.dataViewEditor.userPermissions.editDataView(),
hasEditDataViewPermission: () => servicesMock.dataViewEditor.userPermissions.editDataView(),
dataView,
field,
editField: jest.fn(),
@ -105,7 +101,7 @@ describe('buildEditFieldButton', () => {
"iconType": "pencil",
"label": <FormattedMessage
defaultMessage="Edit data view field"
id="discover.grid.editFieldButton"
id="unifiedDataTable.grid.editFieldButton"
values={Object {}}
/>,
"onClick": [Function],
@ -118,8 +114,7 @@ describe('buildEditFieldButton', () => {
const field = dataView.getFieldByName('bytes') as DataViewField;
const editField = jest.fn();
const buttonProps = buildEditFieldButton({
hasEditDataViewPermission: () =>
discoverServiceMock.dataViewEditor.userPermissions.editDataView(),
hasEditDataViewPermission: () => servicesMock.dataViewEditor.userPermissions.editDataView(),
dataView,
field,
editField,

View file

@ -10,7 +10,7 @@ import { EuiListGroupItemProps } from '@elastic/eui';
import { DataView, DataViewField } from '@kbn/data-views-plugin/common';
import { FormattedMessage } from '@kbn/i18n-react';
import React from 'react';
import { getFieldCapabilities } from '../../utils/get_field_capabilities';
import { getFieldCapabilities } from '../utils/get_field_capabilities';
export const buildEditFieldButton = ({
hasEditDataViewPermission,
@ -37,7 +37,10 @@ export const buildEditFieldButton = ({
const editFieldButton: EuiListGroupItemProps = {
size: 'xs',
label: (
<FormattedMessage id="discover.grid.editFieldButton" defaultMessage="Edit data view field" />
<FormattedMessage
id="unifiedDataTable.grid.editFieldButton"
defaultMessage="Edit data view field"
/>
),
iconType: 'pencil',
iconProps: { size: 'm' },

View file

@ -1,4 +1,4 @@
.dscDiscoverGrid {
.unifiedDataTable {
width: 100%;
max-width: 100%;
height: 100%;
@ -19,8 +19,8 @@
border-right: none;
}
.dscDiscoverGrid__table .euiDataGridRowCell:first-of-type,
.dscDiscoverGrid__table .euiDataGrid--headerShade.euiDataGrid--bordersAll .euiDataGridHeaderCell:first-of-type {
.unifiedDataTable__table .euiDataGridRowCell:first-of-type,
.unifiedDataTable__table .euiDataGrid--headerShade.euiDataGrid--bordersAll .euiDataGridHeaderCell:first-of-type {
border-left: none;
border-right: none;
}
@ -31,11 +31,11 @@
}
}
.dscDiscoverGrid__cellValue {
.unifiedDataTable__cellValue {
font-family: $euiCodeFontFamily;
}
.dscDiscoverGrid__cellPopover {
.unifiedDataTable__cellPopover {
// Fixes https://github.com/elastic/kibana/issues/145216 in Chrome
.lines-content.monaco-editor-background {
overflow: unset !important;
@ -43,7 +43,7 @@
}
}
.dscDiscoverGrid__cellPopoverValue {
.unifiedDataTable__cellPopoverValue {
font-family: $euiCodeFontFamily;
font-size: $euiFontSizeS;
}
@ -52,24 +52,32 @@
white-space: pre-wrap;
}
.dscDiscoverGrid__inner {
.unifiedDataTable__inner {
display: flex;
flex-direction: column;
flex-wrap: nowrap;
height: 100%;
}
.dscDiscoverGrid__table {
.unifiedDataTable__table {
flex-grow: 1;
flex-shrink: 1;
min-height: 0;
}
.dscTable__flyoutHeader {
.unifiedDataTable__footer {
flex-shrink: 0;
background-color: $euiColorLightShade;
padding: $euiSize / 2 $euiSize;
margin-top: $euiSize / 4;
text-align: center;
}
.unifiedDataTable__flyoutHeader {
white-space: nowrap;
}
.dscTable__flyoutDocumentNavigation {
.unifiedDataTable__flyoutDocumentNavigation {
justify-content: flex-end;
}
@ -106,11 +114,11 @@
width: 100%;
}
.dscFormatSource {
.unifiedDataTableFormatSource {
@include euiTextTruncate;
}
.dscDiscoverGrid__descriptionListDescription {
.unifiedDataTable__descriptionListDescription {
word-break: break-all;
white-space: normal;
@ -132,7 +140,7 @@
@include euiBreakpoint('xs', 's', 'm') {
// EUI issue to hide 'of' text https://github.com/elastic/eui/issues/4654
.dscTable__flyoutDocumentNavigation .euiPagination__compressedText {
.unifiedDataTable__flyoutDocumentNavigation .euiPagination__compressedText {
display: none;
}
}

View file

@ -7,16 +7,23 @@
*/
import React from 'react';
import { ReactWrapper } from 'enzyme';
import { EuiCopy } from '@elastic/eui';
import {
EuiButton,
EuiCopy,
EuiDataGridCellValueElementProps,
EuiDataGridCustomBodyProps,
} from '@elastic/eui';
import { Storage } from '@kbn/kibana-utils-plugin/public';
import { act } from 'react-dom/test-utils';
import { findTestSubject } from '@elastic/eui/lib/test';
import { buildDataViewMock, deepMockedFields, esHitsMock } from '@kbn/discover-utils/src/__mocks__';
import { mountWithIntl } from '@kbn/test-jest-helpers';
import { DiscoverGrid, DiscoverGridProps, DataLoadingState } from './discover_grid';
import { DataLoadingState, UnifiedDataTable, UnifiedDataTableProps } from './data_table';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import { discoverServiceMock } from '../../__mocks__/services';
import { servicesMock } from '../../__mocks__/services';
import { buildDataTableRecord, getDocId } from '@kbn/discover-utils';
import type { EsHitRecord } from '@kbn/discover-utils/types';
import type { DataTableRecord, EsHitRecord } from '@kbn/discover-utils/types';
import { testLeadingControlColumn } from '../../__mocks__/external_control_columns';
const mockUseDataGridColumnsCellActions = jest.fn((prop: unknown) => []);
jest.mock('@kbn/cell-actions', () => ({
@ -30,8 +37,8 @@ export const dataViewMock = buildDataViewMock({
timeFieldName: '@timestamp',
});
function getProps() {
const services = discoverServiceMock;
function getProps(): UnifiedDataTableProps {
const services = servicesMock;
services.dataViewFieldEditor.userPermissions.editIndexPattern = jest.fn().mockReturnValue(true);
return {
@ -40,9 +47,7 @@ function getProps() {
dataView: dataViewMock,
loadingState: DataLoadingState.loaded,
expandedDoc: undefined,
onAddColumn: jest.fn(),
onFilter: jest.fn(),
onRemoveColumn: jest.fn(),
onResize: jest.fn(),
onSetColumns: jest.fn(),
onSort: jest.fn(),
@ -55,14 +60,22 @@ function getProps() {
showTimeCol: true,
sort: [],
useNewFieldsApi: true,
services,
services: {
fieldFormats: services.fieldFormats,
uiSettings: services.uiSettings,
dataViewFieldEditor: services.dataViewFieldEditor,
toastNotifications: services.toastNotifications,
storage: services.storage as unknown as Storage,
data: services.data,
theme: services.theme,
},
};
}
async function getComponent(props: DiscoverGridProps = getProps()) {
const Proxy = (innerProps: DiscoverGridProps) => (
<KibanaContextProvider services={discoverServiceMock}>
<DiscoverGrid {...innerProps} />
async function getComponent(props: UnifiedDataTableProps = getProps()) {
const Proxy = (innerProps: UnifiedDataTableProps) => (
<KibanaContextProvider services={servicesMock}>
<UnifiedDataTable {...innerProps} />
</KibanaContextProvider>
);
@ -74,7 +87,7 @@ async function getComponent(props: DiscoverGridProps = getProps()) {
return component;
}
function getSelectedDocNr(component: ReactWrapper<DiscoverGridProps>) {
function getSelectedDocNr(component: ReactWrapper<UnifiedDataTableProps>) {
const gridSelectionBtn = findTestSubject(component, 'dscGridSelectionBtn');
if (!gridSelectionBtn.length) {
return 0;
@ -83,7 +96,7 @@ function getSelectedDocNr(component: ReactWrapper<DiscoverGridProps>) {
return Number(selectedNr);
}
function getDisplayedDocNr(component: ReactWrapper<DiscoverGridProps>) {
function getDisplayedDocNr(component: ReactWrapper<UnifiedDataTableProps>) {
const gridSelectionBtn = findTestSubject(component, 'discoverDocTable');
if (!gridSelectionBtn.length) {
return 0;
@ -93,7 +106,7 @@ function getDisplayedDocNr(component: ReactWrapper<DiscoverGridProps>) {
}
async function toggleDocSelection(
component: ReactWrapper<DiscoverGridProps>,
component: ReactWrapper<UnifiedDataTableProps>,
document: EsHitRecord
) {
act(() => {
@ -103,13 +116,13 @@ async function toggleDocSelection(
component.update();
}
describe('DiscoverGrid', () => {
describe('UnifiedDataTable', () => {
afterEach(async () => {
jest.clearAllMocks();
});
describe('Document selection', () => {
let component: ReactWrapper<DiscoverGridProps>;
let component: ReactWrapper<UnifiedDataTableProps>;
beforeEach(async () => {
component = await getComponent();
});
@ -287,4 +300,136 @@ describe('DiscoverGrid', () => {
`);
});
});
describe('externalControlColumns', () => {
it('should render external leading control columns', async () => {
const component = await getComponent({
...getProps(),
expandedDoc: {
id: 'test',
raw: {
_index: 'test_i',
_id: 'test',
},
flattened: { test: jest.fn() },
},
setExpandedDoc: jest.fn(),
renderDocumentView: jest.fn(),
externalControlColumns: [testLeadingControlColumn],
});
expect(findTestSubject(component, 'docTableExpandToggleColumn').exists()).toBeTruthy();
expect(findTestSubject(component, 'test-body-control-column-cell').exists()).toBeTruthy();
});
});
it('should render provided in renderDocumentView DocumentView on expand clicked', async () => {
const component = await getComponent({
...getProps(),
expandedDoc: {
id: 'test',
raw: {
_index: 'test_i',
_id: 'test',
},
flattened: { test: jest.fn() },
},
setExpandedDoc: jest.fn(),
renderDocumentView: (
hit: DataTableRecord,
displayedRows: DataTableRecord[],
displayedColumns: string[]
) => <div data-test-subj="test-document-view">{hit.id}</div>,
externalControlColumns: [testLeadingControlColumn],
});
findTestSubject(component, 'docTableExpandToggleColumn').first().simulate('click');
expect(findTestSubject(component, 'test-document-view').exists()).toBeTruthy();
});
describe('externalAdditionalControls', () => {
it('should render external additional toolbar controls', async () => {
const component = await getComponent({
...getProps(),
columns: ['message'],
externalAdditionalControls: <EuiButton data-test-subj="test-additional-control" />,
});
expect(findTestSubject(component, 'test-additional-control').exists()).toBeTruthy();
expect(findTestSubject(component, 'dataGridColumnSelectorButton').exists()).toBeTruthy();
});
});
describe('externalCustomRenderers', () => {
it('should render only host column with the custom renderer, message should be rendered with the default cell renderer', async () => {
const component = await getComponent({
...getProps(),
columns: ['message', 'host'],
externalCustomRenderers: {
host: (props: EuiDataGridCellValueElementProps) => (
<div data-test-subj={`test-renderer-${props.columnId}`}>{props.columnId}</div>
),
},
});
expect(findTestSubject(component, 'test-renderer-host').exists()).toBeTruthy();
expect(findTestSubject(component, 'test-renderer-message').exists()).toBeFalsy();
});
});
describe('renderCustomGridBody', () => {
it('should render custom grid body for each row', async () => {
const component = await getComponent({
...getProps(),
columns: ['message', 'host'],
trailingControlColumns: [
{
id: 'row-details',
// The header cell should be visually hidden, but available to screen readers
width: 0,
headerCellRender: () => <></>,
headerCellProps: { className: 'euiScreenReaderOnly' },
// The footer cell can be hidden to both visual & SR users, as it does not contain meaningful information
footerCellProps: { style: { display: 'none' } },
// When rendering this custom cell, we'll want to override
// the automatic width/heights calculated by EuiDataGrid
rowCellRender: jest.fn(),
},
],
renderCustomGridBody: (props: EuiDataGridCustomBodyProps) => (
<div data-test-subj="test-renderer-custom-grid-body">
<EuiButton />
</div>
),
});
expect(findTestSubject(component, 'test-renderer-custom-grid-body').exists()).toBeTruthy();
});
});
describe('componentsTourSteps', () => {
it('should render tour step for the first row of leading control column expandButton', async () => {
const component = await getComponent({
...getProps(),
expandedDoc: {
id: 'test',
raw: {
_index: 'test_i',
_id: 'test',
},
flattened: { test: jest.fn() },
},
setExpandedDoc: jest.fn(),
renderDocumentView: jest.fn(),
componentsTourSteps: { expandButton: 'test-expand' },
});
const gridExpandBtn = findTestSubject(component, 'docTableExpandToggleColumn').first();
const tourStep = gridExpandBtn.getDOMNode().getAttribute('id');
expect(tourStep).toEqual('test-expand');
});
});
});

View file

@ -11,7 +11,8 @@ import classnames from 'classnames';
import { FormattedMessage } from '@kbn/i18n-react';
import { of } from 'rxjs';
import useObservable from 'react-use/lib/useObservable';
import './discover_grid.scss';
import './data_table.scss';
import type { Storage } from '@kbn/kibana-utils-plugin/public';
import {
EuiDataGridSorting,
EuiDataGrid,
@ -23,44 +24,42 @@ import {
EuiIcon,
EuiDataGridRefProps,
EuiDataGridInMemory,
EuiDataGridControlColumn,
EuiDataGridCustomBodyProps,
EuiDataGridCellValueElementProps,
} from '@elastic/eui';
import type { DataView } from '@kbn/data-views-plugin/public';
import type { SortOrder } from '@kbn/saved-search-plugin/public';
import {
useDataGridColumnsCellActions,
type UseDataGridColumnsCellActionsProps,
} from '@kbn/cell-actions';
import type { AggregateQuery, Filter, Query } from '@kbn/es-query';
import { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import type { ToastsStart, IUiSettingsClient, HttpStart, CoreStart } from '@kbn/core/public';
import { DataViewFieldEditorStart } from '@kbn/data-view-field-editor-plugin/public';
import { Serializable } from '@kbn/utility-types';
import type { ToastsStart, IUiSettingsClient } from '@kbn/core/public';
import type { Serializable } from '@kbn/utility-types';
import type { DataTableRecord } from '@kbn/discover-utils/types';
import { getShouldShowFieldHandler } from '@kbn/discover-utils';
import {
DOC_HIDE_TIME_COLUMN_SETTING,
MAX_DOC_FIELDS_DISPLAYED,
SHOW_MULTIFIELDS,
} from '@kbn/discover-utils';
import { getShouldShowFieldHandler, DOC_HIDE_TIME_COLUMN_SETTING } from '@kbn/discover-utils';
import type { DataViewFieldEditorStart } from '@kbn/data-view-field-editor-plugin/public';
import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import type { ThemeServiceStart } from '@kbn/react-kibana-context-common';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { DocViewFilterFn } from '@kbn/unified-doc-viewer/types';
import { getSchemaDetectors } from './discover_grid_schema';
import { DiscoverGridFlyout } from './discover_grid_flyout';
import { DiscoverGridContext } from './discover_grid_context';
import { getRenderCellValueFn } from './get_render_cell_value';
import { DiscoverGridSettings } from './types';
import { UnifiedDataTableSettings, ValueToStringConverter } from '../types';
import { getDisplayedColumns } from '../utils/columns';
import { convertValueToString } from '../utils/convert_value_to_string';
import { getRowsPerPageOptions } from '../utils/rows_per_page';
import { getRenderCellValueFn } from '../utils/get_render_cell_value';
import { getEuiGridColumns, getLeadControlColumns, getVisibleColumns } from './data_table_columns';
import { UnifiedDataTableContext } from '../table_context';
import { getSchemaDetectors } from './data_table_schema';
import { DataTableDocumentToolbarBtn } from './data_table_document_selection';
import { useRowHeightsOptions } from '../hooks/use_row_heights_options';
import {
getEuiGridColumns,
getLeadControlColumns,
getVisibleColumns,
} from './discover_grid_columns';
import { GRID_STYLE, toolbarVisibility as toolbarVisibilityDefaults } from './constants';
import { getDisplayedColumns } from '../../utils/columns';
import { DiscoverGridDocumentToolbarBtn } from './discover_grid_document_selection';
import { DiscoverGridFooter } from './discover_grid_footer';
import type { ValueToStringConverter } from '../../types';
import { useRowHeightsOptions } from '../../hooks/use_row_heights_options';
import { convertValueToString } from '../../utils/convert_value_to_string';
import { getRowsPerPageOptions, getDefaultRowsPerPage } from '../../utils/rows_per_page';
DEFAULT_ROWS_PER_PAGE,
GRID_STYLE,
toolbarVisibility as toolbarVisibilityDefaults,
} from '../constants';
import { UnifiedDataTableFooter } from './data_table_footer';
export type SortOrder = [string, string];
export enum DataLoadingState {
loading = 'loading',
@ -75,7 +74,7 @@ interface SortObj {
direction: string;
}
export interface DiscoverGridProps {
export interface UnifiedDataTableProps {
/**
* Determines which element labels the grid for ARIA
*/
@ -85,7 +84,7 @@ export interface DiscoverGridProps {
*/
className?: string;
/**
* Determines which columns are displayed
* Determines ids of the columns which are displayed
*/
columns: string[];
/**
@ -100,19 +99,10 @@ export interface DiscoverGridProps {
* Determines if data is currently loaded
*/
loadingState: DataLoadingState;
/**
* Function used to add a column in the document flyout
*/
onAddColumn: (column: string) => void;
/**
* Function to add a filter in the grid cell or document flyout
*/
onFilter: DocViewFilterFn;
/**
* Function used in the grid header and flyout to remove a column
* @param column
*/
onRemoveColumn: (column: string) => void;
/**
* Function triggered when a column is resized by the user
*/
@ -140,13 +130,13 @@ export interface DiscoverGridProps {
/**
* Grid display settings persisted in Elasticsearch (e.g. column width)
*/
settings?: DiscoverGridSettings;
settings?: UnifiedDataTableSettings;
/**
* Saved search description
* Search description
*/
searchDescription?: string;
/**
* Saved search title
* Search title
*/
searchTitle?: string;
/**
@ -201,22 +191,6 @@ export interface DiscoverGridProps {
* Callback to execute on edit runtime field
*/
onFieldEdited?: () => void;
/**
* Filters applied by saved search embeddable
*/
filters?: Filter[];
/**
* Query applied by KQL bar or text based editor
*/
query?: Query | AggregateQuery;
/**
* Saved search id used for links to single doc and surrounding docs in the flyout
*/
savedSearchId?: string;
/**
* Document detail view component
*/
DocumentView?: typeof DiscoverGridFlyout;
/**
* Optional triggerId to retrieve the column cell actions that will override the default ones
*/
@ -225,13 +199,38 @@ export interface DiscoverGridProps {
* Service dependencies
*/
services: {
core: CoreStart;
theme: ThemeServiceStart;
fieldFormats: FieldFormatsStart;
addBasePath: HttpStart['basePath']['prepend'];
uiSettings: IUiSettingsClient;
dataViewFieldEditor: DataViewFieldEditorStart;
toastNotifications: ToastsStart;
storage: Storage;
data: DataPublicPluginStart;
};
/**
* Callback to render DocumentView when the document is expanded
*/
renderDocumentView?: (
hit: DataTableRecord,
displayedRows: DataTableRecord[],
displayedColumns: string[]
) => JSX.Element | undefined;
/**
* Optional value for providing configuration setting for UnifiedDataTable rows height
*/
configRowHeight?: number;
/**
* Optional value for providing configuration setting for enabling to display the complex fields in the table. Default is true.
*/
showMultiFields?: boolean;
/**
* Optional value for providing configuration setting for maximum number of document fields to display in the table. Default is 50.
*/
maxDocFieldsDisplayed?: number;
/**
* Optional value for providing EuiDataGridControlColumn list of the additional leading control columns. UnifiedDataTable includes two control columns: Open Details and Select.
*/
externalControlColumns?: EuiDataGridControlColumn[];
/**
* Number total hits from ES
*/
@ -240,24 +239,62 @@ export interface DiscoverGridProps {
* To fetch more
*/
onFetchMoreRecords?: () => void;
/**
* Optional value for providing the additional controls available in the UnifiedDataTable toolbar to manage it's records or state. UnifiedDataTable includes Columns, Sorting and Bulk Actions.
*/
externalAdditionalControls?: React.ReactNode;
/**
* Optional list of number type values to set custom UnifiedDataTable paging options to display the records per page.
*/
rowsPerPageOptions?: number[];
/**
* An optional function called to completely customize and control the rendering of
* EuiDataGrid's body and cell placement. This can be used to, e.g. remove EuiDataGrid's
* virtualization library, or roll your own.
*
* This component is **only** meant as an escape hatch for extremely custom use cases.
*
* Behind the scenes, this function is treated as a React component,
* allowing hooks, context, and other React concepts to be used.
* It receives #EuiDataGridCustomBodyProps as its only argument.
*/
renderCustomGridBody?: (args: EuiDataGridCustomBodyProps) => React.ReactNode;
/**
* An optional list of the EuiDataGridControlColumn type for setting trailing control columns standard for EuiDataGrid.
*/
trailingControlColumns?: EuiDataGridControlColumn[];
/**
* An optional value for a custom number of the visible cell actions in the table. By default is up to 3.
**/
visibleCellActions?: number;
/**
* An optional settings for a specified fields rendering like links. Applied only for the listed fields rendering.
*/
externalCustomRenderers?: Record<
string,
(props: EuiDataGridCellValueElementProps) => React.ReactNode
>;
/**
* Name of the UnifiedDataTable consumer component or application
*/
consumer?: string;
/**
* Optional key/value pairs to set guided onboarding steps ids for a data table components included to guided tour.
*/
componentsTourSteps?: Record<string, string>;
}
export const EuiDataGridMemoized = React.memo(EuiDataGrid);
const CONTROL_COLUMN_IDS_DEFAULT = ['openDetails', 'select'];
export const DiscoverGrid = ({
export const UnifiedDataTable = ({
ariaLabelledBy,
columns,
controlColumnIds = CONTROL_COLUMN_IDS_DEFAULT,
dataView,
loadingState,
expandedDoc,
onAddColumn,
filters,
query,
savedSearchId,
onFilter,
onRemoveColumn,
onResize,
onSetColumns,
onSort,
@ -265,7 +302,6 @@ export const DiscoverGrid = ({
sampleSize,
searchDescription,
searchTitle,
setExpandedDoc,
settings,
showTimeCol,
showFullScreenButton = true,
@ -273,7 +309,6 @@ export const DiscoverGrid = ({
useNewFieldsApi,
isSortEnabled = true,
isPaginationEnabled = true,
controlColumnIds = CONTROL_COLUMN_IDS_DEFAULT,
cellActionsTriggerId,
className,
rowHeightState,
@ -282,13 +317,28 @@ export const DiscoverGrid = ({
rowsPerPageState,
onUpdateRowsPerPage,
onFieldEdited,
DocumentView,
services,
renderCustomGridBody,
trailingControlColumns,
totalHits,
onFetchMoreRecords,
}: DiscoverGridProps) => {
const { fieldFormats, toastNotifications, dataViewFieldEditor, uiSettings } = services;
const { darkMode } = useObservable(services.core.theme?.theme$ ?? of(themeDefault), themeDefault);
renderDocumentView,
setExpandedDoc,
expandedDoc,
configRowHeight,
showMultiFields = true,
maxDocFieldsDisplayed = 50,
externalControlColumns,
externalAdditionalControls,
rowsPerPageOptions,
visibleCellActions,
externalCustomRenderers,
consumer = 'discover',
componentsTourSteps,
}: UnifiedDataTableProps) => {
const { fieldFormats, toastNotifications, dataViewFieldEditor, uiSettings, storage, data } =
services;
const { darkMode } = useObservable(services.theme?.theme$ ?? of(themeDefault), themeDefault);
const dataGridRef = useRef<EuiDataGridRefProps>(null);
const [selectedDocs, setSelectedDocs] = useState<string[]>([]);
const [isFilterActive, setIsFilterActive] = useState(false);
@ -300,7 +350,7 @@ export const DiscoverGrid = ({
}
const idMap = rows.reduce((map, row) => map.set(row.id, true), new Map());
// filter out selected docs that are no longer part of the current data
const result = selectedDocs.filter((docId) => idMap.get(docId));
const result = selectedDocs.filter((docId) => !!idMap.get(docId));
if (result.length === 0 && isFilterActive) {
setIsFilterActive(false);
}
@ -336,17 +386,45 @@ export const DiscoverGrid = ({
[displayedRows, dataView, fieldFormats]
);
const unifiedDataTableContextValue = useMemo(
() => ({
expanded: expandedDoc,
setExpanded: setExpandedDoc,
rows: displayedRows,
onFilter,
dataView,
isDarkMode: darkMode,
selectedDocs: usedSelectedDocs,
setSelectedDocs: (newSelectedDocs: React.SetStateAction<string[]>) => {
setSelectedDocs(newSelectedDocs);
if (isFilterActive && newSelectedDocs.length === 0) {
setIsFilterActive(false);
}
},
valueToStringConverter,
componentsTourSteps,
}),
[
componentsTourSteps,
darkMode,
dataView,
displayedRows,
expandedDoc,
isFilterActive,
onFilter,
setExpandedDoc,
usedSelectedDocs,
valueToStringConverter,
]
);
/**
* Pagination
*/
const defaultRowsPerPage = useMemo(
() => getDefaultRowsPerPage(services.uiSettings),
[services.uiSettings]
);
const currentPageSize =
typeof rowsPerPageState === 'number' && rowsPerPageState > 0
? rowsPerPageState
: defaultRowsPerPage;
: DEFAULT_ROWS_PER_PAGE;
const [pagination, setPagination] = useState({
pageIndex: 0,
pageSize: currentPageSize,
@ -371,10 +449,17 @@ export const DiscoverGrid = ({
onChangePage,
pageIndex: pagination.pageIndex > pageCount - 1 ? 0 : pagination.pageIndex,
pageSize: pagination.pageSize,
pageSizeOptions: getRowsPerPageOptions(pagination.pageSize),
pageSizeOptions: rowsPerPageOptions ?? getRowsPerPageOptions(pagination.pageSize),
}
: undefined;
}, [pagination, pageCount, isPaginationEnabled, onUpdateRowsPerPage]);
}, [
isPaginationEnabled,
pagination.pageIndex,
pagination.pageSize,
pageCount,
rowsPerPageOptions,
onUpdateRowsPerPage,
]);
useEffect(() => {
setPagination((paginationData) =>
@ -403,8 +488,6 @@ export const DiscoverGrid = ({
[onSort, isSortEnabled, isPlainRecord, setInmemorySortingColumns]
);
const showMultiFields = services.uiSettings.get(SHOW_MULTIFIELDS);
const shouldShowFieldHandler = useMemo(() => {
const dataViewFields = dataView.fields.getAll().map((fld) => fld.name);
return getShouldShowFieldHandler(dataViewFields, dataView, showMultiFields);
@ -420,10 +503,20 @@ export const DiscoverGrid = ({
displayedRows,
useNewFieldsApi,
shouldShowFieldHandler,
services.uiSettings.get(MAX_DOC_FIELDS_DISPLAYED),
() => dataGridRef.current?.closeCellPopover()
() => dataGridRef.current?.closeCellPopover(),
services.fieldFormats,
maxDocFieldsDisplayed,
externalCustomRenderers
),
[dataView, displayedRows, useNewFieldsApi, shouldShowFieldHandler, services.uiSettings]
[
dataView,
displayedRows,
useNewFieldsApi,
shouldShowFieldHandler,
maxDocFieldsDisplayed,
services.fieldFormats,
externalCustomRenderers,
]
);
/**
@ -459,7 +552,7 @@ export const DiscoverGrid = ({
);
const visibleColumns = useMemo(
() => getVisibleColumns(displayedColumns, dataView, showTimeCol) as string[],
() => getVisibleColumns(displayedColumns, dataView, showTimeCol),
[dataView, displayedColumns, showTimeCol]
);
@ -511,6 +604,7 @@ export const DiscoverGrid = ({
valueToStringConverter,
onFilter,
editField,
visibleCellActions,
}),
[
onFilter,
@ -527,6 +621,7 @@ export const DiscoverGrid = ({
dataViewFieldEditor,
valueToStringConverter,
editField,
visibleCellActions,
]
);
@ -554,26 +649,33 @@ export const DiscoverGrid = ({
return { columns: sortingColumns, onSort: () => {} };
}, [isSortEnabled, sortingColumns, isPlainRecord, inmemorySortingColumns, onTableSort]);
const canSetExpandedDoc = Boolean(setExpandedDoc && DocumentView);
const canSetExpandedDoc = Boolean(setExpandedDoc && !!renderDocumentView);
const lead = useMemo(
() =>
getLeadControlColumns(canSetExpandedDoc).filter(({ id }) => controlColumnIds.includes(id)),
[controlColumnIds, canSetExpandedDoc]
);
const leadingControlColumns = useMemo(() => {
const internalControlColumns = getLeadControlColumns(canSetExpandedDoc).filter(({ id }) =>
controlColumnIds.includes(id)
);
return externalControlColumns
? [...internalControlColumns, ...externalControlColumns]
: internalControlColumns;
}, [canSetExpandedDoc, externalControlColumns, controlColumnIds]);
const additionalControls = useMemo(
() =>
usedSelectedDocs.length ? (
<DiscoverGridDocumentToolbarBtn
isFilterActive={isFilterActive}
rows={rows!}
selectedDocs={usedSelectedDocs}
setSelectedDocs={setSelectedDocs}
setIsFilterActive={setIsFilterActive}
/>
) : null,
[usedSelectedDocs, isFilterActive, rows, setIsFilterActive]
() => (
<>
{usedSelectedDocs.length ? (
<DataTableDocumentToolbarBtn
isFilterActive={isFilterActive}
rows={rows!}
selectedDocs={usedSelectedDocs}
setSelectedDocs={setSelectedDocs}
setIsFilterActive={setIsFilterActive}
/>
) : null}
{externalAdditionalControls}
</>
),
[usedSelectedDocs, isFilterActive, rows, externalAdditionalControls]
);
const showDisplaySelector = useMemo(
@ -617,17 +719,20 @@ export const DiscoverGrid = ({
const rowHeightsOptions = useRowHeightsOptions({
rowHeightState,
onUpdateRowHeight,
storage,
configRowHeight,
consumer,
});
const isRenderComplete = loadingState !== DataLoadingState.loading;
if (!rowCount && loadingState === DataLoadingState.loading) {
return (
<div className="euiDataGrid__loading" data-test-subj="discoverDataGridLoading">
<div className="euiDataGrid__loading" data-test-subj="unifiedDataTableLoading">
<EuiText size="xs" color="subdued">
<EuiLoadingSpinner />
<EuiSpacer size="s" />
<FormattedMessage id="discover.loadingResults" defaultMessage="Loading results" />
<FormattedMessage id="unifiedDataTable.loadingResults" defaultMessage="Loading results" />
</EuiText>
</div>
);
@ -646,32 +751,18 @@ export const DiscoverGrid = ({
<EuiText size="xs" color="subdued">
<EuiIcon type="discoverApp" size="m" color="subdued" />
<EuiSpacer size="s" />
<FormattedMessage id="discover.noResultsFound" defaultMessage="No results found" />
<FormattedMessage
id="unifiedDataTable.noResultsFound"
defaultMessage="No results found"
/>
</EuiText>
</div>
);
}
return (
<DiscoverGridContext.Provider
value={{
expanded: expandedDoc,
setExpanded: setExpandedDoc,
rows: displayedRows,
onFilter,
dataView,
isDarkMode: darkMode,
selectedDocs: usedSelectedDocs,
setSelectedDocs: (newSelectedDocs) => {
setSelectedDocs(newSelectedDocs);
if (isFilterActive && newSelectedDocs.length === 0) {
setIsFilterActive(false);
}
},
valueToStringConverter,
}}
>
<span className="dscDiscoverGrid__inner">
<UnifiedDataTableContext.Provider value={unifiedDataTableContextValue}>
<span className="unifiedDataTable__inner">
<div
data-test-subj="discoverDocTable"
data-render-complete={isRenderComplete}
@ -679,7 +770,7 @@ export const DiscoverGrid = ({
data-title={searchTitle}
data-description={searchDescription}
data-document-number={displayedRows.length}
className={classnames(className, 'dscDiscoverGrid__table')}
className={classnames(className, 'unifiedDataTable__table')}
>
<EuiDataGridMemoized
aria-describedby={randomId}
@ -687,7 +778,7 @@ export const DiscoverGrid = ({
columns={euiGridColumns}
columnVisibility={columnsVisibility}
data-test-subj="docTable"
leadingControlColumns={lead}
leadingControlColumns={leadingControlColumns}
onColumnResize={onResize}
pagination={paginationObj}
renderCellValue={renderCellValue}
@ -699,11 +790,13 @@ export const DiscoverGrid = ({
rowHeightsOptions={rowHeightsOptions}
inMemory={inMemory}
gridStyle={GRID_STYLE}
renderCustomGridBody={renderCustomGridBody}
trailingControlColumns={trailingControlColumns}
/>
</div>
{loadingState !== DataLoadingState.loading &&
isPaginationEnabled && ( // we hide the footer for Surrounding Documents page
<DiscoverGridFooter
<UnifiedDataTableFooter
isLoadingMore={loadingState === DataLoadingState.loadingMore}
rowCount={rowCount}
sampleSize={sampleSize}
@ -711,6 +804,8 @@ export const DiscoverGrid = ({
pageIndex={paginationObj?.pageIndex}
totalHits={totalHits}
onFetchMoreRecords={onFetchMoreRecords}
data={data}
fieldFormats={fieldFormats}
/>
)}
{searchTitle && (
@ -718,13 +813,13 @@ export const DiscoverGrid = ({
<p id={String(randomId)}>
{searchDescription ? (
<FormattedMessage
id="discover.searchGenerationWithDescriptionGrid"
id="unifiedDataTable.searchGenerationWithDescriptionGrid"
defaultMessage="Table generated by search {searchTitle} ({searchDescription})"
values={{ searchTitle, searchDescription }}
/>
) : (
<FormattedMessage
id="discover.searchGenerationWithDescription"
id="unifiedDataTable.searchGenerationWithDescription"
defaultMessage="Table generated by search {searchTitle}"
values={{ searchTitle }}
/>
@ -732,24 +827,10 @@ export const DiscoverGrid = ({
</p>
</EuiScreenReaderOnly>
)}
{setExpandedDoc && expandedDoc && DocumentView && (
<DocumentView
dataView={dataView}
hit={expandedDoc}
hits={displayedRows}
// if default columns are used, dont make them part of the URL - the context state handling will take care to restore them
columns={defaultColumns ? [] : displayedColumns}
filters={filters}
savedSearchId={savedSearchId}
onFilter={onFilter}
onRemoveColumn={onRemoveColumn}
onAddColumn={onAddColumn}
onClose={() => setExpandedDoc(undefined)}
setExpandedDoc={setExpandedDoc}
query={query}
/>
)}
{canSetExpandedDoc &&
expandedDoc &&
renderDocumentView!(expandedDoc, displayedRows, displayedColumns)}
</span>
</DiscoverGridContext.Provider>
</UnifiedDataTableContext.Provider>
);
};

View file

@ -7,10 +7,10 @@
*/
import { dataViewMock } from '@kbn/discover-utils/src/__mocks__';
import { getEuiGridColumns, getVisibleColumns } from './discover_grid_columns';
import { getEuiGridColumns, getVisibleColumns } from './data_table_columns';
import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefield';
import { discoverGridContextMock } from '../../__mocks__/grid_context';
import { discoverServiceMock } from '../../__mocks__/services';
import { dataTableContextMock } from '../../__mocks__/table_context';
import { servicesMock } from '../../__mocks__/services';
const columns = ['extension', 'message'];
const columnsWithTimeCol = getVisibleColumns(
@ -19,7 +19,7 @@ const columnsWithTimeCol = getVisibleColumns(
true
) as string[];
describe('Discover grid columns', function () {
describe('Data table columns', function () {
describe('getEuiGridColumns', () => {
it('returns eui grid columns showing default columns', async () => {
const actual = getEuiGridColumns({
@ -29,14 +29,14 @@ describe('Discover grid columns', function () {
defaultColumns: true,
isSortEnabled: true,
isPlainRecord: false,
valueToStringConverter: discoverGridContextMock.valueToStringConverter,
valueToStringConverter: dataTableContextMock.valueToStringConverter,
rowsCount: 100,
services: {
uiSettings: discoverServiceMock.uiSettings,
toastNotifications: discoverServiceMock.toastNotifications,
uiSettings: servicesMock.uiSettings,
toastNotifications: servicesMock.toastNotifications,
},
hasEditDataViewPermission: () =>
discoverServiceMock.dataViewFieldEditor.userPermissions.editIndexPattern(),
servicesMock.dataViewFieldEditor.userPermissions.editIndexPattern(),
onFilter: () => {},
});
expect(actual).toMatchInlineSnapshot(`
@ -52,7 +52,7 @@ describe('Discover grid columns', function () {
"iconType": "copyClipboard",
"label": <FormattedMessage
defaultMessage="Copy name"
id="discover.grid.copyColumnNameToClipBoardButton"
id="unifiedDataTable.grid.copyColumnNameToClipBoardButton"
values={Object {}}
/>,
"onClick": [Function],
@ -66,7 +66,7 @@ describe('Discover grid columns', function () {
"iconType": "copyClipboard",
"label": <FormattedMessage
defaultMessage="Copy column"
id="discover.grid.copyColumnValuesToClipBoardButton"
id="unifiedDataTable.grid.copyColumnValuesToClipBoardButton"
values={Object {}}
/>,
"onClick": [Function],
@ -86,6 +86,7 @@ describe('Discover grid columns', function () {
"id": "extension",
"isSortable": false,
"schema": "string",
"visibleCellActions": undefined,
},
Object {
"actions": Object {
@ -98,7 +99,7 @@ describe('Discover grid columns', function () {
"iconType": "copyClipboard",
"label": <FormattedMessage
defaultMessage="Copy name"
id="discover.grid.copyColumnNameToClipBoardButton"
id="unifiedDataTable.grid.copyColumnNameToClipBoardButton"
values={Object {}}
/>,
"onClick": [Function],
@ -112,7 +113,7 @@ describe('Discover grid columns', function () {
"iconType": "copyClipboard",
"label": <FormattedMessage
defaultMessage="Copy column"
id="discover.grid.copyColumnValuesToClipBoardButton"
id="unifiedDataTable.grid.copyColumnValuesToClipBoardButton"
values={Object {}}
/>,
"onClick": [Function],
@ -130,6 +131,7 @@ describe('Discover grid columns', function () {
"id": "message",
"isSortable": false,
"schema": "string",
"visibleCellActions": undefined,
},
]
`);
@ -143,14 +145,14 @@ describe('Discover grid columns', function () {
defaultColumns: false,
isSortEnabled: true,
isPlainRecord: false,
valueToStringConverter: discoverGridContextMock.valueToStringConverter,
valueToStringConverter: dataTableContextMock.valueToStringConverter,
rowsCount: 100,
services: {
uiSettings: discoverServiceMock.uiSettings,
toastNotifications: discoverServiceMock.toastNotifications,
uiSettings: servicesMock.uiSettings,
toastNotifications: servicesMock.toastNotifications,
},
hasEditDataViewPermission: () =>
discoverServiceMock.dataViewFieldEditor.userPermissions.editIndexPattern(),
servicesMock.dataViewFieldEditor.userPermissions.editIndexPattern(),
onFilter: () => {},
});
expect(actual).toMatchInlineSnapshot(`
@ -166,7 +168,7 @@ describe('Discover grid columns', function () {
"iconType": "copyClipboard",
"label": <FormattedMessage
defaultMessage="Copy name"
id="discover.grid.copyColumnNameToClipBoardButton"
id="unifiedDataTable.grid.copyColumnNameToClipBoardButton"
values={Object {}}
/>,
"onClick": [Function],
@ -180,7 +182,7 @@ describe('Discover grid columns', function () {
"iconType": "copyClipboard",
"label": <FormattedMessage
defaultMessage="Copy column"
id="discover.grid.copyColumnValuesToClipBoardButton"
id="unifiedDataTable.grid.copyColumnValuesToClipBoardButton"
values={Object {}}
/>,
"onClick": [Function],
@ -219,6 +221,7 @@ describe('Discover grid columns', function () {
"initialWidth": 210,
"isSortable": true,
"schema": "datetime",
"visibleCellActions": undefined,
},
Object {
"actions": Object {
@ -231,7 +234,7 @@ describe('Discover grid columns', function () {
"iconType": "copyClipboard",
"label": <FormattedMessage
defaultMessage="Copy name"
id="discover.grid.copyColumnNameToClipBoardButton"
id="unifiedDataTable.grid.copyColumnNameToClipBoardButton"
values={Object {}}
/>,
"onClick": [Function],
@ -245,7 +248,7 @@ describe('Discover grid columns', function () {
"iconType": "copyClipboard",
"label": <FormattedMessage
defaultMessage="Copy column"
id="discover.grid.copyColumnValuesToClipBoardButton"
id="unifiedDataTable.grid.copyColumnValuesToClipBoardButton"
values={Object {}}
/>,
"onClick": [Function],
@ -268,6 +271,7 @@ describe('Discover grid columns', function () {
"id": "extension",
"isSortable": false,
"schema": "string",
"visibleCellActions": undefined,
},
Object {
"actions": Object {
@ -280,7 +284,7 @@ describe('Discover grid columns', function () {
"iconType": "copyClipboard",
"label": <FormattedMessage
defaultMessage="Copy name"
id="discover.grid.copyColumnNameToClipBoardButton"
id="unifiedDataTable.grid.copyColumnNameToClipBoardButton"
values={Object {}}
/>,
"onClick": [Function],
@ -294,7 +298,7 @@ describe('Discover grid columns', function () {
"iconType": "copyClipboard",
"label": <FormattedMessage
defaultMessage="Copy column"
id="discover.grid.copyColumnValuesToClipBoardButton"
id="unifiedDataTable.grid.copyColumnValuesToClipBoardButton"
values={Object {}}
/>,
"onClick": [Function],
@ -315,6 +319,7 @@ describe('Discover grid columns', function () {
"id": "message",
"isSortable": false,
"schema": "string",
"visibleCellActions": undefined,
},
]
`);
@ -328,14 +333,14 @@ describe('Discover grid columns', function () {
defaultColumns: false,
isSortEnabled: true,
isPlainRecord: true,
valueToStringConverter: discoverGridContextMock.valueToStringConverter,
valueToStringConverter: dataTableContextMock.valueToStringConverter,
rowsCount: 100,
services: {
uiSettings: discoverServiceMock.uiSettings,
toastNotifications: discoverServiceMock.toastNotifications,
uiSettings: servicesMock.uiSettings,
toastNotifications: servicesMock.toastNotifications,
},
hasEditDataViewPermission: () =>
discoverServiceMock.dataViewFieldEditor.userPermissions.editIndexPattern(),
servicesMock.dataViewFieldEditor.userPermissions.editIndexPattern(),
onFilter: () => {},
});
expect(actual).toMatchInlineSnapshot(`
@ -351,7 +356,7 @@ describe('Discover grid columns', function () {
"iconType": "copyClipboard",
"label": <FormattedMessage
defaultMessage="Copy name"
id="discover.grid.copyColumnNameToClipBoardButton"
id="unifiedDataTable.grid.copyColumnNameToClipBoardButton"
values={Object {}}
/>,
"onClick": [Function],
@ -365,7 +370,7 @@ describe('Discover grid columns', function () {
"iconType": "copyClipboard",
"label": <FormattedMessage
defaultMessage="Copy column"
id="discover.grid.copyColumnValuesToClipBoardButton"
id="unifiedDataTable.grid.copyColumnValuesToClipBoardButton"
values={Object {}}
/>,
"onClick": [Function],
@ -404,6 +409,7 @@ describe('Discover grid columns', function () {
"initialWidth": 210,
"isSortable": true,
"schema": "datetime",
"visibleCellActions": undefined,
},
Object {
"actions": Object {
@ -416,7 +422,7 @@ describe('Discover grid columns', function () {
"iconType": "copyClipboard",
"label": <FormattedMessage
defaultMessage="Copy name"
id="discover.grid.copyColumnNameToClipBoardButton"
id="unifiedDataTable.grid.copyColumnNameToClipBoardButton"
values={Object {}}
/>,
"onClick": [Function],
@ -430,7 +436,7 @@ describe('Discover grid columns', function () {
"iconType": "copyClipboard",
"label": <FormattedMessage
defaultMessage="Copy column"
id="discover.grid.copyColumnValuesToClipBoardButton"
id="unifiedDataTable.grid.copyColumnValuesToClipBoardButton"
values={Object {}}
/>,
"onClick": [Function],
@ -453,6 +459,7 @@ describe('Discover grid columns', function () {
"id": "extension",
"isSortable": true,
"schema": "string",
"visibleCellActions": undefined,
},
Object {
"actions": Object {
@ -465,7 +472,7 @@ describe('Discover grid columns', function () {
"iconType": "copyClipboard",
"label": <FormattedMessage
defaultMessage="Copy name"
id="discover.grid.copyColumnNameToClipBoardButton"
id="unifiedDataTable.grid.copyColumnNameToClipBoardButton"
values={Object {}}
/>,
"onClick": [Function],
@ -479,7 +486,7 @@ describe('Discover grid columns', function () {
"iconType": "copyClipboard",
"label": <FormattedMessage
defaultMessage="Copy column"
id="discover.grid.copyColumnValuesToClipBoardButton"
id="unifiedDataTable.grid.copyColumnValuesToClipBoardButton"
values={Object {}}
/>,
"onClick": [Function],
@ -500,6 +507,7 @@ describe('Discover grid columns', function () {
"id": "message",
"isSortable": true,
"schema": "string",
"visibleCellActions": undefined,
},
]
`);

View file

@ -17,14 +17,14 @@ import {
} from '@elastic/eui';
import type { DataView } from '@kbn/data-views-plugin/public';
import { ToastsStart, IUiSettingsClient } from '@kbn/core/public';
import type { DocViewFilterFn } from '@kbn/unified-doc-viewer/types';
import { ExpandButton } from './discover_grid_expand_button';
import { DiscoverGridSettings } from './types';
import type { ValueToStringConverter } from '../../types';
import { buildCellActions } from './discover_grid_cell_actions';
import { getSchemaByKbnType } from './discover_grid_schema';
import { SelectButton } from './discover_grid_document_selection';
import { defaultTimeColumnWidth } from './constants';
import { DocViewFilterFn } from '@kbn/unified-doc-viewer/types';
import { ExpandButton } from './data_table_expand_button';
import { UnifiedDataTableSettings } from '../types';
import type { ValueToStringConverter } from '../types';
import { buildCellActions } from './default_cell_actions';
import { getSchemaByKbnType } from './data_table_schema';
import { SelectButton } from './data_table_document_selection';
import { defaultTimeColumnWidth } from '../constants';
import { buildCopyColumnNameButton, buildCopyColumnValuesButton } from './build_copy_column_button';
import { buildEditFieldButton } from './build_edit_field_button';
@ -34,7 +34,7 @@ const openDetails = {
headerCellRender: () => (
<EuiScreenReaderOnly>
<span>
{i18n.translate('discover.controlColumnHeader', {
{i18n.translate('unifiedDataTable.controlColumnHeader', {
defaultMessage: 'Control column',
})}
</span>
@ -50,7 +50,7 @@ const select = {
headerCellRender: () => (
<EuiScreenReaderOnly>
<span>
{i18n.translate('discover.selectColumnHeader', {
{i18n.translate('unifiedDataTable.selectColumnHeader', {
defaultMessage: 'Select column',
})}
</span>
@ -79,6 +79,7 @@ function buildEuiGridColumn({
onFilter,
editField,
columnCellActions,
visibleCellActions,
}: {
columnName: string;
columnWidth: number | undefined;
@ -93,6 +94,7 @@ function buildEuiGridColumn({
onFilter?: DocViewFilterFn;
editField?: (fieldName: string) => void;
columnCellActions?: EuiDataGridColumnCellAction[];
visibleCellActions?: number;
}) {
const dataViewField = dataView.getFieldByName(columnName);
const editFieldButton =
@ -101,16 +103,19 @@ function buildEuiGridColumn({
buildEditFieldButton({ hasEditDataViewPermission, dataView, field: dataViewField, editField });
const columnDisplayName =
columnName === '_source'
? i18n.translate('discover.grid.documentHeader', {
? i18n.translate('unifiedDataTable.grid.documentHeader', {
defaultMessage: 'Document',
})
: dataViewField?.displayName || columnName;
let cellActions: EuiDataGridColumnCellAction[];
if (columnCellActions?.length) {
cellActions = columnCellActions;
} else {
cellActions = dataViewField ? buildCellActions(dataViewField, onFilter) : [];
cellActions = dataViewField
? buildCellActions(dataViewField, toastNotifications, valueToStringConverter, onFilter)
: [];
}
const column: EuiDataGridColumn = {
@ -123,7 +128,7 @@ function buildEuiGridColumn({
defaultColumns || columnName === dataView.timeFieldName
? false
: {
label: i18n.translate('discover.removeColumnLabel', {
label: i18n.translate('unifiedDataTable.removeColumnLabel', {
defaultMessage: 'Remove column',
}),
iconType: 'cross',
@ -150,23 +155,21 @@ function buildEuiGridColumn({
],
},
cellActions,
visibleCellActions,
};
if (column.id === dataView.timeFieldName) {
const timeFieldName = dataViewField?.customLabel ?? dataView.timeFieldName;
const primaryTimeAriaLabel = i18n.translate(
'discover.docTable.tableHeader.timeFieldIconTooltipAriaLabel',
'unifiedDataTable.tableHeader.timeFieldIconTooltipAriaLabel',
{
defaultMessage: '{timeFieldName} - this field represents the time that events occurred.',
values: { timeFieldName },
}
);
const primaryTimeTooltip = i18n.translate(
'discover.docTable.tableHeader.timeFieldIconTooltip',
{
defaultMessage: 'This field represents the time that events occurred.',
}
);
const primaryTimeTooltip = i18n.translate('unifiedDataTable.tableHeader.timeFieldIconTooltip', {
defaultMessage: 'This field represents the time that events occurred.',
});
column.display = (
<div aria-label={primaryTimeAriaLabel}>
@ -199,11 +202,12 @@ export function getEuiGridColumns({
valueToStringConverter,
onFilter,
editField,
visibleCellActions,
}: {
columns: string[];
columnsCellActions?: EuiDataGridColumnCellAction[][];
rowsCount: number;
settings: DiscoverGridSettings | undefined;
settings: UnifiedDataTableSettings | undefined;
dataView: DataView;
defaultColumns: boolean;
isSortEnabled: boolean;
@ -216,6 +220,7 @@ export function getEuiGridColumns({
valueToStringConverter: ValueToStringConverter;
onFilter: DocViewFilterFn;
editField?: (fieldName: string) => void;
visibleCellActions?: number;
}) {
const getColWidth = (column: string) => settings?.columns?.[column]?.width ?? 0;
@ -234,6 +239,7 @@ export function getEuiGridColumns({
rowsCount,
onFilter,
editField,
visibleCellActions,
})
);
}

View file

@ -8,9 +8,9 @@
import React from 'react';
import { mountWithIntl } from '@kbn/test-jest-helpers';
import { findTestSubject } from '@elastic/eui/lib/test';
import { DiscoverGridDocumentToolbarBtn, SelectButton } from './discover_grid_document_selection';
import { discoverGridContextMock } from '../../__mocks__/grid_context';
import { DiscoverGridContext } from './discover_grid_context';
import { DataTableDocumentToolbarBtn, SelectButton } from './data_table_document_selection';
import { dataTableContextMock } from '../../__mocks__/table_context';
import { UnifiedDataTableContext } from '../table_context';
import { getDocId } from '@kbn/discover-utils';
describe('document selection', () => {
@ -35,11 +35,11 @@ describe('document selection', () => {
describe('SelectButton', () => {
test('is not checked', () => {
const contextMock = {
...discoverGridContextMock,
...dataTableContextMock,
};
const component = mountWithIntl(
<DiscoverGridContext.Provider value={contextMock}>
<UnifiedDataTableContext.Provider value={contextMock}>
<SelectButton
rowIndex={0}
colIndex={0}
@ -49,7 +49,7 @@ describe('document selection', () => {
isDetails={false}
isExpandable={false}
/>
</DiscoverGridContext.Provider>
</UnifiedDataTableContext.Provider>
);
const checkBox = findTestSubject(component, 'dscGridSelectDoc-i::1::');
@ -58,12 +58,12 @@ describe('document selection', () => {
test('is checked', () => {
const contextMock = {
...discoverGridContextMock,
...dataTableContextMock,
selectedDocs: ['i::1::'],
};
const component = mountWithIntl(
<DiscoverGridContext.Provider value={contextMock}>
<UnifiedDataTableContext.Provider value={contextMock}>
<SelectButton
rowIndex={0}
colIndex={0}
@ -73,7 +73,7 @@ describe('document selection', () => {
isDetails={false}
isExpandable={false}
/>
</DiscoverGridContext.Provider>
</UnifiedDataTableContext.Provider>
);
const checkBox = findTestSubject(component, 'dscGridSelectDoc-i::1::');
@ -82,11 +82,11 @@ describe('document selection', () => {
test('adding a selection', () => {
const contextMock = {
...discoverGridContextMock,
...dataTableContextMock,
};
const component = mountWithIntl(
<DiscoverGridContext.Provider value={contextMock}>
<UnifiedDataTableContext.Provider value={contextMock}>
<SelectButton
rowIndex={0}
colIndex={0}
@ -96,7 +96,7 @@ describe('document selection', () => {
isDetails={false}
isExpandable={false}
/>
</DiscoverGridContext.Provider>
</UnifiedDataTableContext.Provider>
);
const checkBox = findTestSubject(component, 'dscGridSelectDoc-i::1::');
@ -105,12 +105,12 @@ describe('document selection', () => {
});
test('removing a selection', () => {
const contextMock = {
...discoverGridContextMock,
...dataTableContextMock,
selectedDocs: ['i::1::'],
};
const component = mountWithIntl(
<DiscoverGridContext.Provider value={contextMock}>
<UnifiedDataTableContext.Provider value={contextMock}>
<SelectButton
rowIndex={0}
colIndex={0}
@ -120,7 +120,7 @@ describe('document selection', () => {
isDetails={false}
isExpandable={false}
/>
</DiscoverGridContext.Provider>
</UnifiedDataTableContext.Provider>
);
const checkBox = findTestSubject(component, 'dscGridSelectDoc-i::1::');
@ -128,16 +128,16 @@ describe('document selection', () => {
expect(contextMock.setSelectedDocs).toHaveBeenCalledWith([]);
});
});
describe('DiscoverGridDocumentToolbarBtn', () => {
describe('DataTableDocumentToolbarBtn', () => {
test('it renders a button clickable button', () => {
const props = {
isFilterActive: false,
rows: discoverGridContextMock.rows,
rows: dataTableContextMock.rows,
selectedDocs: ['i::1::'],
setIsFilterActive: jest.fn(),
setSelectedDocs: jest.fn(),
};
const component = mountWithIntl(<DiscoverGridDocumentToolbarBtn {...props} />);
const component = mountWithIntl(<DataTableDocumentToolbarBtn {...props} />);
const button = findTestSubject(component, 'dscGridSelectionBtn');
expect(button.length).toBe(1);
});

View file

@ -20,15 +20,15 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { euiDarkVars as themeDark, euiLightVars as themeLight } from '@kbn/ui-theme';
import { i18n } from '@kbn/i18n';
import type { DataTableRecord } from '@kbn/discover-utils/types';
import { DiscoverGridContext } from './discover_grid_context';
import { UnifiedDataTableContext } from '../table_context';
export const SelectButton = ({ rowIndex, setCellProps }: EuiDataGridCellValueElementProps) => {
const { selectedDocs, expanded, rows, isDarkMode, setSelectedDocs } =
useContext(DiscoverGridContext);
useContext(UnifiedDataTableContext);
const doc = useMemo(() => rows[rowIndex], [rows, rowIndex]);
const checked = useMemo(() => selectedDocs.includes(doc.id), [selectedDocs, doc.id]);
const toggleDocumentSelectionLabel = i18n.translate('discover.grid.selectDoc', {
const toggleDocumentSelectionLabel = i18n.translate('unifiedDataTable.grid.selectDoc', {
defaultMessage: `Select document '{rowNumber}'`,
values: { rowNumber: rowIndex + 1 },
});
@ -63,7 +63,7 @@ export const SelectButton = ({ rowIndex, setCellProps }: EuiDataGridCellValueEle
);
};
export function DiscoverGridDocumentToolbarBtn({
export function DataTableDocumentToolbarBtn({
isFilterActive,
rows,
selectedDocs,
@ -90,7 +90,10 @@ export function DiscoverGridDocumentToolbarBtn({
setIsFilterActive(false);
}}
>
<FormattedMessage id="discover.showAllDocuments" defaultMessage="Show all documents" />
<FormattedMessage
id="unifiedDataTable.showAllDocuments"
defaultMessage="Show all documents"
/>
</EuiContextMenuItem>
) : (
<EuiContextMenuItem
@ -103,7 +106,7 @@ export function DiscoverGridDocumentToolbarBtn({
}}
>
<FormattedMessage
id="discover.showSelectedDocumentsOnly"
id="unifiedDataTable.showSelectedDocumentsOnly"
defaultMessage="Show selected documents only"
/>
</EuiContextMenuItem>
@ -122,7 +125,7 @@ export function DiscoverGridDocumentToolbarBtn({
{(copy) => (
<EuiContextMenuItem key="copyJSON" icon="copyClipboard" onClick={copy}>
<FormattedMessage
id="discover.copyToClipboardJSON"
id="unifiedDataTable.copyToClipboardJSON"
defaultMessage="Copy documents to clipboard (JSON)"
/>
</EuiContextMenuItem>
@ -138,7 +141,7 @@ export function DiscoverGridDocumentToolbarBtn({
setIsFilterActive(false);
}}
>
<FormattedMessage id="discover.clearSelection" defaultMessage="Clear selection" />
<FormattedMessage id="unifiedDataTable.clearSelection" defaultMessage="Clear selection" />
</EuiContextMenuItem>,
];
}, [
@ -176,7 +179,7 @@ export function DiscoverGridDocumentToolbarBtn({
})}
>
<FormattedMessage
id="discover.selectedDocumentsNumber"
id="unifiedDataTable.selectedDocumentsNumber"
defaultMessage="{nr} documents selected"
values={{ nr: selectedDocs.length }}
/>

View file

@ -9,18 +9,18 @@
import React from 'react';
import { mountWithIntl } from '@kbn/test-jest-helpers';
import { findTestSubject } from '@elastic/eui/lib/test';
import { ExpandButton } from './discover_grid_expand_button';
import { DiscoverGridContext } from './discover_grid_context';
import { discoverGridContextMock } from '../../__mocks__/grid_context';
import { ExpandButton } from './data_table_expand_button';
import { UnifiedDataTableContext } from '../table_context';
import { dataTableContextMock } from '../../__mocks__/table_context';
describe('Discover grid view button ', function () {
describe('Data table view button ', function () {
it('when no document is expanded, setExpanded is called with current document', async () => {
const contextMock = {
...discoverGridContextMock,
...dataTableContextMock,
};
const component = mountWithIntl(
<DiscoverGridContext.Provider value={contextMock}>
<UnifiedDataTableContext.Provider value={contextMock}>
<ExpandButton
rowIndex={0}
colIndex={0}
@ -30,20 +30,20 @@ describe('Discover grid view button ', function () {
isDetails={false}
isExpandable={false}
/>
</DiscoverGridContext.Provider>
</UnifiedDataTableContext.Provider>
);
const button = findTestSubject(component, 'docTableExpandToggleColumn');
await button.simulate('click');
expect(contextMock.setExpanded).toHaveBeenCalledWith(discoverGridContextMock.rows[0]);
expect(contextMock.setExpanded).toHaveBeenCalledWith(dataTableContextMock.rows[0]);
});
it('when the current document is expanded, setExpanded is called with undefined', async () => {
const contextMock = {
...discoverGridContextMock,
expanded: discoverGridContextMock.rows[0],
...dataTableContextMock,
expanded: dataTableContextMock.rows[0],
};
const component = mountWithIntl(
<DiscoverGridContext.Provider value={contextMock}>
<UnifiedDataTableContext.Provider value={contextMock}>
<ExpandButton
rowIndex={0}
colIndex={0}
@ -53,7 +53,7 @@ describe('Discover grid view button ', function () {
isDetails={false}
isExpandable={false}
/>
</DiscoverGridContext.Provider>
</UnifiedDataTableContext.Provider>
);
const button = findTestSubject(component, 'docTableExpandToggleColumn');
await button.simulate('click');
@ -61,12 +61,12 @@ describe('Discover grid view button ', function () {
});
it('when another document is expanded, setExpanded is called with the current document', async () => {
const contextMock = {
...discoverGridContextMock,
expanded: discoverGridContextMock.rows[0],
...dataTableContextMock,
expanded: dataTableContextMock.rows[0],
};
const component = mountWithIntl(
<DiscoverGridContext.Provider value={contextMock}>
<UnifiedDataTableContext.Provider value={contextMock}>
<ExpandButton
rowIndex={1}
colIndex={0}
@ -76,10 +76,10 @@ describe('Discover grid view button ', function () {
isDetails={false}
isExpandable={false}
/>
</DiscoverGridContext.Provider>
</UnifiedDataTableContext.Provider>
);
const button = findTestSubject(component, 'docTableExpandToggleColumn');
await button.simulate('click');
expect(contextMock.setExpanded).toHaveBeenCalledWith(discoverGridContextMock.rows[1]);
expect(contextMock.setExpanded).toHaveBeenCalledWith(dataTableContextMock.rows[1]);
});
});

View file

@ -10,8 +10,7 @@ import React, { useContext, useEffect, useRef, useState } from 'react';
import { EuiButtonIcon, EuiDataGridCellValueElementProps, EuiToolTip } from '@elastic/eui';
import { euiLightVars as themeLight, euiDarkVars as themeDark } from '@kbn/ui-theme';
import { i18n } from '@kbn/i18n';
import { DiscoverGridContext } from './discover_grid_context';
import { DISCOVER_TOUR_STEP_ANCHOR_IDS } from '../discover_tour';
import { UnifiedDataTableContext } from '../table_context';
/**
* Button to expand a given row
@ -19,9 +18,11 @@ import { DISCOVER_TOUR_STEP_ANCHOR_IDS } from '../discover_tour';
export const ExpandButton = ({ rowIndex, setCellProps }: EuiDataGridCellValueElementProps) => {
const toolTipRef = useRef<EuiToolTip>(null);
const [pressed, setPressed] = useState<boolean>(false);
const { expanded, setExpanded, rows, isDarkMode } = useContext(DiscoverGridContext);
const { expanded, setExpanded, rows, isDarkMode, componentsTourSteps } =
useContext(UnifiedDataTableContext);
const current = rows[rowIndex];
const tourStep = componentsTourSteps ? componentsTourSteps.expandButton : undefined;
useEffect(() => {
if (current.isAnchor) {
setCellProps({
@ -39,7 +40,7 @@ export const ExpandButton = ({ rowIndex, setCellProps }: EuiDataGridCellValueEle
}, [expanded, current, setCellProps, isDarkMode]);
const isCurrentRowExpanded = current === expanded;
const buttonLabel = i18n.translate('discover.grid.viewDoc', {
const buttonLabel = i18n.translate('unifiedDataTable.grid.viewDoc', {
defaultMessage: 'Toggle dialog with details',
});
@ -63,7 +64,7 @@ export const ExpandButton = ({ rowIndex, setCellProps }: EuiDataGridCellValueEle
return (
<EuiToolTip content={buttonLabel} delay="long" ref={toolTipRef}>
<EuiButtonIcon
id={rowIndex === 0 ? DISCOVER_TOUR_STEP_ANCHOR_IDS.expandDocument : undefined}
id={rowIndex === 0 ? tourStep : undefined}
size="xs"
iconSize="s"
aria-label={buttonLabel}

View file

@ -9,20 +9,22 @@
import React from 'react';
import { mountWithIntl } from '@kbn/test-jest-helpers';
import { findTestSubject } from '@elastic/eui/lib/test';
import { UnifiedDataTableFooter } from './data_table_footer';
import { servicesMock } from '../../__mocks__/services';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import { DiscoverGridFooter } from './discover_grid_footer';
import { discoverServiceMock } from '../../__mocks__/services';
describe('DiscoverGridFooter', function () {
describe('UnifiedDataTableFooter', function () {
it('should not render anything when not on the last page', async () => {
const component = mountWithIntl(
<KibanaContextProvider services={discoverServiceMock}>
<DiscoverGridFooter
<KibanaContextProvider services={servicesMock}>
<UnifiedDataTableFooter
pageCount={5}
pageIndex={0}
sampleSize={500}
totalHits={1000}
rowCount={500}
data={servicesMock.data}
fieldFormats={servicesMock.fieldFormats}
/>
</KibanaContextProvider>
);
@ -31,32 +33,32 @@ describe('DiscoverGridFooter', function () {
it('should not render anything yet when all rows shown', async () => {
const component = mountWithIntl(
<KibanaContextProvider services={discoverServiceMock}>
<DiscoverGridFooter
pageCount={5}
pageIndex={4}
sampleSize={500}
totalHits={500}
rowCount={500}
/>
</KibanaContextProvider>
<UnifiedDataTableFooter
pageCount={5}
pageIndex={4}
sampleSize={500}
totalHits={500}
rowCount={500}
data={servicesMock.data}
fieldFormats={servicesMock.fieldFormats}
/>
);
expect(component.isEmptyRender()).toBe(true);
});
it('should render a message for the last page', async () => {
const component = mountWithIntl(
<KibanaContextProvider services={discoverServiceMock}>
<DiscoverGridFooter
pageCount={5}
pageIndex={4}
sampleSize={500}
totalHits={1000}
rowCount={500}
/>
</KibanaContextProvider>
<UnifiedDataTableFooter
pageCount={5}
pageIndex={4}
sampleSize={500}
totalHits={1000}
rowCount={500}
data={servicesMock.data}
fieldFormats={servicesMock.fieldFormats}
/>
);
expect(findTestSubject(component, 'discoverTableFooter').text()).toBe(
expect(findTestSubject(component, 'unifiedDataTableFooter').text()).toBe(
'Search results are limited to 500 documents. Add more search terms to narrow your search.'
);
expect(findTestSubject(component, 'dscGridSampleSizeFetchMoreLink').exists()).toBe(false);
@ -66,19 +68,19 @@ describe('DiscoverGridFooter', function () {
const mockLoadMore = jest.fn();
const component = mountWithIntl(
<KibanaContextProvider services={discoverServiceMock}>
<DiscoverGridFooter
pageCount={5}
pageIndex={4}
sampleSize={500}
totalHits={1000}
rowCount={500}
isLoadingMore={false}
onFetchMoreRecords={mockLoadMore}
/>
</KibanaContextProvider>
<UnifiedDataTableFooter
pageCount={5}
pageIndex={4}
sampleSize={500}
totalHits={1000}
rowCount={500}
isLoadingMore={false}
onFetchMoreRecords={mockLoadMore}
data={servicesMock.data}
fieldFormats={servicesMock.fieldFormats}
/>
);
expect(findTestSubject(component, 'discoverTableFooter').text()).toBe(
expect(findTestSubject(component, 'unifiedDataTableFooter').text()).toBe(
'Search results are limited to 500 documents.Load more'
);
@ -94,19 +96,19 @@ describe('DiscoverGridFooter', function () {
const mockLoadMore = jest.fn();
const component = mountWithIntl(
<KibanaContextProvider services={discoverServiceMock}>
<DiscoverGridFooter
pageCount={5}
pageIndex={4}
sampleSize={500}
totalHits={1000}
rowCount={500}
isLoadingMore={true}
onFetchMoreRecords={mockLoadMore}
/>
</KibanaContextProvider>
<UnifiedDataTableFooter
pageCount={5}
pageIndex={4}
sampleSize={500}
totalHits={1000}
rowCount={500}
isLoadingMore={true}
onFetchMoreRecords={mockLoadMore}
data={servicesMock.data}
fieldFormats={servicesMock.fieldFormats}
/>
);
expect(findTestSubject(component, 'discoverTableFooter').text()).toBe(
expect(findTestSubject(component, 'unifiedDataTableFooter').text()).toBe(
'Search results are limited to 500 documents.Load more'
);
@ -121,17 +123,17 @@ describe('DiscoverGridFooter', function () {
it('should render a message when max total limit is reached', async () => {
const component = mountWithIntl(
<KibanaContextProvider services={discoverServiceMock}>
<DiscoverGridFooter
pageCount={100}
pageIndex={99}
sampleSize={500}
totalHits={11000}
rowCount={10000}
/>
</KibanaContextProvider>
<UnifiedDataTableFooter
pageCount={100}
pageIndex={99}
sampleSize={500}
totalHits={11000}
rowCount={10000}
data={servicesMock.data}
fieldFormats={servicesMock.fieldFormats}
/>
);
expect(findTestSubject(component, 'discoverTableFooter').text()).toBe(
expect(findTestSubject(component, 'unifiedDataTableFooter').text()).toBe(
'Search results are limited to 10000 documents. Add more search terms to narrow your search.'
);
});

View file

@ -12,10 +12,11 @@ import { EuiButtonEmpty, EuiToolTip, useEuiTheme } from '@elastic/eui';
import { css } from '@emotion/react';
import { i18n } from '@kbn/i18n';
import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '@kbn/field-types';
import { MAX_LOADED_GRID_ROWS } from '../../../common/constants';
import { useDiscoverServices } from '../../hooks/use_discover_services';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import { MAX_LOADED_GRID_ROWS } from '../constants';
export interface DiscoverGridFooterProps {
export interface UnifiedDataTableFooterProps {
isLoadingMore?: boolean;
rowCount: number;
sampleSize: number;
@ -23,9 +24,11 @@ export interface DiscoverGridFooterProps {
pageCount: number;
totalHits?: number;
onFetchMoreRecords?: () => void;
data: DataPublicPluginStart;
fieldFormats: FieldFormatsStart;
}
export const DiscoverGridFooter: React.FC<DiscoverGridFooterProps> = (props) => {
export const UnifiedDataTableFooter: React.FC<UnifiedDataTableFooterProps> = (props) => {
const {
isLoadingMore,
rowCount,
@ -34,8 +37,8 @@ export const DiscoverGridFooter: React.FC<DiscoverGridFooterProps> = (props) =>
pageCount,
totalHits = 0,
onFetchMoreRecords,
data,
} = props;
const { data } = useDiscoverServices();
const timefilter = data.query.timefilter.timefilter;
const [refreshInterval, setRefreshInterval] = useState(timefilter.getRefreshInterval());
@ -58,15 +61,15 @@ export const DiscoverGridFooter: React.FC<DiscoverGridFooterProps> = (props) =>
return null;
}
// allow to fetch more records on Discover page
// allow to fetch more records for UnifiedDataTable
if (onFetchMoreRecords && typeof isLoadingMore === 'boolean') {
if (rowCount <= MAX_LOADED_GRID_ROWS - sampleSize) {
return (
<DiscoverGridFooterContainer hasButton={true} {...props}>
<UnifiedDataTableFooterContainer hasButton={true} {...props}>
<EuiToolTip
content={
isRefreshIntervalOn
? i18n.translate('discover.gridSampleSize.fetchMoreLinkDisabledTooltip', {
? i18n.translate('unifiedDataTable.gridSampleSize.fetchMoreLinkDisabledTooltip', {
defaultMessage: 'To load more the refresh interval needs to be disabled first',
})
: undefined
@ -83,37 +86,37 @@ export const DiscoverGridFooter: React.FC<DiscoverGridFooterProps> = (props) =>
`}
>
<FormattedMessage
id="discover.gridSampleSize.fetchMoreLinkLabel"
id="unifiedDataTable.gridSampleSize.fetchMoreLinkLabel"
defaultMessage="Load more"
/>
</EuiButtonEmpty>
</EuiToolTip>
</DiscoverGridFooterContainer>
</UnifiedDataTableFooterContainer>
);
}
return <DiscoverGridFooterContainer hasButton={false} {...props} />;
return <UnifiedDataTableFooterContainer hasButton={false} {...props} />;
}
if (rowCount < totalHits) {
// show only a message for embeddable
return <DiscoverGridFooterContainer hasButton={false} {...props} />;
return <UnifiedDataTableFooterContainer hasButton={false} {...props} />;
}
return null;
};
interface DiscoverGridFooterContainerProps extends DiscoverGridFooterProps {
interface UnifiedDataTableFooterContainerProps extends UnifiedDataTableFooterProps {
hasButton: boolean;
}
const DiscoverGridFooterContainer: React.FC<DiscoverGridFooterContainerProps> = ({
const UnifiedDataTableFooterContainer: React.FC<UnifiedDataTableFooterContainerProps> = ({
hasButton,
rowCount,
children,
fieldFormats,
}) => {
const { euiTheme } = useEuiTheme();
const { fieldFormats } = useDiscoverServices();
const formattedRowCount = fieldFormats
.getDefaultInstance(KBN_FIELD_TYPES.NUMBER, [ES_FIELD_TYPES.INTEGER])
@ -121,7 +124,7 @@ const DiscoverGridFooterContainer: React.FC<DiscoverGridFooterContainerProps> =
return (
<p
data-test-subj="discoverTableFooter"
data-test-subj="unifiedDataTableFooter"
css={css`
display: flex;
flex-direction: row;
@ -139,7 +142,7 @@ const DiscoverGridFooterContainer: React.FC<DiscoverGridFooterContainerProps> =
<span>
{hasButton ? (
<FormattedMessage
id="discover.gridSampleSize.lastPageDescription"
id="unifiedDataTable.gridSampleSize.lastPageDescription"
defaultMessage="Search results are limited to {rowCount} documents."
values={{
rowCount: formattedRowCount,
@ -147,7 +150,7 @@ const DiscoverGridFooterContainer: React.FC<DiscoverGridFooterContainerProps> =
/>
) : (
<FormattedMessage
id="discover.gridSampleSize.limitDescription"
id="unifiedDataTable.gridSampleSize.limitDescription"
defaultMessage="Search results are limited to {sampleSize} documents. Add more search terms to narrow your search."
values={{
sampleSize: formattedRowCount,

View file

@ -6,8 +6,8 @@
* Side Public License, v 1.
*/
import { KBN_FIELD_TYPES } from '@kbn/data-plugin/public';
import { kibanaJSON } from './constants';
import { KBN_FIELD_TYPES } from '@kbn/field-types';
import { kibanaJSON } from '../constants';
export function getSchemaByKbnType(kbnType: string | undefined) {
// Default DataGrid schemas: boolean, numeric, datetime, json, currency, string

View file

@ -0,0 +1,184 @@
/*
* 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 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 or the Server
* Side Public License, v 1.
*/
const mockCopyToClipboard = jest.fn((value) => true);
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
...original,
copyToClipboard: (value: string) => mockCopyToClipboard(value),
};
});
import React from 'react';
import { mountWithIntl } from '@kbn/test-jest-helpers';
import { findTestSubject } from '@elastic/eui/lib/test';
import {
FilterInBtn,
FilterOutBtn,
buildCellActions,
buildCopyValueButton,
} from './default_cell_actions';
import { servicesMock } from '../../__mocks__/services';
import { UnifiedDataTableContext } from '../table_context';
import { EuiButton, EuiDataGridColumnCellActionProps } from '@elastic/eui';
import { dataTableContextMock } from '../../__mocks__/table_context';
import { DataViewField } from '@kbn/data-views-plugin/public';
describe('Default cell actions ', function () {
const CopyBtn = buildCopyValueButton(
{
Component: () => <></>,
colIndex: 0,
columnId: 'extension',
} as unknown as EuiDataGridColumnCellActionProps,
servicesMock.toastNotifications,
dataTableContextMock.valueToStringConverter
);
it('should not show cell actions for unfilterable fields', async () => {
const cellActions = buildCellActions(
{ name: 'foo', filterable: false } as DataViewField,
servicesMock.toastNotifications,
dataTableContextMock.valueToStringConverter
);
expect(cellActions.length).toEqual(1);
expect(
cellActions[0]({
Component: () => <></>,
colIndex: 1,
columnId: 'extension',
} as unknown as EuiDataGridColumnCellActionProps).props['aria-label']
).toEqual(CopyBtn.props['aria-label']);
});
it('should show filter actions for filterable fields', async () => {
const cellActions = buildCellActions(
{ name: 'foo', filterable: true } as DataViewField,
servicesMock.toastNotifications,
dataTableContextMock.valueToStringConverter,
jest.fn()
);
expect(cellActions).toContain(FilterInBtn);
expect(cellActions).toContain(FilterOutBtn);
});
it('should show Copy action for _source field', async () => {
const cellActions = buildCellActions(
{ name: '_source', type: '_source', filterable: false } as DataViewField,
servicesMock.toastNotifications,
dataTableContextMock.valueToStringConverter
);
expect(
cellActions[0]({
Component: () => <></>,
colIndex: 1,
columnId: 'extension',
} as unknown as EuiDataGridColumnCellActionProps).props['aria-label']
).toEqual(CopyBtn.props['aria-label']);
});
it('triggers filter function when FilterInBtn is clicked', async () => {
const component = mountWithIntl(
<UnifiedDataTableContext.Provider value={dataTableContextMock}>
<FilterInBtn
Component={(props: any) => <EuiButton {...props} />}
rowIndex={1}
colIndex={1}
columnId="extension"
isExpanded={false}
/>
</UnifiedDataTableContext.Provider>
);
const button = findTestSubject(component, 'filterForButton');
await button.simulate('click');
expect(dataTableContextMock.onFilter).toHaveBeenCalledWith(
dataTableContextMock.dataView.fields.getByName('extension'),
'jpg',
'+'
);
});
it('triggers filter function when FilterInBtn is clicked for a non-provided value', async () => {
const component = mountWithIntl(
<UnifiedDataTableContext.Provider value={dataTableContextMock}>
<FilterInBtn
Component={(props: any) => <EuiButton {...props} />}
rowIndex={0}
colIndex={1}
columnId="extension"
isExpanded={false}
/>
</UnifiedDataTableContext.Provider>
);
const button = findTestSubject(component, 'filterForButton');
await button.simulate('click');
expect(dataTableContextMock.onFilter).toHaveBeenCalledWith(
dataTableContextMock.dataView.fields.getByName('extension'),
undefined,
'+'
);
});
it('triggers filter function when FilterInBtn is clicked for an empty string value', async () => {
const component = mountWithIntl(
<UnifiedDataTableContext.Provider value={dataTableContextMock}>
<FilterInBtn
Component={(props: any) => <EuiButton {...props} />}
rowIndex={4}
colIndex={1}
columnId="message"
isExpanded={false}
/>
</UnifiedDataTableContext.Provider>
);
const button = findTestSubject(component, 'filterForButton');
await button.simulate('click');
expect(dataTableContextMock.onFilter).toHaveBeenCalledWith(
dataTableContextMock.dataView.fields.getByName('message'),
'',
'+'
);
});
it('triggers filter function when FilterOutBtn is clicked', async () => {
const component = mountWithIntl(
<UnifiedDataTableContext.Provider value={dataTableContextMock}>
<FilterOutBtn
Component={(props: any) => <EuiButton {...props} />}
rowIndex={1}
colIndex={1}
columnId="extension"
isExpanded={false}
/>
</UnifiedDataTableContext.Provider>
);
const button = findTestSubject(component, 'filterOutButton');
await button.simulate('click');
expect(dataTableContextMock.onFilter).toHaveBeenCalledWith(
dataTableContextMock.dataView.fields.getByName('extension'),
'jpg',
'-'
);
});
it('triggers clipboard copy when CopyBtn is clicked', async () => {
const component = mountWithIntl(
<UnifiedDataTableContext.Provider value={dataTableContextMock}>
{buildCopyValueButton(
{
Component: (props: any) => <EuiButton {...props} />,
colIndex: 1,
rowIndex: 1,
columnId: 'extension',
} as unknown as EuiDataGridColumnCellActionProps,
servicesMock.toastNotifications,
dataTableContextMock.valueToStringConverter
)}
</UnifiedDataTableContext.Provider>
);
const button = findTestSubject(component, 'copyClipboardButton');
await button.simulate('click');
expect(mockCopyToClipboard).toHaveBeenCalledWith('jpg');
});
});

View file

@ -9,14 +9,15 @@
import React, { useContext } from 'react';
import { EuiDataGridColumnCellActionProps } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { DataViewField } from '@kbn/data-views-plugin/public';
import type { DataViewField } from '@kbn/data-views-plugin/public';
import { ToastsStart } from '@kbn/core/public';
import type { DocViewFilterFn } from '@kbn/unified-doc-viewer/types';
import { DiscoverGridContext, GridContext } from './discover_grid_context';
import { useDiscoverServices } from '../../hooks/use_discover_services';
import { copyValueToClipboard } from '../../utils/copy_value_to_clipboard';
import { UnifiedDataTableContext, DataTableContext } from '../table_context';
import { copyValueToClipboard } from '../utils/copy_value_to_clipboard';
import { ValueToStringConverter } from '../types';
function onFilterCell(
context: GridContext,
context: DataTableContext,
rowIndex: EuiDataGridColumnCellActionProps['rowIndex'],
columnId: EuiDataGridColumnCellActionProps['columnId'],
mode: '+' | '-'
@ -35,8 +36,8 @@ export const FilterInBtn = ({
rowIndex,
columnId,
}: EuiDataGridColumnCellActionProps) => {
const context = useContext(DiscoverGridContext);
const buttonTitle = i18n.translate('discover.grid.filterForAria', {
const context = useContext(UnifiedDataTableContext);
const buttonTitle = i18n.translate('unifiedDataTable.grid.filterForAria', {
defaultMessage: 'Filter for this {value}',
values: { value: columnId },
});
@ -51,7 +52,7 @@ export const FilterInBtn = ({
title={buttonTitle}
data-test-subj="filterForButton"
>
{i18n.translate('discover.grid.filterFor', {
{i18n.translate('unifiedDataTable.grid.filterFor', {
defaultMessage: 'Filter for',
})}
</Component>
@ -63,8 +64,8 @@ export const FilterOutBtn = ({
rowIndex,
columnId,
}: EuiDataGridColumnCellActionProps) => {
const context = useContext(DiscoverGridContext);
const buttonTitle = i18n.translate('discover.grid.filterOutAria', {
const context = useContext(UnifiedDataTableContext);
const buttonTitle = i18n.translate('unifiedDataTable.grid.filterOutAria', {
defaultMessage: 'Filter out this {value}',
values: { value: columnId },
});
@ -79,18 +80,19 @@ export const FilterOutBtn = ({
title={buttonTitle}
data-test-subj="filterOutButton"
>
{i18n.translate('discover.grid.filterOut', {
{i18n.translate('unifiedDataTable.grid.filterOut', {
defaultMessage: 'Filter out',
})}
</Component>
);
};
export const CopyBtn = ({ Component, rowIndex, columnId }: EuiDataGridColumnCellActionProps) => {
const { valueToStringConverter } = useContext(DiscoverGridContext);
const { toastNotifications } = useDiscoverServices();
const buttonTitle = i18n.translate('discover.grid.copyClipboardButtonTitle', {
export function buildCopyValueButton(
{ Component, rowIndex, columnId }: EuiDataGridColumnCellActionProps,
toastNotifications: ToastsStart,
valueToStringConverter: ValueToStringConverter
) {
const buttonTitle = i18n.translate('unifiedDataTable.grid.copyClipboardButtonTitle', {
defaultMessage: 'Copy value of {column}',
values: { column: columnId },
});
@ -101,8 +103,8 @@ export const CopyBtn = ({ Component, rowIndex, columnId }: EuiDataGridColumnCell
copyValueToClipboard({
rowIndex,
columnId,
toastNotifications,
valueToStringConverter,
toastNotifications,
});
}}
iconType="copyClipboard"
@ -110,13 +112,26 @@ export const CopyBtn = ({ Component, rowIndex, columnId }: EuiDataGridColumnCell
title={buttonTitle}
data-test-subj="copyClipboardButton"
>
{i18n.translate('discover.grid.copyCellValueButton', {
{i18n.translate('unifiedDataTable.grid.copyCellValueButton', {
defaultMessage: 'Copy value',
})}
</Component>
);
};
export function buildCellActions(field: DataViewField, onFilter?: DocViewFilterFn) {
return [...(onFilter && field.filterable ? [FilterInBtn, FilterOutBtn] : []), CopyBtn];
}
export function buildCellActions(
field: DataViewField,
toastNotifications: ToastsStart,
valueToStringConverter: ValueToStringConverter,
onFilter?: DocViewFilterFn
) {
return [
...(onFilter && field.filterable ? [FilterInBtn, FilterOutBtn] : []),
({ Component, rowIndex, columnId }: EuiDataGridColumnCellActionProps) =>
buildCopyValueButton(
{ Component, rowIndex, columnId } as EuiDataGridColumnCellActionProps,
toastNotifications,
valueToStringConverter
),
];
}

View file

@ -0,0 +1,17 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`returns the \`JsonCodeEditor\` component 1`] = `
<JsonCodeEditorCommon
hideCopyButton={true}
jsonValue="{
\\"_index\\": \\"test\\",
\\"_type\\": \\"doc\\",
\\"_id\\": \\"foo\\",
\\"_score\\": 1,
\\"_source\\": {
\\"test\\": 123
}
}"
onEditorDidMount={[Function]}
/>
`;

View file

@ -0,0 +1,3 @@
.unifiedDataTableJsonEditor {
width: 100%;
}

View file

@ -0,0 +1,22 @@
/*
* 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 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 or the Server
* Side Public License, v 1.
*/
import React from 'react';
import { shallow } from 'enzyme';
import JsonCodeEditor from './json_code_editor';
it('returns the `JsonCodeEditor` component', () => {
const value = {
_index: 'test',
_type: 'doc',
_id: 'foo',
_score: 1,
_source: { test: 123 },
};
expect(shallow(<JsonCodeEditor json={value} />)).toMatchSnapshot();
});

View file

@ -0,0 +1,41 @@
/*
* 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 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 or the Server
* Side Public License, v 1.
*/
import './json_code_editor.scss';
import React from 'react';
import { JsonCodeEditorCommon } from './json_code_editor_common';
export interface JsonCodeEditorProps {
json: Record<string, unknown>;
width?: string | number;
height?: string | number;
hasLineNumbers?: boolean;
}
// Required for usage in React.lazy
// eslint-disable-next-line import/no-default-export
export default function JsonCodeEditor({
json,
width,
height,
hasLineNumbers,
}: JsonCodeEditorProps) {
const jsonValue = JSON.stringify(json, null, 2);
return (
<JsonCodeEditorCommon
jsonValue={jsonValue}
width={width}
height={height}
hasLineNumbers={hasLineNumbers}
onEditorDidMount={() => void 0}
hideCopyButton={true}
/>
);
}

View file

@ -0,0 +1,94 @@
/*
* 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 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 or the Server
* Side Public License, v 1.
*/
import './json_code_editor.scss';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { monaco, XJsonLang } from '@kbn/monaco';
import { EuiButtonEmpty, EuiCopy, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
import { CodeEditor } from '@kbn/code-editor';
const codeEditorAriaLabel = i18n.translate('unifiedDataTable.json.codeEditorAriaLabel', {
defaultMessage: 'Read only JSON view of an elasticsearch document',
});
const copyToClipboardLabel = i18n.translate('unifiedDataTable.json.copyToClipboardLabel', {
defaultMessage: 'Copy to clipboard',
});
interface JsonCodeEditorCommonProps {
jsonValue: string;
onEditorDidMount: (editor: monaco.editor.IStandaloneCodeEditor) => void;
width?: string | number;
height?: string | number;
hasLineNumbers?: boolean;
hideCopyButton?: boolean;
}
export const JsonCodeEditorCommon = ({
jsonValue,
width,
height,
hasLineNumbers,
onEditorDidMount,
hideCopyButton,
}: JsonCodeEditorCommonProps) => {
if (jsonValue === '') {
return null;
}
const codeEditor = (
<CodeEditor
languageId={XJsonLang.ID}
width={width}
height={height}
value={jsonValue || ''}
editorDidMount={onEditorDidMount}
aria-label={codeEditorAriaLabel}
options={{
automaticLayout: true,
fontSize: 12,
lineNumbers: hasLineNumbers ? 'on' : 'off',
minimap: {
enabled: false,
},
overviewRulerBorder: false,
readOnly: true,
scrollbar: {
alwaysConsumeMouseWheel: false,
},
scrollBeyondLastLine: false,
wordWrap: 'on',
wrappingIndent: 'indent',
}}
/>
);
if (hideCopyButton) {
return codeEditor;
}
return (
<EuiFlexGroup className="unifiedDataTableJsonEditor" direction="column" gutterSize="s">
<EuiFlexItem>
<EuiSpacer size="s" />
<div className="eui-textRight">
<EuiCopy textToCopy={jsonValue}>
{(copy) => (
<EuiButtonEmpty size="xs" flush="right" iconType="copyClipboard" onClick={copy}>
{copyToClipboardLabel}
</EuiButtonEmpty>
)}
</EuiCopy>
</div>
</EuiFlexItem>
<EuiFlexItem>{codeEditor}</EuiFlexItem>
</EuiFlexGroup>
);
};
export const JSONCodeEditorCommonMemoized = React.memo((props: JsonCodeEditorCommonProps) => {
return <JsonCodeEditorCommon {...props} />;
});

View file

@ -5,11 +5,17 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { EuiDataGridStyle } from '@elastic/eui';
// data types
export const DEFAULT_ROWS_PER_PAGE = 100;
export const MAX_LOADED_GRID_ROWS = 10000;
export const ROWS_PER_PAGE_OPTIONS = [10, 25, 50, DEFAULT_ROWS_PER_PAGE, 250, 500];
export const defaultMonacoEditorWidth = 370;
export const defaultTimeColumnWidth = 210;
export const kibanaJSON = 'kibana-json';
export const GRID_STYLE = {
border: 'all',
fontSize: 's',
@ -17,12 +23,9 @@ export const GRID_STYLE = {
rowHover: 'none',
} as EuiDataGridStyle;
export const defaultTimeColumnWidth = 210;
export const toolbarVisibility = {
showColumnSelector: {
allowHide: false,
allowReorder: true,
},
};
export const defaultMonacoEditorWidth = 370;

View file

@ -9,8 +9,8 @@
import { renderHook } from '@testing-library/react-hooks';
import { useColumns } from './use_data_grid_columns';
import { dataViewMock } from '@kbn/discover-utils/src/__mocks__';
import { configMock } from '../__mocks__/config';
import { dataViewsMock } from '../__mocks__/data_views';
import { configMock } from '../../__mocks__/config';
import { dataViewsMock } from '../../__mocks__/data_views';
import { Capabilities } from '@kbn/core/types';
describe('useColumns', () => {

View file

@ -9,32 +9,30 @@
import { useEffect, useMemo, useState } from 'react';
import type { DataView, DataViewsContract } from '@kbn/data-views-plugin/public';
import { Capabilities, IUiSettingsClient } from '@kbn/core/public';
import { Capabilities } from '@kbn/core/public';
import { isEqual } from 'lodash';
import { DiscoverAppStateContainer } from '../application/main/services/discover_app_state_container';
import { GetStateReturn as ContextGetStateReturn } from '../application/context/services/context_state';
import { getStateColumnActions } from '../components/doc_table/actions/columns';
import { getStateColumnActions } from '../components/actions/columns';
interface UseColumnsProps {
capabilities: Capabilities;
config: IUiSettingsClient;
dataView: DataView;
dataViews: DataViewsContract;
useNewFieldsApi: boolean;
setAppState: DiscoverAppStateContainer['update'] | ContextGetStateReturn['setAppState'];
setAppState: (state: { columns: string[]; sort?: string[][] }) => void;
columns?: string[];
sort?: string[][];
defaultOrder?: string;
}
export const useColumns = ({
capabilities,
config,
dataView,
dataViews,
setAppState,
useNewFieldsApi,
columns,
sort,
defaultOrder = 'desc',
}: UseColumnsProps) => {
const [usedColumns, setUsedColumns] = useState(getColumns(columns, useNewFieldsApi));
useEffect(() => {
@ -48,15 +46,24 @@ export const useColumns = ({
() =>
getStateColumnActions({
capabilities,
config,
dataView,
dataViews,
setAppState,
useNewFieldsApi,
columns: usedColumns,
sort,
defaultOrder,
}),
[capabilities, config, dataView, dataViews, setAppState, sort, useNewFieldsApi, usedColumns]
[
capabilities,
dataView,
dataViews,
defaultOrder,
setAppState,
sort,
useNewFieldsApi,
usedColumns,
]
);
return {

View file

@ -0,0 +1,77 @@
/*
* 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 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 or the Server
* Side Public License, v 1.
*/
import { renderHook } from '@testing-library/react-hooks';
import { Storage } from '@kbn/kibana-utils-plugin/public';
import { LocalStorageMock } from '../../__mocks__/local_storage_mock';
import { useRowHeightsOptions } from './use_row_heights_options';
const CONFIG_ROW_HEIGHT = 3;
describe('useRowHeightsOptions', () => {
test('should apply rowHeight from savedSearch', () => {
const { result } = renderHook(() => {
return useRowHeightsOptions({
rowHeightState: 2,
storage: new LocalStorageMock({}) as unknown as Storage,
consumer: 'discover',
});
});
expect(result.current.defaultHeight).toEqual({ lineCount: 2 });
});
test('should apply rowHeight from local storage', () => {
const { result } = renderHook(() => {
return useRowHeightsOptions({
storage: new LocalStorageMock({
['discover:dataGridRowHeight']: {
previousRowHeight: 5,
previousConfigRowHeight: 3,
},
}) as unknown as Storage,
consumer: 'discover',
});
});
expect(result.current.defaultHeight).toEqual({ lineCount: 5 });
});
test('should apply rowHeight from configRowHeight', () => {
const { result } = renderHook(() => {
return useRowHeightsOptions({
consumer: 'discover',
configRowHeight: 3,
storage: new LocalStorageMock({}) as unknown as Storage,
});
});
expect(result.current.defaultHeight).toEqual({
lineCount: CONFIG_ROW_HEIGHT,
});
});
test('should apply rowHeight from uiSettings instead of local storage value, since uiSettings has been changed', () => {
const { result } = renderHook(() => {
return useRowHeightsOptions({
storage: new LocalStorageMock({
['discover:dataGridRowHeight']: {
previousRowHeight: 4,
// different from uiSettings (config), now user changed it to 3, but prev was 4
previousConfigRowHeight: 4,
},
}) as unknown as Storage,
consumer: 'discover',
});
});
expect(result.current.defaultHeight).toEqual({
lineCount: CONFIG_ROW_HEIGHT,
});
});
});

View file

@ -7,10 +7,9 @@
*/
import type { EuiDataGridRowHeightOption, EuiDataGridRowHeightsOptions } from '@elastic/eui';
import type { Storage } from '@kbn/kibana-utils-plugin/public';
import { useMemo } from 'react';
import { ROW_HEIGHT_OPTION } from '@kbn/discover-utils';
import { isValidRowHeight } from '../utils/validate_row_height';
import { useDiscoverServices } from './use_discover_services';
import {
DataGridOptionsRecord,
getStoredRowHeight,
@ -20,6 +19,9 @@ import {
interface UseRowHeightProps {
rowHeightState?: number;
onUpdateRowHeight?: (rowHeight: number) => void;
storage: Storage;
configRowHeight?: number;
consumer: string;
}
/**
@ -30,6 +32,7 @@ interface UseRowHeightProps {
*/
const SINGLE_ROW_HEIGHT_OPTION = 0;
const AUTO_ROW_HEIGHT_OPTION = -1;
const DEFAULT_ROW_HEIGHT_OPTION = 3;
/**
* Converts rowHeight of EuiDataGrid to rowHeight number (-1 to 20)
@ -57,12 +60,15 @@ const deserializeRowHeight = (number: number): EuiDataGridRowHeightOption | unde
return { lineCount: number }; // custom
};
export const useRowHeightsOptions = ({ rowHeightState, onUpdateRowHeight }: UseRowHeightProps) => {
const { storage, uiSettings } = useDiscoverServices();
export const useRowHeightsOptions = ({
rowHeightState,
onUpdateRowHeight,
storage,
configRowHeight = DEFAULT_ROW_HEIGHT_OPTION,
consumer,
}: UseRowHeightProps) => {
return useMemo((): EuiDataGridRowHeightsOptions => {
const rowHeightFromLS = getStoredRowHeight(storage);
const configRowHeight = uiSettings.get(ROW_HEIGHT_OPTION);
const rowHeightFromLS = getStoredRowHeight(storage, consumer);
const configHasNotChanged = (
localStorageRecord: DataGridOptionsRecord | null
@ -83,9 +89,9 @@ export const useRowHeightsOptions = ({ rowHeightState, onUpdateRowHeight }: UseR
lineHeight: '1.6em',
onChange: ({ defaultHeight: newRowHeight }: EuiDataGridRowHeightsOptions) => {
const newSerializedRowHeight = serializeRowHeight(newRowHeight);
updateStoredRowHeight(newSerializedRowHeight, configRowHeight, storage);
updateStoredRowHeight(newSerializedRowHeight, configRowHeight, storage, consumer);
onUpdateRowHeight?.(newSerializedRowHeight);
},
};
}, [rowHeightState, uiSettings, storage, onUpdateRowHeight]);
}, [storage, consumer, rowHeightState, configRowHeight, onUpdateRowHeight]);
};

View file

@ -9,10 +9,10 @@
import React from 'react';
import type { DataView } from '@kbn/data-views-plugin/public';
import type { DataTableRecord } from '@kbn/discover-utils/types';
import type { DocViewFilterFn } from '@kbn/unified-doc-viewer/types';
import type { ValueToStringConverter } from '../../types';
import { DocViewFilterFn } from '@kbn/unified-doc-viewer/types';
import type { ValueToStringConverter } from './types';
export interface GridContext {
export interface DataTableContext {
expanded?: DataTableRecord | undefined;
setExpanded?: (hit?: DataTableRecord) => void;
rows: DataTableRecord[];
@ -22,8 +22,9 @@ export interface GridContext {
selectedDocs: string[];
setSelectedDocs: (selected: string[]) => void;
valueToStringConverter: ValueToStringConverter;
componentsTourSteps?: Record<string, string>;
}
const defaultContext = {} as unknown as GridContext;
const defaultContext = {} as unknown as DataTableContext;
export const DiscoverGridContext = React.createContext<GridContext>(defaultContext);
export const UnifiedDataTableContext = React.createContext<DataTableContext>(defaultContext);

View file

@ -6,19 +6,19 @@
* Side Public License, v 1.
*/
import type { DatatableColumn } from '@kbn/expressions-plugin/common';
import type { DataTableRecord } from '@kbn/discover-utils/types';
import type { SearchResponseInterceptedWarning } from '@kbn/search-response-warnings';
/**
* User configurable state of data grid, persisted in saved search
*/
export interface UnifiedDataTableSettings {
columns?: Record<string, UnifiedDataTableSettingsColumn>;
}
export interface UnifiedDataTableSettingsColumn {
width?: number;
}
export type ValueToStringConverter = (
rowIndex: number,
columnId: string,
options?: { compatibleWithCSV?: boolean }
) => { formattedString: string; withFormula: boolean };
export interface RecordsFetchResponse {
records: DataTableRecord[];
textBasedQueryColumns?: DatatableColumn[];
textBasedHeaderWarning?: string;
interceptedWarnings?: SearchResponseInterceptedWarning[];
}

View file

@ -6,8 +6,8 @@
* Side Public License, v 1.
*/
import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefield';
import { getDisplayedColumns } from './columns';
import { dataViewWithTimefieldMock } from '../__mocks__/data_view_with_timefield';
import { dataViewMock } from '@kbn/discover-utils/src/__mocks__';
describe('getDisplayedColumns', () => {

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import { DataView } from '@kbn/data-views-plugin/public';
import type { DataView } from '@kbn/data-views-plugin/public';
// We store this outside the function as a constant, so we're not creating a new array every time
// the function is returning this. A changing array might cause the data grid to think it got

View file

@ -6,16 +6,16 @@
* Side Public License, v 1.
*/
import { discoverGridContextComplexMock, discoverGridContextMock } from '../__mocks__/grid_context';
import { discoverServiceMock } from '../__mocks__/services';
import { dataTableContextComplexMock, dataTableContextMock } from '../../__mocks__/table_context';
import { servicesMock } from '../../__mocks__/services';
import { convertValueToString, convertNameToString } from './convert_value_to_string';
describe('convertValueToString', () => {
it('should convert a keyword value to text', () => {
const result = convertValueToString({
rows: discoverGridContextComplexMock.rows,
dataView: discoverGridContextComplexMock.dataView,
fieldFormats: discoverServiceMock.fieldFormats,
rows: dataTableContextComplexMock.rows,
dataView: dataTableContextComplexMock.dataView,
fieldFormats: servicesMock.fieldFormats,
columnId: 'keyword_key',
rowIndex: 0,
options: {
@ -28,9 +28,9 @@ describe('convertValueToString', () => {
it('should convert a text value to text', () => {
const result = convertValueToString({
rows: discoverGridContextComplexMock.rows,
dataView: discoverGridContextComplexMock.dataView,
fieldFormats: discoverServiceMock.fieldFormats,
rows: dataTableContextComplexMock.rows,
dataView: dataTableContextComplexMock.dataView,
fieldFormats: servicesMock.fieldFormats,
columnId: 'text_message',
rowIndex: 0,
options: {
@ -43,9 +43,9 @@ describe('convertValueToString', () => {
it('should convert a text value to text (not for CSV)', () => {
const result = convertValueToString({
rows: discoverGridContextComplexMock.rows,
dataView: discoverGridContextComplexMock.dataView,
fieldFormats: discoverServiceMock.fieldFormats,
rows: dataTableContextComplexMock.rows,
dataView: dataTableContextComplexMock.dataView,
fieldFormats: servicesMock.fieldFormats,
columnId: 'text_message',
rowIndex: 0,
options: {
@ -58,9 +58,9 @@ describe('convertValueToString', () => {
it('should convert a multiline text value to text', () => {
const result = convertValueToString({
rows: discoverGridContextComplexMock.rows,
dataView: discoverGridContextComplexMock.dataView,
fieldFormats: discoverServiceMock.fieldFormats,
rows: dataTableContextComplexMock.rows,
dataView: dataTableContextComplexMock.dataView,
fieldFormats: servicesMock.fieldFormats,
columnId: 'text_message',
rowIndex: 1,
options: {
@ -74,9 +74,9 @@ describe('convertValueToString', () => {
it('should convert a number value to text', () => {
const result = convertValueToString({
rows: discoverGridContextComplexMock.rows,
dataView: discoverGridContextComplexMock.dataView,
fieldFormats: discoverServiceMock.fieldFormats,
rows: dataTableContextComplexMock.rows,
dataView: dataTableContextComplexMock.dataView,
fieldFormats: servicesMock.fieldFormats,
columnId: 'number_price',
rowIndex: 0,
options: {
@ -89,9 +89,9 @@ describe('convertValueToString', () => {
it('should convert a date value to text', () => {
const result = convertValueToString({
rows: discoverGridContextComplexMock.rows,
dataView: discoverGridContextComplexMock.dataView,
fieldFormats: discoverServiceMock.fieldFormats,
rows: dataTableContextComplexMock.rows,
dataView: dataTableContextComplexMock.dataView,
fieldFormats: servicesMock.fieldFormats,
columnId: 'date',
rowIndex: 0,
options: {
@ -104,9 +104,9 @@ describe('convertValueToString', () => {
it('should convert a date nanos value to text', () => {
const result = convertValueToString({
rows: discoverGridContextComplexMock.rows,
dataView: discoverGridContextComplexMock.dataView,
fieldFormats: discoverServiceMock.fieldFormats,
rows: dataTableContextComplexMock.rows,
dataView: dataTableContextComplexMock.dataView,
fieldFormats: servicesMock.fieldFormats,
columnId: 'date_nanos',
rowIndex: 0,
options: {
@ -119,9 +119,9 @@ describe('convertValueToString', () => {
it('should convert a date nanos value to text (not for CSV)', () => {
const result = convertValueToString({
rows: discoverGridContextComplexMock.rows,
dataView: discoverGridContextComplexMock.dataView,
fieldFormats: discoverServiceMock.fieldFormats,
rows: dataTableContextComplexMock.rows,
dataView: dataTableContextComplexMock.dataView,
fieldFormats: servicesMock.fieldFormats,
columnId: 'date_nanos',
rowIndex: 0,
options: {
@ -134,9 +134,9 @@ describe('convertValueToString', () => {
it('should convert a boolean value to text', () => {
const result = convertValueToString({
rows: discoverGridContextComplexMock.rows,
dataView: discoverGridContextComplexMock.dataView,
fieldFormats: discoverServiceMock.fieldFormats,
rows: dataTableContextComplexMock.rows,
dataView: dataTableContextComplexMock.dataView,
fieldFormats: servicesMock.fieldFormats,
columnId: 'bool_enabled',
rowIndex: 0,
options: {
@ -149,9 +149,9 @@ describe('convertValueToString', () => {
it('should convert a binary value to text', () => {
const result = convertValueToString({
rows: discoverGridContextComplexMock.rows,
dataView: discoverGridContextComplexMock.dataView,
fieldFormats: discoverServiceMock.fieldFormats,
rows: dataTableContextComplexMock.rows,
dataView: dataTableContextComplexMock.dataView,
fieldFormats: servicesMock.fieldFormats,
columnId: 'binary_blob',
rowIndex: 0,
options: {
@ -164,9 +164,9 @@ describe('convertValueToString', () => {
it('should convert a binary value to text (not for CSV)', () => {
const result = convertValueToString({
rows: discoverGridContextComplexMock.rows,
dataView: discoverGridContextComplexMock.dataView,
fieldFormats: discoverServiceMock.fieldFormats,
rows: dataTableContextComplexMock.rows,
dataView: dataTableContextComplexMock.dataView,
fieldFormats: servicesMock.fieldFormats,
columnId: 'binary_blob',
rowIndex: 0,
options: {
@ -179,9 +179,9 @@ describe('convertValueToString', () => {
it('should convert an object value to text', () => {
const result = convertValueToString({
rows: discoverGridContextComplexMock.rows,
dataView: discoverGridContextComplexMock.dataView,
fieldFormats: discoverServiceMock.fieldFormats,
rows: dataTableContextComplexMock.rows,
dataView: dataTableContextComplexMock.dataView,
fieldFormats: servicesMock.fieldFormats,
columnId: 'object_user.first',
rowIndex: 0,
options: {
@ -194,9 +194,9 @@ describe('convertValueToString', () => {
it('should convert a nested value to text', () => {
const result = convertValueToString({
rows: discoverGridContextComplexMock.rows,
dataView: discoverGridContextComplexMock.dataView,
fieldFormats: discoverServiceMock.fieldFormats,
rows: dataTableContextComplexMock.rows,
dataView: dataTableContextComplexMock.dataView,
fieldFormats: servicesMock.fieldFormats,
columnId: 'nested_user',
rowIndex: 0,
options: {
@ -211,9 +211,9 @@ describe('convertValueToString', () => {
it('should convert a flattened value to text', () => {
const result = convertValueToString({
rows: discoverGridContextComplexMock.rows,
dataView: discoverGridContextComplexMock.dataView,
fieldFormats: discoverServiceMock.fieldFormats,
rows: dataTableContextComplexMock.rows,
dataView: dataTableContextComplexMock.dataView,
fieldFormats: servicesMock.fieldFormats,
columnId: 'flattened_labels',
rowIndex: 0,
options: {
@ -226,9 +226,9 @@ describe('convertValueToString', () => {
it('should convert a range value to text', () => {
const result = convertValueToString({
rows: discoverGridContextComplexMock.rows,
dataView: discoverGridContextComplexMock.dataView,
fieldFormats: discoverServiceMock.fieldFormats,
rows: dataTableContextComplexMock.rows,
dataView: dataTableContextComplexMock.dataView,
fieldFormats: servicesMock.fieldFormats,
columnId: 'range_time_frame',
rowIndex: 0,
options: {
@ -243,9 +243,9 @@ describe('convertValueToString', () => {
it('should convert a rank features value to text', () => {
const result = convertValueToString({
rows: discoverGridContextComplexMock.rows,
dataView: discoverGridContextComplexMock.dataView,
fieldFormats: discoverServiceMock.fieldFormats,
rows: dataTableContextComplexMock.rows,
dataView: dataTableContextComplexMock.dataView,
fieldFormats: servicesMock.fieldFormats,
columnId: 'rank_features',
rowIndex: 0,
options: {
@ -258,9 +258,9 @@ describe('convertValueToString', () => {
it('should convert a histogram value to text', () => {
const result = convertValueToString({
rows: discoverGridContextComplexMock.rows,
dataView: discoverGridContextComplexMock.dataView,
fieldFormats: discoverServiceMock.fieldFormats,
rows: dataTableContextComplexMock.rows,
dataView: dataTableContextComplexMock.dataView,
fieldFormats: servicesMock.fieldFormats,
columnId: 'histogram',
rowIndex: 0,
options: {
@ -273,9 +273,9 @@ describe('convertValueToString', () => {
it('should convert a IP value to text', () => {
const result = convertValueToString({
rows: discoverGridContextComplexMock.rows,
dataView: discoverGridContextComplexMock.dataView,
fieldFormats: discoverServiceMock.fieldFormats,
rows: dataTableContextComplexMock.rows,
dataView: dataTableContextComplexMock.dataView,
fieldFormats: servicesMock.fieldFormats,
columnId: 'ip_addr',
rowIndex: 0,
options: {
@ -288,9 +288,9 @@ describe('convertValueToString', () => {
it('should convert a IP value to text (not for CSV)', () => {
const result = convertValueToString({
rows: discoverGridContextComplexMock.rows,
dataView: discoverGridContextComplexMock.dataView,
fieldFormats: discoverServiceMock.fieldFormats,
rows: dataTableContextComplexMock.rows,
dataView: dataTableContextComplexMock.dataView,
fieldFormats: servicesMock.fieldFormats,
columnId: 'ip_addr',
rowIndex: 0,
options: {
@ -303,9 +303,9 @@ describe('convertValueToString', () => {
it('should convert a version value to text', () => {
const result = convertValueToString({
rows: discoverGridContextComplexMock.rows,
dataView: discoverGridContextComplexMock.dataView,
fieldFormats: discoverServiceMock.fieldFormats,
rows: dataTableContextComplexMock.rows,
dataView: dataTableContextComplexMock.dataView,
fieldFormats: servicesMock.fieldFormats,
columnId: 'version',
rowIndex: 0,
options: {
@ -318,9 +318,9 @@ describe('convertValueToString', () => {
it('should convert a version value to text (not for CSV)', () => {
const result = convertValueToString({
rows: discoverGridContextComplexMock.rows,
dataView: discoverGridContextComplexMock.dataView,
fieldFormats: discoverServiceMock.fieldFormats,
rows: dataTableContextComplexMock.rows,
dataView: dataTableContextComplexMock.dataView,
fieldFormats: servicesMock.fieldFormats,
columnId: 'version',
rowIndex: 0,
options: {
@ -333,9 +333,9 @@ describe('convertValueToString', () => {
it('should convert a vector value to text', () => {
const result = convertValueToString({
rows: discoverGridContextComplexMock.rows,
dataView: discoverGridContextComplexMock.dataView,
fieldFormats: discoverServiceMock.fieldFormats,
rows: dataTableContextComplexMock.rows,
dataView: dataTableContextComplexMock.dataView,
fieldFormats: servicesMock.fieldFormats,
columnId: 'vector',
rowIndex: 0,
options: {
@ -348,9 +348,9 @@ describe('convertValueToString', () => {
it('should convert a geo point value to text', () => {
const result = convertValueToString({
rows: discoverGridContextComplexMock.rows,
dataView: discoverGridContextComplexMock.dataView,
fieldFormats: discoverServiceMock.fieldFormats,
rows: dataTableContextComplexMock.rows,
dataView: dataTableContextComplexMock.dataView,
fieldFormats: servicesMock.fieldFormats,
columnId: 'geo_point',
rowIndex: 0,
options: {
@ -363,9 +363,9 @@ describe('convertValueToString', () => {
it('should convert a geo point object value to text', () => {
const result = convertValueToString({
rows: discoverGridContextComplexMock.rows,
dataView: discoverGridContextComplexMock.dataView,
fieldFormats: discoverServiceMock.fieldFormats,
rows: dataTableContextComplexMock.rows,
dataView: dataTableContextComplexMock.dataView,
fieldFormats: servicesMock.fieldFormats,
columnId: 'geo_point',
rowIndex: 1,
options: {
@ -378,9 +378,9 @@ describe('convertValueToString', () => {
it('should convert an array value to text', () => {
const result = convertValueToString({
rows: discoverGridContextComplexMock.rows,
dataView: discoverGridContextComplexMock.dataView,
fieldFormats: discoverServiceMock.fieldFormats,
rows: dataTableContextComplexMock.rows,
dataView: dataTableContextComplexMock.dataView,
fieldFormats: servicesMock.fieldFormats,
columnId: 'array_tags',
rowIndex: 0,
options: {
@ -393,9 +393,9 @@ describe('convertValueToString', () => {
it('should convert a shape value to text', () => {
const result = convertValueToString({
rows: discoverGridContextComplexMock.rows,
dataView: discoverGridContextComplexMock.dataView,
fieldFormats: discoverServiceMock.fieldFormats,
rows: dataTableContextComplexMock.rows,
dataView: dataTableContextComplexMock.dataView,
fieldFormats: servicesMock.fieldFormats,
columnId: 'geometry',
rowIndex: 0,
options: {
@ -410,9 +410,9 @@ describe('convertValueToString', () => {
it('should convert a runtime value to text', () => {
const result = convertValueToString({
rows: discoverGridContextComplexMock.rows,
dataView: discoverGridContextComplexMock.dataView,
fieldFormats: discoverServiceMock.fieldFormats,
rows: dataTableContextComplexMock.rows,
dataView: dataTableContextComplexMock.dataView,
fieldFormats: servicesMock.fieldFormats,
columnId: 'runtime_number',
rowIndex: 0,
options: {
@ -425,9 +425,9 @@ describe('convertValueToString', () => {
it('should convert a scripted value to text', () => {
const result = convertValueToString({
rows: discoverGridContextComplexMock.rows,
dataView: discoverGridContextComplexMock.dataView,
fieldFormats: discoverServiceMock.fieldFormats,
rows: dataTableContextComplexMock.rows,
dataView: dataTableContextComplexMock.dataView,
fieldFormats: servicesMock.fieldFormats,
columnId: 'scripted_string',
rowIndex: 0,
options: {
@ -440,9 +440,9 @@ describe('convertValueToString', () => {
it('should convert a scripted value to text (not for CSV)', () => {
const result = convertValueToString({
rows: discoverGridContextComplexMock.rows,
dataView: discoverGridContextComplexMock.dataView,
fieldFormats: discoverServiceMock.fieldFormats,
rows: dataTableContextComplexMock.rows,
dataView: dataTableContextComplexMock.dataView,
fieldFormats: servicesMock.fieldFormats,
columnId: 'scripted_string',
rowIndex: 0,
options: {
@ -455,9 +455,9 @@ describe('convertValueToString', () => {
it('should return an empty string and not fail', () => {
const result = convertValueToString({
rows: discoverGridContextComplexMock.rows,
dataView: discoverGridContextComplexMock.dataView,
fieldFormats: discoverServiceMock.fieldFormats,
rows: dataTableContextComplexMock.rows,
dataView: dataTableContextComplexMock.dataView,
fieldFormats: servicesMock.fieldFormats,
columnId: 'unknown',
rowIndex: 0,
options: {
@ -470,9 +470,9 @@ describe('convertValueToString', () => {
it('should return an empty string when rowIndex is out of range', () => {
const result = convertValueToString({
rows: discoverGridContextComplexMock.rows,
dataView: discoverGridContextComplexMock.dataView,
fieldFormats: discoverServiceMock.fieldFormats,
rows: dataTableContextComplexMock.rows,
dataView: dataTableContextComplexMock.dataView,
fieldFormats: servicesMock.fieldFormats,
columnId: 'unknown',
rowIndex: -1,
options: {
@ -485,9 +485,9 @@ describe('convertValueToString', () => {
it('should return _source value', () => {
const result = convertValueToString({
rows: discoverGridContextMock.rows,
dataView: discoverGridContextMock.dataView,
fieldFormats: discoverServiceMock.fieldFormats,
rows: dataTableContextMock.rows,
dataView: dataTableContextMock.dataView,
fieldFormats: servicesMock.fieldFormats,
columnId: '_source',
rowIndex: 0,
options: {
@ -508,9 +508,9 @@ describe('convertValueToString', () => {
it('should return a formatted _source value', () => {
const result = convertValueToString({
rows: discoverGridContextMock.rows,
dataView: discoverGridContextMock.dataView,
fieldFormats: discoverServiceMock.fieldFormats,
rows: dataTableContextMock.rows,
dataView: dataTableContextMock.dataView,
fieldFormats: servicesMock.fieldFormats,
columnId: '_source',
rowIndex: 0,
options: {
@ -525,9 +525,9 @@ describe('convertValueToString', () => {
it('should escape formula', () => {
const result = convertValueToString({
rows: discoverGridContextComplexMock.rows,
dataView: discoverGridContextComplexMock.dataView,
fieldFormats: discoverServiceMock.fieldFormats,
rows: dataTableContextComplexMock.rows,
dataView: dataTableContextComplexMock.dataView,
fieldFormats: servicesMock.fieldFormats,
columnId: 'array_tags',
rowIndex: 1,
options: {
@ -539,9 +539,9 @@ describe('convertValueToString', () => {
expect(result.withFormula).toBe(true);
const result2 = convertValueToString({
rows: discoverGridContextComplexMock.rows,
dataView: discoverGridContextComplexMock.dataView,
fieldFormats: discoverServiceMock.fieldFormats,
rows: dataTableContextComplexMock.rows,
dataView: dataTableContextComplexMock.dataView,
fieldFormats: servicesMock.fieldFormats,
columnId: 'scripted_string',
rowIndex: 1,
options: {
@ -555,9 +555,9 @@ describe('convertValueToString', () => {
it('should not escape formulas when not for CSV', () => {
const result = convertValueToString({
rows: discoverGridContextComplexMock.rows,
dataView: discoverGridContextComplexMock.dataView,
fieldFormats: discoverServiceMock.fieldFormats,
rows: dataTableContextComplexMock.rows,
dataView: dataTableContextComplexMock.dataView,
fieldFormats: servicesMock.fieldFormats,
columnId: 'array_tags',
rowIndex: 1,
options: {

View file

@ -6,9 +6,9 @@
* Side Public License, v 1.
*/
import { DataView } from '@kbn/data-views-plugin/public';
import type { DataView } from '@kbn/data-views-plugin/public';
import { cellHasFormulas, createEscapeValue } from '@kbn/data-plugin/common';
import { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import type { DataTableRecord } from '@kbn/discover-utils/types';
import { formatFieldValue } from '@kbn/discover-utils';

View file

@ -6,8 +6,8 @@
* Side Public License, v 1.
*/
import { discoverGridContextComplexMock } from '../__mocks__/grid_context';
import { discoverServiceMock } from '../__mocks__/services';
import { dataTableContextComplexMock } from '../../__mocks__/table_context';
import { servicesMock } from '../../__mocks__/services';
import {
copyValueToClipboard,
copyColumnNameToClipboard,
@ -22,9 +22,9 @@ const warn = jest.spyOn(console, 'warn').mockImplementation(() => {});
describe('copyValueToClipboard', () => {
const valueToStringConverter: ValueToStringConverter = (rowIndex, columnId, options) =>
convertValueToString({
rows: discoverGridContextComplexMock.rows,
dataView: discoverGridContextComplexMock.dataView,
fieldFormats: discoverServiceMock.fieldFormats,
rows: dataTableContextComplexMock.rows,
dataView: dataTableContextComplexMock.dataView,
fieldFormats: servicesMock.fieldFormats,
rowIndex,
columnId,
options,
@ -39,6 +39,10 @@ describe('copyValueToClipboard', () => {
},
writable: true,
});
Object.defineProperty(window, 'sessionStorage', {
value: { clear: jest.fn() },
writable: true,
});
});
afterAll(() => {
@ -50,7 +54,7 @@ describe('copyValueToClipboard', () => {
it('should copy a value to clipboard', () => {
execCommandMock.mockImplementationOnce(() => true);
const result = copyValueToClipboard({
toastNotifications: discoverServiceMock.toastNotifications,
toastNotifications: servicesMock.toastNotifications,
columnId: 'keyword_key',
rowIndex: 0,
valueToStringConverter,
@ -59,7 +63,7 @@ describe('copyValueToClipboard', () => {
expect(result).toBe('abcd1');
expect(execCommandMock).toHaveBeenCalledWith('copy');
expect(warn).not.toHaveBeenCalled();
expect(discoverServiceMock.toastNotifications.addInfo).toHaveBeenCalledWith({
expect(servicesMock.toastNotifications.addInfo).toHaveBeenCalledWith({
title: 'Copied to clipboard',
});
});
@ -68,7 +72,7 @@ describe('copyValueToClipboard', () => {
execCommandMock.mockImplementationOnce(() => false);
const result = copyValueToClipboard({
toastNotifications: discoverServiceMock.toastNotifications,
toastNotifications: servicesMock.toastNotifications,
columnId: 'keyword_key',
rowIndex: 0,
valueToStringConverter,
@ -77,7 +81,7 @@ describe('copyValueToClipboard', () => {
expect(result).toBe(null);
expect(execCommandMock).toHaveBeenCalledWith('copy');
expect(warn).toHaveBeenCalledWith('Unable to copy to clipboard.');
expect(discoverServiceMock.toastNotifications.addWarning).toHaveBeenCalledWith({
expect(servicesMock.toastNotifications.addWarning).toHaveBeenCalledWith({
title: 'Unable to copy to clipboard in this browser',
});
});
@ -85,13 +89,13 @@ describe('copyValueToClipboard', () => {
it('should copy a column name to clipboard', () => {
execCommandMock.mockImplementationOnce(() => true);
const result = copyColumnNameToClipboard({
toastNotifications: discoverServiceMock.toastNotifications,
toastNotifications: servicesMock.toastNotifications,
columnDisplayName: 'text_message',
});
expect(result).toBe('"text_message"');
expect(execCommandMock).toHaveBeenCalledWith('copy');
expect(discoverServiceMock.toastNotifications.addInfo).toHaveBeenCalledWith({
expect(servicesMock.toastNotifications.addInfo).toHaveBeenCalledWith({
title: 'Copied to clipboard',
});
});
@ -99,14 +103,14 @@ describe('copyValueToClipboard', () => {
it('should inform when copy a column name to clipboard failed', () => {
execCommandMock.mockImplementationOnce(() => false);
const result = copyColumnNameToClipboard({
toastNotifications: discoverServiceMock.toastNotifications,
toastNotifications: servicesMock.toastNotifications,
columnDisplayName: 'text_message',
});
expect(result).toBe(null);
expect(execCommandMock).toHaveBeenCalledWith('copy');
expect(warn).toHaveBeenCalledWith('Unable to copy to clipboard.');
expect(discoverServiceMock.toastNotifications.addWarning).toHaveBeenCalledWith({
expect(servicesMock.toastNotifications.addWarning).toHaveBeenCalledWith({
title: 'Unable to copy to clipboard in this browser',
});
});
@ -115,7 +119,7 @@ describe('copyValueToClipboard', () => {
execCommandMock.mockImplementationOnce(() => true);
const result = await copyColumnValuesToClipboard({
toastNotifications: discoverServiceMock.toastNotifications,
toastNotifications: servicesMock.toastNotifications,
columnId: 'bool_enabled',
columnDisplayName: 'custom_bool_enabled',
rowsCount: 2,
@ -126,7 +130,7 @@ describe('copyValueToClipboard', () => {
expect(global.window.navigator.clipboard.writeText).toHaveBeenCalledWith(
'"custom_bool_enabled"\nfalse\ntrue'
);
expect(discoverServiceMock.toastNotifications.addInfo).toHaveBeenCalledWith({
expect(servicesMock.toastNotifications.addInfo).toHaveBeenCalledWith({
title: 'Values of "custom_bool_enabled" column copied to clipboard',
});
});
@ -134,7 +138,7 @@ describe('copyValueToClipboard', () => {
it('should copy column values to clipboard with a warning', async () => {
execCommandMock.mockImplementationOnce(() => true);
const result = await copyColumnValuesToClipboard({
toastNotifications: discoverServiceMock.toastNotifications,
toastNotifications: servicesMock.toastNotifications,
columnId: 'scripted_string',
columnDisplayName: 'custom_scripted_string',
rowsCount: 2,
@ -142,7 +146,7 @@ describe('copyValueToClipboard', () => {
});
expect(result).toBe('"custom_scripted_string"\n"hi there"\n"\'=1+2"";=1+2"');
expect(discoverServiceMock.toastNotifications.addWarning).toHaveBeenCalledWith({
expect(servicesMock.toastNotifications.addWarning).toHaveBeenCalledWith({
title: 'Values of "custom_scripted_string" column copied to clipboard',
text: 'Values may contain formulas that are escaped.',
});

View file

@ -13,12 +13,12 @@ import type { ValueToStringConverter } from '../types';
import { convertNameToString } from './convert_value_to_string';
const WARNING_FOR_FORMULAS = i18n.translate(
'discover.grid.copyEscapedValueWithFormulasToClipboardWarningText',
'unifiedDataTable.copyEscapedValueWithFormulasToClipboardWarningText',
{
defaultMessage: 'Values may contain formulas that are escaped.',
}
);
const COPY_FAILED_ERROR_MESSAGE = i18n.translate('discover.grid.copyFailedErrorText', {
const COPY_FAILED_ERROR_MESSAGE = i18n.translate('unifiedDataTable.copyFailedErrorText', {
defaultMessage: 'Unable to copy to clipboard in this browser',
});
@ -46,7 +46,7 @@ export const copyValueToClipboard = ({
return null;
}
const toastTitle = i18n.translate('discover.grid.copyValueToClipboard.toastTitle', {
const toastTitle = i18n.translate('unifiedDataTable.copyValueToClipboard.toastTitle', {
defaultMessage: 'Copied to clipboard',
});
@ -105,7 +105,7 @@ export const copyColumnValuesToClipboard = async ({
return null;
}
const toastTitle = i18n.translate('discover.grid.copyColumnValuesToClipboard.toastTitle', {
const toastTitle = i18n.translate('unifiedDataTable.copyColumnValuesToClipboard.toastTitle', {
defaultMessage: 'Values of "{column}" column copied to clipboard',
values: { column: columnDisplayName },
});
@ -143,7 +143,7 @@ export const copyColumnNameToClipboard = ({
return null;
}
const toastTitle = i18n.translate('discover.grid.copyColumnNameToClipboard.toastTitle', {
const toastTitle = i18n.translate('unifiedDataTable.copyColumnNameToClipboard.toastTitle', {
defaultMessage: 'Copied to clipboard',
});

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import { DataView, DataViewField } from '@kbn/data-views-plugin/common';
import type { DataView, DataViewField } from '@kbn/data-views-plugin/common';
export const getFieldCapabilities = (dataView: DataView, field: DataViewField) => {
const isRuntimeField = Boolean(dataView.getFieldByName(field.name)?.runtimeField);

View file

@ -13,9 +13,38 @@ import { findTestSubject } from '@elastic/eui/lib/test';
import { mountWithIntl } from '@kbn/test-jest-helpers';
import { getRenderCellValueFn } from './get_render_cell_value';
import { dataViewMock } from '@kbn/discover-utils/src/__mocks__';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import { CodeEditorProps, KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import { buildDataTableRecord } from '@kbn/discover-utils';
import type { EsHitRecord } from '@kbn/discover-utils/types';
import { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
jest.mock('@kbn/code-editor', () => {
const original = jest.requireActual('@kbn/code-editor');
const CodeEditorMock = (props: CodeEditorProps) => (
<input
data-test-subj={'mockCodeEditor'}
data-value={props.value}
value={props.value}
onChange={jest.fn()}
/>
);
return {
...original,
CodeEditor: CodeEditorMock,
};
});
window.matchMedia = jest.fn().mockImplementation((query) => {
return {
matches: false,
media: query,
onchange: null,
addListener: jest.fn(),
removeListener: jest.fn(),
};
});
const mockServices = {
settings: {
@ -34,14 +63,6 @@ const mockServices = {
},
};
jest.mock('../../hooks/use_discover_services', () => {
const originalModule = jest.requireActual('../../hooks/use_discover_services');
return {
...originalModule,
useDiscoverServices: () => mockServices,
};
});
const rowsSource: EsHitRecord[] = [
{
_id: '1',
@ -82,18 +103,19 @@ const rowsFieldsWithTopLevelObject: EsHitRecord[] = [
const build = (hit: EsHitRecord) => buildDataTableRecord(hit, dataViewMock);
describe('Discover grid cell rendering', function () {
describe('Unified data table cell rendering', function () {
it('renders bytes column correctly', () => {
const DiscoverGridCellValue = getRenderCellValueFn(
const DataTableCellValue = getRenderCellValueFn(
dataViewMock,
rowsSource.map(build),
false,
() => false,
100,
jest.fn()
jest.fn(),
mockServices.fieldFormats as unknown as FieldFormatsStart,
100
);
const component = shallow(
<DiscoverGridCellValue
<DataTableCellValue
rowIndex={0}
colIndex={0}
columnId="bytes"
@ -104,21 +126,22 @@ describe('Discover grid cell rendering', function () {
/>
);
expect(component.html()).toMatchInlineSnapshot(
`"<span class=\\"dscDiscoverGrid__cellValue\\">100</span>"`
`"<span class=\\"unifiedDataTable__cellValue\\">100</span>"`
);
});
it('renders bytes column correctly using _source when details is true', () => {
const DiscoverGridCellValue = getRenderCellValueFn(
const DataTableCellValue = getRenderCellValueFn(
dataViewMock,
rowsSource.map(build),
false,
() => false,
100,
jest.fn()
jest.fn(),
mockServices.fieldFormats as unknown as FieldFormatsStart,
100
);
const component = shallow(
<DiscoverGridCellValue
<DataTableCellValue
rowIndex={0}
colIndex={0}
columnId="bytes"
@ -129,22 +152,23 @@ describe('Discover grid cell rendering', function () {
/>
);
expect(component.html()).toMatchInlineSnapshot(
`"<div class=\\"euiFlexGroup css-1h68cm-euiFlexGroup-none-flexStart-stretch-row\\"><div class=\\"euiFlexItem css-9sbomz-euiFlexItem-grow-1\\"><span class=\\"dscDiscoverGrid__cellPopoverValue eui-textBreakWord\\">100</span></div><div class=\\"euiFlexItem css-kpsrin-euiFlexItem-growZero\\"><button class=\\"euiButtonIcon css-9sj1hz-euiButtonIcon-xs-empty-primary\\" type=\\"button\\" aria-label=\\"Close popover\\" data-test-subj=\\"docTableClosePopover\\"><span data-euiicon-type=\\"cross\\" class=\\"euiButtonIcon__icon\\" aria-hidden=\\"true\\" color=\\"inherit\\"></span></button></div></div>"`
`"<div class=\\"euiFlexGroup css-1h68cm-euiFlexGroup-none-flexStart-stretch-row\\"><div class=\\"euiFlexItem css-9sbomz-euiFlexItem-grow-1\\"><span class=\\"unifiedDataTable__cellPopoverValue eui-textBreakWord\\">100</span></div><div class=\\"euiFlexItem css-kpsrin-euiFlexItem-growZero\\"><button class=\\"euiButtonIcon css-9sj1hz-euiButtonIcon-xs-empty-primary\\" type=\\"button\\" aria-label=\\"Close popover\\" data-test-subj=\\"docTableClosePopover\\"><span data-euiicon-type=\\"cross\\" class=\\"euiButtonIcon__icon\\" aria-hidden=\\"true\\" color=\\"inherit\\"></span></button></div></div>"`
);
});
it('renders bytes column correctly using fields when details is true', () => {
const closePopoverMockFn = jest.fn();
const DiscoverGridCellValue = getRenderCellValueFn(
const DataTableCellValue = getRenderCellValueFn(
dataViewMock,
rowsFields.map(build),
false,
() => false,
100,
closePopoverMockFn
closePopoverMockFn,
mockServices.fieldFormats as unknown as FieldFormatsStart,
100
);
const component = mountWithIntl(
<DiscoverGridCellValue
<DataTableCellValue
rowIndex={0}
colIndex={0}
columnId="bytes"
@ -155,23 +179,24 @@ describe('Discover grid cell rendering', function () {
/>
);
expect(component.html()).toMatchInlineSnapshot(
`"<div class=\\"euiFlexGroup css-1h68cm-euiFlexGroup-none-flexStart-stretch-row\\"><div class=\\"euiFlexItem css-9sbomz-euiFlexItem-grow-1\\"><span class=\\"dscDiscoverGrid__cellPopoverValue eui-textBreakWord\\">100</span></div><div class=\\"euiFlexItem css-kpsrin-euiFlexItem-growZero\\"><button class=\\"euiButtonIcon css-9sj1hz-euiButtonIcon-xs-empty-primary\\" type=\\"button\\" aria-label=\\"Close popover\\" data-test-subj=\\"docTableClosePopover\\"><span data-euiicon-type=\\"cross\\" class=\\"euiButtonIcon__icon\\" aria-hidden=\\"true\\" color=\\"inherit\\"></span></button></div></div>"`
`"<div class=\\"euiFlexGroup css-1h68cm-euiFlexGroup-none-flexStart-stretch-row\\"><div class=\\"euiFlexItem css-9sbomz-euiFlexItem-grow-1\\"><span class=\\"unifiedDataTable__cellPopoverValue eui-textBreakWord\\">100</span></div><div class=\\"euiFlexItem css-kpsrin-euiFlexItem-growZero\\"><button class=\\"euiButtonIcon css-9sj1hz-euiButtonIcon-xs-empty-primary\\" type=\\"button\\" aria-label=\\"Close popover\\" data-test-subj=\\"docTableClosePopover\\"><span data-euiicon-type=\\"cross\\" class=\\"euiButtonIcon__icon\\" aria-hidden=\\"true\\" color=\\"inherit\\"></span></button></div></div>"`
);
findTestSubject(component, 'docTableClosePopover').simulate('click');
expect(closePopoverMockFn).toHaveBeenCalledTimes(1);
});
it('renders _source column correctly', () => {
const DiscoverGridCellValue = getRenderCellValueFn(
const DataTableCellValue = getRenderCellValueFn(
dataViewMock,
rowsSource.map(build),
false,
(fieldName) => ['extension', 'bytes'].includes(fieldName),
100,
jest.fn()
jest.fn(),
mockServices.fieldFormats as unknown as FieldFormatsStart,
100
);
const component = shallow(
<DiscoverGridCellValue
<DataTableCellValue
rowIndex={0}
colIndex={0}
columnId="_source"
@ -183,7 +208,7 @@ describe('Discover grid cell rendering', function () {
);
expect(component).toMatchInlineSnapshot(`
<EuiDescriptionList
className="dscDiscoverGrid__descriptionList dscDiscoverGrid__cellValue"
className="unifiedDataTable__descriptionList unifiedDataTable__cellValue"
compressed={true}
type="inline"
>
@ -191,7 +216,7 @@ describe('Discover grid cell rendering', function () {
extension
</EuiDescriptionListTitle>
<EuiDescriptionListDescription
className="dscDiscoverGrid__descriptionListDescription"
className="unifiedDataTable__descriptionListDescription"
dangerouslySetInnerHTML={
Object {
"__html": ".gz",
@ -202,7 +227,7 @@ describe('Discover grid cell rendering', function () {
bytesDisplayName
</EuiDescriptionListTitle>
<EuiDescriptionListDescription
className="dscDiscoverGrid__descriptionListDescription"
className="unifiedDataTable__descriptionListDescription"
dangerouslySetInnerHTML={
Object {
"__html": 100,
@ -213,7 +238,7 @@ describe('Discover grid cell rendering', function () {
_index
</EuiDescriptionListTitle>
<EuiDescriptionListDescription
className="dscDiscoverGrid__descriptionListDescription"
className="unifiedDataTable__descriptionListDescription"
dangerouslySetInnerHTML={
Object {
"__html": "test",
@ -224,7 +249,7 @@ describe('Discover grid cell rendering', function () {
_score
</EuiDescriptionListTitle>
<EuiDescriptionListDescription
className="dscDiscoverGrid__descriptionListDescription"
className="unifiedDataTable__descriptionListDescription"
dangerouslySetInnerHTML={
Object {
"__html": 1,
@ -236,16 +261,17 @@ describe('Discover grid cell rendering', function () {
});
it('renders _source column correctly when isDetails is set to true', () => {
const DiscoverGridCellValue = getRenderCellValueFn(
const DataTableCellValue = getRenderCellValueFn(
dataViewMock,
rowsSource.map(build),
false,
() => false,
100,
jest.fn()
jest.fn(),
mockServices.fieldFormats as unknown as FieldFormatsStart,
100
);
const component = shallow(
<DiscoverGridCellValue
<DataTableCellValue
rowIndex={0}
colIndex={0}
columnId="_source"
@ -257,7 +283,7 @@ describe('Discover grid cell rendering', function () {
);
expect(component).toMatchInlineSnapshot(`
<EuiFlexGroup
className="dscDiscoverGrid__cellPopover"
className="unifiedDataTable__cellPopover"
direction="column"
gutterSize="none"
justifyContent="flexEnd"
@ -285,7 +311,7 @@ describe('Discover grid cell rendering', function () {
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem>
<ForwardRef
<JsonCodeEditor
height={200}
json={
Object {
@ -311,16 +337,17 @@ describe('Discover grid cell rendering', function () {
});
it('renders fields-based column correctly', () => {
const DiscoverGridCellValue = getRenderCellValueFn(
const DataTableCellValue = getRenderCellValueFn(
dataViewMock,
rowsFields.map(build),
true,
(fieldName) => ['extension', 'bytes'].includes(fieldName),
100,
jest.fn()
jest.fn(),
mockServices.fieldFormats as unknown as FieldFormatsStart,
100
);
const component = shallow(
<DiscoverGridCellValue
<DataTableCellValue
rowIndex={0}
colIndex={0}
columnId="_source"
@ -332,7 +359,7 @@ describe('Discover grid cell rendering', function () {
);
expect(component).toMatchInlineSnapshot(`
<EuiDescriptionList
className="dscDiscoverGrid__descriptionList dscDiscoverGrid__cellValue"
className="unifiedDataTable__descriptionList unifiedDataTable__cellValue"
compressed={true}
type="inline"
>
@ -340,7 +367,7 @@ describe('Discover grid cell rendering', function () {
extension
</EuiDescriptionListTitle>
<EuiDescriptionListDescription
className="dscDiscoverGrid__descriptionListDescription"
className="unifiedDataTable__descriptionListDescription"
dangerouslySetInnerHTML={
Object {
"__html": Array [
@ -353,7 +380,7 @@ describe('Discover grid cell rendering', function () {
bytesDisplayName
</EuiDescriptionListTitle>
<EuiDescriptionListDescription
className="dscDiscoverGrid__descriptionListDescription"
className="unifiedDataTable__descriptionListDescription"
dangerouslySetInnerHTML={
Object {
"__html": Array [
@ -366,7 +393,7 @@ describe('Discover grid cell rendering', function () {
_index
</EuiDescriptionListTitle>
<EuiDescriptionListDescription
className="dscDiscoverGrid__descriptionListDescription"
className="unifiedDataTable__descriptionListDescription"
dangerouslySetInnerHTML={
Object {
"__html": "test",
@ -377,7 +404,7 @@ describe('Discover grid cell rendering', function () {
_score
</EuiDescriptionListTitle>
<EuiDescriptionListDescription
className="dscDiscoverGrid__descriptionListDescription"
className="unifiedDataTable__descriptionListDescription"
dangerouslySetInnerHTML={
Object {
"__html": 1,
@ -389,17 +416,18 @@ describe('Discover grid cell rendering', function () {
});
it('limits amount of rendered items', () => {
const DiscoverGridCellValue = getRenderCellValueFn(
const DataTableCellValue = getRenderCellValueFn(
dataViewMock,
rowsFields.map(build),
true,
(fieldName) => ['extension', 'bytes'].includes(fieldName),
jest.fn(),
mockServices.fieldFormats as unknown as FieldFormatsStart,
// this is the number of rendered items
1,
jest.fn()
1
);
const component = shallow(
<DiscoverGridCellValue
<DataTableCellValue
rowIndex={0}
colIndex={0}
columnId="_source"
@ -411,7 +439,7 @@ describe('Discover grid cell rendering', function () {
);
expect(component).toMatchInlineSnapshot(`
<EuiDescriptionList
className="dscDiscoverGrid__descriptionList dscDiscoverGrid__cellValue"
className="unifiedDataTable__descriptionList unifiedDataTable__cellValue"
compressed={true}
type="inline"
>
@ -419,7 +447,7 @@ describe('Discover grid cell rendering', function () {
extension
</EuiDescriptionListTitle>
<EuiDescriptionListDescription
className="dscDiscoverGrid__descriptionListDescription"
className="unifiedDataTable__descriptionListDescription"
dangerouslySetInnerHTML={
Object {
"__html": Array [
@ -432,7 +460,7 @@ describe('Discover grid cell rendering', function () {
bytesDisplayName
</EuiDescriptionListTitle>
<EuiDescriptionListDescription
className="dscDiscoverGrid__descriptionListDescription"
className="unifiedDataTable__descriptionListDescription"
dangerouslySetInnerHTML={
Object {
"__html": Array [
@ -445,7 +473,7 @@ describe('Discover grid cell rendering', function () {
_index
</EuiDescriptionListTitle>
<EuiDescriptionListDescription
className="dscDiscoverGrid__descriptionListDescription"
className="unifiedDataTable__descriptionListDescription"
dangerouslySetInnerHTML={
Object {
"__html": "test",
@ -456,7 +484,7 @@ describe('Discover grid cell rendering', function () {
_score
</EuiDescriptionListTitle>
<EuiDescriptionListDescription
className="dscDiscoverGrid__descriptionListDescription"
className="unifiedDataTable__descriptionListDescription"
dangerouslySetInnerHTML={
Object {
"__html": 1,
@ -468,16 +496,17 @@ describe('Discover grid cell rendering', function () {
});
it('renders fields-based column correctly when isDetails is set to true', () => {
const DiscoverGridCellValue = getRenderCellValueFn(
const DataTableCellValue = getRenderCellValueFn(
dataViewMock,
rowsFields.map(build),
true,
(fieldName) => false,
100,
jest.fn()
jest.fn(),
mockServices.fieldFormats as unknown as FieldFormatsStart,
100
);
const component = shallow(
<DiscoverGridCellValue
<DataTableCellValue
rowIndex={0}
colIndex={0}
columnId="_source"
@ -489,7 +518,7 @@ describe('Discover grid cell rendering', function () {
);
expect(component).toMatchInlineSnapshot(`
<EuiFlexGroup
className="dscDiscoverGrid__cellPopover"
className="unifiedDataTable__cellPopover"
direction="column"
gutterSize="none"
justifyContent="flexEnd"
@ -517,7 +546,7 @@ describe('Discover grid cell rendering', function () {
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem>
<ForwardRef
<JsonCodeEditor
height={200}
json={
Object {
@ -548,16 +577,17 @@ describe('Discover grid cell rendering', function () {
});
it('collect object fields and renders them like _source', () => {
const DiscoverGridCellValue = getRenderCellValueFn(
const DataTableCellValue = getRenderCellValueFn(
dataViewMock,
rowsFieldsWithTopLevelObject.map(build),
true,
(fieldName) => ['object.value', 'extension', 'bytes'].includes(fieldName),
100,
jest.fn()
jest.fn(),
mockServices.fieldFormats as unknown as FieldFormatsStart,
100
);
const component = shallow(
<DiscoverGridCellValue
<DataTableCellValue
rowIndex={0}
colIndex={0}
columnId="object"
@ -569,7 +599,7 @@ describe('Discover grid cell rendering', function () {
);
expect(component).toMatchInlineSnapshot(`
<EuiDescriptionList
className="dscDiscoverGrid__descriptionList dscDiscoverGrid__cellValue"
className="unifiedDataTable__descriptionList unifiedDataTable__cellValue"
compressed={true}
type="inline"
>
@ -577,7 +607,7 @@ describe('Discover grid cell rendering', function () {
object.value
</EuiDescriptionListTitle>
<EuiDescriptionListDescription
className="dscDiscoverGrid__descriptionListDescription"
className="unifiedDataTable__descriptionListDescription"
dangerouslySetInnerHTML={
Object {
"__html": "100",
@ -590,16 +620,17 @@ describe('Discover grid cell rendering', function () {
it('collect object fields and renders them like _source with fallback for unmapped', () => {
(dataViewMock.getFieldByName as jest.Mock).mockReturnValueOnce(undefined);
const DiscoverGridCellValue = getRenderCellValueFn(
const DataTableCellValue = getRenderCellValueFn(
dataViewMock,
rowsFieldsWithTopLevelObject.map(build),
true,
(fieldName) => ['extension', 'bytes', 'object.value'].includes(fieldName),
100,
jest.fn()
jest.fn(),
mockServices.fieldFormats as unknown as FieldFormatsStart,
100
);
const component = shallow(
<DiscoverGridCellValue
<DataTableCellValue
rowIndex={0}
colIndex={0}
columnId="object"
@ -611,7 +642,7 @@ describe('Discover grid cell rendering', function () {
);
expect(component).toMatchInlineSnapshot(`
<EuiDescriptionList
className="dscDiscoverGrid__descriptionList dscDiscoverGrid__cellValue"
className="unifiedDataTable__descriptionList unifiedDataTable__cellValue"
compressed={true}
type="inline"
>
@ -619,7 +650,7 @@ describe('Discover grid cell rendering', function () {
object.value
</EuiDescriptionListTitle>
<EuiDescriptionListDescription
className="dscDiscoverGrid__descriptionListDescription"
className="unifiedDataTable__descriptionListDescription"
dangerouslySetInnerHTML={
Object {
"__html": "100",
@ -632,16 +663,17 @@ describe('Discover grid cell rendering', function () {
it('collect object fields and renders them as json in details', () => {
const closePopoverMockFn = jest.fn();
const DiscoverGridCellValue = getRenderCellValueFn(
const DataTableCellValue = getRenderCellValueFn(
dataViewMock,
rowsFieldsWithTopLevelObject.map(build),
true,
() => false,
100,
closePopoverMockFn
closePopoverMockFn,
mockServices.fieldFormats as unknown as FieldFormatsStart,
100
);
const component = shallow(
<DiscoverGridCellValue
<DataTableCellValue
rowIndex={0}
colIndex={0}
columnId="object"
@ -653,7 +685,7 @@ describe('Discover grid cell rendering', function () {
);
expect(component).toMatchInlineSnapshot(`
<EuiFlexGroup
className="dscDiscoverGrid__cellPopover"
className="unifiedDataTable__cellPopover"
direction="column"
gutterSize="none"
justifyContent="flexEnd"
@ -681,7 +713,7 @@ describe('Discover grid cell rendering', function () {
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem>
<ForwardRef
<JsonCodeEditor
height={200}
json={
Object {
@ -699,17 +731,18 @@ describe('Discover grid cell rendering', function () {
it('renders a functional close button when CodeEditor is rendered', () => {
const closePopoverMockFn = jest.fn();
const DiscoverGridCellValue = getRenderCellValueFn(
const DataTableCellValue = getRenderCellValueFn(
dataViewMock,
rowsFieldsWithTopLevelObject.map(build),
true,
() => false,
100,
closePopoverMockFn
closePopoverMockFn,
mockServices.fieldFormats as unknown as FieldFormatsStart,
100
);
const component = mountWithIntl(
<KibanaContextProvider services={mockServices}>
<DiscoverGridCellValue
<DataTableCellValue
rowIndex={0}
colIndex={0}
columnId="object"
@ -727,16 +760,17 @@ describe('Discover grid cell rendering', function () {
it('does not collect subfields when the the column is unmapped but part of fields response', () => {
(dataViewMock.getFieldByName as jest.Mock).mockReturnValueOnce(undefined);
const DiscoverGridCellValue = getRenderCellValueFn(
const DataTableCellValue = getRenderCellValueFn(
dataViewMock,
rowsFieldsWithTopLevelObject.map(build),
true,
() => false,
100,
jest.fn()
jest.fn(),
mockServices.fieldFormats as unknown as FieldFormatsStart,
100
);
const component = shallow(
<DiscoverGridCellValue
<DataTableCellValue
rowIndex={0}
colIndex={0}
columnId="object.value"
@ -748,7 +782,7 @@ describe('Discover grid cell rendering', function () {
);
expect(component).toMatchInlineSnapshot(`
<span
className="dscDiscoverGrid__cellValue"
className="unifiedDataTable__cellValue"
dangerouslySetInnerHTML={
Object {
"__html": Array [
@ -761,16 +795,17 @@ describe('Discover grid cell rendering', function () {
});
it('renders correctly when invalid row is given', () => {
const DiscoverGridCellValue = getRenderCellValueFn(
const DataTableCellValue = getRenderCellValueFn(
dataViewMock,
rowsSource.map(build),
false,
() => false,
100,
jest.fn()
jest.fn(),
mockServices.fieldFormats as unknown as FieldFormatsStart,
100
);
const component = shallow(
<DiscoverGridCellValue
<DataTableCellValue
rowIndex={1}
colIndex={1}
columnId="bytes"
@ -781,21 +816,22 @@ describe('Discover grid cell rendering', function () {
/>
);
expect(component.html()).toMatchInlineSnapshot(
`"<span class=\\"dscDiscoverGrid__cellValue\\">-</span>"`
`"<span class=\\"unifiedDataTable__cellValue\\">-</span>"`
);
});
it('renders correctly when invalid column is given', () => {
const DiscoverGridCellValue = getRenderCellValueFn(
const DataTableCellValue = getRenderCellValueFn(
dataViewMock,
rowsSource.map(build),
false,
() => false,
100,
jest.fn()
jest.fn(),
mockServices.fieldFormats as unknown as FieldFormatsStart,
100
);
const component = shallow(
<DiscoverGridCellValue
<DataTableCellValue
rowIndex={0}
colIndex={0}
columnId="bytes-invalid"
@ -806,7 +842,7 @@ describe('Discover grid cell rendering', function () {
/>
);
expect(component.html()).toMatchInlineSnapshot(
`"<span class=\\"dscDiscoverGrid__cellValue\\">-</span>"`
`"<span class=\\"unifiedDataTable__cellValue\\">-</span>"`
);
});
@ -824,16 +860,17 @@ describe('Discover grid cell rendering', function () {
},
},
];
const DiscoverGridCellValue = getRenderCellValueFn(
const DataTableCellValue = getRenderCellValueFn(
dataViewMock,
rowsFieldsUnmapped.map(build),
true,
(fieldName) => ['unmapped'].includes(fieldName),
100,
jest.fn()
jest.fn(),
mockServices.fieldFormats as unknown as FieldFormatsStart,
100
);
const component = shallow(
<DiscoverGridCellValue
<DataTableCellValue
rowIndex={0}
colIndex={0}
columnId="unmapped"
@ -845,7 +882,7 @@ describe('Discover grid cell rendering', function () {
);
expect(component).toMatchInlineSnapshot(`
<span
className="dscDiscoverGrid__cellValue"
className="unifiedDataTable__cellValue"
dangerouslySetInnerHTML={
Object {
"__html": Array [
@ -857,7 +894,7 @@ describe('Discover grid cell rendering', function () {
`);
const componentWithDetails = shallow(
<DiscoverGridCellValue
<DataTableCellValue
rowIndex={0}
colIndex={0}
columnId="unmapped"
@ -875,7 +912,7 @@ describe('Discover grid cell rendering', function () {
>
<EuiFlexItem>
<span
className="dscDiscoverGrid__cellPopoverValue eui-textBreakWord"
className="unifiedDataTable__cellPopoverValue eui-textBreakWord"
dangerouslySetInnerHTML={
Object {
"__html": Array [

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import React, { Fragment, useContext, useEffect, useMemo } from 'react';
import React, { Fragment, useContext, useEffect } from 'react';
import classnames from 'classnames';
import { i18n } from '@kbn/i18n';
import { euiLightVars as themeLight, euiDarkVars as themeDark } from '@kbn/ui-theme';
@ -20,19 +20,18 @@ import {
EuiFlexGroup,
EuiFlexItem,
} from '@elastic/eui';
import { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import type {
DataTableRecord,
EsHitRecord,
ShouldShowFieldInTableHandler,
} from '@kbn/discover-utils/types';
import { MAX_DOC_FIELDS_DISPLAYED, formatFieldValue, formatHit } from '@kbn/discover-utils';
import { JsonCodeEditor } from '@kbn/unified-doc-viewer-plugin/public';
import { DiscoverGridContext } from './discover_grid_context';
import { defaultMonacoEditorWidth } from './constants';
import { useDiscoverServices } from '../../hooks/use_discover_services';
import { formatFieldValue, formatHit } from '@kbn/discover-utils';
import { UnifiedDataTableContext } from '../table_context';
import { defaultMonacoEditorWidth } from '../constants';
import JsonCodeEditor from '../components/json_code_editor/json_code_editor';
const CELL_CLASS = 'dscDiscoverGrid__cellValue';
const CELL_CLASS = 'unifiedDataTable__cellValue';
export const getRenderCellValueFn =
(
@ -40,18 +39,42 @@ export const getRenderCellValueFn =
rows: DataTableRecord[] | undefined,
useNewFieldsApi: boolean,
shouldShowFieldHandler: ShouldShowFieldInTableHandler,
maxDocFieldsDisplayed: number,
closePopover: () => void
closePopover: () => void,
fieldFormats: FieldFormatsStart,
maxEntries: number,
externalCustomRenderers?: Record<
string,
(props: EuiDataGridCellValueElementProps) => React.ReactNode
>
) =>
({ rowIndex, columnId, isDetails, setCellProps }: EuiDataGridCellValueElementProps) => {
const { uiSettings, fieldFormats } = useDiscoverServices();
const maxEntries = useMemo(() => uiSettings.get(MAX_DOC_FIELDS_DISPLAYED), [uiSettings]);
({
rowIndex,
columnId,
isDetails,
setCellProps,
colIndex,
isExpandable,
isExpanded,
}: EuiDataGridCellValueElementProps) => {
if (!!externalCustomRenderers && !!externalCustomRenderers[columnId]) {
return (
<>
{externalCustomRenderers[columnId]({
rowIndex,
columnId,
isDetails,
setCellProps,
isExpandable,
isExpanded,
colIndex,
})}
</>
);
}
const row = rows ? rows[rowIndex] : undefined;
const field = dataView.fields.getByName(columnId);
const ctx = useContext(DiscoverGridContext);
const ctx = useContext(UnifiedDataTableContext);
useEffect(() => {
if (row?.isAnchor) {
@ -102,7 +125,7 @@ export const getRenderCellValueFn =
const pairs = useTopLevelObjectColumns
? getTopLevelObjectPairs(row.raw, columnId, dataView, shouldShowFieldHandler).slice(
0,
maxDocFieldsDisplayed
maxEntries
)
: formatHit(row, dataView, shouldShowFieldHandler, maxEntries, fieldFormats);
@ -110,13 +133,13 @@ export const getRenderCellValueFn =
<EuiDescriptionList
type="inline"
compressed
className={classnames('dscDiscoverGrid__descriptionList', CELL_CLASS)}
className={classnames('unifiedDataTable__descriptionList', CELL_CLASS)}
>
{pairs.map(([key, value]) => (
<Fragment key={key}>
<EuiDescriptionListTitle>{key}</EuiDescriptionListTitle>
<EuiDescriptionListDescription
className="dscDiscoverGrid__descriptionListDescription"
className="unifiedDataTable__descriptionListDescription"
dangerouslySetInnerHTML={{ __html: value }}
/>
</Fragment>
@ -144,7 +167,7 @@ export const getRenderCellValueFn =
function getInnerColumns(fields: Record<string, unknown[]>, columnId: string) {
return Object.fromEntries(
Object.entries(fields).filter(([key]) => {
return key.indexOf(`${columnId}.`) === 0;
return key.startsWith(`${columnId}.`);
})
);
}
@ -178,7 +201,7 @@ function renderPopoverContent({
}) {
const closeButton = (
<EuiButtonIcon
aria-label={i18n.translate('discover.grid.closePopover', {
aria-label={i18n.translate('unifiedDataTable.grid.closePopover', {
defaultMessage: `Close popover`,
})}
data-test-subj="docTableClosePopover"
@ -194,7 +217,7 @@ function renderPopoverContent({
gutterSize="none"
direction="column"
justifyContent="flexEnd"
className="dscDiscoverGrid__cellPopover"
className="unifiedDataTable__cellPopover"
>
<EuiFlexItem grow={false}>
<EuiFlexGroup justifyContent="flexEnd" gutterSize="none" responsive={false}>
@ -216,7 +239,7 @@ function renderPopoverContent({
<EuiFlexGroup gutterSize="none" direction="row" responsive={false}>
<EuiFlexItem>
<span
className="dscDiscoverGrid__cellPopoverValue eui-textBreakWord"
className="unifiedDataTable__cellPopoverValue eui-textBreakWord"
// formatFieldValue guarantees sanitized values
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{
@ -257,7 +280,7 @@ function getTopLevelObjectPairs(
const formatter = subField
? dataView.getFormatterForField(subField)
: { convert: (v: unknown, ...rest: unknown[]) => String(v) };
const formatted = (values as unknown[])
const formatted = values
.map((val: unknown) =>
formatter.convert(val, 'html', {
field: subField,

View file

@ -7,8 +7,8 @@
*/
import type { Capabilities } from '@kbn/core/public';
import { DataViewsContract } from '@kbn/data-plugin/public';
import { DataView } from '@kbn/data-views-plugin/public';
import type { DataViewsContract } from '@kbn/data-plugin/public';
import type { DataView } from '@kbn/data-views-plugin/public';
async function popularizeField(
dataView: DataView,

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import { Storage } from '@kbn/kibana-utils-plugin/public';
import type { Storage } from '@kbn/kibana-utils-plugin/public';
import { isValidRowHeight } from './validate_row_height';
export interface DataGridOptionsRecord {
@ -14,10 +14,13 @@ export interface DataGridOptionsRecord {
previousConfigRowHeight: number;
}
const ROW_HEIGHT_KEY = 'discover:dataGridRowHeight';
const getRowHeightKey = (consumer: string) => `${consumer}:dataGridRowHeight`;
export const getStoredRowHeight = (storage: Storage): DataGridOptionsRecord | null => {
const entry = storage.get(ROW_HEIGHT_KEY);
export const getStoredRowHeight = (
storage: Storage,
consumer: string
): DataGridOptionsRecord | null => {
const entry = storage.get(getRowHeightKey(consumer));
if (
typeof entry === 'object' &&
entry !== null &&
@ -32,9 +35,10 @@ export const getStoredRowHeight = (storage: Storage): DataGridOptionsRecord | nu
export const updateStoredRowHeight = (
newRowHeight: number,
configRowHeight: number,
storage: Storage
storage: Storage,
consumer: string
) => {
storage.set(ROW_HEIGHT_KEY, {
storage.set(getRowHeightKey(consumer), {
previousRowHeight: newRowHeight,
previousConfigRowHeight: configRowHeight,
});

View file

@ -6,9 +6,7 @@
* Side Public License, v 1.
*/
import { discoverServiceMock } from '../__mocks__/services';
import { SAMPLE_ROWS_PER_PAGE_SETTING } from '@kbn/discover-utils';
import { getRowsPerPageOptions, getDefaultRowsPerPage } from './rows_per_page';
import { getRowsPerPageOptions } from './rows_per_page';
const SORTED_OPTIONS = [10, 25, 50, 100, 250, 500];
@ -26,17 +24,4 @@ describe('rows per page', () => {
expect(getRowsPerPageOptions(350)).toEqual([10, 25, 50, 100, 250, 350, 500]);
});
});
describe('getDefaultRowsPerPage', () => {
it('should return a value from settings', () => {
expect(getDefaultRowsPerPage(discoverServiceMock.uiSettings)).toEqual(150);
expect(discoverServiceMock.uiSettings.get).toHaveBeenCalledWith(SAMPLE_ROWS_PER_PAGE_SETTING);
});
it('should return a default value', () => {
expect(getDefaultRowsPerPage({ ...discoverServiceMock.uiSettings, get: jest.fn() })).toEqual(
100
);
});
});
});

View file

@ -7,9 +7,8 @@
*/
import { sortBy, uniq } from 'lodash';
import { SAMPLE_ROWS_PER_PAGE_SETTING } from '@kbn/discover-utils';
import { DEFAULT_ROWS_PER_PAGE, ROWS_PER_PAGE_OPTIONS } from '../../common/constants';
import { DiscoverServices } from '../build_services';
import { ROWS_PER_PAGE_OPTIONS } from '../constants';
export const getRowsPerPageOptions = (currentRowsPerPage?: number): number[] => {
return sortBy(
@ -20,7 +19,3 @@ export const getRowsPerPageOptions = (currentRowsPerPage?: number): number[] =>
)
);
};
export const getDefaultRowsPerPage = (uiSettings: DiscoverServices['uiSettings']): number => {
return parseInt(uiSettings.get(SAMPLE_ROWS_PER_PAGE_SETTING), 10) || DEFAULT_ROWS_PER_PAGE;
};

View file

@ -0,0 +1,37 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "target/types"
},
"include": ["*.ts", "**/*.tsx", "src/**/*", "__mocks__/**/*.ts"],
"exclude": [
"target/**/*"
],
"kbn_references": [
"@kbn/i18n",
"@kbn/data-views-plugin",
"@kbn/unified-doc-viewer",
"@kbn/discover-utils",
"@kbn/kibana-utils-plugin",
"@kbn/expressions-plugin",
"@kbn/test-jest-helpers",
"@kbn/i18n-react",
"@kbn/ui-theme",
"@kbn/field-types",
"@kbn/kibana-utils-plugin",
"@kbn/cell-actions",
"@kbn/utility-types",
"@kbn/data-view-field-editor-plugin",
"@kbn/field-formats-plugin",
"@kbn/react-kibana-context-common",
"@kbn/data-plugin",
"@kbn/core",
"@kbn/ui-actions-plugin",
"@kbn/charts-plugin",
"@kbn/kibana-react-plugin",
"@kbn/monaco",
"@kbn/code-editor",
"@kbn/config",
"@kbn/monaco",
]
}

View file

@ -351,7 +351,7 @@ exports[`<CodeEditor /> is rendered 1`] = `
<mockMonacoEditor
editorDidMount={[Function]}
editorWillMount={[Function]}
height="100px"
height={250}
language="loglang"
onChange={[Function]}
options={

View file

@ -137,6 +137,7 @@ export const CodeEditor: React.FC<Props> = ({
value,
onChange,
width,
height = '100px',
options,
overrideEditorWillMount,
editorDidMount,
@ -478,7 +479,7 @@ export const CodeEditor: React.FC<Props> = ({
onChange={onChange}
width={isFullScreen ? '100vw' : width}
// previously defaulted to height which defaulted to 100% but this makes it unviewable
height={isFullScreen ? '100vh' : '100px'}
height={isFullScreen ? '100vh' : height}
editorWillMount={_editorWillMount}
editorDidMount={_editorDidMount}
options={{

View file

@ -6,12 +6,18 @@
* Side Public License, v 1.
*/
export const MAX_LOADED_GRID_ROWS = 10000;
import { SAMPLE_ROWS_PER_PAGE_SETTING } from '@kbn/discover-utils';
import { IUiSettingsClient } from '@kbn/core/public';
export const DEFAULT_ROWS_PER_PAGE = 100;
export const ROWS_PER_PAGE_OPTIONS = [10, 25, 50, DEFAULT_ROWS_PER_PAGE, 250, 500];
export enum VIEW_MODE {
DOCUMENT_LEVEL = 'documents',
AGGREGATED_LEVEL = 'aggregated',
}
export const DISABLE_SHARD_FAILURE_WARNING = true;
export const getDefaultRowsPerPage = (uiSettings: IUiSettingsClient): number => {
return parseInt(uiSettings.get(SAMPLE_ROWS_PER_PAGE_SETTING), 10) || DEFAULT_ROWS_PER_PAGE;
};

View file

@ -18,15 +18,18 @@ import { generateFilters } from '@kbn/data-plugin/public';
import { i18n } from '@kbn/i18n';
import { reportPerformanceMetricEvent } from '@kbn/ebt-tools';
import { removeInterceptedWarningDuplicates } from '@kbn/search-response-warnings';
import { DOC_TABLE_LEGACY, SEARCH_FIELDS_FROM_SOURCE } from '@kbn/discover-utils';
import type { DocViewFilterFn } from '@kbn/unified-doc-viewer/types';
import {
DOC_TABLE_LEGACY,
SEARCH_FIELDS_FROM_SOURCE,
SORT_DEFAULT_ORDER_SETTING,
} from '@kbn/discover-utils';
import { popularizeField, useColumns } from '@kbn/unified-data-table';
import { DocViewFilterFn } from '@kbn/unified-doc-viewer/types';
import { ContextErrorMessage } from './components/context_error_message';
import { LoadingStatus } from './services/context_query_state';
import { AppState, GlobalState, isEqualFilters } from './services/context_state';
import { useColumns } from '../../hooks/use_data_grid_columns';
import { useContextAppState } from './hooks/use_context_app_state';
import { useContextAppFetch } from './hooks/use_context_app_fetch';
import { popularizeField } from '../../utils/popularize_field';
import { ContextAppContent } from './context_app_content';
import { SurrDocType } from './services/context';
import { useDiscoverServices } from '../../hooks/use_discover_services';
@ -68,7 +71,7 @@ export const ContextApp = ({ dataView, anchorId, referrer }: ContextAppProps) =>
const { columns, onAddColumn, onRemoveColumn, onSetColumns } = useColumns({
capabilities,
config: uiSettings,
defaultOrder: uiSettings.get(SORT_DEFAULT_ORDER_SETTING),
dataView,
dataViews,
useNewFieldsApi,

View file

@ -12,11 +12,11 @@ import { findTestSubject } from '@elastic/eui/lib/test';
import { ActionBar } from './components/action_bar/action_bar';
import { GetStateReturn } from './services/context_state';
import { SortDirection } from '@kbn/data-plugin/public';
import { UnifiedDataTable } from '@kbn/unified-data-table';
import { ContextAppContent, ContextAppContentProps } from './context_app_content';
import { LoadingStatus } from './services/context_query_state';
import { dataViewMock } from '@kbn/discover-utils/src/__mocks__';
import { discoverServiceMock } from '../../__mocks__/services';
import { DiscoverGrid } from '../../components/discover_grid/discover_grid';
import { DocTableWrapper } from '../../components/doc_table/doc_table_wrapper';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import { buildDataTableRecord } from '@kbn/discover-utils';
@ -103,6 +103,6 @@ describe('ContextAppContent test', () => {
it('should render discover grid correctly', async () => {
const component = await mountComponent({ isLegacy: false });
expect(component.find(DiscoverGrid).length).toBe(1);
expect(component.find(UnifiedDataTable).length).toBe(1);
});
});

View file

@ -18,17 +18,25 @@ import {
type SearchResponseInterceptedWarning,
SearchResponseWarnings,
} from '@kbn/search-response-warnings';
import { CONTEXT_STEP_SETTING, DOC_HIDE_TIME_COLUMN_SETTING } from '@kbn/discover-utils';
import type { DocViewFilterFn } from '@kbn/unified-doc-viewer/types';
import {
CONTEXT_STEP_SETTING,
DOC_HIDE_TIME_COLUMN_SETTING,
MAX_DOC_FIELDS_DISPLAYED,
ROW_HEIGHT_OPTION,
SHOW_MULTIFIELDS,
} from '@kbn/discover-utils';
import { DataLoadingState, UnifiedDataTable } from '@kbn/unified-data-table';
import { DocViewFilterFn } from '@kbn/unified-doc-viewer/types';
import { getDefaultRowsPerPage } from '../../../common/constants';
import { LoadingStatus } from './services/context_query_state';
import { ActionBar } from './components/action_bar/action_bar';
import { DataLoadingState, DiscoverGrid } from '../../components/discover_grid/discover_grid';
import { AppState } from './services/context_state';
import { SurrDocType } from './services/context';
import { MAX_CONTEXT_SIZE, MIN_CONTEXT_SIZE } from './services/constants';
import { DocTableContext } from '../../components/doc_table/doc_table_context';
import { useDiscoverServices } from '../../hooks/use_discover_services';
import { DiscoverGridFlyout } from '../../components/discover_grid/discover_grid_flyout';
import { DiscoverGridFlyout } from '../../components/discover_grid_flyout';
import { DISCOVER_TOUR_STEP_ANCHOR_IDS } from '../../components/discover_tour';
export interface ContextAppContentProps {
columns: string[];
@ -57,7 +65,7 @@ export function clamp(value: number) {
return Math.max(Math.min(MAX_CONTEXT_SIZE, value), MIN_CONTEXT_SIZE);
}
const DiscoverGridMemoized = React.memo(DiscoverGrid);
const DiscoverGridMemoized = React.memo(UnifiedDataTable);
const DocTableContextMemoized = React.memo(DocTableContext);
const ActionBarMemoized = React.memo(ActionBar);
@ -121,6 +129,24 @@ export function ContextAppContent({
return [[dataView.timeFieldName!, SortDirection.desc]];
}, [dataView]);
const renderDocumentView = useCallback(
(hit: DataTableRecord, displayedRows: DataTableRecord[], displayedColumns: string[]) => (
<DiscoverGridFlyout
dataView={dataView}
hit={hit}
hits={displayedRows}
// if default columns are used, dont make them part of the URL - the context state handling will take care to restore them
columns={displayedColumns}
onFilter={addFilter}
onRemoveColumn={onRemoveColumn}
onAddColumn={onAddColumn}
onClose={() => setExpandedDoc(undefined)}
setExpandedDoc={setExpandedDoc}
/>
),
[addFilter, dataView, onAddColumn, onRemoveColumn]
);
return (
<Fragment>
{!!interceptedWarnings?.length && (
@ -174,14 +200,17 @@ export function ContextAppContent({
showTimeCol={showTimeCol}
useNewFieldsApi={useNewFieldsApi}
isPaginationEnabled={false}
rowsPerPageState={getDefaultRowsPerPage(services.uiSettings)}
controlColumnIds={controlColumnIds}
setExpandedDoc={setExpandedDoc}
onFilter={addFilter}
onAddColumn={onAddColumn}
onRemoveColumn={onRemoveColumn}
onSetColumns={onSetColumns}
DocumentView={DiscoverGridFlyout}
configRowHeight={services.uiSettings.get(ROW_HEIGHT_OPTION)}
showMultiFields={services.uiSettings.get(SHOW_MULTIFIELDS)}
maxDocFieldsDisplayed={services.uiSettings.get(MAX_DOC_FIELDS_DISPLAYED)}
renderDocumentView={renderDocumentView}
services={services}
componentsTourSteps={{ expandButton: DISCOVER_TOUR_STEP_ANCHOR_IDS.expandDocument }}
/>
</CellActionsProvider>
</div>

View file

@ -21,29 +21,36 @@ import { SortOrder } from '@kbn/saved-search-plugin/public';
import { CellActionsProvider } from '@kbn/cell-actions';
import type { DataTableRecord } from '@kbn/discover-utils/types';
import { SearchResponseWarnings } from '@kbn/search-response-warnings';
import { DataLoadingState, UnifiedDataTable, useColumns } from '@kbn/unified-data-table';
import {
DOC_HIDE_TIME_COLUMN_SETTING,
DOC_TABLE_LEGACY,
HIDE_ANNOUNCEMENTS,
MAX_DOC_FIELDS_DISPLAYED,
ROW_HEIGHT_OPTION,
SAMPLE_SIZE_SETTING,
SEARCH_FIELDS_FROM_SOURCE,
SHOW_MULTIFIELDS,
SORT_DEFAULT_ORDER_SETTING,
} from '@kbn/discover-utils';
import type { DocViewFilterFn } from '@kbn/unified-doc-viewer/types';
import { getDefaultRowsPerPage } from '../../../../../common/constants';
import { useInternalStateSelector } from '../../services/discover_internal_state_container';
import { useAppStateSelector } from '../../services/discover_app_state_container';
import { useDiscoverServices } from '../../../../hooks/use_discover_services';
import { DataLoadingState, DiscoverGrid } from '../../../../components/discover_grid/discover_grid';
import { FetchStatus } from '../../../types';
import { useColumns } from '../../../../hooks/use_data_grid_columns';
import { RecordRawType } from '../../services/discover_data_state_container';
import { DiscoverStateContainer } from '../../services/discover_state';
import { useDataState } from '../../hooks/use_data_state';
import { DocTableInfinite } from '../../../../components/doc_table/doc_table_infinite';
import { DocumentExplorerCallout } from '../document_explorer_callout';
import { DocumentExplorerUpdateCallout } from '../document_explorer_callout/document_explorer_update_callout';
import { DiscoverTourProvider } from '../../../../components/discover_tour';
import {
DISCOVER_TOUR_STEP_ANCHOR_IDS,
DiscoverTourProvider,
} from '../../../../components/discover_tour';
import { getRawRecordType } from '../../utils/get_raw_record_type';
import { DiscoverGridFlyout } from '../../../../components/discover_grid/discover_grid_flyout';
import { DiscoverGridFlyout } from '../../../../components/discover_grid_flyout';
import { useSavedSearchInitial } from '../../services/discover_state_provider';
import { useFetchMoreRecords } from './use_fetch_more_records';
@ -56,7 +63,7 @@ const progressStyle = css`
`;
const DocTableInfiniteMemoized = React.memo(DocTableInfinite);
const DiscoverGridMemoized = React.memo(DiscoverGrid);
const DataGridMemoized = React.memo(UnifiedDataTable);
// export needs for testing
export const onResize = (
@ -147,7 +154,7 @@ function DiscoverDocumentsComponent({
onSetColumns,
} = useColumns({
capabilities,
config: uiSettings,
defaultOrder: uiSettings.get(SORT_DEFAULT_ORDER_SETTING),
dataView,
dataViews,
setAppState: stateContainer.appState.update,
@ -191,6 +198,26 @@ function DiscoverDocumentsComponent({
[isTextBasedQuery, columns, uiSettings, dataView.timeFieldName]
);
const renderDocumentView = useCallback(
(hit: DataTableRecord, displayedRows: DataTableRecord[], displayedColumns: string[]) => (
<DiscoverGridFlyout
dataView={dataView}
hit={hit}
hits={displayedRows}
// if default columns are used, dont make them part of the URL - the context state handling will take care to restore them
columns={displayedColumns}
savedSearchId={savedSearch.id}
onFilter={onAddFilter}
onRemoveColumn={onRemoveColumn}
onAddColumn={onAddColumn}
onClose={() => setExpandedDoc(undefined)}
setExpandedDoc={setExpandedDoc}
query={query}
/>
),
[dataView, onAddColumn, onAddFilter, onRemoveColumn, query, savedSearch.id, setExpandedDoc]
);
if (isDataViewLoading || (isEmptyDataResult && isDataLoading)) {
return (
<div className="dscDocuments__loading">
@ -217,7 +244,7 @@ function DiscoverDocumentsComponent({
data-test-subj="dscInterceptedWarningsCallout"
/>
)}
{isLegacy && rows && rows.length && (
{isLegacy && rows && rows.length > 0 && (
<>
{!hideAnnouncements && <DocumentExplorerCallout />}
<DocTableInfiniteMemoized
@ -246,11 +273,11 @@ function DiscoverDocumentsComponent({
<DocumentExplorerUpdateCallout />
</DiscoverTourProvider>
)}
<div className="dscDiscoverGrid">
<div className="unifiedDataTable">
<CellActionsProvider
getTriggerCompatibleActions={uiActions.getTriggerCompatibleActions}
>
<DiscoverGridMemoized
<DataGridMemoized
ariaLabelledBy="documentsAriaLabel"
columns={currentColumns}
expandedDoc={expandedDoc}
@ -270,9 +297,7 @@ function DiscoverDocumentsComponent({
setExpandedDoc={setExpandedDoc}
showTimeCol={showTimeCol}
settings={grid}
onAddColumn={onAddColumn}
onFilter={onAddFilter as DocViewFilterFn}
onRemoveColumn={onRemoveColumn}
onSetColumns={onSetColumns}
onSort={!isTextBasedQuery ? onSort : undefined}
onResize={onResizeDataGrid}
@ -281,15 +306,17 @@ function DiscoverDocumentsComponent({
onUpdateRowHeight={onUpdateRowHeight}
isSortEnabled={isTextBasedQuery ? Boolean(currentColumns.length) : true}
isPlainRecord={isTextBasedQuery}
query={query}
rowsPerPageState={rowsPerPage}
rowsPerPageState={rowsPerPage ?? getDefaultRowsPerPage(services.uiSettings)}
onUpdateRowsPerPage={onUpdateRowsPerPage}
onFieldEdited={onFieldEdited}
savedSearchId={savedSearch.id}
DocumentView={DiscoverGridFlyout}
configRowHeight={uiSettings.get(ROW_HEIGHT_OPTION)}
showMultiFields={uiSettings.get(SHOW_MULTIFIELDS)}
maxDocFieldsDisplayed={uiSettings.get(MAX_DOC_FIELDS_DISPLAYED)}
renderDocumentView={renderDocumentView}
services={services}
totalHits={totalHits}
onFetchMoreRecords={onFetchMoreRecords}
componentsTourSteps={{ expandButton: DISCOVER_TOUR_STEP_ANCHOR_IDS.expandDocument }}
/>
</CellActionsProvider>
</div>

View file

@ -23,8 +23,13 @@ import classNames from 'classnames';
import { generateFilters } from '@kbn/data-plugin/public';
import { useDragDropContext } from '@kbn/dom-drag-drop';
import { DataViewField, DataViewType } from '@kbn/data-views-plugin/public';
import { SEARCH_FIELDS_FROM_SOURCE, SHOW_FIELD_STATISTICS } from '@kbn/discover-utils';
import type { DocViewFilterFn } from '@kbn/unified-doc-viewer/types';
import {
SEARCH_FIELDS_FROM_SOURCE,
SHOW_FIELD_STATISTICS,
SORT_DEFAULT_ORDER_SETTING,
} from '@kbn/discover-utils';
import { popularizeField, useColumns } from '@kbn/unified-data-table';
import { DocViewFilterFn } from '@kbn/unified-doc-viewer/types';
import { useSavedSearchInitial } from '../../services/discover_state_provider';
import { DiscoverStateContainer } from '../../services/discover_state';
import { VIEW_MODE } from '../../../../../common/constants';
@ -35,12 +40,10 @@ import { useDiscoverServices } from '../../../../hooks/use_discover_services';
import { DiscoverNoResults } from '../no_results';
import { LoadingSpinner } from '../loading_spinner/loading_spinner';
import { DiscoverSidebarResponsive } from '../sidebar';
import { popularizeField } from '../../../../utils/popularize_field';
import { DiscoverTopNav } from '../top_nav/discover_topnav';
import { getResultState } from '../../utils/get_result_state';
import { DiscoverUninitialized } from '../uninitialized/uninitialized';
import { DataMainMsg, RecordRawType } from '../../services/discover_data_state_container';
import { useColumns } from '../../../../hooks/use_data_grid_columns';
import { FetchStatus } from '../../../types';
import { useDataState } from '../../hooks/use_data_state';
import { getRawRecordType } from '../../utils/get_raw_record_type';
@ -127,7 +130,7 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) {
onRemoveColumn,
} = useColumns({
capabilities,
config: uiSettings,
defaultOrder: uiSettings.get(SORT_DEFAULT_ORDER_SETTING),
dataView,
dataViews,
setAppState: stateContainer.appState.update,

View file

@ -23,12 +23,12 @@ import { SavedSearch, VIEW_MODE } from '@kbn/saved-search-plugin/public';
import { IKbnUrlStateStorage, ISyncStateRef, syncState } from '@kbn/kibana-utils-plugin/public';
import { isEqual } from 'lodash';
import { connectToQueryState, syncGlobalQueryStateWithUrl } from '@kbn/data-plugin/public';
import type { UnifiedDataTableSettings } from '@kbn/unified-data-table';
import type { DiscoverServices } from '../../../build_services';
import { addLog } from '../../../utils/add_log';
import { cleanupUrlState } from '../utils/cleanup_url_state';
import { getStateDefaults } from '../utils/get_state_defaults';
import { handleSourceColumnState } from '../../../utils/state_helpers';
import type { DiscoverGridSettings } from '../../../components/discover_grid/types';
export const APP_STATE_URL_KEY = '_a';
export interface DiscoverAppStateContainer extends ReduxLikeStateContainer<DiscoverAppState> {
@ -87,7 +87,7 @@ export interface DiscoverAppState {
/**
* Data Grid related state
*/
grid?: DiscoverGridSettings;
grid?: UnifiedDataTableSettings;
/**
* Hide chart
*/

View file

@ -12,7 +12,7 @@ import { isCompleteResponse, ISearchSource } from '@kbn/data-plugin/public';
import { SAMPLE_SIZE_SETTING, buildDataTableRecordList } from '@kbn/discover-utils';
import type { EsHitRecord } from '@kbn/discover-utils/types';
import { getSearchResponseInterceptedWarnings } from '@kbn/search-response-warnings';
import type { RecordsFetchResponse } from '../../../types';
import type { RecordsFetchResponse } from '../../types';
import { DISABLE_SHARD_FAILURE_WARNING } from '../../../../common/constants';
import { FetchDeps } from './fetch_all';

View file

@ -15,7 +15,7 @@ import type { Datatable } from '@kbn/expressions-plugin/public';
import type { DataView } from '@kbn/data-views-plugin/common';
import { textBasedQueryStateToAstWithValidation } from '@kbn/data-plugin/common';
import type { DataTableRecord } from '@kbn/discover-utils/types';
import { RecordsFetchResponse } from '../../../types';
import type { RecordsFetchResponse } from '../../types';
interface TextBasedErrorResponse {
error: {

View file

@ -6,6 +6,10 @@
* Side Public License, v 1.
*/
import type { DatatableColumn } from '@kbn/expressions-plugin/common';
import type { DataTableRecord } from '@kbn/discover-utils/types';
import type { SearchResponseInterceptedWarning } from '@kbn/search-response-warnings';
export enum FetchStatus {
UNINITIALIZED = 'uninitialized',
LOADING = 'loading',
@ -16,3 +20,10 @@ export enum FetchStatus {
}
export type DiscoverDisplayMode = 'embedded' | 'standalone';
export interface RecordsFetchResponse {
records: DataTableRecord[];
textBasedQueryColumns?: DatatableColumn[];
textBasedHeaderWarning?: string;
interceptedWarnings?: SearchResponseInterceptedWarning[];
}

View file

@ -1,159 +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 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 or the Server
* Side Public License, v 1.
*/
const mockCopyToClipboard = jest.fn((value) => true);
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
...original,
copyToClipboard: (value: string) => mockCopyToClipboard(value),
};
});
jest.mock('../../hooks/use_discover_services', () => {
const services = {
toastNotifications: {
addInfo: jest.fn(),
},
};
const originalModule = jest.requireActual('../../hooks/use_discover_services');
return {
...originalModule,
useDiscoverServices: () => services,
};
});
import React from 'react';
import { mountWithIntl } from '@kbn/test-jest-helpers';
import { findTestSubject } from '@elastic/eui/lib/test';
import { FilterInBtn, FilterOutBtn, buildCellActions, CopyBtn } from './discover_grid_cell_actions';
import { DiscoverGridContext } from './discover_grid_context';
import { EuiButton } from '@elastic/eui';
import { discoverGridContextMock } from '../../__mocks__/grid_context';
import { DataViewField } from '@kbn/data-views-plugin/public';
describe('Discover cell actions ', function () {
it('should not show cell actions for unfilterable fields', async () => {
expect(buildCellActions({ name: 'foo', filterable: false } as DataViewField)).toEqual([
CopyBtn,
]);
});
it('should show filter actions for filterable fields', async () => {
expect(buildCellActions({ name: 'foo', filterable: true } as DataViewField, jest.fn())).toEqual(
[FilterInBtn, FilterOutBtn, CopyBtn]
);
});
it('should show Copy action for _source field', async () => {
expect(
buildCellActions({ name: '_source', type: '_source', filterable: false } as DataViewField)
).toEqual([CopyBtn]);
});
it('triggers filter function when FilterInBtn is clicked', async () => {
const component = mountWithIntl(
<DiscoverGridContext.Provider value={discoverGridContextMock}>
<FilterInBtn
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Component={(props: any) => <EuiButton {...props} />}
rowIndex={1}
colIndex={1}
columnId="extension"
isExpanded={false}
/>
</DiscoverGridContext.Provider>
);
const button = findTestSubject(component, 'filterForButton');
await button.simulate('click');
expect(discoverGridContextMock.onFilter).toHaveBeenCalledWith(
discoverGridContextMock.dataView.fields.getByName('extension'),
'jpg',
'+'
);
});
it('triggers filter function when FilterInBtn is clicked for a non-provided value', async () => {
const component = mountWithIntl(
<DiscoverGridContext.Provider value={discoverGridContextMock}>
<FilterInBtn
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Component={(props: any) => <EuiButton {...props} />}
rowIndex={0}
colIndex={1}
columnId="extension"
isExpanded={false}
/>
</DiscoverGridContext.Provider>
);
const button = findTestSubject(component, 'filterForButton');
await button.simulate('click');
expect(discoverGridContextMock.onFilter).toHaveBeenCalledWith(
discoverGridContextMock.dataView.fields.getByName('extension'),
undefined,
'+'
);
});
it('triggers filter function when FilterInBtn is clicked for an empty string value', async () => {
const component = mountWithIntl(
<DiscoverGridContext.Provider value={discoverGridContextMock}>
<FilterInBtn
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Component={(props: any) => <EuiButton {...props} />}
rowIndex={4}
colIndex={1}
columnId="message"
isExpanded={false}
/>
</DiscoverGridContext.Provider>
);
const button = findTestSubject(component, 'filterForButton');
await button.simulate('click');
expect(discoverGridContextMock.onFilter).toHaveBeenCalledWith(
discoverGridContextMock.dataView.fields.getByName('message'),
'',
'+'
);
});
it('triggers filter function when FilterOutBtn is clicked', async () => {
const component = mountWithIntl(
<DiscoverGridContext.Provider value={discoverGridContextMock}>
<FilterOutBtn
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Component={(props: any) => <EuiButton {...props} />}
rowIndex={1}
colIndex={1}
columnId="extension"
isExpanded={false}
/>
</DiscoverGridContext.Provider>
);
const button = findTestSubject(component, 'filterOutButton');
await button.simulate('click');
expect(discoverGridContextMock.onFilter).toHaveBeenCalledWith(
discoverGridContextMock.dataView.fields.getByName('extension'),
'jpg',
'-'
);
});
it('triggers clipboard copy when CopyBtn is clicked', async () => {
const component = mountWithIntl(
<DiscoverGridContext.Provider value={discoverGridContextMock}>
<CopyBtn
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Component={(props: any) => <EuiButton {...props} />}
rowIndex={1}
colIndex={1}
columnId="extension"
isExpanded={false}
/>
</DiscoverGridContext.Provider>
);
const button = findTestSubject(component, 'copyClipboardButton');
await button.simulate('click');
expect(mockCopyToClipboard).toHaveBeenCalledWith('jpg');
});
});

View file

@ -45,7 +45,7 @@ export interface DiscoverGridFlyoutProps {
onClose: () => void;
onFilter?: DocViewFilterFn;
onRemoveColumn: (column: string) => void;
setExpandedDoc: (doc: DataTableRecord) => void;
setExpandedDoc: (doc?: DataTableRecord) => void;
}
function getIndexByDocId(hits: DataTableRecord[], id: string) {
@ -120,7 +120,7 @@ export function DiscoverGridFlyout({
<EuiFlyoutHeader hasBorder>
<EuiTitle
size="s"
className="dscTable__flyoutHeader"
className="unifiedDataTable__flyoutHeader"
data-test-subj="docTableRowDetailsTitle"
>
<h2>
@ -216,7 +216,7 @@ export function DiscoverGridFlyout({
pageCount={pageCount}
activePage={activePage}
onPageClick={setPage}
className="dscTable__flyoutDocumentNavigation"
className="unifiedDataTable__flyoutDocumentNavigation"
compressed
data-test-subj="dscDocNavigation"
/>
@ -255,3 +255,6 @@ export function DiscoverGridFlyout({
</EuiPortal>
);
}
// eslint-disable-next-line import/no-default-export
export default DiscoverGridFlyout;

View file

@ -0,0 +1,13 @@
/*
* 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 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 or the Server
* Side Public License, v 1.
*/
import { withSuspense } from '@kbn/shared-ux-utility';
import { lazy } from 'react';
export type { DiscoverGridFlyoutProps } from './discover_grid_flyout';
export const DiscoverGridFlyout = withSuspense(lazy(() => import('./discover_grid_flyout')));

View file

@ -19,7 +19,7 @@ import {
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import { euiLightVars } from '@kbn/ui-theme';
import { getRowsPerPageOptions } from '../../../../utils/rows_per_page';
import { getRowsPerPageOptions } from '@kbn/unified-data-table';
export const MAX_ROWS_PER_PAGE_OPTION = 100;

View file

@ -59,16 +59,20 @@ import {
SORT_DEFAULT_ORDER_SETTING,
buildDataTableRecord,
} from '@kbn/discover-utils';
import { VIEW_MODE, DISABLE_SHARD_FAILURE_WARNING } from '../../common/constants';
import type { UnifiedDataTableProps } from '@kbn/unified-data-table';
import type { UnifiedDataTableSettings } from '@kbn/unified-data-table';
import { columnActions } from '@kbn/unified-data-table';
import {
VIEW_MODE,
DISABLE_SHARD_FAILURE_WARNING,
getDefaultRowsPerPage,
} from '../../common/constants';
import type { ISearchEmbeddable, SearchInput, SearchOutput } from './types';
import type { DiscoverServices } from '../build_services';
import { getSortForEmbeddable, SortPair } from '../utils/sorting';
import { SEARCH_EMBEDDABLE_TYPE, SEARCH_EMBEDDABLE_CELL_ACTIONS_TRIGGER_ID } from './constants';
import { SavedSearchEmbeddableComponent } from './saved_search_embeddable_component';
import * as columnActions from '../components/doc_table/actions/columns';
import { handleSourceColumnState } from '../utils/state_helpers';
import type { DiscoverGridProps } from '../components/discover_grid/discover_grid';
import type { DiscoverGridSettings } from '../components/discover_grid/types';
import type { DocTableProps } from '../components/doc_table/doc_table_wrapper';
import { updateSearchSource } from './utils/update_search_source';
import { FieldStatisticsTable } from '../application/main/components/field_stats_table';
@ -78,11 +82,11 @@ import { getValidViewMode } from '../application/main/utils/get_valid_view_mode'
import { ADHOC_DATA_VIEW_RENDER_EVENT } from '../constants';
import { getDiscoverLocatorParams } from './get_discover_locator_params';
export type SearchProps = Partial<DiscoverGridProps> &
export type SearchProps = Partial<UnifiedDataTableProps> &
Partial<DocTableProps> & {
savedSearchId?: string;
filters?: Filter[];
settings?: DiscoverGridSettings;
settings?: UnifiedDataTableSettings;
description?: string;
sharedItemTitle?: string;
inspectorAdapters?: Adapters;
@ -585,7 +589,10 @@ export class SavedSearchEmbeddable
searchProps.sharedItemTitle = this.panelTitle;
searchProps.searchTitle = this.panelTitle;
searchProps.rowHeightState = this.input.rowHeight || savedSearch.rowHeight;
searchProps.rowsPerPageState = this.input.rowsPerPage || savedSearch.rowsPerPage;
searchProps.rowsPerPageState =
this.input.rowsPerPage ||
savedSearch.rowsPerPage ||
getDefaultRowsPerPage(this.services.uiSettings);
searchProps.filters = savedSearch.searchSource.getField('filter') as Filter[];
searchProps.savedSearchId = savedSearch.id;

View file

@ -8,11 +8,8 @@
import React from 'react';
import { AggregateQuery, Query } from '@kbn/es-query';
import {
DiscoverGridEmbeddable,
DiscoverGridEmbeddableProps,
DataLoadingState,
} from './saved_search_grid';
import { DataLoadingState } from '@kbn/unified-data-table';
import { DiscoverGridEmbeddable, DiscoverGridEmbeddableProps } from './saved_search_grid';
import { DiscoverDocTableEmbeddable } from '../components/doc_table/create_doc_table_embeddable';
import { DocTableEmbeddableProps } from '../components/doc_table/doc_table_embeddable';
import { isTextBasedQuery } from '../application/main/utils/is_text_based_query';
@ -47,7 +44,7 @@ export function SavedSearchEmbeddableComponent({
loadingState={searchProps.isLoading ? DataLoadingState.loading : DataLoadingState.loaded}
showFullScreenButton={false}
query={query}
className="dscDiscoverGrid"
className="unifiedDataTable"
/>
);
}

View file

@ -5,43 +5,80 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import React, { memo, useState } from 'react';
import React, { memo, useCallback, useState } from 'react';
import type { DataTableRecord } from '@kbn/discover-utils/types';
import { AggregateQuery, Query } from '@kbn/es-query';
import type { SearchResponseInterceptedWarning } from '@kbn/search-response-warnings';
import {
DiscoverGrid,
DiscoverGridProps,
DataLoadingState as DiscoverDataLoadingState,
} from '../components/discover_grid/discover_grid';
DataLoadingState as DiscoverGridLoadingState,
UnifiedDataTable,
} from '@kbn/unified-data-table';
import type { UnifiedDataTableProps } from '@kbn/unified-data-table';
import './saved_search_grid.scss';
import { DiscoverGridFlyout } from '../components/discover_grid/discover_grid_flyout';
import { MAX_DOC_FIELDS_DISPLAYED, ROW_HEIGHT_OPTION, SHOW_MULTIFIELDS } from '@kbn/discover-utils';
import { DiscoverGridFlyout } from '../components/discover_grid_flyout';
import { SavedSearchEmbeddableBase } from './saved_search_embeddable_base';
export { DataLoadingState } from '../components/discover_grid/discover_grid';
import { DISCOVER_TOUR_STEP_ANCHOR_IDS } from '../components/discover_tour';
export interface DiscoverGridEmbeddableProps extends DiscoverGridProps {
export interface DiscoverGridEmbeddableProps extends UnifiedDataTableProps {
totalHitCount?: number;
query?: AggregateQuery | Query;
interceptedWarnings?: SearchResponseInterceptedWarning[];
onAddColumn: (column: string) => void;
onRemoveColumn: (column: string) => void;
savedSearchId?: string;
}
export const DiscoverGridMemoized = memo(DiscoverGrid);
export const DataGridMemoized = memo(UnifiedDataTable);
export function DiscoverGridEmbeddable(props: DiscoverGridEmbeddableProps) {
const { interceptedWarnings, ...gridProps } = props;
const [expandedDoc, setExpandedDoc] = useState<DataTableRecord | undefined>(undefined);
const renderDocumentView = useCallback(
(hit: DataTableRecord, displayedRows: DataTableRecord[], displayedColumns: string[]) => (
<DiscoverGridFlyout
dataView={props.dataView}
hit={hit}
hits={displayedRows}
// if default columns are used, dont make them part of the URL - the context state handling will take care to restore them
columns={displayedColumns}
savedSearchId={props.savedSearchId}
onFilter={props.onFilter}
onRemoveColumn={props.onRemoveColumn}
onAddColumn={props.onAddColumn}
onClose={() => setExpandedDoc(undefined)}
setExpandedDoc={setExpandedDoc}
query={props.query}
/>
),
[
props.dataView,
props.onAddColumn,
props.onFilter,
props.onRemoveColumn,
props.query,
props.savedSearchId,
]
);
return (
<SavedSearchEmbeddableBase
totalHitCount={props.totalHitCount}
isLoading={props.loadingState === DiscoverDataLoadingState.loading}
isLoading={props.loadingState === DiscoverGridLoadingState.loading}
dataTestSubj="embeddedSavedSearchDocTable"
interceptedWarnings={props.interceptedWarnings}
>
<DiscoverGridMemoized
<DataGridMemoized
{...gridProps}
totalHits={props.totalHitCount}
setExpandedDoc={setExpandedDoc}
expandedDoc={expandedDoc}
DocumentView={DiscoverGridFlyout}
configRowHeight={props.services.uiSettings.get(ROW_HEIGHT_OPTION)}
showMultiFields={props.services.uiSettings.get(SHOW_MULTIFIELDS)}
maxDocFieldsDisplayed={props.services.uiSettings.get(MAX_DOC_FIELDS_DISPLAYED)}
renderDocumentView={renderDocumentView}
componentsTourSteps={{ expandButton: DISCOVER_TOUR_STEP_ANCHOR_IDS.expandDocument }}
/>
</SavedSearchEmbeddableBase>
);

View file

@ -1,107 +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 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 or the Server
* Side Public License, v 1.
*/
import React, { ReactNode } from 'react';
import { renderHook } from '@testing-library/react-hooks';
import { Storage } from '@kbn/kibana-utils-plugin/public';
import { DiscoverServices } from '../build_services';
import { LocalStorageMock } from '../__mocks__/local_storage_mock';
import { uiSettingsMock } from '../__mocks__/ui_settings';
import { useRowHeightsOptions } from './use_row_heights_options';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
const CONFIG_ROW_HEIGHT = 3;
const getWrapper = (services: DiscoverServices) => {
return ({ children }: { children: ReactNode }) => (
<KibanaContextProvider services={services}>{children}</KibanaContextProvider>
);
};
describe('useRowHeightsOptions', () => {
test('should apply rowHeight from savedSearch', () => {
const { result } = renderHook(
() => {
return useRowHeightsOptions({
rowHeightState: 2,
});
},
{
wrapper: getWrapper({
uiSettings: uiSettingsMock,
storage: new LocalStorageMock({}) as unknown as Storage,
} as DiscoverServices),
}
);
expect(result.current.defaultHeight).toEqual({ lineCount: 2 });
});
test('should apply rowHeight from local storage', () => {
const { result } = renderHook(
() => {
return useRowHeightsOptions({});
},
{
wrapper: getWrapper({
uiSettings: uiSettingsMock,
storage: new LocalStorageMock({
['discover:dataGridRowHeight']: {
previousRowHeight: 5,
previousConfigRowHeight: 3,
},
}) as unknown as Storage,
} as DiscoverServices),
}
);
expect(result.current.defaultHeight).toEqual({ lineCount: 5 });
});
test('should apply rowHeight from uiSettings', () => {
const { result } = renderHook(
() => {
return useRowHeightsOptions({});
},
{
wrapper: getWrapper({
uiSettings: uiSettingsMock,
storage: new LocalStorageMock({}) as unknown as Storage,
} as unknown as DiscoverServices),
}
);
expect(result.current.defaultHeight).toEqual({
lineCount: CONFIG_ROW_HEIGHT,
});
});
test('should apply rowHeight from uiSettings instead of local storage value, since uiSettings has been changed', () => {
const { result } = renderHook(
() => {
return useRowHeightsOptions({});
},
{
wrapper: getWrapper({
uiSettings: uiSettingsMock,
storage: new LocalStorageMock({
['discover:dataGridRowHeight']: {
previousRowHeight: 4,
// different from uiSettings (config), now user changed it to 3, but prev was 4
previousConfigRowHeight: 4,
},
}) as unknown as Storage,
} as unknown as DiscoverServices),
}
);
expect(result.current.defaultHeight).toEqual({
lineCount: CONFIG_ROW_HEIGHT,
});
});
});

View file

@ -63,7 +63,6 @@
"@kbn/core-application-browser",
"@kbn/core-saved-objects-server",
"@kbn/discover-utils",
"@kbn/field-types",
"@kbn/search-response-warnings",
"@kbn/content-management-plugin",
"@kbn/unified-doc-viewer",
@ -71,6 +70,7 @@
"@kbn/serverless",
"@kbn/react-kibana-mount",
"@kbn/react-kibana-context-render",
"@kbn/unified-data-table",
"@kbn/no-data-page-plugin"
],
"exclude": [

View file

@ -16,8 +16,7 @@ import type { DataView } from '@kbn/data-views-plugin/public';
import type { DataTableRecord } from '@kbn/discover-utils/types';
import { ElasticRequestState } from '@kbn/unified-doc-viewer';
import { DOC_TABLE_LEGACY, SEARCH_FIELDS_FROM_SOURCE } from '@kbn/discover-utils';
import { useEsDocSearch } from '../../hooks';
import { useUnifiedDocViewerServices } from '../../hooks';
import { useEsDocSearch, useUnifiedDocViewerServices } from '../../hooks';
import { getHeight } from './get_height';
import { JSONCodeEditorCommonMemoized } from '../json_code_editor';

View file

@ -9,7 +9,7 @@
import expect from '@kbn/expect';
import { FtrProviderContext } from '../ftr_provider_context';
const FOOTER_SELECTOR = 'discoverTableFooter';
const FOOTER_SELECTOR = 'unifiedDataTableFooter';
const LOAD_MORE_SELECTOR = 'dscGridSampleSizeFetchMoreLink';
export default function ({ getService, getPageObjects }: FtrProviderContext) {

View file

@ -52,18 +52,18 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
it('should show footer only for the last page', async () => {
// footer is not shown
await testSubjects.missingOrFail('discoverTableFooter');
await testSubjects.missingOrFail('unifiedDataTableFooter');
// go to next page
await testSubjects.click('pagination-button-next');
// footer is not shown yet
await retry.try(async function () {
await testSubjects.missingOrFail('discoverTableFooter');
await testSubjects.missingOrFail('unifiedDataTableFooter');
});
// go to the last page
await testSubjects.click('pagination-button-4');
// footer is shown now
await retry.try(async function () {
await testSubjects.existOrFail('discoverTableFooter');
await testSubjects.existOrFail('unifiedDataTableFooter');
});
});
@ -80,7 +80,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await retry.try(async function () {
return !testSubjects.exists('pagination-button-1'); // only page 0 is left
});
await testSubjects.existOrFail('discoverTableFooter');
await testSubjects.existOrFail('unifiedDataTableFooter');
});
it('should render exact number of rows which where configured in the saved search or in settings', async () => {

View file

@ -1496,6 +1496,8 @@
"@kbn/ui-shared-deps-src/*": ["packages/kbn-ui-shared-deps-src/*"],
"@kbn/ui-theme": ["packages/kbn-ui-theme"],
"@kbn/ui-theme/*": ["packages/kbn-ui-theme/*"],
"@kbn/unified-data-table": ["packages/kbn-unified-data-table"],
"@kbn/unified-data-table/*": ["packages/kbn-unified-data-table/*"],
"@kbn/unified-doc-viewer": ["packages/kbn-unified-doc-viewer"],
"@kbn/unified-doc-viewer/*": ["packages/kbn-unified-doc-viewer/*"],
"@kbn/unified-doc-viewer-examples": ["examples/unified_doc_viewer"],

View file

@ -2178,11 +2178,6 @@
"discover.dscTour.stepAddFields.description": "Cliquez sur {plusIcon} pour ajouter les champs qui vous intéressent.",
"discover.dscTour.stepExpand.description": "Cliquez sur {expandIcon} pour afficher, comparer et filtrer les documents.",
"discover.errorCalloutFormattedTitle": "{title} : {errorMessage}",
"discover.grid.copyClipboardButtonTitle": "Copier la valeur de {column}",
"discover.grid.copyColumnValuesToClipboard.toastTitle": "Valeurs de la colonne \"{column}\" copiées dans le presse-papiers",
"discover.grid.filterForAria": "Filtrer sur cette {value}",
"discover.grid.filterOutAria": "Exclure cette {value}",
"discover.gridSampleSize.limitDescription": "Les résultats de recherche sont limités à {sampleSize} documents Ajoutez d'autres termes pour affiner votre recherche.",
"discover.howToSeeOtherMatchingDocumentsDescription": "Voici les {sampleSize} premiers documents correspondant à votre recherche. Veuillez affiner cette dernière pour en voir davantage.",
"discover.noMatchRoute.bannerText": "L'application Discover ne reconnaît pas cet itinéraire : {route}",
"discover.noResults.kqlExamples.kqlDescription": "En savoir plus sur {kqlLink}",
@ -2192,9 +2187,6 @@
"discover.pageTitleWithSavedSearch": "Discover {savedSearchTitle}",
"discover.savedSearchAliasMatchRedirect.objectNoun": "Recherche {savedSearch}",
"discover.savedSearchURLConflictCallout.objectNoun": "Recherche {savedSearch}",
"discover.searchGenerationWithDescription": "Tableau généré par la recherche {searchTitle}",
"discover.searchGenerationWithDescriptionGrid": "Tableau généré par la recherche {searchTitle} ({searchDescription})",
"discover.selectedDocumentsNumber": "{nr} documents sélectionnés",
"discover.showingDefaultDataViewWarningDescription": "Affichage de la vue de données par défaut : \"{loadedDataViewTitle}\" ({loadedDataViewId})",
"discover.showingSavedDataViewWarningDescription": "Affichage de la vue de données enregistrée : \"{ownDataViewTitle}\" ({ownDataViewId})",
"discover.singleDocRoute.errorMessage": "Aucune vue de données correspondante pour l'ID {dataViewId}",
@ -2247,7 +2239,6 @@
"discover.backToTopLinkText": "Revenir en haut de la page.",
"discover.badge.readOnly.text": "Lecture seule",
"discover.badge.readOnly.tooltip": "Impossible denregistrer les recherches",
"discover.clearSelection": "Effacer la sélection",
"discover.confirmDataViewSave.cancel": "Annuler",
"discover.confirmDataViewSave.message": "L'action que vous avez choisie requiert une vue de données enregistrée.",
"discover.confirmDataViewSave.saveAndContinue": "Enregistrer et continuer",
@ -2268,8 +2259,6 @@
"discover.context.unableToLoadAnchorDocumentDescription": "Impossible de charger le document ancré",
"discover.context.unableToLoadDocumentDescription": "Impossible de charger les documents",
"discover.contextViewRoute.errorTitle": "Une erreur s'est produite",
"discover.controlColumnHeader": "Colonne de commande",
"discover.copyToClipboardJSON": "Copier les documents dans le presse-papiers (JSON)",
"discover.discoverBreadcrumbTitle": "Discover",
"discover.discoverDefaultSearchSessionName": "Discover",
"discover.discoverDescription": "Explorez vos données de manière interactive en interrogeant et en filtrant des documents bruts.",
@ -2368,29 +2357,15 @@
"discover.fieldChooser.discoverField.removeFieldTooltip": "Supprimer le champ du tableau",
"unifiedDocViewer.fieldChooser.discoverField.value": "Valeur",
"discover.goToDiscoverButtonText": "Aller à Discover",
"discover.grid.closePopover": "Fermer la fenêtre contextuelle",
"discover.grid.copyCellValueButton": "Copier la valeur",
"discover.grid.copyColumnNameToClipboard.toastTitle": "Copié dans le presse-papiers",
"discover.grid.copyColumnNameToClipBoardButton": "Copier le nom",
"discover.grid.copyColumnValuesToClipBoardButton": "Copier la colonne",
"discover.grid.copyEscapedValueWithFormulasToClipboardWarningText": "Les valeurs peuvent contenir des formules avec échappement.",
"discover.grid.copyFailedErrorText": "Impossible de copier dans le presse-papiers avec ce navigateur",
"discover.grid.copyValueToClipboard.toastTitle": "Copié dans le presse-papiers",
"discover.grid.documentHeader": "Document",
"discover.grid.editFieldButton": "Modifier le champ de la vue de données",
"discover.grid.filterFor": "Filtrer sur",
"discover.grid.filterOut": "Exclure",
"discover.grid.flyout.documentNavigation": "Navigation dans le document",
"discover.grid.flyout.toastColumnAdded": "La colonne \"{columnName}\" a été ajoutée.",
"discover.grid.flyout.toastColumnRemoved": "La colonne \"{columnName}\" a été supprimée.",
"discover.grid.selectDoc": "Sélectionner le document \"{rowNumber}\"",
"discover.grid.tableRow.detailHeading": "Document développé",
"discover.grid.tableRow.textBasedDetailHeading": "Ligne développée",
"discover.grid.tableRow.viewSingleDocumentLinkTextSimple": "Document unique",
"discover.grid.tableRow.viewSurroundingDocumentsHover": "Inspectez des documents qui ont été créés avant et après ce document. Seuls les filtres épinglés restent actifs dans la vue Documents relatifs.",
"discover.grid.tableRow.viewSurroundingDocumentsLinkTextSimple": "Documents relatifs",
"discover.grid.tableRow.viewText": "Afficher :",
"discover.grid.viewDoc": "Afficher/Masquer les détails de la boîte de dialogue",
"discover.helpMenu.appName": "Découverte",
"discover.inspectorRequestDataTitleDocuments": "Documents",
"discover.inspectorRequestDescriptionDocument": "Cette requête interroge Elasticsearch afin de récupérer les documents.",
@ -2400,7 +2375,6 @@
"unifiedDocViewer.json.copyToClipboardLabel": "Copier dans le presse-papiers",
"discover.loadingDocuments": "Chargement des documents",
"unifiedDocViewer.loadingJSON": "Chargement de JSON",
"discover.loadingResults": "Chargement des résultats",
"discover.localMenu.alertsDescription": "Alertes",
"discover.localMenu.fallbackReportTitle": "Recherche Discover sans titre",
"discover.localMenu.inspectTitle": "Inspecter",
@ -2443,23 +2417,18 @@
"discover.noResults.suggestion.syntaxPopoverExampleHeader": "Exemple",
"discover.noResults.suggestion.tryText": "Voici quelques solutions à essayer :",
"discover.noResults.suggestion.viewAllMatchesButtonText": "Afficher toutes les correspondances",
"discover.noResultsFound": "Résultat introuvable",
"discover.notifications.invalidTimeRangeText": "La plage temporelle spécifiée n'est pas valide (de : \"{from}\" à \"{to}\").",
"discover.notifications.invalidTimeRangeTitle": "Plage temporelle non valide",
"discover.notifications.notSavedSearchTitle": "La recherche \"{savedSearchTitle}\" n'a pas été enregistrée.",
"discover.notifications.savedSearchTitle": "La recherche \"{savedSearchTitle}\" a été enregistrée.",
"discover.pageTitleWithoutSavedSearch": "Discover - Recherche non encore enregistrée",
"discover.reloadSavedSearchButton": "Réinitialiser la recherche",
"discover.removeColumnLabel": "Supprimer la colonne",
"discover.rootBreadcrumb": "Découverte",
"discover.sampleData.viewLinkLabel": "Découverte",
"discover.savedSearch.savedObjectName": "Recherche enregistrée",
"discover.savedSearchEmbeddable.action.viewSavedSearch.displayName": "Ouvrir dans Discover",
"discover.searchingTitle": "Recherche",
"discover.selectColumnHeader": "Sélectionner la colonne",
"discover.serverLocatorExtension.titleFromLocatorUnknown": "Recherche inconnue",
"discover.showAllDocuments": "Afficher tous les documents",
"discover.showSelectedDocumentsOnly": "Afficher uniquement les documents sélectionnés",
"discover.singleDocRoute.errorTitle": "Une erreur s'est produite",
"discover.skipToBottomButtonLabel": "Atteindre la fin du tableau",
"unifiedDocViewer.sourceViewer.errorMessage": "Impossible de récupérer les données pour le moment. Actualisez l'onglet et réessayez.",
@ -2481,6 +2450,25 @@
"discover.viewAlert.searchSourceErrorTitle": "Erreur lors de la récupération de la source de recherche",
"discover.viewModes.document.label": "Documents",
"discover.viewModes.fieldStatistics.label": "Statistiques de champ",
"unifiedDataTable.tableHeader.timeFieldIconTooltipAriaLabel": "{timeFieldName}  Ce champ représente l'heure à laquelle les événements se sont produits.",
"unifiedDataTable.searchGenerationWithDescription": "Tableau généré par la recherche {searchTitle}",
"unifiedDataTable.searchGenerationWithDescriptionGrid": "Tableau généré par la recherche {searchTitle} ({searchDescription})",
"unifiedDataTable.selectedDocumentsNumber": "{nr} documents sélectionnés",
"unifiedDataTable.clearSelection": "Effacer la sélection",
"unifiedDataTable.controlColumnHeader": "Colonne de commande",
"unifiedDataTable.copyToClipboardJSON": "Copier les documents dans le presse-papiers (JSON)",
"unifiedDataTable.tableHeader.timeFieldIconTooltip": "Ce champ représente l'heure à laquelle les événements se sont produits.",
"unifiedDataTable.grid.copyColumnNameToClipBoardButton": "Copier le nom",
"unifiedDataTable.grid.copyColumnValuesToClipBoardButton": "Copier la colonne",
"unifiedDataTable.grid.documentHeader": "Document",
"unifiedDataTable.grid.editFieldButton": "Modifier le champ de la vue de données",
"unifiedDataTable.grid.selectDoc": "Sélectionner le document \"{rowNumber}\"",
"unifiedDataTable.loadingResults": "Chargement des résultats",
"unifiedDataTable.noResultsFound": "Résultat introuvable",
"unifiedDataTable.removeColumnLabel": "Supprimer la colonne",
"unifiedDataTable.selectColumnHeader": "Sélectionner la colonne",
"unifiedDataTable.showAllDocuments": "Afficher tous les documents",
"unifiedDataTable.showSelectedDocumentsOnly": "Afficher uniquement les documents sélectionnés",
"domDragDrop.announce.cancelled": "Mouvement annulé. {label} revenu à sa position initiale",
"domDragDrop.announce.cancelledItem": "Mouvement annulé. {label} revenu au groupe {groupLabel} à la position {position}",
"domDragDrop.announce.dropped.combineCompatible": "{label} combiné dans le groupe {groupLabel} en {dropLabel} dans le groupe {dropGroupLabel} à la position {dropPosition} dans le calque {dropLayerNumber}",

View file

@ -2193,11 +2193,6 @@
"discover.dscTour.stepAddFields.description": "{plusIcon}をクリックして、関心があるフィールドを追加します。",
"discover.dscTour.stepExpand.description": "{expandIcon}をクリックすると、ドキュメントを表示、比較、フィルタリングできます。",
"discover.errorCalloutFormattedTitle": "{title}: {errorMessage}",
"discover.grid.copyClipboardButtonTitle": "{column}の値をコピー",
"discover.grid.copyColumnValuesToClipboard.toastTitle": "\"{column}\"列の値がクリップボードにコピーされました",
"discover.grid.filterForAria": "この{value}でフィルターを適用",
"discover.grid.filterOutAria": "この{value}を除外",
"discover.gridSampleSize.limitDescription": "検索結果は{sampleSize}ドキュメントに制限されています。検索を絞り込むには、その他の検索用語を追加してください。",
"discover.howToSeeOtherMatchingDocumentsDescription": "これらは検索条件に一致した初めの{sampleSize}件のドキュメントです。他の結果を表示するには検索条件を絞ってください。",
"discover.noMatchRoute.bannerText": "Discoverアプリケーションはこのルートを認識できません{route}",
"discover.noResults.kqlExamples.kqlDescription": "{kqlLink}の詳細",
@ -2207,9 +2202,6 @@
"discover.pageTitleWithSavedSearch": "Discover - {savedSearchTitle}",
"discover.savedSearchAliasMatchRedirect.objectNoun": "{savedSearch}検索",
"discover.savedSearchURLConflictCallout.objectNoun": "{savedSearch}検索",
"discover.searchGenerationWithDescription": "検索{searchTitle}で生成されたテーブル",
"discover.searchGenerationWithDescriptionGrid": "検索{searchTitle}で生成されたテーブル({searchDescription}",
"discover.selectedDocumentsNumber": "{nr}個のドキュメントが選択されました",
"discover.showingDefaultDataViewWarningDescription": "デフォルトのデータビューを表示しています:\"{loadedDataViewTitle}\"{loadedDataViewId}",
"discover.showingSavedDataViewWarningDescription": "保存されたデータビューを表示しています:\"{ownDataViewTitle}\"{ownDataViewId}",
"discover.singleDocRoute.errorMessage": "ID {dataViewId}の一致するデータビューが見つかりません",
@ -2262,7 +2254,6 @@
"discover.backToTopLinkText": "最上部へ戻る。",
"discover.badge.readOnly.text": "読み取り専用",
"discover.badge.readOnly.tooltip": "検索を保存できません",
"discover.clearSelection": "選択した項目をクリア",
"discover.confirmDataViewSave.cancel": "キャンセル",
"discover.confirmDataViewSave.message": "選択したアクションでは、保存されたデータビューが必要です。",
"discover.confirmDataViewSave.saveAndContinue": "保存して続行",
@ -2283,8 +2274,6 @@
"discover.context.unableToLoadAnchorDocumentDescription": "アンカードキュメントを読み込めません",
"discover.context.unableToLoadDocumentDescription": "ドキュメントを読み込めません",
"discover.contextViewRoute.errorTitle": "エラーが発生しました",
"discover.controlColumnHeader": "列の制御",
"discover.copyToClipboardJSON": "ドキュメントをクリップボードにコピーJSON",
"discover.discoverBreadcrumbTitle": "Discover",
"discover.discoverDefaultSearchSessionName": "Discover",
"discover.discoverDescription": "ドキュメントにクエリをかけたりフィルターを適用することで、データをインタラクティブに閲覧できます。",
@ -2383,29 +2372,15 @@
"discover.fieldChooser.discoverField.removeFieldTooltip": "フィールドを表から削除",
"unifiedDocViewer.fieldChooser.discoverField.value": "値",
"discover.goToDiscoverButtonText": "Discoverに移動",
"discover.grid.closePopover": "ポップオーバーを閉じる",
"discover.grid.copyCellValueButton": "値をコピー",
"discover.grid.copyColumnNameToClipboard.toastTitle": "クリップボードにコピーされました",
"discover.grid.copyColumnNameToClipBoardButton": "名前をコピー",
"discover.grid.copyColumnValuesToClipBoardButton": "列をコピー",
"discover.grid.copyEscapedValueWithFormulasToClipboardWarningText": "値にはエスケープされた式を含めることができます。",
"discover.grid.copyFailedErrorText": "このブラウザーではクリップボードにコピーできません",
"discover.grid.copyValueToClipboard.toastTitle": "クリップボードにコピーされました",
"discover.grid.documentHeader": "ドキュメント",
"discover.grid.editFieldButton": "データビューフィールドを編集",
"discover.grid.filterFor": "フィルター",
"discover.grid.filterOut": "除外",
"discover.grid.flyout.documentNavigation": "ドキュメントナビゲーション",
"discover.grid.flyout.toastColumnAdded": "列'{columnName}'が追加されました",
"discover.grid.flyout.toastColumnRemoved": "列'{columnName}'が削除されました",
"discover.grid.selectDoc": "ドキュメント'{rowNumber}'を選択",
"discover.grid.tableRow.detailHeading": "拡張ドキュメント",
"discover.grid.tableRow.textBasedDetailHeading": "展開された行",
"discover.grid.tableRow.viewSingleDocumentLinkTextSimple": "1つのドキュメント",
"discover.grid.tableRow.viewSurroundingDocumentsHover": "このドキュメントの前後に出現したドキュメントを検査します。周りのドキュメントビューでは、固定されたフィルターのみがアクティブのままです。",
"discover.grid.tableRow.viewSurroundingDocumentsLinkTextSimple": "周りのドキュメント",
"discover.grid.tableRow.viewText": "表示:",
"discover.grid.viewDoc": "詳細ダイアログを切り替え",
"discover.helpMenu.appName": "Discover",
"discover.inspectorRequestDataTitleDocuments": "ドキュメント",
"discover.inspectorRequestDescriptionDocument": "このリクエストはElasticsearchにクエリをかけ、ドキュメントを取得します。",
@ -2415,7 +2390,6 @@
"unifiedDocViewer.json.copyToClipboardLabel": "クリップボードにコピー",
"discover.loadingDocuments": "ドキュメントを読み込み中",
"unifiedDocViewer.loadingJSON": "JSONを読み込んでいます",
"discover.loadingResults": "結果を読み込み中",
"discover.localMenu.alertsDescription": "アラート",
"discover.localMenu.fallbackReportTitle": "無題のDiscover検索",
"discover.localMenu.inspectTitle": "検査",
@ -2458,23 +2432,18 @@
"discover.noResults.suggestion.syntaxPopoverExampleHeader": "例",
"discover.noResults.suggestion.tryText": "次の方法を試してください:",
"discover.noResults.suggestion.viewAllMatchesButtonText": "すべての一致を表示",
"discover.noResultsFound": "結果が見つかりませんでした",
"discover.notifications.invalidTimeRangeText": "指定された時間範囲が無効です。(開始:'{from}'、終了:'{to}'",
"discover.notifications.invalidTimeRangeTitle": "無効な時間範囲",
"discover.notifications.notSavedSearchTitle": "検索「{savedSearchTitle}」は保存されませんでした。",
"discover.notifications.savedSearchTitle": "検索「{savedSearchTitle}」が保存されました。",
"discover.pageTitleWithoutSavedSearch": "Discover - 検索は保存されていません",
"discover.reloadSavedSearchButton": "検索をリセット",
"discover.removeColumnLabel": "列を削除",
"discover.rootBreadcrumb": "Discover",
"discover.sampleData.viewLinkLabel": "Discover",
"discover.savedSearch.savedObjectName": "保存検索",
"discover.savedSearchEmbeddable.action.viewSavedSearch.displayName": "Discoverで開く",
"discover.searchingTitle": "検索中",
"discover.selectColumnHeader": "列を選択",
"discover.serverLocatorExtension.titleFromLocatorUnknown": "不明な検索",
"discover.showAllDocuments": "すべてのドキュメントを表示",
"discover.showSelectedDocumentsOnly": "選択したドキュメントのみを表示",
"discover.singleDocRoute.errorTitle": "エラーが発生しました",
"discover.skipToBottomButtonLabel": "テーブルの最後に移動",
"unifiedDocViewer.sourceViewer.errorMessage": "現在データを取得できませんでした。タブを更新して、再試行してください。",
@ -2496,6 +2465,25 @@
"discover.viewAlert.searchSourceErrorTitle": "検索ソースの取得エラー",
"discover.viewModes.document.label": "ドキュメント",
"discover.viewModes.fieldStatistics.label": "フィールド統計情報",
"unifiedDataTable.tableHeader.timeFieldIconTooltipAriaLabel": "{timeFieldName} - このフィールドはイベントの発生時刻を表します。",
"unifiedDataTable.searchGenerationWithDescription": "検索{searchTitle}で生成されたテーブル",
"unifiedDataTable.searchGenerationWithDescriptionGrid": "検索{searchTitle}で生成されたテーブル({searchDescription}",
"unifiedDataTable.selectedDocumentsNumber": "{nr}個のドキュメントが選択されました",
"unifiedDataTable.clearSelection": "選択した項目をクリア",
"unifiedDataTable.controlColumnHeader": "列の制御",
"unifiedDataTable.copyToClipboardJSON": "ドキュメントをクリップボードにコピーJSON",
"unifiedDataTable.tableHeader.timeFieldIconTooltip": "このフィールドはイベントの発生時刻を表します。",
"unifiedDataTable.grid.copyColumnNameToClipBoardButton": "名前をコピー",
"unifiedDataTable.grid.copyColumnValuesToClipBoardButton": "列をコピー",
"unifiedDataTable.grid.documentHeader": "ドキュメント",
"unifiedDataTable.grid.editFieldButton": "データビューフィールドを編集",
"unifiedDataTable.grid.selectDoc": "ドキュメント'{rowNumber}'を選択",
"unifiedDataTable.loadingResults": "結果を読み込み中",
"unifiedDataTable.noResultsFound": "結果が見つかりませんでした",
"unifiedDataTable.removeColumnLabel": "列を削除",
"unifiedDataTable.selectColumnHeader": "列を選択",
"unifiedDataTable.showAllDocuments": "すべてのドキュメントを表示",
"unifiedDataTable.showSelectedDocumentsOnly": "選択したドキュメントのみを表示",
"domDragDrop.announce.cancelled": "移動がキャンセルされました。{label}は初期位置に戻りました",
"domDragDrop.announce.cancelledItem": "移動がキャンセルされました。{label}は位置{position}の{groupLabel}グループに戻りました",
"domDragDrop.announce.dropped.combineCompatible": "レイヤー{dropLayerNumber}の位置{dropPosition}でグループ{dropGroupLabel}の{dropLabel}にグループ{groupLabel}の{label}を結合しました。",

View file

@ -2193,11 +2193,6 @@
"discover.dscTour.stepAddFields.description": "单击 {plusIcon} 以添加您感兴趣的字段。",
"discover.dscTour.stepExpand.description": "单击 {expandIcon} 以查看、比较和筛选文档。",
"discover.errorCalloutFormattedTitle": "{title}: {errorMessage}",
"discover.grid.copyClipboardButtonTitle": "复制 {column} 的值",
"discover.grid.copyColumnValuesToClipboard.toastTitle": "“{column}”列的值已复制到剪贴板",
"discover.grid.filterForAria": "筛留此 {value}",
"discover.grid.filterOutAria": "筛除此 {value}",
"discover.gridSampleSize.limitDescription": "搜索结果被限定为 {sampleSize} 个文档。添加更多搜索词以缩小搜索范围。",
"discover.howToSeeOtherMatchingDocumentsDescription": "以下是匹配您的搜索的前 {sampleSize} 个文档,请优化您的搜索以查看其他文档。",
"discover.noMatchRoute.bannerText": "Discover 应用程序无法识别此路由:{route}",
"discover.noResults.kqlExamples.kqlDescription": "详细了解 {kqlLink}",
@ -2207,9 +2202,6 @@
"discover.pageTitleWithSavedSearch": "Discover - {savedSearchTitle}",
"discover.savedSearchAliasMatchRedirect.objectNoun": "{savedSearch} 搜索",
"discover.savedSearchURLConflictCallout.objectNoun": "{savedSearch} 搜索",
"discover.searchGenerationWithDescription": "搜索 {searchTitle} 生成的表",
"discover.searchGenerationWithDescriptionGrid": "搜索 {searchTitle} 生成的表({searchDescription}",
"discover.selectedDocumentsNumber": "{nr} 个文档已选择",
"discover.showingDefaultDataViewWarningDescription": "正在显示默认数据视图:“{loadedDataViewTitle}”({loadedDataViewId}",
"discover.showingSavedDataViewWarningDescription": "正在显示已保存数据视图:“{ownDataViewTitle}”({ownDataViewId}",
"discover.singleDocRoute.errorMessage": "没有与 ID {dataViewId} 相匹配的数据视图",
@ -2262,7 +2254,6 @@
"discover.backToTopLinkText": "返回顶部。",
"discover.badge.readOnly.text": "只读",
"discover.badge.readOnly.tooltip": "无法保存搜索",
"discover.clearSelection": "清除所选内容",
"discover.confirmDataViewSave.cancel": "取消",
"discover.confirmDataViewSave.message": "您选择的操作需要已保存的数据视图。",
"discover.confirmDataViewSave.saveAndContinue": "保存并继续",
@ -2283,8 +2274,6 @@
"discover.context.unableToLoadAnchorDocumentDescription": "无法加载该定位点文档",
"discover.context.unableToLoadDocumentDescription": "无法加载文档",
"discover.contextViewRoute.errorTitle": "发生错误",
"discover.controlColumnHeader": "控制列",
"discover.copyToClipboardJSON": "将文档复制到剪贴板 (JSON)",
"discover.discoverBreadcrumbTitle": "Discover",
"discover.discoverDefaultSearchSessionName": "发现",
"discover.discoverDescription": "通过查询和筛选原始文档来以交互方式浏览您的数据。",
@ -2383,29 +2372,15 @@
"discover.fieldChooser.discoverField.removeFieldTooltip": "从表中移除字段",
"unifiedDocViewer.fieldChooser.discoverField.value": "值",
"discover.goToDiscoverButtonText": "前往 Discover",
"discover.grid.closePopover": "关闭弹出框",
"discover.grid.copyCellValueButton": "复制值",
"discover.grid.copyColumnNameToClipboard.toastTitle": "已复制到剪贴板",
"discover.grid.copyColumnNameToClipBoardButton": "复制名称",
"discover.grid.copyColumnValuesToClipBoardButton": "复制列",
"discover.grid.copyEscapedValueWithFormulasToClipboardWarningText": "值可能包含已转义的公式。",
"discover.grid.copyFailedErrorText": "无法在此浏览器中复制到剪贴板",
"discover.grid.copyValueToClipboard.toastTitle": "已复制到剪贴板",
"discover.grid.documentHeader": "文档",
"discover.grid.editFieldButton": "编辑数据视图字段",
"discover.grid.filterFor": "筛留",
"discover.grid.filterOut": "筛除",
"discover.grid.flyout.documentNavigation": "文档导航",
"discover.grid.flyout.toastColumnAdded": "已添加列“{columnName}”",
"discover.grid.flyout.toastColumnRemoved": "已移除列“{columnName}”",
"discover.grid.selectDoc": "选择文档“{rowNumber}”",
"discover.grid.tableRow.detailHeading": "已展开文档",
"discover.grid.tableRow.textBasedDetailHeading": "已展开行",
"discover.grid.tableRow.viewSingleDocumentLinkTextSimple": "单个文档",
"discover.grid.tableRow.viewSurroundingDocumentsHover": "检查在此文档之前和之后出现的文档。在周围文档视图中,仅已固定筛选仍处于活动状态。",
"discover.grid.tableRow.viewSurroundingDocumentsLinkTextSimple": "周围文档",
"discover.grid.tableRow.viewText": "视图:",
"discover.grid.viewDoc": "切换具有详情的对话框",
"discover.helpMenu.appName": "Discover",
"discover.inspectorRequestDataTitleDocuments": "文档",
"discover.inspectorRequestDescriptionDocument": "此请求将查询 Elasticsearch 以获取文档。",
@ -2415,7 +2390,6 @@
"unifiedDocViewer.json.copyToClipboardLabel": "复制到剪贴板",
"discover.loadingDocuments": "正在加载文档",
"unifiedDocViewer.loadingJSON": "正在加载 JSON",
"discover.loadingResults": "正在加载结果",
"discover.localMenu.alertsDescription": "告警",
"discover.localMenu.fallbackReportTitle": "未命名 Discover 搜索",
"discover.localMenu.inspectTitle": "检查",
@ -2458,23 +2432,18 @@
"discover.noResults.suggestion.syntaxPopoverExampleHeader": "示例",
"discover.noResults.suggestion.tryText": "这里是要尝试的一些内容:",
"discover.noResults.suggestion.viewAllMatchesButtonText": "查看所有匹配项",
"discover.noResultsFound": "找不到结果",
"discover.notifications.invalidTimeRangeText": "提供的时间范围无效。(自:“{from}”,至:“{to}”)",
"discover.notifications.invalidTimeRangeTitle": "时间范围无效",
"discover.notifications.notSavedSearchTitle": "搜索“{savedSearchTitle}”未保存。",
"discover.notifications.savedSearchTitle": "搜索“{savedSearchTitle}”已保存",
"discover.pageTitleWithoutSavedSearch": "Discover - 尚未保存搜索",
"discover.reloadSavedSearchButton": "重置搜索",
"discover.removeColumnLabel": "移除列",
"discover.rootBreadcrumb": "Discover",
"discover.sampleData.viewLinkLabel": "Discover",
"discover.savedSearch.savedObjectName": "已保存搜索",
"discover.savedSearchEmbeddable.action.viewSavedSearch.displayName": "在 Discover 中打开",
"discover.searchingTitle": "正在搜索",
"discover.selectColumnHeader": "选择列",
"discover.serverLocatorExtension.titleFromLocatorUnknown": "未知搜索",
"discover.showAllDocuments": "显示所有文档",
"discover.showSelectedDocumentsOnly": "仅显示选定的文档",
"discover.singleDocRoute.errorTitle": "发生错误",
"discover.skipToBottomButtonLabel": "转到表尾",
"unifiedDocViewer.sourceViewer.errorMessage": "当前无法获取数据。请刷新选项卡以重试。",
@ -2496,6 +2465,25 @@
"discover.viewAlert.searchSourceErrorTitle": "提取搜索源时出错",
"discover.viewModes.document.label": "文档",
"discover.viewModes.fieldStatistics.label": "字段统计信息",
"unifiedDataTable.tableHeader.timeFieldIconTooltipAriaLabel": "{timeFieldName} - 此字段表示事件发生的时间。",
"unifiedDataTable.searchGenerationWithDescription": "搜索 {searchTitle} 生成的表",
"unifiedDataTable.searchGenerationWithDescriptionGrid": "搜索 {searchTitle} 生成的表({searchDescription}",
"unifiedDataTable.selectedDocumentsNumber": "{nr} 个文档已选择",
"unifiedDataTable.clearSelection": "清除所选内容",
"unifiedDataTable.controlColumnHeader": "控制列",
"unifiedDataTable.copyToClipboardJSON": "将文档复制到剪贴板 (JSON)",
"unifiedDataTable.tableHeader.timeFieldIconTooltip": "此字段表示事件发生的时间。",
"unifiedDataTable.grid.copyColumnNameToClipBoardButton": "复制名称",
"unifiedDataTable.grid.copyColumnValuesToClipBoardButton": "复制列",
"unifiedDataTable.grid.documentHeader": "文档",
"unifiedDataTable.grid.editFieldButton": "编辑数据视图字段",
"unifiedDataTable.grid.selectDoc": "选择文档“{rowNumber}”",
"unifiedDataTable.loadingResults": "正在加载结果",
"unifiedDataTable.noResultsFound": "找不到结果",
"unifiedDataTable.removeColumnLabel": "移除列",
"unifiedDataTable.selectColumnHeader": "选择列",
"unifiedDataTable.showAllDocuments": "显示所有文档",
"unifiedDataTable.showSelectedDocumentsOnly": "仅显示选定的文档",
"domDragDrop.announce.cancelled": "移动已取消。{label} 已返回至其初始位置",
"domDragDrop.announce.cancelledItem": "移动已取消。{label} 返回至 {groupLabel} 组中的位置 {position}",
"domDragDrop.announce.dropped.combineCompatible": "已将组 {groupLabel} 中的 {label} 组合到图层 {dropLayerNumber} 的组 {dropGroupLabel} 中的位置 {dropPosition} 上的 {dropLabel}",

View file

@ -45,7 +45,7 @@ export const DISCOVER_FIELDS_LOADING = getDataTestSubjectSelector(
export const DISCOVER_DATA_GRID_UPDATING = getDataTestSubjectSelector('discoverDataGridUpdating');
export const DISCOVER_DATA_GRID_LOADING = getDataTestSubjectSelector('discoverDataGridLoading');
export const UNIFIED_DATA_TABLE_LOADING = getDataTestSubjectSelector('unifiedDataTableLoading');
export const DISCOVER_NO_RESULTS = getDataTestSubjectSelector('discoverNoResults');
@ -54,7 +54,7 @@ export const DISCOVER_TABLE = getDataTestSubjectSelector('docTable');
export const GET_DISCOVER_DATA_GRID_CELL = (columnId: string, rowIndex: number) => {
return `${DISCOVER_TABLE} ${getDataTestSubjectSelector(
'dataGridRowCell'
)}[data-gridcell-column-id="${columnId}"][data-gridcell-row-index="${rowIndex}"] .dscDiscoverGrid__cellValue`;
)}[data-gridcell-column-id="${columnId}"][data-gridcell-row-index="${rowIndex}"] .unifiedDataTable__cellValue`;
};
export const GET_DISCOVER_DATA_GRID_CELL_HEADER = (columnId: string) =>

View file

@ -5929,6 +5929,10 @@
version "0.0.0"
uid ""
"@kbn/unified-data-table@link:packages/kbn-unified-data-table":
version "0.0.0"
uid ""
"@kbn/unified-field-list-examples-plugin@link:examples/unified_field_list_examples":
version "0.0.0"
uid ""