chore: replace y axis unit selector in dashboards

This commit is contained in:
amlannandy 2025-08-27 16:36:00 +07:00
parent 5c22ef773d
commit 9ce228ca13
8 changed files with 171 additions and 24 deletions

View File

@ -1,7 +1,7 @@
import './Explorer.styles.scss'; import './Explorer.styles.scss';
import * as Sentry from '@sentry/react'; import * as Sentry from '@sentry/react';
import { Switch } from 'antd'; import { Switch, Tooltip } from 'antd';
import logEvent from 'api/common/logEvent'; import logEvent from 'api/common/logEvent';
import { QueryBuilderV2 } from 'components/QueryBuilderV2/QueryBuilderV2'; import { QueryBuilderV2 } from 'components/QueryBuilderV2/QueryBuilderV2';
import WarningPopover from 'components/WarningPopover/WarningPopover'; import WarningPopover from 'components/WarningPopover/WarningPopover';
@ -25,10 +25,10 @@ import { generateExportToDashboardLink } from 'utils/dashboard/generateExportToD
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { MetricsExplorerEventKeys, MetricsExplorerEvents } from '../events'; import { MetricsExplorerEventKeys, MetricsExplorerEvents } from '../events';
// import QuerySection from './QuerySection'; import MetricDetails from '../MetricDetails/MetricDetails';
import TimeSeries from './TimeSeries'; import TimeSeries from './TimeSeries';
import { ExplorerTabs } from './types'; import { ExplorerTabs } from './types';
import { splitQueryIntoOneChartPerQuery } from './utils'; import { splitQueryIntoOneChartPerQuery, useGetMetricUnits } from './utils';
const ONE_CHART_PER_QUERY_ENABLED_KEY = 'isOneChartPerQueryEnabled'; const ONE_CHART_PER_QUERY_ENABLED_KEY = 'isOneChartPerQueryEnabled';
@ -40,6 +40,31 @@ function Explorer(): JSX.Element {
currentQuery, currentQuery,
} = useQueryBuilder(); } = useQueryBuilder();
const { safeNavigate } = useSafeNavigate(); const { safeNavigate } = useSafeNavigate();
const [isMetricDetailsOpen, setIsMetricDetailsOpen] = useState(false);
const metricNames = useMemo(
() =>
stagedQuery?.builder.queryData.map(
(query) => query.aggregateAttribute?.key ?? '',
) ?? [],
[stagedQuery],
);
const {
units,
metrics,
isLoading: isMetricUnitsLoading,
isError: isMetricUnitsError,
} = useGetMetricUnits(metricNames);
const areAllMetricUnitsSame = useMemo(
() =>
!isMetricUnitsLoading &&
!isMetricUnitsError &&
units.length > 0 &&
units.every((unit) => unit === units[0]),
[units, isMetricUnitsLoading, isMetricUnitsError],
);
const [searchParams, setSearchParams] = useSearchParams(); const [searchParams, setSearchParams] = useSearchParams();
const isOneChartPerQueryEnabled = const isOneChartPerQueryEnabled =
@ -48,7 +73,31 @@ function Explorer(): JSX.Element {
const [showOneChartPerQuery, toggleShowOneChartPerQuery] = useState( const [showOneChartPerQuery, toggleShowOneChartPerQuery] = useState(
isOneChartPerQueryEnabled, isOneChartPerQueryEnabled,
); );
const [disableOneChartPerQuery, toggleDisableOneChartPerQuery] = useState(
false,
);
const [selectedTab] = useState<ExplorerTabs>(ExplorerTabs.TIME_SERIES); const [selectedTab] = useState<ExplorerTabs>(ExplorerTabs.TIME_SERIES);
const [yAxisUnit, setYAxisUnit] = useState<string>('');
useEffect(() => {
if (units.length === 0) {
setYAxisUnit('');
} else if (units.length === 1 && units[0] !== '') {
setYAxisUnit(units[0]);
} else if (areAllMetricUnitsSame && units[0] !== '') {
setYAxisUnit(units[0]);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [JSON.stringify(units), areAllMetricUnitsSame]);
useEffect(() => {
if (units.length > 1 && !areAllMetricUnitsSame) {
toggleShowOneChartPerQuery(true);
toggleDisableOneChartPerQuery(true);
} else {
toggleDisableOneChartPerQuery(false);
}
}, [units, areAllMetricUnitsSame]);
const handleToggleShowOneChartPerQuery = (): void => { const handleToggleShowOneChartPerQuery = (): void => {
toggleShowOneChartPerQuery(!showOneChartPerQuery); toggleShowOneChartPerQuery(!showOneChartPerQuery);
@ -68,15 +117,20 @@ function Explorer(): JSX.Element {
[updateAllQueriesOperators], [updateAllQueriesOperators],
); );
const exportDefaultQuery = useMemo( const exportDefaultQuery = useMemo(() => {
() => const query = updateAllQueriesOperators(
updateAllQueriesOperators( currentQuery || initialQueriesMap[DataSource.METRICS],
currentQuery || initialQueriesMap[DataSource.METRICS], PANEL_TYPES.TIME_SERIES,
PANEL_TYPES.TIME_SERIES, DataSource.METRICS,
DataSource.METRICS, );
), if (yAxisUnit) {
[currentQuery, updateAllQueriesOperators], return {
); ...query,
unit: yAxisUnit,
};
}
return query;
}, [currentQuery, updateAllQueriesOperators, yAxisUnit]);
useShareBuilderUrl({ defaultValue: defaultQuery }); useShareBuilderUrl({ defaultValue: defaultQuery });
@ -90,8 +144,16 @@ function Explorer(): JSX.Element {
const widgetId = uuid(); const widgetId = uuid();
let query = queryToExport || exportDefaultQuery;
if (yAxisUnit) {
query = {
...query,
unit: yAxisUnit,
};
}
const dashboardEditView = generateExportToDashboardLink({ const dashboardEditView = generateExportToDashboardLink({
query: queryToExport || exportDefaultQuery, query,
panelType: PANEL_TYPES.TIME_SERIES, panelType: PANEL_TYPES.TIME_SERIES,
dashboardId: dashboard.id, dashboardId: dashboard.id,
widgetId, widgetId,
@ -99,7 +161,7 @@ function Explorer(): JSX.Element {
safeNavigate(dashboardEditView); safeNavigate(dashboardEditView);
}, },
[exportDefaultQuery, safeNavigate], [exportDefaultQuery, safeNavigate, yAxisUnit],
); );
const splitedQueries = useMemo( const splitedQueries = useMemo(
@ -129,11 +191,17 @@ function Explorer(): JSX.Element {
<div className="explore-header"> <div className="explore-header">
<div className="explore-header-left-actions"> <div className="explore-header-left-actions">
<span>1 chart/query</span> <span>1 chart/query</span>
<Switch <Tooltip
checked={showOneChartPerQuery} open={disableOneChartPerQuery ? undefined : false}
onChange={handleToggleShowOneChartPerQuery} title="One chart per query cannot be disabled for multiple queries with different units."
size="small" >
/> <Switch
checked={showOneChartPerQuery}
onChange={handleToggleShowOneChartPerQuery}
disabled={disableOneChartPerQuery}
size="small"
/>
</Tooltip>
</div> </div>
<div className="explore-header-right-actions"> <div className="explore-header-right-actions">
{!isEmpty(warning) && <WarningPopover warningData={warning} />} {!isEmpty(warning) && <WarningPopover warningData={warning} />}
@ -174,6 +242,15 @@ function Explorer(): JSX.Element {
<TimeSeries <TimeSeries
showOneChartPerQuery={showOneChartPerQuery} showOneChartPerQuery={showOneChartPerQuery}
setWarning={setWarning} setWarning={setWarning}
areAllMetricUnitsSame={areAllMetricUnitsSame}
isMetricUnitsLoading={isMetricUnitsLoading}
isMetricUnitsError={isMetricUnitsError}
metricUnits={units}
metricNames={metricNames}
metrics={metrics}
setIsMetricDetailsOpen={setIsMetricDetailsOpen}
yAxisUnit={yAxisUnit}
setYAxisUnit={setYAxisUnit}
/> />
)} )}
{/* TODO: Enable once we have resolved all related metrics issues */} {/* TODO: Enable once we have resolved all related metrics issues */}
@ -190,6 +267,14 @@ function Explorer(): JSX.Element {
isOneChartPerQuery={false} isOneChartPerQuery={false}
splitedQueries={splitedQueries} splitedQueries={splitedQueries}
/> />
{isMetricDetailsOpen && (
<MetricDetails
metricName={metricNames[0]}
isOpen={isMetricDetailsOpen}
onClose={(): void => setIsMetricDetailsOpen(false)}
isModalTimeSelection={false}
/>
)}
</Sentry.ErrorBoundary> </Sentry.ErrorBoundary>
); );
} }

View File

@ -1,3 +1,4 @@
import { MetricDetails } from 'api/metricsExplorer/getMetricDetails';
import { RelatedMetric } from 'api/metricsExplorer/getRelatedMetrics'; import { RelatedMetric } from 'api/metricsExplorer/getRelatedMetrics';
import { Dispatch, SetStateAction } from 'react'; import { Dispatch, SetStateAction } from 'react';
import { UseQueryResult } from 'react-query'; import { UseQueryResult } from 'react-query';
@ -12,6 +13,15 @@ export enum ExplorerTabs {
export interface TimeSeriesProps { export interface TimeSeriesProps {
showOneChartPerQuery: boolean; showOneChartPerQuery: boolean;
setWarning: Dispatch<SetStateAction<Warning | undefined>>; setWarning: Dispatch<SetStateAction<Warning | undefined>>;
areAllMetricUnitsSame: boolean;
isMetricUnitsLoading: boolean;
isMetricUnitsError: boolean;
metricUnits: string[];
metricNames: string[];
metrics: (MetricDetails | undefined)[];
setIsMetricDetailsOpen: (isOpen: boolean) => void;
yAxisUnit: string;
setYAxisUnit: (unit: string) => void;
} }
export interface RelatedMetricsProps { export interface RelatedMetricsProps {

View File

@ -139,7 +139,7 @@ function MetricDetails({
icon={<Crosshair size={18} />} icon={<Crosshair size={18} />}
onClick={(): void => { onClick={(): void => {
if (metric?.name) { if (metric?.name) {
openInspectModal(metric.name); openInspectModal?.(metric.name);
} }
}} }}
data-testid="inspect-metric-button" data-testid="inspect-metric-button"

View File

@ -11,7 +11,7 @@ export interface MetricDetailsProps {
isOpen: boolean; isOpen: boolean;
metricName: string | null; metricName: string | null;
isModalTimeSelection: boolean; isModalTimeSelection: boolean;
openInspectModal: (metricName: string) => void; openInspectModal?: (metricName: string) => void;
} }
export interface DashboardsAndAlertsPopoverProps { export interface DashboardsAndAlertsPopoverProps {

View File

@ -190,7 +190,8 @@
text-transform: uppercase; text-transform: uppercase;
} }
.y-axis-unit-selector { .y-axis-unit-selector,
.y-axis-unit-selector-v2 {
margin-top: 16px; margin-top: 16px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -223,6 +224,21 @@
} }
} }
} }
.y-axis-unit-selector-v2 {
.y-axis-unit-selector-component {
.ant-select {
width: 100%;
border-radius: 2px;
background: var(--bg-ink-300);
.ant-select-selector {
border: 1px solid var(--bg-slate-400);
}
}
}
}
.soft-min-max { .soft-min-max {
display: flex; display: flex;
align-items: center; align-items: center;

View File

@ -0,0 +1,23 @@
import { Typography } from 'antd';
import YAxisUnitSelectorComponent from 'components/YAxisUnitSelector';
import { Dispatch, SetStateAction } from 'react';
type OnSelectType = Dispatch<SetStateAction<string>> | ((val: string) => void);
function YAxisUnitSelectorV2({
defaultValue,
onSelect,
fieldLabel,
}: {
defaultValue: string;
onSelect: OnSelectType;
fieldLabel: string;
}): JSX.Element {
return (
<div className="y-axis-unit-selector-v2">
<Typography.Text className="heading">{fieldLabel}</Typography.Text>
<YAxisUnitSelectorComponent value={defaultValue} onChange={onSelect} />
</div>
);
}
export default YAxisUnitSelectorV2;

View File

@ -63,7 +63,7 @@ import LegendColors from './LegendColors/LegendColors';
import ThresholdSelector from './Threshold/ThresholdSelector'; import ThresholdSelector from './Threshold/ThresholdSelector';
import { ThresholdProps } from './Threshold/types'; import { ThresholdProps } from './Threshold/types';
import { timePreferance } from './timeItems'; import { timePreferance } from './timeItems';
import YAxisUnitSelector from './YAxisUnitSelector'; import YAxisUnitSelector from './YAxisUnitSelectorV2';
const { TextArea } = Input; const { TextArea } = Input;
const { Option } = Select; const { Option } = Select;
@ -347,7 +347,6 @@ function RightContainer({
{allowYAxisUnit && ( {allowYAxisUnit && (
<YAxisUnitSelector <YAxisUnitSelector
onSelect={setYAxisUnit} onSelect={setYAxisUnit}
value={yAxisUnit || ''}
fieldLabel={ fieldLabel={
selectedGraphType === PanelDisplay.VALUE || selectedGraphType === PanelDisplay.VALUE ||
selectedGraphType === PanelDisplay.PIE selectedGraphType === PanelDisplay.PIE

View File

@ -49,6 +49,7 @@ import {
import { Props } from 'types/api/dashboard/update'; import { Props } from 'types/api/dashboard/update';
import { IField } from 'types/api/logs/fields'; import { IField } from 'types/api/logs/fields';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard'; import { EQueryType } from 'types/common/dashboard';
import { DataSource } from 'types/common/queryBuilder'; import { DataSource } from 'types/common/queryBuilder';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
@ -301,6 +302,19 @@ function NewWidget({
contextLinks, contextLinks,
]); ]);
useEffect(() => {
const compositeQuery = query.get('compositeQuery');
if (compositeQuery) {
try {
const decoded = decodeURIComponent(compositeQuery);
const parsedQuery = JSON.parse(decoded) as Query;
setYAxisUnit(parsedQuery.unit || 'none');
} catch (error) {
setYAxisUnit('none');
}
}
}, [query]);
const closeModal = (): void => { const closeModal = (): void => {
setSaveModal(false); setSaveModal(false);
setDiscardModal(false); setDiscardModal(false);