From af9b7fe24969be7485acf3a4d834f614d3d9d602 Mon Sep 17 00:00:00 2001 From: ananzh Date: Fri, 30 Jun 2023 16:59:34 +0000 Subject: [PATCH] Implement table using table gird and remove doc_viewer_link Issue Resolve https://github.com/opensearch-project/OpenSearch-Dashboards/issues/4442 Signed-off-by: ananzh --- .../create_discover_legacy_directive.ts | 2 +- .../components/data_grid/constants.ts | 12 + .../data_grid/data_grid_context.tsx | 20 ++ .../components/data_grid/data_grid_flyout.tsx | 161 +++++++++ .../components/data_grid/data_grid_table.scss | 9 + .../components/data_grid/data_grid_table.tsx | 135 +++++++ .../data_grid/data_grid_table_cell_value.tsx | 80 +++++ .../data_grid/data_grid_table_columns.tsx | 68 ++++ .../data_grid_table_docview_expand_button.tsx | 27 ++ .../application/components/discover.scss | 33 ++ .../application/components/discover.tsx | 317 +++++++++++++++++ .../components/discover_legacy.tsx | 332 ------------------ .../doc_viewer_links.test.tsx.snap | 34 -- .../doc_viewer_links.test.tsx | 68 ---- .../doc_viewer_links/doc_viewer_links.tsx | 35 -- .../components/utils/use_pagination.ts | 39 ++ .../doc_views/doc_views_registry.ts | 16 - .../application/doc_views/doc_views_types.ts | 1 - .../application/angular/doc_viewer_links.tsx | 28 -- 19 files changed, 902 insertions(+), 515 deletions(-) create mode 100644 src/plugins/discover/public/application/components/data_grid/constants.ts create mode 100644 src/plugins/discover/public/application/components/data_grid/data_grid_context.tsx create mode 100644 src/plugins/discover/public/application/components/data_grid/data_grid_flyout.tsx create mode 100644 src/plugins/discover/public/application/components/data_grid/data_grid_table.scss create mode 100644 src/plugins/discover/public/application/components/data_grid/data_grid_table.tsx create mode 100644 src/plugins/discover/public/application/components/data_grid/data_grid_table_cell_value.tsx create mode 100644 src/plugins/discover/public/application/components/data_grid/data_grid_table_columns.tsx create mode 100644 src/plugins/discover/public/application/components/data_grid/data_grid_table_docview_expand_button.tsx create mode 100644 src/plugins/discover/public/application/components/discover.scss create mode 100644 src/plugins/discover/public/application/components/discover.tsx delete mode 100644 src/plugins/discover/public/application/components/discover_legacy.tsx delete mode 100644 src/plugins/discover/public/application/components/doc_viewer_links/__snapshots__/doc_viewer_links.test.tsx.snap delete mode 100644 src/plugins/discover/public/application/components/doc_viewer_links/doc_viewer_links.test.tsx delete mode 100644 src/plugins/discover/public/application/components/doc_viewer_links/doc_viewer_links.tsx create mode 100644 src/plugins/discover/public/application/components/utils/use_pagination.ts delete mode 100644 src/plugins/discover_legacy/public/application/angular/doc_viewer_links.tsx diff --git a/src/plugins/discover/public/application/components/create_discover_legacy_directive.ts b/src/plugins/discover/public/application/components/create_discover_legacy_directive.ts index 09cc33964862..112c66bafe8b 100644 --- a/src/plugins/discover/public/application/components/create_discover_legacy_directive.ts +++ b/src/plugins/discover/public/application/components/create_discover_legacy_directive.ts @@ -28,7 +28,7 @@ * under the License. */ -import { DiscoverLegacy } from './discover_legacy'; +import { DiscoverLegacy } from './discover'; export function createDiscoverLegacyDirective(reactDirective: any) { return reactDirective(DiscoverLegacy, [ diff --git a/src/plugins/discover/public/application/components/data_grid/constants.ts b/src/plugins/discover/public/application/components/data_grid/constants.ts new file mode 100644 index 000000000000..c11f205aff1f --- /dev/null +++ b/src/plugins/discover/public/application/components/data_grid/constants.ts @@ -0,0 +1,12 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export const toolbarVisibility = { + showColumnSelector: { + allowHide: false, + allowReorder: true, + }, + showStyleSelector: false, +}; diff --git a/src/plugins/discover/public/application/components/data_grid/data_grid_context.tsx b/src/plugins/discover/public/application/components/data_grid/data_grid_context.tsx new file mode 100644 index 000000000000..55561119b225 --- /dev/null +++ b/src/plugins/discover/public/application/components/data_grid/data_grid_context.tsx @@ -0,0 +1,20 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { IndexPattern } from '../../../opensearch_dashboards_services'; +import { DocViewFilterFn } from '../../doc_views/doc_views_types'; + +export interface DataGridContextProps { + docViewExpand: any; + onFilter: DocViewFilterFn; + setDocViewExpand: (hit: any) => void; + rows: any[]; + indexPattern: IndexPattern; +} + +export const DataGridContext = React.createContext( + ({} as unknown) as DataGridContextProps +); diff --git a/src/plugins/discover/public/application/components/data_grid/data_grid_flyout.tsx b/src/plugins/discover/public/application/components/data_grid/data_grid_flyout.tsx new file mode 100644 index 000000000000..58a4886bae4b --- /dev/null +++ b/src/plugins/discover/public/application/components/data_grid/data_grid_flyout.tsx @@ -0,0 +1,161 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { i18n } from '@osd/i18n'; +import { stringify } from 'query-string'; +import rison from 'rison-node'; + +import { + EuiFlyout, + EuiFlyoutHeader, + EuiFlyoutBody, + EuiFlexGroup, + EuiFlexItem, + EuiTitle, + EuiLink, + EuiSpacer, +} from '@elastic/eui'; +import { DocViewer } from '../doc_viewer/doc_viewer'; +import { IndexPattern } from '../../../opensearch_dashboards_services'; +import { DocViewFilterFn } from '../../doc_views/doc_views_types'; +import { DiscoverServices } from '../../../build_services'; +import { url } from '../../../../../opensearch_dashboards_utils/common'; +import { opensearchFilters } from '../../../../../data/public'; + +interface Props { + columns: string[]; + hit: any; + indexPattern: IndexPattern; + onAddColumn: (column: string) => void; + onClose: () => void; + onFilter: DocViewFilterFn; + onRemoveColumn: (column: string) => void; + services: DiscoverServices; +} + +export function DataGridFlyout({ + hit, + columns, + indexPattern, + onAddColumn, + onClose, + onFilter, + onRemoveColumn, + services, +}: Props) { + const generateSurroundingDocumentsUrl = (hitId: string, indexPatternId: string) => { + const globalFilters = services.filterManager.getGlobalFilters(); + const appFilters = services.filterManager.getAppFilters(); + + const hash = stringify( + url.encodeQuery({ + _g: rison.encode({ + filters: globalFilters || [], + }), + _a: rison.encode({ + columns, + filters: (appFilters || []).map(opensearchFilters.disableFilter), + }), + }), + { encode: false, sort: false } + ); + + return `#/context/${encodeURIComponent(indexPatternId)}/${encodeURIComponent(hitId)}?${hash}`; + }; + + const generateSingleDocumentUrl = (hitObj: any, indexPatternId: string) => { + return `#/doc/${indexPatternId}/${hitObj._index}?id=${encodeURIComponent(hit._id)}`; + }; + + return ( + + + +

