mirror of
https://github.com/SigNoz/signoz.git
synced 2026-05-14 14:10:32 +01:00
Compare commits
3 Commits
platform-t
...
chore/aler
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fcd0376125 | ||
|
|
b0c797b507 | ||
|
|
fe586228d3 |
@@ -91,7 +91,6 @@ function ChartPreview({
|
||||
const renderQBChartPreview = (): JSX.Element => (
|
||||
<ChartPreviewComponent
|
||||
headline={headline}
|
||||
name=""
|
||||
query={stagedQuery}
|
||||
selectedInterval={globalSelectedInterval}
|
||||
alertDef={alertDef}
|
||||
@@ -107,7 +106,6 @@ function ChartPreview({
|
||||
const renderPromAndChQueryChartPreview = (): JSX.Element => (
|
||||
<ChartPreviewComponent
|
||||
headline={headline}
|
||||
name="Chart Preview"
|
||||
query={stagedQuery}
|
||||
alertDef={alertDef}
|
||||
selectedInterval={globalSelectedInterval}
|
||||
|
||||
@@ -17,7 +17,6 @@ import { CreateAlertProvider } from '../../context';
|
||||
import ChartPreview from '../ChartPreview/ChartPreview';
|
||||
|
||||
const REQUESTS_PER_SEC = 'requests/sec';
|
||||
const CHART_PREVIEW_NAME = 'Chart Preview';
|
||||
const QUERY_TYPE_TEST_ID = 'query-type';
|
||||
const GRAPH_TYPE_TEST_ID = 'graph-type';
|
||||
const CHART_PREVIEW_COMPONENT_TEST_ID = 'chart-preview-component';
|
||||
@@ -34,7 +33,6 @@ jest.mock(
|
||||
return (
|
||||
<div data-testid={CHART_PREVIEW_COMPONENT_TEST_ID}>
|
||||
<div data-testid="headline">{props.headline}</div>
|
||||
<div data-testid="name">{props.name}</div>
|
||||
<div data-testid={QUERY_TYPE_TEST_ID}>{props.query?.queryType}</div>
|
||||
<div data-testid="selected-interval">
|
||||
{props.selectedInterval?.startTime}
|
||||
@@ -175,12 +173,6 @@ describe('ChartPreview', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('renders QueryBuilder chart preview with empty name when query type is QUERY_BUILDER', () => {
|
||||
renderChartPreview();
|
||||
|
||||
expect(screen.getByTestId('name')).toHaveTextContent('');
|
||||
});
|
||||
|
||||
it('renders QueryBuilder chart preview with correct props', () => {
|
||||
renderChartPreview();
|
||||
|
||||
@@ -191,7 +183,6 @@ describe('ChartPreview', () => {
|
||||
expect(screen.getByTestId(GRAPH_TYPE_TEST_ID)).toHaveTextContent(
|
||||
PANEL_TYPES.TIME_SERIES,
|
||||
);
|
||||
expect(screen.getByTestId('name')).toHaveTextContent('');
|
||||
expect(screen.getByTestId('headline')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('selected-interval')).toBeInTheDocument();
|
||||
});
|
||||
@@ -214,7 +205,6 @@ describe('ChartPreview', () => {
|
||||
expect(
|
||||
screen.getByTestId(CHART_PREVIEW_COMPONENT_TEST_ID),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByTestId('name')).toHaveTextContent(CHART_PREVIEW_NAME);
|
||||
expect(screen.getByTestId(QUERY_TYPE_TEST_ID)).toHaveTextContent(
|
||||
EQueryType.PROM,
|
||||
);
|
||||
@@ -238,7 +228,6 @@ describe('ChartPreview', () => {
|
||||
expect(
|
||||
screen.getByTestId(CHART_PREVIEW_COMPONENT_TEST_ID),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByTestId('name')).toHaveTextContent(CHART_PREVIEW_NAME);
|
||||
expect(screen.getByTestId(QUERY_TYPE_TEST_ID)).toHaveTextContent(
|
||||
EQueryType.CLICKHOUSE,
|
||||
);
|
||||
|
||||
@@ -15,12 +15,11 @@ import {
|
||||
LineStyle,
|
||||
} from 'lib/uPlotV2/config/types';
|
||||
import { UPlotConfigBuilder } from 'lib/uPlotV2/config/UPlotConfigBuilder';
|
||||
import { isInvalidPlotValue } from 'lib/uPlotV2/utils/dataUtils';
|
||||
import { hasSingleVisiblePoint } from 'lib/uPlotV2/utils/dataUtils';
|
||||
import get from 'lodash-es/get';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { QueryData } from 'types/api/widgets/getQuery';
|
||||
|
||||
import { PanelMode } from '../types';
|
||||
import { buildBaseConfig } from '../utils/baseConfigBuilder';
|
||||
@@ -35,22 +34,6 @@ export const prepareChartData = (
|
||||
return [timestampArr, ...yAxisValuesArr];
|
||||
};
|
||||
|
||||
function hasSingleVisiblePointForSeries(series: QueryData): boolean {
|
||||
const rawValues = series.values ?? [];
|
||||
let validPointCount = 0;
|
||||
|
||||
for (const [, rawValue] of rawValues) {
|
||||
if (!isInvalidPlotValue(rawValue)) {
|
||||
validPointCount += 1;
|
||||
if (validPointCount > 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export const prepareUPlotConfig = ({
|
||||
widget,
|
||||
isDarkMode,
|
||||
@@ -107,7 +90,7 @@ export const prepareUPlotConfig = ({
|
||||
}
|
||||
|
||||
apiResponse.data.result.forEach((series) => {
|
||||
const hasSingleValidPoint = hasSingleVisiblePointForSeries(series);
|
||||
const hasSingleValidPoint = hasSingleVisiblePoint(series.values);
|
||||
const baseLabelName = getLabelName(
|
||||
series.metric,
|
||||
series.queryName || '', // query
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
import { useMemo } from 'react';
|
||||
import { Timezone } from 'components/CustomTimePicker/timezoneUtils';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import BarChart from 'container/DashboardContainer/visualization/charts/BarChart/BarChart';
|
||||
import TimeSeries from 'container/DashboardContainer/visualization/charts/TimeSeries/TimeSeries';
|
||||
import { ThresholdProps } from 'container/NewWidget/RightContainer/Threshold/types';
|
||||
import { LegendPosition } from 'lib/uPlotV2/components/types';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import uPlot from 'uplot';
|
||||
|
||||
import { AlertChartPanelType, buildAlertChartConfig } from './utils';
|
||||
|
||||
const CHART_ID_PREFIX = 'alert_legend_widget';
|
||||
|
||||
const buildChartId = (alertId?: string): string =>
|
||||
alertId ? `${CHART_ID_PREFIX}_${alertId}` : CHART_ID_PREFIX;
|
||||
|
||||
// Panel types that render through the UPlotConfigBuilder pipeline.
|
||||
// To support a new modern-chart panel type, add an entry here and extend
|
||||
// `AlertChartPanelType` / `buildAlertChartConfig` to handle its series setup.
|
||||
const SUPPORTED_CHARTS: Record<
|
||||
AlertChartPanelType,
|
||||
typeof TimeSeries | typeof BarChart
|
||||
> = {
|
||||
[PANEL_TYPES.TIME_SERIES]: TimeSeries,
|
||||
[PANEL_TYPES.BAR]: BarChart,
|
||||
};
|
||||
|
||||
const isSupportedPanelType = (
|
||||
panelType: PANEL_TYPES,
|
||||
): panelType is AlertChartPanelType => panelType in SUPPORTED_CHARTS;
|
||||
|
||||
export interface ChartContentProps {
|
||||
panelType: PANEL_TYPES;
|
||||
alertId?: string;
|
||||
query: Query;
|
||||
apiResponse?: MetricRangePayloadProps;
|
||||
data: uPlot.AlignedData;
|
||||
thresholds: ThresholdProps[];
|
||||
yAxisUnit: string;
|
||||
legendPosition: LegendPosition;
|
||||
isDarkMode: boolean;
|
||||
timezone: Timezone;
|
||||
width: number;
|
||||
height: number;
|
||||
minTimeScale?: number;
|
||||
maxTimeScale?: number;
|
||||
onDragSelect: (start: number, end: number) => void;
|
||||
}
|
||||
|
||||
export default function ChartContent({
|
||||
panelType,
|
||||
alertId,
|
||||
query,
|
||||
thresholds,
|
||||
apiResponse,
|
||||
data,
|
||||
yAxisUnit,
|
||||
isDarkMode,
|
||||
timezone,
|
||||
minTimeScale,
|
||||
maxTimeScale,
|
||||
onDragSelect,
|
||||
width,
|
||||
height,
|
||||
legendPosition,
|
||||
}: ChartContentProps): JSX.Element | null {
|
||||
const supported = isSupportedPanelType(panelType);
|
||||
|
||||
const config = useMemo(
|
||||
() =>
|
||||
buildAlertChartConfig({
|
||||
id: buildChartId(alertId),
|
||||
panelType: panelType as AlertChartPanelType,
|
||||
query,
|
||||
thresholds,
|
||||
apiResponse,
|
||||
yAxisUnit,
|
||||
isDarkMode,
|
||||
timezone,
|
||||
minTimeScale,
|
||||
maxTimeScale,
|
||||
onDragSelect,
|
||||
}),
|
||||
[
|
||||
alertId,
|
||||
panelType,
|
||||
query,
|
||||
thresholds,
|
||||
apiResponse,
|
||||
yAxisUnit,
|
||||
isDarkMode,
|
||||
timezone,
|
||||
minTimeScale,
|
||||
maxTimeScale,
|
||||
onDragSelect,
|
||||
],
|
||||
);
|
||||
|
||||
if (!supported) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const Component = SUPPORTED_CHARTS[panelType];
|
||||
|
||||
return (
|
||||
<Component
|
||||
config={config}
|
||||
data={data}
|
||||
width={width}
|
||||
height={height}
|
||||
legendConfig={{ position: legendPosition }}
|
||||
canPinTooltip
|
||||
yAxisUnit={yAxisUnit}
|
||||
timezone={timezone}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -15,8 +15,6 @@ import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import AnomalyAlertEvaluationView from 'container/AnomalyAlertEvaluationView';
|
||||
import { INITIAL_CRITICAL_THRESHOLD } from 'container/CreateAlertV2/context/constants';
|
||||
import { Threshold } from 'container/CreateAlertV2/context/types';
|
||||
import { getLocalStorageGraphVisibilityState } from 'container/GridCardLayout/GridCard/utils';
|
||||
import GridPanelSwitch from 'container/GridPanelSwitch';
|
||||
import { populateMultipleResults } from 'container/NewWidget/LeftContainer/WidgetGraph/util';
|
||||
import { getFormatNameByOptionId } from 'container/NewWidget/RightContainer/alertFomatCategories';
|
||||
import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems';
|
||||
@@ -32,7 +30,7 @@ import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import GetMinMax from 'lib/getMinMax';
|
||||
import getTimeString from 'lib/getTimeString';
|
||||
import history from 'lib/history';
|
||||
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
|
||||
import { LegendPosition } from 'lib/uPlotV2/components/types';
|
||||
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
|
||||
import { isEmpty } from 'lodash-es';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
@@ -41,7 +39,6 @@ import { UpdateTimeInterval } from 'store/actions';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { Warning } from 'types/api';
|
||||
import { AlertDef } from 'types/api/alerts/def';
|
||||
import { LegendPosition } from 'types/api/dashboard/getAll';
|
||||
import APIError from 'types/api/error';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
@@ -52,13 +49,16 @@ import { getSortedSeriesData } from 'utils/getSortedSeriesData';
|
||||
import { getTimeRange } from 'utils/getTimeRange';
|
||||
|
||||
import { AlertDetectionTypes } from '..';
|
||||
import ChartContent from './ChartContent';
|
||||
import { ChartContainer } from './styles';
|
||||
import { getThresholds } from './utils';
|
||||
|
||||
import './ChartPreview.styles.scss';
|
||||
|
||||
// Height reserved for the `.chart-preview-header` strip rendered above the chart.
|
||||
const CHART_PREVIEW_HEADER_HEIGHT = 48;
|
||||
|
||||
export interface ChartPreviewProps {
|
||||
name: string;
|
||||
query: Query | null;
|
||||
graphType?: PANEL_TYPES;
|
||||
selectedTime?: timePreferenceType;
|
||||
@@ -77,7 +77,6 @@ export interface ChartPreviewProps {
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
function ChartPreview({
|
||||
name,
|
||||
query,
|
||||
graphType = PANEL_TYPES.TIME_SERIES,
|
||||
selectedTime = 'GLOBAL_TIME',
|
||||
@@ -113,14 +112,6 @@ function ChartPreview({
|
||||
|
||||
const [minTimeScale, setMinTimeScale] = useState<number>();
|
||||
const [maxTimeScale, setMaxTimeScale] = useState<number>();
|
||||
const [graphVisibility, setGraphVisibility] = useState<boolean[]>([]);
|
||||
const legendScrollPositionRef = useRef<{
|
||||
scrollTop: number;
|
||||
scrollLeft: number;
|
||||
}>({
|
||||
scrollTop: 0,
|
||||
scrollLeft: 0,
|
||||
});
|
||||
const { currentQuery } = useQueryBuilder();
|
||||
|
||||
const {
|
||||
@@ -219,18 +210,6 @@ function ChartPreview({
|
||||
setMaxTimeScale(endTime);
|
||||
}, [maxTime, minTime, globalSelectedInterval, queryResponse, setQueryStatus]);
|
||||
|
||||
// Initialize graph visibility from localStorage
|
||||
useEffect(() => {
|
||||
if (queryResponse?.data?.payload?.data?.result) {
|
||||
const { graphVisibilityStates: localStoredVisibilityState } =
|
||||
getLocalStorageGraphVisibilityState({
|
||||
apiResponse: queryResponse.data.payload.data.result,
|
||||
name: 'alert-chart-preview',
|
||||
});
|
||||
setGraphVisibility(localStoredVisibilityState);
|
||||
}
|
||||
}, [queryResponse?.data?.payload?.data?.result]);
|
||||
|
||||
if (queryResponse.data && graphType === PANEL_TYPES.BAR) {
|
||||
const sortedSeriesData = getSortedSeriesData(
|
||||
queryResponse.data?.payload.data.result,
|
||||
@@ -288,59 +267,9 @@ function ChartPreview({
|
||||
return LegendPosition.RIGHT;
|
||||
}, [queryResponse?.data?.payload?.data?.result?.length, showSideLegend]);
|
||||
|
||||
const options = useMemo(
|
||||
() =>
|
||||
getUPlotChartOptions({
|
||||
id: 'alert_legend_widget',
|
||||
yAxisUnit,
|
||||
apiResponse: queryResponse?.data?.payload,
|
||||
dimensions: {
|
||||
height: containerDimensions?.height ? containerDimensions.height - 48 : 0,
|
||||
width: containerDimensions?.width,
|
||||
},
|
||||
minTimeScale,
|
||||
maxTimeScale,
|
||||
isDarkMode,
|
||||
onDragSelect,
|
||||
thresholds: getThresholds(thresholds, t, optionName, yAxisUnit),
|
||||
softMax: null,
|
||||
softMin: null,
|
||||
panelType: graphType,
|
||||
tzDate: (timestamp: number) =>
|
||||
uPlot.tzDate(new Date(timestamp * 1e3), timezone.value),
|
||||
timezone: timezone.value,
|
||||
currentQuery,
|
||||
query: query || currentQuery,
|
||||
graphsVisibilityStates: graphVisibility,
|
||||
setGraphsVisibilityStates: setGraphVisibility,
|
||||
enhancedLegend: true,
|
||||
legendPosition,
|
||||
legendScrollPosition: legendScrollPositionRef.current,
|
||||
setLegendScrollPosition: (position: {
|
||||
scrollTop: number;
|
||||
scrollLeft: number;
|
||||
}) => {
|
||||
legendScrollPositionRef.current = position;
|
||||
},
|
||||
}),
|
||||
[
|
||||
yAxisUnit,
|
||||
queryResponse?.data?.payload,
|
||||
containerDimensions,
|
||||
minTimeScale,
|
||||
maxTimeScale,
|
||||
isDarkMode,
|
||||
onDragSelect,
|
||||
thresholds,
|
||||
t,
|
||||
optionName,
|
||||
graphType,
|
||||
timezone.value,
|
||||
currentQuery,
|
||||
query,
|
||||
graphVisibility,
|
||||
legendPosition,
|
||||
],
|
||||
const resolvedThresholds = useMemo(
|
||||
() => getThresholds(thresholds, t, optionName, yAxisUnit),
|
||||
[thresholds, t, optionName, yAxisUnit],
|
||||
);
|
||||
|
||||
const chartData = getUPlotChartData(queryResponse?.data?.payload);
|
||||
@@ -361,6 +290,12 @@ function ChartPreview({
|
||||
?.active || false;
|
||||
|
||||
const isWarning = !isEmpty(queryResponse.data?.warning);
|
||||
|
||||
const chartWidth = containerDimensions?.width ?? 0;
|
||||
const chartHeight = containerDimensions?.height
|
||||
? containerDimensions.height - CHART_PREVIEW_HEADER_HEIGHT
|
||||
: 0;
|
||||
|
||||
return (
|
||||
<div className="alert-chart-container" ref={graphRef}>
|
||||
<ChartContainer>
|
||||
@@ -384,16 +319,22 @@ function ChartPreview({
|
||||
)}
|
||||
|
||||
{chartDataAvailable && !isAnomalyDetectionAlert && (
|
||||
<GridPanelSwitch
|
||||
options={options}
|
||||
<ChartContent
|
||||
panelType={graphType}
|
||||
data={chartData}
|
||||
name={name || 'Chart Preview'}
|
||||
panelData={
|
||||
queryResponse.data?.payload?.data?.newResult?.data?.result || []
|
||||
}
|
||||
query={query || initialQueriesMap.metrics}
|
||||
alertId={alertDef?.id}
|
||||
query={query || currentQuery}
|
||||
apiResponse={queryResponse.data?.payload}
|
||||
data={chartData as uPlot.AlignedData}
|
||||
thresholds={resolvedThresholds}
|
||||
yAxisUnit={yAxisUnit}
|
||||
legendPosition={legendPosition}
|
||||
isDarkMode={isDarkMode}
|
||||
timezone={timezone}
|
||||
width={chartWidth}
|
||||
height={chartHeight}
|
||||
minTimeScale={minTimeScale}
|
||||
maxTimeScale={maxTimeScale}
|
||||
onDragSelect={onDragSelect}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { ExecStats } from 'api/v5/v5';
|
||||
import { Timezone } from 'components/CustomTimePicker/timezoneUtils';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { Threshold } from 'container/CreateAlertV2/context/types';
|
||||
import { PanelMode } from 'container/DashboardContainer/visualization/panels/types';
|
||||
import { buildBaseConfig } from 'container/DashboardContainer/visualization/panels/utils/baseConfigBuilder';
|
||||
import { ThresholdProps } from 'container/NewWidget/RightContainer/Threshold/types';
|
||||
import {
|
||||
BooleanFormats,
|
||||
@@ -11,6 +15,20 @@ import {
|
||||
TimeFormats,
|
||||
} from 'container/NewWidget/RightContainer/types';
|
||||
import { TFunction } from 'i18next';
|
||||
import { getLegend } from 'lib/dashboard/getQueryResults';
|
||||
import getLabelName from 'lib/getLabelName';
|
||||
import { OnClickPluginOpts } from 'lib/uPlotLib/plugins/onClickPlugin';
|
||||
import {
|
||||
DrawStyle,
|
||||
FillMode,
|
||||
LineInterpolation,
|
||||
LineStyle,
|
||||
} from 'lib/uPlotV2/config/types';
|
||||
import { UPlotConfigBuilder } from 'lib/uPlotV2/config/UPlotConfigBuilder';
|
||||
import { hasSingleVisiblePoint } from 'lib/uPlotV2/utils/dataUtils';
|
||||
import { get } from 'lodash-es';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import {
|
||||
dataFormatConfig,
|
||||
@@ -142,3 +160,107 @@ export const getThresholds = (
|
||||
});
|
||||
return thresholdsToReturn;
|
||||
};
|
||||
|
||||
export type AlertChartPanelType = PANEL_TYPES.TIME_SERIES | PANEL_TYPES.BAR;
|
||||
|
||||
export interface BuildAlertChartConfigParams {
|
||||
id: string;
|
||||
panelType: AlertChartPanelType;
|
||||
query: Query;
|
||||
thresholds: ThresholdProps[];
|
||||
apiResponse?: MetricRangePayloadProps;
|
||||
yAxisUnit?: string;
|
||||
isDarkMode: boolean;
|
||||
timezone: Timezone;
|
||||
minTimeScale?: number;
|
||||
maxTimeScale?: number;
|
||||
onDragSelect: (startTime: number, endTime: number) => void;
|
||||
onClick?: OnClickPluginOpts['onClick'];
|
||||
}
|
||||
|
||||
export const buildAlertChartConfig = ({
|
||||
id,
|
||||
panelType,
|
||||
query,
|
||||
thresholds,
|
||||
apiResponse,
|
||||
yAxisUnit,
|
||||
isDarkMode,
|
||||
timezone,
|
||||
minTimeScale,
|
||||
maxTimeScale,
|
||||
onDragSelect,
|
||||
onClick,
|
||||
}: BuildAlertChartConfigParams): UPlotConfigBuilder => {
|
||||
const stepIntervals: ExecStats['stepIntervals'] = get(
|
||||
apiResponse,
|
||||
'data.newResult.meta.stepIntervals',
|
||||
{},
|
||||
);
|
||||
const stepIntervalValues = Object.values(stepIntervals);
|
||||
const minStepInterval = stepIntervalValues.length
|
||||
? Math.min(...stepIntervalValues)
|
||||
: undefined;
|
||||
|
||||
const builder = buildBaseConfig({
|
||||
id,
|
||||
panelType,
|
||||
panelMode: PanelMode.DASHBOARD_VIEW,
|
||||
thresholds,
|
||||
apiResponse,
|
||||
yAxisUnit,
|
||||
isDarkMode,
|
||||
timezone,
|
||||
minTimeScale,
|
||||
maxTimeScale,
|
||||
stepInterval: minStepInterval,
|
||||
onDragSelect,
|
||||
onClick,
|
||||
});
|
||||
|
||||
const seriesList = apiResponse?.data?.result;
|
||||
if (!seriesList?.length) {
|
||||
return builder;
|
||||
}
|
||||
|
||||
const isBar = panelType === PANEL_TYPES.BAR;
|
||||
|
||||
seriesList.forEach((series) => {
|
||||
const baseLabelName = getLabelName(
|
||||
series.metric,
|
||||
series.queryName || '',
|
||||
series.legend || '',
|
||||
);
|
||||
const label = query ? getLegend(series, query, baseLabelName) : baseLabelName;
|
||||
|
||||
if (isBar) {
|
||||
builder.addSeries({
|
||||
scaleKey: 'y',
|
||||
drawStyle: DrawStyle.Bar,
|
||||
label,
|
||||
colorMapping: {},
|
||||
isDarkMode,
|
||||
stepInterval: get(stepIntervals, series.queryName, undefined),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const hasSingleValidPoint = hasSingleVisiblePoint(series.values);
|
||||
builder.addSeries({
|
||||
scaleKey: 'y',
|
||||
drawStyle: hasSingleValidPoint ? DrawStyle.Points : DrawStyle.Line,
|
||||
label,
|
||||
colorMapping: {},
|
||||
spanGaps: true,
|
||||
lineStyle: LineStyle.Solid,
|
||||
lineInterpolation: LineInterpolation.Spline,
|
||||
showPoints: hasSingleValidPoint,
|
||||
pointSize: 5,
|
||||
fillMode: FillMode.None,
|
||||
isDarkMode,
|
||||
metric: series.metric,
|
||||
});
|
||||
});
|
||||
|
||||
return builder;
|
||||
};
|
||||
|
||||
@@ -709,7 +709,6 @@ function FormAlertRules({
|
||||
panelType={panelType || PANEL_TYPES.TIME_SERIES}
|
||||
/>
|
||||
}
|
||||
name=""
|
||||
query={stagedQuery}
|
||||
selectedInterval={globalSelectedInterval}
|
||||
alertDef={alertDef}
|
||||
@@ -729,7 +728,6 @@ function FormAlertRules({
|
||||
panelType={panelType || PANEL_TYPES.TIME_SERIES}
|
||||
/>
|
||||
}
|
||||
name="Chart Preview"
|
||||
query={stagedQuery}
|
||||
alertDef={alertDef}
|
||||
selectedInterval={globalSelectedInterval}
|
||||
|
||||
@@ -52,6 +52,28 @@ export function normalizePlotValue(
|
||||
return value as number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if at most one entry in `values` is a valid plot value.
|
||||
*
|
||||
* Used to decide whether a series should render as a single point (drawStyle:
|
||||
* Points) vs a line — a continuous line with only one visible sample is
|
||||
* invisible to the user.
|
||||
*/
|
||||
export function hasSingleVisiblePoint(
|
||||
values: ReadonlyArray<readonly [unknown, unknown]> | undefined,
|
||||
): boolean {
|
||||
let validPointCount = 0;
|
||||
for (const [, rawValue] of values ?? []) {
|
||||
if (!isInvalidPlotValue(rawValue)) {
|
||||
validPointCount += 1;
|
||||
if (validPointCount > 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export interface SeriesSpanGapsOption {
|
||||
spanGaps?: boolean | number;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user