Skip to content

Commit

Permalink
[Table Vis] add an experimental table visualization in vis builder
Browse files Browse the repository at this point in the history
In this PR, we hook up an experimental table vis in vis builder. This
table vis is a refactor of previous table. It is written in React and
DataGrid component.

In this PR, we did two main things:
* add an experimental table visualization
* enable it in vis builder

Issue Resolved (hook up table in vis builder):
opensearch-project#2704

The experimental table vis has all the features from current table, including
* restore table vis in react using a Datagrid component
* datagrid component does not support splitted grids. For future transfer
to OUI Datagrid, we create a tableGroup in visData for splitted grids.
* restore basic pagenation, sort and format.
* implement 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
* format table cell to show Date and percent
* restore showTotal feature: it allows table vis to show total,
avg, min, max and count statics on count
* restore export csv feature to table vis
* split table in rows and columns

Beside of restoring original features, there are some changes:

* [IMPROVE] remove repeated column from split tables
Currently, when we split table by columns, the split column is shown
both in the table title and as a separate column. This is not needed.
In this PR, we remove the repeated column in split tables in col.
* [NEW FEATURE] adjustable table column width
In the new table visualization, customer can adjust the column width
as needed.

Issue Resolved:
opensearch-project#2212
opensearch-project#2213
opensearch-project#2305
opensearch-project#2379
opensearch-project#2579

Signed-off-by: Anan Zhuang <ananzh@amazon.com>
  • Loading branch information
ananzh committed Oct 31, 2022
1 parent 57fdef6 commit f2a9287
Show file tree
Hide file tree
Showing 34 changed files with 2,296 additions and 1 deletion.
2 changes: 1 addition & 1 deletion config/opensearch_dashboards.yml
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@

# Set the value of this setting to true to start exploring wizard
# functionality in Visualization.
# vis_builder.enabled: false
vis_builder.enabled: true