Document Details

+
+ + + + + {i18n.translate('discover.docTable.tableRow.viewSingleDocumentLinkText', { + defaultMessage: 'View single document', + })} + + + + + {i18n.translate('discover.docTable.tableRow.viewSurroundingDocumentsLinkText', { + defaultMessage: 'View surrounding documents', + })} + + + +
+ + + { + onRemoveColumn(columnName); + onClose(); + }} + onAddColumn={(columnName: string) => { + onAddColumn(columnName); + onClose(); + }} + filter={(mapping, value, mode) => { + onFilter(mapping, value, mode); + onClose(); + }} + /> + + +
+ ); +} + +// export function DataGridFlyout({ +// hit, +// columns, +// indexPattern, +// onAddColumn, +// onClose, +// onFilter, +// onRemoveColumn, +// }: Props) { +// return ( +// +// +// +//

Document Details

+//
+//
+// +// +// { +// onRemoveColumn(columnName); +// onClose(); +// }} +// onAddColumn={(columnName: string) => { +// onAddColumn(columnName); +// onClose(); +// }} +// filter={(mapping, value, mode) => { +// onFilter(mapping, value, mode); +// onClose(); +// }} +// /> +// +// +//
+// ); +// } diff --git a/src/plugins/discover/public/application/components/data_grid/data_grid_table.scss b/src/plugins/discover/public/application/components/data_grid/data_grid_table.scss new file mode 100644 index 000000000000..23c329dbcdec --- /dev/null +++ b/src/plugins/discover/public/application/components/data_grid/data_grid_table.scss @@ -0,0 +1,9 @@ +.dscDiscoverGrid { + height: 100%; + width: 100%; + overflow: hidden; + + .euiDataGrid__controls { + border: $euiBorderThin; + } +} diff --git a/src/plugins/discover/public/application/components/data_grid/data_grid_table.tsx b/src/plugins/discover/public/application/components/data_grid/data_grid_table.tsx new file mode 100644 index 000000000000..017a61d94dd6 --- /dev/null +++ b/src/plugins/discover/public/application/components/data_grid/data_grid_table.tsx @@ -0,0 +1,135 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import './data_grid_table.scss'; +import React, { useState, useMemo, useCallback } from 'react'; +import { EuiDataGrid } from '@elastic/eui'; +import { IndexPattern } from '../../../opensearch_dashboards_services'; +import { fetchTableDataCell } from './data_grid_table_cell_value'; +import { buildDataGridColumns, computeVisibleColumns } from './data_grid_table_columns'; +import { DocViewExpandButton } from './data_grid_table_docview_expand_button'; +import { DataGridFlyout } from './data_grid_flyout'; +import { DataGridContext } from './data_grid_context'; +import { toolbarVisibility } from './constants'; +import { DocViewFilterFn } from '../../doc_views/doc_views_types'; +import { DiscoverServices } from '../../../build_services'; +import { OpenSearchSearchHit } from '../../doc_views/doc_views_types'; +import { usePagination } from '../utils/use_pagination'; + +export interface DataGridTableProps { + columns: string[]; + indexPattern: IndexPattern; + onAddColumn: (column: string) => void; + onFilter: DocViewFilterFn; + onRemoveColumn: (column: string) => void; + onSort: (sort: string[][]) => void; + rows: OpenSearchSearchHit[]; + onSetColumns: (columns: string[]) => void; + sort: Array<[string, string]>; + displayTimeColumn: boolean; + services: DiscoverServices; +} + +export const DataGridTable = ({ + columns, + indexPattern, + onAddColumn, + onFilter, + onRemoveColumn, + onSetColumns, + onSort, + sort, + rows, + displayTimeColumn, + services, +}: DataGridTableProps) => { + const [docViewExpand, setDocViewExpand] = useState(undefined); + const rowCount = useMemo(() => (rows ? rows.length : 0), [rows]); + const pagination = usePagination(rowCount); + + const sortingColumns = useMemo(() => sort.map(([id, direction]) => ({ id, direction })), [sort]); + + const onColumnSort = useCallback( + (cols) => { + onSort(cols.map(({ id, direction }: any) => [id, direction])); + }, + [onSort] + ); + + const renderCellValue = useMemo(() => fetchTableDataCell(indexPattern, rows), [ + indexPattern, + rows, + ]); + + const dataGridTableColumns = useMemo( + () => buildDataGridColumns(columns, indexPattern, displayTimeColumn), + [columns, indexPattern, displayTimeColumn] + ); + + const dataGridTableColumnsVisibility = useMemo( + () => ({ + visibleColumns: computeVisibleColumns(columns, indexPattern, displayTimeColumn) as string[], + setVisibleColumns: (newColumns: string[]) => { + onSetColumns(newColumns); + }, + }), + [columns, indexPattern, displayTimeColumn, onSetColumns] + ); + + const sorting = useMemo(() => ({ columns: sortingColumns, onSort: onColumnSort }), [ + sortingColumns, + onColumnSort, + ]); + + const leadingControlColumns = useMemo(() => { + return [ + { + id: 'expandCollapseColumn', + headerCellRender: () => null, + rowCellRender: DocViewExpandButton, + width: 40, + }, + ]; + }, []); + + return ( + + <> + + {docViewExpand && ( + setDocViewExpand(undefined)} + services={services} + /> + )} + + + ); +}; diff --git a/src/plugins/discover/public/application/components/data_grid/data_grid_table_cell_value.tsx b/src/plugins/discover/public/application/components/data_grid/data_grid_table_cell_value.tsx new file mode 100644 index 000000000000..2e39dde3ba06 --- /dev/null +++ b/src/plugins/discover/public/application/components/data_grid/data_grid_table_cell_value.tsx @@ -0,0 +1,80 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { Fragment } from 'react'; +import dompurify from 'dompurify'; + +import { + EuiDataGridCellValueElementProps, + EuiDescriptionList, + EuiDescriptionListTitle, + EuiDescriptionListDescription, +} from '@elastic/eui'; +import { IndexPattern } from '../../../opensearch_dashboards_services'; +import { OpenSearchSearchHit } from '../../doc_views/doc_views_types'; + +function fetchSourceTypeDataCell( + idxPattern: IndexPattern, + row: Record, + columnId: string, + isDetails: boolean +) { + if (isDetails) { + return {JSON.stringify(row[columnId], null, 2)}; + } + const formattedRow = idxPattern.formatHit(row); + + return ( + + {Object.keys(formattedRow).map((key) => ( + + {key} + + + ))} + + ); +} + +export const fetchTableDataCell = ( + idxPattern: IndexPattern, + dataRows: OpenSearchSearchHit[] | undefined +) => ({ rowIndex, columnId, isDetails }: EuiDataGridCellValueElementProps) => { + const singleRow = dataRows ? (dataRows[rowIndex] as Record) : undefined; + const flattenedRows = dataRows ? dataRows.map((hit) => idxPattern.flattenHit(hit)) : []; + const flattenedRow = flattenedRows + ? (flattenedRows[rowIndex] as Record) + : undefined; + const fieldInfo = idxPattern.fields.getByName(columnId); + + if (typeof singleRow === 'undefined' || typeof flattenedRow === 'undefined') { + return -; + } + + if (!fieldInfo?.type && flattenedRow && typeof flattenedRow[columnId] === 'object') { + if (isDetails) { + return {JSON.stringify(flattenedRow[columnId], null, 2)}; + } + + return {JSON.stringify(flattenedRow[columnId])}; + } + + if (fieldInfo?.type === '_source') { + return fetchSourceTypeDataCell(idxPattern, singleRow, columnId, isDetails); + } + + const formattedValue = idxPattern.formatField(singleRow, columnId); + if (typeof formattedValue === 'undefined') { + return -; + } else { + const sanitizedCellValue = dompurify.sanitize(idxPattern.formatField(singleRow, columnId)); + return ( + // eslint-disable-next-line react/no-danger + + ); + } +}; diff --git a/src/plugins/discover/public/application/components/data_grid/data_grid_table_columns.tsx b/src/plugins/discover/public/application/components/data_grid/data_grid_table_columns.tsx new file mode 100644 index 000000000000..1561a7e838da --- /dev/null +++ b/src/plugins/discover/public/application/components/data_grid/data_grid_table_columns.tsx @@ -0,0 +1,68 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { EuiDataGridColumn } from '@elastic/eui'; +import { i18n } from '@osd/i18n'; +import { IndexPattern } from '../../../opensearch_dashboards_services'; + +export function buildDataGridColumns( + columnNames: string[], + idxPattern: IndexPattern, + displayTimeColumn: boolean +) { + const timeFieldName = idxPattern.timeFieldName; + let columnsToUse = columnNames; + + if (displayTimeColumn && idxPattern.timeFieldName && !columnNames.includes(timeFieldName)) { + columnsToUse = [idxPattern.timeFieldName, ...columnNames]; + } + + return columnsToUse.map((colName) => generateDataGridTableColumn(colName, idxPattern)); +} + +export function generateDataGridTableColumn(colName: string, idxPattern: IndexPattern) { + const timeLabel = i18n.translate('discover.timeLabel', { + defaultMessage: 'Time', + }); + const idxPatternField = idxPattern.getFieldByName(colName); + const dataGridCol: EuiDataGridColumn = { + id: colName, + schema: idxPatternField?.type, + isSortable: idxPatternField?.sortable, + display: idxPatternField?.displayName, + actions: { + showHide: true, + showMoveLeft: false, + showMoveRight: false, + }, + cellActions: [], + }; + + if (dataGridCol.id === idxPattern.timeFieldName) { + dataGridCol.display = `${timeLabel} (${idxPattern.timeFieldName})`; + dataGridCol.initialWidth = 200; + } + if (dataGridCol.id === '_source') { + dataGridCol.display = i18n.translate('discover.sourceLabel', { + defaultMessage: 'Source', + }); + } + return dataGridCol; +} + +export function computeVisibleColumns( + columnNames: string[], + idxPattern: IndexPattern, + displayTimeColumn: boolean +) { + const timeFieldName = idxPattern.timeFieldName; + let visibleColumnNames = columnNames; + + if (displayTimeColumn && !columnNames.includes(timeFieldName)) { + visibleColumnNames = [timeFieldName, ...columnNames]; + } + + return visibleColumnNames; +} diff --git a/src/plugins/discover/public/application/components/data_grid/data_grid_table_docview_expand_button.tsx b/src/plugins/discover/public/application/components/data_grid/data_grid_table_docview_expand_button.tsx new file mode 100644 index 000000000000..d5c87f3f9188 --- /dev/null +++ b/src/plugins/discover/public/application/components/data_grid/data_grid_table_docview_expand_button.tsx @@ -0,0 +1,27 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useContext } from 'react'; +import { EuiToolTip, EuiButtonIcon, EuiDataGridCellValueElementProps } from '@elastic/eui'; +import { DataGridContext } from './data_grid_context'; + +export const DocViewExpandButton = ({ + rowIndex, + setCellProps, +}: EuiDataGridCellValueElementProps) => { + const { docViewExpand, setDocViewExpand, rows } = useContext(DataGridContext); + const currentExpanded = rows[rowIndex]; + const isCurrentExpanded = currentExpanded === docViewExpand; + + return ( + + setDocViewExpand(isCurrentExpanded ? undefined : currentExpanded)} + iconType={isCurrentExpanded ? 'minimize' : 'expand'} + aria-label={`Expand row ${rowIndex}`} + /> + + ); +}; diff --git a/src/plugins/discover/public/application/components/discover.scss b/src/plugins/discover/public/application/components/discover.scss new file mode 100644 index 000000000000..aaa5a4c5c90d --- /dev/null +++ b/src/plugins/discover/public/application/components/discover.scss @@ -0,0 +1,33 @@ +.dscAppContainer { + flex-direction: column; + flex-grow: 1; + overflow: hidden; + + > * { + position: relative; + } +} + +.dscTimechart { + display: block; + position: relative; + + // SASSTODO: the visualizing component should have an option or a modifier + .series > rect { + fill-opacity: 0.5; + stroke-width: 1; + } +} + +.dscTimechart__header { + display: flex; + justify-content: center; + min-height: $euiSizeXXL; + padding: $euiSizeXS 0; +} + +.dscHistogram { + display: flex; + height: 200px; + padding: $euiSizeS; +} diff --git a/src/plugins/discover/public/application/components/discover.tsx b/src/plugins/discover/public/application/components/discover.tsx new file mode 100644 index 000000000000..b9cd40f5fc2b --- /dev/null +++ b/src/plugins/discover/public/application/components/discover.tsx @@ -0,0 +1,317 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import './discover.scss'; +import React, { useState, useCallback, useEffect } from 'react'; +import { + EuiPage, + EuiPageBody, + EuiButtonEmpty, + EuiButtonIcon, + EuiPageSideBar, + EuiPageContent, + EuiFlexGroup, + EuiFlexItem, +} from '@elastic/eui'; +import { i18n } from '@osd/i18n'; +import { FormattedMessage, I18nProvider } from '@osd/i18n/react'; +import { IUiSettingsClient, MountPoint } from 'opensearch-dashboards/public'; +import { HitsCounter } from './hits_counter'; +import { TimechartHeader } from './timechart_header'; +import { DiscoverSidebar } from './sidebar'; +import { DataGridTable } from './data_grid/data_grid_table'; +import { getServices, IndexPattern } from '../../opensearch_dashboards_services'; +// @ts-ignore +import { DiscoverNoResults } from '../angular/directives/no_results'; +import { DiscoverUninitialized } from '../angular/directives/uninitialized'; +import { DiscoverHistogram } from '../angular/directives/histogram'; +import { LoadingSpinner } from './loading_spinner/loading_spinner'; +import { SkipBottomButton } from './skip_bottom_button'; +import { + IndexPatternField, + search, + ISearchSource, + TimeRange, + Query, + IndexPatternAttributes, +} from '../../../../data/public'; +import { Chart } from '../angular/helpers/point_series'; +import { AppState } from '../angular/discover_state'; +import { SavedSearch } from '../../saved_searches'; + +import { SavedObject } from '../../../../../core/types'; +import { Vis } from '../../../../visualizations/public'; +import { TopNavMenuData } from '../../../../navigation/public'; +import { DocViewFilterFn } from '../doc_views/doc_views_types'; + +export interface DiscoverProps { + addColumn: (column: string) => void; + fetch: () => void; + fetchCounter: number; + fieldCounts: Record; + histogramData: Chart; + hits: number; + indexPattern: IndexPattern; + onAddFilter: DocViewFilterFn; + onChangeInterval: (interval: string) => void; + onMoveColumn: (columns: string, newIdx: number) => void; + onRemoveColumn: (column: string) => void; + onSetColumns: (columns: string[]) => void; + onSkipBottomButtonClick: () => void; + onSort: (sort: string[][]) => void; + opts: { + savedSearch: SavedSearch; + config: IUiSettingsClient; + indexPatternList: Array>; + timefield: string; + sampleSize: number; + fixedScroll: (el: HTMLElement) => void; + setHeaderActionMenu: (menuMount: MountPoint | undefined) => void; + }; + resetQuery: () => void; + resultState: string; + rows: Array>; + searchSource: ISearchSource; + setIndexPattern: (id: string) => void; + showSaveQuery: boolean; + state: AppState; + timefilterUpdateHandler: (ranges: { from: number; to: number }) => void; + timeRange?: { from: string; to: string }; + topNavMenu: TopNavMenuData[]; + updateQuery: (payload: { dateRange: TimeRange; query?: Query }, isUpdate?: boolean) => void; + updateSavedQueryId: (savedQueryId?: string) => void; + vis?: Vis; +} + +export function Discover({ + addColumn, + fetch, + fetchCounter, + fieldCounts, + histogramData, + hits, + indexPattern, + onAddFilter, + onChangeInterval, + onMoveColumn, + onRemoveColumn, + onSkipBottomButtonClick, + onSetColumns, + onSort, + opts, + resetQuery, + resultState, + rows, + searchSource, + setIndexPattern, + showSaveQuery, + state, + timefilterUpdateHandler, + timeRange, + topNavMenu, + updateQuery, + updateSavedQueryId, + vis, +}: DiscoverProps) { + const [isSidebarClosed, setIsSidebarClosed] = useState(false); + const services = getServices(); + const { TopNavMenu } = services.navigation.ui; + const { savedSearch, indexPatternList, config } = opts; + const bucketAggConfig = vis?.data?.aggs?.aggs[1]; + const bucketInterval = + bucketAggConfig && search.aggs.isDateHistogramBucketAggConfig(bucketAggConfig) + ? bucketAggConfig.buckets?.getInterval() + : undefined; + const [fixedScrollEl, setFixedScrollEl] = useState(); + + useEffect(() => (fixedScrollEl ? opts.fixedScroll(fixedScrollEl) : undefined), [ + fixedScrollEl, + opts, + ]); + const fixedScrollRef = useCallback( + (node: HTMLElement) => { + if (node !== null) { + setFixedScrollEl(node); + } + }, + [setFixedScrollEl] + ); + const displayTimeColumn = Boolean( + !config.get('doc_table:hideTimeColumn', false) && indexPattern.timeFieldName + ); + + return ( + + +

