feat: per column unit option for table type (#5134)

* feat: base setup for individual column type units

* feat: added logic for y axis unit selection

* fix: light mode design

* feat: fix the mutation of original datasource array
This commit is contained in:
Vikrant Gupta 2024-06-04 11:14:54 +05:30 committed by GitHub
parent be9c3f0697
commit 2145e353c8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 169 additions and 8 deletions

View File

@ -1,12 +1,13 @@
import { ExclamationCircleFilled } from '@ant-design/icons'; import { ExclamationCircleFilled } from '@ant-design/icons';
import { Space, Tooltip } from 'antd'; import { Space, Tooltip } from 'antd';
import { getYAxisFormattedValue } from 'components/Graph/yAxisConfig';
import { Events } from 'constants/events'; import { Events } from 'constants/events';
import { QueryTable } from 'container/QueryTable'; import { QueryTable } from 'container/QueryTable';
import { import {
createTableColumnsFromQuery, createTableColumnsFromQuery,
RowData, RowData,
} from 'lib/query/createTableColumnsFromQuery'; } from 'lib/query/createTableColumnsFromQuery';
import { get, set } from 'lodash-es'; import { cloneDeep, get, isEmpty, set } from 'lodash-es';
import { memo, ReactNode, useCallback, useEffect, useMemo } from 'react'; import { memo, ReactNode, useCallback, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { eventEmitter } from 'utils/getEventEmitter'; import { eventEmitter } from 'utils/getEventEmitter';
@ -19,11 +20,12 @@ function GridTableComponent({
data, data,
query, query,
thresholds, thresholds,
columnUnits,
tableProcessedDataRef, tableProcessedDataRef,
...props ...props
}: GridTableComponentProps): JSX.Element { }: GridTableComponentProps): JSX.Element {
const { t } = useTranslation(['valueGraph']); const { t } = useTranslation(['valueGraph']);
const { columns, dataSource } = useMemo( const { columns, dataSource: originalDataSource } = useMemo(
() => () =>
createTableColumnsFromQuery({ createTableColumnsFromQuery({
query, query,
@ -31,7 +33,6 @@ function GridTableComponent({
}), }),
[data, query], [data, query],
); );
const createDataInCorrectFormat = useCallback( const createDataInCorrectFormat = useCallback(
(dataSource: RowData[]): RowData[] => (dataSource: RowData[]): RowData[] =>
dataSource.map((d) => { dataSource.map((d) => {
@ -52,6 +53,35 @@ function GridTableComponent({
[columns], [columns],
); );
const applyColumnUnits = useCallback(
(dataSource: RowData[]): RowData[] => {
let mutateDataSource = cloneDeep(dataSource);
if (isEmpty(columnUnits)) {
return mutateDataSource;
}
mutateDataSource = mutateDataSource.map(
(val): RowData => {
const newValue = val;
Object.keys(val).forEach((k) => {
if (columnUnits[k]) {
newValue[k] = getYAxisFormattedValue(String(val[k]), columnUnits[k]);
}
});
return newValue;
},
);
return mutateDataSource;
},
[columnUnits],
);
const dataSource = useMemo(() => applyColumnUnits(originalDataSource), [
applyColumnUnits,
originalDataSource,
]);
useEffect(() => { useEffect(() => {
if (tableProcessedDataRef) { if (tableProcessedDataRef) {
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign

View File

@ -5,11 +5,13 @@ import {
ThresholdProps, ThresholdProps,
} from 'container/NewWidget/RightContainer/Threshold/types'; } from 'container/NewWidget/RightContainer/Threshold/types';
import { RowData } from 'lib/query/createTableColumnsFromQuery'; import { RowData } from 'lib/query/createTableColumnsFromQuery';
import { ColumnUnit } from 'types/api/dashboard/getAll';
import { Query } from 'types/api/queryBuilder/queryBuilderData'; import { Query } from 'types/api/queryBuilder/queryBuilderData';
export type GridTableComponentProps = { export type GridTableComponentProps = {
query: Query; query: Query;
thresholds?: ThresholdProps[]; thresholds?: ThresholdProps[];
columnUnits?: ColumnUnit;
tableProcessedDataRef?: React.MutableRefObject<RowData[]>; tableProcessedDataRef?: React.MutableRefObject<RowData[]>;
} & Pick<LogsExplorerTableProps, 'data'> & } & Pick<LogsExplorerTableProps, 'data'> &
Omit<TableProps<RowData>, 'columns' | 'dataSource'>; Omit<TableProps<RowData>, 'columns' | 'dataSource'>;

View File

@ -0,0 +1,27 @@
.column-unit-selector {
margin-top: 16px;
.heading {
color: var(--bg-vanilla-400);
font-family: 'Space Mono';
font-size: 13px;
font-style: normal;
font-weight: 400;
line-height: 18px; /* 138.462% */
letter-spacing: 0.52px;
text-transform: uppercase;
}
.y-axis-unit-selector {
flex-direction: row !important;
align-items: center;
}
}
.lightMode {
.column-unit-selector {
.heading {
color: var(--bg-ink-400);
}
}
}

View File

@ -0,0 +1,49 @@
import './ColumnUnitSelector.styles.scss';
import { Typography } from 'antd';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { Dispatch, SetStateAction } from 'react';
import { ColumnUnit } from 'types/api/dashboard/getAll';
import YAxisUnitSelector from '../YAxisUnitSelector';
interface ColumnUnitSelectorProps {
columnUnits: ColumnUnit;
setColumnUnits: Dispatch<SetStateAction<ColumnUnit>>;
}
export function ColumnUnitSelector(
props: ColumnUnitSelectorProps,
): JSX.Element {
const { currentQuery } = useQueryBuilder();
function getAggregateColumnsNamesAndLabels(): string[] {
return currentQuery.builder.queryData.map((q) => q.queryName);
}
const { columnUnits, setColumnUnits } = props;
const aggregationQueries = getAggregateColumnsNamesAndLabels();
function handleColumnUnitSelect(queryName: string, value: string): void {
setColumnUnits((prev) => ({
...prev,
[queryName]: value,
}));
}
return (
<section className="column-unit-selector">
<Typography.Text className="heading">Column Units</Typography.Text>
{aggregationQueries.map((query) => (
<YAxisUnitSelector
defaultValue={columnUnits[query]}
onSelect={(value: string): void => handleColumnUnitSelect(query, value)}
fieldLabel={query}
key={query}
handleClear={(): void => {
handleColumnUnitSelect(query, '');
}}
/>
))}
</section>
);
}

View File

@ -13,14 +13,17 @@ const findCategoryByName = (
): Record<string, string> | undefined => ): Record<string, string> | undefined =>
find(flattenedCategories, (option) => option.name === searchValue); find(flattenedCategories, (option) => option.name === searchValue);
type OnSelectType = Dispatch<SetStateAction<string>> | ((val: string) => void);
function YAxisUnitSelector({ function YAxisUnitSelector({
defaultValue, defaultValue,
onSelect, onSelect,
fieldLabel, fieldLabel,
handleClear,
}: { }: {
defaultValue: string; defaultValue: string;
onSelect: Dispatch<SetStateAction<string>>; onSelect: OnSelectType;
fieldLabel: string; fieldLabel: string;
handleClear?: () => void;
}): JSX.Element { }): JSX.Element {
const onSelectHandler = (selectedValue: string): void => { const onSelectHandler = (selectedValue: string): void => {
onSelect(findCategoryByName(selectedValue)?.id || ''); onSelect(findCategoryByName(selectedValue)?.id || '');
@ -35,7 +38,9 @@ function YAxisUnitSelector({
style={{ width: '100%' }} style={{ width: '100%' }}
rootClassName="y-axis-root-popover" rootClassName="y-axis-root-popover"
options={options} options={options}
allowClear
defaultValue={findCategoryById(defaultValue)?.name} defaultValue={findCategoryById(defaultValue)?.name}
onClear={handleClear}
onSelect={onSelectHandler} onSelect={onSelectHandler}
filterOption={(inputValue, option): boolean => { filterOption={(inputValue, option): boolean => {
if (option) { if (option) {
@ -46,10 +51,14 @@ function YAxisUnitSelector({
return false; return false;
}} }}
> >
<Input placeholder="Unit" allowClear rootClassName="input" /> <Input placeholder="Unit" rootClassName="input" />
</AutoComplete> </AutoComplete>
</div> </div>
); );
} }
export default YAxisUnitSelector; export default YAxisUnitSelector;
YAxisUnitSelector.defaultProps = {
handleClear: (): void => {},
};

View File

@ -68,7 +68,7 @@ export const panelTypeVsFillSpan: { [key in PANEL_TYPES]: boolean } = {
export const panelTypeVsYAxisUnit: { [key in PANEL_TYPES]: boolean } = { export const panelTypeVsYAxisUnit: { [key in PANEL_TYPES]: boolean } = {
[PANEL_TYPES.TIME_SERIES]: true, [PANEL_TYPES.TIME_SERIES]: true,
[PANEL_TYPES.VALUE]: true, [PANEL_TYPES.VALUE]: true,
[PANEL_TYPES.TABLE]: true, [PANEL_TYPES.TABLE]: false,
[PANEL_TYPES.LIST]: false, [PANEL_TYPES.LIST]: false,
[PANEL_TYPES.PIE]: false, [PANEL_TYPES.PIE]: false,
[PANEL_TYPES.BAR]: true, [PANEL_TYPES.BAR]: true,
@ -99,3 +99,16 @@ export const panelTypeVsPanelTimePreferences: {
[PANEL_TYPES.TRACE]: false, [PANEL_TYPES.TRACE]: false,
[PANEL_TYPES.EMPTY_WIDGET]: false, [PANEL_TYPES.EMPTY_WIDGET]: false,
} as const; } as const;
export const panelTypeVsColumnUnitPreferences: {
[key in PANEL_TYPES]: boolean;
} = {
[PANEL_TYPES.TIME_SERIES]: false,
[PANEL_TYPES.VALUE]: false,
[PANEL_TYPES.TABLE]: true,
[PANEL_TYPES.LIST]: false,
[PANEL_TYPES.PIE]: false,
[PANEL_TYPES.BAR]: false,
[PANEL_TYPES.TRACE]: false,
[PANEL_TYPES.EMPTY_WIDGET]: false,
} as const;

View File

@ -18,10 +18,12 @@ import {
useEffect, useEffect,
useState, useState,
} from 'react'; } from 'react';
import { Widgets } from 'types/api/dashboard/getAll'; import { ColumnUnit, Widgets } from 'types/api/dashboard/getAll';
import { DataSource } from 'types/common/queryBuilder'; import { DataSource } from 'types/common/queryBuilder';
import { ColumnUnitSelector } from './ColumnUnitSelector/ColumnUnitSelector';
import { import {
panelTypeVsColumnUnitPreferences,
panelTypeVsCreateAlert, panelTypeVsCreateAlert,
panelTypeVsFillSpan, panelTypeVsFillSpan,
panelTypeVsPanelTimePreferences, panelTypeVsPanelTimePreferences,
@ -57,6 +59,8 @@ function RightContainer({
softMin, softMin,
setSoftMax, setSoftMax,
setSoftMin, setSoftMin,
columnUnits,
setColumnUnits,
}: RightContainerProps): JSX.Element { }: RightContainerProps): JSX.Element {
const onChangeHandler = useCallback( const onChangeHandler = useCallback(
(setFunc: Dispatch<SetStateAction<string>>, value: string) => { (setFunc: Dispatch<SetStateAction<string>>, value: string) => {
@ -78,6 +82,9 @@ function RightContainer({
const allowPanelTimePreference = const allowPanelTimePreference =
panelTypeVsPanelTimePreferences[selectedGraph]; panelTypeVsPanelTimePreferences[selectedGraph];
const allowPanelColumnPreference =
panelTypeVsColumnUnitPreferences[selectedGraph];
const { currentQuery } = useQueryBuilder(); const { currentQuery } = useQueryBuilder();
const [graphTypes, setGraphTypes] = useState<ItemsProps[]>(GraphTypes); const [graphTypes, setGraphTypes] = useState<ItemsProps[]>(GraphTypes);
@ -179,6 +186,13 @@ function RightContainer({
</> </>
)} )}
{allowPanelColumnPreference && (
<ColumnUnitSelector
columnUnits={columnUnits}
setColumnUnits={setColumnUnits}
/>
)}
{allowYAxisUnit && ( {allowYAxisUnit && (
<YAxisUnitSelector <YAxisUnitSelector
defaultValue={yAxisUnit} defaultValue={yAxisUnit}
@ -258,6 +272,8 @@ interface RightContainerProps {
setIsFillSpans: Dispatch<SetStateAction<boolean>>; setIsFillSpans: Dispatch<SetStateAction<boolean>>;
softMin: number | null; softMin: number | null;
softMax: number | null; softMax: number | null;
columnUnits: ColumnUnit;
setColumnUnits: Dispatch<SetStateAction<ColumnUnit>>;
setSoftMin: Dispatch<SetStateAction<number | null>>; setSoftMin: Dispatch<SetStateAction<number | null>>;
setSoftMax: Dispatch<SetStateAction<number | null>>; setSoftMax: Dispatch<SetStateAction<number | null>>;
} }

View File

@ -30,7 +30,7 @@ import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { generatePath, useParams } from 'react-router-dom'; import { generatePath, useParams } from 'react-router-dom';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import { Dashboard, Widgets } from 'types/api/dashboard/getAll'; import { ColumnUnit, Dashboard, Widgets } from 'types/api/dashboard/getAll';
import { IField } from 'types/api/logs/fields'; import { IField } from 'types/api/logs/fields';
import { EQueryType } from 'types/common/dashboard'; import { EQueryType } from 'types/common/dashboard';
import { DataSource } from 'types/common/queryBuilder'; import { DataSource } from 'types/common/queryBuilder';
@ -156,6 +156,10 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
: selectedWidget?.softMax || 0, : selectedWidget?.softMax || 0,
); );
const [columnUnits, setColumnUnits] = useState<ColumnUnit>(
selectedWidget?.columnUnits || {},
);
useEffect(() => { useEffect(() => {
setSelectedWidget((prev) => { setSelectedWidget((prev) => {
if (!prev) { if (!prev) {
@ -174,11 +178,13 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
softMin, softMin,
softMax, softMax,
fillSpans: isFillSpans, fillSpans: isFillSpans,
columnUnits,
selectedLogFields, selectedLogFields,
selectedTracesFields, selectedTracesFields,
}; };
}); });
}, [ }, [
columnUnits,
currentQuery, currentQuery,
description, description,
isFillSpans, isFillSpans,
@ -284,6 +290,7 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
panelTypes: graphType, panelTypes: graphType,
query: currentQuery, query: currentQuery,
thresholds: selectedWidget?.thresholds, thresholds: selectedWidget?.thresholds,
columnUnits: selectedWidget?.columnUnits,
softMin: selectedWidget?.softMin || 0, softMin: selectedWidget?.softMin || 0,
softMax: selectedWidget?.softMax || 0, softMax: selectedWidget?.softMax || 0,
fillSpans: selectedWidget?.fillSpans, fillSpans: selectedWidget?.fillSpans,
@ -305,6 +312,7 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
panelTypes: graphType, panelTypes: graphType,
query: currentQuery, query: currentQuery,
thresholds: selectedWidget?.thresholds, thresholds: selectedWidget?.thresholds,
columnUnits: selectedWidget?.columnUnits,
softMin: selectedWidget?.softMin || 0, softMin: selectedWidget?.softMin || 0,
softMax: selectedWidget?.softMax || 0, softMax: selectedWidget?.softMax || 0,
fillSpans: selectedWidget?.fillSpans, fillSpans: selectedWidget?.fillSpans,
@ -483,6 +491,8 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
setStacked={setStacked} setStacked={setStacked}
opacity={opacity} opacity={opacity}
yAxisUnit={yAxisUnit} yAxisUnit={yAxisUnit}
columnUnits={columnUnits}
setColumnUnits={setColumnUnits}
setOpacity={setOpacity} setOpacity={setOpacity}
selectedNullZeroValue={selectedNullZeroValue} selectedNullZeroValue={selectedNullZeroValue}
setSelectedNullZeroValue={setSelectedNullZeroValue} setSelectedNullZeroValue={setSelectedNullZeroValue}

View File

@ -16,6 +16,7 @@ function TablePanelWrapper({
data={panelData} data={panelData}
query={widget.query} query={widget.query}
thresholds={thresholds} thresholds={thresholds}
columnUnits={widget.columnUnits}
tableProcessedDataRef={tableProcessedDataRef} tableProcessedDataRef={tableProcessedDataRef}
// eslint-disable-next-line react/jsx-props-no-spreading // eslint-disable-next-line react/jsx-props-no-spreading
{...GRID_TABLE_CONFIG} {...GRID_TABLE_CONFIG}

View File

@ -83,6 +83,9 @@ export interface WidgetRow {
description: string; description: string;
} }
export interface ColumnUnit {
[key: string]: string;
}
export interface IBaseWidget { export interface IBaseWidget {
isStacked: boolean; isStacked: boolean;
id: string; id: string;
@ -98,6 +101,7 @@ export interface IBaseWidget {
softMin: number | null; softMin: number | null;
softMax: number | null; softMax: number | null;
fillSpans?: boolean; fillSpans?: boolean;
columnUnits?: ColumnUnit;
selectedLogFields: IField[] | null; selectedLogFields: IField[] | null;
selectedTracesFields: BaseAutocompleteData[] | null; selectedTracesFields: BaseAutocompleteData[] | null;
} }