From 7720ea4541d42046c537a87b0da31d6a8fc2bdeb Mon Sep 17 00:00:00 2001 From: Anan Zhuang Date: Thu, 10 Nov 2022 19:51:01 +0000 Subject: [PATCH] [Table Visualization] Replace table visualization with React and DataGrid In this PR, we add back functions to make new table usage to be consistent with the replaced one. * total function * percentage column * filter in/out Meanwhile, we also add back server. Functional tests are removed. We will add new functional test in opensearch-dashboards-functional-test repo. We also clean out some legacy codes. Issue Resolved: https://github.com/opensearch-project/OpenSearch-Dashboards/issues/2855 Signed-off-by: Anan Zhuang --- CHANGELOG.md | 1 + .../data/common/field_formats/field_format.ts | 6 + .../table/components/table_viz_options.tsx | 4 +- .../visualizations/table/to_expression.ts | 4 +- src/plugins/vis_type_table/README.md | 37 +- .../vis_type_table/opensearch_dashboards.json | 6 +- .../__snapshots__/table_vis_fn.test.ts.snap | 47 - .../vis_type_table/public/_table_vis.scss | 23 - .../public/agg_table/_agg_table.scss | 42 - .../public/agg_table/_index.scss | 1 - .../public/agg_table/agg_table.html | 34 - .../public/agg_table/agg_table.js | 295 ------- .../public/agg_table/agg_table.test.js | 512 ----------- .../public/agg_table/agg_table_group.html | 77 -- .../public/agg_table/agg_table_group.js | 67 -- .../public/agg_table/agg_table_group.test.js | 152 ---- .../public/agg_table/tabified_data.js | 806 ------------------ .../public/components/table_vis_app.scss | 0 .../public/components/table_vis_app.tsx | 0 .../public/components/table_vis_component.tsx | 8 + .../components/table_vis_component_group.tsx | 0 .../public/components/table_vis_control.tsx | 2 +- .../components/table_vis_grid_columns.tsx | 148 ++++ .../public/get_inner_angular.ts | 117 --- src/plugins/vis_type_table/public/index.scss | 10 - src/plugins/vis_type_table/public/index.ts | 28 +- .../public/paginated_table/_index.scss | 1 - .../paginated_table/_table_cell_filter.scss | 30 - .../paginated_table/paginated_table.html | 55 -- .../public/paginated_table/paginated_table.js | 120 --- .../paginated_table/paginated_table.test.ts | 485 ----------- .../public/paginated_table/rows.js | 149 ---- .../paginated_table/table_cell_filter.html | 23 - src/plugins/vis_type_table/public/plugin.ts | 76 +- src/plugins/vis_type_table/public/services.ts | 32 +- .../vis_type_table/public/table_vis.html | 29 - .../public/table_vis_controller.js | 67 -- .../public/table_vis_controller.test.ts | 272 ------ .../public/table_vis_fn.test.ts | 89 -- .../vis_type_table/public/table_vis_fn.ts | 50 +- .../public/table_vis_legacy_module.ts | 52 -- .../public/table_vis_renderer.tsx | 0 .../public/table_vis_response_handler.ts | 69 +- .../vis_type_table/public/table_vis_type.ts | 143 ++-- src/plugins/vis_type_table/public/to_ast.ts | 62 ++ src/plugins/vis_type_table/public/types.ts | 43 +- .../public/utils/convert_to_csv_data.ts | 0 .../public/utils/convert_to_formatted_data.ts | 179 ++++ .../public/utils/index.ts | 0 .../public/utils/use_pagination.ts | 0 .../vis_type_table/public/vis_controller.ts | 135 --- src/plugins/vis_type_table_new/README.md | 1 - .../opensearch_dashboards.json | 16 - .../components/table_vis_grid_columns.tsx | 148 ---- .../vis_type_table_new/public/index.ts | 13 - .../vis_type_table_new/public/plugin.ts | 36 - .../vis_type_table_new/public/services.ts | 11 - .../vis_type_table_new/public/table_vis_fn.ts | 65 -- .../public/table_vis_response_handler.ts | 112 --- .../vis_type_table_new/public/types.ts | 70 -- .../public/utils/convert_to_formatted_data.ts | 68 -- .../__snapshots__/build_pipeline.test.ts.snap | 10 - .../public/legacy/build_pipeline.test.ts | 78 -- .../public/legacy/build_pipeline.ts | 27 - .../apps/dashboard/dashboard_filtering.js | 12 - .../apps/dashboard/dashboard_grid.js | 2 +- .../apps/dashboard/embeddable_rendering.js | 6 +- .../apps/dashboard/url_field_formatter.ts | 8 - test/functional/apps/visualize/_data_table.js | 485 ----------- .../visualize/_data_table_nontimeindex.js | 170 ---- .../_data_table_notimeindex_filters.ts | 101 --- .../apps/visualize/_embedding_chart.js | 187 ---- .../visualize/_histogram_request_start.js | 95 --- .../apps/visualize/_linked_saved_searches.ts | 138 --- test/functional/apps/visualize/index.ts | 6 - .../opensearch_dashboards/data.json.gz | Bin 21378 -> 20707 bytes .../functional/page_objects/dashboard_page.ts | 1 - .../page_objects/visualize_chart_page.ts | 72 -- .../functional/page_objects/visualize_page.ts | 4 - .../services/dashboard/expectations.ts | 11 - test/functional/services/index.ts | 4 +- test/functional/services/table.ts | 73 -- 82 files changed, 624 insertions(+), 5994 deletions(-) delete mode 100644 src/plugins/vis_type_table/public/__snapshots__/table_vis_fn.test.ts.snap delete mode 100644 src/plugins/vis_type_table/public/_table_vis.scss delete mode 100644 src/plugins/vis_type_table/public/agg_table/_agg_table.scss delete mode 100644 src/plugins/vis_type_table/public/agg_table/_index.scss delete mode 100644 src/plugins/vis_type_table/public/agg_table/agg_table.html delete mode 100644 src/plugins/vis_type_table/public/agg_table/agg_table.js delete mode 100644 src/plugins/vis_type_table/public/agg_table/agg_table.test.js delete mode 100644 src/plugins/vis_type_table/public/agg_table/agg_table_group.html delete mode 100644 src/plugins/vis_type_table/public/agg_table/agg_table_group.js delete mode 100644 src/plugins/vis_type_table/public/agg_table/agg_table_group.test.js delete mode 100644 src/plugins/vis_type_table/public/agg_table/tabified_data.js rename src/plugins/{vis_type_table_new => vis_type_table}/public/components/table_vis_app.scss (100%) rename src/plugins/{vis_type_table_new => vis_type_table}/public/components/table_vis_app.tsx (100%) rename src/plugins/{vis_type_table_new => vis_type_table}/public/components/table_vis_component.tsx (94%) rename src/plugins/{vis_type_table_new => vis_type_table}/public/components/table_vis_component_group.tsx (100%) rename src/plugins/{vis_type_table_new => vis_type_table}/public/components/table_vis_control.tsx (93%) create mode 100644 src/plugins/vis_type_table/public/components/table_vis_grid_columns.tsx delete mode 100644 src/plugins/vis_type_table/public/get_inner_angular.ts delete mode 100644 src/plugins/vis_type_table/public/index.scss delete mode 100644 src/plugins/vis_type_table/public/paginated_table/_index.scss delete mode 100644 src/plugins/vis_type_table/public/paginated_table/_table_cell_filter.scss delete mode 100644 src/plugins/vis_type_table/public/paginated_table/paginated_table.html delete mode 100644 src/plugins/vis_type_table/public/paginated_table/paginated_table.js delete mode 100644 src/plugins/vis_type_table/public/paginated_table/paginated_table.test.ts delete mode 100644 src/plugins/vis_type_table/public/paginated_table/rows.js delete mode 100644 src/plugins/vis_type_table/public/paginated_table/table_cell_filter.html delete mode 100644 src/plugins/vis_type_table/public/table_vis.html delete mode 100644 src/plugins/vis_type_table/public/table_vis_controller.js delete mode 100644 src/plugins/vis_type_table/public/table_vis_controller.test.ts delete mode 100644 src/plugins/vis_type_table/public/table_vis_fn.test.ts delete mode 100644 src/plugins/vis_type_table/public/table_vis_legacy_module.ts rename src/plugins/{vis_type_table_new => vis_type_table}/public/table_vis_renderer.tsx (100%) create mode 100644 src/plugins/vis_type_table/public/to_ast.ts rename src/plugins/{vis_type_table_new => vis_type_table}/public/utils/convert_to_csv_data.ts (100%) create mode 100644 src/plugins/vis_type_table/public/utils/convert_to_formatted_data.ts rename src/plugins/{vis_type_table_new => vis_type_table}/public/utils/index.ts (100%) rename src/plugins/{vis_type_table_new => vis_type_table}/public/utils/use_pagination.ts (100%) delete mode 100644 src/plugins/vis_type_table/public/vis_controller.ts delete mode 100644 src/plugins/vis_type_table_new/README.md delete mode 100644 src/plugins/vis_type_table_new/opensearch_dashboards.json delete mode 100644 src/plugins/vis_type_table_new/public/components/table_vis_grid_columns.tsx delete mode 100644 src/plugins/vis_type_table_new/public/index.ts delete mode 100644 src/plugins/vis_type_table_new/public/plugin.ts delete mode 100644 src/plugins/vis_type_table_new/public/services.ts delete mode 100644 src/plugins/vis_type_table_new/public/table_vis_fn.ts delete mode 100644 src/plugins/vis_type_table_new/public/table_vis_response_handler.ts delete mode 100644 src/plugins/vis_type_table_new/public/types.ts delete mode 100644 src/plugins/vis_type_table_new/public/utils/convert_to_formatted_data.ts delete mode 100644 test/functional/apps/visualize/_data_table.js delete mode 100644 test/functional/apps/visualize/_data_table_nontimeindex.js delete mode 100644 test/functional/apps/visualize/_data_table_notimeindex_filters.ts delete mode 100644 test/functional/apps/visualize/_embedding_chart.js delete mode 100644 test/functional/apps/visualize/_histogram_request_start.js delete mode 100644 test/functional/apps/visualize/_linked_saved_searches.ts delete mode 100644 test/functional/services/table.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c656898be70..9820733829de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - [Vis Builder] Add an experimental table visualization in vis builder ([#2705](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2705)) - [Vis Builder] Add field summary popovers ([#2682](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2682)) - Add yarn opensearch arg to setup plugin dependencies ([#2544](https://github.com/opensearch-project/OpenSearch-Dashboards/issues/2544)) +- [Table Visualization] Replace table visualization using React and DataGrid component ([#2863](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2863)) ### 🐛 Bug Fixes diff --git a/src/plugins/data/common/field_formats/field_format.ts b/src/plugins/data/common/field_formats/field_format.ts index 8bff51d1f16c..c5c945f1b899 100644 --- a/src/plugins/data/common/field_formats/field_format.ts +++ b/src/plugins/data/common/field_formats/field_format.ts @@ -95,6 +95,12 @@ export abstract class FieldFormat { */ public type: any = this.constructor; + /** + * @property {boolean} - allow numeric aggregation + * @private + */ + allowsNumericalAggregations?: boolean; + protected readonly _params: any; protected getConfig: FieldFormatsGetConfigFn | undefined; diff --git a/src/plugins/vis_builder/public/visualizations/table/components/table_viz_options.tsx b/src/plugins/vis_builder/public/visualizations/table/components/table_viz_options.tsx index 8c934fff8dac..a77a0811e609 100644 --- a/src/plugins/vis_builder/public/visualizations/table/components/table_viz_options.tsx +++ b/src/plugins/vis_builder/public/visualizations/table/components/table_viz_options.tsx @@ -3,14 +3,12 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { get } from 'lodash'; -import React, { useCallback, useEffect, useMemo } from 'react'; +import React, { useCallback } from 'react'; import { i18n } from '@osd/i18n'; import { FormattedMessage } from '@osd/i18n/react'; import produce from 'immer'; import { Draft } from 'immer'; import { EuiIconTip } from '@elastic/eui'; -import { search } from '../../../../../data/public'; import { NumberInputOption, SwitchOption } from '../../../../../charts/public'; import { useTypedDispatch, diff --git a/src/plugins/vis_builder/public/visualizations/table/to_expression.ts b/src/plugins/vis_builder/public/visualizations/table/to_expression.ts index 212c93248d40..bbec4c1cc7e9 100644 --- a/src/plugins/vis_builder/public/visualizations/table/to_expression.ts +++ b/src/plugins/vis_builder/public/visualizations/table/to_expression.ts @@ -4,7 +4,7 @@ */ import { SchemaConfig } from '../../../../visualizations/public'; -import { TableVisExpressionFunctionDefinition } from '../../../../vis_type_table_new/public'; +import { TableVisExpressionFunctionDefinition } from '../../../../vis_type_table/public'; import { AggConfigs, IAggConfig } from '../../../../data/common'; import { buildExpression, buildExpressionFunction } from '../../../../expressions/public'; import { RenderState } from '../../application/utils/state_management'; @@ -120,7 +120,7 @@ export const toExpression = async ({ style: styleState, visualization }: TableRo }; const tableVis = buildExpressionFunction( - 'opensearch_dashboards_table_new', + 'opensearch_dashboards_table', { visConfig: JSON.stringify(visConfig), } diff --git a/src/plugins/vis_type_table/README.md b/src/plugins/vis_type_table/README.md index cf37e133ed1c..46db1d479c9f 100644 --- a/src/plugins/vis_type_table/README.md +++ b/src/plugins/vis_type_table/README.md @@ -1 +1,36 @@ -Contains the data table visualization, that allows presenting data in a simple table format. \ No newline at end of file +# Data Table + +This is an OpenSearch Dashboards plugin that is used to visualize data and aggregations in tabular format. + +## Create Data Table +To create a data table in OpenSearch Dashboards, first select `Visualize` from the navigation menu. Then click `Create Visualization` and choose `Data Table` as the visualization type. + +## Select Metrics + +### Metrics Aggregation +At the `Metrics`, select the metric aggregation type from the menu and config it accordinly. + +### Buckets Aggregation +At the `Buckets`, config the columns to be displayed in the table visualization. +- `Split Rows` allows you to add another column to the table. +- `Split Table` splits the table into seperate tables for the aggregation you choose. + +## Select Options +In the `Options` tab, you could config more options. +- `Max rows per page` is the maximum number of rows displayed per page. +- `Show metrics for every bucket/level` adds metrics aggregation to every columns. +- `Show partial rows` will include data with missing columns. +- `Show total` calculates the selected metrics per column and displays the result at the bottom. +- `Percentage column` adds one percentage column based on the chosen metrics aggregation. + +## Example + +Below is an example of creating a table visualization using sample ecommerce data. + +- Create a new data table visualization and set a relative time 15 weeks ago. +- Compute the count of ecommerce: Choose `Count` in Metrics Aggregation. +- Split the rows on the top 5 of `manufacturer.keyword` ordered by `Metric:Count` in descending and add a label "manufacturer". +geoip.city_name +- Split the table in rows on the top 5 of `geoip.city_name` ordered by `Metric:Count` in ascending order. +- Click the `Save` button on the top left and save the visualization as "Top manufacturers by count per city". +- Choose a table and click the download icon to download the table. diff --git a/src/plugins/vis_type_table/opensearch_dashboards.json b/src/plugins/vis_type_table/opensearch_dashboards.json index e2f050534c1e..bf5bf6d871b3 100644 --- a/src/plugins/vis_type_table/opensearch_dashboards.json +++ b/src/plugins/vis_type_table/opensearch_dashboards.json @@ -6,13 +6,13 @@ "requiredPlugins": [ "expressions", "visualizations", - "data", - "opensearchDashboardsLegacy" + "data" ], "requiredBundles": [ "opensearchDashboardsUtils", - "share", + "opensearchDashboardsReact", "charts", + "share", "visDefaultEditor" ] } diff --git a/src/plugins/vis_type_table/public/__snapshots__/table_vis_fn.test.ts.snap b/src/plugins/vis_type_table/public/__snapshots__/table_vis_fn.test.ts.snap deleted file mode 100644 index dc6571de969f..000000000000 --- a/src/plugins/vis_type_table/public/__snapshots__/table_vis_fn.test.ts.snap +++ /dev/null @@ -1,47 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`interpreter/functions#table returns an object with the correct structure 1`] = ` -Object { - "as": "visualization", - "type": "render", - "value": Object { - "params": Object { - "listenOnChange": true, - }, - "visConfig": Object { - "dimensions": Object { - "buckets": Array [], - "metrics": Array [ - Object { - "accessor": 0, - "aggType": "count", - "format": Object { - "id": "number", - }, - "params": Object {}, - }, - ], - }, - "perPage": 10, - "showMetricsAtAllLevels": false, - "showPartialRows": false, - "showTotal": false, - "sort": Object { - "columnIndex": null, - "direction": null, - }, - "title": "My Chart title", - "totalFunc": "sum", - }, - "visData": Object { - "tables": Array [ - Object { - "columns": Array [], - "rows": Array [], - }, - ], - }, - "visType": "table", - }, -} -`; diff --git a/src/plugins/vis_type_table/public/_table_vis.scss b/src/plugins/vis_type_table/public/_table_vis.scss deleted file mode 100644 index ea4b4d0d1c98..000000000000 --- a/src/plugins/vis_type_table/public/_table_vis.scss +++ /dev/null @@ -1,23 +0,0 @@ -// SASSTODO: Update naming to BEM -// This chart is actively being re-written to React and EUI -// Putting off renaming to avoid conflicts -.table-vis { - display: flex; - flex-direction: column; - flex: 1 0 100%; - overflow: auto; -} - -.table-vis-container { - osd-agg-table-group > .table > tbody > tr > td { - border-top: 0; - } - - .pagination-other-pages { - justify-content: flex-end; - } - - .pagination-size { - display: none; - } -} diff --git a/src/plugins/vis_type_table/public/agg_table/_agg_table.scss b/src/plugins/vis_type_table/public/agg_table/_agg_table.scss deleted file mode 100644 index 156db063c8db..000000000000 --- a/src/plugins/vis_type_table/public/agg_table/_agg_table.scss +++ /dev/null @@ -1,42 +0,0 @@ -osd-agg-table, -osd-agg-table-group { - display: block; -} - -.osdAggTable { - display: flex; - flex: 1 1 auto; - flex-direction: column; -} - -.osdAggTable__paginated { - flex: 1 1 auto; - overflow: auto; - - th { - text-align: left; - font-weight: $euiFontWeightBold; - } - - tr:hover td, - .osdTableCellFilter { - background-color: $euiColorLightestShade; - } -} - -.osdAggTable__controls { - flex: 0 0 auto; - display: flex; - align-items: center; - margin: $euiSizeS $euiSizeXS; - - > paginate-controls { - flex: 1 0 auto; - margin: 0; - padding: 0; - } -} - -.small { - font-size: 0.9em !important; -} diff --git a/src/plugins/vis_type_table/public/agg_table/_index.scss b/src/plugins/vis_type_table/public/agg_table/_index.scss deleted file mode 100644 index ed94e8449120..000000000000 --- a/src/plugins/vis_type_table/public/agg_table/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import "./agg_table"; diff --git a/src/plugins/vis_type_table/public/agg_table/agg_table.html b/src/plugins/vis_type_table/public/agg_table/agg_table.html deleted file mode 100644 index 8e8aafa83fd8..000000000000 --- a/src/plugins/vis_type_table/public/agg_table/agg_table.html +++ /dev/null @@ -1,34 +0,0 @@ - - -
-    - - - -     - - - - - -
-
diff --git a/src/plugins/vis_type_table/public/agg_table/agg_table.js b/src/plugins/vis_type_table/public/agg_table/agg_table.js deleted file mode 100644 index a00aea27869f..000000000000 --- a/src/plugins/vis_type_table/public/agg_table/agg_table.js +++ /dev/null @@ -1,295 +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 _ from 'lodash'; -import { CSV_SEPARATOR_SETTING, CSV_QUOTE_VALUES_SETTING } from '../../../share/public'; -import aggTableTemplate from './agg_table.html'; -import { getFormatService } from '../services'; -import { i18n } from '@osd/i18n'; - -export function OsdAggTable(config, RecursionHelper) { - return { - restrict: 'E', - template: aggTableTemplate, - scope: { - table: '=', - dimensions: '=', - perPage: '=?', - sort: '=?', - exportTitle: '=?', - showTotal: '=', - totalFunc: '=', - percentageCol: '=', - filter: '=', - }, - controllerAs: 'aggTable', - compile: function ($el) { - // Use the compile function from the RecursionHelper, - // And return the linking function(s) which it returns - return RecursionHelper.compile($el); - }, - controller: function ($scope) { - const self = this; - - self._saveAs = require('@elastic/filesaver').saveAs; - self.csv = { - separator: config.get(CSV_SEPARATOR_SETTING), - quoteValues: config.get(CSV_QUOTE_VALUES_SETTING), - }; - - self.exportAsCsv = function (formatted) { - const csv = new Blob([self.toCsv(formatted)], { type: 'text/csv;charset=utf-8' }); - self._saveAs(csv, self.csv.filename); - }; - - self.toCsv = function (formatted) { - const rows = formatted ? $scope.rows : $scope.table.rows; - const columns = formatted ? [...$scope.formattedColumns] : [...$scope.table.columns]; - - if ($scope.splitRow && formatted) { - columns.unshift($scope.splitRow); - } - - const nonAlphaNumRE = /[^a-zA-Z0-9]/; - const allDoubleQuoteRE = /"/g; - - function escape(val) { - if (!formatted && _.isObject(val)) val = val.valueOf(); - val = String(val); - if (self.csv.quoteValues && nonAlphaNumRE.test(val)) { - val = '"' + val.replace(allDoubleQuoteRE, '""') + '"'; - } - return val; - } - - let csvRows = []; - for (const row of rows) { - const rowArray = []; - for (const col of columns) { - const value = row[col.id]; - const formattedValue = - formatted && col.formatter ? escape(col.formatter.convert(value)) : escape(value); - rowArray.push(formattedValue); - } - csvRows = [...csvRows, rowArray]; - } - - // add the columns to the rows - csvRows.unshift( - columns.map(function (col) { - return escape(formatted ? col.title : col.name); - }) - ); - - return csvRows - .map(function (row) { - return row.join(self.csv.separator) + '\r\n'; - }) - .join(''); - }; - - $scope.$watchMulti( - ['table', 'exportTitle', 'percentageCol', 'totalFunc', '=scope.dimensions'], - function () { - const { table, exportTitle, percentageCol } = $scope; - const showPercentage = percentageCol !== ''; - - if (!table) { - $scope.rows = null; - $scope.formattedColumns = null; - $scope.splitRow = null; - return; - } - - self.csv.filename = (exportTitle || table.title || 'unsaved') + '.csv'; - $scope.rows = table.rows; - $scope.formattedColumns = []; - - if (typeof $scope.dimensions === 'undefined') return; - - const { buckets, metrics, splitColumn, splitRow } = $scope.dimensions; - - $scope.formattedColumns = table.columns - .map(function (col, i) { - const isBucket = buckets.find((bucket) => bucket.accessor === i); - const isSplitColumn = splitColumn - ? splitColumn.find((splitColumn) => splitColumn.accessor === i) - : undefined; - const isSplitRow = splitRow - ? splitRow.find((splitRow) => splitRow.accessor === i) - : undefined; - const dimension = - isBucket || isSplitColumn || metrics.find((metric) => metric.accessor === i); - - const formatter = dimension - ? getFormatService().deserialize(dimension.format) - : undefined; - - const formattedColumn = { - id: col.id, - title: col.name, - formatter: formatter, - filterable: !!isBucket, - }; - - if (isSplitRow) { - $scope.splitRow = formattedColumn; - } - - if (!dimension) return; - - const last = i === table.columns.length - 1; - - if (last || !isBucket) { - formattedColumn.class = 'visualize-table-right'; - } - - const isDate = - dimension.format?.id === 'date' || dimension.format?.params?.id === 'date'; - const allowsNumericalAggregations = formatter?.allowsNumericalAggregations; - - let { totalFunc } = $scope; - if (typeof totalFunc === 'undefined' && showPercentage) { - totalFunc = 'sum'; - } - - if (allowsNumericalAggregations || isDate || totalFunc === 'count') { - const sum = (tableRows) => { - return _.reduce( - tableRows, - function (prev, curr) { - // some metrics return undefined for some of the values - // derivative is an example of this as it returns undefined in the first row - if (curr[col.id] === undefined) return prev; - return prev + curr[col.id]; - }, - 0 - ); - }; - - formattedColumn.sumTotal = sum(table.rows); - switch (totalFunc) { - case 'sum': { - if (!isDate) { - const total = formattedColumn.sumTotal; - formattedColumn.formattedTotal = formatter.convert(total); - formattedColumn.total = formattedColumn.sumTotal; - } - break; - } - case 'avg': { - if (!isDate) { - const total = sum(table.rows) / table.rows.length; - formattedColumn.formattedTotal = formatter.convert(total); - formattedColumn.total = total; - } - break; - } - case 'min': { - const total = _.chain(table.rows).map(col.id).min().value(); - formattedColumn.formattedTotal = formatter.convert(total); - formattedColumn.total = total; - break; - } - case 'max': { - const total = _.chain(table.rows).map(col.id).max().value(); - formattedColumn.formattedTotal = formatter.convert(total); - formattedColumn.total = total; - break; - } - case 'count': { - const total = table.rows.length; - formattedColumn.formattedTotal = total; - formattedColumn.total = total; - break; - } - default: - break; - } - } - - return formattedColumn; - }) - .filter((column) => column); - - if (showPercentage) { - const insertAtIndex = _.findIndex($scope.formattedColumns, { title: percentageCol }); - - // column to show percentage for was removed - if (insertAtIndex < 0) return; - - const { cols, rows } = addPercentageCol( - $scope.formattedColumns, - percentageCol, - table.rows, - insertAtIndex - ); - $scope.rows = rows; - $scope.formattedColumns = cols; - } - } - ); - }, - }; -} - -/** - * @param {Object[]} columns - the formatted columns that will be displayed - * @param {String} title - the title of the column to add to - * @param {Object[]} rows - the row data for the columns - * @param {Number} insertAtIndex - the index to insert the percentage column at - * @returns {Object} - cols and rows for the table to render now included percentage column(s) - */ -function addPercentageCol(columns, title, rows, insertAtIndex) { - const { id, sumTotal } = columns[insertAtIndex]; - const newId = `${id}-percents`; - const formatter = getFormatService().deserialize({ id: 'percent' }); - const i18nTitle = i18n.translate('visTypeTable.params.percentageTableColumnName', { - defaultMessage: '{title} percentages', - values: { title }, - }); - const newCols = insert(columns, insertAtIndex, { - title: i18nTitle, - id: newId, - formatter, - }); - const newRows = rows.map((row) => ({ - [newId]: row[id] / sumTotal, - ...row, - })); - - return { cols: newCols, rows: newRows }; -} - -function insert(arr, index, ...items) { - const newArray = [...arr]; - newArray.splice(index + 1, 0, ...items); - return newArray; -} diff --git a/src/plugins/vis_type_table/public/agg_table/agg_table.test.js b/src/plugins/vis_type_table/public/agg_table/agg_table.test.js deleted file mode 100644 index 14d0c7fe7952..000000000000 --- a/src/plugins/vis_type_table/public/agg_table/agg_table.test.js +++ /dev/null @@ -1,512 +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 $ from 'jquery'; -import moment from 'moment'; -import angular from 'angular'; -import 'angular-mocks'; -import sinon from 'sinon'; -import { round } from 'lodash'; - -import { getFieldFormatsRegistry } from '../../../data/public/test_utils'; -import { coreMock } from '../../../../core/public/mocks'; -import { initAngularBootstrap } from '../../../opensearch_dashboards_legacy/public'; -import { setUiSettings } from '../../../data/public/services'; -import { UI_SETTINGS } from '../../../data/public/'; -import { CSV_SEPARATOR_SETTING, CSV_QUOTE_VALUES_SETTING } from '../../../share/public'; - -import { setFormatService } from '../services'; -import { getInnerAngular } from '../get_inner_angular'; -import { initTableVisLegacyModule } from '../table_vis_legacy_module'; -import { tabifiedData } from './tabified_data'; - -const uiSettings = new Map(); - -describe('Table Vis - AggTable Directive', function () { - const core = coreMock.createStart(); - - core.uiSettings.set = jest.fn((key, value) => { - uiSettings.set(key, value); - }); - - core.uiSettings.get = jest.fn((key) => { - const defaultValues = { - dateFormat: 'MMM D, YYYY @ HH:mm:ss.SSS', - 'dateFormat:tz': 'UTC', - [UI_SETTINGS.SHORT_DOTS_ENABLE]: true, - [UI_SETTINGS.FORMAT_CURRENCY_DEFAULT_PATTERN]: '($0,0.[00])', - [UI_SETTINGS.FORMAT_NUMBER_DEFAULT_PATTERN]: '0,0.[000]', - [UI_SETTINGS.FORMAT_PERCENT_DEFAULT_PATTERN]: '0,0.[000]%', - [UI_SETTINGS.FORMAT_NUMBER_DEFAULT_LOCALE]: 'en', - [UI_SETTINGS.FORMAT_DEFAULT_TYPE_MAP]: {}, - [CSV_SEPARATOR_SETTING]: ',', - [CSV_QUOTE_VALUES_SETTING]: true, - }; - - return defaultValues[key] || uiSettings.get(key); - }); - - let $rootScope; - let $compile; - let settings; - - const initLocalAngular = () => { - const tableVisModule = getInnerAngular('opensearch-dashboards/table_vis', core); - initTableVisLegacyModule(tableVisModule); - }; - - beforeEach(() => { - setUiSettings(core.uiSettings); - setFormatService(getFieldFormatsRegistry(core)); - initAngularBootstrap(); - initLocalAngular(); - angular.mock.module('opensearch-dashboards/table_vis'); - angular.mock.inject(($injector, config) => { - settings = config; - - $rootScope = $injector.get('$rootScope'); - $compile = $injector.get('$compile'); - }); - }); - - let $scope; - beforeEach(function () { - $scope = $rootScope.$new(); - }); - afterEach(function () { - $scope.$destroy(); - }); - - test('renders a simple response properly', function () { - $scope.dimensions = { - metrics: [{ accessor: 0, format: { id: 'number' }, params: {} }], - buckets: [], - }; - $scope.table = tabifiedData.metricOnly.tables[0]; - - const $el = $compile('')( - $scope - ); - $scope.$digest(); - - expect($el.find('tbody').length).toBe(1); - expect($el.find('td').length).toBe(1); - expect($el.find('td').text()).toEqual('1,000'); - }); - - test('renders nothing if the table is empty', function () { - $scope.dimensions = {}; - $scope.table = null; - const $el = $compile('')( - $scope - ); - $scope.$digest(); - - expect($el.find('tbody').length).toBe(0); - }); - - test('renders a complex response properly', async function () { - $scope.dimensions = { - buckets: [ - { accessor: 0, params: {} }, - { accessor: 2, params: {} }, - { accessor: 4, params: {} }, - ], - metrics: [ - { accessor: 1, params: {} }, - { accessor: 3, params: {} }, - { accessor: 5, params: {} }, - ], - }; - $scope.table = tabifiedData.threeTermBuckets.tables[0]; - const $el = $(''); - $compile($el)($scope); - $scope.$digest(); - - expect($el.find('tbody').length).toBe(1); - - const $rows = $el.find('tbody tr'); - expect($rows.length).toBeGreaterThan(0); - - function validBytes(str) { - const num = str.replace(/,/g, ''); - if (num !== '-') { - expect(num).toMatch(/^\d+$/); - } - } - - $rows.each(function () { - // 6 cells in every row - const $cells = $(this).find('td'); - expect($cells.length).toBe(6); - - const txts = $cells.map(function () { - return $(this).text().trim(); - }); - - // two character country code - expect(txts[0]).toMatch(/^(png|jpg|gif|html|css)$/); - validBytes(txts[1]); - - // country - expect(txts[2]).toMatch(/^\w\w$/); - validBytes(txts[3]); - - // os - expect(txts[4]).toMatch(/^(win|mac|linux)$/); - validBytes(txts[5]); - }); - }); - - describe('renders totals row', function () { - async function totalsRowTest(totalFunc, expected) { - function setDefaultTimezone() { - moment.tz.setDefault(settings.get('dateFormat:tz')); - } - - const oldTimezoneSetting = settings.get('dateFormat:tz'); - settings.set('dateFormat:tz', 'UTC'); - setDefaultTimezone(); - - $scope.dimensions = { - buckets: [ - { accessor: 0, params: {} }, - { accessor: 1, format: { id: 'date', params: { pattern: 'YYYY-MM-DD' } } }, - ], - metrics: [ - { accessor: 2, format: { id: 'number' } }, - { accessor: 3, format: { id: 'date' } }, - { accessor: 4, format: { id: 'number' } }, - { accessor: 5, format: { id: 'number' } }, - ], - }; - $scope.table = - tabifiedData.oneTermOneHistogramBucketWithTwoMetricsOneTopHitOneDerivative.tables[0]; - $scope.showTotal = true; - $scope.totalFunc = totalFunc; - const $el = $(``); - $compile($el)($scope); - $scope.$digest(); - - expect($el.find('tfoot').length).toBe(1); - - const $rows = $el.find('tfoot tr'); - expect($rows.length).toBe(1); - - const $cells = $($rows[0]).find('th'); - expect($cells.length).toBe(6); - - for (let i = 0; i < 6; i++) { - expect($($cells[i]).text().trim()).toBe(expected[i]); - } - settings.set('dateFormat:tz', oldTimezoneSetting); - setDefaultTimezone(); - } - test('as count', async function () { - await totalsRowTest('count', ['18', '18', '18', '18', '18', '18']); - }); - test('as min', async function () { - await totalsRowTest('min', [ - '', - '2014-09-28', - '9,283', - 'Sep 28, 2014 @ 00:00:00.000', - '1', - '11', - ]); - }); - test('as max', async function () { - await totalsRowTest('max', [ - '', - '2014-10-03', - '220,943', - 'Oct 3, 2014 @ 00:00:00.000', - '239', - '837', - ]); - }); - test('as avg', async function () { - await totalsRowTest('avg', ['', '', '87,221.5', '', '64.667', '206.833']); - }); - test('as sum', async function () { - await totalsRowTest('sum', ['', '', '1,569,987', '', '1,164', '3,723']); - }); - }); - - describe('aggTable.toCsv()', function () { - test('escapes rows and columns properly', function () { - const $el = $compile('')( - $scope - ); - $scope.$digest(); - - const $tableScope = $el.isolateScope(); - const aggTable = $tableScope.aggTable; - $tableScope.table = { - columns: [ - { id: 'a', name: 'one' }, - { id: 'b', name: 'two' }, - { id: 'c', name: 'with double-quotes(")' }, - ], - rows: [{ a: 1, b: 2, c: '"foobar"' }], - }; - - expect(aggTable.toCsv()).toBe( - 'one,two,"with double-quotes("")"' + '\r\n' + '1,2,"""foobar"""' + '\r\n' - ); - }); - - test('exports rows and columns properly', async function () { - $scope.dimensions = { - buckets: [ - { accessor: 0, params: {} }, - { accessor: 2, params: {} }, - { accessor: 4, params: {} }, - ], - metrics: [ - { accessor: 1, params: {} }, - { accessor: 3, params: {} }, - { accessor: 5, params: {} }, - ], - }; - $scope.table = tabifiedData.threeTermBuckets.tables[0]; - - const $el = $compile('')( - $scope - ); - $scope.$digest(); - - const $tableScope = $el.isolateScope(); - const aggTable = $tableScope.aggTable; - $tableScope.table = $scope.table; - - const raw = aggTable.toCsv(false); - expect(raw).toBe( - '"extension: Descending","Average bytes","geo.src: Descending","Average bytes","machine.os: Descending","Average bytes"' + - '\r\n' + - 'png,412032,IT,9299,win,0' + - '\r\n' + - 'png,412032,IT,9299,mac,9299' + - '\r\n' + - 'png,412032,US,8293,linux,3992' + - '\r\n' + - 'png,412032,US,8293,mac,3029' + - '\r\n' + - 'css,412032,MX,9299,win,4992' + - '\r\n' + - 'css,412032,MX,9299,mac,5892' + - '\r\n' + - 'css,412032,US,8293,linux,3992' + - '\r\n' + - 'css,412032,US,8293,mac,3029' + - '\r\n' + - 'html,412032,CN,9299,win,4992' + - '\r\n' + - 'html,412032,CN,9299,mac,5892' + - '\r\n' + - 'html,412032,FR,8293,win,3992' + - '\r\n' + - 'html,412032,FR,8293,mac,3029' + - '\r\n' - ); - }); - - test('exports formatted rows and columns properly', async function () { - $scope.dimensions = { - buckets: [ - { accessor: 0, params: {} }, - { accessor: 2, params: {} }, - { accessor: 4, params: {} }, - ], - metrics: [ - { accessor: 1, params: {} }, - { accessor: 3, params: {} }, - { accessor: 5, params: {} }, - ], - }; - $scope.table = tabifiedData.threeTermBuckets.tables[0]; - - const $el = $compile('')( - $scope - ); - $scope.$digest(); - - const $tableScope = $el.isolateScope(); - const aggTable = $tableScope.aggTable; - $tableScope.table = $scope.table; - - // Create our own converter since the ones we use for tests don't actually transform the provided value - $tableScope.formattedColumns[0].formatter.convert = (v) => `${v}_formatted`; - - const formatted = aggTable.toCsv(true); - expect(formatted).toBe( - '"extension: Descending","Average bytes","geo.src: Descending","Average bytes","machine.os: Descending","Average bytes"' + - '\r\n' + - '"png_formatted",412032,IT,9299,win,0' + - '\r\n' + - '"png_formatted",412032,IT,9299,mac,9299' + - '\r\n' + - '"png_formatted",412032,US,8293,linux,3992' + - '\r\n' + - '"png_formatted",412032,US,8293,mac,3029' + - '\r\n' + - '"css_formatted",412032,MX,9299,win,4992' + - '\r\n' + - '"css_formatted",412032,MX,9299,mac,5892' + - '\r\n' + - '"css_formatted",412032,US,8293,linux,3992' + - '\r\n' + - '"css_formatted",412032,US,8293,mac,3029' + - '\r\n' + - '"html_formatted",412032,CN,9299,win,4992' + - '\r\n' + - '"html_formatted",412032,CN,9299,mac,5892' + - '\r\n' + - '"html_formatted",412032,FR,8293,win,3992' + - '\r\n' + - '"html_formatted",412032,FR,8293,mac,3029' + - '\r\n' - ); - }); - }); - - test('renders percentage columns', async function () { - $scope.dimensions = { - buckets: [ - { accessor: 0, params: {} }, - { accessor: 1, format: { id: 'date', params: { pattern: 'YYYY-MM-DD' } } }, - ], - metrics: [ - { accessor: 2, format: { id: 'number' } }, - { accessor: 3, format: { id: 'date' } }, - { accessor: 4, format: { id: 'number' } }, - { accessor: 5, format: { id: 'number' } }, - ], - }; - $scope.table = - tabifiedData.oneTermOneHistogramBucketWithTwoMetricsOneTopHitOneDerivative.tables[0]; - $scope.percentageCol = 'Average bytes'; - - const $el = $(``); - - $compile($el)($scope); - $scope.$digest(); - - const $headings = $el.find('th'); - expect($headings.length).toBe(7); - expect($headings.eq(3).text().trim()).toBe('Average bytes percentages'); - - const countColId = $scope.table.columns.find((col) => col.name === $scope.percentageCol).id; - const counts = $scope.table.rows.map((row) => row[countColId]); - const total = counts.reduce((sum, curr) => sum + curr, 0); - const $percentageColValues = $el.find('tbody tr').map((i, el) => $(el).find('td').eq(3).text()); - - $percentageColValues.each((i, value) => { - const percentage = `${round((counts[i] / total) * 100, 3)}%`; - expect(value).toBe(percentage); - }); - }); - - describe('aggTable.exportAsCsv()', function () { - let origBlob; - function FakeBlob(slices, opts) { - this.slices = slices; - this.opts = opts; - } - - beforeEach(function () { - origBlob = window.Blob; - window.Blob = FakeBlob; - }); - - afterEach(function () { - window.Blob = origBlob; - }); - - test('calls _saveAs properly', function () { - const $el = $compile('')($scope); - $scope.$digest(); - - const $tableScope = $el.isolateScope(); - const aggTable = $tableScope.aggTable; - - const saveAs = sinon.stub(aggTable, '_saveAs'); - $tableScope.table = { - columns: [ - { id: 'a', name: 'one' }, - { id: 'b', name: 'two' }, - { id: 'c', name: 'with double-quotes(")' }, - ], - rows: [{ a: 1, b: 2, c: '"foobar"' }], - }; - - aggTable.csv.filename = 'somefilename.csv'; - aggTable.exportAsCsv(); - - expect(saveAs.callCount).toBe(1); - const call = saveAs.getCall(0); - expect(call.args[0]).toBeInstanceOf(FakeBlob); - expect(call.args[0].slices).toEqual([ - 'one,two,"with double-quotes("")"' + '\r\n' + '1,2,"""foobar"""' + '\r\n', - ]); - expect(call.args[0].opts).toEqual({ - type: 'text/csv;charset=utf-8', - }); - expect(call.args[1]).toBe('somefilename.csv'); - }); - - test('should use the export-title attribute', function () { - const expected = 'export file name'; - const $el = $compile( - `` - )($scope); - $scope.$digest(); - - const $tableScope = $el.isolateScope(); - const aggTable = $tableScope.aggTable; - $tableScope.table = { - columns: [], - rows: [], - }; - $tableScope.exportTitle = expected; - $scope.$digest(); - - expect(aggTable.csv.filename).toEqual(`${expected}.csv`); - }); - }); -}); diff --git a/src/plugins/vis_type_table/public/agg_table/agg_table_group.html b/src/plugins/vis_type_table/public/agg_table/agg_table_group.html deleted file mode 100644 index 2dcf7f125f61..000000000000 --- a/src/plugins/vis_type_table/public/agg_table/agg_table_group.html +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - -
- {{ table.title }} -
- - - -
- - - - - - - - - - - - -
- {{ table.title }} -
- - - -
diff --git a/src/plugins/vis_type_table/public/agg_table/agg_table_group.js b/src/plugins/vis_type_table/public/agg_table/agg_table_group.js deleted file mode 100644 index 133b20800a18..000000000000 --- a/src/plugins/vis_type_table/public/agg_table/agg_table_group.js +++ /dev/null @@ -1,67 +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 aggTableGroupTemplate from './agg_table_group.html'; - -export function OsdAggTableGroup(RecursionHelper) { - return { - restrict: 'E', - template: aggTableGroupTemplate, - scope: { - group: '=', - dimensions: '=', - perPage: '=?', - sort: '=?', - exportTitle: '=?', - showTotal: '=', - totalFunc: '=', - percentageCol: '=', - filter: '=', - }, - compile: function ($el) { - // Use the compile function from the RecursionHelper, - // And return the linking function(s) which it returns - return RecursionHelper.compile($el, { - post: function ($scope) { - $scope.$watch('group', function (group) { - // clear the previous "state" - $scope.rows = $scope.columns = false; - - if (!group || !group.tables.length) return; - - const childLayout = group.direction === 'row' ? 'rows' : 'columns'; - - $scope[childLayout] = group.tables; - }); - }, - }); - }, - }; -} diff --git a/src/plugins/vis_type_table/public/agg_table/agg_table_group.test.js b/src/plugins/vis_type_table/public/agg_table/agg_table_group.test.js deleted file mode 100644 index 18a48e922116..000000000000 --- a/src/plugins/vis_type_table/public/agg_table/agg_table_group.test.js +++ /dev/null @@ -1,152 +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 $ from 'jquery'; -import angular from 'angular'; -import 'angular-mocks'; -import expect from '@osd/expect'; - -import { getFieldFormatsRegistry } from '../../../data/public/test_utils'; -import { coreMock } from '../../../../core/public/mocks'; -import { initAngularBootstrap } from '../../../opensearch_dashboards_legacy/public'; -import { setUiSettings } from '../../../data/public/services'; -import { setFormatService } from '../services'; -import { getInnerAngular } from '../get_inner_angular'; -import { initTableVisLegacyModule } from '../table_vis_legacy_module'; -import { tabifiedData } from './tabified_data'; - -const uiSettings = new Map(); - -describe('Table Vis - AggTableGroup Directive', function () { - const core = coreMock.createStart(); - let $rootScope; - let $compile; - - core.uiSettings.set = jest.fn((key, value) => { - uiSettings.set(key, value); - }); - - core.uiSettings.get = jest.fn((key) => { - return uiSettings.get(key); - }); - - const initLocalAngular = () => { - const tableVisModule = getInnerAngular('opensearch-dashboards/table_vis', core); - initTableVisLegacyModule(tableVisModule); - }; - - beforeEach(() => { - setUiSettings(core.uiSettings); - setFormatService(getFieldFormatsRegistry(core)); - initAngularBootstrap(); - initLocalAngular(); - angular.mock.module('opensearch-dashboards/table_vis'); - angular.mock.inject(($injector) => { - $rootScope = $injector.get('$rootScope'); - $compile = $injector.get('$compile'); - }); - }); - - let $scope; - beforeEach(function () { - $scope = $rootScope.$new(); - }); - afterEach(function () { - $scope.$destroy(); - }); - - it('renders a simple split response properly', function () { - $scope.dimensions = { - metrics: [{ accessor: 0, format: { id: 'number' }, params: {} }], - buckets: [], - }; - $scope.group = tabifiedData.metricOnly; - $scope.sort = { - columnIndex: null, - direction: null, - }; - const $el = $( - '' - ); - - $compile($el)($scope); - $scope.$digest(); - - // should create one sub-tbale - expect($el.find('osd-agg-table').length).to.be(1); - }); - - it('renders nothing if the table list is empty', function () { - const $el = $( - '' - ); - - $scope.group = { - tables: [], - }; - - $compile($el)($scope); - $scope.$digest(); - - const $subTables = $el.find('osd-agg-table'); - expect($subTables.length).to.be(0); - }); - - it('renders a complex response properly', function () { - $scope.dimensions = { - splitRow: [{ accessor: 0, params: {} }], - buckets: [ - { accessor: 2, params: {} }, - { accessor: 4, params: {} }, - ], - metrics: [ - { accessor: 1, params: {} }, - { accessor: 3, params: {} }, - { accessor: 5, params: {} }, - ], - }; - const group = ($scope.group = tabifiedData.threeTermBucketsWithSplit); - const $el = $( - '' - ); - $compile($el)($scope); - $scope.$digest(); - - const $subTables = $el.find('osd-agg-table'); - expect($subTables.length).to.be(3); - - const $subTableHeaders = $el.find('.osdAggTable__groupHeader'); - expect($subTableHeaders.length).to.be(3); - - $subTableHeaders.each(function (i) { - expect($(this).text()).to.be(group.tables[i].title); - }); - }); -}); diff --git a/src/plugins/vis_type_table/public/agg_table/tabified_data.js b/src/plugins/vis_type_table/public/agg_table/tabified_data.js deleted file mode 100644 index ce344d5c48b6..000000000000 --- a/src/plugins/vis_type_table/public/agg_table/tabified_data.js +++ /dev/null @@ -1,806 +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. - */ - -export const tabifiedData = { - metricOnly: { - tables: [ - { - columns: [ - { - id: 'col-0-1', - name: 'Count', - }, - ], - rows: [ - { - 'col-0-1': 1000, - }, - ], - }, - ], - }, - threeTermBuckets: { - tables: [ - { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'geo.src: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'machine.os: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 0, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 9299, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'linux', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 4992, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 5892, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'linux', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 4992, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 5892, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - ], - }, - threeTermBucketsWithSplit: { - tables: [ - { - title: 'png: extension: Descending', - name: 'extension: Descending', - key: 'png', - column: 0, - row: 0, - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_3', - name: 'geo.src: Descending', - }, - { - id: 'col-2-agg_4', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'png', - 'col-1-agg_3': 'IT', - 'col-2-agg_4': 'win', - 'col-3-agg_1': 0, - }, - { - 'col-0-agg_2': 'png', - 'col-1-agg_3': 'IT', - 'col-2-agg_4': 'mac', - 'col-3-agg_1': 9299, - }, - { - 'col-0-agg_2': 'png', - 'col-1-agg_3': 'US', - 'col-2-agg_4': 'linux', - 'col-3-agg_1': 3992, - }, - { - 'col-0-agg_2': 'png', - 'col-1-agg_3': 'US', - 'col-2-agg_4': 'mac', - 'col-3-agg_1': 3029, - }, - { - 'col-0-agg_2': 'css', - 'col-1-agg_3': 'MX', - 'col-2-agg_4': 'win', - 'col-3-agg_1': 4992, - }, - { - 'col-0-agg_2': 'css', - 'col-1-agg_3': 'MX', - 'col-2-agg_4': 'mac', - 'col-3-agg_1': 5892, - }, - { - 'col-0-agg_2': 'css', - 'col-1-agg_3': 'US', - 'col-2-agg_4': 'linux', - 'col-3-agg_1': 3992, - }, - { - 'col-0-agg_2': 'css', - 'col-1-agg_3': 'US', - 'col-2-agg_4': 'mac', - 'col-3-agg_1': 3029, - }, - { - 'col-0-agg_2': 'html', - 'col-1-agg_3': 'CN', - 'col-2-agg_4': 'win', - 'col-3-agg_1': 4992, - }, - { - 'col-0-agg_2': 'html', - 'col-1-agg_3': 'CN', - 'col-2-agg_4': 'mac', - 'col-3-agg_1': 5892, - }, - { - 'col-0-agg_2': 'html', - 'col-1-agg_3': 'FR', - 'col-2-agg_4': 'win', - 'col-3-agg_1': 3992, - }, - { - 'col-0-agg_2': 'html', - 'col-1-agg_3': 'FR', - 'col-2-agg_4': 'mac', - 'col-3-agg_1': 3029, - }, - ], - }, - tables: [ - { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_3', - name: 'geo.src: Descending', - }, - { - id: 'col-2-agg_4', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'png', - 'col-1-agg_3': 'IT', - 'col-2-agg_4': 'win', - 'col-3-agg_1': 0, - }, - { - 'col-0-agg_2': 'png', - 'col-1-agg_3': 'IT', - 'col-2-agg_4': 'mac', - 'col-3-agg_1': 9299, - }, - { - 'col-0-agg_2': 'png', - 'col-1-agg_3': 'US', - 'col-2-agg_4': 'linux', - 'col-3-agg_1': 3992, - }, - { - 'col-0-agg_2': 'png', - 'col-1-agg_3': 'US', - 'col-2-agg_4': 'mac', - 'col-3-agg_1': 3029, - }, - ], - }, - ], - }, - { - title: 'css: extension: Descending', - name: 'extension: Descending', - key: 'css', - column: 0, - row: 4, - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_3', - name: 'geo.src: Descending', - }, - { - id: 'col-2-agg_4', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'png', - 'col-1-agg_3': 'IT', - 'col-2-agg_4': 'win', - 'col-3-agg_1': 0, - }, - { - 'col-0-agg_2': 'png', - 'col-1-agg_3': 'IT', - 'col-2-agg_4': 'mac', - 'col-3-agg_1': 9299, - }, - { - 'col-0-agg_2': 'png', - 'col-1-agg_3': 'US', - 'col-2-agg_4': 'linux', - 'col-3-agg_1': 3992, - }, - { - 'col-0-agg_2': 'png', - 'col-1-agg_3': 'US', - 'col-2-agg_4': 'mac', - 'col-3-agg_1': 3029, - }, - { - 'col-0-agg_2': 'css', - 'col-1-agg_3': 'MX', - 'col-2-agg_4': 'win', - 'col-3-agg_1': 4992, - }, - { - 'col-0-agg_2': 'css', - 'col-1-agg_3': 'MX', - 'col-2-agg_4': 'mac', - 'col-3-agg_1': 5892, - }, - { - 'col-0-agg_2': 'css', - 'col-1-agg_3': 'US', - 'col-2-agg_4': 'linux', - 'col-3-agg_1': 3992, - }, - { - 'col-0-agg_2': 'css', - 'col-1-agg_3': 'US', - 'col-2-agg_4': 'mac', - 'col-3-agg_1': 3029, - }, - { - 'col-0-agg_2': 'html', - 'col-1-agg_3': 'CN', - 'col-2-agg_4': 'win', - 'col-3-agg_1': 4992, - }, - { - 'col-0-agg_2': 'html', - 'col-1-agg_3': 'CN', - 'col-2-agg_4': 'mac', - 'col-3-agg_1': 5892, - }, - { - 'col-0-agg_2': 'html', - 'col-1-agg_3': 'FR', - 'col-2-agg_4': 'win', - 'col-3-agg_1': 3992, - }, - { - 'col-0-agg_2': 'html', - 'col-1-agg_3': 'FR', - 'col-2-agg_4': 'mac', - 'col-3-agg_1': 3029, - }, - ], - }, - tables: [ - { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_3', - name: 'geo.src: Descending', - }, - { - id: 'col-2-agg_4', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'css', - 'col-1-agg_3': 'MX', - 'col-2-agg_4': 'win', - 'col-3-agg_1': 4992, - }, - { - 'col-0-agg_2': 'css', - 'col-1-agg_3': 'MX', - 'col-2-agg_4': 'mac', - 'col-3-agg_1': 5892, - }, - { - 'col-0-agg_2': 'css', - 'col-1-agg_3': 'US', - 'col-2-agg_4': 'linux', - 'col-3-agg_1': 3992, - }, - { - 'col-0-agg_2': 'css', - 'col-1-agg_3': 'US', - 'col-2-agg_4': 'mac', - 'col-3-agg_1': 3029, - }, - ], - }, - ], - }, - { - title: 'html: extension: Descending', - name: 'extension: Descending', - key: 'html', - column: 0, - row: 8, - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_3', - name: 'geo.src: Descending', - }, - { - id: 'col-2-agg_4', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'png', - 'col-1-agg_3': 'IT', - 'col-2-agg_4': 'win', - 'col-3-agg_1': 0, - }, - { - 'col-0-agg_2': 'png', - 'col-1-agg_3': 'IT', - 'col-2-agg_4': 'mac', - 'col-3-agg_1': 9299, - }, - { - 'col-0-agg_2': 'png', - 'col-1-agg_3': 'US', - 'col-2-agg_4': 'linux', - 'col-3-agg_1': 3992, - }, - { - 'col-0-agg_2': 'png', - 'col-1-agg_3': 'US', - 'col-2-agg_4': 'mac', - 'col-3-agg_1': 3029, - }, - { - 'col-0-agg_2': 'css', - 'col-1-agg_3': 'MX', - 'col-2-agg_4': 'win', - 'col-3-agg_1': 4992, - }, - { - 'col-0-agg_2': 'css', - 'col-1-agg_3': 'MX', - 'col-2-agg_4': 'mac', - 'col-3-agg_1': 5892, - }, - { - 'col-0-agg_2': 'css', - 'col-1-agg_3': 'US', - 'col-2-agg_4': 'linux', - 'col-3-agg_1': 3992, - }, - { - 'col-0-agg_2': 'css', - 'col-1-agg_3': 'US', - 'col-2-agg_4': 'mac', - 'col-3-agg_1': 3029, - }, - { - 'col-0-agg_2': 'html', - 'col-1-agg_3': 'CN', - 'col-2-agg_4': 'win', - 'col-3-agg_1': 4992, - }, - { - 'col-0-agg_2': 'html', - 'col-1-agg_3': 'CN', - 'col-2-agg_4': 'mac', - 'col-3-agg_1': 5892, - }, - { - 'col-0-agg_2': 'html', - 'col-1-agg_3': 'FR', - 'col-2-agg_4': 'win', - 'col-3-agg_1': 3992, - }, - { - 'col-0-agg_2': 'html', - 'col-1-agg_3': 'FR', - 'col-2-agg_4': 'mac', - 'col-3-agg_1': 3029, - }, - ], - }, - tables: [ - { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_3', - name: 'geo.src: Descending', - }, - { - id: 'col-2-agg_4', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'html', - 'col-1-agg_3': 'CN', - 'col-2-agg_4': 'win', - 'col-3-agg_1': 4992, - }, - { - 'col-0-agg_2': 'html', - 'col-1-agg_3': 'CN', - 'col-2-agg_4': 'mac', - 'col-3-agg_1': 5892, - }, - { - 'col-0-agg_2': 'html', - 'col-1-agg_3': 'FR', - 'col-2-agg_4': 'win', - 'col-3-agg_1': 3992, - }, - { - 'col-0-agg_2': 'html', - 'col-1-agg_3': 'FR', - 'col-2-agg_4': 'mac', - 'col-3-agg_1': 3029, - }, - ], - }, - ], - }, - ], - direction: 'row', - }, - oneTermOneHistogramBucketWithTwoMetricsOneTopHitOneDerivative: { - tables: [ - { - columns: [ - { - id: 'col-0-agg_3', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_4', - name: '@timestamp per day', - }, - { - id: 'col-2-agg_1', - name: 'Average bytes', - }, - { - id: 'col-3-agg_2', - name: 'Min @timestamp', - }, - { - id: 'col-4-agg_5', - name: 'Derivative of Count', - }, - { - id: 'col-5-agg_6', - name: 'Last bytes', - }, - ], - rows: [ - { - 'col-0-agg_3': 'png', - 'col-1-agg_4': 1411862400000, - 'col-2-agg_1': 9283, - 'col-3-agg_2': 1411862400000, - 'col-5-agg_6': 23, - }, - { - 'col-0-agg_3': 'png', - 'col-1-agg_4': 1411948800000, - 'col-2-agg_1': 28349, - 'col-3-agg_2': 1411948800000, - 'col-4-agg_5': 203, - 'col-5-agg_6': 39, - }, - { - 'col-0-agg_3': 'png', - 'col-1-agg_4': 1412035200000, - 'col-2-agg_1': 84330, - 'col-3-agg_2': 1412035200000, - 'col-4-agg_5': 200, - 'col-5-agg_6': 329, - }, - { - 'col-0-agg_3': 'png', - 'col-1-agg_4': 1412121600000, - 'col-2-agg_1': 34992, - 'col-3-agg_2': 1412121600000, - 'col-4-agg_5': 103, - 'col-5-agg_6': 22, - }, - { - 'col-0-agg_3': 'png', - 'col-1-agg_4': 1412208000000, - 'col-2-agg_1': 145432, - 'col-3-agg_2': 1412208000000, - 'col-4-agg_5': 153, - 'col-5-agg_6': 93, - }, - { - 'col-0-agg_3': 'png', - 'col-1-agg_4': 1412294400000, - 'col-2-agg_1': 220943, - 'col-3-agg_2': 1412294400000, - 'col-4-agg_5': 239, - 'col-5-agg_6': 72, - }, - { - 'col-0-agg_3': 'css', - 'col-1-agg_4': 1411862400000, - 'col-2-agg_1': 9283, - 'col-3-agg_2': 1411862400000, - 'col-5-agg_6': 75, - }, - { - 'col-0-agg_3': 'css', - 'col-1-agg_4': 1411948800000, - 'col-2-agg_1': 28349, - 'col-3-agg_2': 1411948800000, - 'col-4-agg_5': 10, - 'col-5-agg_6': 11, - }, - { - 'col-0-agg_3': 'css', - 'col-1-agg_4': 1412035200000, - 'col-2-agg_1': 84330, - 'col-3-agg_2': 1412035200000, - 'col-4-agg_5': 24, - 'col-5-agg_6': 238, - }, - { - 'col-0-agg_3': 'css', - 'col-1-agg_4': 1412121600000, - 'col-2-agg_1': 34992, - 'col-3-agg_2': 1412121600000, - 'col-4-agg_5': 49, - 'col-5-agg_6': 343, - }, - { - 'col-0-agg_3': 'css', - 'col-1-agg_4': 1412208000000, - 'col-2-agg_1': 145432, - 'col-3-agg_2': 1412208000000, - 'col-4-agg_5': 100, - 'col-5-agg_6': 837, - }, - { - 'col-0-agg_3': 'css', - 'col-1-agg_4': 1412294400000, - 'col-2-agg_1': 220943, - 'col-3-agg_2': 1412294400000, - 'col-4-agg_5': 23, - 'col-5-agg_6': 302, - }, - { - 'col-0-agg_3': 'html', - 'col-1-agg_4': 1411862400000, - 'col-2-agg_1': 9283, - 'col-3-agg_2': 1411862400000, - 'col-5-agg_6': 30, - }, - { - 'col-0-agg_3': 'html', - 'col-1-agg_4': 1411948800000, - 'col-2-agg_1': 28349, - 'col-3-agg_2': 1411948800000, - 'col-4-agg_5': 1, - 'col-5-agg_6': 43, - }, - { - 'col-0-agg_3': 'html', - 'col-1-agg_4': 1412035200000, - 'col-2-agg_1': 84330, - 'col-3-agg_2': 1412035200000, - 'col-4-agg_5': 5, - 'col-5-agg_6': 88, - }, - { - 'col-0-agg_3': 'html', - 'col-1-agg_4': 1412121600000, - 'col-2-agg_1': 34992, - 'col-3-agg_2': 1412121600000, - 'col-4-agg_5': 10, - 'col-5-agg_6': 91, - }, - { - 'col-0-agg_3': 'html', - 'col-1-agg_4': 1412208000000, - 'col-2-agg_1': 145432, - 'col-3-agg_2': 1412208000000, - 'col-4-agg_5': 43, - 'col-5-agg_6': 534, - }, - { - 'col-0-agg_3': 'html', - 'col-1-agg_4': 1412294400000, - 'col-2-agg_1': 220943, - 'col-3-agg_2': 1412294400000, - 'col-4-agg_5': 1, - 'col-5-agg_6': 553, - }, - ], - }, - ], - }, -}; diff --git a/src/plugins/vis_type_table_new/public/components/table_vis_app.scss b/src/plugins/vis_type_table/public/components/table_vis_app.scss similarity index 100% rename from src/plugins/vis_type_table_new/public/components/table_vis_app.scss rename to src/plugins/vis_type_table/public/components/table_vis_app.scss diff --git a/src/plugins/vis_type_table_new/public/components/table_vis_app.tsx b/src/plugins/vis_type_table/public/components/table_vis_app.tsx similarity index 100% rename from src/plugins/vis_type_table_new/public/components/table_vis_app.tsx rename to src/plugins/vis_type_table/public/components/table_vis_app.tsx diff --git a/src/plugins/vis_type_table_new/public/components/table_vis_component.tsx b/src/plugins/vis_type_table/public/components/table_vis_component.tsx similarity index 94% rename from src/plugins/vis_type_table_new/public/components/table_vis_component.tsx rename to src/plugins/vis_type_table/public/components/table_vis_component.tsx index 4a25395703b0..be854f0e0278 100644 --- a/src/plugins/vis_type_table_new/public/components/table_vis_component.tsx +++ b/src/plugins/vis_type_table/public/components/table_vis_component.tsx @@ -107,6 +107,13 @@ export const TableVisComponent = ({ const ariaLabel = title || visConfig.title || 'tableVis'; + const footerCellValue = visConfig.showTotal + ? ({ columnId }: { columnId: any }) => { + const colIndex = columns.findIndex((col) => col.id === columnId); + return columns[colIndex]?.formattedTotal || null; + } + : undefined; + return ( <> {title && ( @@ -131,6 +138,7 @@ export const TableVisComponent = ({ header: 'underline', }} minSizeForControls={1} + renderFooterCellValue={footerCellValue} toolbarVisibility={{ showColumnSelector: false, showSortSelector: false, diff --git a/src/plugins/vis_type_table_new/public/components/table_vis_component_group.tsx b/src/plugins/vis_type_table/public/components/table_vis_component_group.tsx similarity index 100% rename from src/plugins/vis_type_table_new/public/components/table_vis_component_group.tsx rename to src/plugins/vis_type_table/public/components/table_vis_component_group.tsx diff --git a/src/plugins/vis_type_table_new/public/components/table_vis_control.tsx b/src/plugins/vis_type_table/public/components/table_vis_control.tsx similarity index 93% rename from src/plugins/vis_type_table_new/public/components/table_vis_control.tsx rename to src/plugins/vis_type_table/public/components/table_vis_control.tsx index 26b51c9cc85b..1e11610b0c23 100644 --- a/src/plugins/vis_type_table_new/public/components/table_vis_control.tsx +++ b/src/plugins/vis_type_table/public/components/table_vis_control.tsx @@ -9,7 +9,7 @@ import { OpenSearchDashboardsDatatableRow } from 'src/plugins/expressions'; import { CoreStart } from 'opensearch-dashboards/public'; import { exportAsCsv } from '../utils/convert_to_csv_data'; import { FormattedColumn } from '../types'; -import { useOpenSearchDashboards } from '../../../../../src/plugins/opensearch_dashboards_react/public'; +import { useOpenSearchDashboards } from '../../../opensearch_dashboards_react/public'; interface TableVisControlProps { filename?: string; diff --git a/src/plugins/vis_type_table/public/components/table_vis_grid_columns.tsx b/src/plugins/vis_type_table/public/components/table_vis_grid_columns.tsx new file mode 100644 index 000000000000..e7265d9c9237 --- /dev/null +++ b/src/plugins/vis_type_table/public/components/table_vis_grid_columns.tsx @@ -0,0 +1,148 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { i18n } from '@osd/i18n'; +import { EuiDataGridColumn, EuiDataGridColumnCellActionProps } from '@elastic/eui'; +import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; +import { OpenSearchDashboardsDatatableRow } from 'src/plugins/expressions'; +import { Table } from '../table_vis_response_handler'; +import { ColumnWidth, FormattedColumn } from '../types'; + +export const getDataGridColumns = ( + rows: OpenSearchDashboardsDatatableRow[], + cols: FormattedColumn[], + table: Table, + event: IInterpreterRenderHandlers['event'], + columnsWidth: ColumnWidth[] +) => { + const filterBucket = (rowIndex: number, columnIndex: number, negate: boolean) => { + const foramttedColumnId = cols[columnIndex].id; + const rawColumnIndex = table.columns.findIndex((col) => col.id === foramttedColumnId); + event({ + name: 'filterBucket', + data: { + data: [ + { + table: { + columns: table.columns, + rows, + }, + row: rowIndex, + column: rawColumnIndex, + }, + ], + negate, + }, + }); + }; + + return cols.map((col, colIndex) => { + const cellActions = col.filterable + ? [ + ({ rowIndex, columnId, Component, closePopover }: EuiDataGridColumnCellActionProps) => { + const filterValue = rows[rowIndex][columnId]; + const filterContent = col.formatter?.convert(filterValue); + + const filterForValueText = i18n.translate( + 'visTypeTable.tableVisFilter.filterForValue', + { + defaultMessage: 'Filter for value', + } + ); + const filterForValueLabel = i18n.translate( + 'visTypeTable.tableVisFilter.filterForValueLabel', + { + defaultMessage: 'Filter for value: {filterContent}', + values: { + filterContent, + }, + } + ); + + return ( + filterValue != null && ( + { + filterBucket(rowIndex, colIndex, false); + closePopover(); + }} + iconType="plusInCircle" + aria-label={filterForValueLabel} + data-test-subj="filterForValue" + > + {filterForValueText} + + ) + ); + }, + ({ rowIndex, columnId, Component, closePopover }: EuiDataGridColumnCellActionProps) => { + const filterValue = rows[rowIndex][columnId]; + const filterContent = col.formatter?.convert(filterValue); + + const filterOutValueText = i18n.translate( + 'visTypeTable.tableVisFilter.filterOutValue', + { + defaultMessage: 'Filter out value', + } + ); + const filterOutValueLabel = i18n.translate( + 'visTypeTable.tableVisFilter.filterOutValueLabel', + { + defaultMessage: 'Filter out value: {filterContent}', + values: { + filterContent, + }, + } + ); + + return ( + filterValue != null && ( + { + filterBucket(rowIndex, colIndex, true); + closePopover(); + }} + iconType="minusInCircle" + aria-label={filterOutValueLabel} + data-test-subj="filterOutValue" + > + {filterOutValueText} + + ) + ); + }, + ] + : undefined; + + const initialWidth = columnsWidth.find((c) => c.colIndex === colIndex); + + const dataGridColumn: EuiDataGridColumn = { + id: col.id, + display: col.title, + displayAsText: col.title, + actions: { + showHide: false, + showMoveLeft: false, + showMoveRight: false, + showSortAsc: { + label: i18n.translate('visTypeTable.tableVisSort.ascSortLabel', { + defaultMessage: 'Sort asc', + }), + }, + showSortDesc: { + label: i18n.translate('visTypeTable.tableVisSort.descSortLabel', { + defaultMessage: 'Sort desc', + }), + }, + }, + cellActions, + }; + if (initialWidth) { + dataGridColumn.initialWidth = initialWidth.width; + } + return dataGridColumn; + }); +}; diff --git a/src/plugins/vis_type_table/public/get_inner_angular.ts b/src/plugins/vis_type_table/public/get_inner_angular.ts deleted file mode 100644 index 7f42984d7c02..000000000000 --- a/src/plugins/vis_type_table/public/get_inner_angular.ts +++ /dev/null @@ -1,117 +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. - */ - -// inner angular imports -// these are necessary to bootstrap the local angular. -// They can stay even after NP cutover -import angular from 'angular'; -// required for `ngSanitize` angular module -import 'angular-sanitize'; -import 'angular-recursion'; -import { i18nDirective, i18nFilter, I18nProvider } from '@osd/i18n/angular'; -import { - CoreStart, - IUiSettingsClient, - PluginInitializerContext, -} from 'opensearch-dashboards/public'; -import { - initAngularBootstrap, - PaginateDirectiveProvider, - PaginateControlsDirectiveProvider, - PrivateProvider, - watchMultiDecorator, - OsdAccessibleClickProvider, -} from '../../opensearch_dashboards_legacy/public'; - -initAngularBootstrap(); - -const thirdPartyAngularDependencies = ['ngSanitize', 'ui.bootstrap', 'RecursionHelper']; - -export function getAngularModule(name: string, core: CoreStart, context: PluginInitializerContext) { - const uiModule = getInnerAngular(name, core); - return uiModule; -} - -let initialized = false; - -export function getInnerAngular(name = 'opensearch-dashboards/table_vis', core: CoreStart) { - if (!initialized) { - createLocalPrivateModule(); - createLocalI18nModule(); - createLocalConfigModule(core.uiSettings); - createLocalPaginateModule(); - initialized = true; - } - return angular - .module(name, [ - ...thirdPartyAngularDependencies, - 'tableVisPaginate', - 'tableVisConfig', - 'tableVisPrivate', - 'tableVisI18n', - ]) - .config(watchMultiDecorator) - .directive('osdAccessibleClick', OsdAccessibleClickProvider); -} - -function createLocalPrivateModule() { - angular.module('tableVisPrivate', []).provider('Private', PrivateProvider); -} - -function createLocalConfigModule(uiSettings: IUiSettingsClient) { - angular.module('tableVisConfig', []).provider('config', function () { - return { - $get: () => ({ - get: (value: string) => { - return uiSettings ? uiSettings.get(value) : undefined; - }, - // set method is used in agg_table mocha test - set: (key: string, value: string) => { - return uiSettings ? uiSettings.set(key, value) : undefined; - }, - }), - }; - }); -} - -function createLocalI18nModule() { - angular - .module('tableVisI18n', []) - .provider('i18n', I18nProvider) - .filter('i18n', i18nFilter) - .directive('i18nId', i18nDirective); -} - -function createLocalPaginateModule() { - angular - .module('tableVisPaginate', []) - .directive('paginate', PaginateDirectiveProvider) - .directive('paginateControls', PaginateControlsDirectiveProvider); -} diff --git a/src/plugins/vis_type_table/public/index.scss b/src/plugins/vis_type_table/public/index.scss deleted file mode 100644 index d21bf5262602..000000000000 --- a/src/plugins/vis_type_table/public/index.scss +++ /dev/null @@ -1,10 +0,0 @@ -// Prefix all styles with "tbv" to avoid conflicts. -// Examples -// tbvChart -// tbvChart__legend -// tbvChart__legend--small -// tbvChart__legend-isLoading - -@import "./agg_table/index"; -@import "./paginated_table/index"; -@import "./table_vis"; diff --git a/src/plugins/vis_type_table/public/index.ts b/src/plugins/vis_type_table/public/index.ts index dc7e7a16b6ce..b5ab796210ff 100644 --- a/src/plugins/vis_type_table/public/index.ts +++ b/src/plugins/vis_type_table/public/index.ts @@ -1,34 +1,8 @@ /* + * Copyright OpenSearch Contributors * 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 './index.scss'; import { PluginInitializerContext } from 'opensearch-dashboards/public'; import { TableVisPlugin as Plugin } from './plugin'; diff --git a/src/plugins/vis_type_table/public/paginated_table/_index.scss b/src/plugins/vis_type_table/public/paginated_table/_index.scss deleted file mode 100644 index 66275b5c7da8..000000000000 --- a/src/plugins/vis_type_table/public/paginated_table/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import "./table_cell_filter"; diff --git a/src/plugins/vis_type_table/public/paginated_table/_table_cell_filter.scss b/src/plugins/vis_type_table/public/paginated_table/_table_cell_filter.scss deleted file mode 100644 index 3deece36b2c6..000000000000 --- a/src/plugins/vis_type_table/public/paginated_table/_table_cell_filter.scss +++ /dev/null @@ -1,30 +0,0 @@ -.osdTableCellFilter__hover { - position: relative; - - /** - * 1. Center vertically regardless of row height. - */ - .osdTableCellFilter { - position: absolute; - white-space: nowrap; - right: 0; - top: 50%; /* 1 */ - transform: translateY(-50%); /* 1 */ - display: none; - } - - &:hover { - .osdTableCellFilter { - display: inline; - } - - .osdTableCellFilter__hover-show { - visibility: visible; - } - } -} - -.osdTableCellFilter__hover-show { - // so that the cell doesn't change size on hover - visibility: hidden; -} diff --git a/src/plugins/vis_type_table/public/paginated_table/paginated_table.html b/src/plugins/vis_type_table/public/paginated_table/paginated_table.html deleted file mode 100644 index 83de29a12737..000000000000 --- a/src/plugins/vis_type_table/public/paginated_table/paginated_table.html +++ /dev/null @@ -1,55 +0,0 @@ - -
- - - - - - - - - - - - - -
- - - - - - -
- {{ col.formattedTotal }} -
-
- - - -
-
diff --git a/src/plugins/vis_type_table/public/paginated_table/paginated_table.js b/src/plugins/vis_type_table/public/paginated_table/paginated_table.js deleted file mode 100644 index c97fa6c26587..000000000000 --- a/src/plugins/vis_type_table/public/paginated_table/paginated_table.js +++ /dev/null @@ -1,120 +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 _ from 'lodash'; -import paginatedTableTemplate from './paginated_table.html'; - -export function PaginatedTable($filter) { - const orderBy = $filter('orderBy'); - - return { - restrict: 'E', - template: paginatedTableTemplate, - transclude: true, - scope: { - table: '=', - rows: '=', - columns: '=', - linkToTop: '=', - perPage: '=?', - sortHandler: '=?', - sort: '=?', - showSelector: '=?', - showTotal: '=', - totalFunc: '=', - filter: '=', - percentageCol: '=', - }, - controllerAs: 'paginatedTable', - controller: function ($scope) { - const self = this; - self.sort = { - columnIndex: null, - direction: null, - }; - - self.sortColumn = function (colIndex, sortDirection = 'asc') { - const col = $scope.columns[colIndex]; - - if (!col) return; - if (col.sortable === false) return; - - if (self.sort.columnIndex === colIndex) { - const directions = { - null: 'asc', - asc: 'desc', - desc: null, - }; - sortDirection = directions[self.sort.direction]; - } - - self.sort.columnIndex = colIndex; - self.sort.direction = sortDirection; - if ($scope.sort) { - _.assign($scope.sort, self.sort); - } - }; - - function valueGetter(row) { - const col = $scope.columns[self.sort.columnIndex]; - let value = row[col.id]; - if (typeof value === 'boolean') value = value ? 0 : 1; - return value; - } - - // Set the sort state if it is set - if ($scope.sort && $scope.sort.columnIndex !== null) { - self.sortColumn($scope.sort.columnIndex, $scope.sort.direction); - } - - function resortRows() { - const newSort = $scope.sort; - if (newSort && !_.isEqual(newSort, self.sort)) { - self.sortColumn(newSort.columnIndex, newSort.direction); - } - - if (!$scope.rows || !$scope.columns) { - $scope.sortedRows = false; - return; - } - - const sort = self.sort; - if (sort.direction == null) { - $scope.sortedRows = $scope.rows.slice(0); - } else { - $scope.sortedRows = orderBy($scope.rows, valueGetter, sort.direction === 'desc'); - } - } - - // update the sortedRows result - $scope.$watchMulti(['rows', 'columns', '[]sort', '[]paginatedTable.sort'], resortRows); - }, - }; -} diff --git a/src/plugins/vis_type_table/public/paginated_table/paginated_table.test.ts b/src/plugins/vis_type_table/public/paginated_table/paginated_table.test.ts deleted file mode 100644 index cc86bda46573..000000000000 --- a/src/plugins/vis_type_table/public/paginated_table/paginated_table.test.ts +++ /dev/null @@ -1,485 +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 { isNumber, times, identity, random } from 'lodash'; -import angular, { IRootScopeService, IScope, ICompileService } from 'angular'; -import $ from 'jquery'; -import 'angular-sanitize'; -import 'angular-mocks'; - -import { getAngularModule } from '../get_inner_angular'; -import { initTableVisLegacyModule } from '../table_vis_legacy_module'; -import { coreMock } from '../../../../core/public/mocks'; - -jest.mock('../../../opensearch_dashboards_legacy/public/angular/angular_config', () => ({ - configureAppAngularModule: () => {}, -})); - -interface Sort { - columnIndex: number; - direction: string; -} - -interface Row { - [key: string]: number | string; -} - -interface Column { - id?: string; - title: string; - formatter?: { - convert?: (val: string) => string; - }; - sortable?: boolean; -} - -interface Table { - columns: Column[]; - rows: Row[]; -} - -interface PaginatedTableScope extends IScope { - table?: Table; - cols?: Column[]; - rows?: Row[]; - perPage?: number; - sort?: Sort; - linkToTop?: boolean; -} - -describe('Table Vis - Paginated table', () => { - let $el: JQuery; - let $rootScope: IRootScopeService; - let $compile: ICompileService; - let $scope: PaginatedTableScope; - const defaultPerPage = 10; - let paginatedTable: any; - - const initLocalAngular = () => { - const tableVisModule = getAngularModule( - 'opensearch-dashboards/table_vis', - coreMock.createStart(), - coreMock.createPluginInitializerContext() - ); - initTableVisLegacyModule(tableVisModule); - }; - - beforeEach(initLocalAngular); - beforeEach(angular.mock.module('opensearch-dashboards/table_vis')); - - beforeEach( - angular.mock.inject((_$rootScope_: IRootScopeService, _$compile_: ICompileService) => { - $rootScope = _$rootScope_; - $compile = _$compile_; - $scope = $rootScope.$new(); - }) - ); - - afterEach(() => { - $scope.$destroy(); - }); - - const makeData = (colCount: number | Column[], rowCount: number | string[][]) => { - let columns: Column[] = []; - let rows: Row[] = []; - - if (isNumber(colCount)) { - times(colCount, (i) => { - columns.push({ id: `${i}`, title: `column${i}`, formatter: { convert: identity } }); - }); - } else { - columns = colCount.map( - (col, i) => - ({ - id: `${i}`, - title: col.title, - formatter: col.formatter || { convert: identity }, - } as Column) - ); - } - - if (isNumber(rowCount)) { - times(rowCount, (row) => { - const rowItems: Row = {}; - - times(columns.length, (col) => { - rowItems[`${col}`] = `item-${col}-${row}`; - }); - - rows.push(rowItems); - }); - } else { - rows = rowCount.map((row: string[]) => { - const newRow: Row = {}; - row.forEach((v, i) => (newRow[i] = v)); - return newRow; - }); - } - - return { - columns, - rows, - }; - }; - - const renderTable = ( - table: { columns: Column[]; rows: Row[] } | null, - cols: Column[], - rows: Row[], - perPage?: number, - sort?: Sort, - linkToTop?: boolean - ) => { - $scope.table = table || { columns: [], rows: [] }; - $scope.cols = cols || []; - $scope.rows = rows || []; - $scope.perPage = perPage || defaultPerPage; - $scope.sort = sort; - $scope.linkToTop = linkToTop; - - const template = ` - `; - const element = $compile(template)($scope); - $el = $(element); - - $scope.$digest(); - paginatedTable = element.controller('paginatedTable'); - }; - - describe('rendering', () => { - test('should not display without rows', () => { - const cols: Column[] = [ - { - id: 'col-1-1', - title: 'test1', - }, - ]; - const rows: Row[] = []; - - renderTable(null, cols, rows); - expect($el.children().length).toBe(0); - }); - - test('should render columns and rows', () => { - const data = makeData(2, 2); - const cols = data.columns; - const rows = data.rows; - - renderTable(data, cols, rows); - expect($el.children().length).toBe(1); - const tableRows = $el.find('tbody tr'); - - // should contain the row data - expect(tableRows.eq(0).find('td').eq(0).text()).toBe(rows[0][0]); - expect(tableRows.eq(0).find('td').eq(1).text()).toBe(rows[0][1]); - expect(tableRows.eq(1).find('td').eq(0).text()).toBe(rows[1][0]); - expect(tableRows.eq(1).find('td').eq(1).text()).toBe(rows[1][1]); - }); - - test('should paginate rows', () => { - // note: paginate truncates pages, so don't make too many - const rowCount = random(16, 24); - const perPageCount = random(5, 8); - const data = makeData(3, rowCount); - const pageCount = Math.ceil(rowCount / perPageCount); - - renderTable(data, data.columns, data.rows, perPageCount); - const tableRows = $el.find('tbody tr'); - expect(tableRows.length).toBe(perPageCount); - // add 2 for the first and last page links - expect($el.find('paginate-controls button').length).toBe(pageCount + 2); - }); - - test('should not show blank rows on last page', () => { - const rowCount = 7; - const perPageCount = 10; - const data = makeData(3, rowCount); - - renderTable(data, data.columns, data.rows, perPageCount); - const tableRows = $el.find('tbody tr'); - expect(tableRows.length).toBe(rowCount); - }); - - test('should not show link to top when not set', () => { - const data = makeData(5, 5); - renderTable(data, data.columns, data.rows, 10); - - const linkToTop = $el.find('[data-test-subj="paginateControlsLinkToTop"]'); - expect(linkToTop.length).toBe(0); - }); - - test('should show link to top when set', () => { - const data = makeData(5, 5); - renderTable(data, data.columns, data.rows, 10, undefined, true); - - const linkToTop = $el.find('[data-test-subj="paginateControlsLinkToTop"]'); - expect(linkToTop.length).toBe(1); - }); - }); - - describe('sorting', () => { - let data: Table; - let lastRowIndex: number; - - beforeEach(() => { - data = makeData(3, [ - ['bbbb', 'aaaa', 'zzzz'], - ['cccc', 'cccc', 'aaaa'], - ['zzzz', 'bbbb', 'bbbb'], - ['aaaa', 'zzzz', 'cccc'], - ]); - - lastRowIndex = data.rows.length - 1; - renderTable(data, data.columns, data.rows); - }); - - test('should not sort by default', () => { - const tableRows = $el.find('tbody tr'); - expect(tableRows.eq(0).find('td').eq(0).text()).toBe(data.rows[0][0]); - expect(tableRows.eq(lastRowIndex).find('td').eq(0).text()).toBe(data.rows[lastRowIndex][0]); - }); - - test('should do nothing when sorting by invalid column id', () => { - // sortColumn - paginatedTable.sortColumn(999); - $scope.$digest(); - - const tableRows = $el.find('tbody tr'); - expect(tableRows.eq(0).find('td').eq(0).text()).toBe('bbbb'); - expect(tableRows.eq(0).find('td').eq(1).text()).toBe('aaaa'); - expect(tableRows.eq(0).find('td').eq(2).text()).toBe('zzzz'); - }); - - test('should do nothing when sorting by non sortable column', () => { - data.columns[0].sortable = false; - - // sortColumn - paginatedTable.sortColumn(0); - $scope.$digest(); - - const tableRows = $el.find('tbody tr'); - expect(tableRows.eq(0).find('td').eq(0).text()).toBe('bbbb'); - expect(tableRows.eq(0).find('td').eq(1).text()).toBe('aaaa'); - expect(tableRows.eq(0).find('td').eq(2).text()).toBe('zzzz'); - }); - - test("should set the sort direction to asc when it's not explicitly set", () => { - paginatedTable.sortColumn(1); - $scope.$digest(); - - const tableRows = $el.find('tbody tr'); - expect(tableRows.eq(2).find('td').eq(1).text()).toBe('cccc'); - expect(tableRows.eq(1).find('td').eq(1).text()).toBe('bbbb'); - expect(tableRows.eq(0).find('td').eq(1).text()).toBe('aaaa'); - }); - - test('should allow you to explicitly set the sort direction', () => { - paginatedTable.sortColumn(1, 'desc'); - $scope.$digest(); - - const tableRows = $el.find('tbody tr'); - expect(tableRows.eq(0).find('td').eq(1).text()).toBe('zzzz'); - expect(tableRows.eq(1).find('td').eq(1).text()).toBe('cccc'); - expect(tableRows.eq(2).find('td').eq(1).text()).toBe('bbbb'); - }); - - test('should sort ascending on first invocation', () => { - // sortColumn - paginatedTable.sortColumn(0); - $scope.$digest(); - - const tableRows = $el.find('tbody tr'); - expect(tableRows.eq(0).find('td').eq(0).text()).toBe('aaaa'); - expect(tableRows.eq(lastRowIndex).find('td').eq(0).text()).toBe('zzzz'); - }); - - test('should sort descending on second invocation', () => { - // sortColumn - paginatedTable.sortColumn(0); - paginatedTable.sortColumn(0); - $scope.$digest(); - - const tableRows = $el.find('tbody tr'); - expect(tableRows.eq(0).find('td').eq(0).text()).toBe('zzzz'); - expect(tableRows.eq(lastRowIndex).find('td').eq(0).text()).toBe('aaaa'); - }); - - test('should clear sorting on third invocation', () => { - // sortColumn - paginatedTable.sortColumn(0); - paginatedTable.sortColumn(0); - paginatedTable.sortColumn(0); - $scope.$digest(); - - const tableRows = $el.find('tbody tr'); - expect(tableRows.eq(0).find('td').eq(0).text()).toBe(data.rows[0][0]); - expect(tableRows.eq(lastRowIndex).find('td').eq(0).text()).toBe('aaaa'); - }); - - test('should sort new column ascending', () => { - // sort by first column - paginatedTable.sortColumn(0); - $scope.$digest(); - - // sort by second column - paginatedTable.sortColumn(1); - $scope.$digest(); - - const tableRows = $el.find('tbody tr'); - expect(tableRows.eq(0).find('td').eq(1).text()).toBe('aaaa'); - expect(tableRows.eq(lastRowIndex).find('td').eq(1).text()).toBe('zzzz'); - }); - }); - - describe('sorting duplicate columns', () => { - let data; - const colText = 'test row'; - - beforeEach(() => { - const cols: Column[] = [{ title: colText }, { title: colText }, { title: colText }]; - const rows = [ - ['bbbb', 'aaaa', 'zzzz'], - ['cccc', 'cccc', 'aaaa'], - ['zzzz', 'bbbb', 'bbbb'], - ['aaaa', 'zzzz', 'cccc'], - ]; - data = makeData(cols, rows); - - renderTable(data, data.columns, data.rows); - }); - - test('should have duplicate column titles', () => { - const columns = $el.find('thead th span'); - columns.each((i, col) => { - expect($(col).text()).toBe(colText); - }); - }); - - test('should handle sorting on columns with the same name', () => { - // sort by the last column - paginatedTable.sortColumn(2); - $scope.$digest(); - - const tableRows = $el.find('tbody tr'); - expect(tableRows.eq(0).find('td').eq(0).text()).toBe('cccc'); - expect(tableRows.eq(0).find('td').eq(1).text()).toBe('cccc'); - expect(tableRows.eq(0).find('td').eq(2).text()).toBe('aaaa'); - expect(tableRows.eq(1).find('td').eq(2).text()).toBe('bbbb'); - expect(tableRows.eq(2).find('td').eq(2).text()).toBe('cccc'); - expect(tableRows.eq(3).find('td').eq(2).text()).toBe('zzzz'); - }); - - test('should sort correctly between columns', () => { - // sort by the last column - paginatedTable.sortColumn(2); - $scope.$digest(); - - let tableRows = $el.find('tbody tr'); - expect(tableRows.eq(0).find('td').eq(0).text()).toBe('cccc'); - expect(tableRows.eq(0).find('td').eq(1).text()).toBe('cccc'); - expect(tableRows.eq(0).find('td').eq(2).text()).toBe('aaaa'); - - // sort by the first column - paginatedTable.sortColumn(0); - $scope.$digest(); - - tableRows = $el.find('tbody tr'); - expect(tableRows.eq(0).find('td').eq(0).text()).toBe('aaaa'); - expect(tableRows.eq(0).find('td').eq(1).text()).toBe('zzzz'); - expect(tableRows.eq(0).find('td').eq(2).text()).toBe('cccc'); - - expect(tableRows.eq(1).find('td').eq(0).text()).toBe('bbbb'); - expect(tableRows.eq(2).find('td').eq(0).text()).toBe('cccc'); - expect(tableRows.eq(3).find('td').eq(0).text()).toBe('zzzz'); - }); - - test('should not sort duplicate columns', () => { - paginatedTable.sortColumn(1); - $scope.$digest(); - - const sorters = $el.find('thead th i'); - expect(sorters.eq(0).hasClass('fa-sort')).toBe(true); - expect(sorters.eq(1).hasClass('fa-sort')).toBe(false); - expect(sorters.eq(2).hasClass('fa-sort')).toBe(true); - }); - }); - - describe('object rows', () => { - let cols: Column[]; - let rows: any; - - beforeEach(() => { - cols = [ - { - title: 'object test', - id: '0', - formatter: { - convert: (val) => { - return val === 'zzz' ? '

hello

' : val; - }, - }, - }, - ]; - rows = [['aaaa'], ['zzz'], ['bbbb']]; - renderTable({ columns: cols, rows }, cols, rows); - }); - - test('should append object markup', () => { - const tableRows = $el.find('tbody tr'); - expect(tableRows.eq(0).find('h1').length).toBe(0); - expect(tableRows.eq(1).find('h1').length).toBe(1); - expect(tableRows.eq(2).find('h1').length).toBe(0); - }); - - test('should sort using object value', () => { - paginatedTable.sortColumn(0); - $scope.$digest(); - let tableRows = $el.find('tbody tr'); - expect(tableRows.eq(0).find('h1').length).toBe(0); - expect(tableRows.eq(1).find('h1').length).toBe(0); - // html row should be the last row - expect(tableRows.eq(2).find('h1').length).toBe(1); - - paginatedTable.sortColumn(0); - $scope.$digest(); - tableRows = $el.find('tbody tr'); - // html row should be the first row - expect(tableRows.eq(0).find('h1').length).toBe(1); - expect(tableRows.eq(1).find('h1').length).toBe(0); - expect(tableRows.eq(2).find('h1').length).toBe(0); - }); - }); -}); diff --git a/src/plugins/vis_type_table/public/paginated_table/rows.js b/src/plugins/vis_type_table/public/paginated_table/rows.js deleted file mode 100644 index 5ed2de5de176..000000000000 --- a/src/plugins/vis_type_table/public/paginated_table/rows.js +++ /dev/null @@ -1,149 +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 $ from 'jquery'; -import _ from 'lodash'; -import angular from 'angular'; -import tableCellFilterHtml from './table_cell_filter.html'; - -export function OsdRows($compile) { - return { - restrict: 'A', - link: function ($scope, $el, attr) { - function addCell($tr, contents, column, row) { - function createCell() { - return $(document.createElement('td')); - } - - function createFilterableCell(value) { - const $template = $(tableCellFilterHtml); - $template.addClass('osdTableCellFilter__hover'); - - const scope = $scope.$new(); - - scope.onFilterClick = (event, negate) => { - // Don't add filter if a link was clicked. - if ($(event.target).is('a')) { - return; - } - - $scope.filter({ - data: [ - { - table: $scope.table, - row: $scope.rows.findIndex((r) => r === row), - column: $scope.table.columns.findIndex((c) => c.id === column.id), - value, - }, - ], - negate, - }); - }; - - return $compile($template)(scope); - } - - let $cell; - let $cellContent; - - const contentsIsDefined = contents !== null && contents !== undefined; - - if (column.filterable && contentsIsDefined) { - $cell = createFilterableCell(contents); - // in jest tests 'angular' is using jqLite. In jqLite the method find lookups only by tags. - // Because of this, we should change a way how we get cell content so that tests will pass. - $cellContent = angular.element($cell[0].querySelector('[data-cell-content]')); - } else { - $cell = $cellContent = createCell(); - } - - // An AggConfigResult can "enrich" cell contents by applying a field formatter, - // which we want to do if possible. - contents = contentsIsDefined ? column.formatter.convert(contents, 'html') : ''; - - if (_.isObject(contents)) { - if (contents.attr) { - $cellContent.attr(contents.attr); - } - - if (contents.class) { - $cellContent.addClass(contents.class); - } - - if (contents.scope) { - $cellContent = $compile($cellContent.prepend(contents.markup))(contents.scope); - } else { - $cellContent.prepend(contents.markup); - } - - if (contents.attr) { - $cellContent.attr(contents.attr); - } - } else { - if (contents === '') { - $cellContent.prepend(' '); - } else { - $cellContent.prepend(contents); - } - } - - $tr.append($cell); - } - - $scope.$watchMulti([attr.osdRows, attr.osdRowsMin], function (vals) { - let rows = vals[0]; - const min = vals[1]; - - $el.empty(); - - if (!Array.isArray(rows)) rows = []; - - if (isFinite(min) && rows.length < min) { - // clone the rows so that we can add elements to it without upsetting the original - rows = _.clone(rows); - // crate the empty row which will be pushed into the row list over and over - const emptyRow = {}; - // push as many empty rows into the row array as needed - _.times(min - rows.length, function () { - rows.push(emptyRow); - }); - } - - rows.forEach(function (row) { - const $tr = $(document.createElement('tr')).appendTo($el); - $scope.columns.forEach((column) => { - const value = row[column.id]; - addCell($tr, value, column, row); - }); - }); - }); - }, - }; -} diff --git a/src/plugins/vis_type_table/public/paginated_table/table_cell_filter.html b/src/plugins/vis_type_table/public/paginated_table/table_cell_filter.html deleted file mode 100644 index a9185884dae5..000000000000 --- a/src/plugins/vis_type_table/public/paginated_table/table_cell_filter.html +++ /dev/null @@ -1,23 +0,0 @@ - -
- - - - - -
- diff --git a/src/plugins/vis_type_table/public/plugin.ts b/src/plugins/vis_type_table/public/plugin.ts index 8c01fee8841e..0582ebbedeb0 100644 --- a/src/plugins/vis_type_table/public/plugin.ts +++ b/src/plugins/vis_type_table/public/plugin.ts @@ -1,31 +1,6 @@ /* + * Copyright OpenSearch Contributors * 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 { @@ -40,45 +15,38 @@ import { VisualizationsSetup } from '../../visualizations/public'; import { createTableVisFn } from './table_vis_fn'; import { getTableVisTypeDefinition } from './table_vis_type'; import { DataPublicPluginStart } from '../../data/public'; -import { setFormatService, setOpenSearchDashboardsLegacy } from './services'; -import { OpenSearchDashboardsLegacyStart } from '../../opensearch_dashboards_legacy/public'; - -/** @internal */ -export interface TablePluginSetupDependencies { +import { setFormatService } from './services'; +import { ConfigSchema } from '../config'; +import { getTableVisRenderer } from './table_vis_renderer'; +export interface TableVisPluginSetupDependencies { expressions: ReturnType; visualizations: VisualizationsSetup; } - -/** @internal */ -export interface TablePluginStartDependencies { +export interface TableVisPluginStartDependencies { data: DataPublicPluginStart; - opensearchDashboardsLegacy: OpenSearchDashboardsLegacyStart; } -/** @internal */ -export class TableVisPlugin implements Plugin, void> { - initializerContext: PluginInitializerContext; - createBaseVisualization: any; - - constructor(initializerContext: PluginInitializerContext) { +const setupTableVis = async ( + core: CoreSetup, + { expressions, visualizations }: TableVisPluginSetupDependencies +) => { + const [coreStart] = await core.getStartServices(); + expressions.registerFunction(createTableVisFn); + expressions.registerRenderer(getTableVisRenderer(coreStart)); + visualizations.createBaseVisualization(getTableVisTypeDefinition()); +}; +export class TableVisPlugin implements Plugin { + initializerContext: PluginInitializerContext; + + constructor(initializerContext: PluginInitializerContext) { this.initializerContext = initializerContext; } - public async setup( - core: CoreSetup, - { expressions, visualizations }: TablePluginSetupDependencies - ) { - expressions.registerFunction(createTableVisFn); - visualizations.createBaseVisualization( - getTableVisTypeDefinition(core, this.initializerContext) - ); + public async setup(core: CoreSetup, dependencies: TableVisPluginSetupDependencies) { + setupTableVis(core, dependencies); } - public start( - core: CoreStart, - { data, opensearchDashboardsLegacy }: TablePluginStartDependencies - ) { + public start(core: CoreStart, { data }: TableVisPluginStartDependencies) { setFormatService(data.fieldFormats); - setOpenSearchDashboardsLegacy(opensearchDashboardsLegacy); } } diff --git a/src/plugins/vis_type_table/public/services.ts b/src/plugins/vis_type_table/public/services.ts index 4fb56f6bfbdb..f8ca4b574307 100644 --- a/src/plugins/vis_type_table/public/services.ts +++ b/src/plugins/vis_type_table/public/services.ts @@ -1,41 +1,11 @@ /* + * Copyright OpenSearch Contributors * 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 { createGetterSetter } from '../../opensearch_dashboards_utils/public'; import { DataPublicPluginStart } from '../../data/public'; -import { OpenSearchDashboardsLegacyStart } from '../../opensearch_dashboards_legacy/public'; export const [getFormatService, setFormatService] = createGetterSetter< DataPublicPluginStart['fieldFormats'] >('table data.fieldFormats'); - -export const [getOpenSearchDashboardsLegacy, setOpenSearchDashboardsLegacy] = createGetterSetter< - OpenSearchDashboardsLegacyStart ->('table opensearchDashboardsLegacy'); diff --git a/src/plugins/vis_type_table/public/table_vis.html b/src/plugins/vis_type_table/public/table_vis.html deleted file mode 100644 index 169b53390fe3..000000000000 --- a/src/plugins/vis_type_table/public/table_vis.html +++ /dev/null @@ -1,29 +0,0 @@ -
-
-
- - -
-
- -

-

-
-
- -
- - -
-
diff --git a/src/plugins/vis_type_table/public/table_vis_controller.js b/src/plugins/vis_type_table/public/table_vis_controller.js deleted file mode 100644 index 9fa71534903d..000000000000 --- a/src/plugins/vis_type_table/public/table_vis_controller.js +++ /dev/null @@ -1,67 +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 { assign } from 'lodash'; - -export function TableVisController($scope) { - const uiStateSort = $scope.uiState ? $scope.uiState.get('vis.params.sort') : {}; - assign($scope.visParams.sort, uiStateSort); - - $scope.sort = $scope.visParams.sort; - $scope.$watchCollection('sort', function (newSort) { - $scope.uiState.set('vis.params.sort', newSort); - }); - - /** - * Recreate the entire table when: - * - the underlying data changes (opensearchResponse) - * - one of the view options changes (vis.params) - */ - $scope.$watch('renderComplete', function () { - let tableGroups = ($scope.tableGroups = null); - let hasSomeRows = ($scope.hasSomeRows = null); - - if ($scope.opensearchResponse) { - tableGroups = $scope.opensearchResponse; - - hasSomeRows = tableGroups.tables.some(function haveRows(table) { - if (table.tables) return table.tables.some(haveRows); - return table.rows.length > 0; - }); - } - - $scope.hasSomeRows = hasSomeRows; - if (hasSomeRows) { - $scope.dimensions = $scope.visParams.dimensions; - $scope.tableGroups = tableGroups; - } - $scope.renderComplete(); - }); -} diff --git a/src/plugins/vis_type_table/public/table_vis_controller.test.ts b/src/plugins/vis_type_table/public/table_vis_controller.test.ts deleted file mode 100644 index db12e2b51426..000000000000 --- a/src/plugins/vis_type_table/public/table_vis_controller.test.ts +++ /dev/null @@ -1,272 +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 angular, { IRootScopeService, IScope, ICompileService } from 'angular'; -import 'angular-mocks'; -import 'angular-sanitize'; -import $ from 'jquery'; - -import { getAngularModule } from './get_inner_angular'; -import { initTableVisLegacyModule } from './table_vis_legacy_module'; -import { getTableVisTypeDefinition } from './table_vis_type'; -import { Vis } from '../../visualizations/public'; -import { stubFields } from '../../data/public/stubs'; -import { tableVisResponseHandler } from './table_vis_response_handler'; -import { coreMock } from '../../../core/public/mocks'; -import { IAggConfig, search } from '../../data/public'; -import { getStubIndexPattern } from '../../data/public/test_utils'; -// TODO: remove linting disable -import { searchServiceMock } from '../../data/public/search/mocks'; - -const { createAggConfigs } = searchServiceMock.createStartContract().aggs; - -const { tabifyAggResponse } = search; - -jest.mock('../../opensearch_dashboards_legacy/public/angular/angular_config', () => ({ - configureAppAngularModule: () => {}, -})); - -interface TableVisScope extends IScope { - [key: string]: any; -} - -const oneRangeBucket = { - hits: { - total: 6039, - max_score: 0, - hits: [], - }, - aggregations: { - agg_2: { - buckets: { - '0.0-1000.0': { - from: 0, - from_as_string: '0.0', - to: 1000, - to_as_string: '1000.0', - doc_count: 606, - }, - '1000.0-2000.0': { - from: 1000, - from_as_string: '1000.0', - to: 2000, - to_as_string: '2000.0', - doc_count: 298, - }, - }, - }, - }, -}; - -describe('Table Vis - Controller', () => { - let $rootScope: IRootScopeService & { [key: string]: any }; - let $compile: ICompileService; - let $scope: TableVisScope; - let $el: JQuery; - let tableAggResponse: any; - let tabifiedResponse: any; - let stubIndexPattern: any; - - const initLocalAngular = () => { - const tableVisModule = getAngularModule( - 'opensearch-dashboards/table_vis', - coreMock.createStart(), - coreMock.createPluginInitializerContext() - ); - initTableVisLegacyModule(tableVisModule); - }; - - beforeEach(initLocalAngular); - beforeEach(angular.mock.module('opensearch-dashboards/table_vis')); - - beforeEach( - angular.mock.inject((_$rootScope_: IRootScopeService, _$compile_: ICompileService) => { - $rootScope = _$rootScope_; - $compile = _$compile_; - tableAggResponse = tableVisResponseHandler; - }) - ); - - beforeEach(() => { - stubIndexPattern = getStubIndexPattern( - 'logstash-*', - (cfg: any) => cfg, - 'time', - stubFields, - coreMock.createSetup() - ); - }); - const tableVisTypeDefinition = getTableVisTypeDefinition( - coreMock.createSetup(), - coreMock.createPluginInitializerContext() - ); - - function getRangeVis(params?: object) { - return ({ - type: tableVisTypeDefinition, - params: Object.assign({}, tableVisTypeDefinition.visConfig?.defaults, params), - data: { - aggs: createAggConfigs(stubIndexPattern, [ - { type: 'count', schema: 'metric' }, - { - type: 'range', - schema: 'bucket', - params: { - field: 'bytes', - ranges: [ - { from: 0, to: 1000 }, - { from: 1000, to: 2000 }, - ], - }, - }, - ]), - }, - } as unknown) as Vis; - } - - const dimensions = { - buckets: [ - { - accessor: 0, - }, - ], - metrics: [ - { - accessor: 1, - format: { id: 'range' }, - }, - ], - }; - - // basically a parameterized beforeEach - function initController(vis: Vis) { - vis.data.aggs!.aggs.forEach((agg: IAggConfig, i: number) => { - agg.id = 'agg_' + (i + 1); - }); - - tabifiedResponse = tabifyAggResponse(vis.data.aggs!, oneRangeBucket); - $rootScope.vis = vis; - $rootScope.visParams = vis.params; - $rootScope.uiState = { - get: jest.fn(), - set: jest.fn(), - }; - $rootScope.renderComplete = () => {}; - $rootScope.newScope = (scope: TableVisScope) => { - $scope = scope; - }; - - $el = $('
') - .attr('ng-controller', 'OsdTableVisController') - .attr('ng-init', 'newScope(this)'); - - $compile($el)($rootScope); - } - - // put a response into the controller - function attachOpenSearchResponseToScope(resp: object) { - $rootScope.opensearchResponse = resp; - $rootScope.$apply(); - } - - // remove the response from the controller - function removeOpenSearchResponseFromScope() { - delete $rootScope.opensearchResponse; - $rootScope.renderComplete = () => {}; - $rootScope.$apply(); - } - - test('exposes #tableGroups and #hasSomeRows when a response is attached to scope', async () => { - const vis: Vis = getRangeVis(); - initController(vis); - - expect(!$scope.tableGroups).toBeTruthy(); - expect(!$scope.hasSomeRows).toBeTruthy(); - - attachOpenSearchResponseToScope(await tableAggResponse(tabifiedResponse, dimensions)); - - expect($scope.hasSomeRows).toBeTruthy(); - expect($scope.tableGroups.tables).toBeDefined(); - expect($scope.tableGroups.tables.length).toBe(1); - expect($scope.tableGroups.tables[0].columns.length).toBe(2); - expect($scope.tableGroups.tables[0].rows.length).toBe(2); - }); - - test('clears #tableGroups and #hasSomeRows when the response is removed', async () => { - const vis = getRangeVis(); - initController(vis); - - attachOpenSearchResponseToScope(await tableAggResponse(tabifiedResponse, dimensions)); - removeOpenSearchResponseFromScope(); - - expect(!$scope.hasSomeRows).toBeTruthy(); - expect(!$scope.tableGroups).toBeTruthy(); - }); - - test('sets the sort on the scope when it is passed as a vis param', async () => { - const sortObj = { - columnIndex: 1, - direction: 'asc', - }; - const vis = getRangeVis({ sort: sortObj }); - initController(vis); - - attachOpenSearchResponseToScope(await tableAggResponse(tabifiedResponse, dimensions)); - - expect($scope.sort.columnIndex).toEqual(sortObj.columnIndex); - expect($scope.sort.direction).toEqual(sortObj.direction); - }); - - test('sets #hasSomeRows properly if the table group is empty', async () => { - const vis = getRangeVis(); - initController(vis); - - tabifiedResponse.rows = []; - - attachOpenSearchResponseToScope(await tableAggResponse(tabifiedResponse, dimensions)); - - expect($scope.hasSomeRows).toBeFalsy(); - expect(!$scope.tableGroups).toBeTruthy(); - }); - - test('passes partialRows:true to tabify based on the vis params', () => { - const vis = getRangeVis({ showPartialRows: true }); - initController(vis); - - expect((vis.type.hierarchicalData as Function)(vis)).toEqual(true); - }); - - test('passes partialRows:false to tabify based on the vis params', () => { - const vis = getRangeVis({ showPartialRows: false }); - initController(vis); - - expect((vis.type.hierarchicalData as Function)(vis)).toEqual(false); - }); -}); diff --git a/src/plugins/vis_type_table/public/table_vis_fn.test.ts b/src/plugins/vis_type_table/public/table_vis_fn.test.ts deleted file mode 100644 index f7723456b757..000000000000 --- a/src/plugins/vis_type_table/public/table_vis_fn.test.ts +++ /dev/null @@ -1,89 +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 { createTableVisFn } from './table_vis_fn'; -import { tableVisResponseHandler } from './table_vis_response_handler'; - -import { functionWrapper } from '../../expressions/common/expression_functions/specs/tests/utils'; - -jest.mock('./table_vis_response_handler', () => ({ - tableVisResponseHandler: jest.fn().mockReturnValue({ - tables: [{ columns: [], rows: [] }], - }), -})); - -describe('interpreter/functions#table', () => { - const fn = functionWrapper(createTableVisFn()); - const context = { - type: 'opensearch_dashboards_datatable', - rows: [{ 'col-0-1': 0 }], - columns: [{ id: 'col-0-1', name: 'Count' }], - }; - const visConfig = { - title: 'My Chart title', - perPage: 10, - showPartialRows: false, - showMetricsAtAllLevels: false, - sort: { - columnIndex: null, - direction: null, - }, - showTotal: false, - totalFunc: 'sum', - dimensions: { - metrics: [ - { - accessor: 0, - format: { - id: 'number', - }, - params: {}, - aggType: 'count', - }, - ], - buckets: [], - }, - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('returns an object with the correct structure', async () => { - const actual = await fn(context, { visConfig: JSON.stringify(visConfig) }, undefined); - expect(actual).toMatchSnapshot(); - }); - - it('calls response handler with correct values', async () => { - await fn(context, { visConfig: JSON.stringify(visConfig) }, undefined); - expect(tableVisResponseHandler).toHaveBeenCalledTimes(1); - expect(tableVisResponseHandler).toHaveBeenCalledWith(context, visConfig.dimensions); - }); -}); diff --git a/src/plugins/vis_type_table/public/table_vis_fn.ts b/src/plugins/vis_type_table/public/table_vis_fn.ts index daf76580b59f..3e3bd38dba47 100644 --- a/src/plugins/vis_type_table/public/table_vis_fn.ts +++ b/src/plugins/vis_type_table/public/table_vis_fn.ts @@ -1,31 +1,6 @@ /* + * Copyright OpenSearch Contributors * 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 { i18n } from '@osd/i18n'; @@ -35,7 +10,7 @@ import { OpenSearchDashboardsDatatable, Render, } from '../../expressions/public'; -import { VisRenderValue } from '../../visualizations/public'; +import { TableVisConfig } from './types'; export type Input = OpenSearchDashboardsDatatable; @@ -43,17 +18,20 @@ interface Arguments { visConfig: string | null; } -interface RenderValue extends VisRenderValue { +export interface TableVisRenderValue { visData: TableContext; visType: 'table'; + visConfig: TableVisConfig; } -export const createTableVisFn = (): ExpressionFunctionDefinition< +export type TableVisExpressionFunctionDefinition = ExpressionFunctionDefinition< 'opensearch_dashboards_table', Input, Arguments, - Render -> => ({ + Render +>; + +export const createTableVisFn = (): TableVisExpressionFunctionDefinition => ({ name: 'opensearch_dashboards_table', type: 'render', inputTypes: ['opensearch_dashboards_datatable'], @@ -69,18 +47,18 @@ export const createTableVisFn = (): ExpressionFunctionDefinition< }, fn(input, args) { const visConfig = args.visConfig && JSON.parse(args.visConfig); - const convertedData = tableVisResponseHandler(input, visConfig.dimensions); + const convertedData = tableVisResponseHandler(input, visConfig); return { type: 'render', - as: 'visualization', + as: 'table_vis', value: { visData: convertedData, visType: 'table', visConfig, - params: { - listenOnChange: true, - }, + }, + params: { + listenOnChange: true, }, }; }, diff --git a/src/plugins/vis_type_table/public/table_vis_legacy_module.ts b/src/plugins/vis_type_table/public/table_vis_legacy_module.ts deleted file mode 100644 index 49eed3494f92..000000000000 --- a/src/plugins/vis_type_table/public/table_vis_legacy_module.ts +++ /dev/null @@ -1,52 +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 { IModule } from 'angular'; - -// @ts-ignore -import { TableVisController } from './table_vis_controller.js'; -// @ts-ignore -import { OsdAggTable } from './agg_table/agg_table'; -// @ts-ignore -import { OsdAggTableGroup } from './agg_table/agg_table_group'; -// @ts-ignore -import { OsdRows } from './paginated_table/rows'; -// @ts-ignore -import { PaginatedTable } from './paginated_table/paginated_table'; - -/** @internal */ -export const initTableVisLegacyModule = (angularIns: IModule): void => { - angularIns - .controller('OsdTableVisController', TableVisController) - .directive('osdAggTable', OsdAggTable) - .directive('osdAggTableGroup', OsdAggTableGroup) - .directive('osdRows', OsdRows) - .directive('paginatedTable', PaginatedTable); -}; diff --git a/src/plugins/vis_type_table_new/public/table_vis_renderer.tsx b/src/plugins/vis_type_table/public/table_vis_renderer.tsx similarity index 100% rename from src/plugins/vis_type_table_new/public/table_vis_renderer.tsx rename to src/plugins/vis_type_table/public/table_vis_renderer.tsx diff --git a/src/plugins/vis_type_table/public/table_vis_response_handler.ts b/src/plugins/vis_type_table/public/table_vis_response_handler.ts index 78b2306e744b..b1d41edfff8b 100644 --- a/src/plugins/vis_type_table/public/table_vis_response_handler.ts +++ b/src/plugins/vis_type_table/public/table_vis_response_handler.ts @@ -28,19 +28,17 @@ * under the License. */ -import { Required } from '@osd/utility-types'; - import { getFormatService } from './services'; -import { Input } from './table_vis_fn'; +import { OpenSearchDashboardsDatatable } from '../../expressions/public'; +import { TableVisConfig } from './types'; -export interface TableContext { - tables: Array; - direction?: 'row' | 'column'; +export interface Table { + columns: OpenSearchDashboardsDatatable['columns']; + rows: OpenSearchDashboardsDatatable['rows']; } export interface TableGroup { - $parent: TableContext; - table: Input; + table: OpenSearchDashboardsDatatable; tables: Table[]; title: string; name: string; @@ -49,61 +47,66 @@ export interface TableGroup { row: number; } -export interface Table { - $parent?: TableGroup; - columns: Input['columns']; - rows: Input['rows']; +export interface TableContext { + table?: Table; + tableGroups: TableGroup[]; + direction?: 'row' | 'column'; } -export function tableVisResponseHandler(table: Input, dimensions: any): TableContext { - const converted: TableContext = { - tables: [], - }; +export function tableVisResponseHandler( + input: OpenSearchDashboardsDatatable, + config: TableVisConfig +): TableContext { + let table: Table | undefined; + const tableGroups: TableGroup[] = []; + let direction: TableContext['direction']; - const split = dimensions.splitColumn || dimensions.splitRow; + const split = config.splitColumn || config.splitRow; if (split) { - converted.direction = dimensions.splitRow ? 'row' : 'column'; + direction = config.splitRow ? 'row' : 'column'; const splitColumnIndex = split[0].accessor; const splitColumnFormatter = getFormatService().deserialize(split[0].format); - const splitColumn = table.columns[splitColumnIndex]; - const splitMap = {}; + const splitColumn = input.columns[splitColumnIndex]; + const splitMap: { [key: string]: number } = {}; let splitIndex = 0; - table.rows.forEach((row, rowIndex) => { + input.rows.forEach((row, rowIndex) => { const splitValue: any = row[splitColumn.id]; if (!splitMap.hasOwnProperty(splitValue as any)) { (splitMap as any)[splitValue] = splitIndex++; - const tableGroup: Required = { - $parent: converted, + const tableGroup: TableGroup = { title: `${splitColumnFormatter.convert(splitValue)}: ${splitColumn.name}`, name: splitColumn.name, key: splitValue, column: splitColumnIndex, row: rowIndex, - table, + table: input, tables: [], }; tableGroup.tables.push({ - $parent: tableGroup, - columns: table.columns, + columns: input.columns, rows: [], }); - converted.tables.push(tableGroup); + tableGroups.push(tableGroup); } const tableIndex = (splitMap as any)[splitValue]; - (converted.tables[tableIndex] as any).tables[0].rows.push(row); + (tableGroups[tableIndex] as any).tables[0].rows.push(row); }); } else { - converted.tables.push({ - columns: table.columns, - rows: table.rows, - }); + table = { + columns: input.columns, + rows: input.rows, + }; } - return converted; + return { + table, + tableGroups, + direction, + }; } diff --git a/src/plugins/vis_type_table/public/table_vis_type.ts b/src/plugins/vis_type_table/public/table_vis_type.ts index df1495a3d06b..0c27e7a8af0b 100644 --- a/src/plugins/vis_type_table/public/table_vis_type.ts +++ b/src/plugins/vis_type_table/public/table_vis_type.ts @@ -28,91 +28,78 @@ * under the License. */ -import { CoreSetup, PluginInitializerContext } from 'opensearch-dashboards/public'; import { i18n } from '@osd/i18n'; import { AggGroupNames } from '../../data/public'; import { Schemas } from '../../vis_default_editor/public'; import { BaseVisTypeOptions } from '../../visualizations/public'; import { tableVisResponseHandler } from './table_vis_response_handler'; -// @ts-ignore -import tableVisTemplate from './table_vis.html'; +import { toExpressionAst } from './to_ast'; +import { VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public'; import { TableOptions } from './components/table_vis_options_lazy'; -import { getTableVisualizationControllerClass } from './vis_controller'; -import { VIS_EVENT_TO_TRIGGER } from '../../visualizations/public'; -export function getTableVisTypeDefinition( - core: CoreSetup, - context: PluginInitializerContext -): BaseVisTypeOptions { - return { - name: 'table', - title: i18n.translate('visTypeTable.tableVisTitle', { - defaultMessage: 'Data Table', - }), - icon: 'visTable', - description: i18n.translate('visTypeTable.tableVisDescription', { - defaultMessage: 'Display values in a table', - }), - visualization: getTableVisualizationControllerClass(core, context), - getSupportedTriggers: () => { - return [VIS_EVENT_TO_TRIGGER.filter]; +export const getTableVisTypeDefinition = (): BaseVisTypeOptions => ({ + name: 'table', + title: i18n.translate('visTypeTable.tableVisTitle', { + defaultMessage: 'Data Table', + }), + icon: 'visTable', + description: i18n.translate('visTypeTable.tableVisDescription', { + defaultMessage: 'Display values in a table', + }), + toExpressionAst, + visConfig: { + defaults: { + perPage: 10, + showPartialRows: false, + showMetricsAtAllLevels: false, + showTotal: false, + totalFunc: 'sum', + percentageCol: '', }, - visConfig: { - defaults: { - perPage: 10, - showPartialRows: false, - showMetricsAtAllLevels: false, - sort: { - columnIndex: null, - direction: null, - }, - showTotal: false, - totalFunc: 'sum', - percentageCol: '', - }, - template: tableVisTemplate, - }, - editorConfig: { - optionsTemplate: TableOptions, - schemas: new Schemas([ - { - group: AggGroupNames.Metrics, - name: 'metric', - title: i18n.translate('visTypeTable.tableVisEditorConfig.schemas.metricTitle', { - defaultMessage: 'Metric', - }), - aggFilter: ['!geo_centroid', '!geo_bounds'], - aggSettings: { - top_hits: { - allowStrings: true, - }, + }, + editorConfig: { + optionsTemplate: TableOptions, + schemas: new Schemas([ + { + group: AggGroupNames.Metrics, + name: 'metric', + title: i18n.translate('visTypeTable.tableVisEditorConfig.schemas.metricTitle', { + defaultMessage: 'Metric', + }), + aggFilter: ['!geo_centroid', '!geo_bounds'], + aggSettings: { + top_hits: { + allowStrings: true, }, - min: 1, - defaults: [{ type: 'count', schema: 'metric' }], - }, - { - group: AggGroupNames.Buckets, - name: 'bucket', - title: i18n.translate('visTypeTable.tableVisEditorConfig.schemas.bucketTitle', { - defaultMessage: 'Split rows', - }), - aggFilter: ['!filter'], - }, - { - group: AggGroupNames.Buckets, - name: 'split', - title: i18n.translate('visTypeTable.tableVisEditorConfig.schemas.splitTitle', { - defaultMessage: 'Split table', - }), - min: 0, - max: 1, - aggFilter: ['!filter'], }, - ]), - }, - responseHandler: tableVisResponseHandler, - hierarchicalData: (vis) => { - return Boolean(vis.params.showPartialRows || vis.params.showMetricsAtAllLevels); - }, - }; -} + min: 1, + defaults: [{ type: 'count', schema: 'metric' }], + }, + { + group: AggGroupNames.Buckets, + name: 'bucket', + title: i18n.translate('visTypeTable.tableVisEditorConfig.schemas.bucketTitle', { + defaultMessage: 'Split rows', + }), + aggFilter: ['!filter'], + }, + { + group: AggGroupNames.Buckets, + name: 'split', + title: i18n.translate('visTypeTable.tableVisEditorConfig.schemas.splitTitle', { + defaultMessage: 'Split table', + }), + min: 0, + max: 1, + aggFilter: ['!filter'], + }, + ]), + }, + responseHandler: tableVisResponseHandler, + getSupportedTriggers: () => { + return [VIS_EVENT_TO_TRIGGER.filter]; + }, + hierarchicalData: (vis) => { + return Boolean(vis.params.showPartialRows || vis.params.showMetricsAtAllLevels); + }, +}); diff --git a/src/plugins/vis_type_table/public/to_ast.ts b/src/plugins/vis_type_table/public/to_ast.ts new file mode 100644 index 000000000000..549941062d51 --- /dev/null +++ b/src/plugins/vis_type_table/public/to_ast.ts @@ -0,0 +1,62 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { getVisSchemas, Vis } from '../../visualizations/public'; +import { buildExpression, buildExpressionFunction } from '../../expressions/public'; +import { TableVisExpressionFunctionDefinition } from './table_vis_fn'; +import { OpenSearchaggsExpressionFunctionDefinition } from '../../data/common/search/expressions'; + +export const toExpressionAst = (vis: Vis, params: any) => { + const opensearchaggs = buildExpressionFunction( + 'opensearchaggs', + { + index: vis.data.indexPattern!.id!, + metricsAtAllLevels: vis.isHierarchical(), + partialRows: vis.params.showPartialRows || false, + aggConfigs: JSON.stringify(vis.data.aggs!.aggs), + includeFormatHints: false, + } + ); + + const schemas = getVisSchemas(vis, params); + // remove duplicate metrics + const metrics = + schemas.bucket && vis.params.showPartialRows && !vis.params.showMetricsAtAllLevels + ? schemas.metric.slice(-1 * (schemas.metric.length / schemas.bucket.length)) + : schemas.metric; + + const tableData = { + title: vis.title, + metrics, + buckets: schemas.bucket || [], + splitRow: schemas.split_row, + splitColumn: schemas.split_column, + }; + + const tableConfig = { + perPage: vis.params.perPage, + percentageCol: vis.params.percentageCol, + showPartialRows: vis.params.showPartialRows, + showMetricsAtAllLevels: vis.params.showMetricsAtAllLevels, + showTotal: vis.params.showTotal, + totalFunc: vis.params.totalFunc, + }; + + const visConfig = { + ...tableConfig, + ...tableData, + }; + + const tableVis = buildExpressionFunction( + 'opensearch_dashboards_table', + { + visConfig: JSON.stringify(visConfig), + } + ); + + const ast = buildExpression([opensearchaggs, tableVis]); + + return ast.toAst(); +}; diff --git a/src/plugins/vis_type_table/public/types.ts b/src/plugins/vis_type_table/public/types.ts index c780ef3b5db9..f77abe78a1fa 100644 --- a/src/plugins/vis_type_table/public/types.ts +++ b/src/plugins/vis_type_table/public/types.ts @@ -28,7 +28,8 @@ * under the License. */ -import { SchemaConfig } from '../../visualizations/public'; +import { SchemaConfig } from 'src/plugins/visualizations/public'; +import { IFieldFormat } from 'src/plugins/data/public'; export enum AggTypes { SUM = 'sum', @@ -38,22 +39,46 @@ export enum AggTypes { COUNT = 'count', } -export interface Dimensions { - buckets: SchemaConfig[]; +export interface TableVisConfig extends TableVisParams { + title: string; metrics: SchemaConfig[]; + buckets: SchemaConfig[]; + splitRow?: SchemaConfig[]; + splitColumn?: SchemaConfig[]; } export interface TableVisParams { - type: 'table'; perPage: number | ''; showPartialRows: boolean; showMetricsAtAllLevels: boolean; - sort: { - columnIndex: number | null; - direction: string | null; - }; showTotal: boolean; totalFunc: AggTypes; percentageCol: string; - dimensions: Dimensions; +} + +export interface FormattedColumn { + id: string; + title: string; + formatter: IFieldFormat; + filterable: boolean; + formattedTotal?: string | number; + sumTotal?: number; + total?: number; +} + +export interface ColumnWidth { + colIndex: number; + width: number; +} + +export interface SortColumn { + colIndex: number | null; + direction: 'asc' | 'desc' | null; +} + +export interface TableUiState { + sort: SortColumn; + setSort: (sort: SortColumn) => void; + width: ColumnWidth[]; + setWidth: (columnsWidth: ColumnWidth[]) => void; } diff --git a/src/plugins/vis_type_table_new/public/utils/convert_to_csv_data.ts b/src/plugins/vis_type_table/public/utils/convert_to_csv_data.ts similarity index 100% rename from src/plugins/vis_type_table_new/public/utils/convert_to_csv_data.ts rename to src/plugins/vis_type_table/public/utils/convert_to_csv_data.ts diff --git a/src/plugins/vis_type_table/public/utils/convert_to_formatted_data.ts b/src/plugins/vis_type_table/public/utils/convert_to_formatted_data.ts new file mode 100644 index 000000000000..2ab67e3b0a67 --- /dev/null +++ b/src/plugins/vis_type_table/public/utils/convert_to_formatted_data.ts @@ -0,0 +1,179 @@ +/* + * 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 { i18n } from '@osd/i18n'; +import { chain } from 'lodash'; +import { OpenSearchDashboardsDatatableRow } from 'src/plugins/expressions'; +import { Table } from '../table_vis_response_handler'; +import { AggTypes, TableVisConfig } from '../types'; +import { getFormatService } from '../services'; +import { FormattedColumn } from '../types'; + +function insert(arr: FormattedColumn[], index: number, col: FormattedColumn) { + const newArray = [...arr]; + newArray.splice(index + 1, 0, col); + return newArray; +} + +/** + * @param columns - the formatted columns that will be displayed + * @param title - the title of the column to add to + * @param rows - the row data for the columns + * @param insertAtIndex - the index to insert the percentage column at + * @returns cols and rows for the table to render now included percentage column(s) + */ +function addPercentageCol( + columns: FormattedColumn[], + title: string, + rows: Table['rows'], + insertAtIndex: number +) { + const { id, sumTotal } = columns[insertAtIndex]; + const newId = `${id}-percents`; + const formatter = getFormatService().deserialize({ id: 'percent' }); + const i18nTitle = i18n.translate('visTypeTable.params.percentageTableColumnName', { + defaultMessage: '{title} percentages', + values: { title }, + }); + const newCols = insert(columns, insertAtIndex, { + title: i18nTitle, + id: newId, + formatter, + filterable: false, + }); + const newRows = rows.map((row) => ({ + [newId]: (row[id] as number) / (sumTotal as number), + ...row, + })); + + return { cols: newCols, rows: newRows }; +} + +export interface FormattedDataProps { + formattedRows: OpenSearchDashboardsDatatableRow[]; + formattedColumns: FormattedColumn[]; +} + +export const convertToFormattedData = ( + table: Table, + visConfig: TableVisConfig +): FormattedDataProps => { + const { buckets, metrics } = visConfig; + let formattedRows: OpenSearchDashboardsDatatableRow[] = table.rows; + let formattedColumns: FormattedColumn[] = table.columns + .map(function (col, i) { + const isBucket = buckets.find((bucket) => bucket.accessor === i); + const dimension = isBucket || metrics.find((metric) => metric.accessor === i); + + if (!dimension) return undefined; + + const formatter = getFormatService().deserialize(dimension.format); + + const formattedColumn: FormattedColumn = { + id: col.id, + title: col.name, + formatter, + filterable: !!isBucket, + }; + + const isDate = dimension?.format?.id === 'date' || dimension?.format?.params?.id === 'date'; + const allowsNumericalAggregations = formatter?.allowsNumericalAggregations; + + if (allowsNumericalAggregations || isDate || visConfig.totalFunc === AggTypes.COUNT) { + const sum = table.rows.reduce((prev, curr) => { + // some metrics return undefined for some of the values + // derivative is an example of this as it returns undefined in the first row + if (curr[col.id] === undefined) return prev; + return prev + (curr[col.id] as number); + }, 0); + + formattedColumn.sumTotal = sum; + switch (visConfig.totalFunc) { + case AggTypes.SUM: { + if (!isDate) { + formattedColumn.formattedTotal = formatter?.convert(sum); + formattedColumn.total = formattedColumn.sumTotal; + } + break; + } + case AggTypes.AVG: { + if (!isDate) { + const total = sum / table.rows.length; + formattedColumn.formattedTotal = formatter?.convert(total); + formattedColumn.total = total; + } + break; + } + case AggTypes.MIN: { + const total = chain(table.rows).map(col.id).min().value() as number; + formattedColumn.formattedTotal = formatter?.convert(total); + formattedColumn.total = total; + break; + } + case AggTypes.MAX: { + const total = chain(table.rows).map(col.id).max().value() as number; + formattedColumn.formattedTotal = formatter?.convert(total); + formattedColumn.total = total; + break; + } + case 'count': { + const total = table.rows.length; + formattedColumn.formattedTotal = total; + formattedColumn.total = total; + break; + } + default: + break; + } + } + + return formattedColumn; + }) + .filter((column): column is FormattedColumn => !!column); + + if (visConfig.percentageCol) { + const insertAtIndex = formattedColumns.findIndex( + (col) => col.title === visConfig.percentageCol + ); + + // column to show percentage was removed + if (insertAtIndex < 0) return { formattedRows, formattedColumns }; + + const { cols, rows } = addPercentageCol( + formattedColumns, + visConfig.percentageCol, + table.rows, + insertAtIndex + ); + formattedRows = rows; + formattedColumns = cols; + } + return { formattedRows, formattedColumns }; +}; diff --git a/src/plugins/vis_type_table_new/public/utils/index.ts b/src/plugins/vis_type_table/public/utils/index.ts similarity index 100% rename from src/plugins/vis_type_table_new/public/utils/index.ts rename to src/plugins/vis_type_table/public/utils/index.ts diff --git a/src/plugins/vis_type_table_new/public/utils/use_pagination.ts b/src/plugins/vis_type_table/public/utils/use_pagination.ts similarity index 100% rename from src/plugins/vis_type_table_new/public/utils/use_pagination.ts rename to src/plugins/vis_type_table/public/utils/use_pagination.ts diff --git a/src/plugins/vis_type_table/public/vis_controller.ts b/src/plugins/vis_type_table/public/vis_controller.ts deleted file mode 100644 index aa7ffb05110a..000000000000 --- a/src/plugins/vis_type_table/public/vis_controller.ts +++ /dev/null @@ -1,135 +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 { CoreSetup, PluginInitializerContext } from 'opensearch-dashboards/public'; -import angular, { IModule, auto, IRootScopeService, IScope, ICompileService } from 'angular'; -import $ from 'jquery'; - -import { VisParams, ExprVis } from '../../visualizations/public'; -import { getAngularModule } from './get_inner_angular'; -import { getOpenSearchDashboardsLegacy } from './services'; -import { initTableVisLegacyModule } from './table_vis_legacy_module'; - -const innerAngularName = 'opensearch-dashboards/table_vis'; - -export function getTableVisualizationControllerClass( - core: CoreSetup, - context: PluginInitializerContext -) { - return class TableVisualizationController { - private tableVisModule: IModule | undefined; - private injector: auto.IInjectorService | undefined; - el: JQuery; - vis: ExprVis; - $rootScope: IRootScopeService | null = null; - $scope: (IScope & { [key: string]: any }) | undefined; - $compile: ICompileService | undefined; - - constructor(domeElement: Element, vis: ExprVis) { - this.el = $(domeElement); - this.vis = vis; - } - - getInjector() { - if (!this.injector) { - const mountpoint = document.createElement('div'); - mountpoint.setAttribute('style', 'height: 100%; width: 100%;'); - this.injector = angular.bootstrap(mountpoint, [innerAngularName]); - this.el.append(mountpoint); - } - - return this.injector; - } - - async initLocalAngular() { - if (!this.tableVisModule) { - const [coreStart] = await core.getStartServices(); - this.tableVisModule = getAngularModule(innerAngularName, coreStart, context); - initTableVisLegacyModule(this.tableVisModule); - } - } - - async render(opensearchResponse: object, visParams: VisParams): Promise { - getOpenSearchDashboardsLegacy().loadFontAwesome(); - await this.initLocalAngular(); - - return new Promise(async (resolve, reject) => { - if (!this.$rootScope) { - const $injector = this.getInjector(); - this.$rootScope = $injector.get('$rootScope'); - this.$compile = $injector.get('$compile'); - } - const updateScope = () => { - if (!this.$scope) { - return; - } - - // How things get into this $scope? - // To inject variables into this $scope there's the following pipeline of stuff to check: - // - visualize_embeddable => that's what the editor creates to wrap this Angular component - // - build_pipeline => it serialize all the params into an Angular template compiled on the fly - // - table_vis_fn => unserialize the params and prepare them for the final React/Angular bridge - // - visualization_renderer => creates the wrapper component for this controller and passes the params - // - // In case some prop is missing check into the top of the chain if they are available and check - // the list above that it is passing through - this.$scope.vis = this.vis; - this.$scope.visState = { params: visParams, title: visParams.title }; - this.$scope.opensearchResponse = opensearchResponse; - - this.$scope.visParams = visParams; - this.$scope.renderComplete = resolve; - this.$scope.renderFailed = reject; - this.$scope.resize = Date.now(); - this.$scope.$apply(); - }; - - if (!this.$scope && this.$compile) { - this.$scope = this.$rootScope.$new(); - this.$scope.uiState = this.vis.getUiState(); - updateScope(); - this.el - .find('div') - .append(this.$compile(this.vis.type.visConfig?.template ?? '')(this.$scope)); - this.$scope.$apply(); - } else { - updateScope(); - } - }); - } - - destroy() { - if (this.$rootScope) { - this.$rootScope.$destroy(); - this.$rootScope = null; - } - } - }; -} diff --git a/src/plugins/vis_type_table_new/README.md b/src/plugins/vis_type_table_new/README.md deleted file mode 100644 index 06299ed963a2..000000000000 --- a/src/plugins/vis_type_table_new/README.md +++ /dev/null @@ -1 +0,0 @@ -Contains the data table visualization, that allows presenting data using a Datagrid component. diff --git a/src/plugins/vis_type_table_new/opensearch_dashboards.json b/src/plugins/vis_type_table_new/opensearch_dashboards.json deleted file mode 100644 index 598ca7581b83..000000000000 --- a/src/plugins/vis_type_table_new/opensearch_dashboards.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "id": "visTypeTableNew", - "version": "opensearchDashboards", - "server": false, - "ui": true, - "requiredPlugins": [ - "expressions", - "visualizations", - "data" - ], - "requiredBundles": [ - "opensearchDashboardsUtils", - "opensearchDashboardsReact", - "share" - ] -} diff --git a/src/plugins/vis_type_table_new/public/components/table_vis_grid_columns.tsx b/src/plugins/vis_type_table_new/public/components/table_vis_grid_columns.tsx deleted file mode 100644 index ba204ea6ae33..000000000000 --- a/src/plugins/vis_type_table_new/public/components/table_vis_grid_columns.tsx +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import React from 'react'; -import { i18n } from '@osd/i18n'; -import { EuiDataGridColumn, EuiDataGridColumnCellActionProps } from '@elastic/eui'; -import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; -import { OpenSearchDashboardsDatatableRow } from 'src/plugins/expressions'; -import { Table } from '../table_vis_response_handler'; -import { ColumnWidth, FormattedColumn } from '../types'; - -export const getDataGridColumns = ( - rows: OpenSearchDashboardsDatatableRow[], - cols: FormattedColumn[], - table: Table, - event: IInterpreterRenderHandlers['event'], - columnsWidth: ColumnWidth[] -) => { - const filterBucket = (rowIndex: number, columnIndex: number, negate: boolean) => { - const foramttedColumnId = cols[columnIndex].id; - const rawColumnIndex = table.columns.findIndex((col) => col.id === foramttedColumnId); - event({ - name: 'filterBucket', - data: { - data: [ - { - table: { - columns: table.columns, - rows, - }, - row: rowIndex, - column: rawColumnIndex, - }, - ], - negate, - }, - }); - }; - - return cols.map((col, colIndex) => { - // const cellActions = col.filterable - // ? [ - // ({ rowIndex, columnId, Component, closePopover }: EuiDataGridColumnCellActionProps) => { - // const filterValue = rows[rowIndex][columnId]; - // const filterContent = col.formatter?.convert(filterValue); - - // const filterForValueText = i18n.translate( - // 'visTypeTableNew.tableVisFilter.filterForValue', - // { - // defaultMessage: 'Filter for value', - // } - // ); - // const filterForValueLabel = i18n.translate( - // 'visTypeTableNew.tableVisFilter.filterForValueLabel', - // { - // defaultMessage: 'Filter for value: {filterContent}', - // values: { - // filterContent, - // }, - // } - // ); - - // return ( - // filterValue != null && ( - // { - // filterBucket(rowIndex, colIndex, false); - // closePopover(); - // }} - // iconType="plusInCircle" - // aria-label={filterForValueLabel} - // data-test-subj="filterForValue" - // > - // {filterForValueText} - // - // ) - // ); - // }, - // ({ rowIndex, columnId, Component, closePopover }: EuiDataGridColumnCellActionProps) => { - // const filterValue = rows[rowIndex][columnId]; - // const filterContent = col.formatter?.convert(filterValue); - - // const filterOutValueText = i18n.translate( - // 'visTypeTableNew.tableVisFilter.filterOutValue', - // { - // defaultMessage: 'Filter out value', - // } - // ); - // const filterOutValueLabel = i18n.translate( - // 'visTypeTableNew.tableVisFilter.filterOutValueLabel', - // { - // defaultMessage: 'Filter out value: {filterContent}', - // values: { - // filterContent, - // }, - // } - // ); - - // return ( - // filterValue != null && ( - // { - // filterBucket(rowIndex, colIndex, true); - // closePopover(); - // }} - // iconType="minusInCircle" - // aria-label={filterOutValueLabel} - // data-test-subj="filterOutValue" - // > - // {filterOutValueText} - // - // ) - // ); - // }, - // ] - // : undefined; - - const initialWidth = columnsWidth.find((c) => c.colIndex === colIndex); - - const dataGridColumn: EuiDataGridColumn = { - id: col.id, - display: col.title, - displayAsText: col.title, - actions: { - showHide: false, - showMoveLeft: false, - showMoveRight: false, - showSortAsc: { - label: i18n.translate('visTypeTableNew.tableVisSort.ascSortLabel', { - defaultMessage: 'Sort asc', - }), - }, - showSortDesc: { - label: i18n.translate('visTypeTableNew.tableVisSort.descSortLabel', { - defaultMessage: 'Sort desc', - }), - }, - }, - // cellActions, - }; - if (initialWidth) { - dataGridColumn.initialWidth = initialWidth.width; - } - return dataGridColumn; - }); -}; diff --git a/src/plugins/vis_type_table_new/public/index.ts b/src/plugins/vis_type_table_new/public/index.ts deleted file mode 100644 index 4ed30b71eeaa..000000000000 --- a/src/plugins/vis_type_table_new/public/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -// import { PluginInitializerContext } from 'opensearch-dashboards/public'; -import { TableVisPlugin as Plugin } from './plugin'; - -export function plugin() { - return new Plugin(); -} -/* Public Types */ -export { TableVisExpressionFunctionDefinition } from './table_vis_fn'; diff --git a/src/plugins/vis_type_table_new/public/plugin.ts b/src/plugins/vis_type_table_new/public/plugin.ts deleted file mode 100644 index 9cc96c3e9895..000000000000 --- a/src/plugins/vis_type_table_new/public/plugin.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { CoreSetup, CoreStart, Plugin } from 'opensearch-dashboards/public'; -import { Plugin as ExpressionsPublicPlugin } from '../../expressions/public'; - -import { createTableVisFn } from './table_vis_fn'; -import { DataPublicPluginStart } from '../../data/public'; -import { setFormatService } from './services'; -import { getTableVisRenderer } from './table_vis_renderer'; - -export interface TableVisPluginSetupDependencies { - expressions: ReturnType; -} - -export interface TableVisPluginStartDependencies { - data: DataPublicPluginStart; -} - -const setupTableVis = async (core: CoreSetup, { expressions }: TableVisPluginSetupDependencies) => { - const [coreStart] = await core.getStartServices(); - expressions.registerFunction(createTableVisFn); - expressions.registerRenderer(getTableVisRenderer(coreStart)); -}; - -export class TableVisPlugin implements Plugin { - public async setup(core: CoreSetup, dependencies: TableVisPluginSetupDependencies) { - setupTableVis(core, dependencies); - } - - public start(core: CoreStart, { data }: TableVisPluginStartDependencies) { - setFormatService(data.fieldFormats); - } -} diff --git a/src/plugins/vis_type_table_new/public/services.ts b/src/plugins/vis_type_table_new/public/services.ts deleted file mode 100644 index f8ca4b574307..000000000000 --- a/src/plugins/vis_type_table_new/public/services.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { createGetterSetter } from '../../opensearch_dashboards_utils/public'; -import { DataPublicPluginStart } from '../../data/public'; - -export const [getFormatService, setFormatService] = createGetterSetter< - DataPublicPluginStart['fieldFormats'] ->('table data.fieldFormats'); diff --git a/src/plugins/vis_type_table_new/public/table_vis_fn.ts b/src/plugins/vis_type_table_new/public/table_vis_fn.ts deleted file mode 100644 index ec9eafc344af..000000000000 --- a/src/plugins/vis_type_table_new/public/table_vis_fn.ts +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { i18n } from '@osd/i18n'; -import { tableVisResponseHandler, TableContext } from './table_vis_response_handler'; -import { - ExpressionFunctionDefinition, - OpenSearchDashboardsDatatable, - Render, -} from '../../expressions/public'; -import { TableVisConfig } from './types'; - -export type Input = OpenSearchDashboardsDatatable; - -interface Arguments { - visConfig: string | null; -} - -export interface TableVisRenderValue { - visData: TableContext; - visType: 'table'; - visConfig: TableVisConfig; -} - -export type TableVisExpressionFunctionDefinition = ExpressionFunctionDefinition< - 'opensearch_dashboards_table_new', - Input, - Arguments, - Render ->; - -export const createTableVisFn = (): TableVisExpressionFunctionDefinition => ({ - name: 'opensearch_dashboards_table_new', - type: 'render', - inputTypes: ['opensearch_dashboards_datatable'], - help: i18n.translate('visTypeTableNew.function.help', { - defaultMessage: 'Table visualization', - }), - args: { - visConfig: { - types: ['string', 'null'], - default: '"{}"', - help: '', - }, - }, - fn(input, args) { - const visConfig = args.visConfig && JSON.parse(args.visConfig); - const convertedData = tableVisResponseHandler(input, visConfig); - - return { - type: 'render', - as: 'table_vis', - value: { - visData: convertedData, - visType: 'table', - visConfig, - }, - params: { - listenOnChange: true, - }, - }; - }, -}); diff --git a/src/plugins/vis_type_table_new/public/table_vis_response_handler.ts b/src/plugins/vis_type_table_new/public/table_vis_response_handler.ts deleted file mode 100644 index b1d41edfff8b..000000000000 --- a/src/plugins/vis_type_table_new/public/table_vis_response_handler.ts +++ /dev/null @@ -1,112 +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 { getFormatService } from './services'; -import { OpenSearchDashboardsDatatable } from '../../expressions/public'; -import { TableVisConfig } from './types'; - -export interface Table { - columns: OpenSearchDashboardsDatatable['columns']; - rows: OpenSearchDashboardsDatatable['rows']; -} - -export interface TableGroup { - table: OpenSearchDashboardsDatatable; - tables: Table[]; - title: string; - name: string; - key: any; - column: number; - row: number; -} - -export interface TableContext { - table?: Table; - tableGroups: TableGroup[]; - direction?: 'row' | 'column'; -} - -export function tableVisResponseHandler( - input: OpenSearchDashboardsDatatable, - config: TableVisConfig -): TableContext { - let table: Table | undefined; - const tableGroups: TableGroup[] = []; - let direction: TableContext['direction']; - - const split = config.splitColumn || config.splitRow; - - if (split) { - direction = config.splitRow ? 'row' : 'column'; - const splitColumnIndex = split[0].accessor; - const splitColumnFormatter = getFormatService().deserialize(split[0].format); - const splitColumn = input.columns[splitColumnIndex]; - const splitMap: { [key: string]: number } = {}; - let splitIndex = 0; - - input.rows.forEach((row, rowIndex) => { - const splitValue: any = row[splitColumn.id]; - - if (!splitMap.hasOwnProperty(splitValue as any)) { - (splitMap as any)[splitValue] = splitIndex++; - const tableGroup: TableGroup = { - title: `${splitColumnFormatter.convert(splitValue)}: ${splitColumn.name}`, - name: splitColumn.name, - key: splitValue, - column: splitColumnIndex, - row: rowIndex, - table: input, - tables: [], - }; - - tableGroup.tables.push({ - columns: input.columns, - rows: [], - }); - - tableGroups.push(tableGroup); - } - - const tableIndex = (splitMap as any)[splitValue]; - (tableGroups[tableIndex] as any).tables[0].rows.push(row); - }); - } else { - table = { - columns: input.columns, - rows: input.rows, - }; - } - - return { - table, - tableGroups, - direction, - }; -} diff --git a/src/plugins/vis_type_table_new/public/types.ts b/src/plugins/vis_type_table_new/public/types.ts deleted file mode 100644 index 0c5a9f9955e0..000000000000 --- a/src/plugins/vis_type_table_new/public/types.ts +++ /dev/null @@ -1,70 +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 { SchemaConfig } from 'src/plugins/visualizations/public'; -import { IFieldFormat } from 'src/plugins/data/public'; - -export interface TableVisConfig extends TableVisParams { - title: string; - metrics: SchemaConfig[]; - buckets: SchemaConfig[]; - splitRow?: SchemaConfig[]; - splitColumn?: SchemaConfig[]; -} - -export interface TableVisParams { - perPage: number | ''; - showPartialRows: boolean; - showMetricsAtAllLevels: boolean; -} - -export interface FormattedColumn { - id: string; - title: string; - formatter: IFieldFormat; - filterable: boolean; -} - -export interface ColumnWidth { - colIndex: number; - width: number; -} - -export interface SortColumn { - colIndex: number | null; - direction: 'asc' | 'desc' | null; -} - -export interface TableUiState { - sort: SortColumn; - setSort: (sort: SortColumn) => void; - width: ColumnWidth[]; - setWidth: (columnsWidth: ColumnWidth[]) => void; -} diff --git a/src/plugins/vis_type_table_new/public/utils/convert_to_formatted_data.ts b/src/plugins/vis_type_table_new/public/utils/convert_to_formatted_data.ts deleted file mode 100644 index cd997dfe5d5e..000000000000 --- a/src/plugins/vis_type_table_new/public/utils/convert_to_formatted_data.ts +++ /dev/null @@ -1,68 +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 { OpenSearchDashboardsDatatableRow } from 'src/plugins/expressions'; -import { Table } from '../table_vis_response_handler'; -import { TableVisConfig } from '../types'; -import { getFormatService } from '../services'; -import { FormattedColumn } from '../types'; -export interface FormattedDataProps { - formattedRows: OpenSearchDashboardsDatatableRow[]; - formattedColumns: FormattedColumn[]; -} - -export const convertToFormattedData = ( - table: Table, - visConfig: TableVisConfig -): FormattedDataProps => { - const { buckets, metrics } = visConfig; - const formattedRows: OpenSearchDashboardsDatatableRow[] = table.rows; - const formattedColumns: FormattedColumn[] = table.columns - .map(function (col, i) { - const isBucket = buckets.find((bucket) => bucket.accessor === i); - const dimension = isBucket || metrics.find((metric) => metric.accessor === i); - - if (!dimension) return undefined; - - const formatter = getFormatService().deserialize(dimension.format); - - const formattedColumn: FormattedColumn = { - id: col.id, - title: col.name, - formatter, - filterable: !!isBucket, - }; - - return formattedColumn; - }) - .filter((column): column is FormattedColumn => !!column); - - return { formattedRows, formattedColumns }; -}; diff --git a/src/plugins/visualizations/public/legacy/__snapshots__/build_pipeline.test.ts.snap b/src/plugins/visualizations/public/legacy/__snapshots__/build_pipeline.test.ts.snap index b3b4dc5e09b1..5712f358f7d1 100644 --- a/src/plugins/visualizations/public/legacy/__snapshots__/build_pipeline.test.ts.snap +++ b/src/plugins/visualizations/public/legacy/__snapshots__/build_pipeline.test.ts.snap @@ -12,16 +12,6 @@ exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunct exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles region_map function without buckets 1`] = `"regionmap visConfig='{\\"metric\\":{\\"accessor\\":0,\\"label\\":\\"\\",\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"}}' "`; -exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles table function with showPartialRows=true and showMetricsAtAllLevels=false 1`] = `"opensearch_dashboards_table visConfig='{\\"showMetricsAtAllLevels\\":false,\\"showPartialRows\\":true,\\"dimensions\\":{\\"metrics\\":[{\\"accessor\\":4,\\"label\\":\\"\\",\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"},{\\"accessor\\":5,\\"label\\":\\"\\",\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"}],\\"buckets\\":[0,3]}}' "`; - -exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles table function with showPartialRows=true and showMetricsAtAllLevels=true 1`] = `"opensearch_dashboards_table visConfig='{\\"showMetricsAtAllLevels\\":true,\\"showPartialRows\\":true,\\"dimensions\\":{\\"metrics\\":[{\\"accessor\\":1,\\"label\\":\\"\\",\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"},{\\"accessor\\":2,\\"label\\":\\"\\",\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"},{\\"accessor\\":4,\\"label\\":\\"\\",\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"},{\\"accessor\\":5,\\"label\\":\\"\\",\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"}],\\"buckets\\":[0,3]}}' "`; - -exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles table function with splits 1`] = `"opensearch_dashboards_table visConfig='{\\"foo\\":\\"bar\\",\\"dimensions\\":{\\"metrics\\":[{\\"accessor\\":0,\\"label\\":\\"\\",\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"}],\\"buckets\\":[],\\"splitRow\\":[1,2]}}' "`; - -exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles table function with splits and buckets 1`] = `"opensearch_dashboards_table visConfig='{\\"foo\\":\\"bar\\",\\"dimensions\\":{\\"metrics\\":[{\\"accessor\\":0,\\"label\\":\\"\\",\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"},{\\"accessor\\":1,\\"label\\":\\"\\",\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"}],\\"buckets\\":[3],\\"splitRow\\":[2,4]}}' "`; - -exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles table function without splits or buckets 1`] = `"opensearch_dashboards_table visConfig='{\\"foo\\":\\"bar\\",\\"dimensions\\":{\\"metrics\\":[{\\"accessor\\":0,\\"label\\":\\"\\",\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"},{\\"accessor\\":1,\\"label\\":\\"\\",\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"}],\\"buckets\\":[]}}' "`; - exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles tile_map function 1`] = `"tilemap visConfig='{\\"metric\\":{},\\"dimensions\\":{\\"metric\\":{\\"accessor\\":0,\\"label\\":\\"\\",\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"},\\"geohash\\":1,\\"geocentroid\\":3}}' "`; exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles vega function 1`] = `"vega spec='this is a test' "`; diff --git a/src/plugins/visualizations/public/legacy/build_pipeline.test.ts b/src/plugins/visualizations/public/legacy/build_pipeline.test.ts index 90721f66c4af..5f240f82602c 100644 --- a/src/plugins/visualizations/public/legacy/build_pipeline.test.ts +++ b/src/plugins/visualizations/public/legacy/build_pipeline.test.ts @@ -128,84 +128,6 @@ describe('visualize loader pipeline helpers: build pipeline', () => { expect(actual).toMatchSnapshot(); }); - describe('handles table function', () => { - it('without splits or buckets', () => { - const params = { foo: 'bar' }; - const schemas = { - ...schemasDef, - metric: [ - { ...schemaConfig, accessor: 0 }, - { ...schemaConfig, accessor: 1 }, - ], - }; - const actual = buildPipelineVisFunction.table(params, schemas, uiState); - expect(actual).toMatchSnapshot(); - }); - - it('with splits', () => { - const params = { foo: 'bar' }; - const schemas = { - ...schemasDef, - split_row: [1, 2], - }; - const actual = buildPipelineVisFunction.table(params, schemas, uiState); - expect(actual).toMatchSnapshot(); - }); - - it('with splits and buckets', () => { - const params = { foo: 'bar' }; - const schemas = { - ...schemasDef, - metric: [ - { ...schemaConfig, accessor: 0 }, - { ...schemaConfig, accessor: 1 }, - ], - split_row: [2, 4], - bucket: [3], - }; - const actual = buildPipelineVisFunction.table(params, schemas, uiState); - expect(actual).toMatchSnapshot(); - }); - - it('with showPartialRows=true and showMetricsAtAllLevels=true', () => { - const params = { - showMetricsAtAllLevels: true, - showPartialRows: true, - }; - const schemas = { - ...schemasDef, - metric: [ - { ...schemaConfig, accessor: 1 }, - { ...schemaConfig, accessor: 2 }, - { ...schemaConfig, accessor: 4 }, - { ...schemaConfig, accessor: 5 }, - ], - bucket: [0, 3], - }; - const actual = buildPipelineVisFunction.table(params, schemas, uiState); - expect(actual).toMatchSnapshot(); - }); - - it('with showPartialRows=true and showMetricsAtAllLevels=false', () => { - const params = { - showMetricsAtAllLevels: false, - showPartialRows: true, - }; - const schemas = { - ...schemasDef, - metric: [ - { ...schemaConfig, accessor: 1 }, - { ...schemaConfig, accessor: 2 }, - { ...schemaConfig, accessor: 4 }, - { ...schemaConfig, accessor: 5 }, - ], - bucket: [0, 3], - }; - const actual = buildPipelineVisFunction.table(params, schemas, uiState); - expect(actual).toMatchSnapshot(); - }); - }); - describe('handles region_map function', () => { it('without buckets', () => { const params = { metric: {} }; diff --git a/src/plugins/visualizations/public/legacy/build_pipeline.ts b/src/plugins/visualizations/public/legacy/build_pipeline.ts index 7a28ae7ac394..1cbb3bc38879 100644 --- a/src/plugins/visualizations/public/legacy/build_pipeline.ts +++ b/src/plugins/visualizations/public/legacy/build_pipeline.ts @@ -278,13 +278,6 @@ export const buildPipelineVisFunction: BuildPipelineVisFunction = { const paramsArray = [paramsJson, uiStateJson].filter((param) => Boolean(param)); return `tsvb ${paramsArray.join(' ')}`; }, - table: (params, schemas) => { - const visConfig = { - ...params, - ...buildVisConfig.table(schemas, params), - }; - return `opensearch_dashboards_table ${prepareJson('visConfig', visConfig)}`; - }, region_map: (params, schemas) => { const visConfig = { ...params, @@ -309,26 +302,6 @@ export const buildPipelineVisFunction: BuildPipelineVisFunction = { }; const buildVisConfig: BuildVisConfigFunction = { - table: (schemas, visParams = {}) => { - const visConfig = {} as any; - const metrics = schemas.metric; - const buckets = schemas.bucket || []; - visConfig.dimensions = { - metrics, - buckets, - splitRow: schemas.split_row, - splitColumn: schemas.split_column, - }; - - if (visParams.showMetricsAtAllLevels === false && visParams.showPartialRows === true) { - // Handle case where user wants to see partial rows but not metrics at all levels. - // This requires calculating how many metrics will come back in the tabified response, - // and removing all metrics from the dimensions except the last set. - const metricsPerBucket = metrics.length / buckets.length; - visConfig.dimensions.metrics.splice(0, metricsPerBucket * buckets.length - metricsPerBucket); - } - return visConfig; - }, region_map: (schemas) => { const visConfig = {} as any; visConfig.metric = schemas.metric[0]; diff --git a/test/functional/apps/dashboard/dashboard_filtering.js b/test/functional/apps/dashboard/dashboard_filtering.js index fd05f5b134e9..deb2ae399240 100644 --- a/test/functional/apps/dashboard/dashboard_filtering.js +++ b/test/functional/apps/dashboard/dashboard_filtering.js @@ -98,10 +98,6 @@ export default function ({ getService, getPageObjects }) { await dashboardExpect.seriesElementCount(0); }); - it('data tables are filtered', async () => { - await dashboardExpect.dataTableRowCount(0); - }); - it('goal and guages are filtered', async () => { await dashboardExpect.goalAndGuageLabelsExist(['0', '0%']); }); @@ -159,10 +155,6 @@ export default function ({ getService, getPageObjects }) { await dashboardExpect.seriesElementCount(0); }); - it('data tables are filtered', async () => { - await dashboardExpect.dataTableRowCount(0); - }); - it('goal and guages are filtered', async () => { await dashboardExpect.goalAndGuageLabelsExist(['0', '0%']); }); @@ -212,10 +204,6 @@ export default function ({ getService, getPageObjects }) { await dashboardExpect.seriesElementCount(3); }); - it('data tables', async () => { - await dashboardExpect.dataTableRowCount(10); - }); - it('goal and guages', async () => { await dashboardExpect.goalAndGuageLabelsExist(['39.958%', '7,544']); }); diff --git a/test/functional/apps/dashboard/dashboard_grid.js b/test/functional/apps/dashboard/dashboard_grid.js index 3e68f33f2043..0da0c58573aa 100644 --- a/test/functional/apps/dashboard/dashboard_grid.js +++ b/test/functional/apps/dashboard/dashboard_grid.js @@ -52,7 +52,7 @@ export default function ({ getService, getPageObjects }) { describe('move panel', () => { // Specific test after https://github.com/elastic/kibana/issues/14764 fix it('Can move panel from bottom to top row', async () => { - const lastVisTitle = 'Rendering Test: datatable'; + const lastVisTitle = 'Rendering Test: pie'; const panelTitleBeforeMove = await dashboardPanelActions.getPanelHeading(lastVisTitle); const position1 = await panelTitleBeforeMove.getPosition(); await browser.dragAndDrop( diff --git a/test/functional/apps/dashboard/embeddable_rendering.js b/test/functional/apps/dashboard/embeddable_rendering.js index b11955a1e24d..5cacc85cb0ef 100644 --- a/test/functional/apps/dashboard/embeddable_rendering.js +++ b/test/functional/apps/dashboard/embeddable_rendering.js @@ -67,7 +67,6 @@ export default function ({ getService, getPageObjects }) { await dashboardExpect.markdownWithValuesExists(["I'm a markdown!"]); await dashboardExpect.vegaTextsExist(['5,000']); await dashboardExpect.goalAndGuageLabelsExist(['62.925%', '55.625%', '11.915 GB']); - await dashboardExpect.dataTableRowCount(5); await dashboardExpect.tagCloudWithValuesFound(['CN', 'IN', 'US', 'BR', 'ID']); // TODO add test for 'region map viz' // TODO add test for 'tsvb gauge' viz @@ -90,7 +89,6 @@ export default function ({ getService, getPageObjects }) { const expectNoDataRenders = async () => { await pieChart.expectPieSliceCount(0); await dashboardExpect.seriesElementCount(0); - await dashboardExpect.dataTableRowCount(0); await dashboardExpect.savedSearchRowCount(0); await dashboardExpect.inputControlItemCount(5); await dashboardExpect.metricValuesExist(['0']); @@ -146,7 +144,7 @@ export default function ({ getService, getPageObjects }) { visNames.push(await dashboardAddPanel.addVisualization('Filter Bytes Test: vega')); await PageObjects.header.waitUntilLoadingHasFinished(); await dashboardExpect.visualizationsArePresent(visNames); - expect(visNames.length).to.be.equal(27); + expect(visNames.length).to.be.equal(26); await PageObjects.dashboard.waitForRenderComplete(); }); @@ -157,7 +155,7 @@ export default function ({ getService, getPageObjects }) { await dashboardAddPanel.closeAddPanel(); await PageObjects.header.waitUntilLoadingHasFinished(); await dashboardExpect.visualizationsArePresent(visAndSearchNames); - expect(visAndSearchNames.length).to.be.equal(28); + expect(visAndSearchNames.length).to.be.equal(27); await PageObjects.dashboard.waitForRenderComplete(); await PageObjects.dashboard.saveDashboard('embeddable rendering test', { diff --git a/test/functional/apps/dashboard/url_field_formatter.ts b/test/functional/apps/dashboard/url_field_formatter.ts index 0f7d81720634..798adeb99bf6 100644 --- a/test/functional/apps/dashboard/url_field_formatter.ts +++ b/test/functional/apps/dashboard/url_field_formatter.ts @@ -74,14 +74,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await settings.controlChangeSave(); }); - it('applied on dashboard', async () => { - await common.navigateToApp('dashboard'); - await dashboard.loadSavedDashboard('dashboard with everything'); - await dashboard.waitForRenderComplete(); - const fieldLink = await visChart.getFieldLinkInVisTable(`${fieldName}: Descending`, 1); - await clickFieldAndCheckUrl(fieldLink); - }); - it('applied on discover', async () => { await common.navigateToApp('discover'); await timePicker.setAbsoluteRange( diff --git a/test/functional/apps/visualize/_data_table.js b/test/functional/apps/visualize/_data_table.js deleted file mode 100644 index fde30412bdcb..000000000000 --- a/test/functional/apps/visualize/_data_table.js +++ /dev/null @@ -1,485 +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 expect from '@osd/expect'; - -export default function ({ getService, getPageObjects }) { - const log = getService('log'); - const inspector = getService('inspector'); - const testSubjects = getService('testSubjects'); - const retry = getService('retry'); - const filterBar = getService('filterBar'); - const PageObjects = getPageObjects(['visualize', 'timePicker', 'visEditor', 'visChart']); - - describe('data table', function indexPatternCreation() { - const vizName1 = 'Visualization DataTable'; - - before(async function () { - log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); - log.debug('clickDataTable'); - await PageObjects.visualize.clickDataTable(); - log.debug('clickNewSearch'); - await PageObjects.visualize.clickNewSearch(); - await PageObjects.timePicker.setDefaultAbsoluteRange(); - log.debug('Bucket = Split rows'); - await PageObjects.visEditor.clickBucket('Split rows'); - log.debug('Aggregation = Histogram'); - await PageObjects.visEditor.selectAggregation('Histogram'); - log.debug('Field = bytes'); - await PageObjects.visEditor.selectField('bytes'); - log.debug('Interval = 2000'); - await PageObjects.visEditor.setInterval('2000', { type: 'numeric' }); - await PageObjects.visEditor.clickGo(); - }); - - it('should allow applying changed params', async () => { - await PageObjects.visEditor.setInterval('1', { type: 'numeric', append: true }); - const interval = await PageObjects.visEditor.getNumericInterval(); - expect(interval).to.be('20001'); - const isApplyButtonEnabled = await PageObjects.visEditor.isApplyEnabled(); - expect(isApplyButtonEnabled).to.be(true); - }); - - it('should allow reseting changed params', async () => { - await PageObjects.visEditor.clickReset(); - const interval = await PageObjects.visEditor.getNumericInterval(); - expect(interval).to.be('2000'); - }); - - it('should be able to save and load', async function () { - await PageObjects.visualize.saveVisualizationExpectSuccessAndBreadcrumb(vizName1); - - await PageObjects.visualize.loadSavedVisualization(vizName1); - await PageObjects.visChart.waitForVisualization(); - }); - - it('should have inspector enabled', async function () { - await inspector.expectIsEnabled(); - }); - - it('should show correct data', function () { - const expectedChartData = [ - ['0B', '2,088'], - ['1.953KB', '2,748'], - ['3.906KB', '2,707'], - ['5.859KB', '2,876'], - ['7.813KB', '2,863'], - ['9.766KB', '147'], - ['11.719KB', '148'], - ['13.672KB', '129'], - ['15.625KB', '161'], - ['17.578KB', '137'], - ]; - - return retry.try(async function () { - await inspector.open(); - await inspector.expectTableData(expectedChartData); - await inspector.close(); - }); - }); - - it('should show percentage columns', async () => { - async function expectValidTableData() { - const data = await PageObjects.visChart.getTableVisData(); - expect(data.trim().split('\n')).to.be.eql([ - '≥ 0B and < 1,000B', - '1,351 64.703%', - '≥ 1,000B and < 1.953KB', - '737 35.297%', - ]); - } - - // load a plain table - await PageObjects.visualize.navigateToNewVisualization(); - await PageObjects.visualize.clickDataTable(); - await PageObjects.visualize.clickNewSearch(); - await PageObjects.timePicker.setDefaultAbsoluteRange(); - await PageObjects.visEditor.clickBucket('Split rows'); - await PageObjects.visEditor.selectAggregation('Range'); - await PageObjects.visEditor.selectField('bytes'); - await PageObjects.visEditor.clickGo(); - await PageObjects.visEditor.clickOptionsTab(); - await PageObjects.visEditor.setSelectByOptionText( - 'datatableVisualizationPercentageCol', - 'Count' - ); - await PageObjects.visEditor.clickGo(); - - await expectValidTableData(); - - // check that it works after a save and reload - const SAVE_NAME = 'viz w/ percents'; - await PageObjects.visualize.saveVisualizationExpectSuccessAndBreadcrumb(SAVE_NAME); - - await PageObjects.visualize.loadSavedVisualization(SAVE_NAME); - await PageObjects.visChart.waitForVisualization(); - - await expectValidTableData(); - - // check that it works after selecting a column that's deleted - await PageObjects.visEditor.clickDataTab(); - await PageObjects.visEditor.clickBucket('Metric', 'metrics'); - await PageObjects.visEditor.selectAggregation('Average', 'metrics'); - await PageObjects.visEditor.selectField('bytes', 'metrics'); - await PageObjects.visEditor.removeDimension(1); - await PageObjects.visEditor.clickGo(); - await PageObjects.visEditor.clickOptionsTab(); - - const data = await PageObjects.visChart.getTableVisData(); - expect(data.trim().split('\n')).to.be.eql([ - '≥ 0B and < 1,000B', - '344.094B', - '≥ 1,000B and < 1.953KB', - '1.697KB', - ]); - }); - - it('should show correct data when using average pipeline aggregation', async () => { - await PageObjects.visualize.navigateToNewVisualization(); - await PageObjects.visualize.clickDataTable(); - await PageObjects.visualize.clickNewSearch(); - await PageObjects.timePicker.setDefaultAbsoluteRange(); - await PageObjects.visEditor.clickBucket('Metric', 'metrics'); - await PageObjects.visEditor.selectAggregation('Average Bucket', 'metrics'); - await PageObjects.visEditor.selectAggregation('Terms', 'metrics', 'buckets'); - await PageObjects.visEditor.selectField('geo.src', 'metrics', 'buckets'); - await PageObjects.visEditor.clickGo(); - const data = await PageObjects.visChart.getTableVisData(); - log.debug(data.split('\n')); - expect(data.trim().split('\n')).to.be.eql(['14,004 1,412.6']); - }); - - it('should show correct data for a data table with date histogram', async () => { - await PageObjects.visualize.navigateToNewVisualization(); - await PageObjects.visualize.clickDataTable(); - await PageObjects.visualize.clickNewSearch(); - await PageObjects.timePicker.setDefaultAbsoluteRange(); - await PageObjects.visEditor.clickBucket('Split rows'); - await PageObjects.visEditor.selectAggregation('Date Histogram'); - await PageObjects.visEditor.selectField('@timestamp'); - await PageObjects.visEditor.setInterval('Day'); - await PageObjects.visEditor.clickGo(); - const data = await PageObjects.visChart.getTableVisData(); - log.debug(data.split('\n')); - expect(data.trim().split('\n')).to.be.eql([ - '2015-09-20', - '4,757', - '2015-09-21', - '4,614', - '2015-09-22', - '4,633', - ]); - }); - - it('should show correct data for a data table with date histogram', async () => { - await PageObjects.visualize.navigateToNewVisualization(); - await PageObjects.visualize.clickDataTable(); - await PageObjects.visualize.clickNewSearch(); - await PageObjects.timePicker.setDefaultAbsoluteRange(); - await PageObjects.visEditor.clickBucket('Split rows'); - await PageObjects.visEditor.selectAggregation('Date Histogram'); - await PageObjects.visEditor.selectField('@timestamp'); - await PageObjects.visEditor.setInterval('Day'); - await PageObjects.visEditor.clickGo(); - const data = await PageObjects.visChart.getTableVisData(); - expect(data.trim().split('\n')).to.be.eql([ - '2015-09-20', - '4,757', - '2015-09-21', - '4,614', - '2015-09-22', - '4,633', - ]); - }); - - it('should correctly filter for applied time filter on the main timefield', async () => { - await filterBar.addFilter('@timestamp', 'is between', '2015-09-19', '2015-09-21'); - await PageObjects.visChart.waitForVisualizationRenderingStabilized(); - const data = await PageObjects.visChart.getTableVisData(); - expect(data.trim().split('\n')).to.be.eql(['2015-09-20', '4,757']); - }); - - it('should correctly filter for pinned filters', async () => { - await filterBar.toggleFilterPinned('@timestamp'); - await PageObjects.visChart.waitForVisualizationRenderingStabilized(); - const data = await PageObjects.visChart.getTableVisData(); - expect(data.trim().split('\n')).to.be.eql(['2015-09-20', '4,757']); - }); - - it('should show correct data for a data table with top hits', async () => { - await PageObjects.visualize.navigateToNewVisualization(); - await PageObjects.visualize.clickDataTable(); - await PageObjects.visualize.clickNewSearch(); - await PageObjects.timePicker.setDefaultAbsoluteRange(); - await PageObjects.visEditor.clickMetricEditor(); - await PageObjects.visEditor.selectAggregation('Top Hit', 'metrics'); - await PageObjects.visEditor.selectField('agent.raw', 'metrics'); - await PageObjects.visEditor.clickGo(); - const data = await PageObjects.visChart.getTableVisData(); - log.debug(data); - expect(data.length).to.be.greaterThan(0); - }); - - it('should show correct data for a data table with range agg', async () => { - await PageObjects.visualize.navigateToNewVisualization(); - await PageObjects.visualize.clickDataTable(); - await PageObjects.visualize.clickNewSearch(); - await PageObjects.timePicker.setDefaultAbsoluteRange(); - await PageObjects.visEditor.clickBucket('Split rows'); - await PageObjects.visEditor.selectAggregation('Range'); - await PageObjects.visEditor.selectField('bytes'); - await PageObjects.visEditor.clickGo(); - const data = await PageObjects.visChart.getTableVisData(); - expect(data.trim().split('\n')).to.be.eql([ - '≥ 0B and < 1,000B', - '1,351', - '≥ 1,000B and < 1.953KB', - '737', - ]); - }); - - describe('otherBucket', () => { - before(async () => { - await PageObjects.visualize.navigateToNewVisualization(); - await PageObjects.visualize.clickDataTable(); - await PageObjects.visualize.clickNewSearch(); - await PageObjects.timePicker.setDefaultAbsoluteRange(); - await PageObjects.visEditor.clickBucket('Split rows'); - await PageObjects.visEditor.selectAggregation('Terms'); - await PageObjects.visEditor.selectField('extension.raw'); - await PageObjects.visEditor.setSize(2); - await PageObjects.visEditor.clickGo(); - - await PageObjects.visEditor.toggleOtherBucket(); - await PageObjects.visEditor.toggleMissingBucket(); - await PageObjects.visEditor.clickGo(); - }); - - it('should show correct data', async () => { - const data = await PageObjects.visChart.getTableVisContent(); - expect(data).to.be.eql([ - ['jpg', '9,109'], - ['css', '2,159'], - ['Other', '2,736'], - ]); - }); - - it('should apply correct filter', async () => { - await PageObjects.visChart.filterOnTableCell(1, 3); - await PageObjects.visChart.waitForVisualizationRenderingStabilized(); - const data = await PageObjects.visChart.getTableVisContent(); - expect(data).to.be.eql([ - ['png', '1,373'], - ['gif', '918'], - ['Other', '445'], - ]); - }); - }); - - describe('metricsOnAllLevels', () => { - before(async () => { - await PageObjects.visualize.navigateToNewVisualization(); - await PageObjects.visualize.clickDataTable(); - await PageObjects.visualize.clickNewSearch(); - await PageObjects.timePicker.setDefaultAbsoluteRange(); - await PageObjects.visEditor.clickBucket('Split rows'); - await PageObjects.visEditor.selectAggregation('Terms'); - await PageObjects.visEditor.selectField('extension.raw'); - await PageObjects.visEditor.setSize(2); - await PageObjects.visEditor.toggleOpenEditor(2, 'false'); - await PageObjects.visEditor.clickBucket('Split rows'); - await PageObjects.visEditor.selectAggregation('Terms'); - await PageObjects.visEditor.selectField('geo.dest'); - await PageObjects.visEditor.toggleOpenEditor(3, 'false'); - await PageObjects.visEditor.clickGo(); - }); - - it('should show correct data without showMetricsAtAllLevels', async () => { - const data = await PageObjects.visChart.getTableVisContent(); - expect(data).to.be.eql([ - ['jpg', 'CN', '1,718'], - ['jpg', 'IN', '1,511'], - ['jpg', 'US', '770'], - ['jpg', 'ID', '314'], - ['jpg', 'PK', '244'], - ['css', 'CN', '422'], - ['css', 'IN', '346'], - ['css', 'US', '189'], - ['css', 'ID', '68'], - ['css', 'BR', '58'], - ]); - }); - - it('should show correct data without showMetricsAtAllLevels even if showPartialRows is selected', async () => { - await PageObjects.visEditor.clickOptionsTab(); - await testSubjects.setCheckbox('showPartialRows', 'check'); - await PageObjects.visEditor.clickGo(); - const data = await PageObjects.visChart.getTableVisContent(); - expect(data).to.be.eql([ - ['jpg', 'CN', '1,718'], - ['jpg', 'IN', '1,511'], - ['jpg', 'US', '770'], - ['jpg', 'ID', '314'], - ['jpg', 'PK', '244'], - ['css', 'CN', '422'], - ['css', 'IN', '346'], - ['css', 'US', '189'], - ['css', 'ID', '68'], - ['css', 'BR', '58'], - ]); - }); - - it('should show metrics on each level', async () => { - await PageObjects.visEditor.clickOptionsTab(); - await testSubjects.setCheckbox('showMetricsAtAllLevels', 'check'); - await PageObjects.visEditor.clickGo(); - const data = await PageObjects.visChart.getTableVisContent(); - expect(data).to.be.eql([ - ['jpg', '9,109', 'CN', '1,718'], - ['jpg', '9,109', 'IN', '1,511'], - ['jpg', '9,109', 'US', '770'], - ['jpg', '9,109', 'ID', '314'], - ['jpg', '9,109', 'PK', '244'], - ['css', '2,159', 'CN', '422'], - ['css', '2,159', 'IN', '346'], - ['css', '2,159', 'US', '189'], - ['css', '2,159', 'ID', '68'], - ['css', '2,159', 'BR', '58'], - ]); - }); - - it('should show metrics other than count on each level', async () => { - await PageObjects.visEditor.clickDataTab(); - await PageObjects.visEditor.clickBucket('Metric', 'metrics'); - await PageObjects.visEditor.selectAggregation('Average', 'metrics'); - await PageObjects.visEditor.selectField('bytes', 'metrics'); - await PageObjects.visEditor.clickGo(); - const data = await PageObjects.visChart.getTableVisContent(); - expect(data).to.be.eql([ - ['jpg', '9,109', '5.469KB', 'CN', '1,718', '5.477KB'], - ['jpg', '9,109', '5.469KB', 'IN', '1,511', '5.456KB'], - ['jpg', '9,109', '5.469KB', 'US', '770', '5.371KB'], - ['jpg', '9,109', '5.469KB', 'ID', '314', '5.424KB'], - ['jpg', '9,109', '5.469KB', 'PK', '244', '5.41KB'], - ['css', '2,159', '5.566KB', 'CN', '422', '5.712KB'], - ['css', '2,159', '5.566KB', 'IN', '346', '5.754KB'], - ['css', '2,159', '5.566KB', 'US', '189', '5.333KB'], - ['css', '2,159', '5.566KB', 'ID', '68', '4.82KB'], - ['css', '2,159', '5.566KB', 'BR', '58', '5.915KB'], - ]); - }); - }); - - describe('split tables', () => { - before(async () => { - await PageObjects.visualize.navigateToNewVisualization(); - await PageObjects.visualize.clickDataTable(); - await PageObjects.visualize.clickNewSearch(); - await PageObjects.timePicker.setDefaultAbsoluteRange(); - await PageObjects.visEditor.clickBucket('Split table'); - await PageObjects.visEditor.selectAggregation('Terms'); - await PageObjects.visEditor.selectField('extension.raw'); - await PageObjects.visEditor.setSize(2); - await PageObjects.visEditor.toggleOpenEditor(2, 'false'); - await PageObjects.visEditor.clickBucket('Split rows'); - await PageObjects.visEditor.selectAggregation('Terms'); - await PageObjects.visEditor.selectField('geo.dest'); - await PageObjects.visEditor.setSize(3, 3); - await PageObjects.visEditor.toggleOpenEditor(3, 'false'); - await PageObjects.visEditor.clickBucket('Split rows'); - await PageObjects.visEditor.selectAggregation('Terms'); - await PageObjects.visEditor.selectField('geo.src'); - await PageObjects.visEditor.setSize(3, 4); - await PageObjects.visEditor.toggleOpenEditor(4, 'false'); - await PageObjects.visEditor.clickGo(); - }); - - it('should have a splitted table', async () => { - const data = await PageObjects.visChart.getTableVisContent(); - expect(data).to.be.eql([ - [ - ['CN', 'CN', '330'], - ['CN', 'IN', '274'], - ['CN', 'US', '140'], - ['IN', 'CN', '286'], - ['IN', 'IN', '281'], - ['IN', 'US', '133'], - ['US', 'CN', '135'], - ['US', 'IN', '134'], - ['US', 'US', '52'], - ], - [ - ['CN', 'CN', '90'], - ['CN', 'IN', '84'], - ['CN', 'US', '27'], - ['IN', 'CN', '69'], - ['IN', 'IN', '58'], - ['IN', 'US', '34'], - ['US', 'IN', '36'], - ['US', 'CN', '29'], - ['US', 'US', '13'], - ], - ]); - }); - - it('should show metrics for split bucket when using showMetricsAtAllLevels', async () => { - await PageObjects.visEditor.clickOptionsTab(); - await testSubjects.setCheckbox('showMetricsAtAllLevels', 'check'); - await PageObjects.visEditor.clickGo(); - const data = await PageObjects.visChart.getTableVisContent(); - expect(data).to.be.eql([ - [ - ['CN', '1,718', 'CN', '330'], - ['CN', '1,718', 'IN', '274'], - ['CN', '1,718', 'US', '140'], - ['IN', '1,511', 'CN', '286'], - ['IN', '1,511', 'IN', '281'], - ['IN', '1,511', 'US', '133'], - ['US', '770', 'CN', '135'], - ['US', '770', 'IN', '134'], - ['US', '770', 'US', '52'], - ], - [ - ['CN', '422', 'CN', '90'], - ['CN', '422', 'IN', '84'], - ['CN', '422', 'US', '27'], - ['IN', '346', 'CN', '69'], - ['IN', '346', 'IN', '58'], - ['IN', '346', 'US', '34'], - ['US', '189', 'IN', '36'], - ['US', '189', 'CN', '29'], - ['US', '189', 'US', '13'], - ], - ]); - }); - }); - }); -} diff --git a/test/functional/apps/visualize/_data_table_nontimeindex.js b/test/functional/apps/visualize/_data_table_nontimeindex.js deleted file mode 100644 index 37ad4c2a9eb6..000000000000 --- a/test/functional/apps/visualize/_data_table_nontimeindex.js +++ /dev/null @@ -1,170 +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 expect from '@osd/expect'; - -export default function ({ getService, getPageObjects }) { - const log = getService('log'); - const inspector = getService('inspector'); - const retry = getService('retry'); - const filterBar = getService('filterBar'); - const renderable = getService('renderable'); - const PageObjects = getPageObjects(['visualize', 'visEditor', 'header', 'visChart']); - - describe('data table with index without time filter', function indexPatternCreation() { - const vizName1 = 'Visualization DataTable without time filter'; - - before(async function () { - log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); - log.debug('clickDataTable'); - await PageObjects.visualize.clickDataTable(); - log.debug('clickNewSearch'); - await PageObjects.visualize.clickNewSearch( - PageObjects.visualize.index.LOGSTASH_NON_TIME_BASED - ); - log.debug('Bucket = Split Rows'); - await PageObjects.visEditor.clickBucket('Split rows'); - log.debug('Aggregation = Histogram'); - await PageObjects.visEditor.selectAggregation('Histogram'); - log.debug('Field = bytes'); - await PageObjects.visEditor.selectField('bytes'); - log.debug('Interval = 2000'); - await PageObjects.visEditor.setInterval('2000', { type: 'numeric' }); - await PageObjects.visEditor.clickGo(); - }); - - it('should allow applying changed params', async () => { - await PageObjects.visEditor.setInterval('1', { type: 'numeric', append: true }); - const interval = await PageObjects.visEditor.getNumericInterval(); - expect(interval).to.be('20001'); - const isApplyButtonEnabled = await PageObjects.visEditor.isApplyEnabled(); - expect(isApplyButtonEnabled).to.be(true); - }); - - it('should allow reseting changed params', async () => { - await PageObjects.visEditor.clickReset(); - const interval = await PageObjects.visEditor.getNumericInterval(); - expect(interval).to.be('2000'); - }); - - it('should be able to save and load', async function () { - await PageObjects.visualize.saveVisualizationExpectSuccessAndBreadcrumb(vizName1); - - await PageObjects.visualize.loadSavedVisualization(vizName1); - await PageObjects.visChart.waitForVisualization(); - }); - - it('should have inspector enabled', async function () { - await inspector.expectIsEnabled(); - }); - - it('should show correct data', function () { - const expectedChartData = [ - ['0B', '2,088'], - ['1.953KB', '2,748'], - ['3.906KB', '2,707'], - ['5.859KB', '2,876'], - ['7.813KB', '2,863'], - ['9.766KB', '147'], - ['11.719KB', '148'], - ['13.672KB', '129'], - ['15.625KB', '161'], - ['17.578KB', '137'], - ]; - - return retry.try(async function () { - await inspector.open(); - await inspector.expectTableData(expectedChartData); - await inspector.close(); - }); - }); - - it('should show correct data when using average pipeline aggregation', async () => { - await PageObjects.visualize.navigateToNewVisualization(); - await PageObjects.visualize.clickDataTable(); - await PageObjects.visualize.clickNewSearch( - PageObjects.visualize.index.LOGSTASH_NON_TIME_BASED - ); - await PageObjects.visEditor.clickBucket('Metric', 'metrics'); - await PageObjects.visEditor.selectAggregation('Average Bucket', 'metrics'); - await PageObjects.visEditor.selectAggregation('Terms', 'metrics', 'buckets'); - await PageObjects.visEditor.selectField('geo.src', 'metrics', 'buckets'); - await PageObjects.visEditor.clickGo(); - const data = await PageObjects.visChart.getTableVisData(); - log.debug(data.split('\n')); - expect(data.trim().split('\n')).to.be.eql(['14,004 1,412.6']); - }); - - describe('data table with date histogram', async () => { - before(async () => { - await PageObjects.visualize.navigateToNewVisualization(); - await PageObjects.visualize.clickDataTable(); - await PageObjects.visualize.clickNewSearch( - PageObjects.visualize.index.LOGSTASH_NON_TIME_BASED - ); - await PageObjects.visEditor.clickBucket('Split rows'); - await PageObjects.visEditor.selectAggregation('Date Histogram'); - await PageObjects.visEditor.selectField('@timestamp'); - await PageObjects.visEditor.setInterval('Day'); - await PageObjects.visEditor.clickGo(); - }); - - it('should show correct data', async () => { - const data = await PageObjects.visChart.getTableVisData(); - log.debug(data.split('\n')); - expect(data.trim().split('\n')).to.be.eql([ - '2015-09-20', - '4,757', - '2015-09-21', - '4,614', - '2015-09-22', - '4,633', - ]); - }); - - it('should correctly filter for applied time filter on the main timefield', async () => { - await filterBar.addFilter('@timestamp', 'is between', '2015-09-19', '2015-09-21'); - await PageObjects.header.waitUntilLoadingHasFinished(); - await renderable.waitForRender(); - const data = await PageObjects.visChart.getTableVisData(); - expect(data.trim().split('\n')).to.be.eql(['2015-09-20', '4,757']); - }); - - it('should correctly filter for pinned filters', async () => { - await filterBar.toggleFilterPinned('@timestamp'); - await PageObjects.header.waitUntilLoadingHasFinished(); - await renderable.waitForRender(); - const data = await PageObjects.visChart.getTableVisData(); - expect(data.trim().split('\n')).to.be.eql(['2015-09-20', '4,757']); - }); - }); - }); -} diff --git a/test/functional/apps/visualize/_data_table_notimeindex_filters.ts b/test/functional/apps/visualize/_data_table_notimeindex_filters.ts deleted file mode 100644 index 3661a4847bd6..000000000000 --- a/test/functional/apps/visualize/_data_table_notimeindex_filters.ts +++ /dev/null @@ -1,101 +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 expect from '@osd/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const log = getService('log'); - const filterBar = getService('filterBar'); - const renderable = getService('renderable'); - const dashboardAddPanel = getService('dashboardAddPanel'); - const PageObjects = getPageObjects([ - 'common', - 'visualize', - 'header', - 'dashboard', - 'timePicker', - 'visEditor', - 'visChart', - ]); - - describe('data table with index without time filter filters', function indexPatternCreation() { - const vizName1 = 'Visualization DataTable w/o time filter'; - - before(async function () { - log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); - log.debug('clickDataTable'); - await PageObjects.visualize.clickDataTable(); - log.debug('clickNewSearch'); - await PageObjects.visualize.clickNewSearch( - PageObjects.visualize.index.LOGSTASH_NON_TIME_BASED - ); - log.debug('Bucket = Split Rows'); - await PageObjects.visEditor.clickBucket('Split rows'); - log.debug('Aggregation = Histogram'); - await PageObjects.visEditor.selectAggregation('Histogram'); - log.debug('Field = bytes'); - await PageObjects.visEditor.selectField('bytes'); - log.debug('Interval = 2000'); - await PageObjects.visEditor.setInterval('2000', { type: 'numeric' }); - await PageObjects.visEditor.clickGo(); - }); - - it('should be able to save and load', async function () { - await PageObjects.visualize.saveVisualizationExpectSuccessAndBreadcrumb(vizName1); - - await PageObjects.visualize.loadSavedVisualization(vizName1); - await PageObjects.visChart.waitForVisualization(); - }); - - it('timefilter should be disabled', async () => { - const isOff = await PageObjects.timePicker.isOff(); - expect(isOff).to.be(true); - }); - - // test to cover bug #54548 - add this visualization to a dashboard and filter - it('should add to dashboard and allow filtering', async function () { - await PageObjects.common.navigateToApp('dashboard'); - await PageObjects.dashboard.clickNewDashboard(); - await dashboardAddPanel.addVisualization(vizName1); - - // hover and click on cell to filter - await PageObjects.visChart.filterOnTableCell('1', '2'); - - await PageObjects.header.waitUntilLoadingHasFinished(); - await renderable.waitForRender(); - const filterCount = await filterBar.getFilterCount(); - expect(filterCount).to.be(1); - - await filterBar.removeAllFilters(); - }); - }); -} diff --git a/test/functional/apps/visualize/_embedding_chart.js b/test/functional/apps/visualize/_embedding_chart.js deleted file mode 100644 index c47319303dcf..000000000000 --- a/test/functional/apps/visualize/_embedding_chart.js +++ /dev/null @@ -1,187 +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 expect from '@osd/expect'; - -export default function ({ getService, getPageObjects }) { - const filterBar = getService('filterBar'); - const log = getService('log'); - const renderable = getService('renderable'); - const embedding = getService('embedding'); - const PageObjects = getPageObjects([ - 'visualize', - 'visEditor', - 'visChart', - 'header', - 'timePicker', - ]); - - describe('embedding', () => { - describe('a data table', () => { - before(async function () { - await PageObjects.visualize.navigateToNewVisualization(); - await PageObjects.visualize.clickDataTable(); - await PageObjects.visualize.clickNewSearch(); - await PageObjects.timePicker.setDefaultAbsoluteRange(); - await PageObjects.visEditor.clickBucket('Split rows'); - await PageObjects.visEditor.selectAggregation('Date Histogram'); - await PageObjects.visEditor.selectField('@timestamp'); - await PageObjects.visEditor.toggleOpenEditor(2, 'false'); - await PageObjects.visEditor.clickBucket('Split rows'); - await PageObjects.visEditor.selectAggregation('Histogram'); - await PageObjects.visEditor.selectField('bytes'); - await PageObjects.visEditor.setInterval('2000', { type: 'numeric', aggNth: 3 }); - await PageObjects.visEditor.clickGo(); - }); - - it('should allow opening table vis in embedded mode', async () => { - await embedding.openInEmbeddedMode(); - await renderable.waitForRender(); - - const data = await PageObjects.visChart.getTableVisData(); - log.debug(data.split('\n')); - expect(data.trim().split('\n')).to.be.eql([ - '2015-09-20 00:00', - '0B', - '5', - '2015-09-20 00:00', - '1.953KB', - '5', - '2015-09-20 00:00', - '3.906KB', - '9', - '2015-09-20 00:00', - '5.859KB', - '4', - '2015-09-20 00:00', - '7.813KB', - '14', - '2015-09-20 03:00', - '0B', - '32', - '2015-09-20 03:00', - '1.953KB', - '33', - '2015-09-20 03:00', - '3.906KB', - '45', - '2015-09-20 03:00', - '5.859KB', - '31', - '2015-09-20 03:00', - '7.813KB', - '48', - ]); - }); - - it('should allow to filter in embedded mode', async () => { - await filterBar.addFilter('@timestamp', 'is between', '2015-09-21', '2015-09-23'); - await PageObjects.header.waitUntilLoadingHasFinished(); - await renderable.waitForRender(); - - const data = await PageObjects.visChart.getTableVisData(); - log.debug(data.split('\n')); - expect(data.trim().split('\n')).to.be.eql([ - '2015-09-21 00:00', - '0B', - '7', - '2015-09-21 00:00', - '1.953KB', - '9', - '2015-09-21 00:00', - '3.906KB', - '9', - '2015-09-21 00:00', - '5.859KB', - '6', - '2015-09-21 00:00', - '7.813KB', - '10', - '2015-09-21 00:00', - '11.719KB', - '1', - '2015-09-21 03:00', - '0B', - '28', - '2015-09-21 03:00', - '1.953KB', - '39', - '2015-09-21 03:00', - '3.906KB', - '36', - '2015-09-21 03:00', - '5.859KB', - '43', - ]); - }); - - it('should allow to change timerange from the visualization in embedded mode', async () => { - await PageObjects.visChart.filterOnTableCell(1, 7); - await PageObjects.header.waitUntilLoadingHasFinished(); - await renderable.waitForRender(); - - const data = await PageObjects.visChart.getTableVisData(); - log.debug(data.split('\n')); - expect(data.trim().split('\n')).to.be.eql([ - '03:00', - '0B', - '1', - '03:00', - '1.953KB', - '1', - '03:00', - '3.906KB', - '1', - '03:00', - '5.859KB', - '2', - '03:10', - '0B', - '1', - '03:10', - '5.859KB', - '1', - '03:10', - '7.813KB', - '1', - '03:15', - '0B', - '1', - '03:15', - '1.953KB', - '1', - '03:20', - '1.953KB', - '1', - ]); - }); - }); - }); -} diff --git a/test/functional/apps/visualize/_histogram_request_start.js b/test/functional/apps/visualize/_histogram_request_start.js deleted file mode 100644 index f797b83af730..000000000000 --- a/test/functional/apps/visualize/_histogram_request_start.js +++ /dev/null @@ -1,95 +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 expect from '@osd/expect'; - -export default function ({ getService, getPageObjects }) { - const log = getService('log'); - const retry = getService('retry'); - const PageObjects = getPageObjects([ - 'common', - 'visualize', - 'visEditor', - 'visChart', - 'timePicker', - ]); - - describe('histogram agg onSearchRequestStart', function () { - before(async function () { - log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); - log.debug('clickDataTable'); - await PageObjects.visualize.clickDataTable(); - await PageObjects.visualize.clickNewSearch(); - await PageObjects.timePicker.setDefaultAbsoluteRange(); - log.debug('Bucket = Split Rows'); - await PageObjects.visEditor.clickBucket('Split rows'); - log.debug('Aggregation = Histogram'); - await PageObjects.visEditor.selectAggregation('Histogram'); - log.debug('Field = machine.ram'); - await PageObjects.visEditor.selectField('machine.ram'); - }); - - describe('interval parameter uses autoBounds', function () { - it('should use provided value when number of generated buckets is less than histogram:maxBars', async function () { - const providedInterval = 2400000000; - log.debug(`Interval = ${providedInterval}`); - await PageObjects.visEditor.setInterval(providedInterval, { type: 'numeric' }); - await PageObjects.visEditor.clickGo(); - await retry.try(async () => { - const data = await PageObjects.visChart.getTableVisData(); - const dataArray = data.replace(/,/g, '').split('\n'); - expect(dataArray.length).to.eql(20); - const bucketStart = parseInt(dataArray[0], 10); - const bucketEnd = parseInt(dataArray[2], 10); - const actualInterval = bucketEnd - bucketStart; - expect(actualInterval).to.eql(providedInterval); - }); - }); - - it('should scale value to round number when number of generated buckets is greater than histogram:maxBars', async function () { - const providedInterval = 100; - log.debug(`Interval = ${providedInterval}`); - await PageObjects.visEditor.setInterval(providedInterval, { type: 'numeric' }); - await PageObjects.visEditor.clickGo(); - await PageObjects.common.sleep(1000); //fix this - await retry.try(async () => { - const data = await PageObjects.visChart.getTableVisData(); - const dataArray = data.replace(/,/g, '').split('\n'); - expect(dataArray.length).to.eql(20); - const bucketStart = parseInt(dataArray[0], 10); - const bucketEnd = parseInt(dataArray[2], 10); - const actualInterval = bucketEnd - bucketStart; - expect(actualInterval).to.eql(1200000000); - }); - }); - }); - }); -} diff --git a/test/functional/apps/visualize/_linked_saved_searches.ts b/test/functional/apps/visualize/_linked_saved_searches.ts deleted file mode 100644 index 72c2a4322a2d..000000000000 --- a/test/functional/apps/visualize/_linked_saved_searches.ts +++ /dev/null @@ -1,138 +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 expect from '@osd/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const browser = getService('browser'); - const filterBar = getService('filterBar'); - const retry = getService('retry'); - const testSubjects = getService('testSubjects'); - const PageObjects = getPageObjects([ - 'common', - 'discover', - 'visualize', - 'header', - 'timePicker', - 'visChart', - ]); - - describe('saved search visualizations from visualize app', function describeIndexTests() { - describe('linked saved searched', () => { - const savedSearchName = 'vis_saved_search'; - let discoverSavedSearchUrlPath: string; - - before(async () => { - await PageObjects.common.navigateToApp('discover'); - await filterBar.addFilter('extension.raw', 'is', 'jpg'); - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.discover.saveSearch(savedSearchName); - discoverSavedSearchUrlPath = (await browser.getCurrentUrl()).split('?')[0]; - }); - - it('should create a visualization from a saved search', async () => { - await PageObjects.visualize.navigateToNewVisualization(); - await PageObjects.visualize.clickDataTable(); - await PageObjects.visualize.clickSavedSearch(savedSearchName); - await PageObjects.timePicker.setDefaultAbsoluteRange(); - await retry.waitFor('wait for count to equal 9,109', async () => { - const data = await PageObjects.visChart.getTableVisData(); - return data.trim() === '9,109'; - }); - }); - - it('should have a valid link to the saved search from the visualization', async () => { - await testSubjects.click('showUnlinkSavedSearchPopover'); - await testSubjects.click('viewSavedSearch'); - await PageObjects.header.waitUntilLoadingHasFinished(); - - await retry.waitFor('wait discover load its breadcrumbs', async () => { - const discoverBreadcrumb = await PageObjects.discover.getCurrentQueryName(); - return discoverBreadcrumb === savedSearchName; - }); - - const discoverURLPath = (await browser.getCurrentUrl()).split('?')[0]; - expect(discoverURLPath).to.equal(discoverSavedSearchUrlPath); - - // go back to visualize - await browser.goBack(); - await PageObjects.header.waitUntilLoadingHasFinished(); - }); - - it('should respect the time filter when linked to a saved search', async () => { - await PageObjects.timePicker.setAbsoluteRange( - 'Sep 19, 2015 @ 06:31:44.000', - 'Sep 21, 2015 @ 10:00:00.000' - ); - await retry.waitFor('wait for count to equal 3,950', async () => { - const data = await PageObjects.visChart.getTableVisData(); - return data.trim() === '3,950'; - }); - }); - - it('should allow adding filters while having a linked saved search', async () => { - await filterBar.addFilter('bytes', 'is between', '100', '3000'); - await PageObjects.header.waitUntilLoadingHasFinished(); - await retry.waitFor('wait for count to equal 707', async () => { - const data = await PageObjects.visChart.getTableVisData(); - return data.trim() === '707'; - }); - }); - - it('should allow unlinking from a linked search', async () => { - await PageObjects.visualize.clickUnlinkSavedSearch(); - await retry.waitFor('wait for count to equal 707', async () => { - const data = await PageObjects.visChart.getTableVisData(); - return data.trim() === '707'; - }); - // The filter on the saved search should now be in the editor - expect(await filterBar.hasFilter('extension.raw', 'jpg')).to.be(true); - - // Disabling this filter should now result in different values, since - // the visualization should not be linked anymore with the saved search. - await filterBar.toggleFilterEnabled('extension.raw'); - await PageObjects.header.waitUntilLoadingHasFinished(); - await retry.waitFor('wait for count to equal 1,293', async () => { - const unfilteredData = await PageObjects.visChart.getTableVisData(); - return unfilteredData.trim() === '1,293'; - }); - }); - - it('should not break when saving after unlinking', async () => { - await PageObjects.visualize.saveVisualizationExpectSuccess('Unlinked before saved'); - await PageObjects.header.waitUntilLoadingHasFinished(); - await retry.waitFor('wait for count to equal 1,293', async () => { - const data = await PageObjects.visChart.getTableVisData(); - return data.trim() === '1,293'; - }); - }); - }); - }); -} diff --git a/test/functional/apps/visualize/index.ts b/test/functional/apps/visualize/index.ts index b6563a1c28ca..fb7e721db7de 100644 --- a/test/functional/apps/visualize/index.ts +++ b/test/functional/apps/visualize/index.ts @@ -57,12 +57,8 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { this.tags('ciGroup9'); loadTestFile(require.resolve('./_custom_branding')); - loadTestFile(require.resolve('./_embedding_chart')); loadTestFile(require.resolve('./_chart_types')); loadTestFile(require.resolve('./_area_chart')); - loadTestFile(require.resolve('./_data_table')); - loadTestFile(require.resolve('./_data_table_nontimeindex')); - loadTestFile(require.resolve('./_data_table_notimeindex_filters')); }); describe('', function () { @@ -73,7 +69,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./_gauge_chart')); loadTestFile(require.resolve('./_heatmap_chart')); loadTestFile(require.resolve('./input_control_vis')); - loadTestFile(require.resolve('./_histogram_request_start')); loadTestFile(require.resolve('./_metric_chart')); }); @@ -86,7 +81,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./_markdown_vis')); loadTestFile(require.resolve('./_shared_item')); loadTestFile(require.resolve('./_lab_mode')); - loadTestFile(require.resolve('./_linked_saved_searches')); loadTestFile(require.resolve('./_visualize_listing')); if (isOss) { loadTestFile(require.resolve('./_tile_map')); diff --git a/test/functional/fixtures/opensearch_archiver/dashboard/current/opensearch_dashboards/data.json.gz b/test/functional/fixtures/opensearch_archiver/dashboard/current/opensearch_dashboards/data.json.gz index 9944453bff1a01420b3f46c35af304c4fc5122ee..1ca01589588ce98027d39d1650ce7ae1cfc11e43 100644 GIT binary patch literal 20707 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 M3qUV`;*%f)00w`{3;+NC diff --git a/test/functional/page_objects/dashboard_page.ts b/test/functional/page_objects/dashboard_page.ts index 40e177790695..58f75555b157 100644 --- a/test/functional/page_objects/dashboard_page.ts +++ b/test/functional/page_objects/dashboard_page.ts @@ -425,7 +425,6 @@ export function DashboardPageProvider({ getService, getPageObjects }: FtrProvide { name: PIE_CHART_VIS_NAME, description: 'PieChart' }, { name: 'Visualization☺ VerticalBarChart', description: 'VerticalBarChart' }, { name: AREA_CHART_VIS_NAME, description: 'AreaChart' }, - { name: 'Visualization☺漢字 DataTable', description: 'DataTable' }, { name: LINE_CHART_VIS_NAME, description: 'LineChart' }, { name: 'Visualization TileMap', description: 'TileMap' }, { name: 'Visualization MetricChart', description: 'MetricChart' }, diff --git a/test/functional/page_objects/visualize_chart_page.ts b/test/functional/page_objects/visualize_chart_page.ts index 02d9e60ca06d..e13d8eed6081 100644 --- a/test/functional/page_objects/visualize_chart_page.ts +++ b/test/functional/page_objects/visualize_chart_page.ts @@ -36,7 +36,6 @@ export function VisualizeChartPageProvider({ getService, getPageObjects }: FtrPr const find = getService('find'); const log = getService('log'); const retry = getService('retry'); - const table = getService('table'); const defaultFindTimeout = config.get('timeouts.find'); const { common } = getPageObjects(['common']); @@ -294,18 +293,6 @@ export function VisualizeChartPageProvider({ getService, getPageObjects }: FtrPr }); } - public async filterOnTableCell(column: string, row: string) { - await retry.try(async () => { - const tableVis = await testSubjects.find('tableVis'); - const cell = await tableVis.findByCssSelector( - `tbody tr:nth-child(${row}) td:nth-child(${column})` - ); - await cell.moveMouseTo(); - const filterBtn = await testSubjects.findDescendant('filterForCellValue', cell); - await filterBtn.click(); - }); - } - public async getMarkdownText() { const markdownContainer = await testSubjects.find('markdownBody'); return markdownContainer.getVisibleText(); @@ -317,65 +304,6 @@ export function VisualizeChartPageProvider({ getService, getPageObjects }: FtrPr return element.getVisibleText(); } - public async getFieldLinkInVisTable(fieldName: string, rowIndex: number = 1) { - const tableVis = await testSubjects.find('tableVis'); - const $ = await tableVis.parseDomContent(); - const headers = $('span[ng-bind="::col.title"]') - .toArray() - .map((header: any) => $(header).text()); - const fieldColumnIndex = headers.indexOf(fieldName); - return await find.byCssSelector( - `[data-test-subj="paginated-table-body"] tr:nth-of-type(${rowIndex}) td:nth-of-type(${ - fieldColumnIndex + 1 - }) a` - ); - } - - /** - * If you are writing new tests, you should rather look into getTableVisContent method instead. - * @deprecated Use getTableVisContent instead. - */ - public async getTableVisData() { - return await testSubjects.getVisibleText('paginated-table-body'); - } - - /** - * This function is the newer function to retrieve data from within a table visualization. - * It uses a better return format, than the old getTableVisData, by properly splitting - * cell values into arrays. Please use this function for newer tests. - */ - public async getTableVisContent({ stripEmptyRows = true } = {}) { - return await retry.try(async () => { - const container = await testSubjects.find('tableVis'); - const allTables = await testSubjects.findAllDescendant('paginated-table-body', container); - - if (allTables.length === 0) { - return []; - } - - const allData = await Promise.all( - allTables.map(async (t) => { - let data = await table.getDataFromElement(t); - if (stripEmptyRows) { - data = data.filter( - (row) => row.length > 0 && row.some((cell) => cell.trim().length > 0) - ); - } - return data; - }) - ); - - if (allTables.length === 1) { - // If there was only one table we return only the data for that table - // This prevents an unnecessary array around that single table, which - // is the case we have in most tests. - return allData[0]; - } - - return allData; - }); - } - public async getMetric() { const elements = await find.allByCssSelector( '[data-test-subj="visualizationLoader"] .mtrVis__container' diff --git a/test/functional/page_objects/visualize_page.ts b/test/functional/page_objects/visualize_page.ts index 4f019c1a4a04..b60d50b449c8 100644 --- a/test/functional/page_objects/visualize_page.ts +++ b/test/functional/page_objects/visualize_page.ts @@ -99,10 +99,6 @@ export function VisualizePageProvider({ getService, getPageObjects }: FtrProvide await this.clickVisType('area'); } - public async clickDataTable() { - await this.clickVisType('table'); - } - public async clickLineChart() { await this.clickVisType('line'); } diff --git a/test/functional/services/dashboard/expectations.ts b/test/functional/services/dashboard/expectations.ts index a676730f4c1b..ab6eaed8a9bc 100644 --- a/test/functional/services/dashboard/expectations.ts +++ b/test/functional/services/dashboard/expectations.ts @@ -241,17 +241,6 @@ export function DashboardExpectProvider({ getService, getPageObjects }: FtrProvi }); } - async dataTableRowCount(expectedCount: number) { - log.debug(`DashboardExpect.dataTableRowCount(${expectedCount})`); - await retry.try(async () => { - const dataTableRows = await find.allByCssSelector( - '[data-test-subj="paginated-table-body"] [data-cell-content]', - findTimeout - ); - expect(dataTableRows.length).to.be(expectedCount); - }); - } - async seriesElementCount(expectedCount: number) { log.debug(`DashboardExpect.seriesElementCount(${expectedCount})`); await retry.try(async () => { diff --git a/test/functional/services/index.ts b/test/functional/services/index.ts index 391967de7bce..ecb54866c465 100644 --- a/test/functional/services/index.ts +++ b/test/functional/services/index.ts @@ -48,16 +48,15 @@ import { DashboardVisualizationProvider, } from './dashboard'; import { DocTableProvider } from './doc_table'; -import { EmbeddingProvider } from './embedding'; import { FilterBarProvider } from './filter_bar'; import { FlyoutProvider } from './flyout'; +import { EmbeddingProvider } from './embedding'; import { GlobalNavProvider } from './global_nav'; import { InspectorProvider } from './inspector'; import { ManagementMenuProvider } from './management'; import { QueryBarProvider } from './query_bar'; import { RemoteProvider } from './remote'; import { RenderableProvider } from './renderable'; -import { TableProvider } from './table'; import { ToastsProvider } from './toasts'; import { DataGridProvider } from './data_grid'; import { @@ -93,7 +92,6 @@ export const services = { dataGrid: DataGridProvider, embedding: EmbeddingProvider, renderable: RenderableProvider, - table: TableProvider, browser: BrowserProvider, pieChart: PieChartProvider, inspector: InspectorProvider, diff --git a/test/functional/services/table.ts b/test/functional/services/table.ts deleted file mode 100644 index 34578df40e80..000000000000 --- a/test/functional/services/table.ts +++ /dev/null @@ -1,73 +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 { FtrProviderContext } from '../ftr_provider_context'; -import { WebElementWrapper } from './lib/web_element_wrapper'; - -export function TableProvider({ getService }: FtrProviderContext) { - const testSubjects = getService('testSubjects'); - - class Table { - /** - * Finds table and returns data in the nested array format - * [ [cell1_in_row1, cell2_in_row1], [cell1_in_row2, cell2_in_row2] ] - * @param dataTestSubj data-test-subj selector - */ - - public async getDataFromTestSubj(dataTestSubj: string): Promise { - const table = await testSubjects.find(dataTestSubj); - return await this.getDataFromElement(table); - } - - /** - * Converts the table data into nested array - * [ [cell1_in_row1, cell2_in_row1], [cell1_in_row2, cell2_in_row2] ] - * @param element table - */ - public async getDataFromElement(element: WebElementWrapper): Promise { - const $ = await element.parseDomContent(); - return $('tr') - .toArray() - .map((row) => - $(row) - .find('td') - .toArray() - .map((cell) => - $(cell) - .text() - .replace(/ /g, '') - .trim() - ) - ); - } - } - - return new Table(); -}