{savedSearch.title}

+ + + + + + + + {!isSidebarClosed && ( +
+ +
+ )} + setIsSidebarClosed(!isSidebarClosed)} + data-test-subj="collapseSideBarButton" + aria-controls="discover-sidebar" + aria-expanded={isSidebarClosed ? 'false' : 'true'} + aria-label="Toggle sidebar" + className="dscCollapsibleSidebar__collapseButton euiButtonIcon--auto" + /> +
+
+ + + + {resultState === 'none' && ( + + )} + {resultState === 'uninitialized' && } + + {/* Loading State */} + {resultState === 'loading' && ( +
+ +
+ )} + + {/* Ready State */} + {resultState === 'ready' && ( +
+ + 0 ? hits : 0} + showResetButton={!!(savedSearch && savedSearch.id)} + onResetQuery={resetQuery} + /> + {opts.timefield && ( + + )} + + {opts.timefield && ( +
+ {vis && rows.length !== 0 && ( +
+ +
+ )} +
+ )} + +
+
+

+ +

+ {rows && rows.length && ( +
+ ) || []} + onAddColumn={addColumn} + onFilter={onAddFilter} + onRemoveColumn={onRemoveColumn} + onSetColumns={onSetColumns} + onSort={onSort} + displayTimeColumn={displayTimeColumn} + services={services} + /> + + ​ + + {rows.length === opts.sampleSize && ( +
+ + + window.scrollTo(0, 0)}> + + +
+ )} +
+ )} +
+
+
+ )} +
+
+
+
+
+
+ ); +} diff --git a/src/plugins/discover/public/application/components/discover_legacy.tsx b/src/plugins/discover/public/application/components/discover_legacy.tsx deleted file mode 100644 index 3e6e96fc6124..000000000000 --- a/src/plugins/discover/public/application/components/discover_legacy.tsx +++ /dev/null @@ -1,332 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Any modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React, { useState, useCallback, useEffect } from 'react'; -import classNames from 'classnames'; -import { EuiButtonEmpty, EuiButtonIcon } from '@elastic/eui'; -import { i18n } from '@osd/i18n'; -import { FormattedMessage, I18nProvider } from '@osd/i18n/react'; -import { IUiSettingsClient, MountPoint } from 'opensearch-dashboards/public'; -import { HitsCounter } from './hits_counter'; -import { TimechartHeader } from './timechart_header'; -import { DiscoverSidebar } from './sidebar'; -import { getServices, IndexPattern } from '../../opensearch_dashboards_services'; -// @ts-ignore -import { DiscoverNoResults } from '../angular/directives/no_results'; -import { DiscoverUninitialized } from '../angular/directives/uninitialized'; -import { DiscoverHistogram } from '../angular/directives/histogram'; -import { LoadingSpinner } from './loading_spinner/loading_spinner'; -import { DocTableLegacy } from '../angular/doc_table/create_doc_table_react'; -import { SkipBottomButton } from './skip_bottom_button'; -import { - IndexPatternField, - search, - ISearchSource, - TimeRange, - Query, - IndexPatternAttributes, -} from '../../../../data/public'; -import { Chart } from '../angular/helpers/point_series'; -import { AppState } from '../angular/discover_state'; -import { SavedSearch } from '../../saved_searches'; - -import { SavedObject } from '../../../../../core/types'; -import { Vis } from '../../../../visualizations/public'; -import { TopNavMenuData } from '../../../../navigation/public'; - -export interface DiscoverLegacyProps { - addColumn: (column: string) => void; - fetch: () => void; - fetchCounter: number; - fieldCounts: Record; - histogramData: Chart; - hits: number; - indexPattern: IndexPattern; - minimumVisibleRows: number; - onAddFilter: (field: IndexPatternField | string, value: string, type: '+' | '-') => void; - onChangeInterval: (interval: string) => void; - onMoveColumn: (columns: string, newIdx: number) => void; - onRemoveColumn: (column: string) => void; - onSetColumns: (columns: string[]) => void; - onSkipBottomButtonClick: () => void; - onSort: (sort: string[][]) => void; - opts: { - savedSearch: SavedSearch; - config: IUiSettingsClient; - indexPatternList: Array>; - timefield: string; - sampleSize: number; - fixedScroll: (el: HTMLElement) => void; - setHeaderActionMenu: (menuMount: MountPoint | undefined) => void; - }; - resetQuery: () => void; - resultState: string; - rows: Array>; - searchSource: ISearchSource; - setIndexPattern: (id: string) => void; - showSaveQuery: boolean; - state: AppState; - timefilterUpdateHandler: (ranges: { from: number; to: number }) => void; - timeRange?: { from: string; to: string }; - topNavMenu: TopNavMenuData[]; - updateQuery: (payload: { dateRange: TimeRange; query?: Query }, isUpdate?: boolean) => void; - updateSavedQueryId: (savedQueryId?: string) => void; - vis?: Vis; -} - -export function DiscoverLegacy({ - addColumn, - fetch, - fetchCounter, - fieldCounts, - histogramData, - hits, - indexPattern, - minimumVisibleRows, - onAddFilter, - onChangeInterval, - onMoveColumn, - onRemoveColumn, - onSkipBottomButtonClick, - onSort, - opts, - resetQuery, - resultState, - rows, - searchSource, - setIndexPattern, - showSaveQuery, - state, - timefilterUpdateHandler, - timeRange, - topNavMenu, - updateQuery, - updateSavedQueryId, - vis, -}: DiscoverLegacyProps) { - const [isSidebarClosed, setIsSidebarClosed] = useState(false); - const { TopNavMenu } = getServices().navigation.ui; - const { savedSearch, indexPatternList } = opts; - const bucketAggConfig = vis?.data?.aggs?.aggs[1]; - const bucketInterval = - bucketAggConfig && search.aggs.isDateHistogramBucketAggConfig(bucketAggConfig) - ? bucketAggConfig.buckets?.getInterval() - : undefined; - const [fixedScrollEl, setFixedScrollEl] = useState(); - - useEffect(() => (fixedScrollEl ? opts.fixedScroll(fixedScrollEl) : undefined), [ - fixedScrollEl, - opts, - ]); - const fixedScrollRef = useCallback( - (node: HTMLElement) => { - if (node !== null) { - setFixedScrollEl(node); - } - }, - [setFixedScrollEl] - ); - const sidebarClassName = classNames({ - closed: isSidebarClosed, - }); - - const mainSectionClassName = classNames({ - 'col-md-10': !isSidebarClosed, - 'col-md-12': isSidebarClosed, - }); - - return ( - -
-

