mirror of
https://github.com/SigNoz/signoz.git
synced 2026-09-07 12:00:42 +01:00
Compare commits
1 Commits
fixes/dash
...
refactor/p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53100381a5 |
@@ -0,0 +1,119 @@
|
||||
import {
|
||||
panelTypeDataSourceFormValuesMap,
|
||||
type PartialPanelTypes,
|
||||
} from 'lib/query/panelQuery';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
/**
|
||||
* The map is composed from a few shape rules rather than spelled out per panel type
|
||||
* and data source. These specs pin the rules themselves — each one fails only when a
|
||||
* rule changes, which is the moment to stop and decide, rather than whenever any
|
||||
* field moves.
|
||||
*
|
||||
* The composition it replaced was checked cell by cell against the previous literal
|
||||
* table, which is in git history at `main:frontend/src/lib/query/panelQuery.ts`.
|
||||
*/
|
||||
function fieldsFor(
|
||||
panelType: keyof PartialPanelTypes,
|
||||
dataSource: DataSource,
|
||||
): string[] {
|
||||
return panelTypeDataSourceFormValuesMap[panelType][dataSource].builder
|
||||
.queryData;
|
||||
}
|
||||
|
||||
/** Fields present in `to` but not in `from`. */
|
||||
function added(from: string[], to: string[]): string[] {
|
||||
return to.filter((field) => !from.includes(field)).sort();
|
||||
}
|
||||
|
||||
/** Panel types built on the aggregating field list. */
|
||||
const AGGREGATING_TYPES: (keyof PartialPanelTypes)[] = [
|
||||
PANEL_TYPES.BAR,
|
||||
PANEL_TYPES.HISTOGRAM,
|
||||
PANEL_TYPES.TABLE,
|
||||
PANEL_TYPES.PIE,
|
||||
];
|
||||
|
||||
/** Panel types that reduce each series to one cell or slice. */
|
||||
const SCALAR_TYPES: (keyof PartialPanelTypes)[] = [
|
||||
PANEL_TYPES.TABLE,
|
||||
PANEL_TYPES.PIE,
|
||||
];
|
||||
|
||||
describe('panelTypeDataSourceFormValuesMap', () => {
|
||||
const seriesLogs = fieldsFor(PANEL_TYPES.TIME_SERIES, DataSource.LOGS);
|
||||
const seriesMetrics = fieldsFor(PANEL_TYPES.TIME_SERIES, DataSource.METRICS);
|
||||
|
||||
it('shares one builder surface between logs and traces', () => {
|
||||
Object.values(panelTypeDataSourceFormValuesMap).forEach((sources) => {
|
||||
expect(sources[DataSource.LOGS].builder.queryData).toStrictEqual(
|
||||
sources[DataSource.TRACES].builder.queryData,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// The provider pushes onto the list it reads from this map, so two cells backed by
|
||||
// one instance would leak fields into each other.
|
||||
it('gives every cell its own array instance', () => {
|
||||
const arrays = Object.values(panelTypeDataSourceFormValuesMap).flatMap(
|
||||
(sources) =>
|
||||
Object.values(sources).map((source) => source.builder.queryData),
|
||||
);
|
||||
|
||||
expect(new Set(arrays).size).toBe(arrays.length);
|
||||
});
|
||||
|
||||
// One consequence of composing: the aggregating types share a single field list, so
|
||||
// an edit meant for charts reaches table and pie too.
|
||||
it.each(AGGREGATING_TYPES)(
|
||||
'gives %s the same non-metrics fields as a time series',
|
||||
(panelType) => {
|
||||
expect(fieldsFor(panelType, DataSource.LOGS)).toStrictEqual(seriesLogs);
|
||||
},
|
||||
);
|
||||
|
||||
it('adds both metrics aggregation steps for metrics', () => {
|
||||
expect(added(seriesLogs, seriesMetrics)).toStrictEqual([
|
||||
'spaceAggregation',
|
||||
'timeAggregation',
|
||||
]);
|
||||
});
|
||||
|
||||
it.each(SCALAR_TYPES)('offers reduceTo to %s on metrics only', (panelType) => {
|
||||
expect(
|
||||
added(seriesMetrics, fieldsFor(panelType, DataSource.METRICS)),
|
||||
).toStrictEqual(['reduceTo']);
|
||||
expect(fieldsFor(panelType, DataSource.LOGS)).not.toContain('reduceTo');
|
||||
});
|
||||
|
||||
it('drops grouping, paging and ordering for a single value', () => {
|
||||
const value = fieldsFor(PANEL_TYPES.VALUE, DataSource.LOGS);
|
||||
|
||||
expect(added(value, seriesLogs)).toStrictEqual([
|
||||
'groupBy',
|
||||
'limit',
|
||||
'orderBy',
|
||||
]);
|
||||
expect(value).toContain('reduceTo');
|
||||
});
|
||||
|
||||
it('offers no aggregation fields to raw rows', () => {
|
||||
const rows = fieldsFor(PANEL_TYPES.LIST, DataSource.LOGS);
|
||||
|
||||
expect(rows).not.toContain('aggregateAttribute');
|
||||
expect(rows).not.toContain('aggregateOperator');
|
||||
expect(rows).not.toContain('groupBy');
|
||||
expect(rows).not.toContain('having');
|
||||
expect(rows).not.toContain('stepInterval');
|
||||
});
|
||||
|
||||
it('drops paging and ordering for metrics rows', () => {
|
||||
expect(
|
||||
added(
|
||||
fieldsFor(PANEL_TYPES.LIST, DataSource.METRICS),
|
||||
fieldsFor(PANEL_TYPES.LIST, DataSource.LOGS),
|
||||
),
|
||||
).toStrictEqual(['functions', 'limit', 'orderBy']);
|
||||
});
|
||||
});
|
||||
@@ -101,441 +101,99 @@ export type PartialPanelTypes = {
|
||||
[PANEL_TYPES.HISTOGRAM]: 'histogram';
|
||||
};
|
||||
|
||||
/**
|
||||
* Builder fields carried across a panel-type switch, per panel type and data source.
|
||||
*
|
||||
* The 21 combinations reduce to a handful of rules, so they are composed rather than
|
||||
* spelled out: logs and traces carry the same fields in every case, metrics splits its
|
||||
* aggregation in two, and each panel type is one of four query shapes. Order is
|
||||
* irrelevant — `handleQueryChange` copies each field independently.
|
||||
*
|
||||
* `panelTypeFormValues` in `__tests__/__fixtures__` pins the previous literal table so
|
||||
* the composition can be shown to reproduce it exactly.
|
||||
*/
|
||||
|
||||
/** Every field an aggregating query carries — shared by charts, table and pie. */
|
||||
const AGGREGATING_FIELDS = [
|
||||
'aggregateAttribute',
|
||||
'aggregateOperator',
|
||||
'filters',
|
||||
'filter',
|
||||
'groupBy',
|
||||
'limit',
|
||||
'having',
|
||||
'orderBy',
|
||||
'functions',
|
||||
'stepInterval',
|
||||
'disabled',
|
||||
'queryName',
|
||||
'legend',
|
||||
'expression',
|
||||
'aggregations',
|
||||
] as const;
|
||||
|
||||
/** Metrics aggregates over time and then over space, so it carries both steps. */
|
||||
const METRICS_AGGREGATION = ['timeAggregation', 'spaceAggregation'] as const;
|
||||
|
||||
function omit(fields: readonly string[], ...omitted: string[]): string[] {
|
||||
return fields.filter((field) => !omitted.includes(field));
|
||||
}
|
||||
|
||||
const SERIES = [...AGGREGATING_FIELDS];
|
||||
const SERIES_METRICS = [...SERIES, ...METRICS_AGGREGATION];
|
||||
|
||||
// Table and pie reduce each series to a single cell/slice. Note the asymmetry, carried
|
||||
// over from the previous table: `reduceTo` is offered for metrics only.
|
||||
const SCALAR_METRICS = [...SERIES_METRICS, 'reduceTo'];
|
||||
|
||||
/** A single value has no series to group, limit or order. */
|
||||
const SINGLE_VALUE = [
|
||||
...omit(AGGREGATING_FIELDS, 'groupBy', 'limit', 'orderBy'),
|
||||
'reduceTo',
|
||||
];
|
||||
const SINGLE_VALUE_METRICS = [...SINGLE_VALUE, ...METRICS_AGGREGATION];
|
||||
|
||||
/** Raw rows carry no aggregation at all. */
|
||||
const RAW_ROWS = [
|
||||
'queryName',
|
||||
'filters',
|
||||
'filter',
|
||||
'limit',
|
||||
'orderBy',
|
||||
'functions',
|
||||
'aggregations',
|
||||
];
|
||||
// Metrics rows drop paging and ordering too, as before.
|
||||
const RAW_ROWS_METRICS = ['queryName', 'filters', 'filter', 'aggregations'];
|
||||
|
||||
/**
|
||||
* Logs and traces share a builder surface; metrics is the one that differs.
|
||||
*
|
||||
* Each cell gets its own copy. `QueryBuilder`'s provider pushes onto the list it reads
|
||||
* from this map, so cells sharing one array instance would contaminate each other.
|
||||
*/
|
||||
function bySource(
|
||||
logsAndTraces: readonly string[],
|
||||
metrics: readonly string[],
|
||||
): Record<DataSource, any> {
|
||||
return {
|
||||
[DataSource.LOGS]: { builder: { queryData: [...logsAndTraces] } },
|
||||
[DataSource.TRACES]: { builder: { queryData: [...logsAndTraces] } },
|
||||
[DataSource.METRICS]: { builder: { queryData: [...metrics] } },
|
||||
};
|
||||
}
|
||||
|
||||
export const panelTypeDataSourceFormValuesMap: Record<
|
||||
keyof PartialPanelTypes,
|
||||
Record<DataSource, any>
|
||||
> = {
|
||||
[PANEL_TYPES.BAR]: {
|
||||
[DataSource.LOGS]: {
|
||||
builder: {
|
||||
queryData: [
|
||||
'aggregateAttribute',
|
||||
'aggregateOperator',
|
||||
'filters',
|
||||
'filter',
|
||||
'groupBy',
|
||||
'limit',
|
||||
'having',
|
||||
'orderBy',
|
||||
'functions',
|
||||
'stepInterval',
|
||||
'disabled',
|
||||
'queryName',
|
||||
'legend',
|
||||
'expression',
|
||||
'aggregations',
|
||||
],
|
||||
},
|
||||
},
|
||||
[DataSource.METRICS]: {
|
||||
builder: {
|
||||
queryData: [
|
||||
'aggregateAttribute',
|
||||
'aggregateOperator',
|
||||
'timeAggregation',
|
||||
'filters',
|
||||
'filter',
|
||||
'spaceAggregation',
|
||||
'groupBy',
|
||||
'limit',
|
||||
'having',
|
||||
'orderBy',
|
||||
'stepInterval',
|
||||
'legend',
|
||||
'queryName',
|
||||
'disabled',
|
||||
'functions',
|
||||
'expression',
|
||||
'aggregations',
|
||||
],
|
||||
},
|
||||
},
|
||||
[DataSource.TRACES]: {
|
||||
builder: {
|
||||
queryData: [
|
||||
'aggregateAttribute',
|
||||
'aggregateOperator',
|
||||
'filters',
|
||||
'filter',
|
||||
'groupBy',
|
||||
'limit',
|
||||
'having',
|
||||
'orderBy',
|
||||
'functions',
|
||||
'stepInterval',
|
||||
'disabled',
|
||||
'queryName',
|
||||
'legend',
|
||||
'expression',
|
||||
'aggregations',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
[PANEL_TYPES.TIME_SERIES]: {
|
||||
[DataSource.LOGS]: {
|
||||
builder: {
|
||||
queryData: [
|
||||
'aggregateAttribute',
|
||||
'aggregateOperator',
|
||||
'filters',
|
||||
'filter',
|
||||
'groupBy',
|
||||
'limit',
|
||||
'having',
|
||||
'orderBy',
|
||||
'functions',
|
||||
'stepInterval',
|
||||
'disabled',
|
||||
'queryName',
|
||||
'legend',
|
||||
'expression',
|
||||
'aggregations',
|
||||
],
|
||||
},
|
||||
},
|
||||
[DataSource.METRICS]: {
|
||||
builder: {
|
||||
queryData: [
|
||||
'aggregateAttribute',
|
||||
'aggregateOperator',
|
||||
'timeAggregation',
|
||||
'filters',
|
||||
'filter',
|
||||
'spaceAggregation',
|
||||
'groupBy',
|
||||
'limit',
|
||||
'having',
|
||||
'orderBy',
|
||||
'stepInterval',
|
||||
'legend',
|
||||
'queryName',
|
||||
'disabled',
|
||||
'functions',
|
||||
'expression',
|
||||
'aggregations',
|
||||
],
|
||||
},
|
||||
},
|
||||
[DataSource.TRACES]: {
|
||||
builder: {
|
||||
queryData: [
|
||||
'aggregateAttribute',
|
||||
'aggregateOperator',
|
||||
'filters',
|
||||
'filter',
|
||||
'groupBy',
|
||||
'limit',
|
||||
'having',
|
||||
'orderBy',
|
||||
'functions',
|
||||
'stepInterval',
|
||||
'disabled',
|
||||
'queryName',
|
||||
'legend',
|
||||
'expression',
|
||||
'aggregations',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
[PANEL_TYPES.HISTOGRAM]: {
|
||||
[DataSource.LOGS]: {
|
||||
builder: {
|
||||
queryData: [
|
||||
'aggregateAttribute',
|
||||
'aggregateOperator',
|
||||
'filters',
|
||||
'filter',
|
||||
'groupBy',
|
||||
'limit',
|
||||
'having',
|
||||
'orderBy',
|
||||
'functions',
|
||||
'stepInterval',
|
||||
'disabled',
|
||||
'queryName',
|
||||
'legend',
|
||||
'expression',
|
||||
'aggregations',
|
||||
],
|
||||
},
|
||||
},
|
||||
[DataSource.METRICS]: {
|
||||
builder: {
|
||||
queryData: [
|
||||
'aggregateAttribute',
|
||||
'aggregateOperator',
|
||||
'timeAggregation',
|
||||
'filters',
|
||||
'filter',
|
||||
'spaceAggregation',
|
||||
'groupBy',
|
||||
'limit',
|
||||
'having',
|
||||
'orderBy',
|
||||
'stepInterval',
|
||||
'legend',
|
||||
'queryName',
|
||||
'disabled',
|
||||
'functions',
|
||||
'expression',
|
||||
'aggregations',
|
||||
],
|
||||
},
|
||||
},
|
||||
[DataSource.TRACES]: {
|
||||
builder: {
|
||||
queryData: [
|
||||
'aggregateAttribute',
|
||||
'aggregateOperator',
|
||||
'filters',
|
||||
'filter',
|
||||
'groupBy',
|
||||
'limit',
|
||||
'having',
|
||||
'orderBy',
|
||||
'functions',
|
||||
'stepInterval',
|
||||
'disabled',
|
||||
'queryName',
|
||||
'legend',
|
||||
'expression',
|
||||
'aggregations',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
[PANEL_TYPES.TABLE]: {
|
||||
[DataSource.LOGS]: {
|
||||
builder: {
|
||||
queryData: [
|
||||
'aggregateAttribute',
|
||||
'aggregateOperator',
|
||||
'filters',
|
||||
'filter',
|
||||
'groupBy',
|
||||
'limit',
|
||||
'having',
|
||||
'orderBy',
|
||||
'functions',
|
||||
'stepInterval',
|
||||
'disabled',
|
||||
'queryName',
|
||||
'expression',
|
||||
'legend',
|
||||
'aggregations',
|
||||
],
|
||||
},
|
||||
},
|
||||
[DataSource.METRICS]: {
|
||||
builder: {
|
||||
queryData: [
|
||||
'aggregateAttribute',
|
||||
'aggregateOperator',
|
||||
'timeAggregation',
|
||||
'filters',
|
||||
'filter',
|
||||
'spaceAggregation',
|
||||
'groupBy',
|
||||
'reduceTo',
|
||||
'limit',
|
||||
'having',
|
||||
'orderBy',
|
||||
'stepInterval',
|
||||
'legend',
|
||||
'queryName',
|
||||
'expression',
|
||||
'disabled',
|
||||
'functions',
|
||||
'aggregations',
|
||||
],
|
||||
},
|
||||
},
|
||||
[DataSource.TRACES]: {
|
||||
builder: {
|
||||
queryData: [
|
||||
'aggregateAttribute',
|
||||
'aggregateOperator',
|
||||
'filters',
|
||||
'filter',
|
||||
'groupBy',
|
||||
'limit',
|
||||
'having',
|
||||
'orderBy',
|
||||
'functions',
|
||||
'stepInterval',
|
||||
'disabled',
|
||||
'queryName',
|
||||
'expression',
|
||||
'legend',
|
||||
'aggregations',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
[PANEL_TYPES.PIE]: {
|
||||
[DataSource.LOGS]: {
|
||||
builder: {
|
||||
queryData: [
|
||||
'aggregateAttribute',
|
||||
'aggregateOperator',
|
||||
'filters',
|
||||
'filter',
|
||||
'groupBy',
|
||||
'limit',
|
||||
'having',
|
||||
'orderBy',
|
||||
'functions',
|
||||
'stepInterval',
|
||||
'disabled',
|
||||
'queryName',
|
||||
'expression',
|
||||
'legend',
|
||||
'aggregations',
|
||||
],
|
||||
},
|
||||
},
|
||||
[DataSource.METRICS]: {
|
||||
builder: {
|
||||
queryData: [
|
||||
'aggregateAttribute',
|
||||
'aggregateOperator',
|
||||
'timeAggregation',
|
||||
'filters',
|
||||
'filter',
|
||||
'spaceAggregation',
|
||||
'groupBy',
|
||||
'reduceTo',
|
||||
'limit',
|
||||
'having',
|
||||
'orderBy',
|
||||
'stepInterval',
|
||||
'legend',
|
||||
'queryName',
|
||||
'expression',
|
||||
'disabled',
|
||||
'functions',
|
||||
'aggregations',
|
||||
],
|
||||
},
|
||||
},
|
||||
[DataSource.TRACES]: {
|
||||
builder: {
|
||||
queryData: [
|
||||
'aggregateAttribute',
|
||||
'aggregateOperator',
|
||||
'filters',
|
||||
'filter',
|
||||
'groupBy',
|
||||
'limit',
|
||||
'having',
|
||||
'orderBy',
|
||||
'functions',
|
||||
'stepInterval',
|
||||
'disabled',
|
||||
'queryName',
|
||||
'expression',
|
||||
'legend',
|
||||
'aggregations',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
[PANEL_TYPES.LIST]: {
|
||||
[DataSource.LOGS]: {
|
||||
builder: {
|
||||
queryData: [
|
||||
'queryName',
|
||||
'filters',
|
||||
'filter',
|
||||
'limit',
|
||||
'orderBy',
|
||||
'functions',
|
||||
'aggregations',
|
||||
],
|
||||
},
|
||||
},
|
||||
[DataSource.METRICS]: {
|
||||
builder: {
|
||||
queryData: ['queryName', 'filters', 'filter', 'aggregations'],
|
||||
},
|
||||
},
|
||||
[DataSource.TRACES]: {
|
||||
builder: {
|
||||
queryData: [
|
||||
'queryName',
|
||||
'filters',
|
||||
'filter',
|
||||
'limit',
|
||||
'orderBy',
|
||||
'functions',
|
||||
'aggregations',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
[PANEL_TYPES.VALUE]: {
|
||||
[DataSource.LOGS]: {
|
||||
builder: {
|
||||
queryData: [
|
||||
'aggregateAttribute',
|
||||
'aggregateOperator',
|
||||
'filters',
|
||||
'filter',
|
||||
'reduceTo',
|
||||
'having',
|
||||
'functions',
|
||||
'stepInterval',
|
||||
'queryName',
|
||||
'expression',
|
||||
'disabled',
|
||||
'legend',
|
||||
'aggregations',
|
||||
],
|
||||
},
|
||||
},
|
||||
[DataSource.METRICS]: {
|
||||
builder: {
|
||||
queryData: [
|
||||
'aggregateAttribute',
|
||||
'aggregateOperator',
|
||||
'timeAggregation',
|
||||
'filters',
|
||||
'filter',
|
||||
'spaceAggregation',
|
||||
'having',
|
||||
'reduceTo',
|
||||
'stepInterval',
|
||||
'legend',
|
||||
'queryName',
|
||||
'expression',
|
||||
'disabled',
|
||||
'functions',
|
||||
'aggregations',
|
||||
],
|
||||
},
|
||||
},
|
||||
[DataSource.TRACES]: {
|
||||
builder: {
|
||||
queryData: [
|
||||
'aggregateAttribute',
|
||||
'aggregateOperator',
|
||||
'filters',
|
||||
'filter',
|
||||
'reduceTo',
|
||||
'having',
|
||||
'functions',
|
||||
'stepInterval',
|
||||
'queryName',
|
||||
'expression',
|
||||
'disabled',
|
||||
'legend',
|
||||
'aggregations',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
[PANEL_TYPES.TIME_SERIES]: bySource(SERIES, SERIES_METRICS),
|
||||
[PANEL_TYPES.BAR]: bySource(SERIES, SERIES_METRICS),
|
||||
[PANEL_TYPES.HISTOGRAM]: bySource(SERIES, SERIES_METRICS),
|
||||
[PANEL_TYPES.TABLE]: bySource(SERIES, SCALAR_METRICS),
|
||||
[PANEL_TYPES.PIE]: bySource(SERIES, SCALAR_METRICS),
|
||||
[PANEL_TYPES.VALUE]: bySource(SINGLE_VALUE, SINGLE_VALUE_METRICS),
|
||||
[PANEL_TYPES.LIST]: bySource(RAW_ROWS, RAW_ROWS_METRICS),
|
||||
};
|
||||
|
||||
export function handleQueryChange(
|
||||
|
||||
Reference in New Issue
Block a user