mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-13 23:15:57 +08:00
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:
parent
be9c3f0697
commit
2145e353c8
@ -1,12 +1,13 @@
|
||||
import { ExclamationCircleFilled } from '@ant-design/icons';
|
||||
import { Space, Tooltip } from 'antd';
|
||||
import { getYAxisFormattedValue } from 'components/Graph/yAxisConfig';
|
||||
import { Events } from 'constants/events';
|
||||
import { QueryTable } from 'container/QueryTable';
|
||||
import {
|
||||
createTableColumnsFromQuery,
|
||||
RowData,
|
||||
} 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 { useTranslation } from 'react-i18next';
|
||||
import { eventEmitter } from 'utils/getEventEmitter';
|
||||
@ -19,11 +20,12 @@ function GridTableComponent({
|
||||
data,
|
||||
query,
|
||||
thresholds,
|
||||
columnUnits,
|
||||
tableProcessedDataRef,
|
||||
...props
|
||||
}: GridTableComponentProps): JSX.Element {
|
||||
const { t } = useTranslation(['valueGraph']);
|
||||
const { columns, dataSource } = useMemo(
|
||||
const { columns, dataSource: originalDataSource } = useMemo(
|
||||
() =>
|
||||
createTableColumnsFromQuery({
|
||||
query,
|
||||
@ -31,7 +33,6 @@ function GridTableComponent({
|
||||
}),
|
||||
[data, query],
|
||||
);
|
||||
|
||||
const createDataInCorrectFormat = useCallback(
|
||||
(dataSource: RowData[]): RowData[] =>
|
||||
dataSource.map((d) => {
|
||||
@ -52,6 +53,35 @@ function GridTableComponent({
|
||||
[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(() => {
|
||||
if (tableProcessedDataRef) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
|
@ -5,11 +5,13 @@ import {
|
||||
ThresholdProps,
|
||||
} from 'container/NewWidget/RightContainer/Threshold/types';
|
||||
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
||||
import { ColumnUnit } from 'types/api/dashboard/getAll';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
export type GridTableComponentProps = {
|
||||
query: Query;
|
||||
thresholds?: ThresholdProps[];
|
||||
columnUnits?: ColumnUnit;
|
||||
tableProcessedDataRef?: React.MutableRefObject<RowData[]>;
|
||||
} & Pick<LogsExplorerTableProps, 'data'> &
|
||||
Omit<TableProps<RowData>, 'columns' | 'dataSource'>;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
||||
);
|
||||
}
|
@ -13,14 +13,17 @@ const findCategoryByName = (
|
||||
): Record<string, string> | undefined =>
|
||||
find(flattenedCategories, (option) => option.name === searchValue);
|
||||
|
||||
type OnSelectType = Dispatch<SetStateAction<string>> | ((val: string) => void);
|
||||
function YAxisUnitSelector({
|
||||
defaultValue,
|
||||
onSelect,
|
||||
fieldLabel,
|
||||
handleClear,
|
||||
}: {
|
||||
defaultValue: string;
|
||||
onSelect: Dispatch<SetStateAction<string>>;
|
||||
onSelect: OnSelectType;
|
||||
fieldLabel: string;
|
||||
handleClear?: () => void;
|
||||
}): JSX.Element {
|
||||
const onSelectHandler = (selectedValue: string): void => {
|
||||
onSelect(findCategoryByName(selectedValue)?.id || '');
|
||||
@ -35,7 +38,9 @@ function YAxisUnitSelector({
|
||||
style={{ width: '100%' }}
|
||||
rootClassName="y-axis-root-popover"
|
||||
options={options}
|
||||
allowClear
|
||||
defaultValue={findCategoryById(defaultValue)?.name}
|
||||
onClear={handleClear}
|
||||
onSelect={onSelectHandler}
|
||||
filterOption={(inputValue, option): boolean => {
|
||||
if (option) {
|
||||
@ -46,10 +51,14 @@ function YAxisUnitSelector({
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
<Input placeholder="Unit" allowClear rootClassName="input" />
|
||||
<Input placeholder="Unit" rootClassName="input" />
|
||||
</AutoComplete>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default YAxisUnitSelector;
|
||||
|
||||
YAxisUnitSelector.defaultProps = {
|
||||
handleClear: (): void => {},
|
||||
};
|
||||
|
@ -68,7 +68,7 @@ export const panelTypeVsFillSpan: { [key in PANEL_TYPES]: boolean } = {
|
||||
export const panelTypeVsYAxisUnit: { [key in PANEL_TYPES]: boolean } = {
|
||||
[PANEL_TYPES.TIME_SERIES]: true,
|
||||
[PANEL_TYPES.VALUE]: true,
|
||||
[PANEL_TYPES.TABLE]: true,
|
||||
[PANEL_TYPES.TABLE]: false,
|
||||
[PANEL_TYPES.LIST]: false,
|
||||
[PANEL_TYPES.PIE]: false,
|
||||
[PANEL_TYPES.BAR]: true,
|
||||
@ -99,3 +99,16 @@ export const panelTypeVsPanelTimePreferences: {
|
||||
[PANEL_TYPES.TRACE]: false,
|
||||
[PANEL_TYPES.EMPTY_WIDGET]: false,
|
||||
} 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;
|
||||
|
@ -18,10 +18,12 @@ import {
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { ColumnUnit, Widgets } from 'types/api/dashboard/getAll';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import { ColumnUnitSelector } from './ColumnUnitSelector/ColumnUnitSelector';
|
||||
import {
|
||||
panelTypeVsColumnUnitPreferences,
|
||||
panelTypeVsCreateAlert,
|
||||
panelTypeVsFillSpan,
|
||||
panelTypeVsPanelTimePreferences,
|
||||
@ -57,6 +59,8 @@ function RightContainer({
|
||||
softMin,
|
||||
setSoftMax,
|
||||
setSoftMin,
|
||||
columnUnits,
|
||||
setColumnUnits,
|
||||
}: RightContainerProps): JSX.Element {
|
||||
const onChangeHandler = useCallback(
|
||||
(setFunc: Dispatch<SetStateAction<string>>, value: string) => {
|
||||
@ -78,6 +82,9 @@ function RightContainer({
|
||||
const allowPanelTimePreference =
|
||||
panelTypeVsPanelTimePreferences[selectedGraph];
|
||||
|
||||
const allowPanelColumnPreference =
|
||||
panelTypeVsColumnUnitPreferences[selectedGraph];
|
||||
|
||||
const { currentQuery } = useQueryBuilder();
|
||||
|
||||
const [graphTypes, setGraphTypes] = useState<ItemsProps[]>(GraphTypes);
|
||||
@ -179,6 +186,13 @@ function RightContainer({
|
||||
</>
|
||||
)}
|
||||
|
||||
{allowPanelColumnPreference && (
|
||||
<ColumnUnitSelector
|
||||
columnUnits={columnUnits}
|
||||
setColumnUnits={setColumnUnits}
|
||||
/>
|
||||
)}
|
||||
|
||||
{allowYAxisUnit && (
|
||||
<YAxisUnitSelector
|
||||
defaultValue={yAxisUnit}
|
||||
@ -258,6 +272,8 @@ interface RightContainerProps {
|
||||
setIsFillSpans: Dispatch<SetStateAction<boolean>>;
|
||||
softMin: number | null;
|
||||
softMax: number | null;
|
||||
columnUnits: ColumnUnit;
|
||||
setColumnUnits: Dispatch<SetStateAction<ColumnUnit>>;
|
||||
setSoftMin: Dispatch<SetStateAction<number | null>>;
|
||||
setSoftMax: Dispatch<SetStateAction<number | null>>;
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { generatePath, useParams } from 'react-router-dom';
|
||||
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 { EQueryType } from 'types/common/dashboard';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
@ -156,6 +156,10 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
||||
: selectedWidget?.softMax || 0,
|
||||
);
|
||||
|
||||
const [columnUnits, setColumnUnits] = useState<ColumnUnit>(
|
||||
selectedWidget?.columnUnits || {},
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedWidget((prev) => {
|
||||
if (!prev) {
|
||||
@ -174,11 +178,13 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
||||
softMin,
|
||||
softMax,
|
||||
fillSpans: isFillSpans,
|
||||
columnUnits,
|
||||
selectedLogFields,
|
||||
selectedTracesFields,
|
||||
};
|
||||
});
|
||||
}, [
|
||||
columnUnits,
|
||||
currentQuery,
|
||||
description,
|
||||
isFillSpans,
|
||||
@ -284,6 +290,7 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
||||
panelTypes: graphType,
|
||||
query: currentQuery,
|
||||
thresholds: selectedWidget?.thresholds,
|
||||
columnUnits: selectedWidget?.columnUnits,
|
||||
softMin: selectedWidget?.softMin || 0,
|
||||
softMax: selectedWidget?.softMax || 0,
|
||||
fillSpans: selectedWidget?.fillSpans,
|
||||
@ -305,6 +312,7 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
||||
panelTypes: graphType,
|
||||
query: currentQuery,
|
||||
thresholds: selectedWidget?.thresholds,
|
||||
columnUnits: selectedWidget?.columnUnits,
|
||||
softMin: selectedWidget?.softMin || 0,
|
||||
softMax: selectedWidget?.softMax || 0,
|
||||
fillSpans: selectedWidget?.fillSpans,
|
||||
@ -483,6 +491,8 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
||||
setStacked={setStacked}
|
||||
opacity={opacity}
|
||||
yAxisUnit={yAxisUnit}
|
||||
columnUnits={columnUnits}
|
||||
setColumnUnits={setColumnUnits}
|
||||
setOpacity={setOpacity}
|
||||
selectedNullZeroValue={selectedNullZeroValue}
|
||||
setSelectedNullZeroValue={setSelectedNullZeroValue}
|
||||
|
@ -16,6 +16,7 @@ function TablePanelWrapper({
|
||||
data={panelData}
|
||||
query={widget.query}
|
||||
thresholds={thresholds}
|
||||
columnUnits={widget.columnUnits}
|
||||
tableProcessedDataRef={tableProcessedDataRef}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...GRID_TABLE_CONFIG}
|
||||
|
@ -83,6 +83,9 @@ export interface WidgetRow {
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface ColumnUnit {
|
||||
[key: string]: string;
|
||||
}
|
||||
export interface IBaseWidget {
|
||||
isStacked: boolean;
|
||||
id: string;
|
||||
@ -98,6 +101,7 @@ export interface IBaseWidget {
|
||||
softMin: number | null;
|
||||
softMax: number | null;
|
||||
fillSpans?: boolean;
|
||||
columnUnits?: ColumnUnit;
|
||||
selectedLogFields: IField[] | null;
|
||||
selectedTracesFields: BaseAutocompleteData[] | null;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user