# Set the value of this setting to true to enable the experimental multiple data source
# support feature. Use with caution.
Expand Down
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 @@ -14,6 +14,7 @@ import {
import { EDIT_PATH, PLUGIN_ID } from '../../../../common';
import { VisBuilderServices } from '../../../types';
import { MetricOptionsDefaults } from '../../../visualizations/metric/metric_viz_type';
import { TableOptionsDefaults } from '../../../visualizations/table/table_viz_type';
import { getCreateBreadcrumbs, getEditBreadcrumbs } from '../breadcrumbs';
import { getSavedVisBuilderVis } from '../get_saved_vis_builder_vis';
import {
Expand Down Expand Up @@ -82,6 +83,7 @@ export const useSavedVisBuilderVis = (visualizationIdFromUrl: string | undefined
}

dispatch(setStyleState<MetricOptionsDefaults>(styleState));
dispatch(setStyleState<TableOptionsDefaults>(styleState));
dispatch(setVisualizationState(visualizationState));
}

Expand Down
2 changes: 2 additions & 0 deletions src/plugins/vis_builder/public/visualizations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import type { TypeServiceSetup } from '../services/type_service';
import { createMetricConfig } from './metric';
import { createTableConfig } from './table';
import { createHistogramConfig, createLineConfig, createAreaConfig } from './vislib';

export function registerDefaultTypes(typeServiceSetup: TypeServiceSetup) {
Expand All @@ -13,6 +14,7 @@ export function registerDefaultTypes(typeServiceSetup: TypeServiceSetup) {
createLineConfig,
createAreaConfig,
createMetricConfig,
createTableConfig,
];

visualizationTypes.forEach((createTypeConfig) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { get, cloneDeep } from 'lodash';
import React, { useCallback, useEffect, useMemo } from 'react';
import { i18n } from '@osd/i18n';
import produce from 'immer';
import { Draft } from 'immer';
import { EuiIconTip } from '@elastic/eui';
import { search } from '../../../../../data/public';
import { NumberInputOption, SwitchOption, SelectOption } from '../../../../../charts/public';
import {
useTypedDispatch,
useTypedSelector,
setStyleState,
} from '../../../application/utils/state_management';
import { TableOptionsDefaults } from '../table_viz_type';
import { Option } from '../../../application/app';
import { useIndexPatterns } from '../../../application/utils/use';
import { useOpenSearchDashboards } from '../../../../../opensearch_dashboards_react/public';
import { VisBuilderServices } from '../../../types';

const { tabifyGetColumns } = search;

export enum AggTypes {
SUM = 'sum',
AVG = 'avg',
MIN = 'min',
MAX = 'max',
COUNT = 'count',
}

const totalAggregations = [
{
value: AggTypes.SUM,
text: i18n.translate('visTypeTableNew.totalAggregations.sumText', {
defaultMessage: 'Sum',
}),
},
{
value: AggTypes.AVG,
text: i18n.translate('visTypeTableNew.totalAggregations.averageText', {
defaultMessage: 'Average',
}),
},
{
value: AggTypes.MIN,
text: i18n.translate('visTypeTableNewNew.totalAggregations.minText', {
defaultMessage: 'Min',
}),
},
{
value: AggTypes.MAX,
text: i18n.translate('visTypeTableNewNew.totalAggregations.maxText', {
defaultMessage: 'Max',
}),
},
{
value: AggTypes.COUNT,
text: i18n.translate('visTypeTableNewNew.totalAggregations.countText', {
defaultMessage: 'Count',
}),
},
];

function TableVizOptions() {
const perPageLabel = i18n.translate('visTypeTableNewNew.params.perPageLabel', {
defaultMessage: 'Max rows per page',
});
const showMetricsLabel = i18n.translate('visTypeTableNewNew.params.showMetricsLabel', {
defaultMessage: 'Show metrics for every bucket/level',
});
const showPartialRowsLabel = i18n.translate('visTypeTableNewNew.params.showPartialRowsLabel', {
defaultMessage: 'Show partial rows',
});
const showTotalLabel = i18n.translate('visTypeTableNewNew.params.showTotalLabel', {
defaultMessage: 'Show total',
});
const totalFunctionLabel = i18n.translate('visTypeTableNewNew.params.totalFunctionLabel', {
defaultMessage: 'Total function',
});
const percentageColLabel = i18n.translate('visTypeTableNewNew.params.PercentageColLabel', {
defaultMessage: 'Percentage column',
});
const showPartialRowsTip = i18n.translate('visTypeTableNewNew.params.showPartialRowsTip', {
defaultMessage:
'Show rows that have partial data. This will still calculate metrics for every bucket/level, even if they are not displayed.',
});

const styleState = useTypedSelector((state) => state.style) as TableOptionsDefaults;
const dispatch = useTypedDispatch();
const indexPattern = useIndexPatterns().selected;
const {
services: {
data: {
search: { aggs: aggService },
},
},
} = useOpenSearchDashboards<VisBuilderServices>();
const aggConfigParams = useTypedSelector(
(state) => state.visualization.activeVisualization?.aggConfigParams
);
const aggConfigs = useMemo(() => {
return indexPattern && aggService.createAggConfigs(indexPattern, cloneDeep(aggConfigParams));
}, [aggConfigParams, aggService, indexPattern]);

const setOption = useCallback(
(callback: (draft: Draft<typeof styleState>) => void) => {
const newState = produce(styleState, callback);
dispatch(setStyleState<TableOptionsDefaults>(newState));
},
[dispatch, styleState]
);

const percentageColumns = useMemo(() => {
const defaultPercentageColText = {
value: '',
text: i18n.translate('visTypeTableNew.params.defaultPercentageCol', {
defaultMessage: 'Don’t show',
}),
};
return aggConfigs
? [
defaultPercentageColText,
...tabifyGetColumns(aggConfigs.getResponseAggs(), true)
.filter((col) => get(col.aggConfig.toSerializedFieldFormat(), 'id') === 'number')
.map(({ name }) => ({ value: name, text: name })),
]
: [defaultPercentageColText];
}, [aggConfigs]);

useEffect(() => {
if (
!percentageColumns.find(({ value }) => value === styleState.percentageCol) &&
percentageColumns[0] &&
percentageColumns[0].value !== styleState.percentageCol
) {
setOption((draft) => {
draft.percentageCol = percentageColumns[0].value;
});
}
}, [percentageColumns, styleState.percentageCol, setOption]);

const isPerPageValid = styleState.perPage === '' || styleState.perPage > 0;

return (
<>
<Option
title={i18n.translate('visTypeTableNewNew.params.settingsTitle', {
defaultMessage: 'Settings',
})}
initialIsOpen
>
<NumberInputOption
label={
<>
{perPageLabel},
<EuiIconTip
content="Leaving this field empty means it will use number of buckets from the response."
position="right"
/>
</>
}
isInvalid={!isPerPageValid}
min={1}
paramName="perPage"
value={styleState.perPage}
setValue={(_, value) =>
setOption((draft) => {
draft.perPage = value;
})
}
/>

<SwitchOption
label={showMetricsLabel}
paramName="showMetricsAtAllLevels"
value={styleState.showMetricsAtAllLevels}
setValue={(_, value) =>
setOption((draft) => {
draft.showMetricsAtAllLevels = value;
})
}
data-test-subj="showMetricsAtAllLevels"
/>

<SwitchOption
label={showPartialRowsLabel}
tooltip={showPartialRowsTip}
paramName="showPartialRows"
value={styleState.showPartialRows}
setValue={(_, value) =>
setOption((draft) => {
draft.showPartialRows = value;
})
}
data-test-subj="showPartialRows"
/>

<SwitchOption
label={showTotalLabel}
paramName="showTotal"
value={styleState.showTotal}
setValue={(_, value) =>
setOption((draft) => {
draft.showTotal = value;
})
}
/>

<SelectOption
label={totalFunctionLabel}
disabled={!styleState.showTotal}
options={totalAggregations}
paramName="totalFunc"
value={styleState.totalFunc}
setValue={(_, value) =>
setOption((draft) => {
draft.totalFunc = value;
})
}
/>

<SelectOption
label={percentageColLabel}
options={percentageColumns}
paramName="percentageCol"
value={styleState.percentageCol}
setValue={(_, value) =>
setOption((draft) => {
draft.percentageCol = value;
})
}
id="datatableVisualizationPercentageCol"
/>
</Option>
</>
);
}

export { TableVizOptions };
6 changes: 6 additions & 0 deletions src/plugins/vis_builder/public/visualizations/table/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 { createTableConfig } from './table_viz_type';
Loading

0 comments on commit f2a9287

Please sign in to comment.