From 8fb5a651a0a2c2569a1f7e52299f1ea2add070c9 Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Fri, 1 Sep 2023 23:02:53 -0700 Subject: [PATCH] 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. Screenshot 2023-08-22 at 2 26 57 PM - `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. Screenshot 2023-08-22 at 2 40 28 PM - `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. Screenshot 2023-08-22 at 2 50 27 PM - `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 Screenshot 2023-08-22 at 2 45 49 PM - `externalCustomRenderers?: Record React.ReactNode>;` - an optional settings for a specified fields rendering like links. Applied only for the listed fields rendering: Screenshot 2023-08-22 at 2 51 07 PM - `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> --- .github/CODEOWNERS | 1 + .i18nrc.json | 3 +- package.json | 1 + packages/kbn-unified-data-table/README.md | 241 ++++++++++ .../__mocks__/config.ts | 23 + .../__mocks__/data_view_complex.ts | 419 ++++++++++++++++++ .../__mocks__/data_view_with_timefield.ts | 64 +++ .../__mocks__/data_views.ts | 45 ++ .../__mocks__/es_hits_complex.ts | 0 .../__mocks__/external_control_columns.tsx | 118 +++++ .../__mocks__/local_storage_mock.ts | 26 ++ .../__mocks__/services.ts | 116 +++++ .../__mocks__/table_context.ts | 14 +- packages/kbn-unified-data-table/index.ts | 21 + .../kbn-unified-data-table/jest.config.js | 15 +- packages/kbn-unified-data-table/kibana.jsonc | 6 + packages/kbn-unified-data-table/package.json | 10 + .../src/components}/actions/columns.test.ts | 10 +- .../src/components}/actions/columns.ts | 18 +- .../build_copy_column_button.test.tsx | 16 +- .../components}/build_copy_column_button.tsx | 8 +- .../build_edit_field_button.test.tsx | 21 +- .../components}/build_edit_field_button.tsx | 7 +- .../src/components/data_table.scss | 34 +- .../src/components/data_table.test.tsx | 181 +++++++- .../src/components/data_table.tsx | 383 +++++++++------- .../components/data_table_columns.test.tsx | 72 +-- .../src/components/data_table_columns.tsx | 48 +- .../data_table_document_selection.test.tsx | 36 +- .../data_table_document_selection.tsx | 21 +- .../data_table_expand_button.test.tsx | 34 +- .../components/data_table_expand_button.tsx | 11 +- .../src/components/data_table_footer.test.tsx | 118 ++--- .../src/components/data_table_footer.tsx | 39 +- .../src/components/data_table_schema.ts | 4 +- .../components/default_cell_actions.test.tsx | 184 ++++++++ .../src/components/default_cell_actions.tsx | 57 ++- .../json_code_editor.test.tsx.snap | 17 + .../json_code_editor/json_code_editor.scss | 3 + .../json_code_editor.test.tsx | 22 + .../json_code_editor/json_code_editor.tsx | 41 ++ .../json_code_editor_common.tsx | 94 ++++ .../kbn-unified-data-table/src}/constants.ts | 13 +- .../src}/hooks/use_data_grid_columns.test.tsx | 4 +- .../src}/hooks/use_data_grid_columns.ts | 25 +- .../hooks/use_row_heights_options.test.tsx | 77 ++++ .../src}/hooks/use_row_heights_options.ts | 24 +- .../src/table_context.tsx | 11 +- .../kbn-unified-data-table/src}/types.ts | 20 +- .../src}/utils/columns.test.ts | 2 +- .../src}/utils/columns.ts | 2 +- .../utils/convert_value_to_string.test.tsx | 220 ++++----- .../src}/utils/convert_value_to_string.ts | 4 +- .../utils/copy_value_to_clipboard.test.tsx | 38 +- .../src}/utils/copy_value_to_clipboard.ts | 10 +- .../src}/utils/get_field_capabilities.test.ts | 0 .../src}/utils/get_field_capabilities.ts | 2 +- .../src/utils}/get_render_cell_value.test.tsx | 253 ++++++----- .../src/utils}/get_render_cell_value.tsx | 71 ++- .../src}/utils/popularize_field.test.ts | 0 .../src}/utils/popularize_field.ts | 4 +- .../src}/utils/row_heights.ts | 16 +- .../src}/utils/rows_per_page.test.ts | 17 +- .../src}/utils/rows_per_page.ts | 9 +- .../src}/utils/validate_row_height.ts | 0 packages/kbn-unified-data-table/tsconfig.json | 37 ++ .../__snapshots__/code_editor.test.tsx.snap | 2 +- .../code_editor/impl/code_editor.tsx | 3 +- src/plugins/discover/common/constants.ts | 8 +- .../application/context/context_app.tsx | 13 +- .../context/context_app_content.test.tsx | 4 +- .../context/context_app_content.tsx | 45 +- .../components/layout/discover_documents.tsx | 57 ++- .../components/layout/discover_layout.tsx | 13 +- .../services/discover_app_state_container.ts | 4 +- .../application/main/utils/fetch_documents.ts | 2 +- .../main/utils/fetch_text_based.ts | 2 +- .../discover/public/application/types.ts | 11 + .../discover_grid_cell_actions.test.tsx | 159 ------- .../discover_grid_flyout.test.tsx | 0 .../discover_grid_flyout.tsx | 9 +- .../components/discover_grid_flyout/index.ts | 13 + .../components/pager/tool_bar_pagination.tsx | 2 +- .../embeddable/saved_search_embeddable.tsx | 21 +- .../saved_search_embeddable_component.tsx | 9 +- .../public/embeddable/saved_search_grid.tsx | 61 ++- .../hooks/use_row_heights_options.test.tsx | 107 ----- src/plugins/discover/tsconfig.json | 2 +- .../components/doc_viewer_source/source.tsx | 3 +- .../apps/discover/group2/_data_grid_footer.ts | 2 +- .../discover/group2/_data_grid_pagination.ts | 8 +- tsconfig.base.json | 2 + .../translations/translations/fr-FR.json | 50 +-- .../translations/translations/ja-JP.json | 50 +-- .../translations/translations/zh-CN.json | 50 +-- .../cypress/screens/discover.ts | 4 +- yarn.lock | 4 + 97 files changed, 2956 insertions(+), 1225 deletions(-) create mode 100644 packages/kbn-unified-data-table/README.md create mode 100644 packages/kbn-unified-data-table/__mocks__/config.ts create mode 100644 packages/kbn-unified-data-table/__mocks__/data_view_complex.ts create mode 100644 packages/kbn-unified-data-table/__mocks__/data_view_with_timefield.ts create mode 100644 packages/kbn-unified-data-table/__mocks__/data_views.ts rename {src/plugins/discover/public => packages/kbn-unified-data-table}/__mocks__/es_hits_complex.ts (100%) create mode 100644 packages/kbn-unified-data-table/__mocks__/external_control_columns.tsx create mode 100644 packages/kbn-unified-data-table/__mocks__/local_storage_mock.ts create mode 100644 packages/kbn-unified-data-table/__mocks__/services.ts rename src/plugins/discover/public/__mocks__/grid_context.ts => packages/kbn-unified-data-table/__mocks__/table_context.ts (69%) create mode 100644 packages/kbn-unified-data-table/index.ts rename src/plugins/discover/public/components/discover_grid/types.ts => packages/kbn-unified-data-table/jest.config.js (59%) create mode 100644 packages/kbn-unified-data-table/kibana.jsonc create mode 100644 packages/kbn-unified-data-table/package.json rename {src/plugins/discover/public/components/doc_table => packages/kbn-unified-data-table/src/components}/actions/columns.test.ts (90%) rename {src/plugins/discover/public/components/doc_table => packages/kbn-unified-data-table/src/components}/actions/columns.ts (83%) rename {src/plugins/discover/public/components/discover_grid => packages/kbn-unified-data-table/src/components}/build_copy_column_button.test.tsx (86%) rename {src/plugins/discover/public/components/discover_grid => packages/kbn-unified-data-table/src/components}/build_copy_column_button.tsx (90%) rename {src/plugins/discover/public/components/discover_grid => packages/kbn-unified-data-table/src/components}/build_edit_field_button.test.tsx (81%) rename {src/plugins/discover/public/components/discover_grid => packages/kbn-unified-data-table/src/components}/build_edit_field_button.tsx (87%) rename src/plugins/discover/public/components/discover_grid/discover_grid.scss => packages/kbn-unified-data-table/src/components/data_table.scss (78%) rename src/plugins/discover/public/components/discover_grid/discover_grid.test.tsx => packages/kbn-unified-data-table/src/components/data_table.test.tsx (61%) rename src/plugins/discover/public/components/discover_grid/discover_grid.tsx => packages/kbn-unified-data-table/src/components/data_table.tsx (65%) rename src/plugins/discover/public/components/discover_grid/discover_grid_columns.test.tsx => packages/kbn-unified-data-table/src/components/data_table_columns.test.tsx (86%) rename src/plugins/discover/public/components/discover_grid/discover_grid_columns.tsx => packages/kbn-unified-data-table/src/components/data_table_columns.tsx (82%) rename src/plugins/discover/public/components/discover_grid/discover_grid_document_selection.test.tsx => packages/kbn-unified-data-table/src/components/data_table_document_selection.test.tsx (79%) rename src/plugins/discover/public/components/discover_grid/discover_grid_document_selection.tsx => packages/kbn-unified-data-table/src/components/data_table_document_selection.tsx (89%) rename src/plugins/discover/public/components/discover_grid/discover_grid_expand_button.test.tsx => packages/kbn-unified-data-table/src/components/data_table_expand_button.test.tsx (70%) rename src/plugins/discover/public/components/discover_grid/discover_grid_expand_button.tsx => packages/kbn-unified-data-table/src/components/data_table_expand_button.tsx (86%) rename src/plugins/discover/public/components/discover_grid/discover_grid_footer.test.tsx => packages/kbn-unified-data-table/src/components/data_table_footer.test.tsx (56%) rename src/plugins/discover/public/components/discover_grid/discover_grid_footer.tsx => packages/kbn-unified-data-table/src/components/data_table_footer.tsx (75%) rename src/plugins/discover/public/components/discover_grid/discover_grid_schema.ts => packages/kbn-unified-data-table/src/components/data_table_schema.ts (91%) create mode 100644 packages/kbn-unified-data-table/src/components/default_cell_actions.test.tsx rename src/plugins/discover/public/components/discover_grid/discover_grid_cell_actions.tsx => packages/kbn-unified-data-table/src/components/default_cell_actions.tsx (60%) create mode 100644 packages/kbn-unified-data-table/src/components/json_code_editor/__snapshots__/json_code_editor.test.tsx.snap create mode 100644 packages/kbn-unified-data-table/src/components/json_code_editor/json_code_editor.scss create mode 100644 packages/kbn-unified-data-table/src/components/json_code_editor/json_code_editor.test.tsx create mode 100644 packages/kbn-unified-data-table/src/components/json_code_editor/json_code_editor.tsx create mode 100644 packages/kbn-unified-data-table/src/components/json_code_editor/json_code_editor_common.tsx rename {src/plugins/discover/public/components/discover_grid => packages/kbn-unified-data-table/src}/constants.ts (81%) rename {src/plugins/discover/public => packages/kbn-unified-data-table/src}/hooks/use_data_grid_columns.test.tsx (94%) rename {src/plugins/discover/public => packages/kbn-unified-data-table/src}/hooks/use_data_grid_columns.ts (74%) create mode 100644 packages/kbn-unified-data-table/src/hooks/use_row_heights_options.test.tsx rename {src/plugins/discover/public => packages/kbn-unified-data-table/src}/hooks/use_row_heights_options.ts (84%) rename src/plugins/discover/public/components/discover_grid/discover_grid_context.tsx => packages/kbn-unified-data-table/src/table_context.tsx (70%) rename {src/plugins/discover/public => packages/kbn-unified-data-table/src}/types.ts (55%) rename {src/plugins/discover/public => packages/kbn-unified-data-table/src}/utils/columns.test.ts (95%) rename {src/plugins/discover/public => packages/kbn-unified-data-table/src}/utils/columns.ts (94%) rename {src/plugins/discover/public => packages/kbn-unified-data-table/src}/utils/convert_value_to_string.test.tsx (67%) rename {src/plugins/discover/public => packages/kbn-unified-data-table/src}/utils/convert_value_to_string.ts (95%) rename {src/plugins/discover/public => packages/kbn-unified-data-table/src}/utils/copy_value_to_clipboard.test.tsx (77%) rename {src/plugins/discover/public => packages/kbn-unified-data-table/src}/utils/copy_value_to_clipboard.ts (89%) rename {src/plugins/discover/public => packages/kbn-unified-data-table/src}/utils/get_field_capabilities.test.ts (100%) rename {src/plugins/discover/public => packages/kbn-unified-data-table/src}/utils/get_field_capabilities.ts (90%) rename {src/plugins/discover/public/components/discover_grid => packages/kbn-unified-data-table/src/utils}/get_render_cell_value.test.tsx (75%) rename {src/plugins/discover/public/components/discover_grid => packages/kbn-unified-data-table/src/utils}/get_render_cell_value.tsx (81%) rename {src/plugins/discover/public => packages/kbn-unified-data-table/src}/utils/popularize_field.test.ts (100%) rename {src/plugins/discover/public => packages/kbn-unified-data-table/src}/utils/popularize_field.ts (89%) rename {src/plugins/discover/public => packages/kbn-unified-data-table/src}/utils/row_heights.ts (70%) rename {src/plugins/discover/public => packages/kbn-unified-data-table/src}/utils/rows_per_page.test.ts (58%) rename {src/plugins/discover/public => packages/kbn-unified-data-table/src}/utils/rows_per_page.ts (62%) rename {src/plugins/discover/public => packages/kbn-unified-data-table/src}/utils/validate_row_height.ts (100%) create mode 100644 packages/kbn-unified-data-table/tsconfig.json delete mode 100644 src/plugins/discover/public/components/discover_grid/discover_grid_cell_actions.test.tsx rename src/plugins/discover/public/components/{discover_grid => discover_grid_flyout}/discover_grid_flyout.test.tsx (100%) rename src/plugins/discover/public/components/{discover_grid => discover_grid_flyout}/discover_grid_flyout.tsx (97%) create mode 100644 src/plugins/discover/public/components/discover_grid_flyout/index.ts delete mode 100644 src/plugins/discover/public/hooks/use_row_heights_options.test.tsx diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 1e307f6a38696..cd4fe15e8827c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -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 diff --git a/.i18nrc.json b/.i18nrc.json index 7b2d9f423c6a2..eb098a7011a15 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -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": [] } diff --git a/package.json b/package.json index 99ad7f5a0bb4c..86071279c1ce4 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/packages/kbn-unified-data-table/README.md b/packages/kbn-unified-data-table/README.md new file mode 100644 index 0000000000000..576a676289d7a --- /dev/null +++ b/packages/kbn-unified-data-table/README.md @@ -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 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 | 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 + { + // 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 React.ReactNode> + }} + renderDocumentView={() => + // Implement similar callback to render the Document flyout + const renderDetailsPanel = useCallback( + () => ( + + ), + [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: + +``` + 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[]) => ( + setExpandedDoc(undefined)} + setExpandedDoc={setExpandedDoc} + query={query} + /> + ), + [dataView, onAddColumn, onAddFilter, onRemoveColumn, query, savedSearch.id, setExpandedDoc] + ); +``` \ No newline at end of file diff --git a/packages/kbn-unified-data-table/__mocks__/config.ts b/packages/kbn-unified-data-table/__mocks__/config.ts new file mode 100644 index 0000000000000..b8ae4a531038c --- /dev/null +++ b/packages/kbn-unified-data-table/__mocks__/config.ts @@ -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; + +const createConfigMock = (): ConfigMock => ({ + has: jest.fn(), + get: jest.fn(), + set: jest.fn(), + getFlattenedPaths: jest.fn(), + toRaw: jest.fn(), +}); + +export const configMock = { + create: createConfigMock, +}; diff --git a/packages/kbn-unified-data-table/__mocks__/data_view_complex.ts b/packages/kbn-unified-data-table/__mocks__/data_view_complex.ts new file mode 100644 index 0000000000000..8122103c9f4d7 --- /dev/null +++ b/packages/kbn-unified-data-table/__mocks__/data_view_complex.ts @@ -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', +}); diff --git a/packages/kbn-unified-data-table/__mocks__/data_view_with_timefield.ts b/packages/kbn-unified-data-table/__mocks__/data_view_with_timefield.ts new file mode 100644 index 0000000000000..374b6b23f837b --- /dev/null +++ b/packages/kbn-unified-data-table/__mocks__/data_view_with_timefield.ts @@ -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', +}); diff --git a/packages/kbn-unified-data-table/__mocks__/data_views.ts b/packages/kbn-unified-data-table/__mocks__/data_views.ts new file mode 100644 index 0000000000000..bdbf63ecfbfbe --- /dev/null +++ b/packages/kbn-unified-data-table/__mocks__/data_views.ts @@ -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; +} + +export const dataViewsMock = createDataViewsMock(); diff --git a/src/plugins/discover/public/__mocks__/es_hits_complex.ts b/packages/kbn-unified-data-table/__mocks__/es_hits_complex.ts similarity index 100% rename from src/plugins/discover/public/__mocks__/es_hits_complex.ts rename to packages/kbn-unified-data-table/__mocks__/es_hits_complex.ts diff --git a/packages/kbn-unified-data-table/__mocks__/external_control_columns.tsx b/packages/kbn-unified-data-table/__mocks__/external_control_columns.tsx new file mode 100644 index 0000000000000..d67afccc01559 --- /dev/null +++ b/packages/kbn-unified-data-table/__mocks__/external_control_columns.tsx @@ -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 ( +
+ null} /> +
+ ); +}; + +const SimpleHeaderCell = () => { + return ( +
+ {'Additional Actions'} +
+ ); +}; + +const SelectionRowCell = ({ rowIndex }: { rowIndex: number }) => { + return ( +
+ null} + /> +
+ ); +}; + +const TestTrailingColumn = () => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + return ( + setIsPopoverOpen(!isPopoverOpen)} + /> + } + data-test-subj="test-trailing-column-popover-button" + closePopover={() => setIsPopoverOpen(false)} + > + {'Actions'} +
+ + + +
+
+ ); +}; + +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, +}; diff --git a/packages/kbn-unified-data-table/__mocks__/local_storage_mock.ts b/packages/kbn-unified-data-table/__mocks__/local_storage_mock.ts new file mode 100644 index 0000000000000..42cd33d2eb699 --- /dev/null +++ b/packages/kbn-unified-data-table/__mocks__/local_storage_mock.ts @@ -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; + constructor(defaultStore: Record) { + 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]; + } +} diff --git a/packages/kbn-unified-data-table/__mocks__/services.ts b/packages/kbn-unified-data-table/__mocks__/services.ts new file mode 100644 index 0000000000000..2c74668644497 --- /dev/null +++ b/packages/kbn-unified-data-table/__mocks__/services.ts @@ -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 = { + 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(); diff --git a/src/plugins/discover/public/__mocks__/grid_context.ts b/packages/kbn-unified-data-table/__mocks__/table_context.ts similarity index 69% rename from src/plugins/discover/public/__mocks__/grid_context.ts rename to packages/kbn-unified-data-table/__mocks__/table_context.ts index 8949945c2b0d8..4a4a75b0fa9e5 100644 --- a/src/plugins/discover/public/__mocks__/grid_context.ts +++ b/packages/kbn-unified-data-table/__mocks__/table_context.ts @@ -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); diff --git a/packages/kbn-unified-data-table/index.ts b/packages/kbn-unified-data-table/index.ts new file mode 100644 index 0000000000000..2c5e995619436 --- /dev/null +++ b/packages/kbn-unified-data-table/index.ts @@ -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'; diff --git a/src/plugins/discover/public/components/discover_grid/types.ts b/packages/kbn-unified-data-table/jest.config.js similarity index 59% rename from src/plugins/discover/public/components/discover_grid/types.ts rename to packages/kbn-unified-data-table/jest.config.js index 71d82e35126ac..5256221baacf4 100644 --- a/src/plugins/discover/public/components/discover_grid/types.ts +++ b/packages/kbn-unified-data-table/jest.config.js @@ -6,13 +6,8 @@ * Side Public License, v 1. */ -/** - * User configurable state of data grid, persisted in saved search - */ -export interface DiscoverGridSettings { - columns?: Record; -} - -export interface DiscoverGridSettingsColumn { - width?: number; -} +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-unified-data-table'], +}; diff --git a/packages/kbn-unified-data-table/kibana.jsonc b/packages/kbn-unified-data-table/kibana.jsonc new file mode 100644 index 0000000000000..de49c4caff1e5 --- /dev/null +++ b/packages/kbn-unified-data-table/kibana.jsonc @@ -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" +} diff --git a/packages/kbn-unified-data-table/package.json b/packages/kbn-unified-data-table/package.json new file mode 100644 index 0000000000000..79d4157293c05 --- /dev/null +++ b/packages/kbn-unified-data-table/package.json @@ -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" + ] +} diff --git a/src/plugins/discover/public/components/doc_table/actions/columns.test.ts b/packages/kbn-unified-data-table/src/components/actions/columns.test.ts similarity index 90% rename from src/plugins/discover/public/components/doc_table/actions/columns.test.ts rename to packages/kbn-unified-data-table/src/components/actions/columns.test.ts index c95ff0d8d7252..d8480cf2067b4 100644 --- a/src/plugins/discover/public/components/doc_table/actions/columns.test.ts +++ b/packages/kbn-unified-data-table/src/components/actions/columns.test.ts @@ -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) => 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', }); } diff --git a/src/plugins/discover/public/components/doc_table/actions/columns.ts b/packages/kbn-unified-data-table/src/components/actions/columns.ts similarity index 83% rename from src/plugins/discover/public/components/doc_table/actions/columns.ts rename to packages/kbn-unified-data-table/src/components/actions/columns.ts index b45d95433165a..3355902ece86e 100644 --- a/src/plugins/discover/public/components/doc_table/actions/columns.ts +++ b/packages/kbn-unified-data-table/src/components/actions/columns.ts @@ -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 }); } diff --git a/src/plugins/discover/public/components/discover_grid/build_copy_column_button.test.tsx b/packages/kbn-unified-data-table/src/components/build_copy_column_button.test.tsx similarity index 86% rename from src/plugins/discover/public/components/discover_grid/build_copy_column_button.test.tsx rename to packages/kbn-unified-data-table/src/components/build_copy_column_button.test.tsx index dda3a904bda3f..02a3c6e7e425e 100644 --- a/src/plugins/discover/public/components/discover_grid/build_copy_column_button.test.tsx +++ b/packages/kbn-unified-data-table/src/components/build_copy_column_button.test.tsx @@ -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); diff --git a/src/plugins/discover/public/components/discover_grid/build_copy_column_button.tsx b/packages/kbn-unified-data-table/src/components/build_copy_column_button.tsx similarity index 90% rename from src/plugins/discover/public/components/discover_grid/build_copy_column_button.tsx rename to packages/kbn-unified-data-table/src/components/build_copy_column_button.tsx index 111e80dd62e95..d1bfff1f1da41 100644 --- a/src/plugins/discover/public/components/discover_grid/build_copy_column_button.tsx +++ b/packages/kbn-unified-data-table/src/components/build_copy_column_button.tsx @@ -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: ( ), @@ -72,7 +72,7 @@ export function buildCopyColumnValuesButton({ return buildCopyColumnButton({ label: ( ), diff --git a/src/plugins/discover/public/components/discover_grid/build_edit_field_button.test.tsx b/packages/kbn-unified-data-table/src/components/build_edit_field_button.test.tsx similarity index 81% rename from src/plugins/discover/public/components/discover_grid/build_edit_field_button.test.tsx rename to packages/kbn-unified-data-table/src/components/build_edit_field_button.test.tsx index c78918976e88d..a9d1a37ab6eda 100644 --- a/src/plugins/discover/public/components/discover_grid/build_edit_field_button.test.tsx +++ b/packages/kbn-unified-data-table/src/components/build_edit_field_button.test.tsx @@ -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": , "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, diff --git a/src/plugins/discover/public/components/discover_grid/build_edit_field_button.tsx b/packages/kbn-unified-data-table/src/components/build_edit_field_button.tsx similarity index 87% rename from src/plugins/discover/public/components/discover_grid/build_edit_field_button.tsx rename to packages/kbn-unified-data-table/src/components/build_edit_field_button.tsx index 71da6ca691f93..87d405f608c8c 100644 --- a/src/plugins/discover/public/components/discover_grid/build_edit_field_button.tsx +++ b/packages/kbn-unified-data-table/src/components/build_edit_field_button.tsx @@ -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: ( - + ), iconType: 'pencil', iconProps: { size: 'm' }, diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid.scss b/packages/kbn-unified-data-table/src/components/data_table.scss similarity index 78% rename from src/plugins/discover/public/components/discover_grid/discover_grid.scss rename to packages/kbn-unified-data-table/src/components/data_table.scss index 0e870d366b609..8b0f8719a450f 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid.scss +++ b/packages/kbn-unified-data-table/src/components/data_table.scss @@ -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; } } diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid.test.tsx b/packages/kbn-unified-data-table/src/components/data_table.test.tsx similarity index 61% rename from src/plugins/discover/public/components/discover_grid/discover_grid.test.tsx rename to packages/kbn-unified-data-table/src/components/data_table.test.tsx index 153126b4d471c..7ca0888230749 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid.test.tsx +++ b/packages/kbn-unified-data-table/src/components/data_table.test.tsx @@ -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) => ( - - +async function getComponent(props: UnifiedDataTableProps = getProps()) { + const Proxy = (innerProps: UnifiedDataTableProps) => ( + + ); @@ -74,7 +87,7 @@ async function getComponent(props: DiscoverGridProps = getProps()) { return component; } -function getSelectedDocNr(component: ReactWrapper) { +function getSelectedDocNr(component: ReactWrapper) { const gridSelectionBtn = findTestSubject(component, 'dscGridSelectionBtn'); if (!gridSelectionBtn.length) { return 0; @@ -83,7 +96,7 @@ function getSelectedDocNr(component: ReactWrapper) { return Number(selectedNr); } -function getDisplayedDocNr(component: ReactWrapper) { +function getDisplayedDocNr(component: ReactWrapper) { const gridSelectionBtn = findTestSubject(component, 'discoverDocTable'); if (!gridSelectionBtn.length) { return 0; @@ -93,7 +106,7 @@ function getDisplayedDocNr(component: ReactWrapper) { } async function toggleDocSelection( - component: ReactWrapper, + component: ReactWrapper, 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; + let component: ReactWrapper; 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[] + ) =>
{hit.id}
, + 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: , + }); + + 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) => ( +
{props.columnId}
+ ), + }, + }); + + 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) => ( +
+ +
+ ), + }); + + 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'); + }); + }); }); diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid.tsx b/packages/kbn-unified-data-table/src/components/data_table.tsx similarity index 65% rename from src/plugins/discover/public/components/discover_grid/discover_grid.tsx rename to packages/kbn-unified-data-table/src/components/data_table.tsx index eb938e92bff6e..e5f5e5dbbba39 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid.tsx +++ b/packages/kbn-unified-data-table/src/components/data_table.tsx @@ -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; } 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(null); const [selectedDocs, setSelectedDocs] = useState([]); 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) => { + 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 ? ( - - ) : null, - [usedSelectedDocs, isFilterActive, rows, setIsFilterActive] + () => ( + <> + {usedSelectedDocs.length ? ( + + ) : 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 ( -
+
- +
); @@ -646,32 +751,18 @@ export const DiscoverGrid = ({ - +
); } return ( - { - setSelectedDocs(newSelectedDocs); - if (isFilterActive && newSelectedDocs.length === 0) { - setIsFilterActive(false); - } - }, - valueToStringConverter, - }} - > - + +
{loadingState !== DataLoadingState.loading && isPaginationEnabled && ( // we hide the footer for Surrounding Documents page - )} {searchTitle && ( @@ -718,13 +813,13 @@ export const DiscoverGrid = ({

{searchDescription ? ( ) : ( @@ -732,24 +827,10 @@ export const DiscoverGrid = ({

)} - {setExpandedDoc && expandedDoc && DocumentView && ( - setExpandedDoc(undefined)} - setExpandedDoc={setExpandedDoc} - query={query} - /> - )} + {canSetExpandedDoc && + expandedDoc && + renderDocumentView!(expandedDoc, displayedRows, displayedColumns)}
-
+ ); }; diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_columns.test.tsx b/packages/kbn-unified-data-table/src/components/data_table_columns.test.tsx similarity index 86% rename from src/plugins/discover/public/components/discover_grid/discover_grid_columns.test.tsx rename to packages/kbn-unified-data-table/src/components/data_table_columns.test.tsx index cea7f6c3505c9..01246603643fd 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid_columns.test.tsx +++ b/packages/kbn-unified-data-table/src/components/data_table_columns.test.tsx @@ -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": , "onClick": [Function], @@ -66,7 +66,7 @@ describe('Discover grid columns', function () { "iconType": "copyClipboard", "label": , "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": , "onClick": [Function], @@ -112,7 +113,7 @@ describe('Discover grid columns', function () { "iconType": "copyClipboard", "label": , "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": , "onClick": [Function], @@ -180,7 +182,7 @@ describe('Discover grid columns', function () { "iconType": "copyClipboard", "label": , "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": , "onClick": [Function], @@ -245,7 +248,7 @@ describe('Discover grid columns', function () { "iconType": "copyClipboard", "label": , "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": , "onClick": [Function], @@ -294,7 +298,7 @@ describe('Discover grid columns', function () { "iconType": "copyClipboard", "label": , "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": , "onClick": [Function], @@ -365,7 +370,7 @@ describe('Discover grid columns', function () { "iconType": "copyClipboard", "label": , "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": , "onClick": [Function], @@ -430,7 +436,7 @@ describe('Discover grid columns', function () { "iconType": "copyClipboard", "label": , "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": , "onClick": [Function], @@ -479,7 +486,7 @@ describe('Discover grid columns', function () { "iconType": "copyClipboard", "label": , "onClick": [Function], @@ -500,6 +507,7 @@ describe('Discover grid columns', function () { "id": "message", "isSortable": true, "schema": "string", + "visibleCellActions": undefined, }, ] `); diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_columns.tsx b/packages/kbn-unified-data-table/src/components/data_table_columns.tsx similarity index 82% rename from src/plugins/discover/public/components/discover_grid/discover_grid_columns.tsx rename to packages/kbn-unified-data-table/src/components/data_table_columns.tsx index 8effacbf9427f..4b66f2a2bd6cf 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid_columns.tsx +++ b/packages/kbn-unified-data-table/src/components/data_table_columns.tsx @@ -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: () => ( - {i18n.translate('discover.controlColumnHeader', { + {i18n.translate('unifiedDataTable.controlColumnHeader', { defaultMessage: 'Control column', })} @@ -50,7 +50,7 @@ const select = { headerCellRender: () => ( - {i18n.translate('discover.selectColumnHeader', { + {i18n.translate('unifiedDataTable.selectColumnHeader', { defaultMessage: 'Select column', })} @@ -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 = (
@@ -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, }) ); } diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_document_selection.test.tsx b/packages/kbn-unified-data-table/src/components/data_table_document_selection.test.tsx similarity index 79% rename from src/plugins/discover/public/components/discover_grid/discover_grid_document_selection.test.tsx rename to packages/kbn-unified-data-table/src/components/data_table_document_selection.test.tsx index 7fda5b74ce550..ca0d422948416 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid_document_selection.test.tsx +++ b/packages/kbn-unified-data-table/src/components/data_table_document_selection.test.tsx @@ -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( - + { isDetails={false} isExpandable={false} /> - + ); 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( - + { isDetails={false} isExpandable={false} /> - + ); 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( - + { isDetails={false} isExpandable={false} /> - + ); 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( - + { isDetails={false} isExpandable={false} /> - + ); 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(); + const component = mountWithIntl(); const button = findTestSubject(component, 'dscGridSelectionBtn'); expect(button.length).toBe(1); }); diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_document_selection.tsx b/packages/kbn-unified-data-table/src/components/data_table_document_selection.tsx similarity index 89% rename from src/plugins/discover/public/components/discover_grid/discover_grid_document_selection.tsx rename to packages/kbn-unified-data-table/src/components/data_table_document_selection.tsx index f14ec323f8ca2..213e24790e840 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid_document_selection.tsx +++ b/packages/kbn-unified-data-table/src/components/data_table_document_selection.tsx @@ -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); }} > - + ) : ( @@ -122,7 +125,7 @@ export function DiscoverGridDocumentToolbarBtn({ {(copy) => ( @@ -138,7 +141,7 @@ export function DiscoverGridDocumentToolbarBtn({ setIsFilterActive(false); }} > - + , ]; }, [ @@ -176,7 +179,7 @@ export function DiscoverGridDocumentToolbarBtn({ })} > diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_expand_button.test.tsx b/packages/kbn-unified-data-table/src/components/data_table_expand_button.test.tsx similarity index 70% rename from src/plugins/discover/public/components/discover_grid/discover_grid_expand_button.test.tsx rename to packages/kbn-unified-data-table/src/components/data_table_expand_button.test.tsx index e95853b99b7b7..56d6d3ae3ce0e 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid_expand_button.test.tsx +++ b/packages/kbn-unified-data-table/src/components/data_table_expand_button.test.tsx @@ -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( - + - + ); 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( - + - + ); 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( - + - + ); const button = findTestSubject(component, 'docTableExpandToggleColumn'); await button.simulate('click'); - expect(contextMock.setExpanded).toHaveBeenCalledWith(discoverGridContextMock.rows[1]); + expect(contextMock.setExpanded).toHaveBeenCalledWith(dataTableContextMock.rows[1]); }); }); diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_expand_button.tsx b/packages/kbn-unified-data-table/src/components/data_table_expand_button.tsx similarity index 86% rename from src/plugins/discover/public/components/discover_grid/discover_grid_expand_button.tsx rename to packages/kbn-unified-data-table/src/components/data_table_expand_button.tsx index 1a127f3639432..108ffaa4ec5fe 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid_expand_button.tsx +++ b/packages/kbn-unified-data-table/src/components/data_table_expand_button.tsx @@ -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(null); const [pressed, setPressed] = useState(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 ( { const component = mountWithIntl( - - + ); @@ -31,32 +33,32 @@ describe('DiscoverGridFooter', function () { it('should not render anything yet when all rows shown', async () => { const component = mountWithIntl( - - - + ); expect(component.isEmptyRender()).toBe(true); }); it('should render a message for the last page', async () => { const component = mountWithIntl( - - - + ); - 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( - - - + ); - 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( - - - + ); - 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( - - - + ); - 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.' ); }); diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_footer.tsx b/packages/kbn-unified-data-table/src/components/data_table_footer.tsx similarity index 75% rename from src/plugins/discover/public/components/discover_grid/discover_grid_footer.tsx rename to packages/kbn-unified-data-table/src/components/data_table_footer.tsx index 540c7102bd424..21819a023afed 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid_footer.tsx +++ b/packages/kbn-unified-data-table/src/components/data_table_footer.tsx @@ -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 = (props) => { +export const UnifiedDataTableFooter: React.FC = (props) => { const { isLoadingMore, rowCount, @@ -34,8 +37,8 @@ export const DiscoverGridFooter: React.FC = (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 = (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 ( - + = (props) => `} > - + ); } - return ; + return ; } if (rowCount < totalHits) { // show only a message for embeddable - return ; + return ; } return null; }; -interface DiscoverGridFooterContainerProps extends DiscoverGridFooterProps { +interface UnifiedDataTableFooterContainerProps extends UnifiedDataTableFooterProps { hasButton: boolean; } -const DiscoverGridFooterContainer: React.FC = ({ +const UnifiedDataTableFooterContainer: React.FC = ({ 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 = return (

= {hasButton ? ( = /> ) : ( 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( + + } + rowIndex={1} + colIndex={1} + columnId="extension" + isExpanded={false} + /> + + ); + 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( + + } + rowIndex={0} + colIndex={1} + columnId="extension" + isExpanded={false} + /> + + ); + 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( + + } + rowIndex={4} + colIndex={1} + columnId="message" + isExpanded={false} + /> + + ); + 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( + + } + rowIndex={1} + colIndex={1} + columnId="extension" + isExpanded={false} + /> + + ); + 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( + + {buildCopyValueButton( + { + Component: (props: any) => , + colIndex: 1, + rowIndex: 1, + columnId: 'extension', + } as unknown as EuiDataGridColumnCellActionProps, + servicesMock.toastNotifications, + dataTableContextMock.valueToStringConverter + )} + + ); + const button = findTestSubject(component, 'copyClipboardButton'); + await button.simulate('click'); + expect(mockCopyToClipboard).toHaveBeenCalledWith('jpg'); + }); +}); diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_cell_actions.tsx b/packages/kbn-unified-data-table/src/components/default_cell_actions.tsx similarity index 60% rename from src/plugins/discover/public/components/discover_grid/discover_grid_cell_actions.tsx rename to packages/kbn-unified-data-table/src/components/default_cell_actions.tsx index 32779230e1b7f..6005d75ea6632 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid_cell_actions.tsx +++ b/packages/kbn-unified-data-table/src/components/default_cell_actions.tsx @@ -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', })} @@ -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', })} ); }; -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', })} ); -}; +} -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 + ), + ]; } diff --git a/packages/kbn-unified-data-table/src/components/json_code_editor/__snapshots__/json_code_editor.test.tsx.snap b/packages/kbn-unified-data-table/src/components/json_code_editor/__snapshots__/json_code_editor.test.tsx.snap new file mode 100644 index 0000000000000..7af546298e0d8 --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/json_code_editor/__snapshots__/json_code_editor.test.tsx.snap @@ -0,0 +1,17 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`returns the \`JsonCodeEditor\` component 1`] = ` + +`; diff --git a/packages/kbn-unified-data-table/src/components/json_code_editor/json_code_editor.scss b/packages/kbn-unified-data-table/src/components/json_code_editor/json_code_editor.scss new file mode 100644 index 0000000000000..a07f7ccac408d --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/json_code_editor/json_code_editor.scss @@ -0,0 +1,3 @@ +.unifiedDataTableJsonEditor { + width: 100%; +} diff --git a/packages/kbn-unified-data-table/src/components/json_code_editor/json_code_editor.test.tsx b/packages/kbn-unified-data-table/src/components/json_code_editor/json_code_editor.test.tsx new file mode 100644 index 0000000000000..e1ec1373f8657 --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/json_code_editor/json_code_editor.test.tsx @@ -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()).toMatchSnapshot(); +}); diff --git a/packages/kbn-unified-data-table/src/components/json_code_editor/json_code_editor.tsx b/packages/kbn-unified-data-table/src/components/json_code_editor/json_code_editor.tsx new file mode 100644 index 0000000000000..d08e35eb6d4bf --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/json_code_editor/json_code_editor.tsx @@ -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; + 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 ( + void 0} + hideCopyButton={true} + /> + ); +} diff --git a/packages/kbn-unified-data-table/src/components/json_code_editor/json_code_editor_common.tsx b/packages/kbn-unified-data-table/src/components/json_code_editor/json_code_editor_common.tsx new file mode 100644 index 0000000000000..079a98c305459 --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/json_code_editor/json_code_editor_common.tsx @@ -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 = ( + + ); + if (hideCopyButton) { + return codeEditor; + } + return ( + + + +

