mirror of
https://github.com/SigNoz/signoz.git
synced 2026-08-14 17:00:37 +01:00
Compare commits
5 Commits
main
...
refactor/v
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98d2da9279 | ||
|
|
308e63444c | ||
|
|
7766fb2a2c | ||
|
|
697806937b | ||
|
|
0c87c10ff5 |
@@ -124,7 +124,9 @@ export function buildBaseConfig({
|
||||
side: 2,
|
||||
isDarkMode,
|
||||
isLogScale,
|
||||
panelType,
|
||||
// Graph and bar plot time on X; every other panel type here does not.
|
||||
isTimeAxis:
|
||||
panelType === PANEL_TYPES.TIME_SERIES || panelType === PANEL_TYPES.BAR,
|
||||
});
|
||||
|
||||
builder.addAxis({
|
||||
@@ -134,7 +136,6 @@ export function buildBaseConfig({
|
||||
isDarkMode,
|
||||
isLogScale,
|
||||
yAxisUnit,
|
||||
panelType,
|
||||
});
|
||||
|
||||
return builder;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Timezone } from 'components/CustomTimePicker/timezoneUtils';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { getLegend } from 'lib/dashboard/getQueryResults';
|
||||
import getLabelName from 'lib/getLabelName';
|
||||
import {
|
||||
@@ -76,7 +75,7 @@ export function buildEntityMetricsChartConfig({
|
||||
show: true,
|
||||
side: 2,
|
||||
isDarkMode,
|
||||
panelType: PANEL_TYPES.TIME_SERIES,
|
||||
isTimeAxis: true,
|
||||
});
|
||||
|
||||
builder.addAxis({
|
||||
@@ -85,7 +84,6 @@ export function buildEntityMetricsChartConfig({
|
||||
side: 3,
|
||||
isDarkMode,
|
||||
yAxisUnit,
|
||||
panelType: PANEL_TYPES.TIME_SERIES,
|
||||
});
|
||||
|
||||
if (!apiResponse?.data?.result) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Timezone } from 'components/CustomTimePicker/timezoneUtils';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { getInitialStackedBands } from 'container/DashboardContainer/visualization/charts/utils/stackSeriesUtils';
|
||||
import { getLegend } from 'lib/dashboard/getQueryResults';
|
||||
import getLabelName from 'lib/getLabelName';
|
||||
@@ -73,7 +72,7 @@ export function buildMeterChartConfig({
|
||||
show: true,
|
||||
side: 2,
|
||||
isDarkMode,
|
||||
panelType: PANEL_TYPES.BAR,
|
||||
isTimeAxis: true,
|
||||
});
|
||||
|
||||
builder.addAxis({
|
||||
@@ -82,7 +81,6 @@ export function buildMeterChartConfig({
|
||||
side: 3,
|
||||
isDarkMode,
|
||||
yAxisUnit,
|
||||
panelType: PANEL_TYPES.BAR,
|
||||
});
|
||||
|
||||
if (!apiResponse?.data?.result) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { getToolTipValue } from 'components/Graph/yAxisConfig';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import uPlot, { Axis } from 'uplot';
|
||||
|
||||
import { uPlotXAxisValuesFormat } from '../../uPlotLib/utils/constants';
|
||||
@@ -7,11 +6,6 @@ import getGridColor from '../../uPlotLib/utils/getGridColor';
|
||||
import { buildYAxisSizeCalculator } from '../utils/axis';
|
||||
import { AxisProps, ConfigBuilder } from './types';
|
||||
|
||||
const PANEL_TYPES_WITH_X_AXIS_DATETIME_FORMAT = [
|
||||
PANEL_TYPES.TIME_SERIES,
|
||||
PANEL_TYPES.BAR,
|
||||
];
|
||||
|
||||
/**
|
||||
* Builder for uPlot axis configuration
|
||||
* Handles creation and merging of axis settings
|
||||
@@ -67,12 +61,9 @@ export class UPlotAxisBuilder extends ConfigBuilder<AxisProps, Axis> {
|
||||
* Build values formatter for X-axis (time)
|
||||
*/
|
||||
private buildXAxisValuesFormatter(): uPlot.Axis.Values | undefined {
|
||||
const { panelType } = this.props;
|
||||
const { isTimeAxis } = this.props;
|
||||
|
||||
if (
|
||||
panelType &&
|
||||
PANEL_TYPES_WITH_X_AXIS_DATETIME_FORMAT.includes(panelType)
|
||||
) {
|
||||
if (isTimeAxis) {
|
||||
return uPlotXAxisValuesFormat as uPlot.Axis.Values;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { getToolTipValue } from 'components/Graph/yAxisConfig';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { uPlotXAxisValuesFormat } from 'lib/uPlotLib/utils/constants';
|
||||
import type uPlot from 'uplot';
|
||||
|
||||
@@ -137,11 +136,11 @@ describe('UPlotAxisBuilder', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('uses time-based X-axis values formatter for time-series like panels', () => {
|
||||
it('uses time-based X-axis values formatter when the caller declares a time axis', () => {
|
||||
const builder = new UPlotAxisBuilder(
|
||||
createAxisProps({
|
||||
scaleKey: 'x',
|
||||
panelType: PANEL_TYPES.TIME_SERIES,
|
||||
isTimeAxis: true,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -150,11 +149,11 @@ describe('UPlotAxisBuilder', () => {
|
||||
expect(config.values).toBe(uPlotXAxisValuesFormat);
|
||||
});
|
||||
|
||||
it('does not attach X-axis datetime formatter when panel type is not supported', () => {
|
||||
it('does not attach X-axis datetime formatter for a non-time axis', () => {
|
||||
const builder = new UPlotAxisBuilder(
|
||||
createAxisProps({
|
||||
scaleKey: 'x',
|
||||
panelType: PANEL_TYPES.LIST, // not in PANEL_TYPES_WITH_X_AXIS_DATETIME_FORMAT
|
||||
isTimeAxis: false,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -290,22 +289,9 @@ describe('UPlotAxisBuilder', () => {
|
||||
expect(config.space).toBe(50);
|
||||
});
|
||||
|
||||
it('includes PANEL_TYPES.BAR and PANEL_TYPES.TIME_SERIES in X-axis datetime formatter', () => {
|
||||
const barBuilder = new UPlotAxisBuilder(
|
||||
createAxisProps({
|
||||
scaleKey: 'x',
|
||||
panelType: PANEL_TYPES.BAR,
|
||||
}),
|
||||
);
|
||||
expect(barBuilder.getConfig().values).toBe(uPlotXAxisValuesFormat);
|
||||
|
||||
const timeSeriesBuilder = new UPlotAxisBuilder(
|
||||
createAxisProps({
|
||||
scaleKey: 'x',
|
||||
panelType: PANEL_TYPES.TIME_SERIES,
|
||||
}),
|
||||
);
|
||||
expect(timeSeriesBuilder.getConfig().values).toBe(uPlotXAxisValuesFormat);
|
||||
it('omits the X-axis datetime formatter when no time axis is declared', () => {
|
||||
const builder = new UPlotAxisBuilder(createAxisProps({ scaleKey: 'x' }));
|
||||
expect(builder.getConfig().values).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return the existing size when cycleNum > 1', () => {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { PrecisionOption } from 'components/Graph/types';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import uPlot, { Series } from 'uplot';
|
||||
|
||||
import { ThresholdsDrawHookOptions } from '../hooks/types';
|
||||
@@ -70,7 +69,12 @@ export interface AxisProps {
|
||||
isDarkMode?: boolean;
|
||||
isLogScale?: boolean;
|
||||
yAxisUnit?: string;
|
||||
panelType?: PANEL_TYPES;
|
||||
/**
|
||||
* X axis carries timestamps, so its ticks format as dates/times. Declared by the caller
|
||||
* rather than inferred from a panel type — a chart whose x axis is buckets or categories
|
||||
* (histogram) leaves it off.
|
||||
*/
|
||||
isTimeAxis?: boolean;
|
||||
decimalPrecision?: PrecisionOption;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ import type { TelemetrytypesSignalDTO } from 'api/generated/services/sigNoz.sche
|
||||
import PromQLIcon from 'assets/Dashboard/PromQl';
|
||||
import { QueryBuilderV2 } from 'components/QueryBuilderV2/QueryBuilderV2';
|
||||
import TextToolTip from 'components/TextToolTip';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import ClickHouseQueryContainer from 'container/NewWidget/LeftContainer/QuerySection/QueryBuilder/ClickHouse';
|
||||
import PromQLQueryContainer from 'container/NewWidget/LeftContainer/QuerySection/QueryBuilder/promQL';
|
||||
import RunQueryBtn from 'container/QueryBuilder/components/RunQueryBtn/RunQueryBtn';
|
||||
@@ -26,6 +25,7 @@ import {
|
||||
getHiddenQueryBuilderFields,
|
||||
getSupportedQueryTypes,
|
||||
} from '../../Panels/capabilities';
|
||||
import { getPanelDefinition } from '../../Panels/registry';
|
||||
import {
|
||||
PANEL_KIND_TO_PANEL_TYPE,
|
||||
type PanelKind,
|
||||
@@ -64,8 +64,10 @@ function PanelEditorQueryBuilder({
|
||||
footer,
|
||||
stickyHeader = true,
|
||||
}: PanelEditorQueryBuilderProps): JSX.Element {
|
||||
// The shared QueryBuilderV2 / list-view checks still speak the legacy PANEL_TYPES.
|
||||
// The shared QueryBuilderV2 provider still speaks the legacy PANEL_TYPES; what the
|
||||
// builder offers for this kind comes from the kind's own declaration.
|
||||
const panelType = PANEL_KIND_TO_PANEL_TYPE[panelKind];
|
||||
const { listView, traceOperator } = getPanelDefinition(panelKind).query;
|
||||
const { currentQuery, redirectWithQueryBuilderData } = useQueryBuilder();
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
@@ -112,9 +114,9 @@ function PanelEditorQueryBuilder({
|
||||
<QueryBuilderV2
|
||||
panelType={panelType}
|
||||
filterConfigs={filterConfigs}
|
||||
showTraceOperator={panelType !== PANEL_TYPES.LIST}
|
||||
showTraceOperator={traceOperator}
|
||||
version="v3"
|
||||
isListViewPanel={panelType === PANEL_TYPES.LIST}
|
||||
isListViewPanel={listView}
|
||||
queryComponents={{}}
|
||||
signalSourceChangeEnabled
|
||||
savePreviousQuery
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
import { Spline } from '@signozhq/icons';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import QueryTypeTag from 'container/NewWidget/LeftContainer/QueryTypeTag';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
|
||||
interface PlotTagProps {
|
||||
/** Authoring mode of the panel's query; undefined when no query exists yet. */
|
||||
queryType: EQueryType | undefined;
|
||||
panelType: PANEL_TYPES;
|
||||
/** Kind is authored as a list view — nothing is plotted, so the chip has nothing to say. */
|
||||
isListView: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* "Plotted with <query mode>" chip for the editor preview; V2 counterpart of V1's
|
||||
* PlotTag (duplicated per the split policy). Hidden for list panels and before a
|
||||
* PlotTag (duplicated per the split policy). Hidden for list views and before a
|
||||
* query exists, where the mode is irrelevant.
|
||||
*/
|
||||
function PlotTag({
|
||||
queryType,
|
||||
panelType,
|
||||
isListView,
|
||||
className,
|
||||
}: PlotTagProps): JSX.Element | null {
|
||||
if (queryType === undefined || panelType === PANEL_TYPES.LIST) {
|
||||
if (queryType === undefined || isListView) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import PanelBody from 'pages/DashboardPageV2/DashboardContainer/PanelsAndSection
|
||||
import PanelHeader from 'pages/DashboardPageV2/DashboardContainer/PanelsAndSectionsLayout/Panel/PanelHeader/PanelHeader';
|
||||
import type { AnyPanelInteractionProps } from 'pages/DashboardPageV2/DashboardContainer/Panels/types/interactions';
|
||||
import type { RenderablePanelDefinition } from 'pages/DashboardPageV2/DashboardContainer/Panels/types/panelDefinition';
|
||||
import { PANEL_KIND_TO_PANEL_TYPE } from 'pages/DashboardPageV2/DashboardContainer/Panels/types/panelKind';
|
||||
import type { DashboardPreference } from 'pages/DashboardPageV2/DashboardContainer/Panels/types/rendererProps';
|
||||
import { getPanelQueryType } from 'pages/DashboardPageV2/DashboardContainer/Panels/utils/getPanelQueryType';
|
||||
import type {
|
||||
@@ -72,7 +71,6 @@ function PreviewPane({
|
||||
onClick,
|
||||
enableDrillDown,
|
||||
}: PreviewPaneProps): JSX.Element {
|
||||
const panelType = PANEL_KIND_TO_PANEL_TYPE[panel.spec.plugin.kind];
|
||||
const queryType = getPanelQueryType(panel);
|
||||
|
||||
// Search term is ephemeral preview state, threaded to header + renderer but
|
||||
@@ -86,7 +84,7 @@ function PreviewPane({
|
||||
<div className={styles.header}>
|
||||
<PlotTag
|
||||
queryType={queryType}
|
||||
panelType={panelType}
|
||||
isListView={panelDefinition.query.listView}
|
||||
className={styles.queryType}
|
||||
/>
|
||||
<div className={styles.dateTimeSelector}>
|
||||
|
||||
@@ -1,30 +1,22 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
|
||||
import PlotTag from '../PlotTag';
|
||||
|
||||
describe('PlotTag', () => {
|
||||
it('renders the resolved query mode', () => {
|
||||
render(
|
||||
<PlotTag queryType={EQueryType.PROM} panelType={PANEL_TYPES.TIME_SERIES} />,
|
||||
);
|
||||
render(<PlotTag queryType={EQueryType.PROM} isListView={false} />);
|
||||
expect(screen.getByTestId('panel-editor-plot-tag')).toBeInTheDocument();
|
||||
expect(screen.getByText('PromQL')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders nothing when there is no query yet', () => {
|
||||
render(<PlotTag queryType={undefined} panelType={PANEL_TYPES.TIME_SERIES} />);
|
||||
render(<PlotTag queryType={undefined} isListView={false} />);
|
||||
expect(screen.queryByTestId('panel-editor-plot-tag')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders nothing for list panels (query mode is irrelevant)', () => {
|
||||
render(
|
||||
<PlotTag
|
||||
queryType={EQueryType.QUERY_BUILDER}
|
||||
panelType={PANEL_TYPES.LIST}
|
||||
/>,
|
||||
);
|
||||
it('renders nothing for a list view (query mode is irrelevant)', () => {
|
||||
render(<PlotTag queryType={EQueryType.QUERY_BUILDER} isListView />);
|
||||
expect(screen.queryByTestId('panel-editor-plot-tag')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,7 +4,10 @@ import type {
|
||||
TelemetrytypesSignalDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import type { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { getPanelDefinition } from 'pages/DashboardPageV2/DashboardContainer/Panels/registry';
|
||||
import {
|
||||
getPanelDefinition,
|
||||
isPanelKindSupported,
|
||||
} from 'pages/DashboardPageV2/DashboardContainer/Panels/registry';
|
||||
import type { RenderablePanelDefinition } from 'pages/DashboardPageV2/DashboardContainer/Panels/types/panelDefinition';
|
||||
import {
|
||||
PANEL_KIND_TO_PANEL_TYPE,
|
||||
@@ -91,8 +94,9 @@ export function usePanelEditSession({
|
||||
const query = usePanelQuery({
|
||||
panel: draft,
|
||||
panelId,
|
||||
queryCapabilities: panelDefinition.query,
|
||||
time,
|
||||
enabled: !!panelDefinition,
|
||||
enabled: isPanelKindSupported(panelKind),
|
||||
});
|
||||
|
||||
const { runQuery, isQueryDirty, buildSaveSpec } = usePanelEditorQuerySync({
|
||||
|
||||
@@ -6,7 +6,7 @@ import type {
|
||||
DashboardtypesQueryDTO,
|
||||
TelemetrytypesSignalDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import type { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import {
|
||||
handleQueryChange,
|
||||
type PartialPanelTypes,
|
||||
@@ -19,6 +19,7 @@ import type {
|
||||
} from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { resolveQueryType } from '../../Panels/capabilities';
|
||||
import { getPanelDefinition } from '../../Panels/registry';
|
||||
import {
|
||||
PANEL_KIND_TO_PANEL_TYPE,
|
||||
type PanelKind,
|
||||
@@ -144,11 +145,10 @@ export function usePanelTypeSwitch({
|
||||
{ ...query, queryType },
|
||||
panelTypeRef.current,
|
||||
);
|
||||
// Match a fresh list panel's default order so the builder's Order By isn't empty.
|
||||
const nextQuery =
|
||||
newPanelType === PANEL_TYPES.LIST
|
||||
? withDefaultListOrder(transformed)
|
||||
: transformed;
|
||||
// Match a fresh list view's default order so the builder's Order By isn't empty.
|
||||
const nextQuery = getPanelDefinition(newKind).query.listView
|
||||
? withDefaultListOrder(transformed)
|
||||
: transformed;
|
||||
const signal = getBuilderQueries(currentSpec.queries)[0]
|
||||
?.signal as TelemetrytypesSignalDTO;
|
||||
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import { TelemetrytypesSignalDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import {
|
||||
Querybuildertypesv5RequestTypeDTO,
|
||||
TelemetrytypesSignalDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { OPERATORS } from 'constants/queryBuilder';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
|
||||
import { UNSUPPORTED_PANEL } from '../kinds/UnsupportedPanel/definition';
|
||||
import { getPanelDefinition, isPanelKindSupported } from '../registry';
|
||||
import type { PanelQueryCapabilities } from '../types/panelCapabilities';
|
||||
import { NO_PANEL_ACTIONS } from '../types/panelDefinition';
|
||||
import {
|
||||
getHiddenQueryBuilderFields,
|
||||
getSupportedQueryTypes,
|
||||
@@ -15,6 +22,7 @@ import type { PanelKind } from '../types/panelKind';
|
||||
|
||||
const { QUERY_BUILDER, CLICKHOUSE, PROM } = EQueryType;
|
||||
const { logs, traces, metrics } = TelemetrytypesSignalDTO;
|
||||
const { time_series, scalar, raw } = Querybuildertypesv5RequestTypeDTO;
|
||||
|
||||
const EXPECTED_QUERY_TYPES: Record<PanelKind, EQueryType[]> = {
|
||||
'signoz/TimeSeriesPanel': [QUERY_BUILDER, CLICKHOUSE, PROM],
|
||||
@@ -37,9 +45,131 @@ const EXPECTED_SIGNALS: Record<PanelKind, TelemetrytypesSignalDTO[]> = {
|
||||
'signoz/ListPanel': [logs, traces],
|
||||
};
|
||||
|
||||
// Exhaustive over PanelKind, so a new kind can't ship without stating how its request is
|
||||
// shaped — the check that used to be implicit in a legacy PANEL_TYPES switch.
|
||||
const EXPECTED_QUERY_CAPABILITIES: Record<PanelKind, PanelQueryCapabilities> = {
|
||||
'signoz/TimeSeriesPanel': {
|
||||
requestType: time_series,
|
||||
formatTableResultForUI: false,
|
||||
bucketedStepInterval: false,
|
||||
orderTiebreaker: false,
|
||||
serverPaginated: false,
|
||||
listView: false,
|
||||
traceOperator: true,
|
||||
},
|
||||
// Bar bins client-side, so it asks for a widened step interval over a raw series.
|
||||
'signoz/BarChartPanel': {
|
||||
requestType: time_series,
|
||||
formatTableResultForUI: false,
|
||||
bucketedStepInterval: true,
|
||||
orderTiebreaker: false,
|
||||
serverPaginated: false,
|
||||
listView: false,
|
||||
traceOperator: true,
|
||||
},
|
||||
'signoz/HistogramPanel': {
|
||||
requestType: time_series,
|
||||
formatTableResultForUI: false,
|
||||
bucketedStepInterval: false,
|
||||
orderTiebreaker: false,
|
||||
serverPaginated: false,
|
||||
listView: false,
|
||||
traceOperator: true,
|
||||
},
|
||||
'signoz/NumberPanel': {
|
||||
requestType: scalar,
|
||||
formatTableResultForUI: false,
|
||||
bucketedStepInterval: false,
|
||||
orderTiebreaker: false,
|
||||
serverPaginated: false,
|
||||
listView: false,
|
||||
traceOperator: true,
|
||||
},
|
||||
'signoz/PieChartPanel': {
|
||||
requestType: scalar,
|
||||
formatTableResultForUI: false,
|
||||
bucketedStepInterval: false,
|
||||
orderTiebreaker: false,
|
||||
serverPaginated: false,
|
||||
listView: false,
|
||||
traceOperator: true,
|
||||
},
|
||||
// Only Table asks the server to transpose its scalar result into UI rows.
|
||||
'signoz/TablePanel': {
|
||||
requestType: scalar,
|
||||
formatTableResultForUI: true,
|
||||
bucketedStepInterval: false,
|
||||
orderTiebreaker: false,
|
||||
serverPaginated: false,
|
||||
listView: false,
|
||||
traceOperator: true,
|
||||
},
|
||||
// Only List reads raw rows, pages them server-side, and needs an order tiebreaker.
|
||||
'signoz/ListPanel': {
|
||||
requestType: raw,
|
||||
formatTableResultForUI: false,
|
||||
bucketedStepInterval: false,
|
||||
orderTiebreaker: true,
|
||||
serverPaginated: true,
|
||||
listView: true,
|
||||
traceOperator: false,
|
||||
},
|
||||
};
|
||||
|
||||
const ALL_KINDS = Object.keys(EXPECTED_QUERY_TYPES) as PanelKind[];
|
||||
|
||||
describe('panel capabilities guard', () => {
|
||||
describe('query capabilities', () => {
|
||||
it.each(ALL_KINDS)('declares how %s shapes its request', (kind) => {
|
||||
expect(getPanelDefinition(kind).query).toStrictEqual(
|
||||
EXPECTED_QUERY_CAPABILITIES[kind],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// A dashboard spec written by a newer SigNoz can name a kind this build has no
|
||||
// definition for. The registry answers with UNSUPPORTED_PANEL rather than nothing, so
|
||||
// every guard below reads it without first proving a definition exists.
|
||||
describe('a kind this build cannot render', () => {
|
||||
const unknownKind = 'signoz/SomeFutureKindPanel' as PanelKind;
|
||||
|
||||
it('is not reported as supported', () => {
|
||||
expect(isPanelKindSupported(unknownKind)).toBe(false);
|
||||
expect(isPanelKindSupported('signoz/TimeSeriesPanel')).toBe(true);
|
||||
});
|
||||
|
||||
it('still resolves to a definition', () => {
|
||||
expect(getPanelDefinition(unknownKind)).toBe(UNSUPPORTED_PANEL);
|
||||
});
|
||||
|
||||
it('declares nothing, so it is never offered as authorable', () => {
|
||||
expect(getSupportedSignals(unknownKind)).toStrictEqual([]);
|
||||
expect(getSupportedQueryTypes(unknownKind)).toStrictEqual([]);
|
||||
expect(isSignalSupported(unknownKind, logs)).toBe(false);
|
||||
expect(
|
||||
isPanelCombinationValid({ kind: unknownKind, queryType: QUERY_BUILDER }),
|
||||
).toBe(false);
|
||||
expect(getHiddenQueryBuilderFields(unknownKind, logs)).toStrictEqual({});
|
||||
expect(getPanelDefinition(unknownKind).sections).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it('offers no actions', () => {
|
||||
expect(getPanelDefinition(unknownKind).actions).toStrictEqual(
|
||||
NO_PANEL_ACTIONS,
|
||||
);
|
||||
expect(NO_PANEL_ACTIONS.view).toBe(false);
|
||||
expect(NO_PANEL_ACTIONS.edit).toBe(false);
|
||||
expect(NO_PANEL_ACTIONS.drilldown).toBe(false);
|
||||
});
|
||||
|
||||
it('carries an inert query shape, so a stray request can do no harm', () => {
|
||||
const { query } = getPanelDefinition(unknownKind);
|
||||
expect(query.requestType).toBe(time_series);
|
||||
expect(query.serverPaginated).toBe(false);
|
||||
expect(query.formatTableResultForUI).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('query type support', () => {
|
||||
it.each(ALL_KINDS)('declares the expected query types for %s', (kind) => {
|
||||
expect(getSupportedQueryTypes(kind)).toStrictEqual(
|
||||
|
||||
@@ -53,9 +53,10 @@ function NoData({
|
||||
return <PanelLoader />;
|
||||
}
|
||||
|
||||
const panelType = panel
|
||||
? PANEL_KIND_TO_PANEL_TYPE[panel.spec.plugin.kind]
|
||||
: undefined;
|
||||
// `panelType` stays on the event so existing reports keep resolving; `panelKind` is the
|
||||
// V2 identity, and the only one that can tell two kinds sharing a panel type apart.
|
||||
const panelKind = panel?.spec.plugin.kind;
|
||||
const panelType = panelKind ? PANEL_KIND_TO_PANEL_TYPE[panelKind] : undefined;
|
||||
|
||||
const extendAction: PanelMessageAction | undefined =
|
||||
activeExtend?.canExtend && activeExtend.actionLabel
|
||||
@@ -65,6 +66,7 @@ function NoData({
|
||||
void logEvent(DashboardDetailEvents.NoDataAction, {
|
||||
action: 'extendTime',
|
||||
panelType,
|
||||
panelKind,
|
||||
});
|
||||
activeExtend.extend();
|
||||
},
|
||||
@@ -79,6 +81,7 @@ function NoData({
|
||||
void logEvent(DashboardDetailEvents.NoDataAction, {
|
||||
action: 'retry',
|
||||
panelType,
|
||||
panelKind,
|
||||
});
|
||||
onRetry();
|
||||
},
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import type { PanelDefinition } from '../../types/panelDefinition';
|
||||
import Renderer from './Renderer';
|
||||
import { sections } from './sections';
|
||||
import { TelemetrytypesSignalDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import {
|
||||
Querybuildertypesv5RequestTypeDTO,
|
||||
TelemetrytypesSignalDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
|
||||
export const definition: PanelDefinition<'signoz/BarChartPanel'> = {
|
||||
@@ -20,6 +23,17 @@ export const definition: PanelDefinition<'signoz/BarChartPanel'> = {
|
||||
EQueryType.PROM,
|
||||
],
|
||||
queryBuilderFields: {},
|
||||
// Bars are binned client-side from a raw time series, so the request asks for a
|
||||
// step interval wide enough to keep the bar count readable (V1 parity).
|
||||
query: {
|
||||
requestType: Querybuildertypesv5RequestTypeDTO.time_series,
|
||||
formatTableResultForUI: false,
|
||||
bucketedStepInterval: true,
|
||||
orderTiebreaker: false,
|
||||
serverPaginated: false,
|
||||
listView: false,
|
||||
traceOperator: true,
|
||||
},
|
||||
actions: {
|
||||
view: true,
|
||||
edit: true,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { DashboardtypesBarChartPanelSpecDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { Timezone } from 'components/CustomTimePicker/timezoneUtils';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { getInitialStackedBands } from 'container/DashboardContainer/visualization/charts/utils/stackSeriesUtils';
|
||||
import { PanelMode } from 'container/DashboardContainer/visualization/panels/types';
|
||||
import { buildBaseConfig } from 'pages/DashboardPageV2/DashboardContainer/Panels/utils/baseConfigBuilder';
|
||||
@@ -48,7 +47,7 @@ export function buildBarChartConfig({
|
||||
}: BuildBarChartConfigArgs): UPlotConfigBuilder {
|
||||
const builder = buildBaseConfig({
|
||||
panelId,
|
||||
panelType: PANEL_TYPES.BAR,
|
||||
isTimeAxis: true,
|
||||
isDarkMode,
|
||||
timezone,
|
||||
panelMode,
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import type { PanelDefinition } from '../../types/panelDefinition';
|
||||
import Renderer from './Renderer';
|
||||
import { sections } from './sections';
|
||||
import { TelemetrytypesSignalDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import {
|
||||
Querybuildertypesv5RequestTypeDTO,
|
||||
TelemetrytypesSignalDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
|
||||
export const definition: PanelDefinition<'signoz/HistogramPanel'> = {
|
||||
@@ -20,6 +23,17 @@ export const definition: PanelDefinition<'signoz/HistogramPanel'> = {
|
||||
EQueryType.PROM,
|
||||
],
|
||||
queryBuilderFields: {},
|
||||
// Buckets are computed client-side from the raw series, so the request is a plain
|
||||
// time series — the bucket count is a display concern, not a query one.
|
||||
query: {
|
||||
requestType: Querybuildertypesv5RequestTypeDTO.time_series,
|
||||
formatTableResultForUI: false,
|
||||
bucketedStepInterval: false,
|
||||
orderTiebreaker: false,
|
||||
serverPaginated: false,
|
||||
listView: false,
|
||||
traceOperator: true,
|
||||
},
|
||||
actions: {
|
||||
view: true,
|
||||
edit: true,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { DashboardtypesHistogramPanelSpecDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { Timezone } from 'components/CustomTimePicker/timezoneUtils';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { PanelMode } from 'container/DashboardContainer/visualization/panels/types';
|
||||
import { buildBaseConfig } from 'pages/DashboardPageV2/DashboardContainer/Panels/utils/baseConfigBuilder';
|
||||
import { resolveSeriesLabelV5 } from 'pages/DashboardPageV2/DashboardContainer/Panels/utils/resolveSeriesLabel';
|
||||
@@ -44,7 +43,7 @@ export function buildHistogramConfig({
|
||||
}: BuildHistogramConfigArgs): UPlotConfigBuilder {
|
||||
const builder = buildBaseConfig({
|
||||
panelId,
|
||||
panelType: PANEL_TYPES.HISTOGRAM,
|
||||
isTimeAxis: false,
|
||||
isDarkMode,
|
||||
timezone,
|
||||
panelMode,
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import type { PanelDefinition } from '../../types/panelDefinition';
|
||||
import Renderer from './Renderer';
|
||||
import { sections } from './sections';
|
||||
import { TelemetrytypesSignalDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import {
|
||||
Querybuildertypesv5RequestTypeDTO,
|
||||
TelemetrytypesSignalDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { OPERATORS } from 'constants/queryBuilder';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
|
||||
@@ -30,6 +33,17 @@ export const definition: PanelDefinition<'signoz/ListPanel'> = {
|
||||
},
|
||||
},
|
||||
sections,
|
||||
// The only kind reading raw rows: they page server-side, and the sort needs a
|
||||
// tiebreaker so a duplicated sort key can't repeat or skip a row across pages.
|
||||
query: {
|
||||
requestType: Querybuildertypesv5RequestTypeDTO.raw,
|
||||
formatTableResultForUI: false,
|
||||
bucketedStepInterval: false,
|
||||
orderTiebreaker: true,
|
||||
serverPaginated: true,
|
||||
listView: true,
|
||||
traceOperator: false,
|
||||
},
|
||||
actions: {
|
||||
view: true,
|
||||
edit: true,
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import type { PanelDefinition } from '../../types/panelDefinition';
|
||||
import Renderer from './Renderer';
|
||||
import { sections } from './sections';
|
||||
import { TelemetrytypesSignalDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import {
|
||||
Querybuildertypesv5RequestTypeDTO,
|
||||
TelemetrytypesSignalDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
|
||||
export const definition: PanelDefinition<'signoz/NumberPanel'> = {
|
||||
@@ -20,6 +23,15 @@ export const definition: PanelDefinition<'signoz/NumberPanel'> = {
|
||||
EQueryType.PROM,
|
||||
],
|
||||
queryBuilderFields: {},
|
||||
query: {
|
||||
requestType: Querybuildertypesv5RequestTypeDTO.scalar,
|
||||
formatTableResultForUI: false,
|
||||
bucketedStepInterval: false,
|
||||
orderTiebreaker: false,
|
||||
serverPaginated: false,
|
||||
listView: false,
|
||||
traceOperator: true,
|
||||
},
|
||||
actions: {
|
||||
view: true,
|
||||
edit: true,
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import type { PanelDefinition } from '../../types/panelDefinition';
|
||||
import Renderer from './Renderer';
|
||||
import { sections } from './sections';
|
||||
import { TelemetrytypesSignalDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import {
|
||||
Querybuildertypesv5RequestTypeDTO,
|
||||
TelemetrytypesSignalDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
|
||||
export const definition: PanelDefinition<'signoz/PieChartPanel'> = {
|
||||
@@ -16,6 +19,15 @@ export const definition: PanelDefinition<'signoz/PieChartPanel'> = {
|
||||
],
|
||||
supportedQueryTypes: [EQueryType.QUERY_BUILDER, EQueryType.CLICKHOUSE],
|
||||
queryBuilderFields: {},
|
||||
query: {
|
||||
requestType: Querybuildertypesv5RequestTypeDTO.scalar,
|
||||
formatTableResultForUI: false,
|
||||
bucketedStepInterval: false,
|
||||
orderTiebreaker: false,
|
||||
serverPaginated: false,
|
||||
listView: false,
|
||||
traceOperator: true,
|
||||
},
|
||||
actions: {
|
||||
view: true,
|
||||
edit: true,
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import type { PanelDefinition } from '../../types/panelDefinition';
|
||||
import Renderer from './Renderer';
|
||||
import { sections } from './sections';
|
||||
import { TelemetrytypesSignalDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import {
|
||||
Querybuildertypesv5RequestTypeDTO,
|
||||
TelemetrytypesSignalDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
|
||||
export const definition: PanelDefinition<'signoz/TablePanel'> = {
|
||||
@@ -16,6 +19,16 @@ export const definition: PanelDefinition<'signoz/TablePanel'> = {
|
||||
],
|
||||
supportedQueryTypes: [EQueryType.QUERY_BUILDER, EQueryType.CLICKHOUSE],
|
||||
queryBuilderFields: {},
|
||||
// The only kind that asks the server to transpose its scalar result into UI rows.
|
||||
query: {
|
||||
requestType: Querybuildertypesv5RequestTypeDTO.scalar,
|
||||
formatTableResultForUI: true,
|
||||
bucketedStepInterval: false,
|
||||
orderTiebreaker: false,
|
||||
serverPaginated: false,
|
||||
listView: false,
|
||||
traceOperator: true,
|
||||
},
|
||||
// Tables carry tabular data worth exporting (V1 parity: download is table-only).
|
||||
actions: {
|
||||
view: true,
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import type { PanelDefinition } from '../../types/panelDefinition';
|
||||
import Renderer from './Renderer';
|
||||
import { sections } from './sections';
|
||||
import { TelemetrytypesSignalDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import {
|
||||
Querybuildertypesv5RequestTypeDTO,
|
||||
TelemetrytypesSignalDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
|
||||
export const definition: PanelDefinition<'signoz/TimeSeriesPanel'> = {
|
||||
@@ -20,6 +23,15 @@ export const definition: PanelDefinition<'signoz/TimeSeriesPanel'> = {
|
||||
EQueryType.PROM,
|
||||
],
|
||||
queryBuilderFields: {},
|
||||
query: {
|
||||
requestType: Querybuildertypesv5RequestTypeDTO.time_series,
|
||||
formatTableResultForUI: false,
|
||||
bucketedStepInterval: false,
|
||||
orderTiebreaker: false,
|
||||
serverPaginated: false,
|
||||
listView: false,
|
||||
traceOperator: true,
|
||||
},
|
||||
actions: {
|
||||
view: true,
|
||||
edit: true,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { DashboardtypesTimeSeriesPanelSpecDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { Timezone } from 'components/CustomTimePicker/timezoneUtils';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { PanelMode } from 'container/DashboardContainer/visualization/panels/types';
|
||||
import {
|
||||
buildBaseConfig,
|
||||
@@ -66,7 +65,7 @@ export function buildTimeSeriesConfig({
|
||||
}: BuildTimeSeriesConfigArgs): UPlotConfigBuilder {
|
||||
const builder = buildBaseConfig({
|
||||
panelId,
|
||||
panelType: PANEL_TYPES.TIME_SERIES,
|
||||
isTimeAxis: true,
|
||||
isDarkMode,
|
||||
timezone,
|
||||
panelMode,
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { CircleHelp } from '@signozhq/icons';
|
||||
|
||||
import PanelMessage from '../../components/PanelMessage/PanelMessage';
|
||||
import PanelStyles from '../../panel.module.scss';
|
||||
|
||||
/**
|
||||
* Body for a panel whose kind this build has no renderer for — a spec written by a newer
|
||||
* SigNoz names a visualization that didn't exist when this client shipped. Says so in
|
||||
* place of the chart, so the panel keeps its slot in the layout instead of leaving a hole.
|
||||
*/
|
||||
function UnsupportedPanelRenderer(): JSX.Element {
|
||||
return (
|
||||
<div
|
||||
data-testid="unsupported-panel-renderer"
|
||||
className={PanelStyles.panelContainer}
|
||||
>
|
||||
<PanelMessage
|
||||
icon={<CircleHelp size={18} />}
|
||||
title="Unsupported panel type"
|
||||
description="This panel was built with a newer version of SigNoz. Upgrade to view it."
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default UnsupportedPanelRenderer;
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Querybuildertypesv5RequestTypeDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
|
||||
import {
|
||||
NO_PANEL_ACTIONS,
|
||||
type RenderablePanelDefinition,
|
||||
} from '../../types/panelDefinition';
|
||||
import Renderer from './Renderer';
|
||||
|
||||
/**
|
||||
* Stand-in definition for a kind that isn't in the registry, so `getPanelDefinition`
|
||||
* always resolves and no caller has to branch on a missing one. It declares nothing: no
|
||||
* signals, no query types, no config sections and no actions — an unknown kind can't be
|
||||
* queried, configured or acted on, only shown as unsupported.
|
||||
*
|
||||
* `kind` carries a sentinel that no API enum value can collide with; the cast is the one
|
||||
* place this definition steps outside `PanelKind`.
|
||||
*/
|
||||
export const UNSUPPORTED_PANEL: RenderablePanelDefinition = {
|
||||
kind: '<unsupported>' as RenderablePanelDefinition['kind'],
|
||||
displayName: 'Unsupported panel',
|
||||
Renderer,
|
||||
sections: [],
|
||||
supportedSignals: [],
|
||||
supportedQueryTypes: [],
|
||||
queryBuilderFields: {},
|
||||
query: {
|
||||
requestType: Querybuildertypesv5RequestTypeDTO.time_series,
|
||||
formatTableResultForUI: false,
|
||||
bucketedStepInterval: false,
|
||||
orderTiebreaker: false,
|
||||
serverPaginated: false,
|
||||
listView: false,
|
||||
traceOperator: false,
|
||||
},
|
||||
actions: NO_PANEL_ACTIONS,
|
||||
};
|
||||
@@ -5,6 +5,7 @@ import { definition as PieChart } from './kinds/PieChartPanel/definition';
|
||||
import { definition as TimeSeries } from './kinds/TimeSeriesPanel/definition';
|
||||
import { definition as Table } from './kinds/TablePanel/definition';
|
||||
import { definition as List } from './kinds/ListPanel/definition';
|
||||
import { UNSUPPORTED_PANEL } from './kinds/UnsupportedPanel/definition';
|
||||
import type {
|
||||
PanelRegistry,
|
||||
RenderablePanelDefinition,
|
||||
@@ -22,8 +23,24 @@ export const PANELS: PanelRegistry = {
|
||||
[List.kind]: List,
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether this build can render the kind. `PanelKind` spans every kind the API declares,
|
||||
* but a dashboard spec written by a newer SigNoz can name one this client has never heard
|
||||
* of — so ask before doing work on a panel's behalf, such as fetching its data.
|
||||
*/
|
||||
export function isPanelKindSupported(kind: PanelKind): boolean {
|
||||
return kind in PANELS;
|
||||
}
|
||||
|
||||
/**
|
||||
* The definition for a kind — always one. An unregistered kind resolves to
|
||||
* {@link UNSUPPORTED_PANEL}, which declares no capabilities and renders as unsupported, so
|
||||
* callers read a definition's fields without first proving it exists.
|
||||
*/
|
||||
export function getPanelDefinition(kind: PanelKind): RenderablePanelDefinition {
|
||||
// Single intentional cast widening the per-kind Renderer to the kind-agnostic
|
||||
// prop surface (a per-kind renderer can't be statically validated against the union).
|
||||
return PANELS[kind] as RenderablePanelDefinition;
|
||||
return (
|
||||
(PANELS[kind] as RenderablePanelDefinition | undefined) ?? UNSUPPORTED_PANEL
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import type { TelemetrytypesSignalDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import {
|
||||
Querybuildertypesv5RequestTypeDTO,
|
||||
type TelemetrytypesSignalDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import type { QueryBuilderProps } from 'container/QueryBuilder/QueryBuilder.interfaces';
|
||||
|
||||
/**
|
||||
@@ -18,3 +21,37 @@ export type FilterConfigsPartial = NonNullable<
|
||||
export type QueryBuilderFieldRule = {
|
||||
default?: FilterConfigsPartial;
|
||||
} & Partial<Record<TelemetrytypesSignalDTO, FilterConfigsPartial>>;
|
||||
|
||||
/**
|
||||
* How a kind's query-range request is shaped. Declared per-kind in
|
||||
* `kinds/<Kind>/definition.ts` and read through the capabilities guard, so no V2 code
|
||||
* has to translate a panel kind into the legacy `PANEL_TYPES` enum to answer these.
|
||||
*/
|
||||
export interface PanelQueryCapabilities {
|
||||
/** V5 request type the panel's data comes back as. */
|
||||
requestType: Querybuildertypesv5RequestTypeDTO;
|
||||
/** Server transposes the scalar result into UI table rows (`formatOptions.formatTableResultForUI`). */
|
||||
formatTableResultForUI: boolean;
|
||||
/**
|
||||
* Widen the step interval to cap how many buckets come back — kinds that bin
|
||||
* client-side from a raw time series rather than plotting every point.
|
||||
*/
|
||||
bucketedStepInterval: boolean;
|
||||
/**
|
||||
* Append a deterministic tiebreaker to the query's `order` so offset paging over raw
|
||||
* rows can't repeat or skip a row when the sort key has duplicates.
|
||||
*/
|
||||
orderTiebreaker: boolean;
|
||||
/**
|
||||
* Rows page server-side via `offset`/`limit`. AND-ed at the call site with "the query
|
||||
* carries no explicit limit" — an explicit limit means the user asked for a fixed set.
|
||||
*/
|
||||
serverPaginated: boolean;
|
||||
/**
|
||||
* Authored as a list view: the query builder drops its aggregation controls, and the
|
||||
* editor preview hides the plot-mode chip because nothing is plotted.
|
||||
*/
|
||||
listView: boolean;
|
||||
/** Query builder offers a trace operator alongside the builder queries. */
|
||||
traceOperator: boolean;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,10 @@ import type { EQueryType } from 'types/common/dashboard';
|
||||
import type { SectionConfig } from './sections';
|
||||
import type { AnyPanelInteractionProps } from './interactions';
|
||||
import type { PanelKind } from './panelKind';
|
||||
import type { QueryBuilderFieldRule } from './panelCapabilities';
|
||||
import type {
|
||||
PanelQueryCapabilities,
|
||||
QueryBuilderFieldRule,
|
||||
} from './panelCapabilities';
|
||||
import type { BaseRendererProps, PanelRendererProps } from './rendererProps';
|
||||
|
||||
/** Export formats offered under the single "Download" action. */
|
||||
@@ -39,6 +42,24 @@ export interface PanelActionCapabilities {
|
||||
drilldown: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* No actions at all — for a kind this build can't render, where every action would act on
|
||||
* a panel body that isn't there. See `UNSUPPORTED_PANEL`.
|
||||
*/
|
||||
export const NO_PANEL_ACTIONS: PanelActionCapabilities = {
|
||||
view: false,
|
||||
edit: false,
|
||||
clone: false,
|
||||
download: {
|
||||
[DownloadFormat.CSV]: false,
|
||||
[DownloadFormat.PNG]: false,
|
||||
[DownloadFormat.SVG]: false,
|
||||
},
|
||||
createAlert: false,
|
||||
search: false,
|
||||
drilldown: false,
|
||||
};
|
||||
|
||||
export interface PanelDefinition<K extends PanelKind = PanelKind> {
|
||||
kind: K;
|
||||
displayName: string;
|
||||
@@ -50,6 +71,8 @@ export interface PanelDefinition<K extends PanelKind = PanelKind> {
|
||||
supportedQueryTypes: EQueryType[];
|
||||
/** Query-builder fields this kind hides/disables, optionally per signal (`{}` hides none). */
|
||||
queryBuilderFields: QueryBuilderFieldRule;
|
||||
/** How this kind's query-range request is shaped (request type, paging, result formatting). */
|
||||
query: PanelQueryCapabilities;
|
||||
actions: PanelActionCapabilities;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,31 @@
|
||||
import { Querybuildertypesv5RequestTypeDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
|
||||
import type { PanelQueryCapabilities } from '../../types/panelCapabilities';
|
||||
import { buildDefaultQueries } from '../buildDefaultQueries';
|
||||
|
||||
// What a plotted kind and a list-view kind declare. Passed in rather than resolved from
|
||||
// the registry, which would pull every panel renderer into this suite.
|
||||
const PLOTTED_CAPS: PanelQueryCapabilities = {
|
||||
requestType: Querybuildertypesv5RequestTypeDTO.time_series,
|
||||
formatTableResultForUI: false,
|
||||
bucketedStepInterval: false,
|
||||
orderTiebreaker: false,
|
||||
serverPaginated: false,
|
||||
listView: false,
|
||||
traceOperator: true,
|
||||
};
|
||||
const LIST_CAPS: PanelQueryCapabilities = {
|
||||
...PLOTTED_CAPS,
|
||||
requestType: Querybuildertypesv5RequestTypeDTO.raw,
|
||||
orderTiebreaker: true,
|
||||
serverPaginated: true,
|
||||
listView: true,
|
||||
traceOperator: false,
|
||||
};
|
||||
|
||||
describe('buildDefaultQueries', () => {
|
||||
it('seeds a List panel with a runnable logs query ordered by timestamp desc', () => {
|
||||
const queries = buildDefaultQueries('signoz/ListPanel');
|
||||
it('seeds a list view with a runnable logs query ordered by timestamp desc', () => {
|
||||
const queries = buildDefaultQueries('signoz/ListPanel', LIST_CAPS);
|
||||
|
||||
expect(queries).toHaveLength(1);
|
||||
// orderBy timestamp desc must survive serialization so the preview opens
|
||||
@@ -13,16 +36,20 @@ describe('buildDefaultQueries', () => {
|
||||
expect(serialized.toLowerCase()).toContain('logs');
|
||||
});
|
||||
|
||||
it('seeds a List panel without a limit so it pages server-side by default', () => {
|
||||
const queries = buildDefaultQueries('signoz/ListPanel');
|
||||
it('seeds a list view without a limit so it pages server-side by default', () => {
|
||||
const queries = buildDefaultQueries('signoz/ListPanel', LIST_CAPS);
|
||||
|
||||
// A limit would make usePanelQuery treat the panel as a static, unpaged list.
|
||||
const spec = queries[0].spec.plugin.spec as { limit?: number };
|
||||
expect(spec.limit).toBeUndefined();
|
||||
});
|
||||
|
||||
it('seeds no query for non-List kinds (they seed from the builder)', () => {
|
||||
expect(buildDefaultQueries('signoz/TimeSeriesPanel')).toStrictEqual([]);
|
||||
expect(buildDefaultQueries('signoz/NumberPanel')).toStrictEqual([]);
|
||||
it('seeds no query for plotted kinds (they seed from the builder)', () => {
|
||||
expect(
|
||||
buildDefaultQueries('signoz/TimeSeriesPanel', PLOTTED_CAPS),
|
||||
).toStrictEqual([]);
|
||||
expect(buildDefaultQueries('signoz/NumberPanel', PLOTTED_CAPS)).toStrictEqual(
|
||||
[],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,7 +3,6 @@ import type {
|
||||
DashboardtypesThresholdWithLabelDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { Timezone } from 'components/CustomTimePicker/timezoneUtils';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { PanelMode } from 'container/DashboardContainer/visualization/panels/types';
|
||||
import onClickPlugin, {
|
||||
OnClickPluginOpts,
|
||||
@@ -26,7 +25,11 @@ import {
|
||||
*/
|
||||
export interface BuildBaseConfigArgs {
|
||||
panelId: string;
|
||||
panelType: PANEL_TYPES;
|
||||
/**
|
||||
* X axis plots timestamps, so its ticks format as dates/times. Each kind states this
|
||||
* for itself — a bucketed x axis (histogram) passes false.
|
||||
*/
|
||||
isTimeAxis: boolean;
|
||||
isDarkMode: boolean;
|
||||
timezone: Timezone;
|
||||
panelMode: PanelMode;
|
||||
@@ -63,7 +66,7 @@ export interface BuildBaseConfigArgs {
|
||||
*/
|
||||
export function buildBaseConfig({
|
||||
panelId,
|
||||
panelType,
|
||||
isTimeAxis,
|
||||
isDarkMode,
|
||||
timezone,
|
||||
panelMode,
|
||||
@@ -133,7 +136,7 @@ export function buildBaseConfig({
|
||||
side: 2,
|
||||
isDarkMode,
|
||||
isLogScale,
|
||||
panelType,
|
||||
isTimeAxis,
|
||||
});
|
||||
|
||||
builder.addAxis({
|
||||
@@ -143,7 +146,6 @@ export function buildBaseConfig({
|
||||
isDarkMode,
|
||||
isLogScale,
|
||||
yAxisUnit,
|
||||
panelType,
|
||||
});
|
||||
|
||||
return builder;
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
import type { DashboardtypesQueryDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { listViewInitialLogQuery, PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { listViewInitialLogQuery } from 'constants/queryBuilder';
|
||||
|
||||
import { toPerses } from '../../queryV5/persesQueryAdapters';
|
||||
import type { PanelQueryCapabilities } from '../types/panelCapabilities';
|
||||
import { PANEL_KIND_TO_PANEL_TYPE, type PanelKind } from '../types/panelKind';
|
||||
|
||||
/** Seed query for a new panel. Only List needs one (logs, timestamp desc) so its
|
||||
/** Seed query for a new panel. Only a list view needs one (logs, timestamp desc) so its
|
||||
* preview runs on open; other kinds start empty and seed from the builder. */
|
||||
export function buildDefaultQueries(kind: PanelKind): DashboardtypesQueryDTO[] {
|
||||
if (PANEL_KIND_TO_PANEL_TYPE[kind] === PANEL_TYPES.LIST) {
|
||||
return toPerses(listViewInitialLogQuery, PANEL_TYPES.LIST);
|
||||
export function buildDefaultQueries(
|
||||
kind: PanelKind,
|
||||
queryCapabilities: PanelQueryCapabilities,
|
||||
): DashboardtypesQueryDTO[] {
|
||||
if (!queryCapabilities.listView) {
|
||||
return [];
|
||||
}
|
||||
return [];
|
||||
// `toPerses` pivots through the V1 `Query`, which is still keyed by panel type.
|
||||
return toPerses(listViewInitialLogQuery, PANEL_KIND_TO_PANEL_TYPE[kind]);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { useState } from 'react';
|
||||
import type { DashboardtypesPanelDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import ContextMenu from 'periscope/components/ContextMenu';
|
||||
import { getPanelDefinition } from 'pages/DashboardPageV2/DashboardContainer/Panels/registry';
|
||||
import {
|
||||
getPanelDefinition,
|
||||
isPanelKindSupported,
|
||||
} from 'pages/DashboardPageV2/DashboardContainer/Panels/registry';
|
||||
import {
|
||||
getPanelTimePreference,
|
||||
panelTimePreferenceLabel,
|
||||
@@ -50,15 +53,17 @@ function Panel({
|
||||
|
||||
// Header search: only kinds that declare it render the box. The term is owned
|
||||
// here and threaded to both the header (input) and renderer (filter).
|
||||
const searchable = !!panelDefinition?.actions.search;
|
||||
const searchable = panelDefinition.actions.search;
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
|
||||
const { data, isFetching, isPreviousData, error, refetch, pagination } =
|
||||
usePanelQuery({
|
||||
panel,
|
||||
panelId,
|
||||
// Lazy: fetch only once on screen (undefined → visible) and a renderer exists.
|
||||
enabled: !!panelDefinition && isVisible !== false,
|
||||
queryCapabilities: panelDefinition.query,
|
||||
// Lazy: fetch only once on screen (undefined → visible), and never for a kind
|
||||
// this build can't render — the data would have nothing to render into.
|
||||
enabled: isPanelKindSupported(panelKind) && isVisible !== false,
|
||||
});
|
||||
|
||||
const { onDragSelect, dashboardPreference } = usePanelInteractions();
|
||||
@@ -85,25 +90,23 @@ function Panel({
|
||||
searchTerm={searchTerm}
|
||||
onSearchChange={setSearchTerm}
|
||||
/>
|
||||
{panelDefinition && (
|
||||
<PanelBody
|
||||
panelDefinition={panelDefinition}
|
||||
panel={panel}
|
||||
panelId={panelId}
|
||||
data={data}
|
||||
isFetching={isFetching}
|
||||
isVisible={isVisible}
|
||||
isPreviousData={isPreviousData}
|
||||
error={error}
|
||||
refetch={refetch}
|
||||
onDragSelect={onDragSelect}
|
||||
dashboardPreference={dashboardPreference}
|
||||
searchTerm={searchable ? searchTerm : undefined}
|
||||
pagination={pagination}
|
||||
onClick={drilldown.onPanelClick}
|
||||
enableDrillDown={drilldown.enableDrillDown}
|
||||
/>
|
||||
)}
|
||||
<PanelBody
|
||||
panelDefinition={panelDefinition}
|
||||
panel={panel}
|
||||
panelId={panelId}
|
||||
data={data}
|
||||
isFetching={isFetching}
|
||||
isVisible={isVisible}
|
||||
isPreviousData={isPreviousData}
|
||||
error={error}
|
||||
refetch={refetch}
|
||||
onDragSelect={onDragSelect}
|
||||
dashboardPreference={dashboardPreference}
|
||||
searchTerm={searchable ? searchTerm : undefined}
|
||||
pagination={pagination}
|
||||
onClick={drilldown.onPanelClick}
|
||||
enableDrillDown={drilldown.enableDrillDown}
|
||||
/>
|
||||
<ContextMenu {...drilldown.contextMenuProps} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
import { type KeyboardEvent, useCallback } from 'react';
|
||||
import { QueryBuilderV2 } from 'components/QueryBuilderV2/QueryBuilderV2';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import RightToolbarActions from 'container/QueryBuilder/components/ToolbarActions/RightToolbarActions';
|
||||
|
||||
import styles from './ViewPanelModal.module.scss';
|
||||
|
||||
interface ViewPanelQueryBuilderProps {
|
||||
panelType: PANEL_TYPES;
|
||||
/** Preview fetch in flight — drives the Run/Cancel button state. */
|
||||
isLoadingQueries: boolean;
|
||||
/** Run the current query (Run Query button / ⌘↵). */
|
||||
onStageRunQuery: () => void;
|
||||
/** Abort the in-flight preview fetch. */
|
||||
onCancelQuery: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Drilldown query editor for the View modal. Mirrors V1's FullView: the query builder
|
||||
* rows + a "Run Query" button, with NO query-type tabs (ClickHouse/PromQL) — drilldown
|
||||
* is query-builder only, exactly as V1.
|
||||
*/
|
||||
function ViewPanelQueryBuilder({
|
||||
panelType,
|
||||
isLoadingQueries,
|
||||
onStageRunQuery,
|
||||
onCancelQuery,
|
||||
}: ViewPanelQueryBuilderProps): JSX.Element {
|
||||
const handleKeyDownCapture = useCallback(
|
||||
(event: KeyboardEvent<HTMLDivElement>): void => {
|
||||
if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
onStageRunQuery();
|
||||
}
|
||||
},
|
||||
[onStageRunQuery],
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.queryBuilder}
|
||||
data-testid="view-panel-query-builder"
|
||||
onKeyDownCapture={handleKeyDownCapture}
|
||||
role="presentation"
|
||||
>
|
||||
<QueryBuilderV2
|
||||
panelType={panelType}
|
||||
version="v3"
|
||||
isListViewPanel={panelType === PANEL_TYPES.LIST}
|
||||
signalSourceChangeEnabled
|
||||
/>
|
||||
<div className={styles.queryBuilderToolbar}>
|
||||
<RightToolbarActions
|
||||
handleCancelQuery={onCancelQuery}
|
||||
onStageRunQuery={onStageRunQuery}
|
||||
isLoadingQueries={isLoadingQueries}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ViewPanelQueryBuilder;
|
||||
@@ -148,7 +148,9 @@ describe('useCreateAlertFromPanel', () => {
|
||||
expect(mockBuildQueryRangeRequest).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
queries: panel.spec.queries,
|
||||
panelType: PANEL_TYPES.TIME_SERIES,
|
||||
queryCapabilities: expect.objectContaining({
|
||||
requestType: 'time_series',
|
||||
}),
|
||||
variables: { service: { type: 'query', value: 'checkout' } },
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -81,6 +81,7 @@ export function useClonePanel({
|
||||
void logEvent(DashboardDetailEvents.PanelAction, {
|
||||
action: 'clone',
|
||||
panelType: PANEL_KIND_TO_PANEL_TYPE[source.panel.spec.plugin.kind],
|
||||
panelKind: source.panel.spec.plugin.kind,
|
||||
panelId,
|
||||
dashboardId,
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useReplaceVariables } from 'api/generated/services/querier';
|
||||
import type { DashboardtypesPanelDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||
import { getPanelDefinition } from 'pages/DashboardPageV2/DashboardContainer/Panels/registry';
|
||||
import { PANEL_KIND_TO_PANEL_TYPE } from 'pages/DashboardPageV2/DashboardContainer/Panels/types/panelKind';
|
||||
import { getPanelQueryType } from 'pages/DashboardPageV2/DashboardContainer/Panels/utils/getPanelQueryType';
|
||||
import { buildQueryRangeRequest } from 'pages/DashboardPageV2/DashboardContainer/queryV5/buildQueryRangeRequest';
|
||||
@@ -44,11 +45,15 @@ export function useCreateAlertFromPanel(): (
|
||||
|
||||
return useCallback(
|
||||
(panel: DashboardtypesPanelDTO, panelId: string): void => {
|
||||
const panelType = PANEL_KIND_TO_PANEL_TYPE[panel.spec.plugin.kind];
|
||||
const panelKind = panel.spec.plugin.kind;
|
||||
// Alerts are a V1 surface: the query pivots through the V1 `Query` shape and the
|
||||
// URL carries a legacy panel type, so this flow keeps translating.
|
||||
const panelType = PANEL_KIND_TO_PANEL_TYPE[panelKind];
|
||||
|
||||
void logEvent('Dashboard Detail: Panel action', {
|
||||
action: 'createAlerts',
|
||||
panelType,
|
||||
panelKind,
|
||||
dashboardId,
|
||||
widgetId: panelId,
|
||||
queryType: getPanelQueryType(panel),
|
||||
@@ -62,7 +67,7 @@ export function useCreateAlertFromPanel(): (
|
||||
// Redux global time is nanoseconds; the request DTO takes epoch ms.
|
||||
const request = buildQueryRangeRequest({
|
||||
queries: panel.spec.queries,
|
||||
panelType,
|
||||
queryCapabilities: getPanelDefinition(panelKind).query,
|
||||
startMs: Math.floor(minTime / NANO_SECOND_MULTIPLIER),
|
||||
endMs: Math.floor(maxTime / NANO_SECOND_MULTIPLIER),
|
||||
variables,
|
||||
|
||||
@@ -53,6 +53,7 @@ export function useDeletePanel({
|
||||
panelType: removed?.panel
|
||||
? PANEL_KIND_TO_PANEL_TYPE[removed.panel.spec.plugin.kind]
|
||||
: undefined,
|
||||
panelKind: removed?.panel?.spec.plugin.kind,
|
||||
panelId,
|
||||
dashboardId,
|
||||
});
|
||||
|
||||
@@ -43,6 +43,7 @@ export function useDownloadPanelCsv({
|
||||
void logEvent(DashboardDetailEvents.PanelExported, {
|
||||
format: 'csv',
|
||||
panelType: PANEL_KIND_TO_PANEL_TYPE[panel.spec.plugin.kind],
|
||||
panelKind: panel.spec.plugin.kind,
|
||||
});
|
||||
}, [canDownloadCsv, fileName, panel, data]);
|
||||
}
|
||||
|
||||
@@ -128,11 +128,14 @@ export function useDrilldown(
|
||||
|
||||
const onPanelClick = useCallback(
|
||||
(payload: DrilldownClickPayload): void => {
|
||||
void logEvent(DashboardDetailEvents.DrilldownOpened, { panelType });
|
||||
void logEvent(DashboardDetailEvents.DrilldownOpened, {
|
||||
panelType,
|
||||
panelKind: kind,
|
||||
});
|
||||
setSubMenu(DrilldownSubMenu.Base);
|
||||
onClick(payload.coordinates, payload.context);
|
||||
},
|
||||
[onClick, panelType],
|
||||
[onClick, panelType, kind],
|
||||
);
|
||||
|
||||
const handleClose = useCallback((): void => {
|
||||
@@ -176,7 +179,8 @@ export function useDrilldown(
|
||||
|
||||
const { resolvedQuery, isResolving } = useResolvedDrilldownQuery({
|
||||
queries,
|
||||
panelType,
|
||||
panelKind: kind,
|
||||
queryCapabilities: getPanelDefinition(kind).query,
|
||||
v1Query,
|
||||
enabled: showAggregateMenu,
|
||||
});
|
||||
|
||||
@@ -74,6 +74,7 @@ export function useMovePanelToSection({
|
||||
panelType: moved.panel
|
||||
? PANEL_KIND_TO_PANEL_TYPE[moved.panel.spec.plugin.kind]
|
||||
: undefined,
|
||||
panelKind: moved.panel?.spec.plugin.kind,
|
||||
panelId,
|
||||
dashboardId,
|
||||
});
|
||||
|
||||
@@ -3,7 +3,11 @@ import { useEffect, useMemo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useReplaceVariables } from 'api/generated/services/querier';
|
||||
import type { DashboardtypesQueryDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import type { PanelQueryCapabilities } from 'pages/DashboardPageV2/DashboardContainer/Panels/types/panelCapabilities';
|
||||
import {
|
||||
PANEL_KIND_TO_PANEL_TYPE,
|
||||
type PanelKind,
|
||||
} from 'pages/DashboardPageV2/DashboardContainer/Panels/types/panelKind';
|
||||
import { buildQueryRangeRequest } from 'pages/DashboardPageV2/DashboardContainer/queryV5/buildQueryRangeRequest';
|
||||
import { envelopesToQuery } from 'pages/DashboardPageV2/DashboardContainer/queryV5/persesQueryAdapters';
|
||||
import { selectResolvedVariables } from 'pages/DashboardPageV2/DashboardContainer/store/slices/variableSelectionSlice';
|
||||
@@ -15,7 +19,9 @@ import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
interface UseResolvedDrilldownQueryArgs {
|
||||
/** Panel's perses queries — the substitution source (carries the `$var` refs). */
|
||||
queries: DashboardtypesQueryDTO[];
|
||||
panelType: PANEL_TYPES;
|
||||
panelKind: PanelKind;
|
||||
/** The panel kind's declared query capabilities — shapes the substitution request. */
|
||||
queryCapabilities: PanelQueryCapabilities;
|
||||
/** The raw V5→V1 query; the fallback until substitution resolves / when no vars exist. */
|
||||
v1Query: Query;
|
||||
/** Resolve only while the aggregate menu is open (V1 parity: fires when it appears). */
|
||||
@@ -38,7 +44,8 @@ interface UseResolvedDrilldownQueryResult {
|
||||
*/
|
||||
export function useResolvedDrilldownQuery({
|
||||
queries,
|
||||
panelType,
|
||||
panelKind,
|
||||
queryCapabilities,
|
||||
v1Query,
|
||||
enabled,
|
||||
}: UseResolvedDrilldownQueryArgs): UseResolvedDrilldownQueryResult {
|
||||
@@ -60,7 +67,7 @@ export function useResolvedDrilldownQuery({
|
||||
substituteVars({
|
||||
data: buildQueryRangeRequest({
|
||||
queries,
|
||||
panelType,
|
||||
queryCapabilities,
|
||||
startMs: Math.floor(minTime / 1e6),
|
||||
endMs: Math.floor(maxTime / 1e6),
|
||||
variables,
|
||||
@@ -70,7 +77,7 @@ export function useResolvedDrilldownQuery({
|
||||
enabled,
|
||||
hasVariables,
|
||||
queries,
|
||||
panelType,
|
||||
queryCapabilities,
|
||||
minTime,
|
||||
maxTime,
|
||||
variables,
|
||||
@@ -81,8 +88,13 @@ export function useResolvedDrilldownQuery({
|
||||
if (!hasVariables || !data) {
|
||||
return v1Query;
|
||||
}
|
||||
return envelopesToQuery(data.data.compositeQuery?.queries ?? [], panelType);
|
||||
}, [hasVariables, data, v1Query, panelType]);
|
||||
// View-in-X navigates to a V1 explorer, so the resolved query crosses back into the
|
||||
// V1 `Query` shape — the one place this hook still needs a legacy panel type.
|
||||
return envelopesToQuery(
|
||||
data.data.compositeQuery?.queries ?? [],
|
||||
PANEL_KIND_TO_PANEL_TYPE[panelKind],
|
||||
);
|
||||
}, [hasVariables, data, v1Query, panelKind]);
|
||||
|
||||
return { resolvedQuery, isResolving: enabled && hasVariables && isLoading };
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import type { DashboardtypesPanelDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import {
|
||||
type DashboardtypesPanelDTO,
|
||||
Querybuildertypesv5RequestTypeDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import type { PanelQueryCapabilities } from 'pages/DashboardPageV2/DashboardContainer/Panels/types/panelCapabilities';
|
||||
import {
|
||||
DASHBOARD_CACHE_TIME,
|
||||
DASHBOARD_CACHE_TIME_ON_REFRESH_ENABLED,
|
||||
@@ -54,6 +58,27 @@ function panelWith(
|
||||
} as unknown as DashboardtypesPanelDTO;
|
||||
}
|
||||
|
||||
// The capability blocks TimeSeries and List declare. Passed in rather than resolved from
|
||||
// the registry: the hook takes them as input, and importing the registry here would pull
|
||||
// every panel renderer (and the app's API client) into this suite.
|
||||
const TIME_SERIES_CAPS: PanelQueryCapabilities = {
|
||||
requestType: Querybuildertypesv5RequestTypeDTO.time_series,
|
||||
formatTableResultForUI: false,
|
||||
bucketedStepInterval: false,
|
||||
orderTiebreaker: false,
|
||||
serverPaginated: false,
|
||||
listView: false,
|
||||
traceOperator: true,
|
||||
};
|
||||
const LIST_CAPS: PanelQueryCapabilities = {
|
||||
...TIME_SERIES_CAPS,
|
||||
requestType: Querybuildertypesv5RequestTypeDTO.raw,
|
||||
orderTiebreaker: true,
|
||||
serverPaginated: true,
|
||||
listView: true,
|
||||
traceOperator: false,
|
||||
};
|
||||
|
||||
function builderPanel(): DashboardtypesPanelDTO {
|
||||
return panelWith('signoz/TimeSeriesPanel', {
|
||||
name: 'A',
|
||||
@@ -100,7 +125,13 @@ beforeEach(() => {
|
||||
|
||||
describe('usePanelQuery', () => {
|
||||
it('builds the generated V5 request DTO directly from panel.spec.queries', () => {
|
||||
renderHook(() => usePanelQuery({ panel: builderPanel(), panelId: 'p1' }));
|
||||
renderHook(() =>
|
||||
usePanelQuery({
|
||||
panel: builderPanel(),
|
||||
panelId: 'p1',
|
||||
queryCapabilities: TIME_SERIES_CAPS,
|
||||
}),
|
||||
);
|
||||
const [{ requestPayload }] = mockUseGetQueryRangeV5.mock.calls[0];
|
||||
expect(requestPayload.schemaVersion).toBe('v1');
|
||||
expect(requestPayload.compositeQuery.queries).toStrictEqual([
|
||||
@@ -112,30 +143,30 @@ describe('usePanelQuery', () => {
|
||||
});
|
||||
|
||||
it('converts redux nanosecond time to epoch ms on the request', () => {
|
||||
renderHook(() => usePanelQuery({ panel: builderPanel(), panelId: 'p1' }));
|
||||
renderHook(() =>
|
||||
usePanelQuery({
|
||||
panel: builderPanel(),
|
||||
panelId: 'p1',
|
||||
queryCapabilities: TIME_SERIES_CAPS,
|
||||
}),
|
||||
);
|
||||
const [{ requestPayload }] = mockUseGetQueryRangeV5.mock.calls[0];
|
||||
expect(requestPayload.start).toBe(1_000_000_000);
|
||||
expect(requestPayload.end).toBe(2_000_000_000);
|
||||
});
|
||||
|
||||
it.each([
|
||||
['signoz/TimeSeriesPanel', 'time_series'],
|
||||
['signoz/ListPanel', 'raw'],
|
||||
// HISTOGRAM and BAR panels bin/derive from raw time-series data
|
||||
// client-side, so the backend must receive `time_series` (V1 parity).
|
||||
['signoz/HistogramPanel', 'time_series'],
|
||||
['signoz/BarChartPanel', 'time_series'],
|
||||
['signoz/NumberPanel', 'scalar'],
|
||||
['signoz/PieChartPanel', 'scalar'],
|
||||
])('%s panel sends requestType=%s', (panelKind, requestType) => {
|
||||
// Which requestType each kind declares is asserted in
|
||||
// Panels/__tests__/capabilities.test.ts; here it only has to reach the request.
|
||||
it('sends the requestType from the declared query capabilities', () => {
|
||||
renderHook(() =>
|
||||
usePanelQuery({
|
||||
panel: panelWith(panelKind, { name: 'A', signal: 'logs' }),
|
||||
panel: panelWith('signoz/ListPanel', { name: 'A', signal: 'logs' }),
|
||||
panelId: 'p1',
|
||||
queryCapabilities: LIST_CAPS,
|
||||
}),
|
||||
);
|
||||
const [{ requestPayload }] = mockUseGetQueryRangeV5.mock.calls[0];
|
||||
expect(requestPayload.requestType).toBe(requestType);
|
||||
expect(requestPayload.requestType).toBe('raw');
|
||||
});
|
||||
|
||||
it('exposes the raw V5 response, request payload, and legend map on data', () => {
|
||||
@@ -148,7 +179,11 @@ describe('usePanelQuery', () => {
|
||||
});
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
usePanelQuery({ panel: builderPanel(), panelId: 'p1' }),
|
||||
usePanelQuery({
|
||||
panel: builderPanel(),
|
||||
panelId: 'p1',
|
||||
queryCapabilities: TIME_SERIES_CAPS,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.current.data.response).toBe(v5Response);
|
||||
@@ -158,7 +193,11 @@ describe('usePanelQuery', () => {
|
||||
|
||||
it('exposes an undefined response before data arrives', () => {
|
||||
const { result } = renderHook(() =>
|
||||
usePanelQuery({ panel: builderPanel(), panelId: 'p1' }),
|
||||
usePanelQuery({
|
||||
panel: builderPanel(),
|
||||
panelId: 'p1',
|
||||
queryCapabilities: TIME_SERIES_CAPS,
|
||||
}),
|
||||
);
|
||||
expect(result.current.data.response).toBeUndefined();
|
||||
});
|
||||
@@ -171,7 +210,11 @@ describe('usePanelQuery', () => {
|
||||
error: new Error('boom'),
|
||||
});
|
||||
const { result } = renderHook(() =>
|
||||
usePanelQuery({ panel: builderPanel(), panelId: 'p1' }),
|
||||
usePanelQuery({
|
||||
panel: builderPanel(),
|
||||
panelId: 'p1',
|
||||
queryCapabilities: TIME_SERIES_CAPS,
|
||||
}),
|
||||
);
|
||||
expect(result.current.error?.message).toBe('boom');
|
||||
});
|
||||
@@ -186,7 +229,11 @@ describe('usePanelQuery', () => {
|
||||
error: null,
|
||||
});
|
||||
const { result } = renderHook(() =>
|
||||
usePanelQuery({ panel: builderPanel(), panelId: 'p1' }),
|
||||
usePanelQuery({
|
||||
panel: builderPanel(),
|
||||
panelId: 'p1',
|
||||
queryCapabilities: TIME_SERIES_CAPS,
|
||||
}),
|
||||
);
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
expect(result.current.isFetching).toBe(true);
|
||||
@@ -200,7 +247,11 @@ describe('usePanelQuery', () => {
|
||||
error: null,
|
||||
});
|
||||
const { result } = renderHook(() =>
|
||||
usePanelQuery({ panel: builderPanel(), panelId: 'p1' }),
|
||||
usePanelQuery({
|
||||
panel: builderPanel(),
|
||||
panelId: 'p1',
|
||||
queryCapabilities: TIME_SERIES_CAPS,
|
||||
}),
|
||||
);
|
||||
expect(result.current.isLoading).toBe(true);
|
||||
});
|
||||
@@ -213,14 +264,23 @@ describe('usePanelQuery', () => {
|
||||
error: undefined,
|
||||
});
|
||||
const { result } = renderHook(() =>
|
||||
usePanelQuery({ panel: builderPanel(), panelId: 'p1' }),
|
||||
usePanelQuery({
|
||||
panel: builderPanel(),
|
||||
panelId: 'p1',
|
||||
queryCapabilities: TIME_SERIES_CAPS,
|
||||
}),
|
||||
);
|
||||
expect(result.current.error).toBeNull();
|
||||
});
|
||||
|
||||
it('passes enabled=false to the fetch hook when the caller disables it', () => {
|
||||
renderHook(() =>
|
||||
usePanelQuery({ panel: builderPanel(), panelId: 'p1', enabled: false }),
|
||||
usePanelQuery({
|
||||
panel: builderPanel(),
|
||||
panelId: 'p1',
|
||||
queryCapabilities: TIME_SERIES_CAPS,
|
||||
enabled: false,
|
||||
}),
|
||||
);
|
||||
const [{ enabled }] = mockUseGetQueryRangeV5.mock.calls[0];
|
||||
expect(enabled).toBe(false);
|
||||
@@ -228,7 +288,12 @@ describe('usePanelQuery', () => {
|
||||
|
||||
it('auto-disables the fetch when the panel has no queries (even with enabled=true)', () => {
|
||||
renderHook(() =>
|
||||
usePanelQuery({ panel: emptyPanel(), panelId: 'p1', enabled: true }),
|
||||
usePanelQuery({
|
||||
panel: emptyPanel(),
|
||||
panelId: 'p1',
|
||||
queryCapabilities: TIME_SERIES_CAPS,
|
||||
enabled: true,
|
||||
}),
|
||||
);
|
||||
const [{ enabled }] = mockUseGetQueryRangeV5.mock.calls[0];
|
||||
expect(enabled).toBe(false);
|
||||
@@ -243,6 +308,7 @@ describe('usePanelQuery', () => {
|
||||
aggregations: [{}],
|
||||
}),
|
||||
panelId: 'p1',
|
||||
queryCapabilities: TIME_SERIES_CAPS,
|
||||
}),
|
||||
);
|
||||
const [{ enabled }] = mockUseGetQueryRangeV5.mock.calls[0];
|
||||
@@ -251,7 +317,9 @@ describe('usePanelQuery', () => {
|
||||
|
||||
it('composes a react-query cache key that includes panelId, time range, kind, and queries', () => {
|
||||
const panel = builderPanel();
|
||||
renderHook(() => usePanelQuery({ panel, panelId: 'p1' }));
|
||||
renderHook(() =>
|
||||
usePanelQuery({ panel, panelId: 'p1', queryCapabilities: TIME_SERIES_CAPS }),
|
||||
);
|
||||
const [{ queryKey }] = mockUseGetQueryRangeV5.mock.calls[0];
|
||||
expect(queryKey).toStrictEqual(
|
||||
expect.arrayContaining([
|
||||
@@ -270,6 +338,7 @@ describe('usePanelQuery', () => {
|
||||
renderHook(() =>
|
||||
usePanelQuery({
|
||||
panel,
|
||||
queryCapabilities: TIME_SERIES_CAPS,
|
||||
panelId: 'p1',
|
||||
time: { startMs: 1_700_000_000_000, endMs: 1_700_000_600_000 },
|
||||
}),
|
||||
@@ -296,6 +365,7 @@ describe('usePanelQuery', () => {
|
||||
usePanelQuery({
|
||||
panel: builderPanel(),
|
||||
panelId: 'p1',
|
||||
queryCapabilities: TIME_SERIES_CAPS,
|
||||
time: { startMs: 1_700_000_000_000.546, endMs: 1_700_000_600_000.999 },
|
||||
}),
|
||||
);
|
||||
@@ -316,7 +386,11 @@ describe('usePanelQuery', () => {
|
||||
|
||||
it('exposes server paging at the default page size when the query has no limit', () => {
|
||||
const { result } = renderHook(() =>
|
||||
usePanelQuery({ panel: listPanel({}), panelId: 'p1' }),
|
||||
usePanelQuery({
|
||||
panel: listPanel({}),
|
||||
panelId: 'p1',
|
||||
queryCapabilities: LIST_CAPS,
|
||||
}),
|
||||
);
|
||||
expect(result.current.pagination).toBeDefined();
|
||||
expect(result.current.pagination?.pageSize).toBe(25);
|
||||
@@ -327,20 +401,34 @@ describe('usePanelQuery', () => {
|
||||
|
||||
it('disables the server pager when the query has an explicit limit (V1 parity)', () => {
|
||||
const { result } = renderHook(() =>
|
||||
usePanelQuery({ panel: listPanel({ limit: 100 }), panelId: 'p1' }),
|
||||
usePanelQuery({
|
||||
panel: listPanel({ limit: 100 }),
|
||||
panelId: 'p1',
|
||||
queryCapabilities: LIST_CAPS,
|
||||
}),
|
||||
);
|
||||
expect(result.current.pagination).toBeUndefined();
|
||||
});
|
||||
|
||||
it('keeps previous data while paging so the table/pager stay mounted on page change', () => {
|
||||
renderHook(() => usePanelQuery({ panel: listPanel({}), panelId: 'p1' }));
|
||||
renderHook(() =>
|
||||
usePanelQuery({
|
||||
panel: listPanel({}),
|
||||
panelId: 'p1',
|
||||
queryCapabilities: LIST_CAPS,
|
||||
}),
|
||||
);
|
||||
const [{ keepPreviousData }] = mockUseGetQueryRangeV5.mock.calls[0];
|
||||
expect(keepPreviousData).toBe(true);
|
||||
});
|
||||
|
||||
it('changes the page size (and re-requests with the new limit) via setPageSize', () => {
|
||||
const { result } = renderHook(() =>
|
||||
usePanelQuery({ panel: listPanel({}), panelId: 'p1' }),
|
||||
usePanelQuery({
|
||||
panel: listPanel({}),
|
||||
panelId: 'p1',
|
||||
queryCapabilities: LIST_CAPS,
|
||||
}),
|
||||
);
|
||||
|
||||
act(() => result.current.pagination?.setPageSize(50));
|
||||
@@ -380,7 +468,11 @@ describe('usePanelQuery', () => {
|
||||
|
||||
it('starts on page 0 with no prev/next and does not throw before data arrives', () => {
|
||||
const { result } = renderHook(() =>
|
||||
usePanelQuery({ panel: listPanel({}), panelId: 'p1' }),
|
||||
usePanelQuery({
|
||||
panel: listPanel({}),
|
||||
panelId: 'p1',
|
||||
queryCapabilities: LIST_CAPS,
|
||||
}),
|
||||
);
|
||||
expect(result.current.pagination?.pageIndex).toBe(0);
|
||||
expect(result.current.pagination?.canPrev).toBe(false);
|
||||
@@ -392,21 +484,33 @@ describe('usePanelQuery', () => {
|
||||
// window/cursor path), so a full page is the has-more signal.
|
||||
withResponse(rawResponse(25));
|
||||
const fullPage = renderHook(() =>
|
||||
usePanelQuery({ panel: listPanel({}), panelId: 'p1' }),
|
||||
usePanelQuery({
|
||||
panel: listPanel({}),
|
||||
panelId: 'p1',
|
||||
queryCapabilities: LIST_CAPS,
|
||||
}),
|
||||
);
|
||||
expect(fullPage.result.current.pagination?.canNext).toBe(true);
|
||||
|
||||
// Partial page, no cursor → the last page.
|
||||
withResponse(rawResponse(3));
|
||||
const partialPage = renderHook(() =>
|
||||
usePanelQuery({ panel: listPanel({}), panelId: 'p1' }),
|
||||
usePanelQuery({
|
||||
panel: listPanel({}),
|
||||
panelId: 'p1',
|
||||
queryCapabilities: LIST_CAPS,
|
||||
}),
|
||||
);
|
||||
expect(partialPage.result.current.pagination?.canNext).toBe(false);
|
||||
|
||||
// Cursor present (even on a partial page) → more rows (timestamp window path).
|
||||
withResponse(rawResponse(3, 'cursor-1'));
|
||||
const withCursor = renderHook(() =>
|
||||
usePanelQuery({ panel: listPanel({}), panelId: 'p1' }),
|
||||
usePanelQuery({
|
||||
panel: listPanel({}),
|
||||
panelId: 'p1',
|
||||
queryCapabilities: LIST_CAPS,
|
||||
}),
|
||||
);
|
||||
expect(withCursor.result.current.pagination?.canNext).toBe(true);
|
||||
});
|
||||
@@ -416,7 +520,9 @@ describe('usePanelQuery', () => {
|
||||
// Stable panel reference: a fresh one each render would change the
|
||||
// `queries` identity and trip the offset-reset effect (real props are stable).
|
||||
const panel = listPanel({});
|
||||
const { result } = renderHook(() => usePanelQuery({ panel, panelId: 'p1' }));
|
||||
const { result } = renderHook(() =>
|
||||
usePanelQuery({ panel, panelId: 'p1', queryCapabilities: LIST_CAPS }),
|
||||
);
|
||||
expect(result.current.pagination?.pageIndex).toBe(0);
|
||||
|
||||
act(() => result.current.pagination?.goNext());
|
||||
@@ -428,7 +534,11 @@ describe('usePanelQuery', () => {
|
||||
it('stays defined and zero-paged for a non-raw (scalar) response', () => {
|
||||
withResponse({ data: { type: 'scalar', data: { results: [] } } });
|
||||
const { result } = renderHook(() =>
|
||||
usePanelQuery({ panel: listPanel({}), panelId: 'p1' }),
|
||||
usePanelQuery({
|
||||
panel: listPanel({}),
|
||||
panelId: 'p1',
|
||||
queryCapabilities: LIST_CAPS,
|
||||
}),
|
||||
);
|
||||
expect(result.current.pagination).toBeDefined();
|
||||
expect(result.current.pagination?.canNext).toBe(false);
|
||||
@@ -437,7 +547,11 @@ describe('usePanelQuery', () => {
|
||||
|
||||
it('ignores a non-positive page size so paging never goes invalid', () => {
|
||||
const { result } = renderHook(() =>
|
||||
usePanelQuery({ panel: listPanel({}), panelId: 'p1' }),
|
||||
usePanelQuery({
|
||||
panel: listPanel({}),
|
||||
panelId: 'p1',
|
||||
queryCapabilities: LIST_CAPS,
|
||||
}),
|
||||
);
|
||||
act(() => result.current.pagination?.setPageSize(0));
|
||||
expect(result.current.pagination?.pageSize).toBe(25);
|
||||
@@ -456,14 +570,26 @@ describe('usePanelQuery', () => {
|
||||
|
||||
it('caches for DASHBOARD_CACHE_TIME when auto-refresh is disabled', () => {
|
||||
withAutoRefreshDisabled(true);
|
||||
renderHook(() => usePanelQuery({ panel: builderPanel(), panelId: 'p1' }));
|
||||
renderHook(() =>
|
||||
usePanelQuery({
|
||||
panel: builderPanel(),
|
||||
panelId: 'p1',
|
||||
queryCapabilities: TIME_SERIES_CAPS,
|
||||
}),
|
||||
);
|
||||
const [{ cacheTime }] = mockUseGetQueryRangeV5.mock.calls[0];
|
||||
expect(cacheTime).toBe(DASHBOARD_CACHE_TIME);
|
||||
});
|
||||
|
||||
it('drops cacheTime to 0 when auto-refresh is enabled', () => {
|
||||
withAutoRefreshDisabled(false);
|
||||
renderHook(() => usePanelQuery({ panel: builderPanel(), panelId: 'p1' }));
|
||||
renderHook(() =>
|
||||
usePanelQuery({
|
||||
panel: builderPanel(),
|
||||
panelId: 'p1',
|
||||
queryCapabilities: TIME_SERIES_CAPS,
|
||||
}),
|
||||
);
|
||||
const [{ cacheTime }] = mockUseGetQueryRangeV5.mock.calls[0];
|
||||
expect(cacheTime).toBe(DASHBOARD_CACHE_TIME_ON_REFRESH_ENABLED);
|
||||
});
|
||||
|
||||
@@ -3,7 +3,6 @@ import { useQueryClient } from 'react-query';
|
||||
// eslint-disable-next-line no-restricted-imports -- TODO: migrate global time selector off redux
|
||||
import { useSelector } from 'react-redux';
|
||||
import type { DashboardtypesPanelDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import {
|
||||
DASHBOARD_CACHE_TIME,
|
||||
DASHBOARD_CACHE_TIME_ON_REFRESH_ENABLED,
|
||||
@@ -24,7 +23,7 @@ import {
|
||||
queryReferencesAnyVariable,
|
||||
} from '../queryV5/getReferencedVariables';
|
||||
import { getBuilderQueries } from '../Panels/utils/getBuilderQueries';
|
||||
import { PANEL_KIND_TO_PANEL_TYPE } from '../Panels/types/panelKind';
|
||||
import type { PanelQueryCapabilities } from '../Panels/types/panelCapabilities';
|
||||
import { selectResolvedVariables } from '../store/slices/variableSelectionSlice';
|
||||
import { useDashboardStore } from '../store/useDashboardStore';
|
||||
import { resolvePanelTimeWindow } from './resolvePanelTimeWindow';
|
||||
@@ -38,6 +37,8 @@ const DEFAULT_LIST_PAGE_SIZE = 25;
|
||||
export interface UsePanelQueryArgs {
|
||||
panel: DashboardtypesPanelDTO;
|
||||
panelId: string;
|
||||
/** The panel kind's declared query capabilities — `panelDefinition.query`, or `DEFAULT_QUERY_CAPABILITIES` for a kind the registry doesn't resolve. */
|
||||
queryCapabilities: PanelQueryCapabilities;
|
||||
/**
|
||||
* Gate the fetch (default true). PanelV2 sets false for unregistered kinds to skip a wasted
|
||||
* call. The hook also auto-disables internally when the panel has no runnable queries.
|
||||
@@ -85,21 +86,20 @@ export interface UsePanelQueryResult {
|
||||
export function usePanelQuery({
|
||||
panel,
|
||||
panelId,
|
||||
queryCapabilities,
|
||||
enabled = true,
|
||||
time,
|
||||
}: UsePanelQueryArgs): UsePanelQueryResult {
|
||||
const fullKind = panel.spec.plugin.kind;
|
||||
const panelType =
|
||||
(fullKind && PANEL_KIND_TO_PANEL_TYPE[fullKind]) ?? PANEL_TYPES.TIME_SERIES;
|
||||
const queries = panel.spec.queries;
|
||||
|
||||
// V1 parity: a list query with an explicit `limit` shows without a server pager; without
|
||||
// one it pages server-side at a user-selectable size.
|
||||
// V1 parity: a query with an explicit `limit` shows without a server pager; without
|
||||
// one a paging kind fetches server-side at a user-selectable size.
|
||||
const hasExplicitLimit = useMemo(
|
||||
() => !!getBuilderQueries(queries)[0]?.limit,
|
||||
[queries],
|
||||
);
|
||||
const isPaginated = panelType === PANEL_TYPES.LIST && !hasExplicitLimit;
|
||||
const isPaginated = queryCapabilities.serverPaginated && !hasExplicitLimit;
|
||||
|
||||
const [pageSize, setPageSize] = useState(DEFAULT_LIST_PAGE_SIZE);
|
||||
const [offset, setOffset] = useState(0);
|
||||
@@ -188,7 +188,7 @@ export function usePanelQuery({
|
||||
() =>
|
||||
buildQueryRangeRequest({
|
||||
queries,
|
||||
panelType,
|
||||
queryCapabilities,
|
||||
startMs,
|
||||
endMs,
|
||||
fillGaps,
|
||||
@@ -197,7 +197,7 @@ export function usePanelQuery({
|
||||
}),
|
||||
[
|
||||
queries,
|
||||
panelType,
|
||||
queryCapabilities,
|
||||
startMs,
|
||||
endMs,
|
||||
fillGaps,
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import type { DashboardtypesQueryDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import {
|
||||
type DashboardtypesQueryDTO,
|
||||
Querybuildertypesv5RequestTypeDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
|
||||
import {
|
||||
buildQueryRangeRequest,
|
||||
extractLegendMap,
|
||||
getBarStepIntervalSeconds,
|
||||
hasRunnableQueries,
|
||||
panelTypeToRequestType,
|
||||
toQueryEnvelopes,
|
||||
} from '../buildQueryRangeRequest';
|
||||
|
||||
@@ -40,20 +41,47 @@ function compositeQuery(
|
||||
const HOUR_MS = 60 * 60 * 1000;
|
||||
const START_MS = 1_700_000_000_000;
|
||||
|
||||
describe('panelTypeToRequestType', () => {
|
||||
// Capability blocks matching what each kind declares, so these tests exercise the
|
||||
// builder's response to the flags rather than the declarations themselves (those are
|
||||
// asserted against the registry in Panels/__tests__/capabilities.test.ts).
|
||||
const TIME_SERIES_CAPS = {
|
||||
requestType: Querybuildertypesv5RequestTypeDTO.time_series,
|
||||
formatTableResultForUI: false,
|
||||
bucketedStepInterval: false,
|
||||
orderTiebreaker: false,
|
||||
serverPaginated: false,
|
||||
listView: false,
|
||||
traceOperator: true,
|
||||
};
|
||||
const BAR_CAPS = { ...TIME_SERIES_CAPS, bucketedStepInterval: true };
|
||||
const TABLE_CAPS = {
|
||||
...TIME_SERIES_CAPS,
|
||||
requestType: Querybuildertypesv5RequestTypeDTO.scalar,
|
||||
formatTableResultForUI: true,
|
||||
};
|
||||
const LIST_CAPS = {
|
||||
...TIME_SERIES_CAPS,
|
||||
requestType: Querybuildertypesv5RequestTypeDTO.raw,
|
||||
orderTiebreaker: true,
|
||||
serverPaginated: true,
|
||||
listView: true,
|
||||
traceOperator: false,
|
||||
};
|
||||
|
||||
describe('requestType', () => {
|
||||
it.each([
|
||||
[PANEL_TYPES.TIME_SERIES, 'time_series'],
|
||||
// HISTOGRAM and BAR bin client-side from time-series data; sending
|
||||
// 'distribution' would return a shape the renderers can't bin.
|
||||
[PANEL_TYPES.BAR, 'time_series'],
|
||||
[PANEL_TYPES.HISTOGRAM, 'time_series'],
|
||||
[PANEL_TYPES.TABLE, 'scalar'],
|
||||
[PANEL_TYPES.PIE, 'scalar'],
|
||||
[PANEL_TYPES.VALUE, 'scalar'],
|
||||
[PANEL_TYPES.LIST, 'raw'],
|
||||
[PANEL_TYPES.TRACE, 'trace'],
|
||||
])('%s → %s', (panelType, requestType) => {
|
||||
expect(panelTypeToRequestType(panelType)).toBe(requestType);
|
||||
Querybuildertypesv5RequestTypeDTO.time_series,
|
||||
Querybuildertypesv5RequestTypeDTO.scalar,
|
||||
Querybuildertypesv5RequestTypeDTO.raw,
|
||||
Querybuildertypesv5RequestTypeDTO.trace,
|
||||
])('passes %s through from the declared capabilities', (requestType) => {
|
||||
const request = buildQueryRangeRequest({
|
||||
queries: bareBuilderQuery({ name: 'A', signal: 'metrics' }),
|
||||
queryCapabilities: { ...TIME_SERIES_CAPS, requestType },
|
||||
startMs: START_MS,
|
||||
endMs: START_MS + HOUR_MS,
|
||||
});
|
||||
expect(request.requestType).toBe(requestType);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -135,7 +163,7 @@ describe('buildQueryRangeRequest', () => {
|
||||
it('assembles the full request DTO', () => {
|
||||
const request = buildQueryRangeRequest({
|
||||
queries: bareBuilderQuery({ name: 'A', signal: 'metrics' }),
|
||||
panelType: PANEL_TYPES.TIME_SERIES,
|
||||
queryCapabilities: TIME_SERIES_CAPS,
|
||||
startMs: START_MS,
|
||||
endMs: START_MS + HOUR_MS,
|
||||
});
|
||||
@@ -157,7 +185,7 @@ describe('buildQueryRangeRequest', () => {
|
||||
it('sets formatTableResultForUI only for TABLE panels', () => {
|
||||
const request = buildQueryRangeRequest({
|
||||
queries: bareBuilderQuery({ name: 'A' }),
|
||||
panelType: PANEL_TYPES.TABLE,
|
||||
queryCapabilities: TABLE_CAPS,
|
||||
startMs: START_MS,
|
||||
endMs: START_MS + HOUR_MS,
|
||||
});
|
||||
@@ -167,7 +195,7 @@ describe('buildQueryRangeRequest', () => {
|
||||
it('passes through fillGaps into formatOptions', () => {
|
||||
const request = buildQueryRangeRequest({
|
||||
queries: bareBuilderQuery({ name: 'A' }),
|
||||
panelType: PANEL_TYPES.TIME_SERIES,
|
||||
queryCapabilities: TIME_SERIES_CAPS,
|
||||
startMs: START_MS,
|
||||
endMs: START_MS + HOUR_MS,
|
||||
fillGaps: true,
|
||||
@@ -178,7 +206,7 @@ describe('buildQueryRangeRequest', () => {
|
||||
it('stamps offset/limit onto builder queries when pagination is given', () => {
|
||||
const request = buildQueryRangeRequest({
|
||||
queries: bareBuilderQuery({ name: 'A', signal: 'logs' }),
|
||||
panelType: PANEL_TYPES.LIST,
|
||||
queryCapabilities: LIST_CAPS,
|
||||
startMs: START_MS,
|
||||
endMs: START_MS + HOUR_MS,
|
||||
pagination: { offset: 100, limit: 50 },
|
||||
@@ -198,7 +226,7 @@ describe('buildQueryRangeRequest', () => {
|
||||
it('defaults a logs list with no order to timestamp desc + id tiebreaker', () => {
|
||||
const request = buildQueryRangeRequest({
|
||||
queries: bareBuilderQuery({ name: 'A', signal: 'logs' }),
|
||||
panelType: PANEL_TYPES.LIST,
|
||||
queryCapabilities: LIST_CAPS,
|
||||
startMs: START_MS,
|
||||
endMs: START_MS + HOUR_MS,
|
||||
});
|
||||
@@ -218,7 +246,7 @@ describe('buildQueryRangeRequest', () => {
|
||||
signal: 'logs',
|
||||
order: [{ key: { name: 'timestamp' }, direction: 'desc' }],
|
||||
}),
|
||||
panelType: PANEL_TYPES.LIST,
|
||||
queryCapabilities: LIST_CAPS,
|
||||
startMs: START_MS,
|
||||
endMs: START_MS + HOUR_MS,
|
||||
});
|
||||
@@ -238,7 +266,7 @@ describe('buildQueryRangeRequest', () => {
|
||||
];
|
||||
const request = buildQueryRangeRequest({
|
||||
queries: bareBuilderQuery({ name: 'A', signal: 'logs', order }),
|
||||
panelType: PANEL_TYPES.LIST,
|
||||
queryCapabilities: LIST_CAPS,
|
||||
startMs: START_MS,
|
||||
endMs: START_MS + HOUR_MS,
|
||||
});
|
||||
@@ -252,7 +280,7 @@ describe('buildQueryRangeRequest', () => {
|
||||
const order = [{ key: { name: 'timestamp' }, direction: 'desc' }];
|
||||
const request = buildQueryRangeRequest({
|
||||
queries: bareBuilderQuery({ name: 'A', signal: 'traces', order }),
|
||||
panelType: PANEL_TYPES.LIST,
|
||||
queryCapabilities: LIST_CAPS,
|
||||
startMs: START_MS,
|
||||
endMs: START_MS + HOUR_MS,
|
||||
});
|
||||
@@ -265,7 +293,7 @@ describe('buildQueryRangeRequest', () => {
|
||||
it('injects the range-derived stepInterval into BAR builder queries without one', () => {
|
||||
const request = buildQueryRangeRequest({
|
||||
queries: bareBuilderQuery({ name: 'A', signal: 'metrics' }),
|
||||
panelType: PANEL_TYPES.BAR,
|
||||
queryCapabilities: BAR_CAPS,
|
||||
startMs: START_MS,
|
||||
endMs: START_MS + HOUR_MS,
|
||||
});
|
||||
@@ -280,7 +308,7 @@ describe('buildQueryRangeRequest', () => {
|
||||
it('preserves a user-set stepInterval on BAR builder queries', () => {
|
||||
const request = buildQueryRangeRequest({
|
||||
queries: bareBuilderQuery({ name: 'A', stepInterval: 300 }),
|
||||
panelType: PANEL_TYPES.BAR,
|
||||
queryCapabilities: BAR_CAPS,
|
||||
startMs: START_MS,
|
||||
endMs: START_MS + HOUR_MS,
|
||||
});
|
||||
@@ -293,7 +321,7 @@ describe('buildQueryRangeRequest', () => {
|
||||
it('does not touch stepInterval for non-BAR panels', () => {
|
||||
const request = buildQueryRangeRequest({
|
||||
queries: bareBuilderQuery({ name: 'A' }),
|
||||
panelType: PANEL_TYPES.TIME_SERIES,
|
||||
queryCapabilities: TIME_SERIES_CAPS,
|
||||
startMs: START_MS,
|
||||
endMs: START_MS + HOUR_MS,
|
||||
});
|
||||
|
||||
@@ -7,7 +7,12 @@ import type { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import { envelopesToQuery, fromPerses, toPerses } from '../persesQueryAdapters';
|
||||
import {
|
||||
envelopesToQuery,
|
||||
fromPerses,
|
||||
panelTypeToRequestType,
|
||||
toPerses,
|
||||
} from '../persesQueryAdapters';
|
||||
|
||||
/** A bare perses query (single plugin, not wrapped in a CompositeQuery). */
|
||||
function bareQuery(
|
||||
@@ -21,6 +26,23 @@ function bareQuery(
|
||||
}
|
||||
|
||||
describe('persesQueryAdapters', () => {
|
||||
describe('panelTypeToRequestType', () => {
|
||||
it.each([
|
||||
[PANEL_TYPES.TIME_SERIES, 'time_series'],
|
||||
// HISTOGRAM and BAR bin client-side from time-series data; sending
|
||||
// 'distribution' would return a shape the renderers can't bin.
|
||||
[PANEL_TYPES.BAR, 'time_series'],
|
||||
[PANEL_TYPES.HISTOGRAM, 'time_series'],
|
||||
[PANEL_TYPES.TABLE, 'scalar'],
|
||||
[PANEL_TYPES.PIE, 'scalar'],
|
||||
[PANEL_TYPES.VALUE, 'scalar'],
|
||||
[PANEL_TYPES.LIST, 'raw'],
|
||||
[PANEL_TYPES.TRACE, 'trace'],
|
||||
])('%s → %s', (panelType, requestType) => {
|
||||
expect(panelTypeToRequestType(panelType)).toBe(requestType);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fromPerses', () => {
|
||||
it('returns a fresh metrics builder query for an empty panel', () => {
|
||||
const query = fromPerses([], PANEL_TYPES.TIME_SERIES);
|
||||
|
||||
@@ -14,9 +14,9 @@ import {
|
||||
Querybuildertypesv5QueryEnvelopeBuilderDTOType,
|
||||
Querybuildertypesv5QueryEnvelopeClickHouseSQLDTOType,
|
||||
Querybuildertypesv5QueryEnvelopePromQLDTOType,
|
||||
Querybuildertypesv5RequestTypeDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
|
||||
import type { PanelQueryCapabilities } from '../Panels/types/panelCapabilities';
|
||||
|
||||
// Narrow view over the envelope spec variants. Orval erases envelope `spec` to `unknown`, so
|
||||
// shared fields are read through this view with a localized cast at the envelope boundary.
|
||||
@@ -29,31 +29,6 @@ interface QuerySpecView {
|
||||
order?: Querybuildertypesv5OrderByDTO[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a V2 panel type to the V5 `requestType`. HISTOGRAM/BAR bin client-side from raw
|
||||
* time-series, so their request type is `time_series` (V1 parity).
|
||||
*/
|
||||
export function panelTypeToRequestType(
|
||||
panelType: PANEL_TYPES,
|
||||
): Querybuildertypesv5RequestTypeDTO {
|
||||
switch (panelType) {
|
||||
case PANEL_TYPES.TIME_SERIES:
|
||||
case PANEL_TYPES.BAR:
|
||||
case PANEL_TYPES.HISTOGRAM:
|
||||
return Querybuildertypesv5RequestTypeDTO.time_series;
|
||||
case PANEL_TYPES.TABLE:
|
||||
case PANEL_TYPES.PIE:
|
||||
case PANEL_TYPES.VALUE:
|
||||
return Querybuildertypesv5RequestTypeDTO.scalar;
|
||||
case PANEL_TYPES.LIST:
|
||||
return Querybuildertypesv5RequestTypeDTO.raw;
|
||||
case PANEL_TYPES.TRACE:
|
||||
return Querybuildertypesv5RequestTypeDTO.trace;
|
||||
default:
|
||||
return Querybuildertypesv5RequestTypeDTO.time_series;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwraps the perses query into the V5 `compositeQuery.queries` list: a CompositeQuery passes
|
||||
* through verbatim, bare plugins wrap into one envelope. Top-level Formula/TraceOperator are
|
||||
@@ -239,7 +214,13 @@ function withPagination(
|
||||
|
||||
export interface BuildQueryRangeRequestArgs {
|
||||
queries: DashboardtypesQueryDTO[];
|
||||
panelType: PANEL_TYPES;
|
||||
/**
|
||||
* The panel kind's declared query capabilities (`PanelDefinition.query`): request type,
|
||||
* result formatting, and the step-interval/order treatment. Passed in rather than looked up
|
||||
* by kind so this stays a leaf of the query layer — the panel registry carries every
|
||||
* renderer with it, which has no business in the data path.
|
||||
*/
|
||||
queryCapabilities: PanelQueryCapabilities;
|
||||
/** Epoch milliseconds. */
|
||||
startMs: number;
|
||||
/** Epoch milliseconds. */
|
||||
@@ -258,7 +239,12 @@ export interface BuildQueryRangeRequestArgs {
|
||||
*/
|
||||
export function buildQueryRangeRequest({
|
||||
queries,
|
||||
panelType,
|
||||
queryCapabilities: {
|
||||
requestType,
|
||||
formatTableResultForUI,
|
||||
bucketedStepInterval,
|
||||
orderTiebreaker,
|
||||
},
|
||||
startMs,
|
||||
endMs,
|
||||
fillGaps = false,
|
||||
@@ -266,10 +252,10 @@ export function buildQueryRangeRequest({
|
||||
variables = {},
|
||||
}: BuildQueryRangeRequestArgs): Querybuildertypesv5QueryRangeRequestDTO {
|
||||
let envelopes = toQueryEnvelopes(queries);
|
||||
if (panelType === PANEL_TYPES.BAR) {
|
||||
if (bucketedStepInterval) {
|
||||
envelopes = withBarStepInterval(envelopes, startMs, endMs);
|
||||
}
|
||||
if (panelType === PANEL_TYPES.LIST) {
|
||||
if (orderTiebreaker) {
|
||||
envelopes = withListOrderTiebreaker(envelopes);
|
||||
}
|
||||
if (pagination) {
|
||||
@@ -280,10 +266,10 @@ export function buildQueryRangeRequest({
|
||||
schemaVersion: 'v1',
|
||||
start: startMs,
|
||||
end: endMs,
|
||||
requestType: panelTypeToRequestType(panelType),
|
||||
requestType,
|
||||
compositeQuery: { queries: envelopes },
|
||||
formatOptions: {
|
||||
formatTableResultForUI: panelType === PANEL_TYPES.TABLE,
|
||||
formatTableResultForUI,
|
||||
fillGaps,
|
||||
},
|
||||
variables,
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
Querybuildertypesv5QueryEnvelopeBuilderDTOType,
|
||||
Querybuildertypesv5QueryEnvelopeClickHouseSQLDTOType,
|
||||
Querybuildertypesv5QueryEnvelopePromQLDTOType,
|
||||
Querybuildertypesv5RequestTypeDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { mapCompositeQueryFromQuery } from 'lib/newQueryBuilder/queryBuilderMappers/mapCompositeQueryFromQuery';
|
||||
@@ -20,10 +21,7 @@ import type { QueryEnvelope } from 'types/api/v5/queryRange';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import {
|
||||
panelTypeToRequestType,
|
||||
toQueryEnvelopes,
|
||||
} from './buildQueryRangeRequest';
|
||||
import { toQueryEnvelopes } from './buildQueryRangeRequest';
|
||||
|
||||
/**
|
||||
* Adapters between the V2 perses query shape and the V1 `Query` the shared query
|
||||
@@ -90,6 +88,33 @@ export function deriveQueryType(
|
||||
return EQueryType.QUERY_BUILDER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a legacy panel type to the V5 `requestType`. Lives on this side of the V1 boundary
|
||||
* because only the V1 pivot still speaks `PANEL_TYPES` — V2 panels read `requestType` off
|
||||
* their kind's declared query capabilities instead. BAR/HISTOGRAM bin client-side from a raw
|
||||
* time series, so they request `time_series` (V1 parity).
|
||||
*/
|
||||
export function panelTypeToRequestType(
|
||||
panelType: PANEL_TYPES,
|
||||
): Querybuildertypesv5RequestTypeDTO {
|
||||
switch (panelType) {
|
||||
case PANEL_TYPES.TIME_SERIES:
|
||||
case PANEL_TYPES.BAR:
|
||||
case PANEL_TYPES.HISTOGRAM:
|
||||
return Querybuildertypesv5RequestTypeDTO.time_series;
|
||||
case PANEL_TYPES.TABLE:
|
||||
case PANEL_TYPES.PIE:
|
||||
case PANEL_TYPES.VALUE:
|
||||
return Querybuildertypesv5RequestTypeDTO.scalar;
|
||||
case PANEL_TYPES.LIST:
|
||||
return Querybuildertypesv5RequestTypeDTO.raw;
|
||||
case PANEL_TYPES.TRACE:
|
||||
return Querybuildertypesv5RequestTypeDTO.trace;
|
||||
default:
|
||||
return Querybuildertypesv5RequestTypeDTO.time_series;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* V5 query-envelope list → V1 `Query`, via `mapQueryDataFromApi`. An empty list opens
|
||||
* on a fresh metrics builder query. Used by `fromPerses` and by the envelopes a
|
||||
|
||||
@@ -62,7 +62,10 @@ export function buildNewPanelSeed(
|
||||
if (!isExplorerExport || !compositeQuery) {
|
||||
return {
|
||||
kind: requestedKind,
|
||||
queries: buildDefaultQueries(requestedKind),
|
||||
queries: buildDefaultQueries(
|
||||
requestedKind,
|
||||
getPanelDefinition(requestedKind).query,
|
||||
),
|
||||
pluginSpec: buildPluginSpec(getPanelDefinition(requestedKind).sections),
|
||||
};
|
||||
}
|
||||
@@ -71,7 +74,10 @@ export function buildNewPanelSeed(
|
||||
const pluginSpec = buildPluginSpec(getPanelDefinition(kind).sections);
|
||||
|
||||
const converted = toPerses(compositeQuery, PANEL_KIND_TO_PANEL_TYPE[kind]);
|
||||
const queries = converted.length > 0 ? converted : buildDefaultQueries(kind);
|
||||
const queries =
|
||||
converted.length > 0
|
||||
? converted
|
||||
: buildDefaultQueries(kind, getPanelDefinition(kind).query);
|
||||
|
||||
// Explorers put the single `unit` on the query itself, not the panel spec.
|
||||
if (compositeQuery.unit && kindSupportsUnit(kind)) {
|
||||
|
||||
@@ -40,6 +40,7 @@ function PublicPanel({
|
||||
const { data, isFetching, isPreviousData, error, refetch } =
|
||||
usePublicPanelQuery({
|
||||
panel,
|
||||
queryCapabilities: panelDefinition.query,
|
||||
panelKey,
|
||||
publicDashboardId,
|
||||
startMs,
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { renderHook, waitFor } from '@testing-library/react';
|
||||
import { getPublicDashboardPanelQueryRangeV2 } from 'api/generated/services/dashboard';
|
||||
import type { DashboardtypesPanelDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import {
|
||||
type DashboardtypesPanelDTO,
|
||||
Querybuildertypesv5RequestTypeDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { ReactNode } from 'react';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
|
||||
@@ -42,6 +45,17 @@ const panel = {
|
||||
|
||||
const args = {
|
||||
panel,
|
||||
// What TimeSeries declares; passed in rather than resolved from the registry, which
|
||||
// would pull every panel renderer into this suite.
|
||||
queryCapabilities: {
|
||||
requestType: Querybuildertypesv5RequestTypeDTO.time_series,
|
||||
formatTableResultForUI: false,
|
||||
bucketedStepInterval: false,
|
||||
orderTiebreaker: false,
|
||||
serverPaginated: false,
|
||||
listView: false,
|
||||
traceOperator: true,
|
||||
},
|
||||
panelKey: 'panel-1',
|
||||
publicDashboardId: 'pub-1',
|
||||
startMs: 1000,
|
||||
|
||||
@@ -3,10 +3,9 @@ import type {
|
||||
DashboardtypesPanelDTO,
|
||||
GetPublicDashboardPanelQueryRangeV2200,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import { retryUnlessClientError } from 'pages/DashboardPageV2/DashboardContainer/hooks/useGetQueryRangeV5';
|
||||
import { PANEL_KIND_TO_PANEL_TYPE } from 'pages/DashboardPageV2/DashboardContainer/Panels/types/panelKind';
|
||||
import type { PanelQueryCapabilities } from 'pages/DashboardPageV2/DashboardContainer/Panels/types/panelCapabilities';
|
||||
import {
|
||||
buildQueryRangeRequest,
|
||||
extractLegendMap,
|
||||
@@ -21,6 +20,8 @@ import { useQuery, useQueryClient } from 'react-query';
|
||||
|
||||
export interface UsePublicPanelQueryArgs {
|
||||
panel: DashboardtypesPanelDTO;
|
||||
/** The panel kind's declared query capabilities — `panelDefinition.query`. */
|
||||
queryCapabilities: PanelQueryCapabilities;
|
||||
/** Panel key in `spec.panels` — addresses the panel on the public endpoint. */
|
||||
panelKey: string;
|
||||
publicDashboardId: string;
|
||||
@@ -52,15 +53,13 @@ export interface UsePublicPanelQueryResult {
|
||||
*/
|
||||
export function usePublicPanelQuery({
|
||||
panel,
|
||||
queryCapabilities,
|
||||
panelKey,
|
||||
publicDashboardId,
|
||||
startMs,
|
||||
endMs,
|
||||
enabled = true,
|
||||
}: UsePublicPanelQueryArgs): UsePublicPanelQueryResult {
|
||||
const fullKind = panel.spec.plugin.kind;
|
||||
const panelType =
|
||||
(fullKind && PANEL_KIND_TO_PANEL_TYPE[fullKind]) ?? PANEL_TYPES.TIME_SERIES;
|
||||
const { queries } = panel.spec;
|
||||
|
||||
const pluginSpec = panel.spec.plugin.spec;
|
||||
@@ -77,13 +76,13 @@ export function usePublicPanelQuery({
|
||||
() =>
|
||||
buildQueryRangeRequest({
|
||||
queries,
|
||||
panelType,
|
||||
queryCapabilities,
|
||||
startMs,
|
||||
endMs,
|
||||
fillGaps,
|
||||
variables: {},
|
||||
}),
|
||||
[queries, panelType, startMs, endMs, fillGaps],
|
||||
[queries, queryCapabilities, startMs, endMs, fillGaps],
|
||||
);
|
||||
|
||||
const legendMap = useMemo(() => extractLegendMap(queries), [queries]);
|
||||
|
||||
Reference in New Issue
Block a user