mirror of
https://github.com/SigNoz/signoz.git
synced 2026-11-10 02:10:42 +00:00
Compare commits
2 Commits
nv/exec-st
...
refactor/f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
baf5bcd687 | ||
|
|
52e4c5a28f |
@@ -1,8 +1,8 @@
|
||||
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { render } from 'tests/test-utils';
|
||||
import { QueryMode } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import type { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import { QueryBuilderV2 } from '../QueryBuilderV2';
|
||||
@@ -19,7 +19,7 @@ const aiQuery: Query = {
|
||||
...initialQueriesMap.traces.builder,
|
||||
queryData: initialQueriesMap.traces.builder.queryData.map((query) => ({
|
||||
...query,
|
||||
builderQueryType: QueryMode.AI_QUERY_BUILDER,
|
||||
builderQueryType: EQueryType.AI_QUERY_BUILDER,
|
||||
})),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -40,6 +40,7 @@ export function getQueryNames(currentQuery: Query): BaseOptionType[] {
|
||||
[EQueryType.PROM]: () => getSelectedQueryOptions(currentQuery.promql),
|
||||
[EQueryType.CLICKHOUSE]: () =>
|
||||
getSelectedQueryOptions(currentQuery.clickhouse_sql),
|
||||
[EQueryType.AI_QUERY_BUILDER]: () => [],
|
||||
};
|
||||
|
||||
return queryConfig[currentQuery.queryType]?.() || [];
|
||||
|
||||
@@ -218,6 +218,7 @@ function FormAlertRules({
|
||||
[EQueryType.PROM]: () => getSelectedQueryOptions(currentQuery.promql),
|
||||
[EQueryType.CLICKHOUSE]: () =>
|
||||
getSelectedQueryOptions(currentQuery.clickhouse_sql),
|
||||
[EQueryType.AI_QUERY_BUILDER]: () => [],
|
||||
};
|
||||
|
||||
return queryConfig[currentQuery.queryType]?.() || [];
|
||||
|
||||
@@ -8,11 +8,7 @@ import {
|
||||
initialQueryState,
|
||||
} from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
import {
|
||||
IBuilderQuery,
|
||||
Query,
|
||||
QueryMode,
|
||||
} from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { IBuilderQuery, Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
@@ -63,7 +59,7 @@ describe('normalizeCompositeQuery', () => {
|
||||
extra: 'kept',
|
||||
};
|
||||
const aiQuery = withBuilderQueries(initialQueriesMap.traces, [
|
||||
{ ...tracesQuery, builderQueryType: QueryMode.AI_QUERY_BUILDER },
|
||||
{ ...tracesQuery, builderQueryType: EQueryType.AI_QUERY_BUILDER },
|
||||
]);
|
||||
|
||||
[NOT_AN_EXPLORER, ROUTES.LOGS_EXPLORER].forEach((pathname) =>
|
||||
@@ -209,7 +205,7 @@ describe('normalizeCompositeQuery', () => {
|
||||
expect(
|
||||
firstBuilderQuery(
|
||||
logs,
|
||||
[{ ...logsQuery, builderQueryType: QueryMode.AI_QUERY_BUILDER }],
|
||||
[{ ...logsQuery, builderQueryType: EQueryType.AI_QUERY_BUILDER }],
|
||||
ROUTES.LOGS_EXPLORER,
|
||||
).builderQueryType,
|
||||
).toBeUndefined();
|
||||
@@ -220,7 +216,7 @@ describe('normalizeCompositeQuery', () => {
|
||||
{
|
||||
...tracesQuery,
|
||||
queryName: 'D',
|
||||
builderQueryType: QueryMode.AI_QUERY_BUILDER,
|
||||
builderQueryType: EQueryType.AI_QUERY_BUILDER,
|
||||
...llmFilter,
|
||||
},
|
||||
],
|
||||
@@ -244,7 +240,7 @@ describe('normalizeCompositeQuery', () => {
|
||||
expect(
|
||||
firstBuilderQuery(traces, [{ ...tracesQuery, ...llmFilter }], ai),
|
||||
).toMatchObject({
|
||||
builderQueryType: QueryMode.AI_QUERY_BUILDER,
|
||||
builderQueryType: EQueryType.AI_QUERY_BUILDER,
|
||||
...llmFilter,
|
||||
});
|
||||
expect(
|
||||
@@ -261,14 +257,14 @@ describe('normalizeCompositeQuery', () => {
|
||||
ai,
|
||||
),
|
||||
).toMatchObject({
|
||||
builderQueryType: QueryMode.AI_QUERY_BUILDER,
|
||||
builderQueryType: EQueryType.AI_QUERY_BUILDER,
|
||||
queryName: 'B',
|
||||
filter: { expression: '' },
|
||||
});
|
||||
expect(
|
||||
firstBuilderQuery(traces, [{ ...logsQuery, queryName: 'C' }], ai),
|
||||
).toMatchObject({
|
||||
builderQueryType: QueryMode.AI_QUERY_BUILDER,
|
||||
builderQueryType: EQueryType.AI_QUERY_BUILDER,
|
||||
dataSource: DataSource.TRACES,
|
||||
queryName: 'C',
|
||||
});
|
||||
|
||||
@@ -91,7 +91,12 @@ const buildPromQuery = (
|
||||
return compositeQuery;
|
||||
};
|
||||
|
||||
const queryTypeMethodMapping = {
|
||||
const queryTypeMethodMapping: Partial<
|
||||
Record<
|
||||
EQueryType,
|
||||
(query: Query, panelType: PANEL_TYPES | null) => ICompositeMetricQuery
|
||||
>
|
||||
> = {
|
||||
[EQueryType.QUERY_BUILDER]: buildBuilderQuery,
|
||||
[EQueryType.CLICKHOUSE]: buildClickHouseQuery,
|
||||
[EQueryType.PROM]: buildPromQuery,
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
import {
|
||||
BuilderVariant,
|
||||
type Query,
|
||||
QueryMode,
|
||||
} from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
|
||||
/** Which variant the `builder` slot holds, whatever query type is active. */
|
||||
export function getBuilderVariant(builder: Query['builder']): BuilderVariant {
|
||||
return builder.queryData[0]?.builderQueryType === QueryMode.AI_QUERY_BUILDER
|
||||
return builder.queryData[0]?.builderQueryType === EQueryType.AI_QUERY_BUILDER
|
||||
? BuilderVariant.AI
|
||||
: BuilderVariant.MAIN;
|
||||
}
|
||||
|
||||
/** The variant a tab authors; `undefined` for tabs that own their own slot. */
|
||||
export function toBuilderVariant(mode: QueryMode): BuilderVariant | undefined {
|
||||
if (mode === QueryMode.AI_QUERY_BUILDER) {
|
||||
export function toBuilderVariant(mode: EQueryType): BuilderVariant | undefined {
|
||||
if (mode === EQueryType.AI_QUERY_BUILDER) {
|
||||
return BuilderVariant.AI;
|
||||
}
|
||||
return mode === QueryMode.QUERY_BUILDER ? BuilderVariant.MAIN : undefined;
|
||||
return mode === EQueryType.QUERY_BUILDER ? BuilderVariant.MAIN : undefined;
|
||||
}
|
||||
|
||||
/** The tab a query belongs to: its query type, or AI when a builder query carries the tag. */
|
||||
export function getQueryMode(query: Query): QueryMode {
|
||||
if (query.queryType !== QueryMode.QUERY_BUILDER) {
|
||||
export function getQueryMode(query: Query): EQueryType {
|
||||
if (query.queryType !== EQueryType.QUERY_BUILDER) {
|
||||
return query.queryType;
|
||||
}
|
||||
return getBuilderVariant(query.builder) === BuilderVariant.AI
|
||||
? QueryMode.AI_QUERY_BUILDER
|
||||
: QueryMode.QUERY_BUILDER;
|
||||
? EQueryType.AI_QUERY_BUILDER
|
||||
: EQueryType.QUERY_BUILDER;
|
||||
}
|
||||
|
||||
/** Query Builder and AI both author `Query.builder`; the other modes own their own slot. */
|
||||
export function isBuilderMode(mode: QueryMode): boolean {
|
||||
export function isBuilderMode(mode: EQueryType): boolean {
|
||||
return toBuilderVariant(mode) !== undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { Flame } from '@signozhq/icons';
|
||||
import type { DashboardtypesPanelDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { getQueryMode } from 'lib/query/queryMode';
|
||||
import { getPanelDefinition } from 'pages/DashboardPage/DashboardContainer/Panels/registry';
|
||||
import { useCreateAlertFromPanel } from 'pages/DashboardPage/DashboardContainer/PanelsAndSectionsLayout/Panel/hooks/useCreateAlertFromPanel';
|
||||
import { QueryMode } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import ConfigActionRow from './ConfigActionRow';
|
||||
import styles from './ConfigActions.module.scss';
|
||||
@@ -33,7 +33,7 @@ function ConfigActions({
|
||||
//TODO: AI query check should be removed after we support alerts on ai query @tewarig
|
||||
if (
|
||||
!actions.createAlert ||
|
||||
getQueryMode(currentQuery) === QueryMode.AI_QUERY_BUILDER
|
||||
getQueryMode(currentQuery) === EQueryType.AI_QUERY_BUILDER
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ describe('PanelTypeSwitcher', () => {
|
||||
return {
|
||||
...OPTIONS.find((option) => option.kind === kind),
|
||||
mode: kind === 'signoz/TextPanel' ? 'static' : 'query',
|
||||
supportedQueryModesKindMap: Object.fromEntries(
|
||||
supportedQueryTypesKindMap: Object.fromEntries(
|
||||
queryTypes.map((queryType) => [
|
||||
queryType,
|
||||
queryType === EQueryType.QUERY_BUILDER
|
||||
|
||||
@@ -12,6 +12,7 @@ const QUERY_TYPE_LABEL: Record<EQueryType, string> = {
|
||||
[EQueryType.QUERY_BUILDER]: 'Query Builder',
|
||||
[EQueryType.CLICKHOUSE]: 'ClickHouse',
|
||||
[EQueryType.PROM]: 'PromQL',
|
||||
[EQueryType.AI_QUERY_BUILDER]: 'AI Query Builder',
|
||||
};
|
||||
|
||||
const SIGNAL_LABEL: Record<TelemetrytypesSignalDTO, string> = {
|
||||
|
||||
@@ -14,7 +14,7 @@ jest.mock('pages/DashboardPage/DashboardContainer/Panels/registry', () => ({
|
||||
displayName: 'Time Series',
|
||||
icon: (): null => null,
|
||||
mode: 'query',
|
||||
supportedQueryModesKindMap: {
|
||||
supportedQueryTypesKindMap: {
|
||||
builder: { kind: 'signal', signals: ['metrics', 'logs', 'traces'] },
|
||||
clickhouse_sql: { kind: 'signal-less' },
|
||||
promql: { kind: 'signal-less' },
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import {
|
||||
type KeyboardEvent,
|
||||
type ReactNode,
|
||||
@@ -23,13 +24,12 @@ import { useSyncQueryBuilderFields } from 'components/QueryBuilderV2/useSyncQuer
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import {
|
||||
getQueryModeSignals,
|
||||
getQueryTypeSignals,
|
||||
isRawRequest,
|
||||
} from '../../Panels/types/panelCapabilities';
|
||||
import type { RenderableQueryPanelDefinition } from '../../Panels/types/panelDefinition';
|
||||
import { toPanelType } from '../../Panels/types/panelKind';
|
||||
import { getQueryMode, isBuilderMode } from 'lib/query/queryMode';
|
||||
import { QueryMode } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import styles from './PanelEditorQueryBuilder.module.scss';
|
||||
|
||||
@@ -99,10 +99,10 @@ function PanelEditorQueryBuilder({
|
||||
|
||||
const items = useMemo(() => {
|
||||
const queryTypeComponents: Record<
|
||||
QueryMode,
|
||||
EQueryType,
|
||||
{ icon: ReactNode; label: string; component: ReactNode }
|
||||
> = {
|
||||
[QueryMode.QUERY_BUILDER]: {
|
||||
[EQueryType.QUERY_BUILDER]: {
|
||||
icon: <Atom size={14} />,
|
||||
label: 'Query Builder',
|
||||
component: (
|
||||
@@ -110,9 +110,9 @@ function PanelEditorQueryBuilder({
|
||||
<QueryBuilderV2
|
||||
panelType={panelType}
|
||||
fieldsConfig={panelDefinition.queryBuilderFields}
|
||||
allowedDataSources={getQueryModeSignals(
|
||||
panelDefinition.supportedQueryModesKindMap,
|
||||
QueryMode.QUERY_BUILDER,
|
||||
allowedDataSources={getQueryTypeSignals(
|
||||
panelDefinition.supportedQueryTypesKindMap,
|
||||
EQueryType.QUERY_BUILDER,
|
||||
)}
|
||||
showTraceOperator={!isRawQuery}
|
||||
version="v3"
|
||||
@@ -123,12 +123,12 @@ function PanelEditorQueryBuilder({
|
||||
</div>
|
||||
),
|
||||
},
|
||||
[QueryMode.CLICKHOUSE]: {
|
||||
[EQueryType.CLICKHOUSE]: {
|
||||
icon: <Terminal size={14} />,
|
||||
label: 'ClickHouse Query',
|
||||
component: <ClickHouseQueryContainer />,
|
||||
},
|
||||
[QueryMode.PROM]: {
|
||||
[EQueryType.PROM]: {
|
||||
icon: (
|
||||
<PromQLIcon
|
||||
fillColor={isDarkMode ? Color.BG_VANILLA_200 : Color.BG_INK_300}
|
||||
@@ -137,7 +137,7 @@ function PanelEditorQueryBuilder({
|
||||
label: 'PromQL',
|
||||
component: <PromQLQueryContainer />,
|
||||
},
|
||||
[QueryMode.AI_QUERY_BUILDER]: {
|
||||
[EQueryType.AI_QUERY_BUILDER]: {
|
||||
icon: <Brain size={14} />,
|
||||
label: 'AI Query Builder',
|
||||
component: (
|
||||
@@ -160,8 +160,8 @@ function PanelEditorQueryBuilder({
|
||||
};
|
||||
|
||||
const modes = Object.keys(
|
||||
panelDefinition.supportedQueryModesKindMap,
|
||||
) as QueryMode[];
|
||||
panelDefinition.supportedQueryTypesKindMap,
|
||||
) as EQueryType[];
|
||||
|
||||
return modes.map((queryType) => ({
|
||||
key: queryType,
|
||||
|
||||
@@ -69,7 +69,7 @@ function BuilderHost({ panelKind }: { panelKind: PanelKind }): JSX.Element {
|
||||
const parkedBuilders = useRef<BuildersByVariant>({});
|
||||
const onChangeQueryMode = useQueryModeChange({
|
||||
panelType: toPanelType(panelKind),
|
||||
supportedQueryModesKindMap: panelDefinition.supportedQueryModesKindMap,
|
||||
supportedQueryTypesKindMap: panelDefinition.supportedQueryTypesKindMap,
|
||||
parkedBuilders,
|
||||
});
|
||||
return (
|
||||
|
||||
@@ -84,7 +84,7 @@ function EditorRoute({ panelId }: { panelId: string }): JSX.Element {
|
||||
const parkedBuilders = useRef<BuildersByVariant>({});
|
||||
const onChangeQueryMode = useQueryModeChange({
|
||||
panelType: PANEL_TYPES.TIME_SERIES,
|
||||
supportedQueryModesKindMap: panelDefinition.supportedQueryModesKindMap,
|
||||
supportedQueryTypesKindMap: panelDefinition.supportedQueryTypesKindMap,
|
||||
parkedBuilders,
|
||||
});
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { handleQueryChange } from 'lib/query/panelQuery';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import type { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { resolveQueryMode } from 'pages/DashboardPage/DashboardContainer/Panels/capabilities';
|
||||
import { resolveQueryType } from 'pages/DashboardPage/DashboardContainer/Panels/capabilities';
|
||||
import { getBuilderQueries } from '../../../Panels/utils/getBuilderQueries';
|
||||
import { toPerses } from '../../../queryV5/persesQueryAdapters';
|
||||
import { getSwitchedPluginSpec } from '../../getSwitchedPluginSpec';
|
||||
@@ -18,7 +18,7 @@ jest.mock('lib/query/panelQuery', () => ({
|
||||
handleQueryChange: jest.fn(),
|
||||
}));
|
||||
jest.mock('../../../Panels/capabilities', () => ({
|
||||
resolveQueryMode: jest.fn(),
|
||||
resolveQueryType: jest.fn(),
|
||||
getSupportedSignals: jest.fn(() => ['metrics']),
|
||||
getQueryPanelDefinition: jest.requireActual('../../../Panels/capabilities')
|
||||
.getQueryPanelDefinition,
|
||||
@@ -39,7 +39,7 @@ jest.mock('../../../Panels/utils/getBuilderQueries', () => ({
|
||||
|
||||
const mockUseQueryBuilder = useQueryBuilder as unknown as jest.Mock;
|
||||
const mockHandleQueryChange = handleQueryChange as unknown as jest.Mock;
|
||||
const mockResolveQueryMode = resolveQueryMode as unknown as jest.Mock;
|
||||
const mockResolveQueryMode = resolveQueryType as unknown as jest.Mock;
|
||||
const mockToPerses = toPerses as unknown as jest.Mock;
|
||||
const mockGetSwitchedPluginSpec = getSwitchedPluginSpec as unknown as jest.Mock;
|
||||
const mockGetBuilderQueries = getBuilderQueries as unknown as jest.Mock;
|
||||
|
||||
@@ -16,15 +16,15 @@ import {
|
||||
type BuildersByVariant,
|
||||
type OrderByPayload,
|
||||
type Query,
|
||||
QueryMode,
|
||||
} from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
|
||||
import {
|
||||
getQueryPanelDefinition,
|
||||
isStaticPanelKind,
|
||||
resolveQueryMode,
|
||||
resolveQueryType,
|
||||
} from 'pages/DashboardPage/DashboardContainer/Panels/capabilities';
|
||||
import { getQueryModeSignals } from 'pages/DashboardPage/DashboardContainer/Panels/types/panelCapabilities';
|
||||
import { getQueryTypeSignals } from 'pages/DashboardPage/DashboardContainer/Panels/types/panelCapabilities';
|
||||
import {
|
||||
getBuilderVariant,
|
||||
getQueryMode,
|
||||
@@ -119,9 +119,9 @@ export function usePanelKindAndQueryModeSwitch({
|
||||
|
||||
const onChangeQueryMode = useQueryModeChange({
|
||||
panelType,
|
||||
supportedQueryModesKindMap:
|
||||
supportedQueryTypesKindMap:
|
||||
getQueryPanelDefinition(spec.plugin.kind as PanelKind)
|
||||
?.supportedQueryModesKindMap ?? {},
|
||||
?.supportedQueryTypesKindMap ?? {},
|
||||
parkedBuilders,
|
||||
});
|
||||
|
||||
@@ -149,12 +149,12 @@ export function usePanelKindAndQueryModeSwitch({
|
||||
});
|
||||
|
||||
const newPanelType = toPanelType(newKind);
|
||||
const targetMode = resolveQueryMode(newKind, getQueryMode(query));
|
||||
const targetMode = resolveQueryType(newKind, getQueryMode(query));
|
||||
const targetVariant = toBuilderVariant(targetMode);
|
||||
// An AI query is a builder query carrying the tag, so it rides on `builder`.
|
||||
const targetQueryType =
|
||||
targetMode === QueryMode.AI_QUERY_BUILDER
|
||||
? QueryMode.QUERY_BUILDER
|
||||
targetMode === EQueryType.AI_QUERY_BUILDER
|
||||
? EQueryType.QUERY_BUILDER
|
||||
: targetMode;
|
||||
|
||||
// Only `plugin` needs a cast: it's a discriminated union over `kind`, and a
|
||||
@@ -189,14 +189,14 @@ export function usePanelKindAndQueryModeSwitch({
|
||||
};
|
||||
// Tab unused on this kind: AI gets a fresh seed, Query Builder reuses `source` untagged.
|
||||
const seedTargetTab = (source: Query): Query =>
|
||||
targetMode === QueryMode.AI_QUERY_BUILDER
|
||||
targetMode === EQueryType.AI_QUERY_BUILDER
|
||||
? {
|
||||
...source,
|
||||
builder: seedBuilderForMode({
|
||||
mode: targetMode,
|
||||
defaultSignal: getQueryModeSignals(
|
||||
getQueryPanelDefinition(newKind)?.supportedQueryModesKindMap,
|
||||
QueryMode.QUERY_BUILDER,
|
||||
defaultSignal: getQueryTypeSignals(
|
||||
getQueryPanelDefinition(newKind)?.supportedQueryTypesKindMap,
|
||||
EQueryType.QUERY_BUILDER,
|
||||
)[0],
|
||||
panelType: newPanelType,
|
||||
updateAllQueriesOperators,
|
||||
|
||||
@@ -3,20 +3,20 @@ import type { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
|
||||
import {
|
||||
getQueryModeSignals,
|
||||
type SupportedQueryModesKindMap,
|
||||
getQueryTypeSignals,
|
||||
type SupportedQueryTypesKindMap,
|
||||
} from 'pages/DashboardPage/DashboardContainer/Panels/types/panelCapabilities';
|
||||
import {
|
||||
type BuildersByVariant,
|
||||
type Query,
|
||||
QueryMode,
|
||||
} from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { getBuilderVariant, toBuilderVariant } from 'lib/query/queryMode';
|
||||
import { seedBuilderForMode } from 'pages/DashboardPage/DashboardContainer/Panels/utils/seedBuilderForMode';
|
||||
|
||||
interface UseQueryModeChangeArgs {
|
||||
panelType: PANEL_TYPES;
|
||||
supportedQueryModesKindMap: SupportedQueryModesKindMap;
|
||||
supportedQueryTypesKindMap: SupportedQueryTypesKindMap;
|
||||
/** Builder queries for the variant not on screen. */
|
||||
parkedBuilders: MutableRefObject<BuildersByVariant>;
|
||||
}
|
||||
@@ -27,7 +27,7 @@ interface UseQueryModeChangeArgs {
|
||||
*/
|
||||
export function useQueryModeChange({
|
||||
panelType,
|
||||
supportedQueryModesKindMap,
|
||||
supportedQueryTypesKindMap,
|
||||
parkedBuilders,
|
||||
}: UseQueryModeChangeArgs): (key: string) => void {
|
||||
const {
|
||||
@@ -38,16 +38,16 @@ export function useQueryModeChange({
|
||||
|
||||
return useCallback(
|
||||
(key: string): void => {
|
||||
const targetMode = key as QueryMode;
|
||||
const targetMode = key as EQueryType;
|
||||
const targetVariant = toBuilderVariant(targetMode);
|
||||
const heldVariant = getBuilderVariant(currentQuery.builder);
|
||||
|
||||
const seedFor = (mode: QueryMode): Query['builder'] =>
|
||||
const seedFor = (mode: EQueryType): Query['builder'] =>
|
||||
seedBuilderForMode({
|
||||
mode,
|
||||
defaultSignal: getQueryModeSignals(
|
||||
supportedQueryModesKindMap,
|
||||
QueryMode.QUERY_BUILDER,
|
||||
defaultSignal: getQueryTypeSignals(
|
||||
supportedQueryTypesKindMap,
|
||||
EQueryType.QUERY_BUILDER,
|
||||
)[0],
|
||||
panelType,
|
||||
updateAllQueriesOperators,
|
||||
@@ -65,8 +65,8 @@ export function useQueryModeChange({
|
||||
...currentQuery,
|
||||
// An AI query is a builder query carrying the AI tag; its query type stays `builder`.
|
||||
queryType:
|
||||
targetMode === QueryMode.AI_QUERY_BUILDER
|
||||
? QueryMode.QUERY_BUILDER
|
||||
targetMode === EQueryType.AI_QUERY_BUILDER
|
||||
? EQueryType.QUERY_BUILDER
|
||||
: targetMode,
|
||||
builder,
|
||||
});
|
||||
@@ -76,7 +76,7 @@ export function useQueryModeChange({
|
||||
panelType,
|
||||
parkedBuilders,
|
||||
redirectWithQueryBuilderData,
|
||||
supportedQueryModesKindMap,
|
||||
supportedQueryTypesKindMap,
|
||||
updateAllQueriesOperators,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
TelemetrytypesSignalDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { QueryMode } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { UNSUPPORTED_PANEL } from '../kinds/UnsupportedPanel/definition';
|
||||
import { getPanelDefinition, isPanelKindSupported } from '../registry';
|
||||
@@ -18,7 +17,7 @@ import {
|
||||
isPanelCombinationValid,
|
||||
isQueryTypeSupportedByPanelKind,
|
||||
isSignalSupported,
|
||||
resolveQueryMode,
|
||||
resolveQueryType,
|
||||
} from '../capabilities';
|
||||
import type { PanelKind } from '../types/panelKind';
|
||||
|
||||
@@ -41,45 +40,45 @@ const EXPECTED_QUERY_TYPES: Record<PanelKind, EQueryType[]> = {
|
||||
'signoz/TextPanel': [],
|
||||
};
|
||||
|
||||
const EXPECTED_QUERY_MODES: Record<PanelKind, QueryMode[]> = {
|
||||
const EXPECTED_QUERY_MODES: Record<PanelKind, EQueryType[]> = {
|
||||
'signoz/TimeSeriesPanel': [
|
||||
QUERY_BUILDER,
|
||||
CLICKHOUSE,
|
||||
PROM,
|
||||
QueryMode.AI_QUERY_BUILDER,
|
||||
EQueryType.AI_QUERY_BUILDER,
|
||||
],
|
||||
'signoz/BarChartPanel': [
|
||||
QUERY_BUILDER,
|
||||
CLICKHOUSE,
|
||||
PROM,
|
||||
QueryMode.AI_QUERY_BUILDER,
|
||||
EQueryType.AI_QUERY_BUILDER,
|
||||
],
|
||||
'signoz/AreaChartPanel': [
|
||||
QUERY_BUILDER,
|
||||
CLICKHOUSE,
|
||||
PROM,
|
||||
QueryMode.AI_QUERY_BUILDER,
|
||||
EQueryType.AI_QUERY_BUILDER,
|
||||
],
|
||||
'signoz/NumberPanel': [
|
||||
QUERY_BUILDER,
|
||||
CLICKHOUSE,
|
||||
PROM,
|
||||
QueryMode.AI_QUERY_BUILDER,
|
||||
EQueryType.AI_QUERY_BUILDER,
|
||||
],
|
||||
'signoz/HistogramPanel': [
|
||||
QUERY_BUILDER,
|
||||
CLICKHOUSE,
|
||||
PROM,
|
||||
QueryMode.AI_QUERY_BUILDER,
|
||||
EQueryType.AI_QUERY_BUILDER,
|
||||
],
|
||||
'signoz/HeatmapPanel': [QUERY_BUILDER, CLICKHOUSE, PROM],
|
||||
'signoz/PieChartPanel': [
|
||||
QUERY_BUILDER,
|
||||
CLICKHOUSE,
|
||||
QueryMode.AI_QUERY_BUILDER,
|
||||
EQueryType.AI_QUERY_BUILDER,
|
||||
],
|
||||
'signoz/TablePanel': [QUERY_BUILDER, CLICKHOUSE, QueryMode.AI_QUERY_BUILDER],
|
||||
'signoz/ListPanel': [QUERY_BUILDER, QueryMode.AI_QUERY_BUILDER],
|
||||
'signoz/TablePanel': [QUERY_BUILDER, CLICKHOUSE, EQueryType.AI_QUERY_BUILDER],
|
||||
'signoz/ListPanel': [QUERY_BUILDER, EQueryType.AI_QUERY_BUILDER],
|
||||
'signoz/ScatterPlotPanel': [QUERY_BUILDER, CLICKHOUSE],
|
||||
'signoz/TextPanel': [],
|
||||
};
|
||||
@@ -281,10 +280,10 @@ describe('panel capabilities guard', () => {
|
||||
|
||||
it('reads a specific mode when one is given', () => {
|
||||
expect(
|
||||
getSupportedSignals('signoz/TablePanel', QueryMode.AI_QUERY_BUILDER),
|
||||
getSupportedSignals('signoz/TablePanel', EQueryType.AI_QUERY_BUILDER),
|
||||
).toStrictEqual([traces]);
|
||||
expect(
|
||||
getSupportedSignals('signoz/TablePanel', QueryMode.CLICKHOUSE),
|
||||
getSupportedSignals('signoz/TablePanel', EQueryType.CLICKHOUSE),
|
||||
).toStrictEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -345,26 +344,26 @@ describe('panel capabilities guard', () => {
|
||||
|
||||
it('keeps AI out of the query types', () => {
|
||||
expect(getSupportedQueryTypes('signoz/TimeSeriesPanel')).not.toContain(
|
||||
QueryMode.AI_QUERY_BUILDER,
|
||||
EQueryType.AI_QUERY_BUILDER,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveQueryMode', () => {
|
||||
describe('resolveQueryType', () => {
|
||||
it('keeps a supported mode', () => {
|
||||
expect(resolveQueryMode('signoz/TimeSeriesPanel', PROM)).toBe(PROM);
|
||||
expect(resolveQueryType('signoz/TimeSeriesPanel', PROM)).toBe(PROM);
|
||||
expect(
|
||||
resolveQueryMode('signoz/TablePanel', QueryMode.AI_QUERY_BUILDER),
|
||||
).toBe(QueryMode.AI_QUERY_BUILDER);
|
||||
resolveQueryType('signoz/TablePanel', EQueryType.AI_QUERY_BUILDER),
|
||||
).toBe(EQueryType.AI_QUERY_BUILDER);
|
||||
expect(
|
||||
resolveQueryMode('signoz/ListPanel', QueryMode.AI_QUERY_BUILDER),
|
||||
).toBe(QueryMode.AI_QUERY_BUILDER);
|
||||
resolveQueryType('signoz/ListPanel', EQueryType.AI_QUERY_BUILDER),
|
||||
).toBe(EQueryType.AI_QUERY_BUILDER);
|
||||
});
|
||||
|
||||
it('coerces an unsupported mode to the first supported one', () => {
|
||||
// PromQL → List has no PromQL, falls back to its first (and only) type.
|
||||
expect(resolveQueryMode('signoz/ListPanel', PROM)).toBe(QUERY_BUILDER);
|
||||
expect(resolveQueryMode('signoz/TablePanel', PROM)).toBe(QUERY_BUILDER);
|
||||
expect(resolveQueryType('signoz/ListPanel', PROM)).toBe(QUERY_BUILDER);
|
||||
expect(resolveQueryType('signoz/TablePanel', PROM)).toBe(QUERY_BUILDER);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { type TelemetrytypesSignalDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { QueryMode } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { getPanelDefinition } from './registry';
|
||||
import { getQueryModeSignals } from './types/panelCapabilities';
|
||||
import { getQueryTypeSignals } from './types/panelCapabilities';
|
||||
import type { RenderableQueryPanelDefinition } from './types/panelDefinition';
|
||||
import type { PanelKind } from './types/panelKind';
|
||||
|
||||
@@ -50,16 +49,16 @@ export function requireQueryPanelDefinition(
|
||||
return definition;
|
||||
}
|
||||
|
||||
const DEFAULT_QUERY_MODE = QueryMode.QUERY_BUILDER;
|
||||
const DEFAULT_QUERY_TYPE = EQueryType.QUERY_BUILDER;
|
||||
|
||||
/** Signals a kind can visualize in `mode`. */
|
||||
/** Signals a kind can visualize in `queryType`. */
|
||||
export function getSupportedSignals(
|
||||
kind: PanelKind,
|
||||
mode: QueryMode = DEFAULT_QUERY_MODE,
|
||||
queryType: EQueryType = DEFAULT_QUERY_TYPE,
|
||||
): TelemetrytypesSignalDTO[] {
|
||||
return getQueryModeSignals(
|
||||
getQueryPanelDefinition(kind)?.supportedQueryModesKindMap,
|
||||
mode,
|
||||
return getQueryTypeSignals(
|
||||
getQueryPanelDefinition(kind)?.supportedQueryTypesKindMap,
|
||||
queryType,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -71,14 +70,14 @@ export function isSignalSupported(
|
||||
}
|
||||
|
||||
/** Authoring modes a kind supports, in tab order. */
|
||||
export function getSupportedQueryModes(kind: PanelKind): QueryMode[] {
|
||||
const modes = getQueryPanelDefinition(kind)?.supportedQueryModesKindMap ?? {};
|
||||
return Object.keys(modes) as QueryMode[];
|
||||
export function getSupportedQueryModes(kind: PanelKind): EQueryType[] {
|
||||
const modes = getQueryPanelDefinition(kind)?.supportedQueryTypesKindMap ?? {};
|
||||
return Object.keys(modes) as EQueryType[];
|
||||
}
|
||||
/** Query languages a kind supports (Query Builder / ClickHouse / PromQL). */
|
||||
export function getSupportedQueryTypes(kind: PanelKind): EQueryType[] {
|
||||
return getSupportedQueryModes(kind).filter(
|
||||
(mode): mode is EQueryType => mode !== QueryMode.AI_QUERY_BUILDER,
|
||||
(queryType) => queryType !== EQueryType.AI_QUERY_BUILDER,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -116,15 +115,15 @@ export function isPanelCombinationValid({
|
||||
return true;
|
||||
}
|
||||
|
||||
/** `preferred` if the kind supports it, else its first supported mode (e.g. PromQL → List gives Query Builder). */
|
||||
export function resolveQueryMode(
|
||||
/** `preferred` if the kind supports it, else its first supported query type (e.g. PromQL → List gives Query Builder). */
|
||||
export function resolveQueryType(
|
||||
kind: PanelKind,
|
||||
preferred: QueryMode,
|
||||
): QueryMode {
|
||||
preferred: EQueryType,
|
||||
): EQueryType {
|
||||
const supported = getSupportedQueryModes(kind);
|
||||
if (supported.includes(preferred)) {
|
||||
return preferred;
|
||||
}
|
||||
// A query-less kind has no supported modes; the builder is the neutral answer.
|
||||
return supported[0] ?? DEFAULT_QUERY_MODE;
|
||||
// A query-less kind has no supported query types; the builder is the neutral answer.
|
||||
return supported[0] ?? DEFAULT_QUERY_TYPE;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { ChartArea } from '@signozhq/icons';
|
||||
|
||||
import type { PanelDefinition } from '../../types/panelDefinition';
|
||||
import { QueryModeKind } from '../../types/panelCapabilities';
|
||||
import { QueryTypeKind } from '../../types/panelCapabilities';
|
||||
import QueryBuilderEditorPane from 'pages/DashboardPage/DashboardContainer/PanelEditor/PanelEditorQueryBuilder/QueryBuilderEditorPane';
|
||||
import Renderer from './Renderer';
|
||||
import { sections } from './sections';
|
||||
@@ -9,7 +10,6 @@ import {
|
||||
Querybuildertypesv5RequestTypeDTO,
|
||||
TelemetrytypesSignalDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { QueryMode } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
export const definition: PanelDefinition<'signoz/AreaChartPanel'> = {
|
||||
kind: 'signoz/AreaChartPanel',
|
||||
@@ -19,19 +19,19 @@ export const definition: PanelDefinition<'signoz/AreaChartPanel'> = {
|
||||
Renderer,
|
||||
EditorPane: QueryBuilderEditorPane,
|
||||
sections,
|
||||
supportedQueryModesKindMap: {
|
||||
[QueryMode.QUERY_BUILDER]: {
|
||||
kind: QueryModeKind.SIGNAL,
|
||||
supportedQueryTypesKindMap: {
|
||||
[EQueryType.QUERY_BUILDER]: {
|
||||
kind: QueryTypeKind.SIGNAL,
|
||||
signals: [
|
||||
TelemetrytypesSignalDTO.metrics,
|
||||
TelemetrytypesSignalDTO.logs,
|
||||
TelemetrytypesSignalDTO.traces,
|
||||
],
|
||||
},
|
||||
[QueryMode.CLICKHOUSE]: { kind: QueryModeKind.SIGNAL_LESS },
|
||||
[QueryMode.PROM]: { kind: QueryModeKind.SIGNAL_LESS },
|
||||
[QueryMode.AI_QUERY_BUILDER]: {
|
||||
kind: QueryModeKind.SIGNAL,
|
||||
[EQueryType.CLICKHOUSE]: { kind: QueryTypeKind.SIGNAL_LESS },
|
||||
[EQueryType.PROM]: { kind: QueryTypeKind.SIGNAL_LESS },
|
||||
[EQueryType.AI_QUERY_BUILDER]: {
|
||||
kind: QueryTypeKind.SIGNAL,
|
||||
signals: [TelemetrytypesSignalDTO.traces],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { BarChart } from '@signozhq/icons';
|
||||
|
||||
import type { PanelDefinition } from '../../types/panelDefinition';
|
||||
import { QueryModeKind } from '../../types/panelCapabilities';
|
||||
import { QueryTypeKind } from '../../types/panelCapabilities';
|
||||
import QueryBuilderEditorPane from 'pages/DashboardPage/DashboardContainer/PanelEditor/PanelEditorQueryBuilder/QueryBuilderEditorPane';
|
||||
import Renderer from './Renderer';
|
||||
import { sections } from './sections';
|
||||
@@ -9,7 +10,6 @@ import {
|
||||
Querybuildertypesv5RequestTypeDTO,
|
||||
TelemetrytypesSignalDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { QueryMode } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
export const definition: PanelDefinition<'signoz/BarChartPanel'> = {
|
||||
kind: 'signoz/BarChartPanel',
|
||||
@@ -19,19 +19,19 @@ export const definition: PanelDefinition<'signoz/BarChartPanel'> = {
|
||||
Renderer,
|
||||
EditorPane: QueryBuilderEditorPane,
|
||||
sections,
|
||||
supportedQueryModesKindMap: {
|
||||
[QueryMode.QUERY_BUILDER]: {
|
||||
kind: QueryModeKind.SIGNAL,
|
||||
supportedQueryTypesKindMap: {
|
||||
[EQueryType.QUERY_BUILDER]: {
|
||||
kind: QueryTypeKind.SIGNAL,
|
||||
signals: [
|
||||
TelemetrytypesSignalDTO.metrics,
|
||||
TelemetrytypesSignalDTO.logs,
|
||||
TelemetrytypesSignalDTO.traces,
|
||||
],
|
||||
},
|
||||
[QueryMode.CLICKHOUSE]: { kind: QueryModeKind.SIGNAL_LESS },
|
||||
[QueryMode.PROM]: { kind: QueryModeKind.SIGNAL_LESS },
|
||||
[QueryMode.AI_QUERY_BUILDER]: {
|
||||
kind: QueryModeKind.SIGNAL,
|
||||
[EQueryType.CLICKHOUSE]: { kind: QueryTypeKind.SIGNAL_LESS },
|
||||
[EQueryType.PROM]: { kind: QueryTypeKind.SIGNAL_LESS },
|
||||
[EQueryType.AI_QUERY_BUILDER]: {
|
||||
kind: QueryTypeKind.SIGNAL,
|
||||
signals: [TelemetrytypesSignalDTO.traces],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { LayoutGrid } from '@signozhq/icons';
|
||||
|
||||
import { QueryBuilderField } from 'components/QueryBuilderV2/queryBuilderFields.types';
|
||||
|
||||
import type { PanelDefinition } from '../../types/panelDefinition';
|
||||
import { QueryModeKind } from '../../types/panelCapabilities';
|
||||
import { QueryTypeKind } from '../../types/panelCapabilities';
|
||||
import QueryBuilderEditorPane from 'pages/DashboardPage/DashboardContainer/PanelEditor/PanelEditorQueryBuilder/QueryBuilderEditorPane';
|
||||
import Renderer from './Renderer';
|
||||
import { sections } from './sections';
|
||||
@@ -11,7 +12,6 @@ import {
|
||||
Querybuildertypesv5RequestTypeDTO,
|
||||
TelemetrytypesSignalDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { QueryMode } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
export const definition: PanelDefinition<'signoz/HeatmapPanel'> = {
|
||||
kind: 'signoz/HeatmapPanel',
|
||||
@@ -21,14 +21,14 @@ export const definition: PanelDefinition<'signoz/HeatmapPanel'> = {
|
||||
Renderer,
|
||||
EditorPane: QueryBuilderEditorPane,
|
||||
sections,
|
||||
supportedQueryModesKindMap: {
|
||||
supportedQueryTypesKindMap: {
|
||||
// Only metrics carry a bucket axis; the request rejects the other signals.
|
||||
[QueryMode.QUERY_BUILDER]: {
|
||||
kind: QueryModeKind.SIGNAL,
|
||||
[EQueryType.QUERY_BUILDER]: {
|
||||
kind: QueryTypeKind.SIGNAL,
|
||||
signals: [TelemetrytypesSignalDTO.metrics],
|
||||
},
|
||||
[QueryMode.CLICKHOUSE]: { kind: QueryModeKind.SIGNAL_LESS },
|
||||
[QueryMode.PROM]: { kind: QueryModeKind.SIGNAL_LESS },
|
||||
[EQueryType.CLICKHOUSE]: { kind: QueryTypeKind.SIGNAL_LESS },
|
||||
[EQueryType.PROM]: { kind: QueryTypeKind.SIGNAL_LESS },
|
||||
},
|
||||
// The request rejects both: a point is a count per bucket, not a single value.
|
||||
queryBuilderFields: {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { BarChart } from '@signozhq/icons';
|
||||
|
||||
import type { PanelDefinition } from '../../types/panelDefinition';
|
||||
import { QueryModeKind } from '../../types/panelCapabilities';
|
||||
import { QueryTypeKind } from '../../types/panelCapabilities';
|
||||
import QueryBuilderEditorPane from 'pages/DashboardPage/DashboardContainer/PanelEditor/PanelEditorQueryBuilder/QueryBuilderEditorPane';
|
||||
import Renderer from './Renderer';
|
||||
import { sections } from './sections';
|
||||
@@ -9,7 +10,6 @@ import {
|
||||
Querybuildertypesv5RequestTypeDTO,
|
||||
TelemetrytypesSignalDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { QueryMode } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
export const definition: PanelDefinition<'signoz/HistogramPanel'> = {
|
||||
kind: 'signoz/HistogramPanel',
|
||||
@@ -19,19 +19,19 @@ export const definition: PanelDefinition<'signoz/HistogramPanel'> = {
|
||||
Renderer,
|
||||
EditorPane: QueryBuilderEditorPane,
|
||||
sections,
|
||||
supportedQueryModesKindMap: {
|
||||
[QueryMode.QUERY_BUILDER]: {
|
||||
kind: QueryModeKind.SIGNAL,
|
||||
supportedQueryTypesKindMap: {
|
||||
[EQueryType.QUERY_BUILDER]: {
|
||||
kind: QueryTypeKind.SIGNAL,
|
||||
signals: [
|
||||
TelemetrytypesSignalDTO.metrics,
|
||||
TelemetrytypesSignalDTO.logs,
|
||||
TelemetrytypesSignalDTO.traces,
|
||||
],
|
||||
},
|
||||
[QueryMode.CLICKHOUSE]: { kind: QueryModeKind.SIGNAL_LESS },
|
||||
[QueryMode.PROM]: { kind: QueryModeKind.SIGNAL_LESS },
|
||||
[QueryMode.AI_QUERY_BUILDER]: {
|
||||
kind: QueryModeKind.SIGNAL,
|
||||
[EQueryType.CLICKHOUSE]: { kind: QueryTypeKind.SIGNAL_LESS },
|
||||
[EQueryType.PROM]: { kind: QueryTypeKind.SIGNAL_LESS },
|
||||
[EQueryType.AI_QUERY_BUILDER]: {
|
||||
kind: QueryTypeKind.SIGNAL,
|
||||
signals: [TelemetrytypesSignalDTO.traces],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { List } from '@signozhq/icons';
|
||||
|
||||
import type { PanelDefinition } from '../../types/panelDefinition';
|
||||
import { QueryModeKind } from '../../types/panelCapabilities';
|
||||
import { QueryTypeKind } from '../../types/panelCapabilities';
|
||||
import ListEditorPane from './ListEditorPane';
|
||||
import Renderer from './Renderer';
|
||||
import { sections } from './sections';
|
||||
@@ -9,7 +10,6 @@ import {
|
||||
Querybuildertypesv5RequestTypeDTO,
|
||||
TelemetrytypesSignalDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { QueryMode } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
export const definition: PanelDefinition<'signoz/ListPanel'> = {
|
||||
kind: 'signoz/ListPanel',
|
||||
@@ -19,13 +19,13 @@ export const definition: PanelDefinition<'signoz/ListPanel'> = {
|
||||
Renderer,
|
||||
EditorPane: ListEditorPane,
|
||||
// Raw records come from logs and traces; metrics don't produce row data.
|
||||
supportedQueryModesKindMap: {
|
||||
[QueryMode.QUERY_BUILDER]: {
|
||||
kind: QueryModeKind.SIGNAL,
|
||||
supportedQueryTypesKindMap: {
|
||||
[EQueryType.QUERY_BUILDER]: {
|
||||
kind: QueryTypeKind.SIGNAL,
|
||||
signals: [TelemetrytypesSignalDTO.logs, TelemetrytypesSignalDTO.traces],
|
||||
},
|
||||
[QueryMode.AI_QUERY_BUILDER]: {
|
||||
kind: QueryModeKind.SIGNAL,
|
||||
[EQueryType.AI_QUERY_BUILDER]: {
|
||||
kind: QueryTypeKind.SIGNAL,
|
||||
signals: [TelemetrytypesSignalDTO.traces],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { Hash } from '@signozhq/icons';
|
||||
|
||||
import type { PanelDefinition } from '../../types/panelDefinition';
|
||||
import { QueryModeKind } from '../../types/panelCapabilities';
|
||||
import { QueryTypeKind } from '../../types/panelCapabilities';
|
||||
import QueryBuilderEditorPane from 'pages/DashboardPage/DashboardContainer/PanelEditor/PanelEditorQueryBuilder/QueryBuilderEditorPane';
|
||||
import Renderer from './Renderer';
|
||||
import { sections } from './sections';
|
||||
@@ -9,7 +10,6 @@ import {
|
||||
Querybuildertypesv5RequestTypeDTO,
|
||||
TelemetrytypesSignalDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { QueryMode } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
export const definition: PanelDefinition<'signoz/NumberPanel'> = {
|
||||
kind: 'signoz/NumberPanel',
|
||||
@@ -19,19 +19,19 @@ export const definition: PanelDefinition<'signoz/NumberPanel'> = {
|
||||
Renderer,
|
||||
EditorPane: QueryBuilderEditorPane,
|
||||
sections,
|
||||
supportedQueryModesKindMap: {
|
||||
[QueryMode.QUERY_BUILDER]: {
|
||||
kind: QueryModeKind.SIGNAL,
|
||||
supportedQueryTypesKindMap: {
|
||||
[EQueryType.QUERY_BUILDER]: {
|
||||
kind: QueryTypeKind.SIGNAL,
|
||||
signals: [
|
||||
TelemetrytypesSignalDTO.metrics,
|
||||
TelemetrytypesSignalDTO.logs,
|
||||
TelemetrytypesSignalDTO.traces,
|
||||
],
|
||||
},
|
||||
[QueryMode.CLICKHOUSE]: { kind: QueryModeKind.SIGNAL_LESS },
|
||||
[QueryMode.PROM]: { kind: QueryModeKind.SIGNAL_LESS },
|
||||
[QueryMode.AI_QUERY_BUILDER]: {
|
||||
kind: QueryModeKind.SIGNAL,
|
||||
[EQueryType.CLICKHOUSE]: { kind: QueryTypeKind.SIGNAL_LESS },
|
||||
[EQueryType.PROM]: { kind: QueryTypeKind.SIGNAL_LESS },
|
||||
[EQueryType.AI_QUERY_BUILDER]: {
|
||||
kind: QueryTypeKind.SIGNAL,
|
||||
signals: [TelemetrytypesSignalDTO.traces],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { ChartPie } from '@signozhq/icons';
|
||||
|
||||
import type { PanelDefinition } from '../../types/panelDefinition';
|
||||
import { QueryModeKind } from '../../types/panelCapabilities';
|
||||
import { QueryTypeKind } from '../../types/panelCapabilities';
|
||||
import QueryBuilderEditorPane from 'pages/DashboardPage/DashboardContainer/PanelEditor/PanelEditorQueryBuilder/QueryBuilderEditorPane';
|
||||
import Renderer from './Renderer';
|
||||
import { sections } from './sections';
|
||||
@@ -9,7 +10,6 @@ import {
|
||||
Querybuildertypesv5RequestTypeDTO,
|
||||
TelemetrytypesSignalDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { QueryMode } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
export const definition: PanelDefinition<'signoz/PieChartPanel'> = {
|
||||
kind: 'signoz/PieChartPanel',
|
||||
@@ -19,18 +19,18 @@ export const definition: PanelDefinition<'signoz/PieChartPanel'> = {
|
||||
Renderer,
|
||||
EditorPane: QueryBuilderEditorPane,
|
||||
sections,
|
||||
supportedQueryModesKindMap: {
|
||||
[QueryMode.QUERY_BUILDER]: {
|
||||
kind: QueryModeKind.SIGNAL,
|
||||
supportedQueryTypesKindMap: {
|
||||
[EQueryType.QUERY_BUILDER]: {
|
||||
kind: QueryTypeKind.SIGNAL,
|
||||
signals: [
|
||||
TelemetrytypesSignalDTO.metrics,
|
||||
TelemetrytypesSignalDTO.logs,
|
||||
TelemetrytypesSignalDTO.traces,
|
||||
],
|
||||
},
|
||||
[QueryMode.CLICKHOUSE]: { kind: QueryModeKind.SIGNAL_LESS },
|
||||
[QueryMode.AI_QUERY_BUILDER]: {
|
||||
kind: QueryModeKind.SIGNAL,
|
||||
[EQueryType.CLICKHOUSE]: { kind: QueryTypeKind.SIGNAL_LESS },
|
||||
[EQueryType.AI_QUERY_BUILDER]: {
|
||||
kind: QueryTypeKind.SIGNAL,
|
||||
signals: [TelemetrytypesSignalDTO.traces],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ChartScatter } from '@signozhq/icons';
|
||||
|
||||
import type { PanelDefinition } from '../../types/panelDefinition';
|
||||
import { QueryModeKind } from '../../types/panelCapabilities';
|
||||
import { QueryTypeKind } from '../../types/panelCapabilities';
|
||||
import QueryBuilderEditorPane from 'pages/DashboardPage/DashboardContainer/PanelEditor/PanelEditorQueryBuilder/QueryBuilderEditorPane';
|
||||
import Renderer from './Renderer';
|
||||
import { MAX_PLOTTED_GROUPS } from './utils/prepareData';
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
Querybuildertypesv5RequestTypeDTO,
|
||||
TelemetrytypesSignalDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { QueryMode } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { QueryBuilderField } from 'components/QueryBuilderV2/queryBuilderFields.types';
|
||||
|
||||
export const definition: PanelDefinition<'signoz/ScatterPlotPanel'> = {
|
||||
@@ -23,16 +23,16 @@ export const definition: PanelDefinition<'signoz/ScatterPlotPanel'> = {
|
||||
Renderer,
|
||||
EditorPane: QueryBuilderEditorPane,
|
||||
sections,
|
||||
supportedQueryModesKindMap: {
|
||||
[QueryMode.QUERY_BUILDER]: {
|
||||
kind: QueryModeKind.SIGNAL,
|
||||
supportedQueryTypesKindMap: {
|
||||
[EQueryType.QUERY_BUILDER]: {
|
||||
kind: QueryTypeKind.SIGNAL,
|
||||
signals: [
|
||||
TelemetrytypesSignalDTO.metrics,
|
||||
TelemetrytypesSignalDTO.logs,
|
||||
TelemetrytypesSignalDTO.traces,
|
||||
],
|
||||
},
|
||||
[QueryMode.CLICKHOUSE]: { kind: QueryModeKind.SIGNAL_LESS },
|
||||
[EQueryType.CLICKHOUSE]: { kind: QueryTypeKind.SIGNAL_LESS },
|
||||
},
|
||||
queryBuilderFields: {
|
||||
// Pinned so it opens empty: the group-by template a click would seed names nothing here.
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { Table } from '@signozhq/icons';
|
||||
|
||||
import type { PanelDefinition } from '../../types/panelDefinition';
|
||||
import { QueryModeKind } from '../../types/panelCapabilities';
|
||||
import { QueryTypeKind } from '../../types/panelCapabilities';
|
||||
import QueryBuilderEditorPane from 'pages/DashboardPage/DashboardContainer/PanelEditor/PanelEditorQueryBuilder/QueryBuilderEditorPane';
|
||||
import Renderer from './Renderer';
|
||||
import { sections } from './sections';
|
||||
@@ -9,7 +10,6 @@ import {
|
||||
Querybuildertypesv5RequestTypeDTO,
|
||||
TelemetrytypesSignalDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { QueryMode } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
export const definition: PanelDefinition<'signoz/TablePanel'> = {
|
||||
kind: 'signoz/TablePanel',
|
||||
@@ -19,18 +19,18 @@ export const definition: PanelDefinition<'signoz/TablePanel'> = {
|
||||
Renderer,
|
||||
EditorPane: QueryBuilderEditorPane,
|
||||
sections,
|
||||
supportedQueryModesKindMap: {
|
||||
[QueryMode.QUERY_BUILDER]: {
|
||||
kind: QueryModeKind.SIGNAL,
|
||||
supportedQueryTypesKindMap: {
|
||||
[EQueryType.QUERY_BUILDER]: {
|
||||
kind: QueryTypeKind.SIGNAL,
|
||||
signals: [
|
||||
TelemetrytypesSignalDTO.metrics,
|
||||
TelemetrytypesSignalDTO.logs,
|
||||
TelemetrytypesSignalDTO.traces,
|
||||
],
|
||||
},
|
||||
[QueryMode.CLICKHOUSE]: { kind: QueryModeKind.SIGNAL_LESS },
|
||||
[QueryMode.AI_QUERY_BUILDER]: {
|
||||
kind: QueryModeKind.SIGNAL,
|
||||
[EQueryType.CLICKHOUSE]: { kind: QueryTypeKind.SIGNAL_LESS },
|
||||
[EQueryType.AI_QUERY_BUILDER]: {
|
||||
kind: QueryTypeKind.SIGNAL,
|
||||
signals: [TelemetrytypesSignalDTO.traces],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { ChartLine } from '@signozhq/icons';
|
||||
|
||||
import type { PanelDefinition } from '../../types/panelDefinition';
|
||||
import { QueryModeKind } from '../../types/panelCapabilities';
|
||||
import { QueryTypeKind } from '../../types/panelCapabilities';
|
||||
import QueryBuilderEditorPane from 'pages/DashboardPage/DashboardContainer/PanelEditor/PanelEditorQueryBuilder/QueryBuilderEditorPane';
|
||||
import Renderer from './Renderer';
|
||||
import { sections } from './sections';
|
||||
@@ -9,7 +10,6 @@ import {
|
||||
Querybuildertypesv5RequestTypeDTO,
|
||||
TelemetrytypesSignalDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { QueryMode } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
export const definition: PanelDefinition<'signoz/TimeSeriesPanel'> = {
|
||||
kind: 'signoz/TimeSeriesPanel',
|
||||
@@ -19,19 +19,19 @@ export const definition: PanelDefinition<'signoz/TimeSeriesPanel'> = {
|
||||
Renderer,
|
||||
EditorPane: QueryBuilderEditorPane,
|
||||
sections,
|
||||
supportedQueryModesKindMap: {
|
||||
[QueryMode.QUERY_BUILDER]: {
|
||||
kind: QueryModeKind.SIGNAL,
|
||||
supportedQueryTypesKindMap: {
|
||||
[EQueryType.QUERY_BUILDER]: {
|
||||
kind: QueryTypeKind.SIGNAL,
|
||||
signals: [
|
||||
TelemetrytypesSignalDTO.metrics,
|
||||
TelemetrytypesSignalDTO.logs,
|
||||
TelemetrytypesSignalDTO.traces,
|
||||
],
|
||||
},
|
||||
[QueryMode.CLICKHOUSE]: { kind: QueryModeKind.SIGNAL_LESS },
|
||||
[QueryMode.PROM]: { kind: QueryModeKind.SIGNAL_LESS },
|
||||
[QueryMode.AI_QUERY_BUILDER]: {
|
||||
kind: QueryModeKind.SIGNAL,
|
||||
[EQueryType.CLICKHOUSE]: { kind: QueryTypeKind.SIGNAL_LESS },
|
||||
[EQueryType.PROM]: { kind: QueryTypeKind.SIGNAL_LESS },
|
||||
[EQueryType.AI_QUERY_BUILDER]: {
|
||||
kind: QueryTypeKind.SIGNAL,
|
||||
signals: [TelemetrytypesSignalDTO.traces],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -25,7 +25,7 @@ export const UNSUPPORTED_PANEL: RenderablePanelDefinition = {
|
||||
Renderer,
|
||||
EditorPane: QueryBuilderEditorPane,
|
||||
sections: [],
|
||||
supportedQueryModesKindMap: {},
|
||||
supportedQueryTypesKindMap: {},
|
||||
queryBuilderFields: {},
|
||||
queryCapabilities: {
|
||||
requestType: Querybuildertypesv5RequestTypeDTO.time_series,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import {
|
||||
Querybuildertypesv5RequestTypeDTO,
|
||||
type TelemetrytypesSignalDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import type { QueryMode } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
export type { QueryBuilderFieldsConfig } from 'components/QueryBuilderV2/queryBuilderFields.types';
|
||||
|
||||
@@ -44,24 +44,24 @@ export function isRawRequest(capabilities: PanelQueryCapabilities): boolean {
|
||||
return capabilities.requestType === Querybuildertypesv5RequestTypeDTO.raw;
|
||||
}
|
||||
|
||||
export enum QueryModeKind {
|
||||
export enum QueryTypeKind {
|
||||
SIGNAL = 'signal',
|
||||
SIGNAL_LESS = 'signal-less',
|
||||
}
|
||||
|
||||
export type QueryModeCapability =
|
||||
| { kind: QueryModeKind.SIGNAL; signals: TelemetrytypesSignalDTO[] }
|
||||
| { kind: QueryModeKind.SIGNAL_LESS };
|
||||
export type QueryTypeCapability =
|
||||
| { kind: QueryTypeKind.SIGNAL; signals: TelemetrytypesSignalDTO[] }
|
||||
| { kind: QueryTypeKind.SIGNAL_LESS };
|
||||
|
||||
export type SupportedQueryModesKindMap = Partial<
|
||||
Record<QueryMode, QueryModeCapability>
|
||||
export type SupportedQueryTypesKindMap = Partial<
|
||||
Record<EQueryType, QueryTypeCapability>
|
||||
>;
|
||||
|
||||
/** Signals `mode` accepts; empty for a signal-less or undeclared mode. */
|
||||
export function getQueryModeSignals(
|
||||
modes: SupportedQueryModesKindMap | undefined,
|
||||
mode: QueryMode,
|
||||
/** Signals `queryType` accepts; empty for a signal-less or undeclared query type. */
|
||||
export function getQueryTypeSignals(
|
||||
queryTypes: SupportedQueryTypesKindMap | undefined,
|
||||
queryType: EQueryType,
|
||||
): TelemetrytypesSignalDTO[] {
|
||||
const capability = modes?.[mode];
|
||||
return capability?.kind === QueryModeKind.SIGNAL ? capability.signals : [];
|
||||
const capability = queryTypes?.[queryType];
|
||||
return capability?.kind === QueryTypeKind.SIGNAL ? capability.signals : [];
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import type { SyncedQueryBuilderField } from 'components/QueryBuilderV2/queryBui
|
||||
import type {
|
||||
PanelQueryCapabilities,
|
||||
QueryBuilderFieldsConfig,
|
||||
SupportedQueryModesKindMap,
|
||||
SupportedQueryTypesKindMap,
|
||||
} from './panelCapabilities';
|
||||
import type {
|
||||
BaseRendererProps,
|
||||
@@ -122,7 +122,7 @@ export interface QueryPanelDefinition<
|
||||
/** Lower editor pane — the shared query-builder pane, or a kind wrapper of it. */
|
||||
EditorPane: ComponentType<QueryEditorPaneProps>;
|
||||
/** Authoring modes (Query Builder / ClickHouse / PromQL / AI) and the signals each accepts. */
|
||||
supportedQueryModesKindMap: SupportedQueryModesKindMap;
|
||||
supportedQueryTypesKindMap: SupportedQueryTypesKindMap;
|
||||
queryBuilderFields: QueryBuilderFieldsConfig;
|
||||
/** How this kind's query-range request is shaped (request type, paging, result formatting). */
|
||||
queryCapabilities: PanelQueryCapabilities;
|
||||
|
||||
@@ -5,14 +5,14 @@ import {
|
||||
type PANEL_TYPES,
|
||||
} from 'constants/queryBuilder';
|
||||
import type { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { QueryMode } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import {
|
||||
DataSource,
|
||||
type QueryBuilderContextType,
|
||||
} from 'types/common/queryBuilder';
|
||||
|
||||
interface SeedBuilderForModeArgs {
|
||||
mode: QueryMode;
|
||||
mode: EQueryType;
|
||||
/** The kind's first Query Builder signal; what a fresh panel of this kind starts on. */
|
||||
defaultSignal?: TelemetrytypesSignalDTO;
|
||||
panelType: PANEL_TYPES;
|
||||
@@ -26,7 +26,7 @@ export function seedBuilderForMode({
|
||||
panelType,
|
||||
updateAllQueriesOperators,
|
||||
}: SeedBuilderForModeArgs): Query['builder'] {
|
||||
if (mode === QueryMode.AI_QUERY_BUILDER) {
|
||||
if (mode === EQueryType.AI_QUERY_BUILDER) {
|
||||
return updateAllQueriesOperators(
|
||||
initialQueryAIWithType,
|
||||
panelType,
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import {
|
||||
Querybuildertypesv5BucketOptionsDTO,
|
||||
Querybuildertypesv5QueryTypeDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { Querybuildertypesv5BucketOptionsDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { TelemetryFieldKey } from 'api/v5/v5';
|
||||
import { Format } from 'constants/formats/types';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
@@ -23,14 +20,6 @@ import {
|
||||
} from '../v5/queryRange';
|
||||
import { BaseAutocompleteData } from './queryAutocompleteResponse';
|
||||
|
||||
export const QueryMode = {
|
||||
...EQueryType,
|
||||
/** Not an `EQueryType`: AI queries are builder queries tagged `builderQueryType: builder_ai_query`. */
|
||||
AI_QUERY_BUILDER: Querybuildertypesv5QueryTypeDTO.builder_ai_query,
|
||||
} as const;
|
||||
|
||||
export type QueryMode = (typeof QueryMode)[keyof typeof QueryMode];
|
||||
|
||||
// Type for Formula
|
||||
export interface IBuilderFormula {
|
||||
expression: string;
|
||||
|
||||
@@ -2,4 +2,5 @@ export enum EQueryType {
|
||||
QUERY_BUILDER = 'builder',
|
||||
CLICKHOUSE = 'clickhouse_sql',
|
||||
PROM = 'promql',
|
||||
AI_QUERY_BUILDER = 'builder_ai_query',
|
||||
}
|
||||
|
||||
@@ -26,9 +26,8 @@ const bucketCacheCellSteps = 60
|
||||
type BucketCache interface {
|
||||
// cached portion + list of gaps to fetch
|
||||
GetMissRanges(ctx context.Context, orgID valuer.UUID, q qbtypes.Query, step qbtypes.Step) (cached *qbtypes.Result, missing []*qbtypes.TimeRange)
|
||||
// store fresh buckets for future hits; only the ranges in the map were
|
||||
// queried for fresh, the rest of it came from the cache
|
||||
Put(ctx context.Context, orgID valuer.UUID, q qbtypes.Query, step qbtypes.Step, fresh *qbtypes.Result, execStatsForQueriedTimeRanges map[qbtypes.TimeRange]qbtypes.ExecStats)
|
||||
// store fresh buckets for future hits
|
||||
Put(ctx context.Context, orgID valuer.UUID, q qbtypes.Query, step qbtypes.Step, fresh *qbtypes.Result)
|
||||
}
|
||||
|
||||
// bucketCache implements the BucketCache interface.
|
||||
@@ -117,7 +116,7 @@ func (bc *bucketCache) GetMissRanges(
|
||||
}
|
||||
|
||||
// Merge buckets into a single result
|
||||
mergedResult := bc.mergeBuckets(ctx, relevantBuckets, cachedWarnings, startMs, endMs)
|
||||
mergedResult := bc.mergeBuckets(ctx, relevantBuckets, cachedWarnings)
|
||||
|
||||
// Filter the merged result to only include values within the requested time range
|
||||
mergedResult = bc.filterResultToTimeRange(mergedResult, q, startMs, endMs, stepMs)
|
||||
@@ -126,7 +125,7 @@ func (bc *bucketCache) GetMissRanges(
|
||||
}
|
||||
|
||||
// Put stores fresh query results in the cache.
|
||||
func (bc *bucketCache) Put(ctx context.Context, orgID valuer.UUID, q qbtypes.Query, step qbtypes.Step, fresh *qbtypes.Result, execStatsForQueriedTimeRanges map[qbtypes.TimeRange]qbtypes.ExecStats) {
|
||||
func (bc *bucketCache) Put(ctx context.Context, orgID valuer.UUID, q qbtypes.Query, step qbtypes.Step, fresh *qbtypes.Result) {
|
||||
// Get query window
|
||||
startMs, endMs := q.Window()
|
||||
|
||||
@@ -189,7 +188,7 @@ func (bc *bucketCache) Put(ctx context.Context, orgID valuer.UUID, q qbtypes.Que
|
||||
|
||||
// Convert trimmed result to buckets, one per grid cell it spans
|
||||
cellWidthMs := bucketCacheCellSteps * stepMs
|
||||
freshBuckets := bc.resultToBuckets(ctx, trimmedResult, cachableStartMs, cachableEndMs, cellWidthMs, execStatsForQueriedTimeRanges)
|
||||
freshBuckets := bc.resultToBuckets(ctx, trimmedResult, cachableStartMs, cachableEndMs, cellWidthMs)
|
||||
|
||||
cellKeys := make([]string, len(freshBuckets))
|
||||
cellEntries := make([]cachetypes.Cacheable, len(freshBuckets))
|
||||
@@ -211,7 +210,7 @@ func (bc *bucketCache) Put(ctx context.Context, orgID valuer.UUID, q qbtypes.Que
|
||||
cellEntry = cellEntries[i].(*qbtypes.CachedData)
|
||||
}
|
||||
|
||||
cellBuckets, changed := bc.mergeFreshBucketIntoCell(ctx, cellEntry.Buckets, freshBucket, execStatsForQueriedTimeRanges)
|
||||
cellBuckets, changed := bc.mergeFreshBucketIntoCell(ctx, cellEntry.Buckets, freshBucket)
|
||||
if !changed {
|
||||
continue
|
||||
}
|
||||
@@ -464,7 +463,7 @@ func (bc *bucketCache) filterRelevantBuckets(buckets []*qbtypes.CachedBucket, st
|
||||
}
|
||||
|
||||
// mergeBuckets combines multiple cached buckets into a single result.
|
||||
func (bc *bucketCache) mergeBuckets(ctx context.Context, buckets []*qbtypes.CachedBucket, warnings []string, startMs, endMs uint64) *qbtypes.Result {
|
||||
func (bc *bucketCache) mergeBuckets(ctx context.Context, buckets []*qbtypes.CachedBucket, warnings []string) *qbtypes.Result {
|
||||
if len(buckets) == 0 {
|
||||
return &qbtypes.Result{}
|
||||
}
|
||||
@@ -472,13 +471,12 @@ func (bc *bucketCache) mergeBuckets(ctx context.Context, buckets []*qbtypes.Cach
|
||||
// All buckets should have the same type
|
||||
resultType := buckets[0].Type
|
||||
|
||||
// A bucket can reach past the window; only the part inside it is served
|
||||
// Aggregate stats
|
||||
var totalStats qbtypes.ExecStats
|
||||
for _, bucket := range buckets {
|
||||
servedStats := bucket.ProrateStatsToWindow(startMs, endMs)
|
||||
totalStats.RowsScanned += servedStats.RowsScanned
|
||||
totalStats.BytesScanned += servedStats.BytesScanned
|
||||
totalStats.DurationMS += servedStats.DurationMS
|
||||
totalStats.RowsScanned += bucket.Stats.RowsScanned
|
||||
totalStats.BytesScanned += bucket.Stats.BytesScanned
|
||||
totalStats.DurationMS += bucket.Stats.DurationMS
|
||||
}
|
||||
|
||||
// Merge values based on type
|
||||
@@ -675,7 +673,7 @@ func (bc *bucketCache) isEmptyResult(result *qbtypes.Result) (isEmpty bool, isFi
|
||||
}
|
||||
|
||||
// resultToBuckets converts a query result into time-based buckets.
|
||||
func (bc *bucketCache) resultToBuckets(ctx context.Context, result *qbtypes.Result, startMs, endMs, cellWidthMs uint64, execStatsForQueriedTimeRanges map[qbtypes.TimeRange]qbtypes.ExecStats) []*qbtypes.CachedBucket {
|
||||
func (bc *bucketCache) resultToBuckets(ctx context.Context, result *qbtypes.Result, startMs, endMs, cellWidthMs uint64) []*qbtypes.CachedBucket {
|
||||
// Check if result is empty
|
||||
isEmpty, isFiltered := bc.isEmptyResult(result)
|
||||
|
||||
@@ -687,7 +685,7 @@ func (bc *bucketCache) resultToBuckets(ctx context.Context, result *qbtypes.Resu
|
||||
}
|
||||
|
||||
// Empty filtered results still become buckets, so the range is not re-queried
|
||||
perCellBuckets, err := result.SplitIntoCacheCellBuckets(startMs, endMs, cellWidthMs, execStatsForQueriedTimeRanges)
|
||||
perCellBuckets, err := result.SplitIntoCacheCellBuckets(startMs, endMs, cellWidthMs)
|
||||
if err != nil {
|
||||
bc.logger.ErrorContext(ctx, "failed to convert result to buckets", errors.Attr(err))
|
||||
return nil
|
||||
@@ -699,7 +697,7 @@ func (bc *bucketCache) resultToBuckets(ctx context.Context, result *qbtypes.Resu
|
||||
// and reports whether the cell changed: a bucket already covering the fresh
|
||||
// one leaves it as is, buckets inside the fresh one are replaced, buckets
|
||||
// overlapping or edge to edge with it are unioned, and the rest are kept apart.
|
||||
func (bc *bucketCache) mergeFreshBucketIntoCell(ctx context.Context, cellBuckets []*qbtypes.CachedBucket, freshBucket *qbtypes.CachedBucket, execStatsForQueriedTimeRanges map[qbtypes.TimeRange]qbtypes.ExecStats) ([]*qbtypes.CachedBucket, bool) {
|
||||
func (bc *bucketCache) mergeFreshBucketIntoCell(ctx context.Context, cellBuckets []*qbtypes.CachedBucket, freshBucket *qbtypes.CachedBucket) ([]*qbtypes.CachedBucket, bool) {
|
||||
keptBuckets := make([]*qbtypes.CachedBucket, 0, len(cellBuckets)+1)
|
||||
touchingBuckets := []*qbtypes.CachedBucket{}
|
||||
for _, cellBucket := range cellBuckets {
|
||||
@@ -712,7 +710,7 @@ func (bc *bucketCache) mergeFreshBucketIntoCell(ctx context.Context, cellBuckets
|
||||
keptBuckets = append(keptBuckets, cellBucket)
|
||||
}
|
||||
}
|
||||
keptBuckets = append(keptBuckets, bc.unionBuckets(ctx, freshBucket, touchingBuckets, execStatsForQueriedTimeRanges))
|
||||
keptBuckets = append(keptBuckets, bc.unionBuckets(ctx, freshBucket, touchingBuckets))
|
||||
|
||||
slices.SortFunc(keptBuckets, func(a, b *qbtypes.CachedBucket) int {
|
||||
return cmp.Compare(a.StartMs, b.StartMs)
|
||||
@@ -723,16 +721,7 @@ func (bc *bucketCache) mergeFreshBucketIntoCell(ctx context.Context, cellBuckets
|
||||
// unionBuckets widens the fresh bucket by the buckets it touches. Buckets
|
||||
// inside it are replaced outright, so only a genuine partial overlap decodes
|
||||
// anything.
|
||||
func (bc *bucketCache) unionBuckets(ctx context.Context, freshBucket *qbtypes.CachedBucket, touchingBuckets []*qbtypes.CachedBucket, execStatsForQueriedTimeRanges map[qbtypes.TimeRange]qbtypes.ExecStats) *qbtypes.CachedBucket {
|
||||
// Data queried again takes the new query's cost, which the fresh bucket
|
||||
// carries; a touching bucket keeps its cost only for what was not queried again
|
||||
for _, touchingBucket := range touchingBuckets {
|
||||
keptStats := touchingBucket.ProrateStatsToUnqueried(execStatsForQueriedTimeRanges)
|
||||
freshBucket.Stats.RowsScanned += keptStats.RowsScanned
|
||||
freshBucket.Stats.BytesScanned += keptStats.BytesScanned
|
||||
freshBucket.Stats.DurationMS += keptStats.DurationMS
|
||||
}
|
||||
|
||||
func (bc *bucketCache) unionBuckets(ctx context.Context, freshBucket *qbtypes.CachedBucket, touchingBuckets []*qbtypes.CachedBucket) *qbtypes.CachedBucket {
|
||||
bucketsToMerge := []*qbtypes.CachedBucket{freshBucket}
|
||||
for _, touchingBucket := range touchingBuckets {
|
||||
if !freshBucket.Contains(touchingBucket) {
|
||||
@@ -753,11 +742,14 @@ func (bc *bucketCache) unionBuckets(ctx context.Context, freshBucket *qbtypes.Ca
|
||||
EndMs: freshBucket.EndMs,
|
||||
Type: freshBucket.Type,
|
||||
Value: value,
|
||||
Stats: freshBucket.Stats,
|
||||
Stats: qbtypes.ExecStats{StepIntervals: freshBucket.Stats.StepIntervals},
|
||||
}
|
||||
for _, mergedBucket := range bucketsToMerge {
|
||||
unionBucket.StartMs = min(unionBucket.StartMs, mergedBucket.StartMs)
|
||||
unionBucket.EndMs = max(unionBucket.EndMs, mergedBucket.EndMs)
|
||||
unionBucket.Stats.RowsScanned += mergedBucket.Stats.RowsScanned
|
||||
unionBucket.Stats.BytesScanned += mergedBucket.Stats.BytesScanned
|
||||
unionBucket.Stats.DurationMS += mergedBucket.Stats.DurationMS
|
||||
}
|
||||
return unionBucket
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ func BenchmarkBucketCache_GetMissRanges(b *testing.B) {
|
||||
endMs: uint64((i + 1) * 10000),
|
||||
}
|
||||
result := createBenchmarkResult(query.startMs, query.endMs, 1000)
|
||||
bc.Put(ctx, orgID, query, qbtypes.Step{Duration: 1000 * time.Millisecond}, result, map[qbtypes.TimeRange]qbtypes.ExecStats{{From: query.startMs, To: query.endMs}: result.Stats})
|
||||
bc.Put(ctx, orgID, query, qbtypes.Step{Duration: 1000 * time.Millisecond}, result)
|
||||
}
|
||||
|
||||
// Create test queries with varying cache hit patterns
|
||||
@@ -121,7 +121,7 @@ func BenchmarkBucketCache_Put(b *testing.B) {
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
for j := 0; j < tc.numQueries; j++ {
|
||||
bc.Put(ctx, orgID, queries[j], qbtypes.Step{Duration: 1000 * time.Millisecond}, results[j], map[qbtypes.TimeRange]qbtypes.ExecStats{{From: queries[j].startMs, To: queries[j].endMs}: results[j].Stats})
|
||||
bc.Put(ctx, orgID, queries[j], qbtypes.Step{Duration: 1000 * time.Millisecond}, results[j])
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -259,7 +259,7 @@ func BenchmarkBucketCache_ConcurrentOperations(b *testing.B) {
|
||||
endMs: uint64((i + 1) * 10000),
|
||||
}
|
||||
result := createBenchmarkResult(query.startMs, query.endMs, 1000)
|
||||
bc.Put(ctx, orgID, query, qbtypes.Step{Duration: 1000 * time.Millisecond}, result, map[qbtypes.TimeRange]qbtypes.ExecStats{{From: query.startMs, To: query.endMs}: result.Stats})
|
||||
bc.Put(ctx, orgID, query, qbtypes.Step{Duration: 1000 * time.Millisecond}, result)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
@@ -284,7 +284,7 @@ func BenchmarkBucketCache_ConcurrentOperations(b *testing.B) {
|
||||
endMs: uint64((i + 1) * 10000),
|
||||
}
|
||||
result := createBenchmarkResult(query.startMs, query.endMs, 1000)
|
||||
bc.Put(ctx, orgID, query, qbtypes.Step{Duration: 1000 * time.Millisecond}, result, map[qbtypes.TimeRange]qbtypes.ExecStats{{From: query.startMs, To: query.endMs}: result.Stats})
|
||||
bc.Put(ctx, orgID, query, qbtypes.Step{Duration: 1000 * time.Millisecond}, result)
|
||||
case 2: // Partial read
|
||||
query := &mockQuery{
|
||||
fingerprint: fmt.Sprintf("concurrent-query-%d", i%100),
|
||||
|
||||
@@ -59,7 +59,7 @@ func TestBucketCacheStepAlignment(t *testing.T) {
|
||||
}
|
||||
|
||||
// Put result in cache
|
||||
bc.Put(ctx, orgID, query, step, result, nil)
|
||||
bc.Put(ctx, orgID, query, step, result)
|
||||
|
||||
// Get cached data
|
||||
cached, missing := bc.GetMissRanges(ctx, orgID, query, step)
|
||||
@@ -109,7 +109,7 @@ func TestBucketCacheNoStepInterval(t *testing.T) {
|
||||
}
|
||||
|
||||
// Should cache the entire range when step is 0
|
||||
bc.Put(ctx, orgID, query, step, result, nil)
|
||||
bc.Put(ctx, orgID, query, step, result)
|
||||
|
||||
cached, missing := bc.GetMissRanges(ctx, orgID, query, step)
|
||||
assert.NotNil(t, cached)
|
||||
|
||||
@@ -156,7 +156,7 @@ func TestBucketCache_Put_And_Get(t *testing.T) {
|
||||
}
|
||||
|
||||
// Store in cache
|
||||
bc.Put(context.Background(), valuer.UUID{}, query, qbtypes.Step{Duration: 1000 * time.Millisecond}, result, map[qbtypes.TimeRange]qbtypes.ExecStats{{From: query.startMs, To: query.endMs}: result.Stats})
|
||||
bc.Put(context.Background(), valuer.UUID{}, query, qbtypes.Step{Duration: 1000 * time.Millisecond}, result)
|
||||
|
||||
// Wait a bit for cache to be written
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
@@ -189,7 +189,7 @@ func TestBucketCache_PartialHit(t *testing.T) {
|
||||
Type: qbtypes.RequestTypeTimeSeries,
|
||||
Value: createTestTimeSeries("A", 1000, 3000, 1000),
|
||||
}
|
||||
bc.Put(context.Background(), valuer.UUID{}, query1, qbtypes.Step{Duration: 1000 * time.Millisecond}, result1, nil)
|
||||
bc.Put(context.Background(), valuer.UUID{}, query1, qbtypes.Step{Duration: 1000 * time.Millisecond}, result1)
|
||||
|
||||
// Wait for cache write
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
@@ -225,7 +225,7 @@ func TestBucketCache_MultipleBuckets(t *testing.T) {
|
||||
bc.Put(context.Background(), valuer.UUID{}, query1, qbtypes.Step{Duration: 100 * time.Millisecond}, &qbtypes.Result{
|
||||
Type: qbtypes.RequestTypeTimeSeries,
|
||||
Value: createTestTimeSeries("A", 1000, 2000, 100),
|
||||
}, nil)
|
||||
})
|
||||
|
||||
query2 := &mockQuery{
|
||||
fingerprint: "test-query",
|
||||
@@ -235,7 +235,7 @@ func TestBucketCache_MultipleBuckets(t *testing.T) {
|
||||
bc.Put(context.Background(), valuer.UUID{}, query2, qbtypes.Step{Duration: 100 * time.Millisecond}, &qbtypes.Result{
|
||||
Type: qbtypes.RequestTypeTimeSeries,
|
||||
Value: createTestTimeSeries("A", 3000, 4000, 100),
|
||||
}, nil)
|
||||
})
|
||||
|
||||
// Wait for cache writes
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
@@ -280,7 +280,7 @@ func TestBucketCache_FluxInterval(t *testing.T) {
|
||||
}
|
||||
|
||||
// This should not be cached due to flux interval
|
||||
bc.Put(context.Background(), valuer.UUID{}, query, qbtypes.Step{Duration: 1000 * time.Millisecond}, result, nil)
|
||||
bc.Put(context.Background(), valuer.UUID{}, query, qbtypes.Step{Duration: 1000 * time.Millisecond}, result)
|
||||
|
||||
// Wait a bit
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
@@ -358,7 +358,7 @@ func TestBucketCache_MergeTimeSeriesResults(t *testing.T) {
|
||||
{Series: []*qbtypes.TimeSeries{series1}},
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
})
|
||||
|
||||
// Cache second part
|
||||
query2 := &mockQuery{
|
||||
@@ -374,7 +374,7 @@ func TestBucketCache_MergeTimeSeriesResults(t *testing.T) {
|
||||
{Series: []*qbtypes.TimeSeries{series2}},
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
})
|
||||
|
||||
// Wait for cache writes
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
@@ -441,7 +441,7 @@ func TestBucketCache_RawData(t *testing.T) {
|
||||
Value: rawData,
|
||||
}
|
||||
|
||||
bc.Put(context.Background(), valuer.UUID{}, query, qbtypes.Step{Duration: 1000 * time.Millisecond}, result, nil)
|
||||
bc.Put(context.Background(), valuer.UUID{}, query, qbtypes.Step{Duration: 1000 * time.Millisecond}, result)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
cached, missing := bc.GetMissRanges(context.Background(), valuer.UUID{}, query, qbtypes.Step{Duration: 1000 * time.Millisecond})
|
||||
@@ -481,7 +481,7 @@ func TestBucketCache_ScalarData(t *testing.T) {
|
||||
Value: scalarData,
|
||||
}
|
||||
|
||||
bc.Put(context.Background(), valuer.UUID{}, query, qbtypes.Step{Duration: 1000 * time.Millisecond}, result, nil)
|
||||
bc.Put(context.Background(), valuer.UUID{}, query, qbtypes.Step{Duration: 1000 * time.Millisecond}, result)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
cached, missing := bc.GetMissRanges(context.Background(), valuer.UUID{}, query, qbtypes.Step{Duration: 1000 * time.Millisecond})
|
||||
@@ -509,7 +509,7 @@ func TestBucketCache_EmptyFingerprint(t *testing.T) {
|
||||
Value: createTestTimeSeries("A", 1000, 5000, 1000),
|
||||
}
|
||||
|
||||
bc.Put(context.Background(), valuer.UUID{}, query, qbtypes.Step{Duration: 1000 * time.Millisecond}, result, nil)
|
||||
bc.Put(context.Background(), valuer.UUID{}, query, qbtypes.Step{Duration: 1000 * time.Millisecond}, result)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
// Should still be able to retrieve
|
||||
@@ -564,7 +564,7 @@ func TestBucketCache_ConcurrentAccess(t *testing.T) {
|
||||
Type: qbtypes.RequestTypeTimeSeries,
|
||||
Value: createTestTimeSeries(fmt.Sprintf("Q%d", id), query.startMs, query.endMs, 100),
|
||||
}
|
||||
bc.Put(context.Background(), valuer.UUID{}, query, qbtypes.Step{Duration: 100 * time.Microsecond}, result, nil)
|
||||
bc.Put(context.Background(), valuer.UUID{}, query, qbtypes.Step{Duration: 100 * time.Microsecond}, result)
|
||||
done <- true
|
||||
}(i)
|
||||
}
|
||||
@@ -624,7 +624,7 @@ func TestBucketCache_GetMissRanges_FluxInterval(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
bc.Put(ctx, orgID, query, qbtypes.Step{Duration: 1000 * time.Millisecond}, cachedResult, nil)
|
||||
bc.Put(ctx, orgID, query, qbtypes.Step{Duration: 1000 * time.Millisecond}, cachedResult)
|
||||
|
||||
// Get miss ranges
|
||||
cached, missing := bc.GetMissRanges(ctx, orgID, query, qbtypes.Step{Duration: 1000 * time.Millisecond})
|
||||
@@ -686,7 +686,7 @@ func TestBucketCache_Put_FluxIntervalTrimming(t *testing.T) {
|
||||
}
|
||||
|
||||
// Put the result
|
||||
bc.Put(ctx, orgID, query, qbtypes.Step{Duration: 1000 * time.Millisecond}, result, map[qbtypes.TimeRange]qbtypes.ExecStats{{From: query.startMs, To: query.endMs}: result.Stats})
|
||||
bc.Put(ctx, orgID, query, qbtypes.Step{Duration: 1000 * time.Millisecond}, result)
|
||||
|
||||
// Retrieve cached data
|
||||
cached, missing := bc.GetMissRanges(ctx, orgID, query, qbtypes.Step{Duration: 1000 * time.Millisecond})
|
||||
@@ -756,7 +756,7 @@ func TestBucketCache_Put_EntireRangeInFluxInterval(t *testing.T) {
|
||||
}
|
||||
|
||||
// Put the result - should not cache anything
|
||||
bc.Put(ctx, orgID, query, qbtypes.Step{Duration: 1000 * time.Millisecond}, result, nil)
|
||||
bc.Put(ctx, orgID, query, qbtypes.Step{Duration: 1000 * time.Millisecond}, result)
|
||||
|
||||
// Try to get cached data - should have no cached data
|
||||
cached, missing := bc.GetMissRanges(ctx, orgID, query, qbtypes.Step{Duration: 1000 * time.Millisecond})
|
||||
@@ -862,7 +862,7 @@ func TestBucketCache_EmptyDataHandling(t *testing.T) {
|
||||
}
|
||||
|
||||
// Put the result
|
||||
bc.Put(ctx, orgID, query, qbtypes.Step{Duration: 1000 * time.Millisecond}, tt.result, nil)
|
||||
bc.Put(ctx, orgID, query, qbtypes.Step{Duration: 1000 * time.Millisecond}, tt.result)
|
||||
|
||||
// Wait a bit for cache to be written
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
@@ -927,7 +927,7 @@ func TestBucketCache_PartialValues(t *testing.T) {
|
||||
}
|
||||
|
||||
// Put the result
|
||||
bc.Put(ctx, orgID, query, qbtypes.Step{Duration: 1000 * time.Millisecond}, result, map[qbtypes.TimeRange]qbtypes.ExecStats{{From: query.startMs, To: query.endMs}: result.Stats})
|
||||
bc.Put(ctx, orgID, query, qbtypes.Step{Duration: 1000 * time.Millisecond}, result)
|
||||
|
||||
// Wait for cache to be written
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
@@ -997,7 +997,7 @@ func TestBucketCache_AllPartialValues(t *testing.T) {
|
||||
}
|
||||
|
||||
// Put the result
|
||||
bc.Put(ctx, orgID, query, qbtypes.Step{Duration: 1000 * time.Millisecond}, result, nil)
|
||||
bc.Put(ctx, orgID, query, qbtypes.Step{Duration: 1000 * time.Millisecond}, result)
|
||||
|
||||
// Wait for cache to be written
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
@@ -1058,7 +1058,7 @@ func TestBucketCache_FilteredCachedResults(t *testing.T) {
|
||||
}
|
||||
|
||||
// Cache the wide range
|
||||
bc.Put(ctx, orgID, query1, qbtypes.Step{Duration: 1000 * time.Millisecond}, result1, nil)
|
||||
bc.Put(ctx, orgID, query1, qbtypes.Step{Duration: 1000 * time.Millisecond}, result1)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
// Now query for a smaller range (2000-3500ms)
|
||||
@@ -1125,7 +1125,7 @@ func TestBucketCache_PromQLKeepsTheValueAtTheWindowEnd(t *testing.T) {
|
||||
}},
|
||||
}},
|
||||
},
|
||||
}, nil)
|
||||
})
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
cached, missing := bc.GetMissRanges(ctx, orgID, query, step)
|
||||
@@ -1291,7 +1291,7 @@ func TestBucketCache_PartialValueDetection(t *testing.T) {
|
||||
}
|
||||
|
||||
// Put the result
|
||||
bc.Put(ctx, orgID, query, qbtypes.Step{Duration: 1000 * time.Millisecond}, result, nil)
|
||||
bc.Put(ctx, orgID, query, qbtypes.Step{Duration: 1000 * time.Millisecond}, result)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
// Get cached data
|
||||
@@ -1345,7 +1345,7 @@ func TestBucketCache_PartialValueDetection(t *testing.T) {
|
||||
}
|
||||
|
||||
// Put the result
|
||||
bc.Put(ctx, orgID, query, qbtypes.Step{Duration: 1000 * time.Millisecond}, result, nil)
|
||||
bc.Put(ctx, orgID, query, qbtypes.Step{Duration: 1000 * time.Millisecond}, result)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
// Get cached data
|
||||
@@ -1397,7 +1397,7 @@ func TestBucketCache_PartialValueDetection(t *testing.T) {
|
||||
}
|
||||
|
||||
// Put the result
|
||||
bc.Put(ctx, orgID, query, qbtypes.Step{Duration: 1000 * time.Millisecond}, result, nil)
|
||||
bc.Put(ctx, orgID, query, qbtypes.Step{Duration: 1000 * time.Millisecond}, result)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
// Get cached data
|
||||
@@ -1454,7 +1454,7 @@ func TestBucketCache_NoCache(t *testing.T) {
|
||||
}
|
||||
|
||||
// Put the result in cache
|
||||
bc.Put(ctx, orgID, query, qbtypes.Step{Duration: 1000 * time.Millisecond}, result, nil)
|
||||
bc.Put(ctx, orgID, query, qbtypes.Step{Duration: 1000 * time.Millisecond}, result)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
// Verify data is cached
|
||||
@@ -1475,12 +1475,11 @@ func TestBucketCachePutSlicesAtCellBoundaries(t *testing.T) {
|
||||
|
||||
// a 1s step gives 60s cells, so 30s to 150s spans three of them
|
||||
query := &mockQuery{fingerprint: "sliced-query", startMs: 30000, endMs: 150000}
|
||||
result := &qbtypes.Result{
|
||||
bc.Put(ctx, orgID, query, step, &qbtypes.Result{
|
||||
Type: qbtypes.RequestTypeTimeSeries,
|
||||
Value: createTestTimeSeries("A", 30000, 150000, 1000),
|
||||
Stats: qbtypes.ExecStats{RowsScanned: 120, BytesScanned: 1200, DurationMS: 12},
|
||||
}
|
||||
bc.Put(ctx, orgID, query, step, result, map[qbtypes.TimeRange]qbtypes.ExecStats{{From: 30000, To: 150000}: result.Stats})
|
||||
})
|
||||
|
||||
type bucketShape struct {
|
||||
cell, startMs, endMs, rowsScanned uint64
|
||||
@@ -1540,7 +1539,7 @@ func TestBucketCachePutAbsorbsWritesWithinACell(t *testing.T) {
|
||||
name: "OverlappingWrites_BecomeOne",
|
||||
firstWindow: qbtypes.TimeRange{From: 1000, To: 4000},
|
||||
secondWindow: qbtypes.TimeRange{From: 3000, To: 6000},
|
||||
expectedBuckets: []bucketShape{{1000, 6000, 5}},
|
||||
expectedBuckets: []bucketShape{{1000, 6000, 6}},
|
||||
expectedMissingRanges: []*qbtypes.TimeRange{},
|
||||
expectedValueCount: 5,
|
||||
},
|
||||
@@ -1571,13 +1570,12 @@ func TestBucketCachePutAbsorbsWritesWithinACell(t *testing.T) {
|
||||
|
||||
for _, window := range []qbtypes.TimeRange{testCase.firstWindow, testCase.secondWindow} {
|
||||
query := &mockQuery{fingerprint: "absorb-query", startMs: window.From, endMs: window.To}
|
||||
result := &qbtypes.Result{
|
||||
bc.Put(ctx, orgID, query, step, &qbtypes.Result{
|
||||
Type: qbtypes.RequestTypeTimeSeries,
|
||||
Value: createTestTimeSeries("A", window.From, window.To, 1000),
|
||||
// one row per second, so a bucket's stats read as the seconds of data it holds
|
||||
// one row per second, so a bucket's stats read as the seconds it was scanned for
|
||||
Stats: qbtypes.ExecStats{RowsScanned: (window.To - window.From) / 1000},
|
||||
}
|
||||
bc.Put(ctx, orgID, query, step, result, map[qbtypes.TimeRange]qbtypes.ExecStats{window: result.Stats})
|
||||
})
|
||||
}
|
||||
|
||||
query := &mockQuery{
|
||||
@@ -1611,7 +1609,7 @@ func TestBucketCachePutLeavesAnUnchangedCellAlone(t *testing.T) {
|
||||
Type: qbtypes.RequestTypeTimeSeries,
|
||||
Value: createTestTimeSeries("A", 1000, 10000, 1000),
|
||||
Warnings: []string{"from the wide write"},
|
||||
}, nil)
|
||||
})
|
||||
|
||||
// a write already covered by the cell's bucket must not rewrite the entry,
|
||||
// which shows as its warnings never arriving
|
||||
@@ -1620,7 +1618,7 @@ func TestBucketCachePutLeavesAnUnchangedCellAlone(t *testing.T) {
|
||||
Type: qbtypes.RequestTypeTimeSeries,
|
||||
Value: createTestTimeSeries("A", 3000, 5000, 1000),
|
||||
Warnings: []string{"from the inside write"},
|
||||
}, nil)
|
||||
})
|
||||
|
||||
var cellEntry qbtypes.CachedData
|
||||
require.NoError(t, bc.cache.Get(ctx, orgID, bc.generateCacheKey(wide, 0), &cellEntry))
|
||||
|
||||
@@ -823,7 +823,7 @@ func (q *querier) executeWithCache(ctx context.Context, orgID valuer.UUID, query
|
||||
return nil, err
|
||||
}
|
||||
// Store in cache for future use
|
||||
q.bucketCache.Put(ctx, orgID, query, step, result, map[qbtypes.TimeRange]qbtypes.ExecStats{{From: startMs, To: endMs}: result.Stats})
|
||||
q.bucketCache.Put(ctx, orgID, query, step, result)
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
@@ -879,19 +879,16 @@ func (q *querier) executeWithCache(ctx context.Context, orgID valuer.UUID, query
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
startMs, endMs := query.Window()
|
||||
q.bucketCache.Put(ctx, orgID, query, step, result, map[qbtypes.TimeRange]qbtypes.ExecStats{{From: startMs, To: endMs}: result.Stats})
|
||||
q.bucketCache.Put(ctx, orgID, query, step, result)
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate total stats and filter out nil results
|
||||
validResults := make([]*qbtypes.Result, 0, len(freshResults))
|
||||
execStatsForQueriedTimeRanges := make(map[qbtypes.TimeRange]qbtypes.ExecStats, len(freshResults))
|
||||
for i, result := range freshResults {
|
||||
for _, result := range freshResults {
|
||||
if result != nil {
|
||||
validResults = append(validResults, result)
|
||||
execStatsForQueriedTimeRanges[*missingRanges[i]] = result.Stats
|
||||
totalStats.RowsScanned += result.Stats.RowsScanned
|
||||
totalStats.BytesScanned += result.Stats.BytesScanned
|
||||
totalStats.DurationMS += result.Stats.DurationMS
|
||||
@@ -906,7 +903,7 @@ func (q *querier) executeWithCache(ctx context.Context, orgID valuer.UUID, query
|
||||
mergedResult.Stats.DurationMS += totalStats.DurationMS
|
||||
|
||||
// Store merged result in cache
|
||||
q.bucketCache.Put(ctx, orgID, query, step, mergedResult, execStatsForQueriedTimeRanges)
|
||||
q.bucketCache.Put(ctx, orgID, query, step, mergedResult)
|
||||
|
||||
return mergedResult, nil
|
||||
}
|
||||
|
||||
@@ -43,23 +43,6 @@ func (c *CachedBucket) Touches(other *CachedBucket) bool {
|
||||
return c.StartMs <= other.EndMs && other.StartMs <= c.EndMs
|
||||
}
|
||||
|
||||
// ProrateStatsToWindow returns the share of the stats for the part of the
|
||||
// bucket inside [startMs, endMs).
|
||||
func (c *CachedBucket) ProrateStatsToWindow(startMs, endMs uint64) ExecStats {
|
||||
return c.Stats.prorate(overlapMs(c.StartMs, c.EndMs, startMs, endMs), c.EndMs-c.StartMs)
|
||||
}
|
||||
|
||||
// ProrateStatsToUnqueried returns the share of the stats for the part of the
|
||||
// bucket no queried range covers.
|
||||
func (c *CachedBucket) ProrateStatsToUnqueried(execStatsForQueriedTimeRanges map[TimeRange]ExecStats) ExecStats {
|
||||
widthMs := c.EndMs - c.StartMs
|
||||
queriedMs := uint64(0)
|
||||
for queried := range execStatsForQueriedTimeRanges {
|
||||
queriedMs += overlapMs(c.StartMs, c.EndMs, queried.From, queried.To)
|
||||
}
|
||||
return c.Stats.prorate(widthMs-min(queriedMs, widthMs), widthMs)
|
||||
}
|
||||
|
||||
// CachedData represents the full cached data for a query.
|
||||
type CachedData struct {
|
||||
Buckets []*CachedBucket `json:"buckets"`
|
||||
@@ -105,7 +88,7 @@ func (c *CachedData) Cost() int64 {
|
||||
return size
|
||||
}
|
||||
|
||||
func (r *Result) SplitIntoCacheCellBuckets(startMs, endMs, cellWidthMs uint64, execStatsForQueriedTimeRanges map[TimeRange]ExecStats) ([]*CachedBucket, error) {
|
||||
func (r *Result) SplitIntoCacheCellBuckets(startMs, endMs, cellWidthMs uint64) ([]*CachedBucket, error) {
|
||||
tsData, ok := r.Value.(*TimeSeriesData)
|
||||
if !ok || tsData == nil {
|
||||
return nil, errors.NewInternalf(errors.CodeInternal, "result of type %s cannot be bucketed", r.Type.StringValue())
|
||||
@@ -128,39 +111,18 @@ func (r *Result) SplitIntoCacheCellBuckets(startMs, endMs, cellWidthMs uint64, e
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// A slice costs what was queried inside it; the rest of the result came
|
||||
// out of the cache and is costed there already
|
||||
sliceStats := ExecStats{StepIntervals: maps.Clone(r.Stats.StepIntervals)}
|
||||
for queried, stats := range execStatsForQueriedTimeRanges {
|
||||
queriedMs := overlapMs(from, to, queried.From, queried.To)
|
||||
if queriedMs == 0 {
|
||||
continue
|
||||
}
|
||||
share := stats.prorate(queriedMs, queried.To-queried.From)
|
||||
sliceStats.RowsScanned += share.RowsScanned
|
||||
sliceStats.BytesScanned += share.BytesScanned
|
||||
sliceStats.DurationMS += share.DurationMS
|
||||
}
|
||||
cellBuckets = append(cellBuckets, &CachedBucket{
|
||||
StartMs: from,
|
||||
EndMs: to,
|
||||
Type: r.Type,
|
||||
Value: value,
|
||||
Stats: sliceStats,
|
||||
Stats: r.Stats.prorate(to-from, endMs-startMs),
|
||||
})
|
||||
from = to
|
||||
}
|
||||
return cellBuckets, nil
|
||||
}
|
||||
|
||||
// overlapMs is the length of [aFromMs, aToMs) ∩ [bFromMs, bToMs).
|
||||
func overlapMs(aFromMs, aToMs, bFromMs, bToMs uint64) uint64 {
|
||||
if fromMs, toMs := max(aFromMs, bFromMs), min(aToMs, bToMs); fromMs < toMs {
|
||||
return toMs - fromMs
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// prorate returns the share of the stats that partMs out of wholeMs of the
|
||||
// scanned window accounts for.
|
||||
func (e ExecStats) prorate(partMs, wholeMs uint64) ExecStats {
|
||||
|
||||
@@ -442,55 +442,3 @@ def test_builder_refreshing_a_sliding_time_range(
|
||||
assert uncached.status_code == HTTPStatus.OK, uncached.text
|
||||
|
||||
assert_results_equal(from_cache.json(), uncached.json(), "A", "the twentieth refresh")
|
||||
|
||||
|
||||
def test_builder_refreshing_does_not_inflate_rows_scanned(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: None, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
insert_metrics: Callable[[list[Metrics]], None],
|
||||
) -> None:
|
||||
metric_name = f"cache_rows_scanned_{uuid4().hex[:8]}"
|
||||
|
||||
# 90 minutes back so even the last refresh closes clear of the flux
|
||||
# interval, which holds recent data out of the cache
|
||||
start_time = datetime.fromtimestamp(int((datetime.now(tz=UTC) - timedelta(minutes=90)).timestamp()) // 60 * 60, tz=UTC)
|
||||
start_time_ms = int(start_time.timestamp() * 1000)
|
||||
|
||||
query = [build_builder_query("A", metric_name, "max", "max")]
|
||||
|
||||
insert_metrics(
|
||||
[
|
||||
Metrics(
|
||||
metric_name=metric_name,
|
||||
labels={"service": "api"},
|
||||
timestamp=start_time + timedelta(minutes=minute),
|
||||
value=minute,
|
||||
type_="Gauge",
|
||||
is_monotonic=False,
|
||||
)
|
||||
for minute in range(70)
|
||||
]
|
||||
)
|
||||
|
||||
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
# a dashboard left open on a thirty minute range, re-running a minute later
|
||||
# each time. rowsScanned is a cost indicator, so a refresh served mostly out
|
||||
# of the cache cannot claim more than every read made so far: the first
|
||||
# full read and then one new minute per refresh, measured with the cache off
|
||||
uncached_rows_scanned_so_far = 0
|
||||
for refresh in range(10):
|
||||
refresh_start_ms = start_time_ms + refresh * MINUTE_MS
|
||||
refresh_end_ms = refresh_start_ms + 30 * MINUTE_MS
|
||||
|
||||
uncached_start_ms = refresh_start_ms if refresh == 0 else refresh_end_ms - MINUTE_MS
|
||||
uncached_response = make_query_request(signoz, token, uncached_start_ms, refresh_end_ms, query, no_cache=True)
|
||||
assert uncached_response.status_code == HTTPStatus.OK, f"refresh {refresh}: uncached read failed: {uncached_response.text}"
|
||||
uncached_rows_scanned_so_far += uncached_response.json()["data"]["meta"]["rowsScanned"]
|
||||
assert uncached_rows_scanned_so_far > 0, f"refresh {refresh}: uncached read reported no rows scanned"
|
||||
|
||||
cached_response = make_query_request(signoz, token, refresh_start_ms, refresh_end_ms, query, no_cache=False)
|
||||
assert cached_response.status_code == HTTPStatus.OK, f"refresh {refresh}: cached read failed: {cached_response.text}"
|
||||
cached_rows_scanned = cached_response.json()["data"]["meta"]["rowsScanned"]
|
||||
assert cached_rows_scanned <= uncached_rows_scanned_so_far, f"refresh {refresh}: cached read reported {cached_rows_scanned} rows scanned, more than the {uncached_rows_scanned_so_far} rows all uncached reads scanned so far"
|
||||
|
||||
Reference in New Issue
Block a user