+ + {(copy) => ( + + {copyToClipboardLabel} + + )} + +
+ + {codeEditor} + + ); +}; + +export const JSONCodeEditorCommonMemoized = React.memo((props: JsonCodeEditorCommonProps) => { + return ; +}); diff --git a/src/plugins/discover/public/components/discover_grid/constants.ts b/packages/kbn-unified-data-table/src/constants.ts similarity index 81% rename from src/plugins/discover/public/components/discover_grid/constants.ts rename to packages/kbn-unified-data-table/src/constants.ts index 8f7c40e33b957..1fb391ddc7f70 100644 --- a/src/plugins/discover/public/components/discover_grid/constants.ts +++ b/packages/kbn-unified-data-table/src/constants.ts @@ -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; diff --git a/src/plugins/discover/public/hooks/use_data_grid_columns.test.tsx b/packages/kbn-unified-data-table/src/hooks/use_data_grid_columns.test.tsx similarity index 94% rename from src/plugins/discover/public/hooks/use_data_grid_columns.test.tsx rename to packages/kbn-unified-data-table/src/hooks/use_data_grid_columns.test.tsx index 4a1874ec8e940..e0afdab4ff043 100644 --- a/src/plugins/discover/public/hooks/use_data_grid_columns.test.tsx +++ b/packages/kbn-unified-data-table/src/hooks/use_data_grid_columns.test.tsx @@ -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', () => { diff --git a/src/plugins/discover/public/hooks/use_data_grid_columns.ts b/packages/kbn-unified-data-table/src/hooks/use_data_grid_columns.ts similarity index 74% rename from src/plugins/discover/public/hooks/use_data_grid_columns.ts rename to packages/kbn-unified-data-table/src/hooks/use_data_grid_columns.ts index 22fc8e9836888..088f7b0491c69 100644 --- a/src/plugins/discover/public/hooks/use_data_grid_columns.ts +++ b/packages/kbn-unified-data-table/src/hooks/use_data_grid_columns.ts @@ -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 { diff --git a/packages/kbn-unified-data-table/src/hooks/use_row_heights_options.test.tsx b/packages/kbn-unified-data-table/src/hooks/use_row_heights_options.test.tsx new file mode 100644 index 0000000000000..2da08c178720a --- /dev/null +++ b/packages/kbn-unified-data-table/src/hooks/use_row_heights_options.test.tsx @@ -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, + }); + }); +}); diff --git a/src/plugins/discover/public/hooks/use_row_heights_options.ts b/packages/kbn-unified-data-table/src/hooks/use_row_heights_options.ts similarity index 84% rename from src/plugins/discover/public/hooks/use_row_heights_options.ts rename to packages/kbn-unified-data-table/src/hooks/use_row_heights_options.ts index a9ef67ace530b..9d460c8ea2ba9 100644 --- a/src/plugins/discover/public/hooks/use_row_heights_options.ts +++ b/packages/kbn-unified-data-table/src/hooks/use_row_heights_options.ts @@ -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]); }; diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_context.tsx b/packages/kbn-unified-data-table/src/table_context.tsx similarity index 70% rename from src/plugins/discover/public/components/discover_grid/discover_grid_context.tsx rename to packages/kbn-unified-data-table/src/table_context.tsx index f2dfd540c7d05..2a1d4656d4a65 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid_context.tsx +++ b/packages/kbn-unified-data-table/src/table_context.tsx @@ -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; } -const defaultContext = {} as unknown as GridContext; +const defaultContext = {} as unknown as DataTableContext; -export const DiscoverGridContext = React.createContext(defaultContext); +export const UnifiedDataTableContext = React.createContext(defaultContext); diff --git a/src/plugins/discover/public/types.ts b/packages/kbn-unified-data-table/src/types.ts similarity index 55% rename from src/plugins/discover/public/types.ts rename to packages/kbn-unified-data-table/src/types.ts index e812e11f77ec1..79ca4e721e910 100644 --- a/src/plugins/discover/public/types.ts +++ b/packages/kbn-unified-data-table/src/types.ts @@ -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; +} + +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[]; -} diff --git a/src/plugins/discover/public/utils/columns.test.ts b/packages/kbn-unified-data-table/src/utils/columns.test.ts similarity index 95% rename from src/plugins/discover/public/utils/columns.test.ts rename to packages/kbn-unified-data-table/src/utils/columns.test.ts index 5ef7d8fea450f..36a8b60a6bc68 100644 --- a/src/plugins/discover/public/utils/columns.test.ts +++ b/packages/kbn-unified-data-table/src/utils/columns.test.ts @@ -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', () => { diff --git a/src/plugins/discover/public/utils/columns.ts b/packages/kbn-unified-data-table/src/utils/columns.ts similarity index 94% rename from src/plugins/discover/public/utils/columns.ts rename to packages/kbn-unified-data-table/src/utils/columns.ts index 49e234b11decc..f2a72f0a8b650 100644 --- a/src/plugins/discover/public/utils/columns.ts +++ b/packages/kbn-unified-data-table/src/utils/columns.ts @@ -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 diff --git a/src/plugins/discover/public/utils/convert_value_to_string.test.tsx b/packages/kbn-unified-data-table/src/utils/convert_value_to_string.test.tsx similarity index 67% rename from src/plugins/discover/public/utils/convert_value_to_string.test.tsx rename to packages/kbn-unified-data-table/src/utils/convert_value_to_string.test.tsx index dd81ad621f182..aa8ba719c5ba2 100644 --- a/src/plugins/discover/public/utils/convert_value_to_string.test.tsx +++ b/packages/kbn-unified-data-table/src/utils/convert_value_to_string.test.tsx @@ -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: { diff --git a/src/plugins/discover/public/utils/convert_value_to_string.ts b/packages/kbn-unified-data-table/src/utils/convert_value_to_string.ts similarity index 95% rename from src/plugins/discover/public/utils/convert_value_to_string.ts rename to packages/kbn-unified-data-table/src/utils/convert_value_to_string.ts index 605c7912b17f1..486ed5574dbf2 100644 --- a/src/plugins/discover/public/utils/convert_value_to_string.ts +++ b/packages/kbn-unified-data-table/src/utils/convert_value_to_string.ts @@ -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'; diff --git a/src/plugins/discover/public/utils/copy_value_to_clipboard.test.tsx b/packages/kbn-unified-data-table/src/utils/copy_value_to_clipboard.test.tsx similarity index 77% rename from src/plugins/discover/public/utils/copy_value_to_clipboard.test.tsx rename to packages/kbn-unified-data-table/src/utils/copy_value_to_clipboard.test.tsx index 595b7601b6f65..7ff5c9b3f19b6 100644 --- a/src/plugins/discover/public/utils/copy_value_to_clipboard.test.tsx +++ b/packages/kbn-unified-data-table/src/utils/copy_value_to_clipboard.test.tsx @@ -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.', }); diff --git a/src/plugins/discover/public/utils/copy_value_to_clipboard.ts b/packages/kbn-unified-data-table/src/utils/copy_value_to_clipboard.ts similarity index 89% rename from src/plugins/discover/public/utils/copy_value_to_clipboard.ts rename to packages/kbn-unified-data-table/src/utils/copy_value_to_clipboard.ts index c700fa748f335..2e9620b42728b 100644 --- a/src/plugins/discover/public/utils/copy_value_to_clipboard.ts +++ b/packages/kbn-unified-data-table/src/utils/copy_value_to_clipboard.ts @@ -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', }); diff --git a/src/plugins/discover/public/utils/get_field_capabilities.test.ts b/packages/kbn-unified-data-table/src/utils/get_field_capabilities.test.ts similarity index 100% rename from src/plugins/discover/public/utils/get_field_capabilities.test.ts rename to packages/kbn-unified-data-table/src/utils/get_field_capabilities.test.ts diff --git a/src/plugins/discover/public/utils/get_field_capabilities.ts b/packages/kbn-unified-data-table/src/utils/get_field_capabilities.ts similarity index 90% rename from src/plugins/discover/public/utils/get_field_capabilities.ts rename to packages/kbn-unified-data-table/src/utils/get_field_capabilities.ts index cd63c1c189e73..8374801ec311e 100644 --- a/src/plugins/discover/public/utils/get_field_capabilities.ts +++ b/packages/kbn-unified-data-table/src/utils/get_field_capabilities.ts @@ -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); diff --git a/src/plugins/discover/public/components/discover_grid/get_render_cell_value.test.tsx b/packages/kbn-unified-data-table/src/utils/get_render_cell_value.test.tsx similarity index 75% rename from src/plugins/discover/public/components/discover_grid/get_render_cell_value.test.tsx rename to packages/kbn-unified-data-table/src/utils/get_render_cell_value.test.tsx index 536a84172a88c..941dccabf2474 100644 --- a/src/plugins/discover/public/components/discover_grid/get_render_cell_value.test.tsx +++ b/packages/kbn-unified-data-table/src/utils/get_render_cell_value.test.tsx @@ -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) => ( + + ); + + 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( - ); expect(component.html()).toMatchInlineSnapshot( - `"100"` + `"100"` ); }); 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( - ); expect(component.html()).toMatchInlineSnapshot( - `"
100
"` + `"
100
"` ); }); 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( - ); expect(component.html()).toMatchInlineSnapshot( - `"
100
"` + `"
100
"` ); 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( - @@ -191,7 +216,7 @@ describe('Discover grid cell rendering', function () { extension { - 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( - - { - 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( - @@ -340,7 +367,7 @@ describe('Discover grid cell rendering', function () { extension { - 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( - @@ -419,7 +447,7 @@ describe('Discover grid cell rendering', function () { extension { - 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( - - { - 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( - @@ -577,7 +607,7 @@ describe('Discover grid cell rendering', function () { object.value { (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( - @@ -619,7 +650,7 @@ describe('Discover grid cell rendering', function () { object.value { 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( - - { 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( - { (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( - { - 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( - ); expect(component.html()).toMatchInlineSnapshot( - `"-"` + `"-"` ); }); 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( - ); expect(component.html()).toMatchInlineSnapshot( - `"-"` + `"-"` ); }); @@ -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( - 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 = {pairs.map(([key, value]) => ( {key} @@ -144,7 +167,7 @@ export const getRenderCellValueFn = function getInnerColumns(fields: Record, 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 = ( @@ -216,7 +239,7 @@ function renderPopoverContent({ String(v) }; - const formatted = (values as unknown[]) + const formatted = values .map((val: unknown) => formatter.convert(val, 'html', { field: subField, diff --git a/src/plugins/discover/public/utils/popularize_field.test.ts b/packages/kbn-unified-data-table/src/utils/popularize_field.test.ts similarity index 100% rename from src/plugins/discover/public/utils/popularize_field.test.ts rename to packages/kbn-unified-data-table/src/utils/popularize_field.test.ts diff --git a/src/plugins/discover/public/utils/popularize_field.ts b/packages/kbn-unified-data-table/src/utils/popularize_field.ts similarity index 89% rename from src/plugins/discover/public/utils/popularize_field.ts rename to packages/kbn-unified-data-table/src/utils/popularize_field.ts index 9ab711be49266..3feca3fd3d4e7 100644 --- a/src/plugins/discover/public/utils/popularize_field.ts +++ b/packages/kbn-unified-data-table/src/utils/popularize_field.ts @@ -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, diff --git a/src/plugins/discover/public/utils/row_heights.ts b/packages/kbn-unified-data-table/src/utils/row_heights.ts similarity index 70% rename from src/plugins/discover/public/utils/row_heights.ts rename to packages/kbn-unified-data-table/src/utils/row_heights.ts index f1e096b76b9f9..45f472286d030 100644 --- a/src/plugins/discover/public/utils/row_heights.ts +++ b/packages/kbn-unified-data-table/src/utils/row_heights.ts @@ -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, }); diff --git a/src/plugins/discover/public/utils/rows_per_page.test.ts b/packages/kbn-unified-data-table/src/utils/rows_per_page.test.ts similarity index 58% rename from src/plugins/discover/public/utils/rows_per_page.test.ts rename to packages/kbn-unified-data-table/src/utils/rows_per_page.test.ts index 25eddf9a44de2..8da8ea099734b 100644 --- a/src/plugins/discover/public/utils/rows_per_page.test.ts +++ b/packages/kbn-unified-data-table/src/utils/rows_per_page.test.ts @@ -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 - ); - }); - }); }); diff --git a/src/plugins/discover/public/utils/rows_per_page.ts b/packages/kbn-unified-data-table/src/utils/rows_per_page.ts similarity index 62% rename from src/plugins/discover/public/utils/rows_per_page.ts rename to packages/kbn-unified-data-table/src/utils/rows_per_page.ts index bc5f07f6253d3..2eb547df1a36f 100644 --- a/src/plugins/discover/public/utils/rows_per_page.ts +++ b/packages/kbn-unified-data-table/src/utils/rows_per_page.ts @@ -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; -}; diff --git a/src/plugins/discover/public/utils/validate_row_height.ts b/packages/kbn-unified-data-table/src/utils/validate_row_height.ts similarity index 100% rename from src/plugins/discover/public/utils/validate_row_height.ts rename to packages/kbn-unified-data-table/src/utils/validate_row_height.ts diff --git a/packages/kbn-unified-data-table/tsconfig.json b/packages/kbn-unified-data-table/tsconfig.json new file mode 100644 index 0000000000000..bed8d16a279b1 --- /dev/null +++ b/packages/kbn-unified-data-table/tsconfig.json @@ -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", + ] +} diff --git a/packages/shared-ux/code_editor/impl/__snapshots__/code_editor.test.tsx.snap b/packages/shared-ux/code_editor/impl/__snapshots__/code_editor.test.tsx.snap index f86ba86999236..b8a983ce582f2 100644 --- a/packages/shared-ux/code_editor/impl/__snapshots__/code_editor.test.tsx.snap +++ b/packages/shared-ux/code_editor/impl/__snapshots__/code_editor.test.tsx.snap @@ -351,7 +351,7 @@ exports[` is rendered 1`] = ` = ({ value, onChange, width, + height = '100px', options, overrideEditorWillMount, editorDidMount, @@ -478,7 +479,7 @@ export const CodeEditor: React.FC = ({ 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={{ diff --git a/src/plugins/discover/common/constants.ts b/src/plugins/discover/common/constants.ts index 3cb696766b65f..e80f9d2449ebc 100644 --- a/src/plugins/discover/common/constants.ts +++ b/src/plugins/discover/common/constants.ts @@ -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; +}; diff --git a/src/plugins/discover/public/application/context/context_app.tsx b/src/plugins/discover/public/application/context/context_app.tsx index c11a9187a937f..19a5058638392 100644 --- a/src/plugins/discover/public/application/context/context_app.tsx +++ b/src/plugins/discover/public/application/context/context_app.tsx @@ -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, diff --git a/src/plugins/discover/public/application/context/context_app_content.test.tsx b/src/plugins/discover/public/application/context/context_app_content.test.tsx index f7ce2235333d4..f6809d63c035d 100644 --- a/src/plugins/discover/public/application/context/context_app_content.test.tsx +++ b/src/plugins/discover/public/application/context/context_app_content.test.tsx @@ -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); }); }); diff --git a/src/plugins/discover/public/application/context/context_app_content.tsx b/src/plugins/discover/public/application/context/context_app_content.tsx index 7e07a594e53c6..0443718be6e2b 100644 --- a/src/plugins/discover/public/application/context/context_app_content.tsx +++ b/src/plugins/discover/public/application/context/context_app_content.tsx @@ -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[]) => ( + setExpandedDoc(undefined)} + setExpandedDoc={setExpandedDoc} + /> + ), + [addFilter, dataView, onAddColumn, onRemoveColumn] + ); + return ( {!!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 }} />
diff --git a/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx b/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx index cce575c308175..ec809e2c2f760 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx @@ -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[]) => ( + setExpandedDoc(undefined)} + setExpandedDoc={setExpandedDoc} + query={query} + /> + ), + [dataView, onAddColumn, onAddFilter, onRemoveColumn, query, savedSearch.id, setExpandedDoc] + ); + if (isDataViewLoading || (isEmptyDataResult && isDataLoading)) { return (
@@ -217,7 +244,7 @@ function DiscoverDocumentsComponent({ data-test-subj="dscInterceptedWarningsCallout" /> )} - {isLegacy && rows && rows.length && ( + {isLegacy && rows && rows.length > 0 && ( <> {!hideAnnouncements && } )} -
+
-
diff --git a/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx b/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx index 3d0771efaeeec..58c23aa561e12 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx @@ -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, diff --git a/src/plugins/discover/public/application/main/services/discover_app_state_container.ts b/src/plugins/discover/public/application/main/services/discover_app_state_container.ts index ea2d1d1f2324a..47cd216b1547e 100644 --- a/src/plugins/discover/public/application/main/services/discover_app_state_container.ts +++ b/src/plugins/discover/public/application/main/services/discover_app_state_container.ts @@ -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 { @@ -87,7 +87,7 @@ export interface DiscoverAppState { /** * Data Grid related state */ - grid?: DiscoverGridSettings; + grid?: UnifiedDataTableSettings; /** * Hide chart */ diff --git a/src/plugins/discover/public/application/main/utils/fetch_documents.ts b/src/plugins/discover/public/application/main/utils/fetch_documents.ts index 767163259304f..892da705f4b8f 100644 --- a/src/plugins/discover/public/application/main/utils/fetch_documents.ts +++ b/src/plugins/discover/public/application/main/utils/fetch_documents.ts @@ -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'; diff --git a/src/plugins/discover/public/application/main/utils/fetch_text_based.ts b/src/plugins/discover/public/application/main/utils/fetch_text_based.ts index 4794803c56408..6a164bfd8a5f8 100644 --- a/src/plugins/discover/public/application/main/utils/fetch_text_based.ts +++ b/src/plugins/discover/public/application/main/utils/fetch_text_based.ts @@ -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: { diff --git a/src/plugins/discover/public/application/types.ts b/src/plugins/discover/public/application/types.ts index 57677236cbf7a..3fc375a5ebcb7 100644 --- a/src/plugins/discover/public/application/types.ts +++ b/src/plugins/discover/public/application/types.ts @@ -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[]; +} diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_cell_actions.test.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid_cell_actions.test.tsx deleted file mode 100644 index 079a26c265d27..0000000000000 --- a/src/plugins/discover/public/components/discover_grid/discover_grid_cell_actions.test.tsx +++ /dev/null @@ -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( - - } - rowIndex={1} - colIndex={1} - columnId="extension" - isExpanded={false} - /> - - ); - 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( - - } - rowIndex={0} - colIndex={1} - columnId="extension" - isExpanded={false} - /> - - ); - 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( - - } - rowIndex={4} - colIndex={1} - columnId="message" - isExpanded={false} - /> - - ); - 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( - - } - rowIndex={1} - colIndex={1} - columnId="extension" - isExpanded={false} - /> - - ); - 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( - - } - rowIndex={1} - colIndex={1} - columnId="extension" - isExpanded={false} - /> - - ); - const button = findTestSubject(component, 'copyClipboardButton'); - await button.simulate('click'); - expect(mockCopyToClipboard).toHaveBeenCalledWith('jpg'); - }); -}); diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_flyout.test.tsx b/src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout.test.tsx similarity index 100% rename from src/plugins/discover/public/components/discover_grid/discover_grid_flyout.test.tsx rename to src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout.test.tsx diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_flyout.tsx b/src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout.tsx similarity index 97% rename from src/plugins/discover/public/components/discover_grid/discover_grid_flyout.tsx rename to src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout.tsx index ab25ac97fdc3c..a9130df52738e 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid_flyout.tsx +++ b/src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout.tsx @@ -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({

@@ -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({ ); } + +// eslint-disable-next-line import/no-default-export +export default DiscoverGridFlyout; diff --git a/src/plugins/discover/public/components/discover_grid_flyout/index.ts b/src/plugins/discover/public/components/discover_grid_flyout/index.ts new file mode 100644 index 0000000000000..da7fa274494b1 --- /dev/null +++ b/src/plugins/discover/public/components/discover_grid_flyout/index.ts @@ -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'))); diff --git a/src/plugins/discover/public/components/doc_table/components/pager/tool_bar_pagination.tsx b/src/plugins/discover/public/components/doc_table/components/pager/tool_bar_pagination.tsx index 18ba8817391ac..bd0e2e3439451 100644 --- a/src/plugins/discover/public/components/doc_table/components/pager/tool_bar_pagination.tsx +++ b/src/plugins/discover/public/components/doc_table/components/pager/tool_bar_pagination.tsx @@ -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; diff --git a/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx b/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx index 1bf0d80dd7d6e..9a44ae3834f7c 100644 --- a/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx +++ b/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx @@ -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 & +export type SearchProps = Partial & Partial & { 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; diff --git a/src/plugins/discover/public/embeddable/saved_search_embeddable_component.tsx b/src/plugins/discover/public/embeddable/saved_search_embeddable_component.tsx index fd28f3114211f..f8c7aa39c1a7c 100644 --- a/src/plugins/discover/public/embeddable/saved_search_embeddable_component.tsx +++ b/src/plugins/discover/public/embeddable/saved_search_embeddable_component.tsx @@ -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" /> ); } diff --git a/src/plugins/discover/public/embeddable/saved_search_grid.tsx b/src/plugins/discover/public/embeddable/saved_search_grid.tsx index b818286660301..580a55534b573 100644 --- a/src/plugins/discover/public/embeddable/saved_search_grid.tsx +++ b/src/plugins/discover/public/embeddable/saved_search_grid.tsx @@ -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(undefined); + const renderDocumentView = useCallback( + (hit: DataTableRecord, displayedRows: DataTableRecord[], displayedColumns: string[]) => ( + setExpandedDoc(undefined)} + setExpandedDoc={setExpandedDoc} + query={props.query} + /> + ), + [ + props.dataView, + props.onAddColumn, + props.onFilter, + props.onRemoveColumn, + props.query, + props.savedSearchId, + ] + ); + return ( - ); diff --git a/src/plugins/discover/public/hooks/use_row_heights_options.test.tsx b/src/plugins/discover/public/hooks/use_row_heights_options.test.tsx deleted file mode 100644 index 113f34ab723dd..0000000000000 --- a/src/plugins/discover/public/hooks/use_row_heights_options.test.tsx +++ /dev/null @@ -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 }) => ( - {children} - ); -}; - -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, - }); - }); -}); diff --git a/src/plugins/discover/tsconfig.json b/src/plugins/discover/tsconfig.json index 231dcc926b830..3328073a026e7 100644 --- a/src/plugins/discover/tsconfig.json +++ b/src/plugins/discover/tsconfig.json @@ -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": [ diff --git a/src/plugins/unified_doc_viewer/public/components/doc_viewer_source/source.tsx b/src/plugins/unified_doc_viewer/public/components/doc_viewer_source/source.tsx index 733f9040b3b3a..26c771e405be8 100644 --- a/src/plugins/unified_doc_viewer/public/components/doc_viewer_source/source.tsx +++ b/src/plugins/unified_doc_viewer/public/components/doc_viewer_source/source.tsx @@ -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'; diff --git a/test/functional/apps/discover/group2/_data_grid_footer.ts b/test/functional/apps/discover/group2/_data_grid_footer.ts index a1d2ff0294d23..4cc7b96a52607 100644 --- a/test/functional/apps/discover/group2/_data_grid_footer.ts +++ b/test/functional/apps/discover/group2/_data_grid_footer.ts @@ -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) { diff --git a/test/functional/apps/discover/group2/_data_grid_pagination.ts b/test/functional/apps/discover/group2/_data_grid_pagination.ts index 7da02308c73be..4d0c81c4cfebc 100644 --- a/test/functional/apps/discover/group2/_data_grid_pagination.ts +++ b/test/functional/apps/discover/group2/_data_grid_pagination.ts @@ -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 () => { diff --git a/tsconfig.base.json b/tsconfig.base.json index f30cf88e71157..afbbdcfe7601b 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -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"], diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 55fe275930510..e320b5a24c8be 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -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 d’enregistrer 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}", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 84fc3b101eb4e..26fa98a750f8d 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -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}を結合しました。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index a177efa7c53f4..e96481d694485 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -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}", diff --git a/x-pack/test/security_solution_cypress/cypress/screens/discover.ts b/x-pack/test/security_solution_cypress/cypress/screens/discover.ts index 577246e5f6568..72f0801fde778 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/discover.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/discover.ts @@ -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) => diff --git a/yarn.lock b/yarn.lock index 38cdd71492d8e..e1dc303ef252a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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 ""