Skip to content

Commit

Permalink
[Table Visualization] restore datagrid columns (#2411)
Browse files Browse the repository at this point in the history
* restore datagrid columns
* display column title correctly
* deangular and re-use formatted column
* convert formatted column to data grid column
* restore filter in and filter out value functions

Partially resolve:
#2305

Signed-off-by: Anan Zhuang <[email protected]>
  • Loading branch information
ananzh authored Sep 29, 2022
1 parent 2c16598 commit 38c46c0
Show file tree
Hide file tree
Showing 6 changed files with 349 additions and 1 deletion.
6 changes: 6 additions & 0 deletions src/plugins/data/common/field_formats/field_format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { EuiDataGridProps, EuiDataGrid } from '@elastic/eui';
import { IInterpreterRenderHandlers } from 'src/plugins/expressions';
import { Table } from '../table_vis_response_handler';
import { TableVisConfig } from '../types';
import { getDataGridColumns } from './table_vis_grid_columns';

interface TableVisComponentProps {
table: Table;
Expand Down Expand Up @@ -44,10 +45,11 @@ export const TableVisComponent = ({ table, visConfig, handlers }: TableVisCompon
}) as EuiDataGridProps['renderCellValue'];
}, [rows, pagination.pageIndex, pagination.pageSize]);

const dataGridColumns = getDataGridColumns(table, visConfig, handlers);
return (
<EuiDataGrid
aria-label="tableVis"
columns={columns}
columns={dataGridColumns}
columnVisibility={{
visibleColumns: columns.map(({ id }) => id),
setVisibleColumns: () => {},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/*
* 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 { Table } from '../table_vis_response_handler';
import { TableVisConfig } from '../types';
import { convertToFormattedData } from '../utils';

export const getDataGridColumns = (
table: Table,
visConfig: TableVisConfig,
handlers: IInterpreterRenderHandlers
) => {
const { formattedRows, formattedColumns } = convertToFormattedData(table, visConfig);

const filterBucket = (rowIndex: number, columnIndex: number, negate: boolean) => {
const foramttedColumnId = formattedColumns[columnIndex].id;
const rawColumnIndex = table.columns.findIndex((col) => col.id === foramttedColumnId);
handlers.event({
name: 'filterBucket',
data: {
data: [
{
table: {
columns: table.columns,
rows: formattedRows,
},
row: rowIndex,
column: rawColumnIndex,
},
],
negate,
},
});
};

return formattedColumns.map((col, colIndex) => {
const cellActions = col.filterable
? [
({ rowIndex, columnId, Component, closePopover }: EuiDataGridColumnCellActionProps) => {
const filterValue = formattedRows[rowIndex][columnId];
const filterContent = col.formatter?.convert(formattedRows[rowIndex][columnId]);

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 && (
<Component
onClick={() => {
filterBucket(rowIndex, colIndex, false);
closePopover();
}}
iconType="plusInCircle"
aria-label={filterForValueLabel}
data-test-subj="filterForValue"
>
{filterForValueText}
</Component>
)
);
},
({ rowIndex, columnId, Component, closePopover }: EuiDataGridColumnCellActionProps) => {
const filterValue = formattedRows[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 && (
<Component
onClick={() => {
filterBucket(rowIndex, colIndex, true);
closePopover();
}}
iconType="minusInCircle"
aria-label={filterOutValueLabel}
data-test-subj="filterOutValue"
>
{filterOutValueText}
</Component>
)
);
},
]
: undefined;

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,
};
return dataGridColumn;
});
};
11 changes: 11 additions & 0 deletions src/plugins/vis_type_table/public/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
*/

import { SchemaConfig } from 'src/plugins/visualizations/public';
import { IFieldFormat } from 'src/plugins/data/public';

export enum AggTypes {
SUM = 'sum',
Expand All @@ -53,3 +54,13 @@ export interface TableVisParams {
totalFunc: AggTypes;
percentageCol: string;
}

export interface FormattedColumn {
id: string;
title: string;
formatter: IFieldFormat | undefined;
filterable: boolean;
formattedTotal?: string | number;
sumTotal?: number;
total?: number;
}
180 changes: 180 additions & 0 deletions src/plugins/vis_type_table/public/utils/convert_to_formatted_data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/*
* 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, splitColumn } = visConfig;
let formattedRows: OpenSearchDashboardsDatatableRow[] = table.rows;
let formattedColumns: FormattedColumn[] = table.columns
.map(function (col, i) {
const isBucket = buckets.find((bucket) => bucket.accessor === i);
const isSplitColumn = splitColumn
? splitColumn.find((splitCol) => splitCol.accessor === i)
: undefined;
const dimension =
isBucket || isSplitColumn || metrics.find((metric) => metric.accessor === i);

const formatter = dimension ? getFormatService().deserialize(dimension.format) : undefined;

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);

if (visConfig.percentageCol) {
const insertAtIndex = formattedColumns.findIndex(
(col) => col.title === visConfig.percentageCol
);

// column to show percentage for was removed
if (insertAtIndex < 0) return;

const { cols, rows } = addPercentageCol(
formattedColumns,
visConfig.percentageCol,
table.rows,
insertAtIndex
);
formattedRows = rows;
formattedColumns = cols;
}
return { formattedRows, formattedColumns };
};
6 changes: 6 additions & 0 deletions src/plugins/vis_type_table/public/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

export * from './convert_to_formatted_data';

0 comments on commit 38c46c0

Please sign in to comment.