{savedSearch.title}

- -
-
-
- {!isSidebarClosed && ( -
- -
- )} - setIsSidebarClosed(!isSidebarClosed)} - data-test-subj="collapseSideBarButton" - aria-controls="discover-sidebar" - aria-expanded={isSidebarClosed ? 'false' : 'true'} - aria-label="Toggle sidebar" - className="dscCollapsibleSidebar__collapseButton euiButtonIcon--auto" - /> -
-
- {resultState === 'none' && ( - - )} - {resultState === 'uninitialized' && } - {/* @TODO: Solved in the Angular way to satisfy functional test - should be improved*/} - -
- -
-
- {resultState === 'ready' && ( -
- - 0 ? hits : 0} - showResetButton={!!(savedSearch && savedSearch.id)} - onResetQuery={resetQuery} - /> - {opts.timefield && ( - - )} - - {opts.timefield && ( -
- {vis && rows.length !== 0 && ( -
- -
- )} -
- )} - -
-
-

- -

- {rows && rows.length && ( -
- - - ​ - - {rows.length === opts.sampleSize && ( -
- - - window.scrollTo(0, 0)}> - - -
- )} -
- )} -
-
-
- )} -
-
-
-
-
- ); -} diff --git a/src/plugins/discover/public/application/components/doc_viewer_links/__snapshots__/doc_viewer_links.test.tsx.snap b/src/plugins/discover/public/application/components/doc_viewer_links/__snapshots__/doc_viewer_links.test.tsx.snap deleted file mode 100644 index 95fb0c377180..000000000000 --- a/src/plugins/discover/public/application/components/doc_viewer_links/__snapshots__/doc_viewer_links.test.tsx.snap +++ /dev/null @@ -1,34 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Dont Render if generateCb.hide 1`] = ` - -`; - -exports[`Render with 2 different links 1`] = ` - - - - - - - - -`; diff --git a/src/plugins/discover/public/application/components/doc_viewer_links/doc_viewer_links.test.tsx b/src/plugins/discover/public/application/components/doc_viewer_links/doc_viewer_links.test.tsx deleted file mode 100644 index 8aba555b3a37..000000000000 --- a/src/plugins/discover/public/application/components/doc_viewer_links/doc_viewer_links.test.tsx +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import React from 'react'; -import { shallow } from 'enzyme'; -import { DocViewerLinks } from './doc_viewer_links'; -import { getDocViewsLinksRegistry } from '../../../opensearch_dashboards_services'; -import { DocViewLinkRenderProps } from '../../doc_views_links/doc_views_links_types'; - -jest.mock('../../../opensearch_dashboards_services', () => { - let registry: any[] = []; - return { - getDocViewsLinksRegistry: () => ({ - addDocViewLink(view: any) { - registry.push(view); - }, - getDocViewsLinksSorted() { - return registry; - }, - resetRegistry: () => { - registry = []; - }, - }), - }; -}); - -beforeEach(() => { - (getDocViewsLinksRegistry() as any).resetRegistry(); - jest.clearAllMocks(); -}); - -test('Render with 2 different links', () => { - const registry = getDocViewsLinksRegistry(); - registry.addDocViewLink({ - order: 10, - label: 'generateCb link', - generateCb: () => ({ - url: 'aaa', - }), - }); - registry.addDocViewLink({ order: 20, label: 'href link', href: 'bbb' }); - - const renderProps = { hit: {} } as DocViewLinkRenderProps; - - const wrapper = shallow(); - - expect(wrapper).toMatchSnapshot(); -}); - -test('Dont Render if generateCb.hide', () => { - const registry = getDocViewsLinksRegistry(); - registry.addDocViewLink({ - order: 10, - label: 'generateCb link', - generateCb: () => ({ - url: 'aaa', - hide: true, - }), - }); - - const renderProps = { hit: {} } as DocViewLinkRenderProps; - - const wrapper = shallow(); - - expect(wrapper).toMatchSnapshot(); -}); diff --git a/src/plugins/discover/public/application/components/doc_viewer_links/doc_viewer_links.tsx b/src/plugins/discover/public/application/components/doc_viewer_links/doc_viewer_links.tsx deleted file mode 100644 index 9efb0693fde6..000000000000 --- a/src/plugins/discover/public/application/components/doc_viewer_links/doc_viewer_links.tsx +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import React from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiListGroupItem, EuiListGroupItemProps } from '@elastic/eui'; -import { getDocViewsLinksRegistry } from '../../../opensearch_dashboards_services'; -import { DocViewLinkRenderProps } from '../../doc_views_links/doc_views_links_types'; - -export function DocViewerLinks(renderProps: DocViewLinkRenderProps) { - const listItems = getDocViewsLinksRegistry() - .getDocViewsLinksSorted() - .filter((item) => !(item.generateCb && item.generateCb(renderProps)?.hide)) - .map((item) => { - const { generateCb, href, ...props } = item; - const listItem: EuiListGroupItemProps = { - 'data-test-subj': 'docTableRowAction', - ...props, - href: generateCb ? generateCb(renderProps).url : href, - }; - - return listItem; - }); - - return ( - - {listItems.map((item, index) => ( - - - - ))} - - ); -} diff --git a/src/plugins/discover/public/application/components/utils/use_pagination.ts b/src/plugins/discover/public/application/components/utils/use_pagination.ts new file mode 100644 index 000000000000..98363e57ed95 --- /dev/null +++ b/src/plugins/discover/public/application/components/utils/use_pagination.ts @@ -0,0 +1,39 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { useState, useMemo, useCallback } from 'react'; + +export const usePagination = (rowCount: number) => { + const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 100 }); + const pageCount = useMemo(() => Math.ceil(rowCount / pagination.pageSize), [ + rowCount, + pagination, + ]); + + const onChangeItemsPerPage = useCallback( + (pageSize: number) => setPagination((p) => ({ ...p, pageSize })), + [] + ); + + const onChangePage = useCallback( + (pageIndex: number) => setPagination((p) => ({ ...p, pageIndex })), + [] + ); + + return useMemo( + () => + pagination.pageSize + ? { + ...pagination, + onChangeItemsPerPage, + onChangePage, + pageIndex: pagination.pageIndex > pageCount - 1 ? 0 : pagination.pageIndex, + pageSize: pagination.pageSize, + pageSizeOptions: [25, 50, 100], // TODO: make this configurable + } + : undefined, + [pagination, onChangeItemsPerPage, onChangePage, pageCount] + ); +}; diff --git a/src/plugins/discover/public/application/doc_views/doc_views_registry.ts b/src/plugins/discover/public/application/doc_views/doc_views_registry.ts index 56f167b5f2cc..904d3813cd69 100644 --- a/src/plugins/discover/public/application/doc_views/doc_views_registry.ts +++ b/src/plugins/discover/public/application/doc_views/doc_views_registry.ts @@ -28,32 +28,16 @@ * under the License. */ -import { auto } from 'angular'; -import { convertDirectiveToRenderFn } from './doc_views_helpers'; import { DocView, DocViewInput, OpenSearchSearchHit, DocViewInputFn } from './doc_views_types'; export class DocViewsRegistry { private docViews: DocView[] = []; - private angularInjectorGetter: (() => Promise) | null = null; - - setAngularInjectorGetter = (injectorGetter: () => Promise) => { - this.angularInjectorGetter = injectorGetter; - }; /** * Extends and adds the given doc view to the registry array */ addDocView(docViewRaw: DocViewInput | DocViewInputFn) { const docView = typeof docViewRaw === 'function' ? docViewRaw() : docViewRaw; - if (docView.directive) { - // convert angular directive to render function for backwards compatibility - docView.render = convertDirectiveToRenderFn(docView.directive, () => { - if (!this.angularInjectorGetter) { - throw new Error('Angular was not initialized'); - } - return this.angularInjectorGetter(); - }); - } if (typeof docView.shouldShow !== 'function') { docView.shouldShow = () => true; } diff --git a/src/plugins/discover/public/application/doc_views/doc_views_types.ts b/src/plugins/discover/public/application/doc_views/doc_views_types.ts index 961fc98516f6..bf027d32848a 100644 --- a/src/plugins/discover/public/application/doc_views/doc_views_types.ts +++ b/src/plugins/discover/public/application/doc_views/doc_views_types.ts @@ -72,7 +72,6 @@ export type DocViewRenderFn = ( export interface DocViewInput { component?: DocViewerComponent; - directive?: AngularDirective; order: number; render?: DocViewRenderFn; shouldShow?: (hit: OpenSearchSearchHit) => boolean; diff --git a/src/plugins/discover_legacy/public/application/angular/doc_viewer_links.tsx b/src/plugins/discover_legacy/public/application/angular/doc_viewer_links.tsx deleted file mode 100644 index 763a75e51300..000000000000 --- a/src/plugins/discover_legacy/public/application/angular/doc_viewer_links.tsx +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import React from 'react'; -import { DocViewerLinks } from '../components/doc_viewer_links/doc_viewer_links'; - -export function createDocViewerLinksDirective(reactDirective: any) { - return reactDirective( - (props: any) => { - return ; - }, - [ - 'hit', - ['indexPattern', { watchDepth: 'reference' }], - ['columns', { watchDepth: 'collection' }], - ], - { - restrict: 'E', - scope: { - hit: '=', - indexPattern: '=', - columns: '=?', - }, - } - ); -}