mirror of
https://github.com/SigNoz/signoz.git
synced 2026-07-17 03:40:31 +01:00
Compare commits
7 Commits
issue-4293
...
issue-5121
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b40b119d9 | ||
|
|
ddb50bb692 | ||
|
|
fbf2e62044 | ||
|
|
62c44f5eac | ||
|
|
65fde71b72 | ||
|
|
7f5f63b20a | ||
|
|
63cfbe8bfb |
@@ -1,13 +1,17 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { Button, Tooltip } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import { listHosts } from 'api/generated/services/inframonitoring';
|
||||
import {
|
||||
InframonitoringtypesHostRecordDTO,
|
||||
InframonitoringtypesHostStatusDTO,
|
||||
InframonitoringtypesResponseTypeDTO,
|
||||
Querybuildertypesv5OrderDirectionDTO,
|
||||
RenderErrorResponseDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { AxiosError } from 'axios';
|
||||
import APIError from 'types/api/error';
|
||||
import QuickFilters from 'components/QuickFilters/QuickFilters';
|
||||
import { QuickFiltersSource } from 'components/QuickFilters/types';
|
||||
import { InfraMonitoringEvents } from 'constants/events';
|
||||
@@ -122,13 +126,12 @@ function Hosts(): JSX.Element {
|
||||
endTimeBeforeRetention: data.endTimeBeforeRetention,
|
||||
};
|
||||
} catch (error) {
|
||||
const errMsg =
|
||||
error instanceof Error ? error.message : 'Failed to fetch hosts';
|
||||
return {
|
||||
type: 'list' as const,
|
||||
records: [] as InframonitoringtypesHostRecordDTO[],
|
||||
total: 0,
|
||||
error: errMsg,
|
||||
error:
|
||||
convertToApiError(error as AxiosError<RenderErrorResponseDTO>) ?? null,
|
||||
};
|
||||
}
|
||||
},
|
||||
@@ -141,7 +144,7 @@ function Hosts(): JSX.Element {
|
||||
signal?: AbortSignal,
|
||||
): Promise<{
|
||||
data: InframonitoringtypesHostRecordDTO | null;
|
||||
error?: string | null;
|
||||
error?: APIError | null;
|
||||
}> => {
|
||||
try {
|
||||
const response = await listHosts(
|
||||
@@ -159,11 +162,10 @@ function Hosts(): JSX.Element {
|
||||
data: response.data.records.length > 0 ? response.data.records[0] : null,
|
||||
};
|
||||
} catch (error) {
|
||||
const errMsg =
|
||||
error instanceof Error ? error.message : 'Failed to fetch host';
|
||||
return {
|
||||
data: null,
|
||||
error: errMsg,
|
||||
error:
|
||||
convertToApiError(error as AxiosError<RenderErrorResponseDTO>) ?? null,
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
@@ -51,7 +51,7 @@ function ColumnHeader({
|
||||
<a
|
||||
href={`${DOCS_BASE_URL}${docPath}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
rel="noopener"
|
||||
onClick={(e): void => e.stopPropagation()}
|
||||
>
|
||||
Learn more.
|
||||
|
||||
@@ -30,7 +30,7 @@ function EntityGroupHeader({
|
||||
<a
|
||||
href={`${DOCS_BASE_URL}${docPath}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
rel="noopener"
|
||||
onClick={(e): void => e.stopPropagation()}
|
||||
>
|
||||
Learn more.
|
||||
|
||||
@@ -12,6 +12,8 @@ import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
|
||||
import { Divider } from '@signozhq/ui/divider';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import ErrorContent from 'components/ErrorModal/components/ErrorContent';
|
||||
import APIError from 'types/api/error';
|
||||
import { combineInitialAndUserExpression } from 'components/QueryBuilderV2/QueryV2/QuerySearch/utils';
|
||||
import { InfraMonitoringEvents } from 'constants/events';
|
||||
import { QueryParams } from 'constants/query';
|
||||
@@ -63,6 +65,10 @@ import LoadingContainer from '../LoadingContainer';
|
||||
|
||||
import '../EntityDetailsUtils/entityDetails.styles.scss';
|
||||
import { parseAsString, useQueryState } from 'nuqs';
|
||||
import {
|
||||
EntityCountConfig,
|
||||
EntityCountsSection,
|
||||
} from './components/EntityCountsSection/EntityCountsSection';
|
||||
|
||||
const TimeRangeOffset = 1000000000;
|
||||
|
||||
@@ -72,6 +78,8 @@ export interface K8sDetailsMetadataConfig<T> {
|
||||
render?: (value: string | number, entity: T) => React.ReactNode;
|
||||
}
|
||||
|
||||
export type K8sDetailsCountConfig<T> = EntityCountConfig<T>;
|
||||
|
||||
export interface K8sDetailsFilters {
|
||||
filter: { expression: string };
|
||||
start: number;
|
||||
@@ -86,15 +94,18 @@ export interface K8sBaseDetailsProps<T> {
|
||||
fetchEntityData: (
|
||||
filters: K8sDetailsFilters,
|
||||
signal?: AbortSignal,
|
||||
) => Promise<{ data: T | null; error?: string | null }>;
|
||||
) => Promise<{ data: T | null; error?: APIError | null }>;
|
||||
// Entity configuration
|
||||
getEntityName: (entity: T) => string;
|
||||
getInitialLogTracesExpression: (entity: T) => string;
|
||||
getInitialEventsExpression: (entity: T) => string;
|
||||
metadataConfig: K8sDetailsMetadataConfig<T>[];
|
||||
countsConfig?: K8sDetailsCountConfig<T>[];
|
||||
getCountsFilterExpression?: (entity: T) => string;
|
||||
entityWidgetInfo: {
|
||||
title: string;
|
||||
yAxisUnit: string;
|
||||
docPath?: string;
|
||||
}[];
|
||||
getEntityQueryPayload: (
|
||||
entity: T,
|
||||
@@ -137,6 +148,8 @@ export default function K8sBaseDetails<T>({
|
||||
getInitialLogTracesExpression,
|
||||
getInitialEventsExpression,
|
||||
metadataConfig,
|
||||
countsConfig,
|
||||
getCountsFilterExpression,
|
||||
entityWidgetInfo,
|
||||
getEntityQueryPayload,
|
||||
queryKeyPrefix,
|
||||
@@ -436,12 +449,18 @@ export default function K8sBaseDetails<T>({
|
||||
{isEntityLoading && <LoadingContainer />}
|
||||
{(isEntityError || hasResponseError) && (
|
||||
<div className="entity-error-container">
|
||||
<Typography.Text color="danger">
|
||||
{entityResponse?.error ||
|
||||
(entityError instanceof Error
|
||||
? entityError.message
|
||||
: 'Failed to load entity details')}
|
||||
</Typography.Text>
|
||||
<ErrorContent
|
||||
error={
|
||||
entityResponse?.error ??
|
||||
(entityError instanceof APIError ? entityError : null) ?? {
|
||||
code: 500,
|
||||
message:
|
||||
entityError instanceof Error
|
||||
? entityError.message
|
||||
: 'Failed to load entity details',
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{entity && !isEntityLoading && !hasResponseError && (
|
||||
@@ -479,6 +498,19 @@ export default function K8sBaseDetails<T>({
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{countsConfig &&
|
||||
countsConfig.length > 0 &&
|
||||
selectedItem &&
|
||||
getCountsFilterExpression && (
|
||||
<EntityCountsSection
|
||||
entity={entity}
|
||||
countsConfig={countsConfig}
|
||||
selectedItem={selectedItem}
|
||||
filterExpression={getCountsFilterExpression(entity)}
|
||||
closeDrawer={handleClose}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!hideDetailViewTabs && (
|
||||
|
||||
@@ -14,6 +14,7 @@ import { useGlobalTimeStore } from 'store/globalTime';
|
||||
import { NANO_SECOND_MULTIPLIER } from 'store/globalTime/utils';
|
||||
import { Querybuildertypesv5QueryWarnDataDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
import {
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
@@ -33,13 +34,14 @@ import K8sHeader from './K8sHeader';
|
||||
import { K8sPaginationWarning } from './K8sPaginationWarning';
|
||||
import { K8sBaseFilters } from './types';
|
||||
import { getGroupedByMeta } from './utils';
|
||||
import { K8sInstrumentationChecksCallout } from './components/K8sInstrumentationChecksCallout/K8sInstrumentationChecksCallout';
|
||||
|
||||
import styles from './K8sBaseList.module.scss';
|
||||
import cx from 'classnames';
|
||||
|
||||
export type K8sBaseListEmptyStateContext = {
|
||||
isError: boolean;
|
||||
error?: string | null;
|
||||
error?: APIError | null;
|
||||
totalCount: number;
|
||||
hasFilters: boolean;
|
||||
isLoading: boolean;
|
||||
@@ -66,7 +68,7 @@ export type K8sBaseListProps<
|
||||
records?: T[];
|
||||
data?: T[];
|
||||
total: number;
|
||||
error?: string | null;
|
||||
error?: APIError | null;
|
||||
rawData?: unknown;
|
||||
endTimeBeforeRetention?: boolean;
|
||||
warning?: Querybuildertypesv5QueryWarnDataDTO | null;
|
||||
@@ -377,6 +379,8 @@ export function K8sBaseList<
|
||||
cancelQuery={cancelQuery}
|
||||
/>
|
||||
<div ref={containerRef} className={styles.tableContainer}>
|
||||
<K8sInstrumentationChecksCallout entity={entity} />
|
||||
|
||||
{isError && (
|
||||
<Typography>
|
||||
{data?.error?.toString() || 'Something went wrong'}
|
||||
|
||||
@@ -44,13 +44,11 @@
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
.errorIcon {
|
||||
color: var(--danger-background);
|
||||
}
|
||||
|
||||
.actions {
|
||||
.errorContent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 16px;
|
||||
gap: var(--spacing-3);
|
||||
max-width: 500px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import { useCallback } from 'react';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
||||
import history from 'lib/history';
|
||||
import { LifeBuoy, TriangleAlert } from '@signozhq/icons';
|
||||
import ErrorContent from 'components/ErrorModal/components/ErrorContent';
|
||||
|
||||
import emptyStateUrl from '@/assets/Icons/emptyState.svg';
|
||||
import eyesEmojiUrl from '@/assets/Images/eyesEmoji.svg';
|
||||
@@ -13,26 +9,12 @@ import styles from './K8sEmptyState.module.scss';
|
||||
|
||||
type K8sEmptyStateProps = Partial<K8sBaseListEmptyStateContext>;
|
||||
|
||||
const handleContactSupport = (isCloudUser: boolean): void => {
|
||||
if (isCloudUser) {
|
||||
history.push('/support');
|
||||
} else {
|
||||
window.open('https://signoz.io/slack', '_blank');
|
||||
}
|
||||
};
|
||||
|
||||
export function K8sEmptyState({
|
||||
isError,
|
||||
error,
|
||||
isLoading,
|
||||
endTimeBeforeRetention,
|
||||
}: K8sEmptyStateProps): JSX.Element | null {
|
||||
const { isCloudUser } = useGetTenantLicense();
|
||||
|
||||
const handleSupport = useCallback(() => {
|
||||
handleContactSupport(isCloudUser);
|
||||
}, [isCloudUser]);
|
||||
|
||||
if (isLoading) {
|
||||
return null;
|
||||
}
|
||||
@@ -40,25 +22,15 @@ export function K8sEmptyState({
|
||||
if (isError || error) {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.content}>
|
||||
<TriangleAlert size={32} className={styles.errorIcon} />
|
||||
<span className={styles.message}>
|
||||
{error || 'An error occurred while fetching data.'}
|
||||
</span>
|
||||
<p>
|
||||
Our team is getting on top to resolve this. Please reach out to support if
|
||||
the issue persists.
|
||||
</p>
|
||||
<div className={styles.actions}>
|
||||
<Button
|
||||
onClick={handleSupport}
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
prefix={<LifeBuoy size={14} />}
|
||||
>
|
||||
Contact Support
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles.errorContent}>
|
||||
<ErrorContent
|
||||
error={
|
||||
error ?? {
|
||||
code: 500,
|
||||
message: 'An error occurred while fetching data.',
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useLocation } from 'react-router-dom';
|
||||
import { Querybuildertypesv5QueryWarnDataDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import APIError from 'types/api/error';
|
||||
import TanStackTable, {
|
||||
SortState,
|
||||
TableColumnDef,
|
||||
@@ -51,7 +52,7 @@ export type K8sExpandedRowProps<T, TItemKey = string> = {
|
||||
records?: T[];
|
||||
data?: T[];
|
||||
total: number;
|
||||
error?: string | null;
|
||||
error?: APIError | null;
|
||||
rawData?: unknown;
|
||||
warning?: Querybuildertypesv5QueryWarnDataDTO | null;
|
||||
}>;
|
||||
|
||||
@@ -9,6 +9,8 @@ import { TooltipProvider } from '@signozhq/ui/tooltip';
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { InfraMonitoringEvents } from 'constants/events';
|
||||
import { server } from 'mocks-server/server';
|
||||
import { rest } from 'msw';
|
||||
import {
|
||||
NuqsTestingAdapter,
|
||||
OnUrlUpdateFunction,
|
||||
@@ -17,6 +19,7 @@ import {
|
||||
import { AppProvider } from 'providers/App/App';
|
||||
import TimezoneProvider from 'providers/Timezone';
|
||||
import store from 'store';
|
||||
import APIError from 'types/api/error';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
import { TableColumnDef } from 'components/TanStackTableView';
|
||||
@@ -630,7 +633,15 @@ describe('K8sBaseList', () => {
|
||||
fetchListDataMock.mockResolvedValue({
|
||||
data: [],
|
||||
total: 0,
|
||||
error: 'Failed to fetch pods',
|
||||
error: new APIError({
|
||||
httpStatusCode: 500,
|
||||
error: {
|
||||
code: '500',
|
||||
message: 'Failed to fetch pods',
|
||||
url: '',
|
||||
errors: [],
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
renderComponent<TestItem>({
|
||||
@@ -1054,4 +1065,304 @@ describe('K8sBaseList', () => {
|
||||
expect(url).not.toContain('selectedItemNamespaceName');
|
||||
});
|
||||
});
|
||||
|
||||
describe('instrumentation checks callout', () => {
|
||||
const fetchListDataMock = jest.fn<
|
||||
ReturnType<NonNullable<K8sBaseListProps<TestItem>['fetchListData']>>,
|
||||
Parameters<NonNullable<K8sBaseListProps<TestItem>['fetchListData']>>
|
||||
>();
|
||||
|
||||
beforeEach(() => {
|
||||
fetchListDataMock.mockClear();
|
||||
fetchListDataMock.mockResolvedValue({
|
||||
data: [{ id: 'item-1' }],
|
||||
total: 1,
|
||||
error: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('should not render callout when ready is true', async () => {
|
||||
server.use(
|
||||
rest.get('http://localhost/api/v2/infra_monitoring/checks', (_, res, ctx) =>
|
||||
res(
|
||||
ctx.json({
|
||||
status: 'success',
|
||||
data: {
|
||||
ready: true,
|
||||
type: 'pods',
|
||||
presentDefaultEnabledMetrics: [
|
||||
{
|
||||
associatedComponent: { name: 'otel-collector' },
|
||||
metrics: ['k8s.pod.cpu.usage'],
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
renderComponent<TestItem>({
|
||||
entity: InfraMonitoringEntity.PODS,
|
||||
eventCategory: InfraMonitoringEvents.Pod,
|
||||
fetchListData: fetchListDataMock,
|
||||
tableColumns: createTestColumns(),
|
||||
getRowKey: (row): string => row.id,
|
||||
getItemKey: (row): string => row.id,
|
||||
});
|
||||
|
||||
await screen.findByText('item-1');
|
||||
|
||||
expect(screen.queryByText('Instrumentation checks')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render callout when no entries exist', async () => {
|
||||
server.use(
|
||||
rest.get('http://localhost/api/v2/infra_monitoring/checks', (_, res, ctx) =>
|
||||
res(
|
||||
ctx.json({
|
||||
status: 'success',
|
||||
data: {
|
||||
ready: false,
|
||||
type: 'pods',
|
||||
presentDefaultEnabledMetrics: null,
|
||||
presentOptionalMetrics: null,
|
||||
presentRequiredAttributes: null,
|
||||
missingDefaultEnabledMetrics: null,
|
||||
missingOptionalMetrics: null,
|
||||
missingRequiredAttributes: null,
|
||||
},
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
renderComponent<TestItem>({
|
||||
entity: InfraMonitoringEntity.PODS,
|
||||
eventCategory: InfraMonitoringEvents.Pod,
|
||||
fetchListData: fetchListDataMock,
|
||||
tableColumns: createTestColumns(),
|
||||
getRowKey: (row): string => row.id,
|
||||
getItemKey: (row): string => row.id,
|
||||
});
|
||||
|
||||
await screen.findByText('item-1');
|
||||
|
||||
expect(screen.queryByText('Instrumentation checks')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render callout with present entries', async () => {
|
||||
server.use(
|
||||
rest.get('http://localhost/api/v2/infra_monitoring/checks', (_, res, ctx) =>
|
||||
res(
|
||||
ctx.json({
|
||||
status: 'success',
|
||||
data: {
|
||||
ready: false,
|
||||
type: 'pods',
|
||||
presentDefaultEnabledMetrics: [
|
||||
{
|
||||
associatedComponent: { name: 'otel-collector' },
|
||||
metrics: ['k8s.pod.cpu.usage', 'k8s.pod.memory.usage'],
|
||||
},
|
||||
],
|
||||
presentOptionalMetrics: null,
|
||||
presentRequiredAttributes: null,
|
||||
missingDefaultEnabledMetrics: null,
|
||||
missingOptionalMetrics: null,
|
||||
missingRequiredAttributes: null,
|
||||
},
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
renderComponent<TestItem>({
|
||||
entity: InfraMonitoringEntity.PODS,
|
||||
eventCategory: InfraMonitoringEvents.Pod,
|
||||
fetchListData: fetchListDataMock,
|
||||
tableColumns: createTestColumns(),
|
||||
getRowKey: (row): string => row.id,
|
||||
getItemKey: (row): string => row.id,
|
||||
});
|
||||
|
||||
await screen.findByText('Instrumentation checks');
|
||||
|
||||
await expect(
|
||||
screen.findByText('Default enabled metrics'),
|
||||
).resolves.toBeInTheDocument();
|
||||
await expect(
|
||||
screen.findByText('k8s.pod.cpu.usage, k8s.pod.memory.usage'),
|
||||
).resolves.toBeInTheDocument();
|
||||
await expect(
|
||||
screen.findByText('otel-collector'),
|
||||
).resolves.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render callout with missing entries', async () => {
|
||||
server.use(
|
||||
rest.get('http://localhost/api/v2/infra_monitoring/checks', (_, res, ctx) =>
|
||||
res(
|
||||
ctx.json({
|
||||
status: 'success',
|
||||
data: {
|
||||
ready: false,
|
||||
type: 'pods',
|
||||
presentDefaultEnabledMetrics: null,
|
||||
presentOptionalMetrics: null,
|
||||
presentRequiredAttributes: null,
|
||||
missingDefaultEnabledMetrics: [
|
||||
{
|
||||
associatedComponent: { name: 'otel-collector' },
|
||||
metrics: ['k8s.pod.cpu.limit'],
|
||||
documentationLink: 'https://example.com/docs',
|
||||
},
|
||||
],
|
||||
missingOptionalMetrics: null,
|
||||
missingRequiredAttributes: null,
|
||||
},
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
renderComponent<TestItem>({
|
||||
entity: InfraMonitoringEntity.PODS,
|
||||
eventCategory: InfraMonitoringEvents.Pod,
|
||||
fetchListData: fetchListDataMock,
|
||||
tableColumns: createTestColumns(),
|
||||
getRowKey: (row): string => row.id,
|
||||
getItemKey: (row): string => row.id,
|
||||
});
|
||||
|
||||
await screen.findByText('Instrumentation checks');
|
||||
|
||||
await expect(
|
||||
screen.findByText('Missing default metrics'),
|
||||
).resolves.toBeInTheDocument();
|
||||
await expect(
|
||||
screen.findByText('k8s.pod.cpu.limit'),
|
||||
).resolves.toBeInTheDocument();
|
||||
await expect(screen.findByText('Learn here')).resolves.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should trigger recheck on button click', async () => {
|
||||
let recheckCallCount = 0;
|
||||
server.use(
|
||||
rest.get(
|
||||
'http://localhost/api/v2/infra_monitoring/checks',
|
||||
(_, res, ctx) => {
|
||||
recheckCallCount++;
|
||||
return res(
|
||||
ctx.json({
|
||||
status: 'success',
|
||||
data: {
|
||||
ready: false,
|
||||
type: 'pods',
|
||||
presentDefaultEnabledMetrics: [
|
||||
{
|
||||
associatedComponent: { name: 'otel-collector' },
|
||||
metrics: ['k8s.pod.cpu.usage'],
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
const user = userEvent.setup();
|
||||
|
||||
renderComponent<TestItem>({
|
||||
entity: InfraMonitoringEntity.PODS,
|
||||
eventCategory: InfraMonitoringEvents.Pod,
|
||||
fetchListData: fetchListDataMock,
|
||||
tableColumns: createTestColumns(),
|
||||
getRowKey: (row): string => row.id,
|
||||
getItemKey: (row): string => row.id,
|
||||
});
|
||||
|
||||
await screen.findByText('Instrumentation checks');
|
||||
|
||||
const initialCallCount = recheckCallCount;
|
||||
|
||||
const recheckBtn = screen.getByTestId('instrumentation-checks-recheck-btn');
|
||||
await user.click(recheckBtn);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(recheckCallCount).toBeGreaterThan(initialCallCount);
|
||||
});
|
||||
});
|
||||
|
||||
it('should render both present and missing entries', async () => {
|
||||
server.use(
|
||||
rest.get('http://localhost/api/v2/infra_monitoring/checks', (_, res, ctx) =>
|
||||
res(
|
||||
ctx.json({
|
||||
status: 'success',
|
||||
data: {
|
||||
ready: false,
|
||||
type: 'pods',
|
||||
presentDefaultEnabledMetrics: [
|
||||
{
|
||||
associatedComponent: { name: 'otel-collector' },
|
||||
metrics: ['k8s.pod.cpu.usage'],
|
||||
},
|
||||
],
|
||||
presentOptionalMetrics: null,
|
||||
presentRequiredAttributes: [
|
||||
{
|
||||
associatedComponent: { name: 'otel-collector' },
|
||||
attributes: ['k8s.namespace.name'],
|
||||
},
|
||||
],
|
||||
missingDefaultEnabledMetrics: [
|
||||
{
|
||||
associatedComponent: { name: 'otel-collector' },
|
||||
metrics: ['k8s.pod.memory.limit'],
|
||||
},
|
||||
],
|
||||
missingOptionalMetrics: null,
|
||||
missingRequiredAttributes: null,
|
||||
},
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
renderComponent<TestItem>({
|
||||
entity: InfraMonitoringEntity.PODS,
|
||||
eventCategory: InfraMonitoringEvents.Pod,
|
||||
fetchListData: fetchListDataMock,
|
||||
tableColumns: createTestColumns(),
|
||||
getRowKey: (row): string => row.id,
|
||||
getItemKey: (row): string => row.id,
|
||||
});
|
||||
|
||||
await screen.findByText('Instrumentation checks');
|
||||
|
||||
// Present entries
|
||||
await expect(
|
||||
screen.findByText('Default enabled metrics'),
|
||||
).resolves.toBeInTheDocument();
|
||||
await expect(
|
||||
screen.findByText('k8s.pod.cpu.usage'),
|
||||
).resolves.toBeInTheDocument();
|
||||
await expect(
|
||||
screen.findByText('Required attributes'),
|
||||
).resolves.toBeInTheDocument();
|
||||
await expect(
|
||||
screen.findByText('k8s.namespace.name'),
|
||||
).resolves.toBeInTheDocument();
|
||||
|
||||
// Missing entries
|
||||
await expect(
|
||||
screen.findByText('Missing default metrics'),
|
||||
).resolves.toBeInTheDocument();
|
||||
await expect(
|
||||
screen.findByText('k8s.pod.memory.limit'),
|
||||
).resolves.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
.countsContainer {
|
||||
display: flex;
|
||||
gap: var(--spacing-6);
|
||||
margin-top: var(--spacing-8);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.countCard {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
min-width: 120px;
|
||||
max-width: 180px;
|
||||
border: 1px solid var(--l3-border);
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-2);
|
||||
padding: var(--spacing-6);
|
||||
}
|
||||
|
||||
.countLabel {
|
||||
color: var(--l2-foreground);
|
||||
letter-spacing: var(--letter-spacing-wide);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.countValue {
|
||||
color: var(--l1-foreground);
|
||||
font-family: var(--periscope-font-family-mono);
|
||||
font-size: var(--font-size-xl);
|
||||
}
|
||||
|
||||
.navigateButton {
|
||||
position: absolute;
|
||||
top: var(--spacing-4);
|
||||
right: var(--spacing-4);
|
||||
|
||||
--button-padding: var(--spacing-1);
|
||||
--button-height: 24px;
|
||||
--button-width: 24px;
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import { Tooltip } from 'antd';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { Compass } from '@signozhq/icons';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { initialQueriesMap } from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import {
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
InfraMonitoringEntity,
|
||||
} from '../../../constants';
|
||||
import styles from './EntityCountsSection.module.scss';
|
||||
|
||||
export interface EntityCountConfig<T> {
|
||||
label: string;
|
||||
getValue: (entity: T) => number;
|
||||
targetCategory: InfraMonitoringEntity;
|
||||
}
|
||||
|
||||
interface EntityCountsSectionProps<T> {
|
||||
entity: T;
|
||||
countsConfig: EntityCountConfig<T>[];
|
||||
selectedItem: string;
|
||||
filterExpression: string;
|
||||
closeDrawer: () => void;
|
||||
}
|
||||
|
||||
export function EntityCountsSection<T>({
|
||||
entity,
|
||||
countsConfig,
|
||||
selectedItem,
|
||||
filterExpression,
|
||||
closeDrawer,
|
||||
}: EntityCountsSectionProps<T>): JSX.Element {
|
||||
const buildNavigationUrl = (targetCategory: InfraMonitoringEntity): string => {
|
||||
const defaultQuery = initialQueriesMap[DataSource.METRICS];
|
||||
|
||||
const compositeQuery = {
|
||||
...defaultQuery,
|
||||
id: uuid(),
|
||||
builder: {
|
||||
...defaultQuery.builder,
|
||||
queryData: defaultQuery.builder.queryData.map((query) => ({
|
||||
...query,
|
||||
filter: { expression: filterExpression },
|
||||
filters: { items: [], op: 'AND' as const },
|
||||
})),
|
||||
},
|
||||
};
|
||||
|
||||
// TODO(H4ad): After https://github.com/SigNoz/signoz/pull/12038, inherit custom time of drawer to list
|
||||
const urlParams = new URLSearchParams();
|
||||
urlParams.set(INFRA_MONITORING_K8S_PARAMS_KEYS.CATEGORY, targetCategory);
|
||||
urlParams.set(
|
||||
QueryParams.compositeQuery,
|
||||
encodeURIComponent(JSON.stringify(compositeQuery)),
|
||||
);
|
||||
|
||||
return `${ROUTES.INFRASTRUCTURE_MONITORING_KUBERNETES}?${urlParams.toString()}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.countsContainer}>
|
||||
{countsConfig.map((config) => (
|
||||
<div
|
||||
key={config.label}
|
||||
className={styles.countCard}
|
||||
data-testid={`count-card-${config.label.toLowerCase().replace(/\s+/g, '-')}`}
|
||||
>
|
||||
<Typography.Text
|
||||
color="muted"
|
||||
size="small"
|
||||
weight="medium"
|
||||
className={styles.countLabel}
|
||||
>
|
||||
{config.label}
|
||||
</Typography.Text>
|
||||
<Typography.Text className={styles.countValue} size="xl" weight="semibold">
|
||||
{config.getValue(entity) || '-'}
|
||||
</Typography.Text>
|
||||
<Link
|
||||
to={buildNavigationUrl(config.targetCategory)}
|
||||
onClick={closeDrawer}
|
||||
data-testid={`navigate-${config.label.toLowerCase().replace(/\s+/g, '-')}`}
|
||||
>
|
||||
<Tooltip
|
||||
title={`View ${config.label.toLowerCase()} of '${selectedItem}'`}
|
||||
placement="top"
|
||||
>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
className={styles.navigateButton}
|
||||
prefix={<Compass size={14} />}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Link>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
.checkContainer {
|
||||
padding: 0px var(--spacing-4) var(--spacing-4) var(--spacing-4);
|
||||
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.entriesList {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
import { MouseEvent, useCallback, useMemo, useState } from 'react';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { Callout } from '@signozhq/ui/callout';
|
||||
import { RefreshCw } from '@signozhq/icons';
|
||||
import setLocalStorage from 'api/browser/localstorage/set';
|
||||
import {
|
||||
invalidateGetChecks,
|
||||
useGetChecks,
|
||||
} from 'api/generated/services/inframonitoring';
|
||||
import { InframonitoringtypesCheckTypeDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { InfraMonitoringEntity } from '../../../constants';
|
||||
|
||||
import styles from './K8sInstrumentationChecksCallout.module.scss';
|
||||
import { MissingEntryRow } from './MissingEntryRow';
|
||||
import { PresentEntryRow } from './PresentEntryRow';
|
||||
import {
|
||||
ENTITY_TO_CHECK_TYPE,
|
||||
getStorageKey,
|
||||
hasAnyEntries,
|
||||
hasMissingEntries,
|
||||
readExpandedState,
|
||||
} from './utils';
|
||||
|
||||
export interface InstrumentationChecksCalloutProps {
|
||||
entity: InfraMonitoringEntity;
|
||||
}
|
||||
|
||||
export function K8sInstrumentationChecksCallout({
|
||||
entity,
|
||||
}: InstrumentationChecksCalloutProps): JSX.Element | null {
|
||||
const checkType = ENTITY_TO_CHECK_TYPE[entity];
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const [isExpanded, setIsExpanded] = useState(() => readExpandedState(entity));
|
||||
|
||||
const { data, isLoading, isFetching } = useGetChecks(
|
||||
{ type: checkType as InframonitoringtypesCheckTypeDTO },
|
||||
{ query: { enabled: !!checkType } },
|
||||
);
|
||||
|
||||
const handleRecheck = useCallback(
|
||||
(e: MouseEvent): void => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (checkType) {
|
||||
void invalidateGetChecks(queryClient, { type: checkType });
|
||||
}
|
||||
},
|
||||
[queryClient, checkType],
|
||||
);
|
||||
|
||||
const handleExpandToggle = useCallback((): void => {
|
||||
setIsExpanded((prev) => {
|
||||
const next = !prev;
|
||||
setLocalStorage(getStorageKey(entity), String(next));
|
||||
return next;
|
||||
});
|
||||
}, [entity]);
|
||||
|
||||
const checksData = data?.data;
|
||||
|
||||
const hasMissingItems = useMemo(
|
||||
() => (checksData ? hasMissingEntries(checksData) : false),
|
||||
[checksData],
|
||||
);
|
||||
|
||||
if (!checkType || isLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!checksData || checksData.ready) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!hasAnyEntries(checksData)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.checkContainer}>
|
||||
<Callout
|
||||
type={hasMissingItems ? 'warning' : 'info'}
|
||||
showIcon
|
||||
size="medium"
|
||||
title={
|
||||
<div className={styles.header}>
|
||||
Instrumentation checks
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="warning"
|
||||
size="sm"
|
||||
onClick={handleRecheck}
|
||||
loading={isFetching}
|
||||
prefix={<RefreshCw size={12} />}
|
||||
data-testid="instrumentation-checks-recheck-btn"
|
||||
>
|
||||
Recheck
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
action="expandable"
|
||||
defaultExpanded={isExpanded}
|
||||
onClick={handleExpandToggle}
|
||||
>
|
||||
<div className={styles.container} onClick={(e) => e.stopPropagation()}>
|
||||
<div className={styles.entriesList}>
|
||||
{checksData.presentDefaultEnabledMetrics?.map((entry) => (
|
||||
<PresentEntryRow
|
||||
key={`present-default-${entry.associatedComponent.name}`}
|
||||
entry={entry}
|
||||
typeLabel="Default enabled metrics"
|
||||
itemType="metrics"
|
||||
/>
|
||||
))}
|
||||
{checksData.presentOptionalMetrics?.map((entry) => (
|
||||
<PresentEntryRow
|
||||
key={`present-optional-${entry.associatedComponent.name}`}
|
||||
entry={entry}
|
||||
typeLabel="Optional metrics"
|
||||
itemType="metrics"
|
||||
/>
|
||||
))}
|
||||
{checksData.presentRequiredAttributes?.map((entry) => (
|
||||
<PresentEntryRow
|
||||
key={`present-attrs-${entry.associatedComponent.name}`}
|
||||
entry={entry}
|
||||
typeLabel="Required attributes"
|
||||
itemType="attributes"
|
||||
/>
|
||||
))}
|
||||
{checksData.missingDefaultEnabledMetrics?.map((entry) => (
|
||||
<MissingEntryRow
|
||||
key={`missing-default-${entry.associatedComponent.name}`}
|
||||
entry={entry}
|
||||
typeLabel="Missing default metrics"
|
||||
itemType="metrics"
|
||||
/>
|
||||
))}
|
||||
{checksData.missingOptionalMetrics?.map((entry) => (
|
||||
<MissingEntryRow
|
||||
key={`missing-optional-${entry.associatedComponent.name}`}
|
||||
entry={entry}
|
||||
typeLabel="Missing optional metrics"
|
||||
itemType="metrics"
|
||||
/>
|
||||
))}
|
||||
{checksData.missingRequiredAttributes?.map((entry) => (
|
||||
<MissingEntryRow
|
||||
key={`missing-attrs-${entry.associatedComponent.name}`}
|
||||
entry={entry}
|
||||
typeLabel="Missing required attributes"
|
||||
itemType="attributes"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Callout>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
.entryRow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
.missingIcon {
|
||||
color: var(--warning-background);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.learnMoreLink {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
margin-left: 4px;
|
||||
|
||||
svg {
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
--divider-color: var(--callout-warning-border);
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import styles from './MissingEntryRow.module.scss';
|
||||
import { ExternalLink, TriangleAlert } from '@signozhq/icons';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { Divider } from '@signozhq/ui/divider';
|
||||
import type {
|
||||
InframonitoringtypesMissingAttributesComponentEntryDTO,
|
||||
InframonitoringtypesMissingMetricsComponentEntryDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
|
||||
type MissingEntryRowProps =
|
||||
| {
|
||||
entry: InframonitoringtypesMissingMetricsComponentEntryDTO;
|
||||
typeLabel: string;
|
||||
itemType: 'metrics';
|
||||
}
|
||||
| {
|
||||
entry: InframonitoringtypesMissingAttributesComponentEntryDTO;
|
||||
typeLabel: string;
|
||||
itemType: 'attributes';
|
||||
};
|
||||
|
||||
export function MissingEntryRow({
|
||||
entry,
|
||||
typeLabel,
|
||||
itemType,
|
||||
}: MissingEntryRowProps): JSX.Element {
|
||||
const items = itemType === 'metrics' ? entry.metrics : entry.attributes;
|
||||
|
||||
return (
|
||||
<div className={styles.entryRow}>
|
||||
<TriangleAlert size={14} className={styles.missingIcon} />
|
||||
<Typography.Text size="base" weight="medium">
|
||||
{typeLabel}
|
||||
</Typography.Text>
|
||||
<Typography.Text size="base" color="muted">
|
||||
-
|
||||
</Typography.Text>
|
||||
<Typography.Text
|
||||
size="base"
|
||||
weight="semibold"
|
||||
className={styles.entryRowMetric}
|
||||
>
|
||||
{items?.join(', ')}
|
||||
</Typography.Text>
|
||||
<Typography.Text size="base" color="muted">
|
||||
-
|
||||
</Typography.Text>
|
||||
<Typography.Text size="base" italic color="muted">
|
||||
{entry.associatedComponent.name}
|
||||
</Typography.Text>
|
||||
{entry.documentationLink && (
|
||||
<Typography.Link
|
||||
href={entry.documentationLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
size="base"
|
||||
className={styles.learnMoreLink}
|
||||
>
|
||||
Learn here
|
||||
<ExternalLink size={12} />
|
||||
</Typography.Link>
|
||||
)}
|
||||
<Divider className={styles.divider} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
.entryRow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
.presentIcon {
|
||||
color: var(--success-background);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.divider {
|
||||
--divider-color: var(--callout-warning-border);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import styles from './PresentEntryRow.module.scss';
|
||||
import { CircleCheck } from '@signozhq/icons';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { Divider } from '@signozhq/ui/divider';
|
||||
import type {
|
||||
InframonitoringtypesAttributesComponentEntryDTO,
|
||||
InframonitoringtypesMetricsComponentEntryDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
|
||||
type PresentEntryRowProps =
|
||||
| {
|
||||
entry: InframonitoringtypesMetricsComponentEntryDTO;
|
||||
typeLabel: string;
|
||||
itemType: 'metrics';
|
||||
}
|
||||
| {
|
||||
entry: InframonitoringtypesAttributesComponentEntryDTO;
|
||||
typeLabel: string;
|
||||
itemType: 'attributes';
|
||||
};
|
||||
|
||||
export function PresentEntryRow({
|
||||
entry,
|
||||
typeLabel,
|
||||
itemType,
|
||||
}: PresentEntryRowProps): JSX.Element {
|
||||
const items = itemType === 'metrics' ? entry.metrics : entry.attributes;
|
||||
|
||||
return (
|
||||
<div className={styles.entryRow}>
|
||||
<CircleCheck size={14} className={styles.presentIcon} />
|
||||
<Typography.Text size="base" weight="medium">
|
||||
{typeLabel}
|
||||
</Typography.Text>
|
||||
<Typography.Text size="base" color="muted">
|
||||
-
|
||||
</Typography.Text>
|
||||
<Typography.Text
|
||||
size="base"
|
||||
weight="semibold"
|
||||
className={styles.entryRowMetric}
|
||||
>
|
||||
{items?.join(', ')}
|
||||
</Typography.Text>
|
||||
<Typography.Text size="base" color="muted">
|
||||
-
|
||||
</Typography.Text>
|
||||
<Typography.Text size="base" italic color="muted">
|
||||
{entry.associatedComponent.name}
|
||||
</Typography.Text>
|
||||
<Divider className={styles.divider} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import getLocalStorage from 'api/browser/localstorage/get';
|
||||
import {
|
||||
InframonitoringtypesChecksDTO,
|
||||
InframonitoringtypesCheckTypeDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { InfraMonitoringEntity } from '../../../constants';
|
||||
|
||||
export const ENTITY_TO_CHECK_TYPE: Record<
|
||||
InfraMonitoringEntity,
|
||||
InframonitoringtypesCheckTypeDTO
|
||||
> = {
|
||||
[InfraMonitoringEntity.HOSTS]: InframonitoringtypesCheckTypeDTO.hosts,
|
||||
[InfraMonitoringEntity.PODS]: InframonitoringtypesCheckTypeDTO.pods,
|
||||
[InfraMonitoringEntity.NODES]: InframonitoringtypesCheckTypeDTO.nodes,
|
||||
[InfraMonitoringEntity.NAMESPACES]:
|
||||
InframonitoringtypesCheckTypeDTO.namespaces,
|
||||
[InfraMonitoringEntity.CLUSTERS]: InframonitoringtypesCheckTypeDTO.clusters,
|
||||
[InfraMonitoringEntity.DEPLOYMENTS]:
|
||||
InframonitoringtypesCheckTypeDTO.deployments,
|
||||
[InfraMonitoringEntity.STATEFULSETS]:
|
||||
InframonitoringtypesCheckTypeDTO.statefulsets,
|
||||
[InfraMonitoringEntity.DAEMONSETS]:
|
||||
InframonitoringtypesCheckTypeDTO.daemonsets,
|
||||
[InfraMonitoringEntity.JOBS]: InframonitoringtypesCheckTypeDTO.jobs,
|
||||
[InfraMonitoringEntity.VOLUMES]: InframonitoringtypesCheckTypeDTO.volumes,
|
||||
[InfraMonitoringEntity.CONTAINERS]:
|
||||
InframonitoringtypesCheckTypeDTO.kube_containers,
|
||||
};
|
||||
|
||||
export function hasMissingEntries(
|
||||
data: InframonitoringtypesChecksDTO,
|
||||
): boolean {
|
||||
return (
|
||||
(data.missingDefaultEnabledMetrics?.length ?? 0) > 0 ||
|
||||
(data.missingOptionalMetrics?.length ?? 0) > 0 ||
|
||||
(data.missingRequiredAttributes?.length ?? 0) > 0
|
||||
);
|
||||
}
|
||||
|
||||
export function hasAnyEntries(data: InframonitoringtypesChecksDTO): boolean {
|
||||
return (
|
||||
(data.presentDefaultEnabledMetrics?.length ?? 0) > 0 ||
|
||||
(data.presentOptionalMetrics?.length ?? 0) > 0 ||
|
||||
(data.presentRequiredAttributes?.length ?? 0) > 0 ||
|
||||
(data.missingDefaultEnabledMetrics?.length ?? 0) > 0 ||
|
||||
(data.missingOptionalMetrics?.length ?? 0) > 0 ||
|
||||
(data.missingRequiredAttributes?.length ?? 0) > 0
|
||||
);
|
||||
}
|
||||
|
||||
const STORAGE_PREFIX = '@signozhq/k8s-instrumentation-checks-expanded';
|
||||
|
||||
export function getStorageKey(entity: InfraMonitoringEntity): string {
|
||||
return `${STORAGE_PREFIX}-${entity}`;
|
||||
}
|
||||
|
||||
export function readExpandedState(entity: InfraMonitoringEntity): boolean {
|
||||
const stored = getLocalStorage(getStorageKey(entity));
|
||||
return stored === null ? true : stored === 'true';
|
||||
}
|
||||
@@ -1,11 +1,15 @@
|
||||
import { useCallback } from 'react';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import { listClusters } from 'api/generated/services/inframonitoring';
|
||||
import { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { AxiosError } from 'axios';
|
||||
import {
|
||||
InframonitoringtypesClusterRecordDTO,
|
||||
InframonitoringtypesResponseTypeDTO,
|
||||
Querybuildertypesv5OrderDirectionDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { InfraMonitoringEvents } from 'constants/events';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
import K8sBaseDetails, { K8sDetailsFilters } from '../Base/K8sBaseDetails';
|
||||
import { K8sBaseList } from '../Base/K8sBaseList';
|
||||
@@ -14,7 +18,9 @@ import { InfraMonitoringEntity } from '../constants';
|
||||
import {
|
||||
clusterWidgetInfo,
|
||||
getClusterMetricsQueryPayload,
|
||||
k8sClusterDetailsCountsConfig,
|
||||
k8sClusterDetailsMetadataConfig,
|
||||
k8sClusterGetCountsFilterExpression,
|
||||
k8sClusterGetEntityName,
|
||||
k8sClusterGetSelectedItemExpression,
|
||||
k8sClusterInitialEventsExpression,
|
||||
@@ -67,13 +73,12 @@ function K8sClustersList({
|
||||
warning: data.warning,
|
||||
};
|
||||
} catch (error) {
|
||||
const errMsg =
|
||||
error instanceof Error ? error.message : 'Failed to fetch clusters';
|
||||
return {
|
||||
type: 'list' as const,
|
||||
records: [] as InframonitoringtypesClusterRecordDTO[],
|
||||
total: 0,
|
||||
error: errMsg,
|
||||
error:
|
||||
convertToApiError(error as AxiosError<RenderErrorResponseDTO>) ?? null,
|
||||
};
|
||||
}
|
||||
},
|
||||
@@ -86,7 +91,7 @@ function K8sClustersList({
|
||||
signal?: AbortSignal,
|
||||
): Promise<{
|
||||
data: InframonitoringtypesClusterRecordDTO | null;
|
||||
error?: string | null;
|
||||
error?: APIError | null;
|
||||
}> => {
|
||||
try {
|
||||
const response = await listClusters(
|
||||
@@ -104,11 +109,10 @@ function K8sClustersList({
|
||||
data: response.data.records.length > 0 ? response.data.records[0] : null,
|
||||
};
|
||||
} catch (error) {
|
||||
const errMsg =
|
||||
error instanceof Error ? error.message : 'Failed to fetch cluster';
|
||||
return {
|
||||
data: null,
|
||||
error: errMsg,
|
||||
error:
|
||||
convertToApiError(error as AxiosError<RenderErrorResponseDTO>) ?? null,
|
||||
};
|
||||
}
|
||||
},
|
||||
@@ -136,6 +140,8 @@ function K8sClustersList({
|
||||
getInitialLogTracesExpression={k8sClusterInitialLogTracesExpression}
|
||||
getInitialEventsExpression={k8sClusterInitialEventsExpression}
|
||||
metadataConfig={k8sClusterDetailsMetadataConfig}
|
||||
countsConfig={k8sClusterDetailsCountsConfig}
|
||||
getCountsFilterExpression={k8sClusterGetCountsFilterExpression}
|
||||
entityWidgetInfo={clusterWidgetInfo}
|
||||
getEntityQueryPayload={getClusterMetricsQueryPayload}
|
||||
queryKeyPrefix="cluster"
|
||||
|
||||
@@ -6,9 +6,15 @@ import { EQueryType } from 'types/common/dashboard';
|
||||
import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { K8sDetailsMetadataConfig } from '../Base/K8sBaseDetails';
|
||||
import {
|
||||
K8sDetailsCountConfig,
|
||||
K8sDetailsMetadataConfig,
|
||||
} from '../Base/K8sBaseDetails';
|
||||
import { formatValueForExpression } from 'components/QueryBuilderV2/utils';
|
||||
import { INFRA_MONITORING_ATTR_KEYS } from '../constants';
|
||||
import {
|
||||
INFRA_MONITORING_ATTR_KEYS,
|
||||
InfraMonitoringEntity,
|
||||
} from '../constants';
|
||||
import { SelectedItemParams } from '../hooks';
|
||||
import {
|
||||
buildEventsExpression,
|
||||
@@ -23,6 +29,40 @@ export const k8sClusterGetSelectedItemExpression = (
|
||||
export const k8sClusterDetailsMetadataConfig: K8sDetailsMetadataConfig<InframonitoringtypesClusterRecordDTO>[] =
|
||||
[{ label: 'Cluster Name', getValue: (p): string => p.clusterName || '' }];
|
||||
|
||||
export const k8sClusterDetailsCountsConfig: K8sDetailsCountConfig<InframonitoringtypesClusterRecordDTO>[] =
|
||||
[
|
||||
{
|
||||
label: 'Namespaces',
|
||||
getValue: (p): number => p.counts?.namespaces ?? 0,
|
||||
targetCategory: InfraMonitoringEntity.NAMESPACES,
|
||||
},
|
||||
{
|
||||
label: 'Nodes',
|
||||
getValue: (p): number => p.counts?.nodes ?? 0,
|
||||
targetCategory: InfraMonitoringEntity.NODES,
|
||||
},
|
||||
{
|
||||
label: 'Deployments',
|
||||
getValue: (p): number => p.counts?.deployments ?? 0,
|
||||
targetCategory: InfraMonitoringEntity.DEPLOYMENTS,
|
||||
},
|
||||
{
|
||||
label: 'StatefulSets',
|
||||
getValue: (p): number => p.counts?.statefulSets ?? 0,
|
||||
targetCategory: InfraMonitoringEntity.STATEFULSETS,
|
||||
},
|
||||
{
|
||||
label: 'DaemonSets',
|
||||
getValue: (p): number => p.counts?.daemonSets ?? 0,
|
||||
targetCategory: InfraMonitoringEntity.DAEMONSETS,
|
||||
},
|
||||
{
|
||||
label: 'Jobs',
|
||||
getValue: (p): number => p.counts?.jobs ?? 0,
|
||||
targetCategory: InfraMonitoringEntity.JOBS,
|
||||
},
|
||||
];
|
||||
|
||||
export const k8sClusterInitialEventsExpression = (
|
||||
item: InframonitoringtypesClusterRecordDTO,
|
||||
): string =>
|
||||
@@ -43,38 +83,54 @@ export const k8sClusterGetEntityName = (
|
||||
item: InframonitoringtypesClusterRecordDTO,
|
||||
): string => item.clusterName || '';
|
||||
|
||||
export const k8sClusterGetCountsFilterExpression = (
|
||||
item: InframonitoringtypesClusterRecordDTO,
|
||||
): string =>
|
||||
`k8s.cluster.name = ${formatValueForExpression(item.clusterName ?? '')}`;
|
||||
|
||||
export const clusterWidgetInfo = [
|
||||
{
|
||||
title: 'CPU Usage, allocatable',
|
||||
yAxisUnit: '',
|
||||
docPath:
|
||||
'/infrastructure-monitoring/kubernetes/clusters/#cpu-usage-allocatable',
|
||||
},
|
||||
{
|
||||
title: 'Memory Usage, allocatable',
|
||||
yAxisUnit: 'bytes',
|
||||
docPath:
|
||||
'/infrastructure-monitoring/kubernetes/clusters/#memory-usage-allocatable',
|
||||
},
|
||||
{
|
||||
title: 'Ready Nodes',
|
||||
yAxisUnit: '',
|
||||
docPath: '/infrastructure-monitoring/kubernetes/clusters/#ready-nodes',
|
||||
},
|
||||
{
|
||||
title: 'NotReady Nodes',
|
||||
yAxisUnit: '',
|
||||
docPath: '/infrastructure-monitoring/kubernetes/clusters/#notready-nodes',
|
||||
},
|
||||
{
|
||||
title: 'Deployments available and desired',
|
||||
yAxisUnit: '',
|
||||
docPath:
|
||||
'/infrastructure-monitoring/kubernetes/clusters/#deployments-available-and-desired',
|
||||
},
|
||||
{
|
||||
title: 'Statefulset pods',
|
||||
yAxisUnit: '',
|
||||
docPath: '/infrastructure-monitoring/kubernetes/clusters/#statefulset-pods',
|
||||
},
|
||||
{
|
||||
title: 'Daemonset nodes',
|
||||
yAxisUnit: '',
|
||||
docPath: '/infrastructure-monitoring/kubernetes/clusters/#daemonset-nodes',
|
||||
},
|
||||
{
|
||||
title: 'Jobs',
|
||||
yAxisUnit: '',
|
||||
docPath: '/infrastructure-monitoring/kubernetes/clusters/#jobs',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { useCallback } from 'react';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import { listDaemonSets } from 'api/generated/services/inframonitoring';
|
||||
import { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { AxiosError } from 'axios';
|
||||
import {
|
||||
InframonitoringtypesDaemonSetRecordDTO,
|
||||
InframonitoringtypesResponseTypeDTO,
|
||||
Querybuildertypesv5OrderDirectionDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { InfraMonitoringEvents } from 'constants/events';
|
||||
import APIError from 'types/api/error';
|
||||
import K8sBaseDetails, { K8sDetailsFilters } from '../Base/K8sBaseDetails';
|
||||
import { K8sBaseList } from '../Base/K8sBaseList';
|
||||
import { K8sBaseFilters } from '../Base/types';
|
||||
@@ -65,13 +69,12 @@ function K8sDaemonSetsList({
|
||||
warning: data.warning,
|
||||
};
|
||||
} catch (error) {
|
||||
const errMsg =
|
||||
error instanceof Error ? error.message : 'Failed to fetch daemonsets';
|
||||
return {
|
||||
type: 'list' as const,
|
||||
records: [] as InframonitoringtypesDaemonSetRecordDTO[],
|
||||
total: 0,
|
||||
error: errMsg,
|
||||
error:
|
||||
convertToApiError(error as AxiosError<RenderErrorResponseDTO>) ?? null,
|
||||
};
|
||||
}
|
||||
},
|
||||
@@ -83,7 +86,7 @@ function K8sDaemonSetsList({
|
||||
signal?: AbortSignal,
|
||||
): Promise<{
|
||||
data: InframonitoringtypesDaemonSetRecordDTO | null;
|
||||
error?: string | null;
|
||||
error?: APIError | null;
|
||||
}> => {
|
||||
try {
|
||||
const response = await listDaemonSets(
|
||||
@@ -100,11 +103,10 @@ function K8sDaemonSetsList({
|
||||
data: response.data.records.length > 0 ? response.data.records[0] : null,
|
||||
};
|
||||
} catch (error) {
|
||||
const errMsg =
|
||||
error instanceof Error ? error.message : 'Failed to fetch daemonset';
|
||||
return {
|
||||
data: null,
|
||||
error: errMsg,
|
||||
error:
|
||||
convertToApiError(error as AxiosError<RenderErrorResponseDTO>) ?? null,
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
@@ -71,18 +71,25 @@ export const daemonSetWidgetInfo = [
|
||||
{
|
||||
title: 'CPU usage, request, limits',
|
||||
yAxisUnit: '',
|
||||
docPath:
|
||||
'/infrastructure-monitoring/kubernetes/daemonsets/#cpu-usage-request-limits',
|
||||
},
|
||||
{
|
||||
title: 'Memory usage, request, limits',
|
||||
yAxisUnit: 'bytes',
|
||||
docPath:
|
||||
'/infrastructure-monitoring/kubernetes/daemonsets/#memory-usage-request-limits',
|
||||
},
|
||||
{
|
||||
title: 'Network IO',
|
||||
yAxisUnit: 'binBps',
|
||||
docPath: '/infrastructure-monitoring/kubernetes/daemonsets/#network-io',
|
||||
},
|
||||
{
|
||||
title: 'Network errors count',
|
||||
yAxisUnit: '',
|
||||
docPath:
|
||||
'/infrastructure-monitoring/kubernetes/daemonsets/#network-errors-count',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -140,10 +140,10 @@ export const k8sDaemonSetsColumnsConfig: DaemonSetTableColumnConfig[] = [
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'node_status',
|
||||
id: 'scheduled_nodes',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/daemonsets#node-status">
|
||||
Node Status
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/daemonsets#scheduled-nodes">
|
||||
Scheduled Nodes
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.currentNodes,
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { useCallback } from 'react';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import { listDeployments } from 'api/generated/services/inframonitoring';
|
||||
import { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { AxiosError } from 'axios';
|
||||
import {
|
||||
InframonitoringtypesDeploymentRecordDTO,
|
||||
InframonitoringtypesResponseTypeDTO,
|
||||
Querybuildertypesv5OrderDirectionDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { InfraMonitoringEvents } from 'constants/events';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
import K8sBaseDetails, { K8sDetailsFilters } from '../Base/K8sBaseDetails';
|
||||
import { K8sBaseList } from '../Base/K8sBaseList';
|
||||
@@ -68,13 +72,12 @@ function K8sDeploymentsList({
|
||||
warning: data.warning,
|
||||
};
|
||||
} catch (error) {
|
||||
const errMsg =
|
||||
error instanceof Error ? error.message : 'Failed to fetch deployments';
|
||||
return {
|
||||
type: 'list' as const,
|
||||
records: [] as InframonitoringtypesDeploymentRecordDTO[],
|
||||
total: 0,
|
||||
error: errMsg,
|
||||
error:
|
||||
convertToApiError(error as AxiosError<RenderErrorResponseDTO>) ?? null,
|
||||
};
|
||||
}
|
||||
},
|
||||
@@ -87,7 +90,7 @@ function K8sDeploymentsList({
|
||||
signal?: AbortSignal,
|
||||
): Promise<{
|
||||
data: InframonitoringtypesDeploymentRecordDTO | null;
|
||||
error?: string | null;
|
||||
error?: APIError | null;
|
||||
}> => {
|
||||
try {
|
||||
const response = await listDeployments(
|
||||
@@ -105,11 +108,10 @@ function K8sDeploymentsList({
|
||||
data: response.data.records.length > 0 ? response.data.records[0] : null,
|
||||
};
|
||||
} catch (error) {
|
||||
const errMsg =
|
||||
error instanceof Error ? error.message : 'Failed to fetch deployment';
|
||||
return {
|
||||
data: null,
|
||||
error: errMsg,
|
||||
error:
|
||||
convertToApiError(error as AxiosError<RenderErrorResponseDTO>) ?? null,
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
@@ -71,18 +71,25 @@ export const deploymentWidgetInfo = [
|
||||
{
|
||||
title: 'CPU usage, request, limits',
|
||||
yAxisUnit: '',
|
||||
docPath:
|
||||
'/infrastructure-monitoring/kubernetes/deployments/#cpu-usage-request-limits',
|
||||
},
|
||||
{
|
||||
title: 'Memory usage, request, limits',
|
||||
yAxisUnit: 'bytes',
|
||||
docPath:
|
||||
'/infrastructure-monitoring/kubernetes/deployments/#memory-usage-request-limits',
|
||||
},
|
||||
{
|
||||
title: 'Network IO',
|
||||
yAxisUnit: 'binBps',
|
||||
docPath: '/infrastructure-monitoring/kubernetes/deployments/#network-io',
|
||||
},
|
||||
{
|
||||
title: 'Network error count',
|
||||
yAxisUnit: '',
|
||||
docPath:
|
||||
'/infrastructure-monitoring/kubernetes/deployments/#network-error-count',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -133,10 +133,10 @@ export const k8sDeploymentsColumnsConfig: TableColumnDef<InframonitoringtypesDep
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'replica_status',
|
||||
id: 'pod_replicas',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/deployments#replica-status">
|
||||
Replica Status
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/deployments#pod-replicas">
|
||||
Pod Replicas
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.availablePods,
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
.chartHeader {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-2);
|
||||
}
|
||||
|
||||
.infoIcon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: var(--l2-foreground);
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: var(--text-slate-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.metricsExplorerLink {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: var(--l2-foreground);
|
||||
transition: opacity 0.2s;
|
||||
|
||||
&:hover {
|
||||
color: var(--l3-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
.chartHeaderLabel {
|
||||
font-size: var(--periscope-font-size-base);
|
||||
color: var(--l2-foreground);
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Compass, Info } from '@signozhq/icons';
|
||||
import { Tooltip } from 'antd';
|
||||
|
||||
import styles from './ChartHeader.module.scss';
|
||||
|
||||
const DOCS_BASE_URL = `${process.env.DOCS_BASE_URL}/docs`;
|
||||
|
||||
interface ChartHeaderProps {
|
||||
title: string;
|
||||
docPath?: string;
|
||||
tooltip?: string;
|
||||
metricsExplorerUrl?: string;
|
||||
metricsExplorerTestId?: string;
|
||||
}
|
||||
|
||||
function ChartHeader({
|
||||
title,
|
||||
docPath,
|
||||
tooltip,
|
||||
metricsExplorerUrl,
|
||||
metricsExplorerTestId = 'open-metrics-explorer',
|
||||
}: ChartHeaderProps): JSX.Element {
|
||||
const renderInfoIcon = (): React.ReactNode => {
|
||||
if (docPath) {
|
||||
const tooltipTitle = tooltip || 'Not sure what this represents?';
|
||||
return (
|
||||
<Tooltip
|
||||
arrow
|
||||
title={
|
||||
<>
|
||||
{tooltipTitle}{' '}
|
||||
<a
|
||||
href={`${DOCS_BASE_URL}${docPath}`}
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
onClick={(e): void => e.stopPropagation()}
|
||||
>
|
||||
Learn more.
|
||||
</a>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<span className={styles.infoIcon} data-testid="chart-header-info-icon">
|
||||
<Info size="md" />
|
||||
</span>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
if (tooltip) {
|
||||
return (
|
||||
<Tooltip title={tooltip}>
|
||||
<span className={styles.infoIcon} data-testid="chart-header-info-icon">
|
||||
<Info size="md" />
|
||||
</span>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.chartHeader} data-testid="chart-header">
|
||||
<span className={styles.chartHeaderLabel}>{title}</span>
|
||||
{renderInfoIcon()}
|
||||
{metricsExplorerUrl && (
|
||||
<Tooltip title="Open in Metrics Explorer">
|
||||
<Link
|
||||
to={metricsExplorerUrl}
|
||||
className={styles.metricsExplorerLink}
|
||||
data-testid={metricsExplorerTestId}
|
||||
>
|
||||
<Compass size={14} />
|
||||
</Link>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ChartHeader;
|
||||
@@ -14,28 +14,6 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.entityMetricsTitleContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.entityMetricsTitle {
|
||||
font-size: var(--periscope-font-size-base);
|
||||
color: var(--l2-foreground);
|
||||
}
|
||||
|
||||
.metricsExplorerLink {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--l2-foreground);
|
||||
transition: opacity 0.2s;
|
||||
|
||||
&:hover {
|
||||
color: var(--l3-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
.metricsHeader {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { useCallback, useMemo, useRef } from 'react';
|
||||
import { UseQueryResult } from 'react-query';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Compass } from '@signozhq/icons';
|
||||
import { Skeleton, Tooltip } from 'antd';
|
||||
import { Skeleton } from 'antd';
|
||||
import cx from 'classnames';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import TimeSeries from 'container/DashboardContainer/visualization/charts/TimeSeries/TimeSeries';
|
||||
@@ -24,6 +22,7 @@ import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { getMetricsExplorerUrl } from 'utils/explorerUtils';
|
||||
|
||||
import { buildEntityMetricsChartConfig } from './configBuilder';
|
||||
import ChartHeader from './ChartHeader';
|
||||
|
||||
import { useEntityMetrics } from './hooks';
|
||||
import { isKeyNotFoundError } from '../utils';
|
||||
@@ -47,6 +46,7 @@ interface EntityMetricsProps<T> {
|
||||
entityWidgetInfo: {
|
||||
title: string;
|
||||
yAxisUnit: string;
|
||||
docPath?: string;
|
||||
}[];
|
||||
getEntityQueryPayload: (
|
||||
node: T,
|
||||
@@ -207,31 +207,24 @@ function EntityMetrics<T>({
|
||||
key={entityWidgetInfo[idx].title}
|
||||
className={styles.entityMetricsCol}
|
||||
>
|
||||
<div className={styles.entityMetricsTitleContainer}>
|
||||
<span className={styles.entityMetricsTitle}>
|
||||
{entityWidgetInfo[idx].title}
|
||||
</span>
|
||||
{queryPayloads[idx] &&
|
||||
queryPayloads[idx].graphType !== PANEL_TYPES.TABLE && (
|
||||
<Tooltip title="Open in Metrics Explorer">
|
||||
<Link
|
||||
to={getMetricsExplorerUrl({
|
||||
query: queryPayloads[idx].query,
|
||||
...(selectedInterval && selectedInterval !== 'custom'
|
||||
? { relativeTime: selectedInterval }
|
||||
: {
|
||||
startTimeMs: timeRange.startTime * 1000,
|
||||
endTimeMs: timeRange.endTime * 1000,
|
||||
}),
|
||||
})}
|
||||
className={styles.metricsExplorerLink}
|
||||
data-testid={`open-metrics-explorer-${idx}`}
|
||||
>
|
||||
<Compass size={14} />
|
||||
</Link>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
<ChartHeader
|
||||
title={entityWidgetInfo[idx].title}
|
||||
docPath={entityWidgetInfo[idx].docPath}
|
||||
metricsExplorerUrl={
|
||||
queryPayloads[idx] && queryPayloads[idx].graphType !== PANEL_TYPES.TABLE
|
||||
? getMetricsExplorerUrl({
|
||||
query: queryPayloads[idx].query,
|
||||
...(selectedInterval && selectedInterval !== 'custom'
|
||||
? { relativeTime: selectedInterval }
|
||||
: {
|
||||
startTimeMs: timeRange.startTime * 1000,
|
||||
endTimeMs: timeRange.endTime * 1000,
|
||||
}),
|
||||
})
|
||||
: undefined
|
||||
}
|
||||
metricsExplorerTestId={`open-metrics-explorer-${idx}`}
|
||||
/>
|
||||
<div className={styles.entityMetricsCard} ref={graphRef}>
|
||||
{renderCardContent(query, idx)}
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { useCallback } from 'react';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import { listJobs } from 'api/generated/services/inframonitoring';
|
||||
import { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { AxiosError } from 'axios';
|
||||
import {
|
||||
InframonitoringtypesJobRecordDTO,
|
||||
InframonitoringtypesResponseTypeDTO,
|
||||
Querybuildertypesv5OrderDirectionDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { InfraMonitoringEvents } from 'constants/events';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
import K8sBaseDetails, { K8sDetailsFilters } from '../Base/K8sBaseDetails';
|
||||
import { K8sBaseList } from '../Base/K8sBaseList';
|
||||
@@ -68,13 +72,12 @@ function K8sJobsList({
|
||||
warning: data.warning,
|
||||
};
|
||||
} catch (error) {
|
||||
const errMsg =
|
||||
error instanceof Error ? error.message : 'Failed to fetch jobs';
|
||||
return {
|
||||
type: 'list' as const,
|
||||
records: [] as InframonitoringtypesJobRecordDTO[],
|
||||
total: 0,
|
||||
error: errMsg,
|
||||
error:
|
||||
convertToApiError(error as AxiosError<RenderErrorResponseDTO>) ?? null,
|
||||
};
|
||||
}
|
||||
},
|
||||
@@ -87,7 +90,7 @@ function K8sJobsList({
|
||||
signal?: AbortSignal,
|
||||
): Promise<{
|
||||
data: InframonitoringtypesJobRecordDTO | null;
|
||||
error?: string | null;
|
||||
error?: APIError | null;
|
||||
}> => {
|
||||
try {
|
||||
const response = await listJobs(
|
||||
@@ -105,11 +108,10 @@ function K8sJobsList({
|
||||
data: response.data.records.length > 0 ? response.data.records[0] : null,
|
||||
};
|
||||
} catch (error) {
|
||||
const errMsg =
|
||||
error instanceof Error ? error.message : 'Failed to fetch job';
|
||||
return {
|
||||
data: null,
|
||||
error: errMsg,
|
||||
error:
|
||||
convertToApiError(error as AxiosError<RenderErrorResponseDTO>) ?? null,
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
@@ -70,18 +70,22 @@ export const jobWidgetInfo = [
|
||||
{
|
||||
title: 'CPU usage',
|
||||
yAxisUnit: '',
|
||||
docPath: '/infrastructure-monitoring/kubernetes/jobs/#cpu-usage',
|
||||
},
|
||||
{
|
||||
title: 'Memory Usage',
|
||||
yAxisUnit: 'bytes',
|
||||
docPath: '/infrastructure-monitoring/kubernetes/jobs/#memory-usage',
|
||||
},
|
||||
{
|
||||
title: 'Network IO',
|
||||
yAxisUnit: 'binBps',
|
||||
docPath: '/infrastructure-monitoring/kubernetes/jobs/#network-io',
|
||||
},
|
||||
{
|
||||
title: 'Network errors count',
|
||||
yAxisUnit: '',
|
||||
docPath: '/infrastructure-monitoring/kubernetes/jobs/#network-errors-count',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -132,10 +132,10 @@ export const k8sJobsColumnsConfig: JobTableColumnConfig[] = [
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'completion_status',
|
||||
id: 'completion',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/jobs#completion-status">
|
||||
Completion Status
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/jobs#completion">
|
||||
Completions
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.successfulPods,
|
||||
@@ -155,7 +155,7 @@ export const k8sJobsColumnsConfig: JobTableColumnConfig[] = [
|
||||
{
|
||||
value: row.desiredSuccessfulPods,
|
||||
label: 'Desired',
|
||||
color: Color.BG_ROBIN_500,
|
||||
color: Color.BG_AMBER_500,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { useCallback } from 'react';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import { listNamespaces } from 'api/generated/services/inframonitoring';
|
||||
import { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { AxiosError } from 'axios';
|
||||
import {
|
||||
InframonitoringtypesNamespaceRecordDTO,
|
||||
InframonitoringtypesResponseTypeDTO,
|
||||
Querybuildertypesv5OrderDirectionDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { InfraMonitoringEvents } from 'constants/events';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
import K8sBaseDetails, { K8sDetailsFilters } from '../Base/K8sBaseDetails';
|
||||
import { K8sBaseList } from '../Base/K8sBaseList';
|
||||
@@ -14,7 +18,9 @@ import { InfraMonitoringEntity } from '../constants';
|
||||
import { SelectedItemParams } from '../hooks';
|
||||
import {
|
||||
getNamespaceMetricsQueryPayload,
|
||||
k8sNamespaceDetailsCountsConfig,
|
||||
k8sNamespaceDetailsMetadataConfig,
|
||||
k8sNamespaceGetCountsFilterExpression,
|
||||
k8sNamespaceGetEntityName,
|
||||
k8sNamespaceGetSelectedItemExpression,
|
||||
k8sNamespaceInitialEventsExpression,
|
||||
@@ -68,13 +74,12 @@ function K8sNamespacesList({
|
||||
warning: data.warning,
|
||||
};
|
||||
} catch (error) {
|
||||
const errMsg =
|
||||
error instanceof Error ? error.message : 'Failed to fetch namespaces';
|
||||
return {
|
||||
type: 'list' as const,
|
||||
records: [] as InframonitoringtypesNamespaceRecordDTO[],
|
||||
total: 0,
|
||||
error: errMsg,
|
||||
error:
|
||||
convertToApiError(error as AxiosError<RenderErrorResponseDTO>) ?? null,
|
||||
};
|
||||
}
|
||||
},
|
||||
@@ -87,7 +92,7 @@ function K8sNamespacesList({
|
||||
signal?: AbortSignal,
|
||||
): Promise<{
|
||||
data: InframonitoringtypesNamespaceRecordDTO | null;
|
||||
error?: string | null;
|
||||
error?: APIError | null;
|
||||
}> => {
|
||||
try {
|
||||
const response = await listNamespaces(
|
||||
@@ -105,11 +110,10 @@ function K8sNamespacesList({
|
||||
data: response.data.records.length > 0 ? response.data.records[0] : null,
|
||||
};
|
||||
} catch (error) {
|
||||
const errMsg =
|
||||
error instanceof Error ? error.message : 'Failed to fetch namespace';
|
||||
return {
|
||||
data: null,
|
||||
error: errMsg,
|
||||
error:
|
||||
convertToApiError(error as AxiosError<RenderErrorResponseDTO>) ?? null,
|
||||
};
|
||||
}
|
||||
},
|
||||
@@ -137,6 +141,8 @@ function K8sNamespacesList({
|
||||
getInitialLogTracesExpression={k8sNamespaceInitialLogTracesExpression}
|
||||
getInitialEventsExpression={k8sNamespaceInitialEventsExpression}
|
||||
metadataConfig={k8sNamespaceDetailsMetadataConfig}
|
||||
countsConfig={k8sNamespaceDetailsCountsConfig}
|
||||
getCountsFilterExpression={k8sNamespaceGetCountsFilterExpression}
|
||||
entityWidgetInfo={namespaceWidgetInfo}
|
||||
getEntityQueryPayload={getNamespaceMetricsQueryPayload}
|
||||
queryKeyPrefix="namespace"
|
||||
|
||||
@@ -6,9 +6,16 @@ import { EQueryType } from 'types/common/dashboard';
|
||||
import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { K8sDetailsMetadataConfig } from '../Base/K8sBaseDetails';
|
||||
import { INFRA_MONITORING_ATTR_KEYS } from '../constants';
|
||||
import {
|
||||
K8sDetailsCountConfig,
|
||||
K8sDetailsMetadataConfig,
|
||||
} from '../Base/K8sBaseDetails';
|
||||
import {
|
||||
INFRA_MONITORING_ATTR_KEYS,
|
||||
InfraMonitoringEntity,
|
||||
} from '../constants';
|
||||
import { SelectedItemParams } from '../hooks';
|
||||
import { formatValueForExpression } from 'components/QueryBuilderV2/utils';
|
||||
import {
|
||||
buildEventsExpression,
|
||||
buildExpressionFromSelectedItemParams,
|
||||
@@ -33,6 +40,30 @@ export const k8sNamespaceDetailsMetadataConfig: K8sDetailsMetadataConfig<Inframo
|
||||
},
|
||||
];
|
||||
|
||||
export const k8sNamespaceDetailsCountsConfig: K8sDetailsCountConfig<InframonitoringtypesNamespaceRecordDTO>[] =
|
||||
[
|
||||
{
|
||||
label: 'Deployments',
|
||||
getValue: (p): number => p.counts?.deployments ?? 0,
|
||||
targetCategory: InfraMonitoringEntity.DEPLOYMENTS,
|
||||
},
|
||||
{
|
||||
label: 'StatefulSets',
|
||||
getValue: (p): number => p.counts?.statefulSets ?? 0,
|
||||
targetCategory: InfraMonitoringEntity.STATEFULSETS,
|
||||
},
|
||||
{
|
||||
label: 'DaemonSets',
|
||||
getValue: (p): number => p.counts?.daemonSets ?? 0,
|
||||
targetCategory: InfraMonitoringEntity.DAEMONSETS,
|
||||
},
|
||||
{
|
||||
label: 'Jobs',
|
||||
getValue: (p): number => p.counts?.jobs ?? 0,
|
||||
targetCategory: InfraMonitoringEntity.JOBS,
|
||||
},
|
||||
];
|
||||
|
||||
export const k8sNamespaceInitialEventsExpression = (
|
||||
item: InframonitoringtypesNamespaceRecordDTO,
|
||||
): string =>
|
||||
@@ -55,46 +86,78 @@ export const k8sNamespaceGetEntityName = (
|
||||
item: InframonitoringtypesNamespaceRecordDTO,
|
||||
): string => item.namespaceName || '';
|
||||
|
||||
export const k8sNamespaceGetCountsFilterExpression = (
|
||||
item: InframonitoringtypesNamespaceRecordDTO,
|
||||
): string => {
|
||||
const clusterName = item.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME];
|
||||
const clauses: string[] = [];
|
||||
|
||||
if (clusterName) {
|
||||
clauses.push(
|
||||
`${INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME} = ${formatValueForExpression(clusterName)}`,
|
||||
);
|
||||
}
|
||||
if (item.namespaceName) {
|
||||
clauses.push(
|
||||
`${INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME} = ${formatValueForExpression(item.namespaceName)}`,
|
||||
);
|
||||
}
|
||||
|
||||
return clauses.join(' AND ');
|
||||
};
|
||||
|
||||
export const namespaceWidgetInfo = [
|
||||
{
|
||||
title: 'CPU Usage (cores)',
|
||||
yAxisUnit: '',
|
||||
docPath: '/infrastructure-monitoring/kubernetes/namespaces/#cpu-usage-cores',
|
||||
},
|
||||
{
|
||||
title: 'Memory Usage (bytes)',
|
||||
yAxisUnit: 'bytes',
|
||||
docPath:
|
||||
'/infrastructure-monitoring/kubernetes/namespaces/#memory-usage-bytes',
|
||||
},
|
||||
{
|
||||
title: 'Pods CPU (top 10)',
|
||||
yAxisUnit: '',
|
||||
docPath: '/infrastructure-monitoring/kubernetes/namespaces/#pods-cpu-top-10',
|
||||
},
|
||||
{
|
||||
title: 'Pods Memory (top 10)',
|
||||
yAxisUnit: 'bytes',
|
||||
docPath:
|
||||
'/infrastructure-monitoring/kubernetes/namespaces/#pods-memory-top-10',
|
||||
},
|
||||
{
|
||||
title: 'Network rate',
|
||||
yAxisUnit: 'binBps',
|
||||
docPath: '/infrastructure-monitoring/kubernetes/namespaces/#network-rate',
|
||||
},
|
||||
{
|
||||
title: 'Network errors',
|
||||
yAxisUnit: '',
|
||||
docPath: '/infrastructure-monitoring/kubernetes/namespaces/#network-errors',
|
||||
},
|
||||
{
|
||||
title: 'StatefulSets',
|
||||
title: 'StatefulSets (pods)',
|
||||
yAxisUnit: '',
|
||||
docPath: '/infrastructure-monitoring/kubernetes/namespaces/#statefulsets',
|
||||
},
|
||||
{
|
||||
title: 'ReplicaSets',
|
||||
title: 'ReplicaSets (pods)',
|
||||
yAxisUnit: '',
|
||||
docPath: '/infrastructure-monitoring/kubernetes/namespaces/#replicasets',
|
||||
},
|
||||
{
|
||||
title: 'DaemonSets',
|
||||
title: 'DaemonSets (nodes)',
|
||||
yAxisUnit: '',
|
||||
docPath: '/infrastructure-monitoring/kubernetes/namespaces/#daemonsets',
|
||||
},
|
||||
{
|
||||
title: 'Deployments',
|
||||
title: 'Deployments (pods)',
|
||||
yAxisUnit: '',
|
||||
docPath: '/infrastructure-monitoring/kubernetes/namespaces/#deployments',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { useCallback } from 'react';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import { listNodes } from 'api/generated/services/inframonitoring';
|
||||
import { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { AxiosError } from 'axios';
|
||||
import {
|
||||
InframonitoringtypesNodeRecordDTO,
|
||||
InframonitoringtypesResponseTypeDTO,
|
||||
Querybuildertypesv5OrderDirectionDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { InfraMonitoringEvents } from 'constants/events';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
import K8sBaseDetails, { K8sDetailsFilters } from '../Base/K8sBaseDetails';
|
||||
import { K8sBaseList } from '../Base/K8sBaseList';
|
||||
@@ -67,13 +71,12 @@ function K8sNodesList({
|
||||
warning: data.warning,
|
||||
};
|
||||
} catch (error) {
|
||||
const errMsg =
|
||||
error instanceof Error ? error.message : 'Failed to fetch nodes';
|
||||
return {
|
||||
type: 'list' as const,
|
||||
records: [] as InframonitoringtypesNodeRecordDTO[],
|
||||
total: 0,
|
||||
error: errMsg,
|
||||
error:
|
||||
convertToApiError(error as AxiosError<RenderErrorResponseDTO>) ?? null,
|
||||
};
|
||||
}
|
||||
},
|
||||
@@ -86,7 +89,7 @@ function K8sNodesList({
|
||||
signal?: AbortSignal,
|
||||
): Promise<{
|
||||
data: InframonitoringtypesNodeRecordDTO | null;
|
||||
error?: string | null;
|
||||
error?: APIError | null;
|
||||
}> => {
|
||||
try {
|
||||
const response = await listNodes(
|
||||
@@ -104,11 +107,10 @@ function K8sNodesList({
|
||||
data: response.data.records.length > 0 ? response.data.records[0] : null,
|
||||
};
|
||||
} catch (error) {
|
||||
const errMsg =
|
||||
error instanceof Error ? error.message : 'Failed to fetch node';
|
||||
return {
|
||||
data: null,
|
||||
error: errMsg,
|
||||
error:
|
||||
convertToApiError(error as AxiosError<RenderErrorResponseDTO>) ?? null,
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
@@ -57,42 +57,53 @@ export const nodeWidgetInfo = [
|
||||
{
|
||||
title: 'CPU Usage (cores)',
|
||||
yAxisUnit: '',
|
||||
docPath: '/infrastructure-monitoring/kubernetes/nodes/#cpu-usage-cores',
|
||||
},
|
||||
{
|
||||
title: 'Memory Usage (bytes)',
|
||||
yAxisUnit: 'bytes',
|
||||
docPath: '/infrastructure-monitoring/kubernetes/nodes/#memory-usage-bytes',
|
||||
},
|
||||
{
|
||||
title: 'CPU Usage (%)',
|
||||
yAxisUnit: 'percentunit',
|
||||
docPath: '/infrastructure-monitoring/kubernetes/nodes/#cpu-usage-',
|
||||
},
|
||||
{
|
||||
title: 'Memory Usage (%)',
|
||||
yAxisUnit: 'percentunit',
|
||||
docPath: '/infrastructure-monitoring/kubernetes/nodes/#memory-usage-',
|
||||
},
|
||||
{
|
||||
title: 'Pods by CPU (top 10)',
|
||||
yAxisUnit: '',
|
||||
docPath: '/infrastructure-monitoring/kubernetes/nodes/#pods-by-cpu-top-10',
|
||||
},
|
||||
{
|
||||
title: 'Pods by Memory (top 10)',
|
||||
yAxisUnit: 'bytes',
|
||||
docPath: '/infrastructure-monitoring/kubernetes/nodes/#pods-by-memory-top-10',
|
||||
},
|
||||
{
|
||||
title: 'Network error count',
|
||||
yAxisUnit: '',
|
||||
docPath: '/infrastructure-monitoring/kubernetes/nodes/#network-error-count',
|
||||
},
|
||||
{
|
||||
title: 'Network IO rate',
|
||||
yAxisUnit: 'binBps',
|
||||
docPath: '/infrastructure-monitoring/kubernetes/nodes/#network-io-rate',
|
||||
},
|
||||
{
|
||||
title: 'Filesystem usage (bytes)',
|
||||
yAxisUnit: 'bytes',
|
||||
docPath:
|
||||
'/infrastructure-monitoring/kubernetes/nodes/#filesystem-usage-bytes',
|
||||
},
|
||||
{
|
||||
title: 'Filesystem usage (%)',
|
||||
yAxisUnit: 'percentunit',
|
||||
docPath: '/infrastructure-monitoring/kubernetes/nodes/#filesystem-usage-',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { useCallback } from 'react';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import { listPods } from 'api/generated/services/inframonitoring';
|
||||
import { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { AxiosError } from 'axios';
|
||||
import {
|
||||
InframonitoringtypesPodRecordDTO,
|
||||
InframonitoringtypesResponseTypeDTO,
|
||||
Querybuildertypesv5OrderDirectionDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { InfraMonitoringEvents } from 'constants/events';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
import K8sBaseDetails, { K8sDetailsFilters } from '../Base/K8sBaseDetails';
|
||||
import { K8sBaseList } from '../Base/K8sBaseList';
|
||||
@@ -67,13 +71,12 @@ function K8sPodsList({
|
||||
warning: data.warning,
|
||||
};
|
||||
} catch (error) {
|
||||
const errMsg =
|
||||
error instanceof Error ? error.message : 'Failed to fetch pods';
|
||||
return {
|
||||
type: 'list' as const,
|
||||
records: [] as InframonitoringtypesPodRecordDTO[],
|
||||
total: 0,
|
||||
error: errMsg,
|
||||
error:
|
||||
convertToApiError(error as AxiosError<RenderErrorResponseDTO>) ?? null,
|
||||
};
|
||||
}
|
||||
},
|
||||
@@ -86,7 +89,7 @@ function K8sPodsList({
|
||||
signal?: AbortSignal,
|
||||
): Promise<{
|
||||
data: InframonitoringtypesPodRecordDTO | null;
|
||||
error?: string | null;
|
||||
error?: APIError | null;
|
||||
}> => {
|
||||
try {
|
||||
const response = await listPods(
|
||||
@@ -104,11 +107,10 @@ function K8sPodsList({
|
||||
data: response.data.records.length > 0 ? response.data.records[0] : null,
|
||||
};
|
||||
} catch (error) {
|
||||
const errMsg =
|
||||
error instanceof Error ? error.message : 'Failed to fetch pod';
|
||||
return {
|
||||
data: null,
|
||||
error: errMsg,
|
||||
error:
|
||||
convertToApiError(error as AxiosError<RenderErrorResponseDTO>) ?? null,
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
@@ -67,54 +67,74 @@ export const podWidgetInfo = [
|
||||
{
|
||||
title: 'CPU Usage (cores)',
|
||||
yAxisUnit: '',
|
||||
docPath: '/infrastructure-monitoring/kubernetes/pods/#cpu-usage-cores-1',
|
||||
},
|
||||
{
|
||||
title: 'CPU Request, Limit Utilization',
|
||||
yAxisUnit: 'percentunit',
|
||||
docPath:
|
||||
'/infrastructure-monitoring/kubernetes/pods/#cpu-request-limit-utilization',
|
||||
},
|
||||
{
|
||||
title: 'Memory Usage (bytes)',
|
||||
yAxisUnit: 'bytes',
|
||||
docPath: '/infrastructure-monitoring/kubernetes/pods/#memory-usage-bytes',
|
||||
},
|
||||
{
|
||||
title: 'Memory Request, Limit Utilization',
|
||||
yAxisUnit: 'percentunit',
|
||||
docPath:
|
||||
'/infrastructure-monitoring/kubernetes/pods/#memory-request-limit-utilization',
|
||||
},
|
||||
{
|
||||
title: 'Memory by State',
|
||||
yAxisUnit: 'bytes',
|
||||
docPath: '/infrastructure-monitoring/kubernetes/pods/#memory-by-state',
|
||||
},
|
||||
{
|
||||
title: 'Memory Major Page Faults',
|
||||
yAxisUnit: '',
|
||||
docPath:
|
||||
'/infrastructure-monitoring/kubernetes/pods/#memory-major-page-faults',
|
||||
},
|
||||
{
|
||||
title: 'CPU Usage by Container (cores)',
|
||||
yAxisUnit: '',
|
||||
docPath:
|
||||
'/infrastructure-monitoring/kubernetes/pods/#cpu-usage-by-container-cores',
|
||||
},
|
||||
{
|
||||
title: 'CPU Request, Limit Utilization by Container',
|
||||
yAxisUnit: 'percentunit',
|
||||
docPath:
|
||||
'/infrastructure-monitoring/kubernetes/pods/#cpu-request-limit-utilization-by-container',
|
||||
},
|
||||
{
|
||||
title: 'Memory Usage by Container (bytes)',
|
||||
yAxisUnit: 'bytes',
|
||||
docPath:
|
||||
'/infrastructure-monitoring/kubernetes/pods/#memory-usage-by-container-bytes',
|
||||
},
|
||||
{
|
||||
title: 'Memory Request, Limit Utilization by Container',
|
||||
yAxisUnit: 'percentunit',
|
||||
docPath:
|
||||
'/infrastructure-monitoring/kubernetes/pods/#memory-request-limit-utilization-by-container',
|
||||
},
|
||||
{
|
||||
title: 'Network rate',
|
||||
yAxisUnit: 'binBps',
|
||||
docPath: '/infrastructure-monitoring/kubernetes/pods/#network-rate',
|
||||
},
|
||||
{
|
||||
title: 'Network errors',
|
||||
yAxisUnit: '',
|
||||
docPath: '/infrastructure-monitoring/kubernetes/pods/#network-errors',
|
||||
},
|
||||
{
|
||||
title: 'File system (bytes)',
|
||||
yAxisUnit: 'bytes',
|
||||
docPath: '/infrastructure-monitoring/kubernetes/pods/#file-system-bytes',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -152,7 +152,11 @@ export const k8sPodColumnsConfig: PodTableColumnConfig[] = [
|
||||
},
|
||||
{
|
||||
id: 'podAge',
|
||||
header: 'Age',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/pods#age">
|
||||
Age
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.podAge,
|
||||
width: { min: 100 },
|
||||
enableSort: false,
|
||||
@@ -316,7 +320,11 @@ export const k8sPodColumnsConfig: PodTableColumnConfig[] = [
|
||||
},
|
||||
{
|
||||
id: 'namespace',
|
||||
header: 'Namespace',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/pods#additional-columns">
|
||||
Namespace
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): string =>
|
||||
row.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
width: { default: 100 },
|
||||
@@ -328,7 +336,11 @@ export const k8sPodColumnsConfig: PodTableColumnConfig[] = [
|
||||
},
|
||||
{
|
||||
id: 'node',
|
||||
header: 'Node',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/pods#additional-columns">
|
||||
Node
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): string =>
|
||||
row.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NODE_NAME] || '',
|
||||
width: { default: 100 },
|
||||
@@ -340,7 +352,11 @@ export const k8sPodColumnsConfig: PodTableColumnConfig[] = [
|
||||
},
|
||||
{
|
||||
id: 'cluster',
|
||||
header: 'Cluster',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/pods#additional-columns">
|
||||
Cluster
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): string =>
|
||||
row.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME] || '',
|
||||
width: { default: 100 },
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { useCallback } from 'react';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import { listStatefulSets } from 'api/generated/services/inframonitoring';
|
||||
import { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { AxiosError } from 'axios';
|
||||
import {
|
||||
InframonitoringtypesResponseTypeDTO,
|
||||
InframonitoringtypesStatefulSetRecordDTO,
|
||||
Querybuildertypesv5OrderDirectionDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { InfraMonitoringEvents } from 'constants/events';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
import K8sBaseDetails, { K8sDetailsFilters } from '../Base/K8sBaseDetails';
|
||||
import { K8sBaseList } from '../Base/K8sBaseList';
|
||||
@@ -68,13 +72,12 @@ function K8sStatefulSetsList({
|
||||
warning: data.warning,
|
||||
};
|
||||
} catch (error) {
|
||||
const errMsg =
|
||||
error instanceof Error ? error.message : 'Failed to fetch statefulsets';
|
||||
return {
|
||||
type: 'list' as const,
|
||||
records: [] as InframonitoringtypesStatefulSetRecordDTO[],
|
||||
total: 0,
|
||||
error: errMsg,
|
||||
error:
|
||||
convertToApiError(error as AxiosError<RenderErrorResponseDTO>) ?? null,
|
||||
};
|
||||
}
|
||||
},
|
||||
@@ -87,7 +90,7 @@ function K8sStatefulSetsList({
|
||||
signal?: AbortSignal,
|
||||
): Promise<{
|
||||
data: InframonitoringtypesStatefulSetRecordDTO | null;
|
||||
error?: string | null;
|
||||
error?: APIError | null;
|
||||
}> => {
|
||||
try {
|
||||
const response = await listStatefulSets(
|
||||
@@ -105,11 +108,10 @@ function K8sStatefulSetsList({
|
||||
data: response.data.records.length > 0 ? response.data.records[0] : null,
|
||||
};
|
||||
} catch (error) {
|
||||
const errMsg =
|
||||
error instanceof Error ? error.message : 'Failed to fetch statefulset';
|
||||
return {
|
||||
data: null,
|
||||
error: errMsg,
|
||||
error:
|
||||
convertToApiError(error as AxiosError<RenderErrorResponseDTO>) ?? null,
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
@@ -72,26 +72,37 @@ export const statefulSetWidgetInfo = [
|
||||
{
|
||||
title: 'CPU usage, request, limits',
|
||||
yAxisUnit: '',
|
||||
docPath:
|
||||
'/infrastructure-monitoring/kubernetes/statefulsets/#cpu-usage-request-limits',
|
||||
},
|
||||
{
|
||||
title: 'CPU request, limit util (%)',
|
||||
yAxisUnit: 'percentunit',
|
||||
docPath:
|
||||
'/infrastructure-monitoring/kubernetes/statefulsets/#cpu-request-limit-utilization-',
|
||||
},
|
||||
{
|
||||
title: 'Memory usage, request, limits',
|
||||
yAxisUnit: 'bytes',
|
||||
docPath:
|
||||
'/infrastructure-monitoring/kubernetes/statefulsets/#memory-usage-request-limits',
|
||||
},
|
||||
{
|
||||
title: 'Memory request, limit util (%)',
|
||||
yAxisUnit: 'percentunit',
|
||||
docPath:
|
||||
'/infrastructure-monitoring/kubernetes/statefulsets/#memory-request-limit-utilization-',
|
||||
},
|
||||
{
|
||||
title: 'Network IO',
|
||||
yAxisUnit: 'binBps',
|
||||
docPath: '/infrastructure-monitoring/kubernetes/statefulsets/#network-io',
|
||||
},
|
||||
{
|
||||
title: 'Network errors count',
|
||||
yAxisUnit: '',
|
||||
docPath:
|
||||
'/infrastructure-monitoring/kubernetes/statefulsets/#network-errors-count',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -141,10 +141,10 @@ export const k8sStatefulSetsColumnsConfig: TableColumnDef<InframonitoringtypesSt
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'pod_status',
|
||||
id: 'pod_replicas',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/statefulsets#pod-status">
|
||||
Pod Status
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/statefulsets#pod-replicas">
|
||||
Pod Replicas
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.currentPods,
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { useCallback } from 'react';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import { listVolumes } from 'api/generated/services/inframonitoring';
|
||||
import { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { AxiosError } from 'axios';
|
||||
import {
|
||||
InframonitoringtypesResponseTypeDTO,
|
||||
InframonitoringtypesVolumeRecordDTO,
|
||||
Querybuildertypesv5OrderDirectionDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { InfraMonitoringEvents } from 'constants/events';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
import K8sBaseDetails, { K8sDetailsFilters } from '../Base/K8sBaseDetails';
|
||||
import { K8sBaseList } from '../Base/K8sBaseList';
|
||||
@@ -68,13 +72,12 @@ function K8sVolumesList({
|
||||
warning: data.warning,
|
||||
};
|
||||
} catch (error) {
|
||||
const errMsg =
|
||||
error instanceof Error ? error.message : 'Failed to fetch volumes';
|
||||
return {
|
||||
type: 'list' as const,
|
||||
records: [] as InframonitoringtypesVolumeRecordDTO[],
|
||||
total: 0,
|
||||
error: errMsg,
|
||||
error:
|
||||
convertToApiError(error as AxiosError<RenderErrorResponseDTO>) ?? null,
|
||||
};
|
||||
}
|
||||
},
|
||||
@@ -87,7 +90,7 @@ function K8sVolumesList({
|
||||
signal?: AbortSignal,
|
||||
): Promise<{
|
||||
data: InframonitoringtypesVolumeRecordDTO | null;
|
||||
error?: string | null;
|
||||
error?: APIError | null;
|
||||
}> => {
|
||||
try {
|
||||
const response = await listVolumes(
|
||||
@@ -105,11 +108,10 @@ function K8sVolumesList({
|
||||
data: response.data.records.length > 0 ? response.data.records[0] : null,
|
||||
};
|
||||
} catch (error) {
|
||||
const errMsg =
|
||||
error instanceof Error ? error.message : 'Failed to fetch volume';
|
||||
return {
|
||||
data: null,
|
||||
error: errMsg,
|
||||
error:
|
||||
convertToApiError(error as AxiosError<RenderErrorResponseDTO>) ?? null,
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
@@ -69,22 +69,27 @@ export const volumeWidgetInfo = [
|
||||
{
|
||||
title: 'Volume available',
|
||||
yAxisUnit: 'bytes',
|
||||
docPath: '/infrastructure-monitoring/kubernetes/volumes/#volume-available',
|
||||
},
|
||||
{
|
||||
title: 'Volume capacity',
|
||||
yAxisUnit: 'bytes',
|
||||
docPath: '/infrastructure-monitoring/kubernetes/volumes/#volume-capacity',
|
||||
},
|
||||
{
|
||||
title: 'Volume inodes used',
|
||||
yAxisUnit: 'short',
|
||||
docPath: '/infrastructure-monitoring/kubernetes/volumes/#volume-inodes-used',
|
||||
},
|
||||
{
|
||||
title: 'Volume inodes',
|
||||
yAxisUnit: 'short',
|
||||
docPath: '/infrastructure-monitoring/kubernetes/volumes/#volume-inodes',
|
||||
},
|
||||
{
|
||||
title: 'Volume inodes free',
|
||||
yAxisUnit: 'short',
|
||||
docPath: '/infrastructure-monitoring/kubernetes/volumes/#volume-inodes-free',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -2872,17 +2872,72 @@ export const nodeWidgetInfo = [
|
||||
];
|
||||
|
||||
export const hostWidgetInfo = [
|
||||
{ title: 'CPU Usage', yAxisUnit: 'percentunit' },
|
||||
{ title: 'Memory Usage', yAxisUnit: 'bytes' },
|
||||
{ title: 'System Load Average', yAxisUnit: '' },
|
||||
{ title: 'Network usage (bytes)', yAxisUnit: 'bytes' },
|
||||
{ title: 'Network usage (packet/s)', yAxisUnit: 'pps' },
|
||||
{ title: 'Network errors', yAxisUnit: 'short' },
|
||||
{ title: 'Network drops', yAxisUnit: 'short' },
|
||||
{ title: 'Network connections', yAxisUnit: 'short' },
|
||||
{ title: 'System disk io (bytes transferred)', yAxisUnit: 'bytes' },
|
||||
{ title: 'System disk operations/s', yAxisUnit: 'short' },
|
||||
{ title: 'Queue size', yAxisUnit: 'short' },
|
||||
{ title: 'System disk operation time/s', yAxisUnit: 's' },
|
||||
{ title: 'Disk Usage (%) by mountpoint', yAxisUnit: 'percentunit' },
|
||||
{
|
||||
title: 'CPU Usage',
|
||||
yAxisUnit: 'percentunit',
|
||||
docPath: '/infrastructure-monitoring/host-monitoring/#cpu-usage',
|
||||
},
|
||||
{
|
||||
title: 'Memory Usage',
|
||||
yAxisUnit: 'bytes',
|
||||
docPath: '/infrastructure-monitoring/host-monitoring/#memory-usage',
|
||||
},
|
||||
{
|
||||
title: 'System Load Average',
|
||||
yAxisUnit: '',
|
||||
docPath: '/infrastructure-monitoring/host-monitoring/#system-load-average',
|
||||
},
|
||||
{
|
||||
title: 'Network usage (bytes)',
|
||||
yAxisUnit: 'bytes',
|
||||
docPath: '/infrastructure-monitoring/host-monitoring/#network-usage-bytes',
|
||||
},
|
||||
{
|
||||
title: 'Network usage (packet/s)',
|
||||
yAxisUnit: 'pps',
|
||||
docPath: '/infrastructure-monitoring/host-monitoring/#network-usage-packetss',
|
||||
},
|
||||
{
|
||||
title: 'Network errors',
|
||||
yAxisUnit: 'short',
|
||||
docPath: '/infrastructure-monitoring/host-monitoring/#network-errors',
|
||||
},
|
||||
{
|
||||
title: 'Network drops',
|
||||
yAxisUnit: 'short',
|
||||
docPath: '/infrastructure-monitoring/host-monitoring/#network-drops',
|
||||
},
|
||||
{
|
||||
title: 'Network connections',
|
||||
yAxisUnit: 'short',
|
||||
docPath: '/infrastructure-monitoring/host-monitoring/#network-connections',
|
||||
},
|
||||
{
|
||||
title: 'System disk io (bytes transferred)',
|
||||
yAxisUnit: 'bytes',
|
||||
docPath: '/infrastructure-monitoring/host-monitoring/#system-disk-io-bytes',
|
||||
},
|
||||
{
|
||||
title: 'System disk operations/s',
|
||||
yAxisUnit: 'short',
|
||||
docPath:
|
||||
'/infrastructure-monitoring/host-monitoring/#system-disk-operationss',
|
||||
},
|
||||
{
|
||||
title: 'Queue size',
|
||||
yAxisUnit: 'short',
|
||||
docPath: '/infrastructure-monitoring/host-monitoring/#queue-size',
|
||||
},
|
||||
{
|
||||
title: 'System disk operation time/s',
|
||||
yAxisUnit: 's',
|
||||
docPath:
|
||||
'/infrastructure-monitoring/host-monitoring/#system-disk-operation-times',
|
||||
},
|
||||
{
|
||||
title: 'Disk Usage (%) by mountpoint',
|
||||
yAxisUnit: 'percentunit',
|
||||
docPath:
|
||||
'/infrastructure-monitoring/host-monitoring/#disk-usage--by-mountpoint',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"reflect"
|
||||
"sort"
|
||||
"sync"
|
||||
"testing"
|
||||
@@ -28,6 +27,7 @@ import (
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/prometheus/common/promslog"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -110,42 +110,44 @@ func TestAggrGroup(t *testing.T) {
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
last = time.Now()
|
||||
current = time.Now()
|
||||
lastCurMtx = &sync.Mutex{}
|
||||
alertsCh = make(chan alertmanagertypes.AlertSlice)
|
||||
)
|
||||
type notification struct {
|
||||
alerts alertmanagertypes.AlertSlice
|
||||
notifiedAt time.Time
|
||||
}
|
||||
alertsCh := make(chan notification)
|
||||
|
||||
ntfy := func(ctx context.Context, alerts ...*alertmanagertypes.Alert) bool {
|
||||
// Validate that the context is properly populated.
|
||||
if _, ok := notify.Now(ctx); !ok {
|
||||
t.Errorf("now missing")
|
||||
notifiedAt, ok := notify.Now(ctx)
|
||||
assert.True(t, ok, "now missing")
|
||||
_, ok = notify.GroupKey(ctx)
|
||||
assert.True(t, ok, "group key missing")
|
||||
lbls, ok := notify.GroupLabels(ctx)
|
||||
if assert.True(t, ok, "group labels missing") {
|
||||
assert.Equal(t, lset, lbls, "wrong group labels")
|
||||
}
|
||||
if _, ok := notify.GroupKey(ctx); !ok {
|
||||
t.Errorf("group key missing")
|
||||
rcv, ok := notify.ReceiverName(ctx)
|
||||
if assert.True(t, ok, "receiver missing") {
|
||||
assert.Equal(t, opts.Receiver, rcv, "wrong receiver")
|
||||
}
|
||||
if lbls, ok := notify.GroupLabels(ctx); !ok || !reflect.DeepEqual(lbls, lset) {
|
||||
t.Errorf("wrong group labels: %q", lbls)
|
||||
}
|
||||
if rcv, ok := notify.ReceiverName(ctx); !ok || rcv != opts.Receiver {
|
||||
t.Errorf("wrong receiver: %q", rcv)
|
||||
}
|
||||
if ri, ok := notify.RepeatInterval(ctx); !ok || ri != notificationConfig.Renotify.RenotifyInterval {
|
||||
t.Errorf("wrong repeat interval: %q", ri)
|
||||
ri, ok := notify.RepeatInterval(ctx)
|
||||
if assert.True(t, ok, "repeat interval missing") {
|
||||
assert.Equal(t, notificationConfig.Renotify.RenotifyInterval, ri, "wrong repeat interval")
|
||||
}
|
||||
|
||||
lastCurMtx.Lock()
|
||||
last = current
|
||||
// Subtract a millisecond to allow for races.
|
||||
current = time.Now().Add(-time.Millisecond)
|
||||
lastCurMtx.Unlock()
|
||||
|
||||
alertsCh <- alertmanagertypes.AlertSlice(alerts)
|
||||
alertsCh <- notification{
|
||||
alerts: alertmanagertypes.AlertSlice(alerts),
|
||||
notifiedAt: notifiedAt,
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
assertNotifiedAfter := func(previous, current time.Time, interval time.Duration) {
|
||||
t.Helper()
|
||||
require.GreaterOrEqual(t, current.Sub(previous), interval, "received batch too early")
|
||||
}
|
||||
|
||||
removeEndsAt := func(as alertmanagertypes.AlertSlice) alertmanagertypes.AlertSlice {
|
||||
for i, a := range as {
|
||||
ac := *a
|
||||
@@ -156,29 +158,26 @@ func TestAggrGroup(t *testing.T) {
|
||||
}
|
||||
|
||||
// Test regular situation where we wait for group_wait to send out alerts.
|
||||
groupStartedAt := time.Now()
|
||||
ag := newAggrGroup(context.Background(), lset, route, nil, promslog.NewNopLogger(), notificationConfig.Renotify.RenotifyInterval)
|
||||
|
||||
go ag.run(ntfy)
|
||||
|
||||
ag.insert(a1)
|
||||
var lastNotificationAt time.Time
|
||||
|
||||
select {
|
||||
case <-time.After(2 * opts.GroupWait):
|
||||
t.Fatalf("expected initial batch after group_wait")
|
||||
require.FailNow(t, "expected initial batch after group_wait")
|
||||
|
||||
case batch := <-alertsCh:
|
||||
lastCurMtx.Lock()
|
||||
s := time.Since(last)
|
||||
lastCurMtx.Unlock()
|
||||
if s < opts.GroupWait {
|
||||
t.Fatalf("received batch too early after %v", s)
|
||||
}
|
||||
case notification := <-alertsCh:
|
||||
assertNotifiedAfter(groupStartedAt, notification.notifiedAt, opts.GroupWait)
|
||||
lastNotificationAt = notification.notifiedAt
|
||||
batch := notification.alerts
|
||||
exp := removeEndsAt(alertmanagertypes.AlertSlice{a1})
|
||||
sort.Sort(batch)
|
||||
|
||||
if !reflect.DeepEqual(batch, exp) {
|
||||
t.Fatalf("expected alerts %v but got %v", exp, batch)
|
||||
}
|
||||
require.Equal(t, exp, batch)
|
||||
}
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
@@ -187,21 +186,16 @@ func TestAggrGroup(t *testing.T) {
|
||||
|
||||
select {
|
||||
case <-time.After(2 * opts.GroupInterval):
|
||||
t.Fatalf("expected new batch after group interval but received none")
|
||||
require.FailNow(t, "expected new batch after group interval but received none")
|
||||
|
||||
case batch := <-alertsCh:
|
||||
lastCurMtx.Lock()
|
||||
s := time.Since(last)
|
||||
lastCurMtx.Unlock()
|
||||
if s < opts.GroupInterval {
|
||||
t.Fatalf("received batch too early after %v", s)
|
||||
}
|
||||
case notification := <-alertsCh:
|
||||
assertNotifiedAfter(lastNotificationAt, notification.notifiedAt, opts.GroupInterval)
|
||||
lastNotificationAt = notification.notifiedAt
|
||||
batch := notification.alerts
|
||||
exp := removeEndsAt(alertmanagertypes.AlertSlice{a1, a3})
|
||||
sort.Sort(batch)
|
||||
|
||||
if !reflect.DeepEqual(batch, exp) {
|
||||
t.Fatalf("expected alerts %v but got %v", exp, batch)
|
||||
}
|
||||
require.Equal(t, exp, batch)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,15 +214,15 @@ func TestAggrGroup(t *testing.T) {
|
||||
// a2 lies way in the past so the initial group_wait should be skipped.
|
||||
select {
|
||||
case <-time.After(opts.GroupWait / 2):
|
||||
t.Fatalf("expected immediate alert but received none")
|
||||
require.FailNow(t, "expected immediate alert but received none")
|
||||
|
||||
case batch := <-alertsCh:
|
||||
case notification := <-alertsCh:
|
||||
lastNotificationAt = notification.notifiedAt
|
||||
batch := notification.alerts
|
||||
exp := removeEndsAt(alertmanagertypes.AlertSlice{a1, a2})
|
||||
sort.Sort(batch)
|
||||
|
||||
if !reflect.DeepEqual(batch, exp) {
|
||||
t.Fatalf("expected alerts %v but got %v", exp, batch)
|
||||
}
|
||||
require.Equal(t, exp, batch)
|
||||
}
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
@@ -237,21 +231,16 @@ func TestAggrGroup(t *testing.T) {
|
||||
|
||||
select {
|
||||
case <-time.After(2 * opts.GroupInterval):
|
||||
t.Fatalf("expected new batch after group interval but received none")
|
||||
require.FailNow(t, "expected new batch after group interval but received none")
|
||||
|
||||
case batch := <-alertsCh:
|
||||
lastCurMtx.Lock()
|
||||
s := time.Since(last)
|
||||
lastCurMtx.Unlock()
|
||||
if s < opts.GroupInterval {
|
||||
t.Fatalf("received batch too early after %v", s)
|
||||
}
|
||||
case notification := <-alertsCh:
|
||||
assertNotifiedAfter(lastNotificationAt, notification.notifiedAt, opts.GroupInterval)
|
||||
lastNotificationAt = notification.notifiedAt
|
||||
batch := notification.alerts
|
||||
exp := removeEndsAt(alertmanagertypes.AlertSlice{a1, a2, a3})
|
||||
sort.Sort(batch)
|
||||
|
||||
if !reflect.DeepEqual(batch, exp) {
|
||||
t.Fatalf("expected alerts %v but got %v", exp, batch)
|
||||
}
|
||||
require.Equal(t, exp, batch)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,19 +252,14 @@ func TestAggrGroup(t *testing.T) {
|
||||
|
||||
select {
|
||||
case <-time.After(2 * opts.GroupInterval):
|
||||
t.Fatalf("expected new batch after group interval but received none")
|
||||
case batch := <-alertsCh:
|
||||
lastCurMtx.Lock()
|
||||
s := time.Since(last)
|
||||
lastCurMtx.Unlock()
|
||||
if s < opts.GroupInterval {
|
||||
t.Fatalf("received batch too early after %v", s)
|
||||
}
|
||||
require.FailNow(t, "expected new batch after group interval but received none")
|
||||
case notification := <-alertsCh:
|
||||
assertNotifiedAfter(lastNotificationAt, notification.notifiedAt, opts.GroupInterval)
|
||||
lastNotificationAt = notification.notifiedAt
|
||||
batch := notification.alerts
|
||||
sort.Sort(batch)
|
||||
|
||||
if !reflect.DeepEqual(batch, exp) {
|
||||
t.Fatalf("expected alerts %v but got %v", exp, batch)
|
||||
}
|
||||
require.Equal(t, exp, batch)
|
||||
}
|
||||
|
||||
// Resolve all remaining alerts, they should be removed after the next batch was sent.
|
||||
@@ -289,24 +273,16 @@ func TestAggrGroup(t *testing.T) {
|
||||
|
||||
select {
|
||||
case <-time.After(2 * opts.GroupInterval):
|
||||
t.Fatalf("expected new batch after group interval but received none")
|
||||
require.FailNow(t, "expected new batch after group interval but received none")
|
||||
|
||||
case batch := <-alertsCh:
|
||||
lastCurMtx.Lock()
|
||||
s := time.Since(last)
|
||||
lastCurMtx.Unlock()
|
||||
if s < opts.GroupInterval {
|
||||
t.Fatalf("received batch too early after %v", s)
|
||||
}
|
||||
case notification := <-alertsCh:
|
||||
assertNotifiedAfter(lastNotificationAt, notification.notifiedAt, opts.GroupInterval)
|
||||
batch := notification.alerts
|
||||
sort.Sort(batch)
|
||||
|
||||
if !reflect.DeepEqual(batch, resolved) {
|
||||
t.Fatalf("expected alerts %v but got %v", resolved, batch)
|
||||
}
|
||||
require.Equal(t, resolved, batch)
|
||||
|
||||
if !ag.empty() {
|
||||
t.Fatalf("Expected aggregation group to be empty after resolving alerts: %v", ag)
|
||||
}
|
||||
require.Eventually(t, ag.empty, 2*opts.GroupInterval, 10*time.Millisecond, "expected aggregation group to be empty after resolving alerts: %v", ag)
|
||||
}
|
||||
|
||||
ag.stop()
|
||||
@@ -340,9 +316,7 @@ func TestGroupLabels(t *testing.T) {
|
||||
|
||||
ls := getGroupLabels(a, route.RouteOpts.GroupBy, false)
|
||||
|
||||
if !reflect.DeepEqual(ls, expLs) {
|
||||
t.Fatalf("expected labels are %v, but got %v", expLs, ls)
|
||||
}
|
||||
require.Equal(t, expLs, ls)
|
||||
}
|
||||
|
||||
func TestAggrRouteMap(t *testing.T) {
|
||||
@@ -358,17 +332,13 @@ route:
|
||||
group_interval: 1m
|
||||
receiver: 'slack'`
|
||||
conf, err := config.Load(confData)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
providerSettings := createTestProviderSettings()
|
||||
logger := providerSettings.Logger
|
||||
route := dispatch.NewRoute(conf.Route, nil)
|
||||
marker := alertmanagertypes.NewMarker(prometheus.NewRegistry())
|
||||
alerts, err := mem.NewAlerts(context.Background(), marker, time.Hour, 0, nil, logger, prometheus.NewRegistry(), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
defer alerts.Close()
|
||||
|
||||
timeout := func(d time.Duration) time.Duration { return time.Duration(0) }
|
||||
@@ -377,9 +347,7 @@ route:
|
||||
store := nfroutingstoretest.NewMockSQLRouteStore()
|
||||
store.MatchExpectationsInOrder(false)
|
||||
nfManager, err := rulebasednotification.New(context.Background(), providerSettings, nfmanager.Config{}, store)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
orgId := "test-org"
|
||||
|
||||
ctx := context.Background()
|
||||
@@ -497,9 +465,7 @@ route:
|
||||
require.NoError(t, err)
|
||||
}
|
||||
err = alerts.Put(ctx, inputAlerts...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
// Let alerts get processed.
|
||||
for i := 0; len(recorder.Alerts()) != 4; i++ {
|
||||
@@ -631,17 +597,13 @@ route:
|
||||
group_interval: 10ms
|
||||
receiver: 'slack'`
|
||||
conf, err := config.Load(confData)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
providerSettings := createTestProviderSettings()
|
||||
logger := providerSettings.Logger
|
||||
route := dispatch.NewRoute(conf.Route, nil)
|
||||
marker := alertmanagertypes.NewMarker(prometheus.NewRegistry())
|
||||
alerts, err := mem.NewAlerts(context.Background(), marker, time.Hour, 0, nil, logger, prometheus.NewRegistry(), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
defer alerts.Close()
|
||||
|
||||
timeout := func(d time.Duration) time.Duration { return time.Duration(0) }
|
||||
@@ -650,9 +612,7 @@ route:
|
||||
store := nfroutingstoretest.NewMockSQLRouteStore()
|
||||
store.MatchExpectationsInOrder(false)
|
||||
nfManager, err := rulebasednotification.New(context.Background(), providerSettings, nfmanager.Config{}, store)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
orgId := "test-org"
|
||||
|
||||
ctx := context.Background()
|
||||
@@ -799,9 +759,7 @@ route:
|
||||
require.NoError(t, err)
|
||||
}
|
||||
err = alerts.Put(ctx, inputAlerts...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
for i := 0; len(recorder.Alerts()) != 9; i++ {
|
||||
time.Sleep(400 * time.Millisecond)
|
||||
@@ -890,17 +848,13 @@ route:
|
||||
group_interval: 10ms
|
||||
receiver: 'slack'`
|
||||
conf, err := config.Load(confData)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
providerSettings := createTestProviderSettings()
|
||||
logger := providerSettings.Logger
|
||||
route := dispatch.NewRoute(conf.Route, nil)
|
||||
marker := alertmanagertypes.NewMarker(prometheus.NewRegistry())
|
||||
alerts, err := mem.NewAlerts(context.Background(), marker, time.Hour, 0, nil, logger, prometheus.NewRegistry(), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
defer alerts.Close()
|
||||
|
||||
timeout := func(d time.Duration) time.Duration { return time.Duration(0) }
|
||||
@@ -909,9 +863,7 @@ route:
|
||||
store := nfroutingstoretest.NewMockSQLRouteStore()
|
||||
store.MatchExpectationsInOrder(false)
|
||||
nfManager, err := rulebasednotification.New(context.Background(), providerSettings, nfmanager.Config{}, store)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
orgId := "test-org"
|
||||
|
||||
ctx := context.Background()
|
||||
@@ -1029,9 +981,7 @@ route:
|
||||
require.NoError(t, err)
|
||||
}
|
||||
err = alerts.Put(ctx, inputAlerts...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
for i := 0; len(recorder.Alerts()) != 3 && i < 15; i++ {
|
||||
time.Sleep(400 * time.Millisecond)
|
||||
@@ -1160,9 +1110,7 @@ func TestDispatcherRace(t *testing.T) {
|
||||
logger := promslog.NewNopLogger()
|
||||
marker := alertmanagertypes.NewMarker(prometheus.NewRegistry())
|
||||
alerts, err := mem.NewAlerts(context.Background(), marker, time.Hour, 0, nil, logger, prometheus.NewRegistry(), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
defer alerts.Close()
|
||||
|
||||
timeout := func(d time.Duration) time.Duration { return time.Duration(0) }
|
||||
@@ -1188,17 +1136,13 @@ route:
|
||||
group_interval: 5m
|
||||
receiver: 'slack'`
|
||||
conf, err := config.Load(confData)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
route := dispatch.NewRoute(conf.Route, nil)
|
||||
providerSettings := createTestProviderSettings()
|
||||
logger := providerSettings.Logger
|
||||
marker := alertmanagertypes.NewMarker(prometheus.NewRegistry())
|
||||
alerts, err := mem.NewAlerts(context.Background(), marker, time.Hour, 0, nil, logger, prometheus.NewRegistry(), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
defer alerts.Close()
|
||||
timeout := func(d time.Duration) time.Duration { return d }
|
||||
recorder := &recordStage{alerts: make(map[string]map[model.Fingerprint]*alertmanagertypes.Alert)}
|
||||
@@ -1206,9 +1150,7 @@ route:
|
||||
store := nfroutingstoretest.NewMockSQLRouteStore()
|
||||
store.MatchExpectationsInOrder(false)
|
||||
nfManager, err := rulebasednotification.New(context.Background(), providerSettings, nfmanager.Config{}, store)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
orgId := "test-org"
|
||||
|
||||
for i := 0; i < numAlerts; i++ {
|
||||
@@ -1267,9 +1209,7 @@ func TestDispatcher_DoMaintenance(t *testing.T) {
|
||||
marker := alertmanagertypes.NewMarker(r)
|
||||
|
||||
alerts, err := mem.NewAlerts(context.Background(), marker, time.Minute, 0, nil, promslog.NewNopLogger(), prometheus.NewRegistry(), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
route := &dispatch.Route{
|
||||
RouteOpts: dispatch.RouteOpts{
|
||||
@@ -1363,18 +1303,14 @@ route:
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
conf, err := config.Load(tc.confData)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
providerSettings := createTestProviderSettings()
|
||||
logger := providerSettings.Logger
|
||||
route := dispatch.NewRoute(conf.Route, nil)
|
||||
marker := alertmanagertypes.NewMarker(prometheus.NewRegistry())
|
||||
alerts, err := mem.NewAlerts(context.Background(), marker, time.Hour, 0, nil, logger, prometheus.NewRegistry(), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
defer alerts.Close()
|
||||
|
||||
timeout := func(d time.Duration) time.Duration { return time.Duration(0) }
|
||||
@@ -1383,9 +1319,7 @@ route:
|
||||
store := nfroutingstoretest.NewMockSQLRouteStore()
|
||||
store.MatchExpectationsInOrder(false)
|
||||
nfManager, err := rulebasednotification.New(context.Background(), providerSettings, nfmanager.Config{}, store)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
d := NewDispatcher(alerts, route, recorder, marker, timeout, nil, logger, metrics, nfManager, "test-org")
|
||||
// setup the dispatcher for tests
|
||||
d.receiverRoutes = map[string]*dispatch.Route{}
|
||||
|
||||
@@ -33,7 +33,13 @@ func (handler *handler) GetFieldsKeys(rw http.ResponseWriter, req *http.Request)
|
||||
|
||||
fieldKeySelector := telemetrytypes.NewFieldKeySelectorFromPostableFieldKeysParams(params)
|
||||
|
||||
keys, complete, err := handler.telemetryMetadataStore.GetKeys(ctx, fieldKeySelector)
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
keys, complete, err := handler.telemetryMetadataStore.GetKeys(ctx, valuer.MustNewUUID(claims.OrgID), fieldKeySelector)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
@@ -68,7 +74,7 @@ func (handler *handler) GetFieldsValues(rw http.ResponseWriter, req *http.Reques
|
||||
return
|
||||
}
|
||||
|
||||
relatedValues, relatedComplete, err := handler.telemetryMetadataStore.GetRelatedValues(ctx, fieldValueSelector)
|
||||
relatedValues, relatedComplete, err := handler.telemetryMetadataStore.GetRelatedValues(ctx, valuer.MustNewUUID(claims.OrgID), fieldValueSelector)
|
||||
if err != nil {
|
||||
// we don't want to return error if we fail to get related values for some reason
|
||||
relatedValues = []string{}
|
||||
|
||||
@@ -208,6 +208,7 @@ func (m *module) getContainersTableMetadata(ctx context.Context, orgID valuer.UU
|
||||
// runs the query. The returned counts map is empty (never nil) when gated off.
|
||||
func (m *module) getPerGroupContainerStatusCountsWithReqMetricChecks(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
start, end int64,
|
||||
filter *qbtypes.Filter,
|
||||
groupBy []qbtypes.GroupByKey,
|
||||
@@ -237,7 +238,7 @@ func (m *module) getPerGroupContainerStatusCountsWithReqMetricChecks(
|
||||
return map[string]containerStatusCounts{}, warning, nil
|
||||
}
|
||||
|
||||
counts, err := m.getPerGroupContainerStatusCounts(ctx, start, end, filter, groupBy, pageGroups)
|
||||
counts, err := m.getPerGroupContainerStatusCounts(ctx, orgID, start, end, filter, groupBy, pageGroups)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -263,6 +264,7 @@ func (m *module) getPerGroupContainerStatusCountsWithReqMetricChecks(
|
||||
// Groups absent from the result map have implicit zero counts (caller default).
|
||||
func (m *module) getPerGroupContainerStatusCounts(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
start, end int64,
|
||||
filter *qbtypes.Filter,
|
||||
groupBy []qbtypes.GroupByKey,
|
||||
@@ -288,7 +290,7 @@ func (m *module) getPerGroupContainerStatusCounts(
|
||||
err error
|
||||
)
|
||||
if mergedFilterExpr != "" {
|
||||
filterClause, err = m.buildFilterClause(ctx, &qbtypes.Filter{Expression: mergedFilterExpr}, start, end)
|
||||
filterClause, err = m.buildFilterClause(ctx, orgID, &qbtypes.Filter{Expression: mergedFilterExpr}, start, end)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -527,6 +529,7 @@ func (m *module) getPerGroupContainerStatusCounts(
|
||||
// Groups absent from the result map have no data (caller default).
|
||||
func (m *module) getPerGroupContainerRestartCounts(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
start, end int64,
|
||||
filter *qbtypes.Filter,
|
||||
groupBy []qbtypes.GroupByKey,
|
||||
@@ -550,7 +553,7 @@ func (m *module) getPerGroupContainerRestartCounts(
|
||||
err error
|
||||
)
|
||||
if mergedFilterExpr != "" {
|
||||
filterClause, err = m.buildFilterClause(ctx, &qbtypes.Filter{Expression: mergedFilterExpr}, start, end)
|
||||
filterClause, err = m.buildFilterClause(ctx, orgID, &qbtypes.Filter{Expression: mergedFilterExpr}, start, end)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -670,6 +673,7 @@ func (m *module) getPerGroupContainerRestartCounts(
|
||||
// Groups absent from the result map have no data (caller default).
|
||||
func (m *module) getPerGroupContainerReadyCounts(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
start, end int64,
|
||||
filter *qbtypes.Filter,
|
||||
groupBy []qbtypes.GroupByKey,
|
||||
@@ -693,7 +697,7 @@ func (m *module) getPerGroupContainerReadyCounts(
|
||||
err error
|
||||
)
|
||||
if mergedFilterExpr != "" {
|
||||
filterClause, err = m.buildFilterClause(ctx, &qbtypes.Filter{Expression: mergedFilterExpr}, start, end)
|
||||
filterClause, err = m.buildFilterClause(ctx, orgID, &qbtypes.Filter{Expression: mergedFilterExpr}, start, end)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -398,7 +398,7 @@ func (m *module) buildReducedSamplesTblFingerprintSubQuery(metricNames []string,
|
||||
return fpSB
|
||||
}
|
||||
|
||||
func (m *module) buildFilterClause(ctx context.Context, filter *qbtypes.Filter, startMillis, endMillis int64) (*sqlbuilder.WhereClause, error) {
|
||||
func (m *module) buildFilterClause(ctx context.Context, orgID valuer.UUID, filter *qbtypes.Filter, startMillis, endMillis int64) (*sqlbuilder.WhereClause, error) {
|
||||
expression := ""
|
||||
if filter != nil {
|
||||
expression = strings.TrimSpace(filter.Expression)
|
||||
@@ -413,7 +413,7 @@ func (m *module) buildFilterClause(ctx context.Context, filter *qbtypes.Filter,
|
||||
whereClauseSelectors[idx].SelectorMatchType = telemetrytypes.FieldSelectorMatchTypeExact
|
||||
}
|
||||
|
||||
keys, _, err := m.telemetryMetadataStore.GetKeysMulti(ctx, whereClauseSelectors)
|
||||
keys, _, err := m.telemetryMetadataStore.GetKeysMulti(ctx, orgID, whereClauseSelectors)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -646,7 +646,7 @@ func (m *module) getMetadata(
|
||||
var filterClause *sqlbuilder.WhereClause
|
||||
if filter != nil && strings.TrimSpace(filter.Expression) != "" {
|
||||
var err error
|
||||
filterClause, err = m.buildFilterClause(ctx, filter, startMs, endMs)
|
||||
filterClause, err = m.buildFilterClause(ctx, orgID, filter, startMs, endMs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -692,7 +692,7 @@ func (m *module) getMetadata(
|
||||
)
|
||||
|
||||
if filter != nil && strings.TrimSpace(filter.Expression) != "" {
|
||||
filterClause, err := m.buildFilterClause(ctx, filter, startMs, endMs)
|
||||
filterClause, err := m.buildFilterClause(ctx, orgID, filter, startMs, endMs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -860,7 +860,7 @@ func (m *module) getPerGroupDistinctCounts(
|
||||
var filterClause *sqlbuilder.WhereClause
|
||||
if mergedFilterExpr != "" {
|
||||
var err error
|
||||
filterClause, err = m.buildFilterClause(ctx, &qbtypes.Filter{Expression: mergedFilterExpr}, start, end)
|
||||
filterClause, err = m.buildFilterClause(ctx, orgID, &qbtypes.Filter{Expression: mergedFilterExpr}, start, end)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -904,7 +904,7 @@ func (m *module) getPerGroupDistinctCounts(
|
||||
fmt.Sprintf("fingerprint IN (%s)", sb.Var(fpSB)),
|
||||
)
|
||||
if mergedFilterExpr != "" {
|
||||
filterClause, err := m.buildFilterClause(ctx, &qbtypes.Filter{Expression: mergedFilterExpr}, start, end)
|
||||
filterClause, err := m.buildFilterClause(ctx, orgID, &qbtypes.Filter{Expression: mergedFilterExpr}, start, end)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ func (m *module) getPerGroupHostStatusCounts(
|
||||
var filterClause *sqlbuilder.WhereClause
|
||||
if filterExpr != "" {
|
||||
var err error
|
||||
filterClause, err = m.buildFilterClause(ctx, &qbtypes.Filter{Expression: filterExpr}, req.Start, req.End)
|
||||
filterClause, err = m.buildFilterClause(ctx, orgID, &qbtypes.Filter{Expression: filterExpr}, req.Start, req.End)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -110,7 +110,7 @@ func (m *module) getPerGroupHostStatusCounts(
|
||||
)
|
||||
|
||||
if filterExpr != "" {
|
||||
filterClause, err := m.buildFilterClause(ctx, &qbtypes.Filter{Expression: filterExpr}, req.Start, req.End)
|
||||
filterClause, err := m.buildFilterClause(ctx, orgID, &qbtypes.Filter{Expression: filterExpr}, req.Start, req.End)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -332,17 +332,17 @@ func (m *module) ListPods(ctx context.Context, orgID valuer.UUID, req *inframoni
|
||||
})
|
||||
g.Go(func() error {
|
||||
var err error
|
||||
phaseCounts, err = m.getPerGroupPodPhaseCounts(gCtx, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
phaseCounts, err = m.getPerGroupPodPhaseCounts(gCtx, orgID, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
return err
|
||||
})
|
||||
g.Go(func() error {
|
||||
var err error
|
||||
statusCounts, statusWarning, err = m.getPerGroupPodStatusCountsWithReqMetricChecks(gCtx, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
statusCounts, statusWarning, err = m.getPerGroupPodStatusCountsWithReqMetricChecks(gCtx, orgID, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
return err
|
||||
})
|
||||
g.Go(func() error {
|
||||
var err error
|
||||
restartCounts, err = m.getPerGroupPodRestartCounts(gCtx, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
restartCounts, err = m.getPerGroupPodRestartCounts(gCtx, orgID, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
return err
|
||||
})
|
||||
|
||||
@@ -436,17 +436,17 @@ func (m *module) ListContainers(ctx context.Context, orgID valuer.UUID, req *inf
|
||||
})
|
||||
g.Go(func() error {
|
||||
var err error
|
||||
statusCounts, statusWarning, err = m.getPerGroupContainerStatusCountsWithReqMetricChecks(gCtx, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
statusCounts, statusWarning, err = m.getPerGroupContainerStatusCountsWithReqMetricChecks(gCtx, orgID, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
return err
|
||||
})
|
||||
g.Go(func() error {
|
||||
var err error
|
||||
restartCounts, err = m.getPerGroupContainerRestartCounts(gCtx, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
restartCounts, err = m.getPerGroupContainerRestartCounts(gCtx, orgID, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
return err
|
||||
})
|
||||
g.Go(func() error {
|
||||
var err error
|
||||
readyCounts, err = m.getPerGroupContainerReadyCounts(gCtx, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
readyCounts, err = m.getPerGroupContainerReadyCounts(gCtx, orgID, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
return err
|
||||
})
|
||||
|
||||
@@ -540,17 +540,17 @@ func (m *module) ListNodes(ctx context.Context, orgID valuer.UUID, req *inframon
|
||||
})
|
||||
g.Go(func() error {
|
||||
var err error
|
||||
nodeConditionCounts, err = m.getPerGroupNodeConditionCounts(gCtx, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
nodeConditionCounts, err = m.getPerGroupNodeConditionCounts(gCtx, orgID, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
return err
|
||||
})
|
||||
g.Go(func() error {
|
||||
var err error
|
||||
podPhaseCounts, err = m.getPerGroupPodPhaseCounts(gCtx, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
podPhaseCounts, err = m.getPerGroupPodPhaseCounts(gCtx, orgID, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
return err
|
||||
})
|
||||
g.Go(func() error {
|
||||
var err error
|
||||
podStatusCounts, podStatusWarning, err = m.getPerGroupPodStatusCountsWithReqMetricChecks(gCtx, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
podStatusCounts, podStatusWarning, err = m.getPerGroupPodStatusCountsWithReqMetricChecks(gCtx, orgID, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
return err
|
||||
})
|
||||
|
||||
@@ -644,12 +644,12 @@ func (m *module) ListNamespaces(ctx context.Context, orgID valuer.UUID, req *inf
|
||||
})
|
||||
g.Go(func() error {
|
||||
var err error
|
||||
phaseCounts, err = m.getPerGroupPodPhaseCounts(gCtx, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
phaseCounts, err = m.getPerGroupPodPhaseCounts(gCtx, orgID, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
return err
|
||||
})
|
||||
g.Go(func() error {
|
||||
var err error
|
||||
podStatusCounts, podStatusWarning, err = m.getPerGroupPodStatusCountsWithReqMetricChecks(gCtx, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
podStatusCounts, podStatusWarning, err = m.getPerGroupPodStatusCountsWithReqMetricChecks(gCtx, orgID, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
return err
|
||||
})
|
||||
g.Go(func() error {
|
||||
@@ -750,17 +750,17 @@ func (m *module) ListClusters(ctx context.Context, orgID valuer.UUID, req *infra
|
||||
})
|
||||
g.Go(func() error {
|
||||
var err error
|
||||
nodeConditionCountsMap, err = m.getPerGroupNodeConditionCounts(gCtx, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
nodeConditionCountsMap, err = m.getPerGroupNodeConditionCounts(gCtx, orgID, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
return err
|
||||
})
|
||||
g.Go(func() error {
|
||||
var err error
|
||||
podPhaseCountsMap, err = m.getPerGroupPodPhaseCounts(gCtx, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
podPhaseCountsMap, err = m.getPerGroupPodPhaseCounts(gCtx, orgID, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
return err
|
||||
})
|
||||
g.Go(func() error {
|
||||
var err error
|
||||
podStatusCounts, podStatusWarning, err = m.getPerGroupPodStatusCountsWithReqMetricChecks(gCtx, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
podStatusCounts, podStatusWarning, err = m.getPerGroupPodStatusCountsWithReqMetricChecks(gCtx, orgID, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
return err
|
||||
})
|
||||
g.Go(func() error {
|
||||
@@ -941,12 +941,12 @@ func (m *module) ListDeployments(ctx context.Context, orgID valuer.UUID, req *in
|
||||
})
|
||||
g.Go(func() error {
|
||||
var err error
|
||||
phaseCounts, err = m.getPerGroupPodPhaseCounts(gCtx, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
phaseCounts, err = m.getPerGroupPodPhaseCounts(gCtx, orgID, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
return err
|
||||
})
|
||||
g.Go(func() error {
|
||||
var err error
|
||||
podStatusCounts, podStatusWarning, err = m.getPerGroupPodStatusCountsWithReqMetricChecks(gCtx, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
podStatusCounts, podStatusWarning, err = m.getPerGroupPodStatusCountsWithReqMetricChecks(gCtx, orgID, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
return err
|
||||
})
|
||||
|
||||
@@ -1046,12 +1046,12 @@ func (m *module) ListStatefulSets(ctx context.Context, orgID valuer.UUID, req *i
|
||||
})
|
||||
g.Go(func() error {
|
||||
var err error
|
||||
phaseCounts, err = m.getPerGroupPodPhaseCounts(gCtx, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
phaseCounts, err = m.getPerGroupPodPhaseCounts(gCtx, orgID, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
return err
|
||||
})
|
||||
g.Go(func() error {
|
||||
var err error
|
||||
podStatusCounts, podStatusWarning, err = m.getPerGroupPodStatusCountsWithReqMetricChecks(gCtx, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
podStatusCounts, podStatusWarning, err = m.getPerGroupPodStatusCountsWithReqMetricChecks(gCtx, orgID, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
return err
|
||||
})
|
||||
|
||||
@@ -1151,12 +1151,12 @@ func (m *module) ListJobs(ctx context.Context, orgID valuer.UUID, req *inframoni
|
||||
})
|
||||
g.Go(func() error {
|
||||
var err error
|
||||
phaseCounts, err = m.getPerGroupPodPhaseCounts(gCtx, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
phaseCounts, err = m.getPerGroupPodPhaseCounts(gCtx, orgID, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
return err
|
||||
})
|
||||
g.Go(func() error {
|
||||
var err error
|
||||
podStatusCounts, podStatusWarning, err = m.getPerGroupPodStatusCountsWithReqMetricChecks(gCtx, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
podStatusCounts, podStatusWarning, err = m.getPerGroupPodStatusCountsWithReqMetricChecks(gCtx, orgID, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
return err
|
||||
})
|
||||
|
||||
@@ -1256,12 +1256,12 @@ func (m *module) ListDaemonSets(ctx context.Context, orgID valuer.UUID, req *inf
|
||||
})
|
||||
g.Go(func() error {
|
||||
var err error
|
||||
phaseCounts, err = m.getPerGroupPodPhaseCounts(gCtx, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
phaseCounts, err = m.getPerGroupPodPhaseCounts(gCtx, orgID, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
return err
|
||||
})
|
||||
g.Go(func() error {
|
||||
var err error
|
||||
podStatusCounts, podStatusWarning, err = m.getPerGroupPodStatusCountsWithReqMetricChecks(gCtx, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
podStatusCounts, podStatusWarning, err = m.getPerGroupPodStatusCountsWithReqMetricChecks(gCtx, orgID, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
return err
|
||||
})
|
||||
|
||||
|
||||
@@ -177,6 +177,7 @@ func (m *module) getNodesTableMetadata(ctx context.Context, orgID valuer.UUID, r
|
||||
// Groups absent from the result map have implicit zero counts (caller default).
|
||||
func (m *module) getPerGroupNodeConditionCounts(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
start, end int64,
|
||||
filter *qbtypes.Filter,
|
||||
groupBy []qbtypes.GroupByKey,
|
||||
@@ -217,7 +218,7 @@ func (m *module) getPerGroupNodeConditionCounts(
|
||||
timeSeriesFPs.LE("unix_milli", flooredEndMs),
|
||||
)
|
||||
if mergedFilterExpr != "" {
|
||||
filterClause, err := m.buildFilterClause(ctx, &qbtypes.Filter{Expression: mergedFilterExpr}, start, end)
|
||||
filterClause, err := m.buildFilterClause(ctx, orgID, &qbtypes.Filter{Expression: mergedFilterExpr}, start, end)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -248,6 +248,7 @@ func (m *module) getPodsTableMetadata(ctx context.Context, orgID valuer.UUID, re
|
||||
// Groups absent from the result map have implicit zero counts (caller default).
|
||||
func (m *module) getPerGroupPodPhaseCounts(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
start, end int64,
|
||||
filter *qbtypes.Filter,
|
||||
groupBy []qbtypes.GroupByKey,
|
||||
@@ -288,7 +289,7 @@ func (m *module) getPerGroupPodPhaseCounts(
|
||||
timeSeriesFPs.LE("unix_milli", flooredEndMs),
|
||||
)
|
||||
if mergedFilterExpr != "" {
|
||||
filterClause, err := m.buildFilterClause(ctx, &qbtypes.Filter{Expression: mergedFilterExpr}, start, end)
|
||||
filterClause, err := m.buildFilterClause(ctx, orgID, &qbtypes.Filter{Expression: mergedFilterExpr}, start, end)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -397,6 +398,7 @@ func (m *module) getPerGroupPodPhaseCounts(
|
||||
// runs the query. The returned counts map is empty (never nil) when gated off.
|
||||
func (m *module) getPerGroupPodStatusCountsWithReqMetricChecks(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
start, end int64,
|
||||
filter *qbtypes.Filter,
|
||||
groupBy []qbtypes.GroupByKey,
|
||||
@@ -426,7 +428,7 @@ func (m *module) getPerGroupPodStatusCountsWithReqMetricChecks(
|
||||
return map[string]podStatusCounts{}, warning, nil
|
||||
}
|
||||
|
||||
counts, err := m.getPerGroupPodStatusCounts(ctx, start, end, filter, groupBy, pageGroups)
|
||||
counts, err := m.getPerGroupPodStatusCounts(ctx, orgID, start, end, filter, groupBy, pageGroups)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -448,6 +450,7 @@ func (m *module) getPerGroupPodStatusCountsWithReqMetricChecks(
|
||||
// Groups absent from the result map have implicit zero counts (caller default).
|
||||
func (m *module) getPerGroupPodStatusCounts(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
start, end int64,
|
||||
filter *qbtypes.Filter,
|
||||
groupBy []qbtypes.GroupByKey,
|
||||
@@ -476,7 +479,7 @@ func (m *module) getPerGroupPodStatusCounts(
|
||||
err error
|
||||
)
|
||||
if mergedFilterExpr != "" {
|
||||
filterClause, err = m.buildFilterClause(ctx, &qbtypes.Filter{Expression: mergedFilterExpr}, start, end)
|
||||
filterClause, err = m.buildFilterClause(ctx, orgID, &qbtypes.Filter{Expression: mergedFilterExpr}, start, end)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -774,6 +777,7 @@ func (m *module) getPerGroupPodStatusCounts(
|
||||
// Groups absent from the result map have no data (caller default).
|
||||
func (m *module) getPerGroupPodRestartCounts(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
start, end int64,
|
||||
filter *qbtypes.Filter,
|
||||
groupBy []qbtypes.GroupByKey,
|
||||
@@ -798,7 +802,7 @@ func (m *module) getPerGroupPodRestartCounts(
|
||||
err error
|
||||
)
|
||||
if mergedFilterExpr != "" {
|
||||
filterClause, err = m.buildFilterClause(ctx, &qbtypes.Filter{Expression: mergedFilterExpr}, start, end)
|
||||
filterClause, err = m.buildFilterClause(ctx, orgID, &qbtypes.Filter{Expression: mergedFilterExpr}, start, end)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -565,7 +565,7 @@ func (m *module) InspectMetrics(
|
||||
start := uint64(req.Start)
|
||||
end := uint64(req.End)
|
||||
|
||||
filterWhereClause, err := m.buildFilterClause(ctx, req.Filter, req.Start, req.End)
|
||||
filterWhereClause, err := m.buildFilterClause(ctx, orgID, req.Filter, req.Start, req.End)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -950,7 +950,7 @@ func (m *module) insertMetricsMetadata(ctx context.Context, orgID valuer.UUID, r
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *module) buildFilterClause(ctx context.Context, filter *qbtypes.Filter, startMillis, endMillis int64) (*sqlbuilder.WhereClause, error) {
|
||||
func (m *module) buildFilterClause(ctx context.Context, orgID valuer.UUID, filter *qbtypes.Filter, startMillis, endMillis int64) (*sqlbuilder.WhereClause, error) {
|
||||
expression := ""
|
||||
if filter != nil {
|
||||
expression = strings.TrimSpace(filter.Expression)
|
||||
@@ -969,7 +969,7 @@ func (m *module) buildFilterClause(ctx context.Context, filter *qbtypes.Filter,
|
||||
// whereClauseSelectors[idx].Source = query.Source
|
||||
}
|
||||
|
||||
keys, _, err := m.telemetryMetadataStore.GetKeysMulti(ctx, whereClauseSelectors)
|
||||
keys, _, err := m.telemetryMetadataStore.GetKeysMulti(ctx, orgID, whereClauseSelectors)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1010,7 +1010,7 @@ func (m *module) fetchMetricsStatsWithSamples(
|
||||
var filterWhereClause *sqlbuilder.WhereClause
|
||||
if hasFilter {
|
||||
var err error
|
||||
filterWhereClause, err = m.buildFilterClause(ctx, req.Filter, req.Start, req.End)
|
||||
filterWhereClause, err = m.buildFilterClause(ctx, orgID, req.Filter, req.Start, req.End)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
@@ -1214,7 +1214,7 @@ func (m *module) computeTimeseriesTreemap(ctx context.Context, orgID valuer.UUID
|
||||
var filterWhereClause *sqlbuilder.WhereClause
|
||||
if hasFilter {
|
||||
var err error
|
||||
filterWhereClause, err = m.buildFilterClause(ctx, req.Filter, req.Start, req.End)
|
||||
filterWhereClause, err = m.buildFilterClause(ctx, orgID, req.Filter, req.Start, req.End)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1332,7 +1332,7 @@ func (m *module) computeSamplesTreemap(ctx context.Context, orgID valuer.UUID, r
|
||||
var filterWhereClause *sqlbuilder.WhereClause
|
||||
if hasFilter {
|
||||
var err error
|
||||
filterWhereClause, err = m.buildFilterClause(ctx, req.Filter, req.Start, req.End)
|
||||
filterWhereClause, err = m.buildFilterClause(ctx, orgID, req.Filter, req.Start, req.End)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/querybuilder"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/huandu/go-sqlbuilder"
|
||||
)
|
||||
|
||||
@@ -22,6 +23,7 @@ func newConditionBuilder(fm qbtypes.FieldMapper) qbtypes.ConditionBuilder {
|
||||
|
||||
func (c *conditionBuilder) ConditionFor(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
startNs uint64,
|
||||
endNs uint64,
|
||||
key *telemetrytypes.TelemetryFieldKey,
|
||||
@@ -47,7 +49,7 @@ func (c *conditionBuilder) ConditionFor(
|
||||
|
||||
conds := make([]string, 0, len(keys))
|
||||
for _, k := range keys {
|
||||
cond, err := c.conditionForKey(ctx, startNs, endNs, k, operator, value, sb)
|
||||
cond, err := c.conditionForKey(ctx, orgID, startNs, endNs, k, operator, value, sb)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -58,6 +60,7 @@ func (c *conditionBuilder) ConditionFor(
|
||||
|
||||
func (c *conditionBuilder) conditionForKey(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
startNs uint64,
|
||||
endNs uint64,
|
||||
key *telemetrytypes.TelemetryFieldKey,
|
||||
@@ -69,7 +72,7 @@ func (c *conditionBuilder) conditionForKey(
|
||||
value = querybuilder.FormatValueForContains(value)
|
||||
}
|
||||
|
||||
fieldName, err := c.fm.FieldFor(ctx, startNs, endNs, key)
|
||||
fieldName, err := c.fm.FieldFor(ctx, orgID, startNs, endNs, key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
schema "github.com/SigNoz/signoz-otel-collector/cmd/signozschemamigrator/schema_migrator"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/huandu/go-sqlbuilder"
|
||||
)
|
||||
|
||||
@@ -30,6 +31,12 @@ func newFieldMapper() qbtypes.FieldMapper {
|
||||
return &fieldMapper{}
|
||||
}
|
||||
|
||||
// CandidateKeys returns nil: rule-state history has no attribute-map fallback, so a
|
||||
// context-missing key stays unresolved and the caller errors.
|
||||
func (m *fieldMapper) CandidateKeys(_ context.Context, _ valuer.UUID, _ *telemetrytypes.TelemetryFieldKey, _ any, _ map[string][]*telemetrytypes.TelemetryFieldKey) []*telemetrytypes.TelemetryFieldKey {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *fieldMapper) getColumn(_ context.Context, key *telemetrytypes.TelemetryFieldKey) (*schema.Column, error) { //nolint:unparam
|
||||
name := strings.TrimSpace(key.Name)
|
||||
if col, ok := ruleStateHistoryColumns[name]; ok {
|
||||
@@ -38,7 +45,7 @@ func (m *fieldMapper) getColumn(_ context.Context, key *telemetrytypes.Telemetry
|
||||
return ruleStateHistoryColumns["labels"], nil
|
||||
}
|
||||
|
||||
func (m *fieldMapper) FieldFor(ctx context.Context, _, _ uint64, key *telemetrytypes.TelemetryFieldKey) (string, error) {
|
||||
func (m *fieldMapper) FieldFor(ctx context.Context, _ valuer.UUID, _, _ uint64, key *telemetrytypes.TelemetryFieldKey) (string, error) {
|
||||
col, err := m.getColumn(ctx, key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -49,7 +56,7 @@ func (m *fieldMapper) FieldFor(ctx context.Context, _, _ uint64, key *telemetryt
|
||||
return col.Name, nil
|
||||
}
|
||||
|
||||
func (m *fieldMapper) ColumnFor(ctx context.Context, _, _ uint64, key *telemetrytypes.TelemetryFieldKey) ([]*schema.Column, error) {
|
||||
func (m *fieldMapper) ColumnFor(ctx context.Context, _ valuer.UUID, _, _ uint64, key *telemetrytypes.TelemetryFieldKey) ([]*schema.Column, error) {
|
||||
col, err := m.getColumn(ctx, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -57,8 +64,8 @@ func (m *fieldMapper) ColumnFor(ctx context.Context, _, _ uint64, key *telemetry
|
||||
return []*schema.Column{col}, nil
|
||||
}
|
||||
|
||||
func (m *fieldMapper) ColumnExpressionFor(ctx context.Context, tsStart, tsEnd uint64, field *telemetrytypes.TelemetryFieldKey, _ map[string][]*telemetrytypes.TelemetryFieldKey) (string, error) {
|
||||
colName, err := m.FieldFor(ctx, tsStart, tsEnd, field)
|
||||
func (m *fieldMapper) ColumnExpressionFor(ctx context.Context, orgID valuer.UUID, tsStart, tsEnd uint64, field *telemetrytypes.TelemetryFieldKey, _ map[string][]*telemetrytypes.TelemetryFieldKey) (string, error) {
|
||||
colName, err := m.FieldFor(ctx, orgID, tsStart, tsEnd, field)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -10,9 +10,11 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/http/binding"
|
||||
"github.com/SigNoz/signoz/pkg/http/render"
|
||||
"github.com/SigNoz/signoz/pkg/modules/rulestatehistory"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/rulestatehistorytypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
@@ -85,7 +87,13 @@ func (h *handler) GetRuleHistoryTimeline(w http.ResponseWriter, r *http.Request)
|
||||
req.Query.Limit = 50
|
||||
}
|
||||
|
||||
timelineItems, timelineTotal, err := h.module.GetHistoryTimeline(r.Context(), ruleID, req.Query)
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
timelineItems, timelineTotal, err := h.module.GetHistoryTimeline(r.Context(), valuer.MustNewUUID(claims.OrgID), ruleID, req.Query)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
@@ -127,7 +135,13 @@ func (h *handler) GetRuleHistoryContributors(w http.ResponseWriter, r *http.Requ
|
||||
return
|
||||
}
|
||||
|
||||
res, err := h.module.GetHistoryContributors(r.Context(), ruleID, query)
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
res, err := h.module.GetHistoryContributors(r.Context(), valuer.MustNewUUID(claims.OrgID), ruleID, query)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
@@ -152,7 +166,13 @@ func (h *handler) GetRuleHistoryFilterKeys(w http.ResponseWriter, r *http.Reques
|
||||
return
|
||||
}
|
||||
|
||||
res, err := h.module.GetHistoryFilterKeys(r.Context(), ruleID, query, search, limit)
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
res, err := h.module.GetHistoryFilterKeys(r.Context(), valuer.MustNewUUID(claims.OrgID), ruleID, query, search, limit)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
@@ -167,7 +187,13 @@ func (h *handler) GetRuleHistoryFilterValues(w http.ResponseWriter, r *http.Requ
|
||||
return
|
||||
}
|
||||
|
||||
res, err := h.module.GetHistoryFilterValues(r.Context(), ruleID, key, query, search, limit)
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
res, err := h.module.GetHistoryFilterValues(r.Context(), valuer.MustNewUUID(claims.OrgID), ruleID, key, query, search, limit)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/types/rulestatehistorytypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/ruletypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type module struct {
|
||||
@@ -23,20 +24,20 @@ func (m *module) GetLastSavedRuleStateHistory(ctx context.Context, ruleID string
|
||||
return m.store.GetLastSavedRuleStateHistory(ctx, ruleID)
|
||||
}
|
||||
|
||||
func (m *module) GetHistoryTimeline(ctx context.Context, ruleID string, query rulestatehistorytypes.Query) ([]rulestatehistorytypes.RuleStateHistory, uint64, error) {
|
||||
return m.store.ReadRuleStateHistoryByRuleID(ctx, ruleID, &query)
|
||||
func (m *module) GetHistoryTimeline(ctx context.Context, orgID valuer.UUID, ruleID string, query rulestatehistorytypes.Query) ([]rulestatehistorytypes.RuleStateHistory, uint64, error) {
|
||||
return m.store.ReadRuleStateHistoryByRuleID(ctx, orgID, ruleID, &query)
|
||||
}
|
||||
|
||||
func (m *module) GetHistoryFilterKeys(ctx context.Context, ruleID string, query rulestatehistorytypes.Query, search string, limit int64) (*telemetrytypes.GettableFieldKeys, error) {
|
||||
return m.store.ReadRuleStateHistoryFilterKeysByRuleID(ctx, ruleID, &query, search, limit)
|
||||
func (m *module) GetHistoryFilterKeys(ctx context.Context, orgID valuer.UUID, ruleID string, query rulestatehistorytypes.Query, search string, limit int64) (*telemetrytypes.GettableFieldKeys, error) {
|
||||
return m.store.ReadRuleStateHistoryFilterKeysByRuleID(ctx, orgID, ruleID, &query, search, limit)
|
||||
}
|
||||
|
||||
func (m *module) GetHistoryFilterValues(ctx context.Context, ruleID string, key string, query rulestatehistorytypes.Query, search string, limit int64) (*telemetrytypes.GettableFieldValues, error) {
|
||||
return m.store.ReadRuleStateHistoryFilterValuesByRuleID(ctx, ruleID, key, &query, search, limit)
|
||||
func (m *module) GetHistoryFilterValues(ctx context.Context, orgID valuer.UUID, ruleID string, key string, query rulestatehistorytypes.Query, search string, limit int64) (*telemetrytypes.GettableFieldValues, error) {
|
||||
return m.store.ReadRuleStateHistoryFilterValuesByRuleID(ctx, orgID, ruleID, key, &query, search, limit)
|
||||
}
|
||||
|
||||
func (m *module) GetHistoryContributors(ctx context.Context, ruleID string, query rulestatehistorytypes.Query) ([]rulestatehistorytypes.RuleStateHistoryContributor, error) {
|
||||
return m.store.ReadRuleStateHistoryTopContributorsByRuleID(ctx, ruleID, &query)
|
||||
func (m *module) GetHistoryContributors(ctx context.Context, orgID valuer.UUID, ruleID string, query rulestatehistorytypes.Query) ([]rulestatehistorytypes.RuleStateHistoryContributor, error) {
|
||||
return m.store.ReadRuleStateHistoryTopContributorsByRuleID(ctx, orgID, ruleID, &query)
|
||||
}
|
||||
|
||||
func (m *module) GetHistoryOverallStatus(ctx context.Context, ruleID string, query rulestatehistorytypes.Query) ([]rulestatehistorytypes.GettableRuleStateWindow, error) {
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/types/rulestatehistorytypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/ruletypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
sqlbuilder "github.com/huandu/go-sqlbuilder"
|
||||
)
|
||||
|
||||
@@ -102,7 +103,7 @@ func (s *store) GetLastSavedRuleStateHistory(ctx context.Context, ruleID string)
|
||||
return history, nil
|
||||
}
|
||||
|
||||
func (s *store) ReadRuleStateHistoryByRuleID(ctx context.Context, ruleID string, query *rulestatehistorytypes.Query) ([]rulestatehistorytypes.RuleStateHistory, uint64, error) {
|
||||
func (s *store) ReadRuleStateHistoryByRuleID(ctx context.Context, orgID valuer.UUID, ruleID string, query *rulestatehistorytypes.Query) ([]rulestatehistorytypes.RuleStateHistory, uint64, error) {
|
||||
sb := sqlbuilder.NewSelectBuilder()
|
||||
sb.Select(
|
||||
"rule_id",
|
||||
@@ -119,7 +120,7 @@ func (s *store) ReadRuleStateHistoryByRuleID(ctx context.Context, ruleID string,
|
||||
sb.From(historyTable())
|
||||
s.applyBaseHistoryFilters(sb, ruleID, query)
|
||||
|
||||
whereClause, err := s.buildFilterClause(ctx, query.FilterExpression, query.Start, query.End)
|
||||
whereClause, err := s.buildFilterClause(ctx, orgID, query.FilterExpression, query.Start, query.End)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
@@ -155,7 +156,7 @@ func (s *store) ReadRuleStateHistoryByRuleID(ctx context.Context, ruleID string,
|
||||
return history, total, nil
|
||||
}
|
||||
|
||||
func (s *store) ReadRuleStateHistoryFilterKeysByRuleID(ctx context.Context, ruleID string, query *rulestatehistorytypes.Query, search string, limit int64) (*telemetrytypes.GettableFieldKeys, error) {
|
||||
func (s *store) ReadRuleStateHistoryFilterKeysByRuleID(ctx context.Context, orgID valuer.UUID, ruleID string, query *rulestatehistorytypes.Query, search string, limit int64) (*telemetrytypes.GettableFieldKeys, error) {
|
||||
if limit <= 0 {
|
||||
limit = 50
|
||||
}
|
||||
@@ -172,7 +173,7 @@ func (s *store) ReadRuleStateHistoryFilterKeysByRuleID(ctx context.Context, rule
|
||||
sb.Where(fmt.Sprintf("positionCaseInsensitiveUTF8(%s, %s) > 0", keyExpr, sb.Var(search)))
|
||||
}
|
||||
|
||||
whereClause, err := s.buildFilterClause(ctx, query.FilterExpression, query.Start, query.End)
|
||||
whereClause, err := s.buildFilterClause(ctx, orgID, query.FilterExpression, query.Start, query.End)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -226,7 +227,7 @@ func (s *store) ReadRuleStateHistoryFilterKeysByRuleID(ctx context.Context, rule
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *store) ReadRuleStateHistoryFilterValuesByRuleID(ctx context.Context, ruleID string, key string, query *rulestatehistorytypes.Query, search string, limit int64) (*telemetrytypes.GettableFieldValues, error) {
|
||||
func (s *store) ReadRuleStateHistoryFilterValuesByRuleID(ctx context.Context, orgID valuer.UUID, ruleID string, key string, query *rulestatehistorytypes.Query, search string, limit int64) (*telemetrytypes.GettableFieldValues, error) {
|
||||
if limit <= 0 {
|
||||
limit = 50
|
||||
}
|
||||
@@ -244,7 +245,7 @@ func (s *store) ReadRuleStateHistoryFilterValuesByRuleID(ctx context.Context, ru
|
||||
sb.Where(fmt.Sprintf("positionCaseInsensitiveUTF8(%s, %s) > 0", valExpr, sb.Var(search)))
|
||||
}
|
||||
|
||||
whereClause, err := s.buildFilterClause(ctx, query.FilterExpression, query.Start, query.End)
|
||||
whereClause, err := s.buildFilterClause(ctx, orgID, query.FilterExpression, query.Start, query.End)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -291,7 +292,7 @@ func (s *store) ReadRuleStateHistoryFilterValuesByRuleID(ctx context.Context, ru
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *store) ReadRuleStateHistoryTopContributorsByRuleID(ctx context.Context, ruleID string, query *rulestatehistorytypes.Query) ([]rulestatehistorytypes.RuleStateHistoryContributor, error) {
|
||||
func (s *store) ReadRuleStateHistoryTopContributorsByRuleID(ctx context.Context, orgID valuer.UUID, ruleID string, query *rulestatehistorytypes.Query) ([]rulestatehistorytypes.RuleStateHistoryContributor, error) {
|
||||
sb := sqlbuilder.NewSelectBuilder()
|
||||
sb.Select(
|
||||
"fingerprint",
|
||||
@@ -305,7 +306,7 @@ func (s *store) ReadRuleStateHistoryTopContributorsByRuleID(ctx context.Context,
|
||||
sb.Where(sb.GE("unix_milli", query.Start))
|
||||
sb.Where(sb.LT("unix_milli", query.End))
|
||||
|
||||
whereClause, err := s.buildFilterClause(ctx, query.FilterExpression, query.Start, query.End)
|
||||
whereClause, err := s.buildFilterClause(ctx, orgID, query.FilterExpression, query.Start, query.End)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -472,7 +473,7 @@ func (s *store) querySeries(ctx context.Context, selectQuery string, args ...any
|
||||
return series, nil
|
||||
}
|
||||
|
||||
func (s *store) buildFilterClause(ctx context.Context, filter qbtypes.Filter, startMillis, endMillis int64) (*sqlbuilder.WhereClause, error) {
|
||||
func (s *store) buildFilterClause(ctx context.Context, orgID valuer.UUID, filter qbtypes.Filter, startMillis, endMillis int64) (*sqlbuilder.WhereClause, error) {
|
||||
expression := strings.TrimSpace(filter.Expression)
|
||||
if expression == "" {
|
||||
return nil, nil //nolint:nilnil
|
||||
@@ -483,7 +484,7 @@ func (s *store) buildFilterClause(ctx context.Context, filter qbtypes.Filter, st
|
||||
selectors[i].SelectorMatchType = telemetrytypes.FieldSelectorMatchTypeExact
|
||||
}
|
||||
|
||||
fieldKeys, _, err := s.telemetryMetadataStore.GetKeysMulti(ctx, selectors)
|
||||
fieldKeys, _, err := s.telemetryMetadataStore.GetKeysMulti(ctx, orgID, selectors)
|
||||
if err != nil || len(fieldKeys) == 0 {
|
||||
fieldKeys = map[string][]*telemetrytypes.TelemetryFieldKey{}
|
||||
for _, sel := range selectors {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/rulestatehistorytypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
// Module defines the core operations for managing rule state history.
|
||||
@@ -23,16 +24,16 @@ type Module interface {
|
||||
|
||||
// GetHistoryTimeline returns a time-ordered list of rule state history entries and a total count
|
||||
// for the given query, suitable for paginated timeline views.
|
||||
GetHistoryTimeline(context.Context, string, rulestatehistorytypes.Query) ([]rulestatehistorytypes.RuleStateHistory, uint64, error)
|
||||
GetHistoryTimeline(context.Context, valuer.UUID, string, rulestatehistorytypes.Query) ([]rulestatehistorytypes.RuleStateHistory, uint64, error)
|
||||
|
||||
// GetHistoryFilterKeys returns the available filter keys for rule state history queries.
|
||||
GetHistoryFilterKeys(context.Context, string, rulestatehistorytypes.Query, string, int64) (*telemetrytypes.GettableFieldKeys, error)
|
||||
GetHistoryFilterKeys(context.Context, valuer.UUID, string, rulestatehistorytypes.Query, string, int64) (*telemetrytypes.GettableFieldKeys, error)
|
||||
|
||||
// GetHistoryFilterValues returns the available values for a specific filter key in rule state history.
|
||||
GetHistoryFilterValues(context.Context, string, string, rulestatehistorytypes.Query, string, int64) (*telemetrytypes.GettableFieldValues, error)
|
||||
GetHistoryFilterValues(context.Context, valuer.UUID, string, string, rulestatehistorytypes.Query, string, int64) (*telemetrytypes.GettableFieldValues, error)
|
||||
|
||||
// GetHistoryContributors returns the top contributors to trigger alert, for the given query.
|
||||
GetHistoryContributors(context.Context, string, rulestatehistorytypes.Query) ([]rulestatehistorytypes.RuleStateHistoryContributor, error)
|
||||
GetHistoryContributors(context.Context, valuer.UUID, string, rulestatehistorytypes.Query) ([]rulestatehistorytypes.RuleStateHistoryContributor, error)
|
||||
|
||||
// GetHistoryOverallStatus returns the overall status windows for rule state history,
|
||||
// providing an aggregated view of rule health over time.
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
const traceOutsideRangeWarn = "Query %s references a trace_id that exists between %s and %s (UTC) but lies outside the selected time range; adjust the time range to see results"
|
||||
@@ -24,6 +25,7 @@ const traceOutsideRangeWarn = "Query %s references a trace_id that exists betwee
|
||||
type builderQuery[T any] struct {
|
||||
logger *slog.Logger
|
||||
telemetryStore telemetrystore.TelemetryStore
|
||||
orgID valuer.UUID
|
||||
stmtBuilder qbtypes.StatementBuilder[T]
|
||||
spec qbtypes.QueryBuilderQuery[T]
|
||||
variables map[string]qbtypes.VariableItem
|
||||
@@ -45,6 +47,7 @@ type builderConfig struct {
|
||||
func newBuilderQuery[T any](
|
||||
logger *slog.Logger,
|
||||
telemetryStore telemetrystore.TelemetryStore,
|
||||
orgID valuer.UUID,
|
||||
stmtBuilder qbtypes.StatementBuilder[T],
|
||||
spec qbtypes.QueryBuilderQuery[T],
|
||||
tr qbtypes.TimeRange,
|
||||
@@ -55,6 +58,7 @@ func newBuilderQuery[T any](
|
||||
return &builderQuery[T]{
|
||||
logger: logger,
|
||||
telemetryStore: telemetryStore,
|
||||
orgID: orgID,
|
||||
stmtBuilder: stmtBuilder,
|
||||
spec: spec,
|
||||
variables: variables,
|
||||
@@ -214,7 +218,7 @@ func (q *builderQuery[T]) isWindowList() bool {
|
||||
|
||||
// Statement renders the SQL without executing it, for the preview path.
|
||||
func (q *builderQuery[T]) Statement(ctx context.Context) (*qbtypes.Statement, error) {
|
||||
return q.stmtBuilder.Build(ctx, q.fromMS, q.toMS, q.kind, q.spec, q.variables)
|
||||
return q.stmtBuilder.Build(ctx, q.orgID, q.fromMS, q.toMS, q.kind, q.spec, q.variables)
|
||||
}
|
||||
|
||||
func (q *builderQuery[T]) Execute(ctx context.Context) (*qbtypes.Result, error) {
|
||||
@@ -238,7 +242,7 @@ func (q *builderQuery[T]) Execute(ctx context.Context) (*qbtypes.Result, error)
|
||||
}
|
||||
}
|
||||
|
||||
stmt, err := q.stmtBuilder.Build(ctx, fromMS, toMS, q.kind, q.spec, q.variables)
|
||||
stmt, err := q.stmtBuilder.Build(ctx, q.orgID, fromMS, toMS, q.kind, q.spec, q.variables)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -491,7 +495,7 @@ func (q *builderQuery[T]) executeWindowList(ctx context.Context) (*qbtypes.Resul
|
||||
q.spec.Offset = 0
|
||||
q.spec.Limit = need
|
||||
|
||||
stmt, err := q.stmtBuilder.Build(ctx, r.fromNS/1e6, r.toNS/1e6, q.kind, q.spec, q.variables)
|
||||
stmt, err := q.stmtBuilder.Build(ctx, q.orgID, r.fromNS/1e6, r.toNS/1e6, q.kind, q.spec, q.variables)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -21,6 +21,11 @@ import (
|
||||
|
||||
var (
|
||||
aggRe = regexp.MustCompile(`^__result_(\d+)$`)
|
||||
// keyAliasRe matches the traces statement builder's positional column-alias prefix
|
||||
// `__SELECT_KEY_<n>_` / `__GROUP_BY_KEY_<n>_`, which disambiguates select/group-by
|
||||
// aliases from real table columns in the generated SQL. It is stripped here so the
|
||||
// original field name surfaces as the label / column / raw-data key.
|
||||
keyAliasRe = regexp.MustCompile(`^__(?:SELECT|GROUP_BY)_KEY_\d+_`)
|
||||
// legacyReservedColumnTargetAliases identifies result value from a user
|
||||
// written clickhouse query. The column alias indcate which value is
|
||||
// to be considered as final result (or target).
|
||||
@@ -29,6 +34,12 @@ var (
|
||||
CodeFailUnmarshalJSONColumn = errors.MustNewCode("fail_unmarshal_json_column")
|
||||
)
|
||||
|
||||
// stripKeyAlias removes the __SELECT_KEY_<n>_ / __GROUP_BY_KEY_<n>_ prefix from a result
|
||||
// column name, recovering the field name; unprefixed names are returned unchanged.
|
||||
func stripKeyAlias(name string) string {
|
||||
return keyAliasRe.ReplaceAllString(name, "")
|
||||
}
|
||||
|
||||
// consume reads every row and shapes it into the payload expected for the
|
||||
// given request type.
|
||||
//
|
||||
@@ -126,7 +137,7 @@ func readAsTimeSeries(rows driver.Rows, queryWindow *qbtypes.TimeRange, step qbt
|
||||
)
|
||||
|
||||
for idx, ptr := range slots {
|
||||
name := colNames[idx]
|
||||
name := stripKeyAlias(colNames[idx])
|
||||
|
||||
switch v := ptr.(type) {
|
||||
case *time.Time:
|
||||
@@ -296,6 +307,7 @@ func readAsScalar(rows driver.Rows, queryName string) (*qbtypes.ScalarData, erro
|
||||
|
||||
var aggIndex int64
|
||||
for i, name := range colNames {
|
||||
name = stripKeyAlias(name)
|
||||
colType := qbtypes.ColumnTypeGroup
|
||||
// Builder queries aliases aggregation columns as __result_N (always numeric) and wraps group-by keys with toString (always string);
|
||||
// Raw ClickHouse queries may use any aliases.
|
||||
@@ -406,7 +418,7 @@ func readAsRaw(rows driver.Rows, queryName string) (*qbtypes.RawData, error) {
|
||||
}
|
||||
|
||||
for i, cellPtr := range scan {
|
||||
name := colNames[i]
|
||||
name := stripKeyAlias(colNames[i])
|
||||
|
||||
// de-reference the typed pointer to any
|
||||
val := reflect.ValueOf(cellPtr).Elem().Interface()
|
||||
|
||||
@@ -78,7 +78,7 @@ func (q *querier) QueryRangePreview(
|
||||
skip[name] = true
|
||||
}
|
||||
}
|
||||
providers, buildErrs := q.buildPreviewProviders(req, dependencyQueries, missingMetricQuerySet, skip)
|
||||
providers, buildErrs := q.buildPreviewProviders(orgID, req, dependencyQueries, missingMetricQuerySet, skip)
|
||||
|
||||
// Render each executing query's statement and collect the ClickHouse-bound
|
||||
// analysis work to run concurrently.
|
||||
@@ -192,6 +192,7 @@ func missingMetricNames(env qbtypes.QueryEnvelope) []string {
|
||||
}
|
||||
|
||||
func (q *querier) buildPreviewProviders(
|
||||
orgID valuer.UUID,
|
||||
req *qbtypes.QueryRangeRequest,
|
||||
dependencyQueries map[string]bool,
|
||||
missingMetricQuerySet map[string]bool,
|
||||
@@ -230,7 +231,7 @@ func (q *querier) buildPreviewProviders(
|
||||
sub.CompositeQuery = qbtypes.CompositeQuery{Queries: []qbtypes.QueryEnvelope{query}}
|
||||
}
|
||||
|
||||
built, _, bErr := q.buildQueries(&sub, deps, missingMetricQuerySet, event)
|
||||
built, _, bErr := q.buildQueries(orgID, &sub, deps, missingMetricQuerySet, event)
|
||||
if bErr != nil {
|
||||
errs[name] = bErr
|
||||
continue
|
||||
|
||||
@@ -132,7 +132,7 @@ func (q *querier) QueryRange(ctx context.Context, orgID valuer.UUID, req *qbtype
|
||||
missingMetricQuerySet[name] = true
|
||||
}
|
||||
|
||||
queries, steps, err := q.buildQueries(req, dependencyQueries, missingMetricQuerySet, event)
|
||||
queries, steps, err := q.buildQueries(orgID, req, dependencyQueries, missingMetricQuerySet, event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -176,6 +176,7 @@ func (q *querier) QueryRange(ctx context.Context, orgID valuer.UUID, req *qbtype
|
||||
}
|
||||
|
||||
func (q *querier) buildQueries(
|
||||
orgID valuer.UUID,
|
||||
req *qbtypes.QueryRangeRequest,
|
||||
dependencyQueries map[string]bool,
|
||||
missingMetricQuerySet map[string]bool,
|
||||
@@ -221,6 +222,7 @@ func (q *querier) buildQueries(
|
||||
}
|
||||
toq := &traceOperatorQuery{
|
||||
telemetryStore: q.telemetryStore,
|
||||
orgID: orgID,
|
||||
stmtBuilder: q.traceOperatorStmtBuilder,
|
||||
spec: traceOpQuery,
|
||||
compositeQuery: &req.CompositeQuery,
|
||||
@@ -235,7 +237,7 @@ func (q *querier) buildQueries(
|
||||
case qbtypes.QueryBuilderQuery[qbtypes.TraceAggregation]:
|
||||
spec.ShiftBy = extractShiftFromBuilderQuery(spec)
|
||||
timeRange := adjustTimeRangeForShift(spec, qbtypes.TimeRange{From: req.Start, To: req.End}, req.RequestType)
|
||||
bq := newBuilderQuery(q.logger, q.telemetryStore, q.traceStmtBuilder, spec, timeRange, req.RequestType, tmplVars, builderConfig{})
|
||||
bq := newBuilderQuery(q.logger, q.telemetryStore, orgID, q.traceStmtBuilder, spec, timeRange, req.RequestType, tmplVars, builderConfig{})
|
||||
queries[spec.Name] = bq
|
||||
steps[spec.Name] = spec.StepInterval
|
||||
case qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]:
|
||||
@@ -245,7 +247,7 @@ func (q *querier) buildQueries(
|
||||
if spec.Source == telemetrytypes.SourceAudit {
|
||||
stmtBuilder = q.auditStmtBuilder
|
||||
}
|
||||
bq := newBuilderQuery(q.logger, q.telemetryStore, stmtBuilder, spec, timeRange, req.RequestType, tmplVars, q.builderConfig)
|
||||
bq := newBuilderQuery(q.logger, q.telemetryStore, orgID, stmtBuilder, spec, timeRange, req.RequestType, tmplVars, q.builderConfig)
|
||||
queries[spec.Name] = bq
|
||||
steps[spec.Name] = spec.StepInterval
|
||||
case qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation]:
|
||||
@@ -262,9 +264,9 @@ func (q *querier) buildQueries(
|
||||
|
||||
if spec.Source == telemetrytypes.SourceMeter {
|
||||
event.Source = telemetrytypes.SourceMeter.StringValue()
|
||||
bq = newBuilderQuery(q.logger, q.telemetryStore, q.meterStmtBuilder, spec, timeRange, req.RequestType, tmplVars, builderConfig{})
|
||||
bq = newBuilderQuery(q.logger, q.telemetryStore, orgID, q.meterStmtBuilder, spec, timeRange, req.RequestType, tmplVars, builderConfig{})
|
||||
} else {
|
||||
bq = newBuilderQuery(q.logger, q.telemetryStore, q.metricStmtBuilder, spec, timeRange, req.RequestType, tmplVars, builderConfig{})
|
||||
bq = newBuilderQuery(q.logger, q.telemetryStore, orgID, q.metricStmtBuilder, spec, timeRange, req.RequestType, tmplVars, builderConfig{})
|
||||
}
|
||||
|
||||
queries[spec.Name] = bq
|
||||
@@ -535,7 +537,7 @@ func (q *querier) QueryRawStream(ctx context.Context, orgID valuer.UUID, req *qb
|
||||
if spec.Source == telemetrytypes.SourceAudit {
|
||||
liveTailStmtBuilder = q.auditStmtBuilder
|
||||
}
|
||||
bq := newBuilderQuery(q.logger, q.telemetryStore, liveTailStmtBuilder, spec, timeRange, req.RequestType, map[string]qbtypes.VariableItem{
|
||||
bq := newBuilderQuery(q.logger, q.telemetryStore, orgID, liveTailStmtBuilder, spec, timeRange, req.RequestType, map[string]qbtypes.VariableItem{
|
||||
"id": {
|
||||
Value: updatedLogID,
|
||||
},
|
||||
@@ -780,7 +782,7 @@ func (q *querier) executeWithCache(ctx context.Context, orgID valuer.UUID, query
|
||||
defer func() { <-sem }()
|
||||
|
||||
// Create a new query with the missing time range
|
||||
rangedQuery := q.createRangedQuery(query, *tr)
|
||||
rangedQuery := q.createRangedQuery(orgID, query, *tr)
|
||||
if rangedQuery == nil {
|
||||
errs[idx] = errors.NewInternalf(errors.CodeInternal, "failed to create ranged query for range %d-%d", tr.From, tr.To)
|
||||
return
|
||||
@@ -841,7 +843,7 @@ func (q *querier) executeWithCache(ctx context.Context, orgID valuer.UUID, query
|
||||
}
|
||||
|
||||
// createRangedQuery creates a copy of the query with a different time range.
|
||||
func (q *querier) createRangedQuery(originalQuery qbtypes.Query, timeRange qbtypes.TimeRange) qbtypes.Query {
|
||||
func (q *querier) createRangedQuery(_ valuer.UUID, originalQuery qbtypes.Query, timeRange qbtypes.TimeRange) qbtypes.Query {
|
||||
// this is called in a goroutine, so we create a copy of the query to avoid race conditions
|
||||
switch qt := originalQuery.(type) {
|
||||
case *promqlQuery:
|
||||
@@ -858,7 +860,7 @@ func (q *querier) createRangedQuery(originalQuery qbtypes.Query, timeRange qbtyp
|
||||
specCopy := qt.spec.Copy()
|
||||
specCopy.ShiftBy = extractShiftFromBuilderQuery(specCopy)
|
||||
adjustedTimeRange := adjustTimeRangeForShift(specCopy, timeRange, qt.kind)
|
||||
return newBuilderQuery(q.logger, q.telemetryStore, q.traceStmtBuilder, specCopy, adjustedTimeRange, qt.kind, qt.variables, builderConfig{})
|
||||
return newBuilderQuery(q.logger, q.telemetryStore, qt.orgID, q.traceStmtBuilder, specCopy, adjustedTimeRange, qt.kind, qt.variables, builderConfig{})
|
||||
|
||||
case *builderQuery[qbtypes.LogAggregation]:
|
||||
specCopy := qt.spec.Copy()
|
||||
@@ -868,20 +870,21 @@ func (q *querier) createRangedQuery(originalQuery qbtypes.Query, timeRange qbtyp
|
||||
if qt.spec.Source == telemetrytypes.SourceAudit {
|
||||
shiftStmtBuilder = q.auditStmtBuilder
|
||||
}
|
||||
return newBuilderQuery(q.logger, q.telemetryStore, shiftStmtBuilder, specCopy, adjustedTimeRange, qt.kind, qt.variables, q.builderConfig)
|
||||
return newBuilderQuery(q.logger, q.telemetryStore, qt.orgID, shiftStmtBuilder, specCopy, adjustedTimeRange, qt.kind, qt.variables, q.builderConfig)
|
||||
|
||||
case *builderQuery[qbtypes.MetricAggregation]:
|
||||
specCopy := qt.spec.Copy()
|
||||
specCopy.ShiftBy = extractShiftFromBuilderQuery(specCopy)
|
||||
adjustedTimeRange := adjustTimeRangeForShift(specCopy, timeRange, qt.kind)
|
||||
if qt.spec.Source == telemetrytypes.SourceMeter {
|
||||
return newBuilderQuery(q.logger, q.telemetryStore, q.meterStmtBuilder, specCopy, adjustedTimeRange, qt.kind, qt.variables, builderConfig{})
|
||||
return newBuilderQuery(q.logger, q.telemetryStore, qt.orgID, q.meterStmtBuilder, specCopy, adjustedTimeRange, qt.kind, qt.variables, builderConfig{})
|
||||
}
|
||||
return newBuilderQuery(q.logger, q.telemetryStore, q.metricStmtBuilder, specCopy, adjustedTimeRange, qt.kind, qt.variables, builderConfig{})
|
||||
return newBuilderQuery(q.logger, q.telemetryStore, qt.orgID, q.metricStmtBuilder, specCopy, adjustedTimeRange, qt.kind, qt.variables, builderConfig{})
|
||||
case *traceOperatorQuery:
|
||||
specCopy := qt.spec.Copy()
|
||||
return &traceOperatorQuery{
|
||||
telemetryStore: q.telemetryStore,
|
||||
orgID: qt.orgID,
|
||||
stmtBuilder: q.traceOperatorStmtBuilder,
|
||||
spec: specCopy,
|
||||
fromMS: uint64(timeRange.From),
|
||||
|
||||
@@ -30,7 +30,7 @@ func (m *queryMatcherAny) Match(string, string) error { return nil }
|
||||
// and returns a fixed query string so the mock ClickHouse can match it.
|
||||
type mockMetricStmtBuilder struct{}
|
||||
|
||||
func (m *mockMetricStmtBuilder) Build(_ context.Context, _, _ uint64, _ qbtypes.RequestType, _ qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation], _ map[string]qbtypes.VariableItem) (*qbtypes.Statement, error) {
|
||||
func (m *mockMetricStmtBuilder) Build(_ context.Context, _ valuer.UUID, _, _ uint64, _ qbtypes.RequestType, _ qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation], _ map[string]qbtypes.VariableItem) (*qbtypes.Statement, error) {
|
||||
return &qbtypes.Statement{
|
||||
Query: "SELECT ts, value FROM signoz_metrics",
|
||||
Args: nil,
|
||||
|
||||
@@ -10,10 +10,12 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type traceOperatorQuery struct {
|
||||
telemetryStore telemetrystore.TelemetryStore
|
||||
orgID valuer.UUID
|
||||
stmtBuilder qbtypes.TraceOperatorStatementBuilder
|
||||
spec qbtypes.QueryBuilderTraceOperator
|
||||
compositeQuery *qbtypes.CompositeQuery
|
||||
@@ -35,12 +37,13 @@ func (q *traceOperatorQuery) Window() (uint64, uint64) {
|
||||
|
||||
// Statement renders the SQL without executing it, for the preview path.
|
||||
func (q *traceOperatorQuery) Statement(ctx context.Context) (*qbtypes.Statement, error) {
|
||||
return q.stmtBuilder.Build(ctx, q.fromMS, q.toMS, q.kind, q.spec, q.compositeQuery)
|
||||
return q.stmtBuilder.Build(ctx, q.orgID, q.fromMS, q.toMS, q.kind, q.spec, q.compositeQuery)
|
||||
}
|
||||
|
||||
func (q *traceOperatorQuery) Execute(ctx context.Context) (*qbtypes.Result, error) {
|
||||
stmt, err := q.stmtBuilder.Build(
|
||||
ctx,
|
||||
q.orgID,
|
||||
q.fromMS,
|
||||
q.toMS,
|
||||
q.kind,
|
||||
|
||||
@@ -402,7 +402,6 @@ func (pc *LogParsingPipelineController) RecommendAgentConfig(
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
// TODO(Tushar): thread orgID here to evaluate correctly
|
||||
if pc.fl.BooleanOrEmpty(ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(orgId)) {
|
||||
// add default normalize pipeline at the beginning, only for sending to collector
|
||||
enrichedPipelines = append([]pipelinetypes.GettablePipeline{pc.getNormalizePipeline()}, enrichedPipelines...)
|
||||
|
||||
@@ -53,6 +53,7 @@ func NewAggExprRewriter(
|
||||
// and the args if the parametric aggregation function is used.
|
||||
func (r *aggExprRewriter) Rewrite(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
startNs uint64,
|
||||
endNs uint64,
|
||||
expr string,
|
||||
@@ -83,6 +84,7 @@ func (r *aggExprRewriter) Rewrite(
|
||||
|
||||
visitor := newExprVisitor(
|
||||
ctx,
|
||||
orgID,
|
||||
startNs,
|
||||
endNs,
|
||||
r.logger,
|
||||
@@ -107,6 +109,7 @@ func (r *aggExprRewriter) Rewrite(
|
||||
// RewriteMulti rewrites a slice of expressions.
|
||||
func (r *aggExprRewriter) RewriteMulti(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
startNs uint64,
|
||||
endNs uint64,
|
||||
exprs []string,
|
||||
@@ -117,7 +120,7 @@ func (r *aggExprRewriter) RewriteMulti(
|
||||
var errs []error
|
||||
var chArgsList [][]any
|
||||
for i, e := range exprs {
|
||||
w, chArgs, err := r.Rewrite(ctx, startNs, endNs, e, rateInterval, keys)
|
||||
w, chArgs, err := r.Rewrite(ctx, orgID, startNs, endNs, e, rateInterval, keys)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
out[i] = e
|
||||
@@ -135,6 +138,7 @@ func (r *aggExprRewriter) RewriteMulti(
|
||||
// exprVisitor walks FunctionExpr nodes and applies the mappers.
|
||||
type exprVisitor struct {
|
||||
ctx context.Context
|
||||
orgID valuer.UUID
|
||||
startNs uint64
|
||||
endNs uint64
|
||||
chparser.DefaultASTVisitor
|
||||
@@ -152,6 +156,7 @@ type exprVisitor struct {
|
||||
|
||||
func newExprVisitor(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
startNs uint64,
|
||||
endNs uint64,
|
||||
logger *slog.Logger,
|
||||
@@ -164,6 +169,7 @@ func newExprVisitor(
|
||||
) *exprVisitor {
|
||||
return &exprVisitor{
|
||||
ctx: ctx,
|
||||
orgID: orgID,
|
||||
startNs: startNs,
|
||||
endNs: endNs,
|
||||
logger: logger,
|
||||
@@ -206,7 +212,7 @@ func (v *exprVisitor) VisitFunctionExpr(fn *chparser.FunctionExpr) error {
|
||||
dataType = telemetrytypes.FieldDataTypeFloat64
|
||||
}
|
||||
|
||||
bodyJSONEnabled := v.flagger.BooleanOrEmpty(v.ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(valuer.UUID{}))
|
||||
bodyJSONEnabled := v.flagger.BooleanOrEmpty(v.ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(v.orgID))
|
||||
|
||||
// Handle *If functions with predicate + values
|
||||
if aggFunc.FuncCombinator {
|
||||
@@ -216,6 +222,7 @@ func (v *exprVisitor) VisitFunctionExpr(fn *chparser.FunctionExpr) error {
|
||||
origPred,
|
||||
FilterExprVisitorOpts{
|
||||
Context: v.ctx,
|
||||
OrgID: v.orgID,
|
||||
Logger: v.logger,
|
||||
FieldKeys: v.fieldKeys,
|
||||
FieldMapper: v.fieldMapper,
|
||||
@@ -247,7 +254,7 @@ func (v *exprVisitor) VisitFunctionExpr(fn *chparser.FunctionExpr) error {
|
||||
for i := 0; i < len(args)-1; i++ {
|
||||
origVal := args[i].String()
|
||||
fieldKey := telemetrytypes.GetFieldKeyFromKeyText(origVal)
|
||||
expr, exprArgs, err := CollisionHandledFinalExpr(v.ctx, v.startNs, v.endNs, &fieldKey, v.fieldMapper, v.conditionBuilder, v.fieldKeys, dataType, v.jsonKeyToKey, bodyJSONEnabled)
|
||||
expr, exprArgs, err := CollisionHandledFinalExpr(v.ctx, v.orgID, v.startNs, v.endNs, &fieldKey, v.fieldMapper, v.conditionBuilder, v.fieldKeys, dataType, v.jsonKeyToKey, bodyJSONEnabled)
|
||||
if err != nil {
|
||||
return errors.WrapInvalidInputf(err, errors.CodeInvalidInput, "failed to get table field name for %q", origVal)
|
||||
}
|
||||
@@ -265,7 +272,7 @@ func (v *exprVisitor) VisitFunctionExpr(fn *chparser.FunctionExpr) error {
|
||||
for i, arg := range args {
|
||||
orig := arg.String()
|
||||
fieldKey := telemetrytypes.GetFieldKeyFromKeyText(orig)
|
||||
expr, exprArgs, err := CollisionHandledFinalExpr(v.ctx, v.startNs, v.endNs, &fieldKey, v.fieldMapper, v.conditionBuilder, v.fieldKeys, dataType, v.jsonKeyToKey, bodyJSONEnabled)
|
||||
expr, exprArgs, err := CollisionHandledFinalExpr(v.ctx, v.orgID, v.startNs, v.endNs, &fieldKey, v.fieldMapper, v.conditionBuilder, v.fieldKeys, dataType, v.jsonKeyToKey, bodyJSONEnabled)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -13,12 +13,14 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/huandu/go-sqlbuilder"
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
func CollisionHandledFinalExpr(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
startNs uint64,
|
||||
endNs uint64,
|
||||
field *telemetrytypes.TelemetryFieldKey,
|
||||
@@ -47,7 +49,7 @@ func CollisionHandledFinalExpr(
|
||||
|
||||
addCondition := func(key *telemetrytypes.TelemetryFieldKey) error {
|
||||
sb := sqlbuilder.NewSelectBuilder()
|
||||
conds, _, err := cb.ConditionFor(ctx, startNs, endNs, key, []*telemetrytypes.TelemetryFieldKey{key}, qbtypes.FilterOperatorExists, nil, sb)
|
||||
conds, _, err := cb.ConditionFor(ctx, orgID, startNs, endNs, key, []*telemetrytypes.TelemetryFieldKey{key}, qbtypes.FilterOperatorExists, nil, sb)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -60,7 +62,7 @@ func CollisionHandledFinalExpr(
|
||||
return nil
|
||||
}
|
||||
|
||||
fieldExpression, fieldForErr := fm.FieldFor(ctx, startNs, endNs, field)
|
||||
fieldExpression, fieldForErr := fm.FieldFor(ctx, orgID, startNs, endNs, field)
|
||||
if errors.Is(fieldForErr, qbtypes.ErrColumnNotFound) {
|
||||
// the key didn't have the right context to be added to the query
|
||||
// we try to use the context we know of
|
||||
@@ -76,23 +78,23 @@ func CollisionHandledFinalExpr(
|
||||
}
|
||||
|
||||
if len(keysForField) == 0 {
|
||||
// - the context is not provided
|
||||
// - there are not keys for the field
|
||||
// - it is not a static field
|
||||
// - the next best thing to do is see if there is a typo
|
||||
// and suggest a correction
|
||||
// No metadata match: let the field mapper synthesize type-variant keys.
|
||||
// keys were already checked above, so pass nil here.
|
||||
keysForField = fm.CandidateKeys(ctx, orgID, field, nil, nil)
|
||||
}
|
||||
if len(keysForField) == 0 {
|
||||
// The mapper can't synthesize (e.g. metrics): fall back to the typo suggestion.
|
||||
wrappedErr := errors.WithSuggestiveAdditionalf(fieldForErr, errors.NewSuggestionsOnLevenshteinDistance(field.Name, errors.NounKeys, maps.Keys(keys)), "field `%s` not found", field.Name)
|
||||
return "", nil, wrappedErr
|
||||
} else {
|
||||
for _, key := range keysForField {
|
||||
err := addCondition(key)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
fieldExpression, _ = fm.FieldFor(ctx, startNs, endNs, key)
|
||||
fieldExpression, _ = DataTypeCollisionHandledFieldName(key, dummyValue, fieldExpression, qbtypes.FilterOperatorUnknown)
|
||||
stmts = append(stmts, fieldExpression)
|
||||
}
|
||||
for _, key := range keysForField {
|
||||
err := addCondition(key)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
fieldExpression, _ = fm.FieldFor(ctx, orgID, startNs, endNs, key)
|
||||
fieldExpression, _ = DataTypeCollisionHandledFieldName(key, dummyValue, fieldExpression, qbtypes.FilterOperatorUnknown)
|
||||
stmts = append(stmts, fieldExpression)
|
||||
}
|
||||
} else {
|
||||
err := addCondition(field)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package querybuilder
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
@@ -68,6 +69,100 @@ func NewKeyNotFoundError(name string) error {
|
||||
return errors.NewInvalidInputf(errors.CodeInvalidInput, "key `%s` not found", name).WithUrl(KeyNotFoundDocURL)
|
||||
}
|
||||
|
||||
// NewKeyNotFoundWarning is the warning surfaced when a referenced key is absent from
|
||||
// metadata and the query falls back to synthesized keys.
|
||||
func NewKeyNotFoundWarning(name string) string {
|
||||
return fmt.Sprintf("key `%s` not found in metadata; querying the underlying data directly. If this is unexpected, check the key name for typos.", name)
|
||||
}
|
||||
|
||||
// SynthesizeKeys builds the field keys to query when metadata has no match: a qualified
|
||||
// key is honored as-is; a bare key defaults to attribute context with the data type
|
||||
// inferred from the operand, or fanned out across string/number/bool without one.
|
||||
func SynthesizeKeys(field *telemetrytypes.TelemetryFieldKey, value any) []*telemetrytypes.TelemetryFieldKey {
|
||||
base := *field
|
||||
if base.FieldContext == telemetrytypes.FieldContextUnspecified {
|
||||
base.FieldContext = telemetrytypes.FieldContextAttribute
|
||||
}
|
||||
// Resource values are strings; pin the type so operand coercion applies.
|
||||
if base.FieldContext == telemetrytypes.FieldContextResource &&
|
||||
base.FieldDataType == telemetrytypes.FieldDataTypeUnspecified {
|
||||
base.FieldDataType = telemetrytypes.FieldDataTypeString
|
||||
}
|
||||
|
||||
// A set data type needs only one synthesized key.
|
||||
if base.FieldDataType != telemetrytypes.FieldDataTypeUnspecified {
|
||||
clone := base
|
||||
return []*telemetrytypes.TelemetryFieldKey{&clone}
|
||||
}
|
||||
|
||||
dataTypes := inferDataTypesFromOperand(value)
|
||||
keys := make([]*telemetrytypes.TelemetryFieldKey, 0, len(dataTypes))
|
||||
for _, dt := range dataTypes {
|
||||
clone := base
|
||||
clone.FieldDataType = dt
|
||||
keys = append(keys, &clone)
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
// allVariantDataTypes is the fanout used when no operand pins the type (exists / group by).
|
||||
func allVariantDataTypes() []telemetrytypes.FieldDataType {
|
||||
return []telemetrytypes.FieldDataType{
|
||||
telemetrytypes.FieldDataTypeString,
|
||||
telemetrytypes.FieldDataTypeNumber,
|
||||
telemetrytypes.FieldDataTypeBool,
|
||||
}
|
||||
}
|
||||
|
||||
// inferDataTypesFromOperand maps an operand value to the data type(s) to query. With no
|
||||
// operand or an unrecognized type it fans out across all variants.
|
||||
func inferDataTypesFromOperand(value any) []telemetrytypes.FieldDataType {
|
||||
switch v := value.(type) {
|
||||
case nil:
|
||||
return allVariantDataTypes()
|
||||
case string:
|
||||
return []telemetrytypes.FieldDataType{telemetrytypes.FieldDataTypeString}
|
||||
case bool:
|
||||
return []telemetrytypes.FieldDataType{telemetrytypes.FieldDataTypeBool}
|
||||
case float32, float64, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, json.Number:
|
||||
return []telemetrytypes.FieldDataType{telemetrytypes.FieldDataTypeNumber}
|
||||
case []any:
|
||||
return inferDataTypesFromList(v)
|
||||
default:
|
||||
return allVariantDataTypes()
|
||||
}
|
||||
}
|
||||
|
||||
// inferDataTypesFromList derives the distinct data types present in an `in` list, kept
|
||||
// in string/number/bool order; an empty or all-unknown list fans out.
|
||||
func inferDataTypesFromList(values []any) []telemetrytypes.FieldDataType {
|
||||
var hasString, hasNumber, hasBool bool
|
||||
for _, v := range values {
|
||||
switch v.(type) {
|
||||
case string:
|
||||
hasString = true
|
||||
case bool:
|
||||
hasBool = true
|
||||
case float32, float64, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, json.Number:
|
||||
hasNumber = true
|
||||
}
|
||||
}
|
||||
var out []telemetrytypes.FieldDataType
|
||||
if hasString {
|
||||
out = append(out, telemetrytypes.FieldDataTypeString)
|
||||
}
|
||||
if hasNumber {
|
||||
out = append(out, telemetrytypes.FieldDataTypeNumber)
|
||||
}
|
||||
if hasBool {
|
||||
out = append(out, telemetrytypes.FieldDataTypeBool)
|
||||
}
|
||||
if len(out) == 0 {
|
||||
return allVariantDataTypes()
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// NewFunctionUnsupportedError returns the error for a has/hasAny/hasAll/hasToken operator
|
||||
// on a builder that doesn't support it (logs body only), or nil for other operators.
|
||||
func NewFunctionUnsupportedError(operator qbtypes.FilterOperator) error {
|
||||
|
||||
89
pkg/querybuilder/key_resolution_test.go
Normal file
89
pkg/querybuilder/key_resolution_test.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package querybuilder
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSynthesizeKeys(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
field telemetrytypes.TelemetryFieldKey
|
||||
value any
|
||||
wantContexts []telemetrytypes.FieldContext
|
||||
wantDataTypes []telemetrytypes.FieldDataType
|
||||
}{
|
||||
{
|
||||
name: "bare key with string operand infers string",
|
||||
field: telemetrytypes.TelemetryFieldKey{Name: "error.type"},
|
||||
value: "timeout",
|
||||
wantContexts: []telemetrytypes.FieldContext{telemetrytypes.FieldContextAttribute},
|
||||
wantDataTypes: []telemetrytypes.FieldDataType{telemetrytypes.FieldDataTypeString},
|
||||
},
|
||||
{
|
||||
name: "bare key with number operand infers number",
|
||||
field: telemetrytypes.TelemetryFieldKey{Name: "http.status"},
|
||||
value: float64(500),
|
||||
wantContexts: []telemetrytypes.FieldContext{telemetrytypes.FieldContextAttribute},
|
||||
wantDataTypes: []telemetrytypes.FieldDataType{telemetrytypes.FieldDataTypeNumber},
|
||||
},
|
||||
{
|
||||
name: "bare key with bool operand infers bool",
|
||||
field: telemetrytypes.TelemetryFieldKey{Name: "sampled"},
|
||||
value: true,
|
||||
wantContexts: []telemetrytypes.FieldContext{telemetrytypes.FieldContextAttribute},
|
||||
wantDataTypes: []telemetrytypes.FieldDataType{telemetrytypes.FieldDataTypeBool},
|
||||
},
|
||||
{
|
||||
name: "bare key with no operand fans out across variants",
|
||||
field: telemetrytypes.TelemetryFieldKey{Name: "exception.type"},
|
||||
value: nil,
|
||||
wantContexts: []telemetrytypes.FieldContext{telemetrytypes.FieldContextAttribute, telemetrytypes.FieldContextAttribute, telemetrytypes.FieldContextAttribute},
|
||||
wantDataTypes: []telemetrytypes.FieldDataType{telemetrytypes.FieldDataTypeString, telemetrytypes.FieldDataTypeNumber, telemetrytypes.FieldDataTypeBool},
|
||||
},
|
||||
{
|
||||
name: "qualified data type honored as-is",
|
||||
field: telemetrytypes.TelemetryFieldKey{Name: "custom.key", FieldDataType: telemetrytypes.FieldDataTypeString},
|
||||
value: nil,
|
||||
wantContexts: []telemetrytypes.FieldContext{telemetrytypes.FieldContextAttribute},
|
||||
wantDataTypes: []telemetrytypes.FieldDataType{telemetrytypes.FieldDataTypeString},
|
||||
},
|
||||
{
|
||||
name: "qualified resource context honored as single string key",
|
||||
field: telemetrytypes.TelemetryFieldKey{Name: "k8s.cluster.name", FieldContext: telemetrytypes.FieldContextResource},
|
||||
value: nil,
|
||||
wantContexts: []telemetrytypes.FieldContext{telemetrytypes.FieldContextResource},
|
||||
wantDataTypes: []telemetrytypes.FieldDataType{telemetrytypes.FieldDataTypeString},
|
||||
},
|
||||
{
|
||||
name: "in list with homogeneous strings infers single string variant",
|
||||
field: telemetrytypes.TelemetryFieldKey{Name: "error.type"},
|
||||
value: []any{"a", "b"},
|
||||
wantContexts: []telemetrytypes.FieldContext{telemetrytypes.FieldContextAttribute},
|
||||
wantDataTypes: []telemetrytypes.FieldDataType{telemetrytypes.FieldDataTypeString},
|
||||
},
|
||||
{
|
||||
name: "in list with mixed types fans out to present variants",
|
||||
field: telemetrytypes.TelemetryFieldKey{Name: "error.type"},
|
||||
value: []any{"a", float64(1)},
|
||||
wantContexts: []telemetrytypes.FieldContext{telemetrytypes.FieldContextAttribute, telemetrytypes.FieldContextAttribute},
|
||||
wantDataTypes: []telemetrytypes.FieldDataType{telemetrytypes.FieldDataTypeString, telemetrytypes.FieldDataTypeNumber},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
field := tt.field
|
||||
keys := SynthesizeKeys(&field, tt.value)
|
||||
require.Len(t, keys, len(tt.wantDataTypes))
|
||||
for i, k := range keys {
|
||||
assert.Equal(t, tt.field.Name, k.Name, "name preserved")
|
||||
assert.Equal(t, tt.wantContexts[i], k.FieldContext)
|
||||
assert.Equal(t, tt.wantDataTypes[i], k.FieldDataType)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
grammar "github.com/SigNoz/signoz/pkg/parser/filterquery/grammar"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/antlr4-go/antlr/v4"
|
||||
|
||||
sqlbuilder "github.com/huandu/go-sqlbuilder"
|
||||
@@ -25,6 +26,7 @@ const stringMatchingOperatorDocURL = "https://signoz.io/docs/userguide/operators
|
||||
// to convert the parsed filter expressions into ClickHouse WHERE clause.
|
||||
type filterExpressionVisitor struct {
|
||||
context context.Context
|
||||
orgID valuer.UUID
|
||||
fieldMapper qbtypes.FieldMapper
|
||||
conditionBuilder qbtypes.ConditionBuilder
|
||||
warnings []string
|
||||
@@ -45,6 +47,7 @@ type filterExpressionVisitor struct {
|
||||
|
||||
type FilterExprVisitorOpts struct {
|
||||
Context context.Context
|
||||
OrgID valuer.UUID
|
||||
Logger *slog.Logger
|
||||
FieldMapper qbtypes.FieldMapper
|
||||
ConditionBuilder qbtypes.ConditionBuilder
|
||||
@@ -62,6 +65,7 @@ type FilterExprVisitorOpts struct {
|
||||
func newFilterExpressionVisitor(opts FilterExprVisitorOpts) *filterExpressionVisitor {
|
||||
return &filterExpressionVisitor{
|
||||
context: opts.Context,
|
||||
orgID: opts.OrgID,
|
||||
fieldMapper: opts.FieldMapper,
|
||||
conditionBuilder: opts.ConditionBuilder,
|
||||
fieldKeys: opts.FieldKeys,
|
||||
@@ -365,12 +369,11 @@ func (v *filterExpressionVisitor) VisitPrimary(ctx *grammar.PrimaryContext) any
|
||||
// VisitComparison handles all comparison operators.
|
||||
func (v *filterExpressionVisitor) VisitComparison(ctx *grammar.ComparisonContext) any {
|
||||
key := v.Visit(ctx.Key()).(*telemetrytypes.TelemetryFieldKey)
|
||||
matching := matchingFieldKeys(key, v.fieldKeys)
|
||||
matching := MatchingFieldKeys(key, v.fieldKeys)
|
||||
|
||||
// Skip resource filtering on the main table when a sub-query covers it. Resolve
|
||||
// ambiguity first (resource+attribute defaults to resource), then drop resource
|
||||
// matches; skip the term if nothing remains. Empty matches flow to the builder.
|
||||
if v.skipResourceFilter && len(matching) > 0 {
|
||||
// Skip resource filtering on the main table when a sub-query covers it; a resolving
|
||||
// condition builder applies this itself in ConditionForKeys.
|
||||
if _, resolvesKeys := v.conditionBuilder.(qbtypes.ResolvingConditionBuilder); v.skipResourceFilter && len(matching) > 0 && !resolvesKeys {
|
||||
resolved, warning := ResolveKeys(key, matching)
|
||||
// emit the ambiguity warning even when the term is skipped below
|
||||
v.addWarnings([]string{warning}, len(matching) > 1)
|
||||
@@ -731,7 +734,7 @@ func (v *filterExpressionVisitor) VisitFunctionCall(ctx *grammar.FunctionCallCon
|
||||
|
||||
value := params[1:]
|
||||
|
||||
conds, ok := v.buildConditions(key, matchingFieldKeys(key, v.fieldKeys), operator, value)
|
||||
conds, ok := v.buildConditions(key, MatchingFieldKeys(key, v.fieldKeys), operator, value)
|
||||
if !ok {
|
||||
return ErrorConditionLiteral
|
||||
}
|
||||
@@ -811,10 +814,20 @@ func (v *filterExpressionVisitor) VisitKey(ctx *grammar.KeyContext) any {
|
||||
}
|
||||
|
||||
// buildConditions invokes the condition builder for a filter term, folding its
|
||||
// warnings/errors into visitor state. It returns the conditions and false if an error
|
||||
// was recorded.
|
||||
// warnings/errors into visitor state; returns false if an error was recorded.
|
||||
func (v *filterExpressionVisitor) buildConditions(key *telemetrytypes.TelemetryFieldKey, matching []*telemetrytypes.TelemetryFieldKey, op qbtypes.FilterOperator, value any) ([]string, bool) {
|
||||
conds, warns, err := v.conditionBuilder.ConditionFor(v.context, v.startNs, v.endNs, key, matching, op, value, v.builder)
|
||||
var (
|
||||
conds []string
|
||||
warns []string
|
||||
err error
|
||||
)
|
||||
// A resolving condition builder owns key resolution, so hand it the raw key + full map;
|
||||
// other signals use the pre-matched ConditionFor path.
|
||||
if rcb, ok := v.conditionBuilder.(qbtypes.ResolvingConditionBuilder); ok {
|
||||
conds, warns, err = rcb.ConditionForKeys(v.context, v.orgID, v.startNs, v.endNs, key, v.fieldKeys, qbtypes.ConditionBuilderOptions{SkipResourceFilter: v.skipResourceFilter}, op, value, v.builder)
|
||||
} else {
|
||||
conds, warns, err = v.conditionBuilder.ConditionFor(v.context, v.orgID, v.startNs, v.endNs, key, matching, op, value, v.builder)
|
||||
}
|
||||
if err != nil {
|
||||
_, _, _, _, errURL, _ := errors.Unwrapb(err)
|
||||
assignIfEmpty(&v.mainErrorURL, errURL)
|
||||
@@ -870,10 +883,9 @@ func assignIfEmpty(s *string, value string) {
|
||||
}
|
||||
}
|
||||
|
||||
// matchingFieldKeys returns the field keys from the provided map that match the given
|
||||
// key, honoring any context/data type the user specified. It also resolves the case
|
||||
// where GetFieldKeyFromKeyText split a context off a name that legitimately contained it.
|
||||
func matchingFieldKeys(field *telemetrytypes.TelemetryFieldKey, fieldKeys map[string][]*telemetrytypes.TelemetryFieldKey) []*telemetrytypes.TelemetryFieldKey {
|
||||
// MatchingFieldKeys returns the field keys from the map that match the given key,
|
||||
// honoring any context/data type the user specified.
|
||||
func MatchingFieldKeys(field *telemetrytypes.TelemetryFieldKey, fieldKeys map[string][]*telemetrytypes.TelemetryFieldKey) []*telemetrytypes.TelemetryFieldKey {
|
||||
fieldKeysForName := []*telemetrytypes.TelemetryFieldKey{}
|
||||
|
||||
// match by name; keep items whose context and data type match (unspecified matches any)
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
grammar "github.com/SigNoz/signoz/pkg/parser/filterquery/grammar"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/antlr4-go/antlr/v4"
|
||||
sqlbuilder "github.com/huandu/go-sqlbuilder"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -588,7 +589,7 @@ func TestVisitKey(t *testing.T) {
|
||||
// VisitKey only parses; the condition builder matches, resolves ambiguity
|
||||
// and decides not-found handling. Replay that here against the generic
|
||||
// builder behavior (error unless the key is ignored).
|
||||
matching := matchingFieldKeys(key, tt.fieldKeys)
|
||||
matching := MatchingFieldKeys(key, tt.fieldKeys)
|
||||
keys, warning := ResolveKeys(key, matching)
|
||||
|
||||
var gotErrors []string
|
||||
@@ -746,6 +747,7 @@ type resourceConditionBuilder struct{}
|
||||
|
||||
func (b *resourceConditionBuilder) ConditionFor(
|
||||
_ context.Context,
|
||||
_ valuer.UUID,
|
||||
_ uint64,
|
||||
_ uint64,
|
||||
key *telemetrytypes.TelemetryFieldKey,
|
||||
@@ -781,6 +783,7 @@ type conditionBuilder struct{}
|
||||
|
||||
func (b *conditionBuilder) ConditionFor(
|
||||
_ context.Context,
|
||||
_ valuer.UUID,
|
||||
_ uint64,
|
||||
_ uint64,
|
||||
key *telemetrytypes.TelemetryFieldKey,
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/querybuilder"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/huandu/go-sqlbuilder"
|
||||
)
|
||||
|
||||
@@ -22,13 +23,14 @@ func NewConditionBuilder(fm qbtypes.FieldMapper) *conditionBuilder {
|
||||
|
||||
func (c *conditionBuilder) conditionFor(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
startNs, endNs uint64,
|
||||
key *telemetrytypes.TelemetryFieldKey,
|
||||
operator qbtypes.FilterOperator,
|
||||
value any,
|
||||
sb *sqlbuilder.SelectBuilder,
|
||||
) (string, error) {
|
||||
columns, err := c.fm.ColumnFor(ctx, startNs, endNs, key)
|
||||
columns, err := c.fm.ColumnFor(ctx, orgID, startNs, endNs, key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -37,7 +39,7 @@ func (c *conditionBuilder) conditionFor(
|
||||
value = querybuilder.FormatValueForContains(value)
|
||||
}
|
||||
|
||||
fieldExpression, err := c.fm.FieldFor(ctx, startNs, endNs, key)
|
||||
fieldExpression, err := c.fm.FieldFor(ctx, orgID, startNs, endNs, key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -172,6 +174,7 @@ func (c *conditionBuilder) conditionFor(
|
||||
|
||||
func (c *conditionBuilder) ConditionFor(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
startNs uint64,
|
||||
endNs uint64,
|
||||
key *telemetrytypes.TelemetryFieldKey,
|
||||
@@ -197,7 +200,7 @@ func (c *conditionBuilder) ConditionFor(
|
||||
|
||||
conds := make([]string, 0, len(keys))
|
||||
for _, k := range keys {
|
||||
cond, err := c.conditionForKey(ctx, startNs, endNs, k, operator, value, sb)
|
||||
cond, err := c.conditionForKey(ctx, orgID, startNs, endNs, k, operator, value, sb)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -208,6 +211,7 @@ func (c *conditionBuilder) ConditionFor(
|
||||
|
||||
func (c *conditionBuilder) conditionForKey(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
startNs uint64,
|
||||
endNs uint64,
|
||||
key *telemetrytypes.TelemetryFieldKey,
|
||||
@@ -215,7 +219,7 @@ func (c *conditionBuilder) conditionForKey(
|
||||
value any,
|
||||
sb *sqlbuilder.SelectBuilder,
|
||||
) (string, error) {
|
||||
condition, err := c.conditionFor(ctx, startNs, endNs, key, operator, value, sb)
|
||||
condition, err := c.conditionFor(ctx, orgID, startNs, endNs, key, operator, value, sb)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -225,7 +229,7 @@ func (c *conditionBuilder) conditionForKey(
|
||||
}
|
||||
|
||||
if operator.AddDefaultExistsFilter() {
|
||||
existsCondition, err := c.conditionFor(ctx, startNs, endNs, key, qbtypes.FilterOperatorExists, nil, sb)
|
||||
existsCondition, err := c.conditionFor(ctx, orgID, startNs, endNs, key, qbtypes.FilterOperatorExists, nil, sb)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/huandu/go-sqlbuilder"
|
||||
|
||||
"golang.org/x/exp/maps"
|
||||
@@ -51,7 +52,7 @@ func (m *fieldMapper) getColumn(_ context.Context, key *telemetrytypes.Telemetry
|
||||
return nil, qbtypes.ErrColumnNotFound
|
||||
}
|
||||
|
||||
func (m *fieldMapper) FieldFor(ctx context.Context, _, _ uint64, key *telemetrytypes.TelemetryFieldKey) (string, error) {
|
||||
func (m *fieldMapper) FieldFor(ctx context.Context, _ valuer.UUID, _, _ uint64, key *telemetrytypes.TelemetryFieldKey) (string, error) {
|
||||
columns, err := m.getColumn(ctx, key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -91,31 +92,38 @@ func (m *fieldMapper) FieldFor(ctx context.Context, _, _ uint64, key *telemetryt
|
||||
return column.Name, nil
|
||||
}
|
||||
|
||||
func (m *fieldMapper) ColumnFor(ctx context.Context, _, _ uint64, key *telemetrytypes.TelemetryFieldKey) ([]*schema.Column, error) {
|
||||
func (m *fieldMapper) ColumnFor(ctx context.Context, _ valuer.UUID, _, _ uint64, key *telemetrytypes.TelemetryFieldKey) ([]*schema.Column, error) {
|
||||
return m.getColumn(ctx, key)
|
||||
}
|
||||
|
||||
func (m *fieldMapper) ColumnExpressionFor(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
tsStart, tsEnd uint64,
|
||||
field *telemetrytypes.TelemetryFieldKey,
|
||||
keys map[string][]*telemetrytypes.TelemetryFieldKey,
|
||||
) (string, error) {
|
||||
fieldExpression, err := m.FieldFor(ctx, tsStart, tsEnd, field)
|
||||
fieldExpression, err := m.FieldFor(ctx, orgID, tsStart, tsEnd, field)
|
||||
if errors.Is(err, qbtypes.ErrColumnNotFound) {
|
||||
keysForField := keys[field.Name]
|
||||
if len(keysForField) == 0 {
|
||||
if _, ok := auditLogColumns[field.Name]; ok {
|
||||
field.FieldContext = telemetrytypes.FieldContextLog
|
||||
fieldExpression, _ = m.FieldFor(ctx, tsStart, tsEnd, field)
|
||||
fieldExpression, _ = m.FieldFor(ctx, orgID, tsStart, tsEnd, field)
|
||||
} else {
|
||||
wrappedErr := errors.Wrapf(err, errors.TypeInvalidInput, errors.CodeInvalidInput, "field `%s` not found", field.Name).WithSuggestions(errors.NewSuggestionsOnLevenshteinDistance(field.Name, errors.NounKeys, maps.Keys(keys))...)
|
||||
return "", wrappedErr
|
||||
}
|
||||
} else {
|
||||
fieldExpression, _ = m.FieldFor(ctx, tsStart, tsEnd, keysForField[0])
|
||||
fieldExpression, _ = m.FieldFor(ctx, orgID, tsStart, tsEnd, keysForField[0])
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s AS `%s`", sqlbuilder.Escape(fieldExpression), field.Name), nil
|
||||
}
|
||||
|
||||
// CandidateKeys returns nil: audit has no synthesize-on-unknown-key fallback, so an
|
||||
// unknown key stays unresolved and the caller errors.
|
||||
func (m *fieldMapper) CandidateKeys(_ context.Context, _ valuer.UUID, _ *telemetrytypes.TelemetryFieldKey, _ any, _ map[string][]*telemetrytypes.TelemetryFieldKey) []*telemetrytypes.TelemetryFieldKey {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/telemetryresourcefilter"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/huandu/go-sqlbuilder"
|
||||
)
|
||||
|
||||
@@ -66,6 +67,7 @@ func NewAuditQueryStatementBuilder(
|
||||
|
||||
func (b *auditQueryStatementBuilder) Build(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
start uint64,
|
||||
end uint64,
|
||||
requestType qbtypes.RequestType,
|
||||
@@ -76,7 +78,7 @@ func (b *auditQueryStatementBuilder) Build(
|
||||
end = querybuilder.ToNanoSecs(end)
|
||||
|
||||
keySelectors := getKeySelectors(query)
|
||||
keys, _, err := b.metadataStore.GetKeysMulti(ctx, keySelectors)
|
||||
keys, _, err := b.metadataStore.GetKeysMulti(ctx, orgID, keySelectors)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -88,11 +90,11 @@ func (b *auditQueryStatementBuilder) Build(
|
||||
var stmt *qbtypes.Statement
|
||||
switch requestType {
|
||||
case qbtypes.RequestTypeRaw, qbtypes.RequestTypeRawStream:
|
||||
stmt, err = b.buildListQuery(ctx, q, query, start, end, keys, variables)
|
||||
stmt, err = b.buildListQuery(ctx, orgID, q, query, start, end, keys, variables)
|
||||
case qbtypes.RequestTypeTimeSeries:
|
||||
stmt, err = b.buildTimeSeriesQuery(ctx, q, query, start, end, keys, variables)
|
||||
stmt, err = b.buildTimeSeriesQuery(ctx, orgID, q, query, start, end, keys, variables)
|
||||
case qbtypes.RequestTypeScalar:
|
||||
stmt, err = b.buildScalarQuery(ctx, q, query, start, end, keys, false, variables)
|
||||
stmt, err = b.buildScalarQuery(ctx, orgID, q, query, start, end, keys, false, variables)
|
||||
default:
|
||||
return nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "unsupported request type: %s", requestType)
|
||||
}
|
||||
@@ -201,6 +203,7 @@ func (b *auditQueryStatementBuilder) adjustKey(key *telemetrytypes.TelemetryFiel
|
||||
|
||||
func (b *auditQueryStatementBuilder) buildListQuery(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
sb *sqlbuilder.SelectBuilder,
|
||||
query qbtypes.QueryBuilderQuery[qbtypes.LogAggregation],
|
||||
start, end uint64,
|
||||
@@ -212,7 +215,7 @@ func (b *auditQueryStatementBuilder) buildListQuery(
|
||||
cteArgs [][]any
|
||||
)
|
||||
|
||||
if frag, args, err := b.maybeAttachResourceFilter(ctx, sb, query, start, end, variables); err != nil {
|
||||
if frag, args, err := b.maybeAttachResourceFilter(ctx, orgID, sb, query, start, end, variables); err != nil {
|
||||
return nil, err
|
||||
} else if frag != "" {
|
||||
cteFragments = append(cteFragments, frag)
|
||||
@@ -242,7 +245,7 @@ func (b *auditQueryStatementBuilder) buildListQuery(
|
||||
continue
|
||||
}
|
||||
|
||||
colExpr, err := b.fm.ColumnExpressionFor(ctx, start, end, &query.SelectFields[index], keys)
|
||||
colExpr, err := b.fm.ColumnExpressionFor(ctx, orgID, start, end, &query.SelectFields[index], keys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -252,13 +255,13 @@ func (b *auditQueryStatementBuilder) buildListQuery(
|
||||
|
||||
sb.From(fmt.Sprintf("%s.%s", DBName, AuditLogsTableName))
|
||||
|
||||
preparedWhereClause, err := b.addFilterCondition(ctx, sb, start, end, query, keys, variables)
|
||||
preparedWhereClause, err := b.addFilterCondition(ctx, orgID, sb, start, end, query, keys, variables)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, orderBy := range query.Order {
|
||||
colExpr, err := b.fm.ColumnExpressionFor(ctx, start, end, &orderBy.Key.TelemetryFieldKey, keys)
|
||||
colExpr, err := b.fm.ColumnExpressionFor(ctx, orgID, start, end, &orderBy.Key.TelemetryFieldKey, keys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -292,6 +295,7 @@ func (b *auditQueryStatementBuilder) buildListQuery(
|
||||
|
||||
func (b *auditQueryStatementBuilder) buildTimeSeriesQuery(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
sb *sqlbuilder.SelectBuilder,
|
||||
query qbtypes.QueryBuilderQuery[qbtypes.LogAggregation],
|
||||
start, end uint64,
|
||||
@@ -303,7 +307,7 @@ func (b *auditQueryStatementBuilder) buildTimeSeriesQuery(
|
||||
cteArgs [][]any
|
||||
)
|
||||
|
||||
if frag, args, err := b.maybeAttachResourceFilter(ctx, sb, query, start, end, variables); err != nil {
|
||||
if frag, args, err := b.maybeAttachResourceFilter(ctx, orgID, sb, query, start, end, variables); err != nil {
|
||||
return nil, err
|
||||
} else if frag != "" {
|
||||
cteFragments = append(cteFragments, frag)
|
||||
@@ -319,7 +323,7 @@ func (b *auditQueryStatementBuilder) buildTimeSeriesQuery(
|
||||
|
||||
fieldNames := make([]string, 0, len(query.GroupBy))
|
||||
for _, gb := range query.GroupBy {
|
||||
expr, args, err := querybuilder.CollisionHandledFinalExpr(ctx, start, end, &gb.TelemetryFieldKey, b.fm, b.cb, keys, telemetrytypes.FieldDataTypeString, b.jsonKeyToKey, false)
|
||||
expr, args, err := querybuilder.CollisionHandledFinalExpr(ctx, orgID, start, end, &gb.TelemetryFieldKey, b.fm, b.cb, keys, telemetrytypes.FieldDataTypeString, b.jsonKeyToKey, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -332,7 +336,7 @@ func (b *auditQueryStatementBuilder) buildTimeSeriesQuery(
|
||||
|
||||
allAggChArgs := make([]any, 0)
|
||||
for i, agg := range query.Aggregations {
|
||||
rewritten, chArgs, err := b.aggExprRewriter.Rewrite(ctx, start, end, agg.Expression, uint64(query.StepInterval.Seconds()), keys)
|
||||
rewritten, chArgs, err := b.aggExprRewriter.Rewrite(ctx, orgID, start, end, agg.Expression, uint64(query.StepInterval.Seconds()), keys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -342,7 +346,7 @@ func (b *auditQueryStatementBuilder) buildTimeSeriesQuery(
|
||||
|
||||
sb.From(fmt.Sprintf("%s.%s", DBName, AuditLogsTableName))
|
||||
|
||||
preparedWhereClause, err := b.addFilterCondition(ctx, sb, start, end, query, keys, variables)
|
||||
preparedWhereClause, err := b.addFilterCondition(ctx, orgID, sb, start, end, query, keys, variables)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -352,7 +356,7 @@ func (b *auditQueryStatementBuilder) buildTimeSeriesQuery(
|
||||
|
||||
if query.Limit > 0 && len(query.GroupBy) > 0 {
|
||||
cteSB := sqlbuilder.NewSelectBuilder()
|
||||
cteStmt, err := b.buildScalarQuery(ctx, cteSB, query, start, end, keys, true, variables)
|
||||
cteStmt, err := b.buildScalarQuery(ctx, orgID, cteSB, query, start, end, keys, true, variables)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -430,6 +434,7 @@ func (b *auditQueryStatementBuilder) buildTimeSeriesQuery(
|
||||
|
||||
func (b *auditQueryStatementBuilder) buildScalarQuery(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
sb *sqlbuilder.SelectBuilder,
|
||||
query qbtypes.QueryBuilderQuery[qbtypes.LogAggregation],
|
||||
start, end uint64,
|
||||
@@ -442,7 +447,7 @@ func (b *auditQueryStatementBuilder) buildScalarQuery(
|
||||
cteArgs [][]any
|
||||
)
|
||||
|
||||
if frag, args, err := b.maybeAttachResourceFilter(ctx, sb, query, start, end, variables); err != nil {
|
||||
if frag, args, err := b.maybeAttachResourceFilter(ctx, orgID, sb, query, start, end, variables); err != nil {
|
||||
return nil, err
|
||||
} else if frag != "" && !skipResourceCTE {
|
||||
cteFragments = append(cteFragments, frag)
|
||||
@@ -454,7 +459,7 @@ func (b *auditQueryStatementBuilder) buildScalarQuery(
|
||||
var allGroupByArgs []any
|
||||
|
||||
for _, gb := range query.GroupBy {
|
||||
expr, args, err := querybuilder.CollisionHandledFinalExpr(ctx, start, end, &gb.TelemetryFieldKey, b.fm, b.cb, keys, telemetrytypes.FieldDataTypeString, b.jsonKeyToKey, false)
|
||||
expr, args, err := querybuilder.CollisionHandledFinalExpr(ctx, orgID, start, end, &gb.TelemetryFieldKey, b.fm, b.cb, keys, telemetrytypes.FieldDataTypeString, b.jsonKeyToKey, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -469,7 +474,7 @@ func (b *auditQueryStatementBuilder) buildScalarQuery(
|
||||
if len(query.Aggregations) > 0 {
|
||||
for idx := range query.Aggregations {
|
||||
aggExpr := query.Aggregations[idx]
|
||||
rewritten, chArgs, err := b.aggExprRewriter.Rewrite(ctx, start, end, aggExpr.Expression, rateInterval, keys)
|
||||
rewritten, chArgs, err := b.aggExprRewriter.Rewrite(ctx, orgID, start, end, aggExpr.Expression, rateInterval, keys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -480,7 +485,7 @@ func (b *auditQueryStatementBuilder) buildScalarQuery(
|
||||
|
||||
sb.From(fmt.Sprintf("%s.%s", DBName, AuditLogsTableName))
|
||||
|
||||
preparedWhereClause, err := b.addFilterCondition(ctx, sb, start, end, query, keys, variables)
|
||||
preparedWhereClause, err := b.addFilterCondition(ctx, orgID, sb, start, end, query, keys, variables)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -532,6 +537,7 @@ func (b *auditQueryStatementBuilder) buildScalarQuery(
|
||||
|
||||
func (b *auditQueryStatementBuilder) addFilterCondition(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
sb *sqlbuilder.SelectBuilder,
|
||||
start, end uint64,
|
||||
query qbtypes.QueryBuilderQuery[qbtypes.LogAggregation],
|
||||
@@ -544,6 +550,7 @@ func (b *auditQueryStatementBuilder) addFilterCondition(
|
||||
if query.Filter != nil && query.Filter.Expression != "" {
|
||||
preparedWhereClause, err = querybuilder.PrepareWhereClause(query.Filter.Expression, querybuilder.FilterExprVisitorOpts{
|
||||
Context: ctx,
|
||||
OrgID: orgID,
|
||||
Logger: b.logger,
|
||||
FieldMapper: b.fm,
|
||||
ConditionBuilder: b.cb,
|
||||
@@ -591,12 +598,13 @@ func aggOrderBy(k qbtypes.OrderBy, q qbtypes.QueryBuilderQuery[qbtypes.LogAggreg
|
||||
|
||||
func (b *auditQueryStatementBuilder) maybeAttachResourceFilter(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
sb *sqlbuilder.SelectBuilder,
|
||||
query qbtypes.QueryBuilderQuery[qbtypes.LogAggregation],
|
||||
start, end uint64,
|
||||
variables map[string]qbtypes.VariableItem,
|
||||
) (cteSQL string, cteArgs []any, err error) {
|
||||
stmt, err := b.resourceFilterStmtBuilder.Build(ctx, start, end, qbtypes.RequestTypeRaw, query, variables)
|
||||
stmt, err := b.resourceFilterStmtBuilder.Build(ctx, orgID, start, end, qbtypes.RequestTypeRaw, query, variables)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes/telemetrytypestest"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -213,7 +214,7 @@ func TestStatementBuilder(t *testing.T) {
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
q, err := statementBuilder.Build(ctx, 1747947419000, 1747983448000, testCase.requestType, testCase.query, nil)
|
||||
q, err := statementBuilder.Build(ctx, valuer.UUID{}, 1747947419000, 1747983448000, testCase.requestType, testCase.query, nil)
|
||||
if testCase.expectedErr != nil {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), testCase.expectedErr.Error())
|
||||
|
||||
@@ -45,6 +45,7 @@ func isBodyJSONSearch(key *telemetrytypes.TelemetryFieldKey, columns []*schema.C
|
||||
// legacy string extraction (flag off); value[0] is the needle.
|
||||
func (c *conditionBuilder) conditionForArrayFunction(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
startNs, endNs uint64,
|
||||
key *telemetrytypes.TelemetryFieldKey,
|
||||
operator qbtypes.FilterOperator,
|
||||
@@ -63,8 +64,8 @@ func (c *conditionBuilder) conditionForArrayFunction(
|
||||
}
|
||||
|
||||
var fieldExpr string
|
||||
if c.fl.BooleanOrEmpty(ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(valuer.UUID{})) {
|
||||
fe, err := c.fm.FieldFor(ctx, startNs, endNs, key)
|
||||
if c.fl.BooleanOrEmpty(ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(orgID)) {
|
||||
fe, err := c.fm.FieldFor(ctx, orgID, startNs, endNs, key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -82,6 +83,7 @@ func (c *conditionBuilder) conditionForArrayFunction(
|
||||
// name + use_json_body flag, validates the field/value, and tags errors with the doc URL.
|
||||
func (c *conditionBuilder) conditionForHasToken(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
key *telemetrytypes.TelemetryFieldKey,
|
||||
value any,
|
||||
sb *sqlbuilder.SelectBuilder,
|
||||
@@ -92,8 +94,7 @@ func (c *conditionBuilder) conditionForHasToken(
|
||||
needle = args[0]
|
||||
}
|
||||
|
||||
// TODO(Tushar): thread orgID here to evaluate correctly
|
||||
bodyJSONEnabled := c.fl.BooleanOrEmpty(ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(valuer.UUID{}))
|
||||
bodyJSONEnabled := c.fl.BooleanOrEmpty(ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(orgID))
|
||||
|
||||
columnName := LogsV2BodyColumn
|
||||
if bodyJSONEnabled {
|
||||
@@ -118,6 +119,7 @@ func (c *conditionBuilder) conditionForHasToken(
|
||||
|
||||
func (c *conditionBuilder) conditionFor(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
startNs, endNs uint64,
|
||||
key *telemetrytypes.TelemetryFieldKey,
|
||||
operator qbtypes.FilterOperator,
|
||||
@@ -127,10 +129,10 @@ func (c *conditionBuilder) conditionFor(
|
||||
// hasToken is a token search over the body column resolved purely from the key
|
||||
// name + flag, independent of column resolution, so handle it before anything else.
|
||||
if operator == qbtypes.FilterOperatorHasToken {
|
||||
return c.conditionForHasToken(ctx, key, value, sb)
|
||||
return c.conditionForHasToken(ctx, orgID, key, value, sb)
|
||||
}
|
||||
|
||||
columns, err := c.fm.ColumnFor(ctx, startNs, endNs, key)
|
||||
columns, err := c.fm.ColumnFor(ctx, orgID, startNs, endNs, key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -138,13 +140,12 @@ func (c *conditionBuilder) conditionFor(
|
||||
// has/hasAny/hasAll build `has(<arrayFieldExpr>, value)` over body JSON arrays
|
||||
// rather than going through the normal operator paths, so handle them up front.
|
||||
if operator.IsArrayFunctionOperator() {
|
||||
return c.conditionForArrayFunction(ctx, startNs, endNs, key, operator, value, columns, sb)
|
||||
return c.conditionForArrayFunction(ctx, orgID, startNs, endNs, key, operator, value, columns, sb)
|
||||
}
|
||||
|
||||
// TODO(Piyush): Update this to support multiple JSON columns based on evolutions
|
||||
for _, column := range columns {
|
||||
// TODO(Tushar): thread orgID here to evaluate correctly
|
||||
if column.Type.GetType() == schema.ColumnTypeEnumJSON && isBodyJSONSearch(key, columns) && c.fl.BooleanOrEmpty(ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(valuer.UUID{})) && key.Name != messageSubField {
|
||||
if column.Type.GetType() == schema.ColumnTypeEnumJSON && isBodyJSONSearch(key, columns) && c.fl.BooleanOrEmpty(ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(orgID)) && key.Name != messageSubField {
|
||||
valueType, value := InferDataType(value, operator, key)
|
||||
cond, err := NewJSONConditionBuilder(key, valueType).buildJSONCondition(operator, value, sb)
|
||||
if err != nil {
|
||||
@@ -158,14 +159,13 @@ func (c *conditionBuilder) conditionFor(
|
||||
value = querybuilder.FormatValueForContains(value)
|
||||
}
|
||||
|
||||
fieldExpression, err := c.fm.FieldFor(ctx, startNs, endNs, key)
|
||||
fieldExpression, err := c.fm.FieldFor(ctx, orgID, startNs, endNs, key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Check if this is a body JSON search (legacy string-body path, JSON flag off).
|
||||
// TODO(Tushar): thread orgID here to evaluate correctly
|
||||
if isBodyJSONSearch(key, columns) && !c.fl.BooleanOrEmpty(ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(valuer.UUID{})) {
|
||||
if isBodyJSONSearch(key, columns) && !c.fl.BooleanOrEmpty(ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(orgID)) {
|
||||
fieldExpression, value = GetBodyJSONKey(ctx, key, operator, value)
|
||||
}
|
||||
|
||||
@@ -276,8 +276,7 @@ func (c *conditionBuilder) conditionFor(
|
||||
// in the UI based query builder, `exists` and `not exists` are used for
|
||||
// key membership checks, so depending on the column type, the condition changes
|
||||
case qbtypes.FilterOperatorExists, qbtypes.FilterOperatorNotExists:
|
||||
// TODO(Tushar): thread orgID here to evaluate correctly
|
||||
if isBodyJSONSearch(key, columns) && !c.fl.BooleanOrEmpty(ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(valuer.UUID{})) {
|
||||
if isBodyJSONSearch(key, columns) && !c.fl.BooleanOrEmpty(ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(orgID)) {
|
||||
if operator == qbtypes.FilterOperatorExists {
|
||||
return GetBodyJSONKeyForExists(ctx, key, operator, value), nil
|
||||
} else {
|
||||
@@ -372,6 +371,7 @@ func (c *conditionBuilder) conditionFor(
|
||||
|
||||
func (c *conditionBuilder) ConditionFor(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
startNs uint64,
|
||||
endNs uint64,
|
||||
key *telemetrytypes.TelemetryFieldKey,
|
||||
@@ -390,12 +390,11 @@ func (c *conditionBuilder) ConditionFor(
|
||||
if len(keys) == 0 {
|
||||
// No known field key matched. Legacy string-body mode still searches unknown
|
||||
// Body-context keys as body JSON paths; JSON-body mode requires a metadata match.
|
||||
// TODO(Tushar): thread orgID here to evaluate correctly
|
||||
switch {
|
||||
case key.FieldContext == telemetrytypes.FieldContextBody && key.Name == "":
|
||||
return nil, warnings, errors.NewInvalidInputf(errors.CodeInvalidInput, "missing key for body json search - expected key of the form `body.key` (ex: `body.status`)")
|
||||
case key.FieldContext == telemetrytypes.FieldContextBody &&
|
||||
!c.fl.BooleanOrEmpty(ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(valuer.UUID{})):
|
||||
!c.fl.BooleanOrEmpty(ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(orgID)):
|
||||
keys = []*telemetrytypes.TelemetryFieldKey{key}
|
||||
default:
|
||||
return nil, warnings, querybuilder.NewKeyNotFoundError(key.Name)
|
||||
@@ -405,7 +404,7 @@ func (c *conditionBuilder) ConditionFor(
|
||||
// has/hasAny/hasAll need an array field: in JSON-body mode drop non-array matches so a
|
||||
// scalar errors clearly instead of failing at ClickHouse runtime (legacy mode skips this).
|
||||
if operator.IsArrayFunctionOperator() &&
|
||||
c.fl.BooleanOrEmpty(ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(valuer.UUID{})) {
|
||||
c.fl.BooleanOrEmpty(ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(orgID)) {
|
||||
arrayKeys := make([]*telemetrytypes.TelemetryFieldKey, 0, len(keys))
|
||||
for _, k := range keys {
|
||||
if k.FieldDataType.IsArray() {
|
||||
@@ -421,12 +420,12 @@ func (c *conditionBuilder) ConditionFor(
|
||||
|
||||
conds := make([]string, 0, len(keys))
|
||||
for _, k := range keys {
|
||||
cond, err := c.conditionForKey(ctx, startNs, endNs, k, operator, value, sb)
|
||||
cond, err := c.conditionForKey(ctx, orgID, startNs, endNs, k, operator, value, sb)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
conds = append(conds, cond)
|
||||
if w := c.bodyFullTextDefaultWarning(ctx, startNs, endNs, k, operator); w != "" {
|
||||
if w := c.bodyFullTextDefaultWarning(ctx, orgID, startNs, endNs, k, operator); w != "" {
|
||||
warnings = append(warnings, w)
|
||||
}
|
||||
}
|
||||
@@ -436,11 +435,11 @@ func (c *conditionBuilder) ConditionFor(
|
||||
// bodyFullTextDefaultWarning returns the advisory shown when a regexp full-text
|
||||
// search on `body` resolves to the body.message sub-field (JSON mode), else "". This
|
||||
// keeps the JSON-vs-legacy decision in the builder rather than the filter visitor.
|
||||
func (c *conditionBuilder) bodyFullTextDefaultWarning(ctx context.Context, startNs, endNs uint64, key *telemetrytypes.TelemetryFieldKey, operator qbtypes.FilterOperator) string {
|
||||
func (c *conditionBuilder) bodyFullTextDefaultWarning(ctx context.Context, orgID valuer.UUID, startNs, endNs uint64, key *telemetrytypes.TelemetryFieldKey, operator qbtypes.FilterOperator) string {
|
||||
if operator != qbtypes.FilterOperatorRegexp || key.Name != LogsV2BodyColumn {
|
||||
return ""
|
||||
}
|
||||
if field, err := c.fm.FieldFor(ctx, startNs, endNs, key); err == nil && field == messageSubColumn {
|
||||
if field, err := c.fm.FieldFor(ctx, orgID, startNs, endNs, key); err == nil && field == messageSubColumn {
|
||||
return querybuilder.BodyFullTextSearchDefaultWarning
|
||||
}
|
||||
return ""
|
||||
@@ -448,6 +447,7 @@ func (c *conditionBuilder) bodyFullTextDefaultWarning(ctx context.Context, start
|
||||
|
||||
func (c *conditionBuilder) conditionForKey(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
startNs uint64,
|
||||
endNs uint64,
|
||||
key *telemetrytypes.TelemetryFieldKey,
|
||||
@@ -456,7 +456,7 @@ func (c *conditionBuilder) conditionForKey(
|
||||
sb *sqlbuilder.SelectBuilder,
|
||||
) (string, error) {
|
||||
|
||||
condition, err := c.conditionFor(ctx, startNs, endNs, key, operator, value, sb)
|
||||
condition, err := c.conditionFor(ctx, orgID, startNs, endNs, key, operator, value, sb)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -473,14 +473,13 @@ func (c *conditionBuilder) conditionForKey(
|
||||
case telemetrytypes.FieldContextBody:
|
||||
// Querying JSON fields already account for Nullability of fields
|
||||
// so additional exists checks are not needed
|
||||
// TODO(Tushar): thread orgID here to evaluate correctly
|
||||
if c.fl.BooleanOrEmpty(ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(valuer.UUID{})) {
|
||||
if c.fl.BooleanOrEmpty(ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(orgID)) {
|
||||
return condition, nil
|
||||
}
|
||||
}
|
||||
|
||||
if buildExistCondition {
|
||||
existsCondition, err := c.conditionFor(ctx, startNs, endNs, key, qbtypes.FilterOperatorExists, nil, sb)
|
||||
existsCondition, err := c.conditionFor(ctx, orgID, startNs, endNs, key, qbtypes.FilterOperatorExists, nil, sb)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/flagger/flaggertest"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/huandu/go-sqlbuilder"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -131,7 +132,7 @@ func TestExistsConditionForWithEvolutions(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
sb := sqlbuilder.NewSelectBuilder()
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cond, _, err := conditionBuilder.ConditionFor(ctx, tc.startTs, tc.endTs, &tc.key, []*telemetrytypes.TelemetryFieldKey{&tc.key}, tc.operator, tc.value, sb)
|
||||
cond, _, err := conditionBuilder.ConditionFor(ctx, valuer.UUID{}, tc.startTs, tc.endTs, &tc.key, []*telemetrytypes.TelemetryFieldKey{&tc.key}, tc.operator, tc.value, sb)
|
||||
sb.Where(cond...)
|
||||
|
||||
if tc.expectedError != nil {
|
||||
@@ -522,7 +523,7 @@ func TestConditionFor(t *testing.T) {
|
||||
sb := sqlbuilder.NewSelectBuilder()
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
tc.key.Evolutions = tc.evolutions
|
||||
cond, _, err := conditionBuilder.ConditionFor(ctx, 0, 0, &tc.key, []*telemetrytypes.TelemetryFieldKey{&tc.key}, tc.operator, tc.value, sb)
|
||||
cond, _, err := conditionBuilder.ConditionFor(ctx, valuer.UUID{}, 0, 0, &tc.key, []*telemetrytypes.TelemetryFieldKey{&tc.key}, tc.operator, tc.value, sb)
|
||||
sb.Where(cond...)
|
||||
|
||||
if tc.expectedError != nil {
|
||||
@@ -578,7 +579,7 @@ func TestConditionForMultipleKeys(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var err error
|
||||
for _, key := range tc.keys {
|
||||
cond, err := conditionBuilder.conditionFor(ctx, 0, 0, &key, tc.operator, tc.value, sb)
|
||||
cond, err := conditionBuilder.conditionFor(ctx, valuer.UUID{}, 0, 0, &key, tc.operator, tc.value, sb)
|
||||
sb.Where(cond)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting condition for key %s: %v", key.Name, err)
|
||||
@@ -836,7 +837,7 @@ func TestConditionForJSONBodySearch(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
sb := sqlbuilder.NewSelectBuilder()
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cond, err := conditionBuilder.conditionFor(ctx, 0, 0, &tc.key, tc.operator, tc.value, sb)
|
||||
cond, err := conditionBuilder.conditionFor(ctx, valuer.UUID{}, 0, 0, &tc.key, tc.operator, tc.value, sb)
|
||||
sb.Where(cond)
|
||||
|
||||
if tc.expectedError != nil {
|
||||
|
||||
@@ -72,7 +72,7 @@ func NewFieldMapper(fl flagger.Flagger) qbtypes.FieldMapper {
|
||||
return &fieldMapper{fl: fl}
|
||||
}
|
||||
|
||||
func (m *fieldMapper) getColumn(ctx context.Context, key *telemetrytypes.TelemetryFieldKey) ([]*schema.Column, error) {
|
||||
func (m *fieldMapper) getColumn(ctx context.Context, orgID valuer.UUID, key *telemetrytypes.TelemetryFieldKey) ([]*schema.Column, error) {
|
||||
switch key.FieldContext {
|
||||
case telemetrytypes.FieldContextResource:
|
||||
columns := []*schema.Column{logsV2Columns["resources_string"], logsV2Columns["resource"]}
|
||||
@@ -96,8 +96,7 @@ func (m *fieldMapper) getColumn(ctx context.Context, key *telemetrytypes.Telemet
|
||||
}
|
||||
case telemetrytypes.FieldContextBody:
|
||||
// Body context is for JSON body fields. Use body_v2 if feature flag is enabled.
|
||||
// TODO(Tushar): thread orgID here to evaluate correctly
|
||||
if m.fl.BooleanOrEmpty(ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(valuer.UUID{})) {
|
||||
if m.fl.BooleanOrEmpty(ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(orgID)) {
|
||||
if key.Name == messageSubField {
|
||||
return []*schema.Column{logsV2Columns[messageSubColumn]}, nil
|
||||
}
|
||||
@@ -106,8 +105,7 @@ func (m *fieldMapper) getColumn(ctx context.Context, key *telemetrytypes.Telemet
|
||||
// Fall back to legacy body column
|
||||
return []*schema.Column{logsV2Columns["body"]}, nil
|
||||
case telemetrytypes.FieldContextLog, telemetrytypes.FieldContextUnspecified:
|
||||
// TODO(Tushar): thread orgID here to evaluate correctly
|
||||
if key.Name == LogsV2BodyColumn && m.fl.BooleanOrEmpty(ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(valuer.UUID{})) {
|
||||
if key.Name == LogsV2BodyColumn && m.fl.BooleanOrEmpty(ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(orgID)) {
|
||||
return []*schema.Column{logsV2Columns[messageSubColumn]}, nil
|
||||
}
|
||||
col, ok := logsV2Columns[key.Name]
|
||||
@@ -115,8 +113,7 @@ func (m *fieldMapper) getColumn(ctx context.Context, key *telemetrytypes.Telemet
|
||||
// check if the key has body JSON search
|
||||
if strings.HasPrefix(key.Name, telemetrytypes.BodyJSONStringSearchPrefix) {
|
||||
// Use body_v2 if feature flag is enabled and we have a body condition builder
|
||||
// TODO(Tushar): thread orgID here to evaluate correctly
|
||||
if m.fl.BooleanOrEmpty(ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(valuer.UUID{})) {
|
||||
if m.fl.BooleanOrEmpty(ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(orgID)) {
|
||||
// TODO(Piyush): Update this to support multiple JSON columns based on evolutions
|
||||
// i.e return both the body json and body json promoted and let the evolutions decide which one to use
|
||||
// based on the query range time.
|
||||
@@ -133,8 +130,8 @@ func (m *fieldMapper) getColumn(ctx context.Context, key *telemetrytypes.Telemet
|
||||
return nil, qbtypes.ErrColumnNotFound
|
||||
}
|
||||
|
||||
func (m *fieldMapper) FieldFor(ctx context.Context, tsStart, tsEnd uint64, key *telemetrytypes.TelemetryFieldKey) (string, error) {
|
||||
columns, err := m.getColumn(ctx, key)
|
||||
func (m *fieldMapper) FieldFor(ctx context.Context, orgID valuer.UUID, tsStart, tsEnd uint64, key *telemetrytypes.TelemetryFieldKey) (string, error) {
|
||||
columns, err := m.getColumn(ctx, orgID, key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -236,17 +233,18 @@ func (m *fieldMapper) FieldFor(ctx context.Context, tsStart, tsEnd uint64, key *
|
||||
return columns[0].Name, nil
|
||||
}
|
||||
|
||||
func (m *fieldMapper) ColumnFor(ctx context.Context, _, _ uint64, key *telemetrytypes.TelemetryFieldKey) ([]*schema.Column, error) {
|
||||
return m.getColumn(ctx, key)
|
||||
func (m *fieldMapper) ColumnFor(ctx context.Context, orgID valuer.UUID, _, _ uint64, key *telemetrytypes.TelemetryFieldKey) ([]*schema.Column, error) {
|
||||
return m.getColumn(ctx, orgID, key)
|
||||
}
|
||||
|
||||
func (m *fieldMapper) ColumnExpressionFor(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
tsStart, tsEnd uint64,
|
||||
field *telemetrytypes.TelemetryFieldKey,
|
||||
keys map[string][]*telemetrytypes.TelemetryFieldKey,
|
||||
) (string, error) {
|
||||
fieldExpression, err := m.FieldFor(ctx, tsStart, tsEnd, field)
|
||||
fieldExpression, err := m.FieldFor(ctx, orgID, tsStart, tsEnd, field)
|
||||
if errors.Is(err, qbtypes.ErrColumnNotFound) {
|
||||
// the key didn't have the right context to be added to the query
|
||||
// we try to use the context we know of
|
||||
@@ -256,7 +254,7 @@ func (m *fieldMapper) ColumnExpressionFor(
|
||||
if _, ok := logsV2Columns[field.Name]; ok {
|
||||
// if it is, attach the column name directly
|
||||
field.FieldContext = telemetrytypes.FieldContextLog
|
||||
fieldExpression, _ = m.FieldFor(ctx, tsStart, tsEnd, field)
|
||||
fieldExpression, _ = m.FieldFor(ctx, orgID, tsStart, tsEnd, field)
|
||||
} else {
|
||||
// - the context is not provided
|
||||
// - there are not keys for the field
|
||||
@@ -268,12 +266,12 @@ func (m *fieldMapper) ColumnExpressionFor(
|
||||
}
|
||||
} else if len(keysForField) == 1 {
|
||||
// we have a single key for the field, use it
|
||||
fieldExpression, _ = m.FieldFor(ctx, tsStart, tsEnd, keysForField[0])
|
||||
fieldExpression, _ = m.FieldFor(ctx, orgID, tsStart, tsEnd, keysForField[0])
|
||||
} else {
|
||||
// select any non-empty value from the keys
|
||||
args := []string{}
|
||||
for _, key := range keysForField {
|
||||
fieldExpression, _ = m.FieldFor(ctx, tsStart, tsEnd, key)
|
||||
fieldExpression, _ = m.FieldFor(ctx, orgID, tsStart, tsEnd, key)
|
||||
args = append(args, fmt.Sprintf("toString(%s) != '', toString(%s)", fieldExpression, fieldExpression))
|
||||
}
|
||||
fieldExpression = fmt.Sprintf("multiIf(%s, NULL)", strings.Join(args, ", "))
|
||||
@@ -285,6 +283,12 @@ func (m *fieldMapper) ColumnExpressionFor(
|
||||
return fmt.Sprintf("%s AS `%s`", sqlbuilder.Escape(fieldExpression), field.Name), nil
|
||||
}
|
||||
|
||||
// CandidateKeys returns nil: logs has no synthesize-on-unknown-key fallback, so an
|
||||
// unknown key stays unresolved and the caller errors.
|
||||
func (m *fieldMapper) CandidateKeys(_ context.Context, _ valuer.UUID, _ *telemetrytypes.TelemetryFieldKey, _ any, _ map[string][]*telemetrytypes.TelemetryFieldKey) []*telemetrytypes.TelemetryFieldKey {
|
||||
return nil
|
||||
}
|
||||
|
||||
// buildFieldForJSON builds the field expression for body JSON fields using arrayConcat pattern.
|
||||
func (m *fieldMapper) buildFieldForJSON(key *telemetrytypes.TelemetryFieldKey) (string, error) {
|
||||
plan := key.JSONPlan
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/flagger/flaggertest"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -171,7 +172,7 @@ func TestGetColumn(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
col, err := fm.ColumnFor(ctx, 0, 0, &tc.key)
|
||||
col, err := fm.ColumnFor(ctx, valuer.UUID{}, 0, 0, &tc.key)
|
||||
|
||||
if tc.expectedError != nil {
|
||||
assert.Equal(t, tc.expectedError, err)
|
||||
@@ -277,7 +278,7 @@ func TestGetFieldKeyName(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
fl := flaggertest.New(t)
|
||||
fm := NewFieldMapper(fl)
|
||||
result, err := fm.FieldFor(ctx, 0, 0, &tc.key)
|
||||
result, err := fm.FieldFor(ctx, valuer.UUID{}, 0, 0, &tc.key)
|
||||
|
||||
if tc.expectedError != nil {
|
||||
assert.Equal(t, tc.expectedError, err)
|
||||
@@ -524,7 +525,7 @@ func TestFieldForWithEvolutions(t *testing.T) {
|
||||
tsEnd := uint64(tc.tsEndTime.UnixNano())
|
||||
tc.key.Evolutions = tc.evolutions
|
||||
|
||||
result, err := fm.FieldFor(ctx, tsStart, tsEnd, tc.key)
|
||||
result, err := fm.FieldFor(ctx, valuer.UUID{}, tsStart, tsEnd, tc.key)
|
||||
|
||||
if tc.expectedError != nil {
|
||||
assert.Equal(t, tc.expectedError, err)
|
||||
@@ -590,7 +591,7 @@ func TestFieldForWithMaterialized(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
start := uint64(tc.start.UnixNano())
|
||||
end := uint64(tc.end.UnixNano())
|
||||
result, err := fm.FieldFor(ctx, start, end, materializedKey)
|
||||
result, err := fm.FieldFor(ctx, valuer.UUID{}, start, end, materializedKey)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.expectedResult, result)
|
||||
})
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes/telemetrytypestest"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -94,7 +95,7 @@ func TestJSONStmtBuilder_TimeSeries(t *testing.T) {
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
|
||||
q, err := statementBuilder.Build(context.Background(), 1747947419000, 1747983448000, c.requestType, c.query, nil)
|
||||
q, err := statementBuilder.Build(context.Background(), valuer.UUID{}, 1747947419000, 1747983448000, c.requestType, c.query, nil)
|
||||
|
||||
if c.expectedErrContains != "" {
|
||||
require.Error(t, err)
|
||||
@@ -155,7 +156,7 @@ func TestStmtBuilderTimeSeriesBodyGroupByPromoted(t *testing.T) {
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
q, err := statementBuilder.Build(context.Background(), 1747947419000, 1747983448000, c.requestType, c.query, nil)
|
||||
q, err := statementBuilder.Build(context.Background(), valuer.UUID{}, 1747947419000, 1747983448000, c.requestType, c.query, nil)
|
||||
if c.expectedErrContains != "" {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), c.expectedErrContains)
|
||||
@@ -308,7 +309,7 @@ func TestJSONStmtBuilder_PrimitivePaths(t *testing.T) {
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
q, err := statementBuilder.Build(context.Background(), 1747947419000, 1747983448000, qbtypes.RequestTypeRaw,
|
||||
q, err := statementBuilder.Build(context.Background(), valuer.UUID{}, 1747947419000, 1747983448000, qbtypes.RequestTypeRaw,
|
||||
qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]{
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
Filter: &qbtypes.Filter{Expression: c.filter},
|
||||
@@ -477,7 +478,7 @@ func TestStatementBuilderListQueryBodyPromoted(t *testing.T) {
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
|
||||
q, err := statementBuilder.Build(context.Background(), 1747947419000, 1747983448000, c.requestType, c.query, nil)
|
||||
q, err := statementBuilder.Build(context.Background(), valuer.UUID{}, 1747947419000, 1747983448000, c.requestType, c.query, nil)
|
||||
|
||||
if c.expectedErr != nil {
|
||||
require.Error(t, err)
|
||||
@@ -779,7 +780,7 @@ func TestJSONStmtBuilder_ArrayPaths(t *testing.T) {
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
q, err := statementBuilder.Build(context.Background(), 1747947419000, 1747983448000, qbtypes.RequestTypeRaw,
|
||||
q, err := statementBuilder.Build(context.Background(), valuer.UUID{}, 1747947419000, 1747983448000, qbtypes.RequestTypeRaw,
|
||||
qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]{
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
Filter: &qbtypes.Filter{Expression: c.filter},
|
||||
@@ -904,7 +905,7 @@ func TestJSONStmtBuilder_IndexedPaths(t *testing.T) {
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
q, err := statementBuilder.Build(context.Background(), 1747947419000, 1747983448000, qbtypes.RequestTypeRaw, c.query, nil)
|
||||
q, err := statementBuilder.Build(context.Background(), valuer.UUID{}, 1747947419000, 1747983448000, qbtypes.RequestTypeRaw, c.query, nil)
|
||||
if c.expectedErr != nil {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), c.expectedErr.Error())
|
||||
@@ -991,7 +992,7 @@ func TestJSONStmtBuilder_SelectField(t *testing.T) {
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
q, err := statementBuilder.Build(context.Background(), 1747947419000, 1747983448000, c.requestType, c.query, nil)
|
||||
q, err := statementBuilder.Build(context.Background(), valuer.UUID{}, 1747947419000, 1747983448000, c.requestType, c.query, nil)
|
||||
if c.expectedErrContains != "" {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), c.expectedErrContains)
|
||||
@@ -1068,7 +1069,7 @@ func TestJSONStmtBuilder_OrderBy(t *testing.T) {
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
q, err := statementBuilder.Build(context.Background(), 1747947419000, 1747983448000, c.requestType, c.query, nil)
|
||||
q, err := statementBuilder.Build(context.Background(), valuer.UUID{}, 1747947419000, 1747983448000, c.requestType, c.query, nil)
|
||||
if c.expectedErrContains != "" {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), c.expectedErrContains)
|
||||
@@ -1131,7 +1132,7 @@ func TestResourceAggrAndGroupBy_WithJSONEnabled(t *testing.T) {
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
q, err := statementBuilder.Build(context.Background(), 1747947419000, 1747983448000, c.requestType, c.query, nil)
|
||||
q, err := statementBuilder.Build(context.Background(), valuer.UUID{}, 1747947419000, 1747983448000, c.requestType, c.query, nil)
|
||||
if c.expectedErrContains != "" {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), c.expectedErrContains)
|
||||
|
||||
@@ -80,6 +80,7 @@ func NewLogQueryStatementBuilder(
|
||||
// Build builds a SQL query for logs based on the given parameters.
|
||||
func (b *logQueryStatementBuilder) Build(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
start uint64,
|
||||
end uint64,
|
||||
requestType qbtypes.RequestType,
|
||||
@@ -89,11 +90,10 @@ func (b *logQueryStatementBuilder) Build(
|
||||
|
||||
start = querybuilder.ToNanoSecs(start)
|
||||
end = querybuilder.ToNanoSecs(end)
|
||||
// TODO(Tushar): thread orgID here to evaluate correctly
|
||||
bodyJSONEnabled := b.fl.BooleanOrEmpty(ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(valuer.UUID{}))
|
||||
bodyJSONEnabled := b.fl.BooleanOrEmpty(ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(orgID))
|
||||
|
||||
keySelectors, warnings := getKeySelectors(query, bodyJSONEnabled)
|
||||
keys, _, err := b.metadataStore.GetKeysMulti(ctx, keySelectors)
|
||||
keys, _, err := b.metadataStore.GetKeysMulti(ctx, orgID, keySelectors)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -106,11 +106,11 @@ func (b *logQueryStatementBuilder) Build(
|
||||
var stmt *qbtypes.Statement
|
||||
switch requestType {
|
||||
case qbtypes.RequestTypeRaw, qbtypes.RequestTypeRawStream:
|
||||
stmt, err = b.buildListQuery(ctx, q, query, start, end, keys, variables)
|
||||
stmt, err = b.buildListQuery(ctx, orgID, q, query, start, end, keys, variables)
|
||||
case qbtypes.RequestTypeTimeSeries:
|
||||
stmt, err = b.buildTimeSeriesQuery(ctx, q, query, start, end, keys, variables)
|
||||
stmt, err = b.buildTimeSeriesQuery(ctx, orgID, q, query, start, end, keys, variables)
|
||||
case qbtypes.RequestTypeScalar:
|
||||
stmt, err = b.buildScalarQuery(ctx, q, query, start, end, keys, false, variables)
|
||||
stmt, err = b.buildScalarQuery(ctx, orgID, q, query, start, end, keys, false, variables)
|
||||
default:
|
||||
return nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "unsupported request type: %s", requestType)
|
||||
}
|
||||
@@ -264,6 +264,7 @@ func (b *logQueryStatementBuilder) adjustKey(key *telemetrytypes.TelemetryFieldK
|
||||
// buildListQuery builds a query for list panel type.
|
||||
func (b *logQueryStatementBuilder) buildListQuery(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
sb *sqlbuilder.SelectBuilder,
|
||||
query qbtypes.QueryBuilderQuery[qbtypes.LogAggregation],
|
||||
start, end uint64,
|
||||
@@ -272,13 +273,12 @@ func (b *logQueryStatementBuilder) buildListQuery(
|
||||
) (*qbtypes.Statement, error) {
|
||||
|
||||
var (
|
||||
cteFragments []string
|
||||
cteArgs [][]any
|
||||
// TODO(Tushar): thread orgID here to evaluate correctly
|
||||
bodyJSONEnabled = b.fl.BooleanOrEmpty(ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(valuer.UUID{}))
|
||||
cteFragments []string
|
||||
cteArgs [][]any
|
||||
bodyJSONEnabled = b.fl.BooleanOrEmpty(ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(orgID))
|
||||
)
|
||||
|
||||
frag, args, skipResourceFilter, err := b.maybeAttachResourceFilter(ctx, sb, query, start, end, variables)
|
||||
frag, args, skipResourceFilter, err := b.maybeAttachResourceFilter(ctx, orgID, sb, query, start, end, variables)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -314,7 +314,7 @@ func (b *logQueryStatementBuilder) buildListQuery(
|
||||
}
|
||||
|
||||
// get column expression for the field - use array index directly to avoid pointer to loop variable
|
||||
colExpr, err := b.fm.ColumnExpressionFor(ctx, start, end, &query.SelectFields[index], keys)
|
||||
colExpr, err := b.fm.ColumnExpressionFor(ctx, orgID, start, end, &query.SelectFields[index], keys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -324,7 +324,7 @@ func (b *logQueryStatementBuilder) buildListQuery(
|
||||
|
||||
sb.From(fmt.Sprintf("%s.%s", DBName, LogsV2TableName))
|
||||
// Add filter conditions
|
||||
preparedWhereClause, err := b.addFilterCondition(ctx, sb, start, end, query, keys, variables, skipResourceFilter)
|
||||
preparedWhereClause, err := b.addFilterCondition(ctx, orgID, sb, start, end, query, keys, variables, skipResourceFilter)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -333,7 +333,7 @@ func (b *logQueryStatementBuilder) buildListQuery(
|
||||
// Add order by
|
||||
for _, orderBy := range query.Order {
|
||||
|
||||
colExpr, err := b.fm.ColumnExpressionFor(ctx, start, end, &orderBy.Key.TelemetryFieldKey, keys)
|
||||
colExpr, err := b.fm.ColumnExpressionFor(ctx, orgID, start, end, &orderBy.Key.TelemetryFieldKey, keys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -368,6 +368,7 @@ func (b *logQueryStatementBuilder) buildListQuery(
|
||||
|
||||
func (b *logQueryStatementBuilder) buildTimeSeriesQuery(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
sb *sqlbuilder.SelectBuilder,
|
||||
query qbtypes.QueryBuilderQuery[qbtypes.LogAggregation],
|
||||
start, end uint64,
|
||||
@@ -376,13 +377,12 @@ func (b *logQueryStatementBuilder) buildTimeSeriesQuery(
|
||||
) (*qbtypes.Statement, error) {
|
||||
|
||||
var (
|
||||
cteFragments []string
|
||||
cteArgs [][]any
|
||||
// TODO(Tushar): thread orgID here to evaluate correctly
|
||||
bodyJSONEnabled = b.fl.BooleanOrEmpty(ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(valuer.UUID{}))
|
||||
cteFragments []string
|
||||
cteArgs [][]any
|
||||
bodyJSONEnabled = b.fl.BooleanOrEmpty(ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(orgID))
|
||||
)
|
||||
|
||||
frag, args, skipResourceFilter, err := b.maybeAttachResourceFilter(ctx, sb, query, start, end, variables)
|
||||
frag, args, skipResourceFilter, err := b.maybeAttachResourceFilter(ctx, orgID, sb, query, start, end, variables)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -401,7 +401,7 @@ func (b *logQueryStatementBuilder) buildTimeSeriesQuery(
|
||||
// Keep original column expressions so we can build the tuple
|
||||
fieldNames := make([]string, 0, len(query.GroupBy))
|
||||
for _, gb := range query.GroupBy {
|
||||
expr, args, err := querybuilder.CollisionHandledFinalExpr(ctx, start, end, &gb.TelemetryFieldKey, b.fm, b.cb, keys, telemetrytypes.FieldDataTypeString, b.jsonKeyToKey, bodyJSONEnabled)
|
||||
expr, args, err := querybuilder.CollisionHandledFinalExpr(ctx, orgID, start, end, &gb.TelemetryFieldKey, b.fm, b.cb, keys, telemetrytypes.FieldDataTypeString, b.jsonKeyToKey, bodyJSONEnabled)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -416,7 +416,7 @@ func (b *logQueryStatementBuilder) buildTimeSeriesQuery(
|
||||
allAggChArgs := make([]any, 0)
|
||||
for i, agg := range query.Aggregations {
|
||||
rewritten, chArgs, err := b.aggExprRewriter.Rewrite(
|
||||
ctx, start, end, agg.Expression,
|
||||
ctx, orgID, start, end, agg.Expression,
|
||||
uint64(query.StepInterval.Seconds()),
|
||||
keys,
|
||||
)
|
||||
@@ -430,7 +430,7 @@ func (b *logQueryStatementBuilder) buildTimeSeriesQuery(
|
||||
// Add FROM clause
|
||||
sb.From(fmt.Sprintf("%s.%s", DBName, LogsV2TableName))
|
||||
|
||||
preparedWhereClause, err := b.addFilterCondition(ctx, sb, start, end, query, keys, variables, skipResourceFilter)
|
||||
preparedWhereClause, err := b.addFilterCondition(ctx, orgID, sb, start, end, query, keys, variables, skipResourceFilter)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -442,7 +442,7 @@ func (b *logQueryStatementBuilder) buildTimeSeriesQuery(
|
||||
if query.Limit > 0 && len(query.GroupBy) > 0 {
|
||||
// build the scalar “top/bottom-N” query in its own builder.
|
||||
cteSB := sqlbuilder.NewSelectBuilder()
|
||||
cteStmt, err := b.buildScalarQuery(ctx, cteSB, query, start, end, keys, true, variables)
|
||||
cteStmt, err := b.buildScalarQuery(ctx, orgID, cteSB, query, start, end, keys, true, variables)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -527,6 +527,7 @@ func (b *logQueryStatementBuilder) buildTimeSeriesQuery(
|
||||
// buildScalarQuery builds a query for scalar panel type.
|
||||
func (b *logQueryStatementBuilder) buildScalarQuery(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
sb *sqlbuilder.SelectBuilder,
|
||||
query qbtypes.QueryBuilderQuery[qbtypes.LogAggregation],
|
||||
start, end uint64,
|
||||
@@ -536,13 +537,12 @@ func (b *logQueryStatementBuilder) buildScalarQuery(
|
||||
) (*qbtypes.Statement, error) {
|
||||
|
||||
var (
|
||||
cteFragments []string
|
||||
cteArgs [][]any
|
||||
// TODO(Tushar): thread orgID here to evaluate correctly
|
||||
bodyJSONEnabled = b.fl.BooleanOrEmpty(ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(valuer.UUID{}))
|
||||
cteFragments []string
|
||||
cteArgs [][]any
|
||||
bodyJSONEnabled = b.fl.BooleanOrEmpty(ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(orgID))
|
||||
)
|
||||
|
||||
frag, args, skipResourceFilter, err := b.maybeAttachResourceFilter(ctx, sb, query, start, end, variables)
|
||||
frag, args, skipResourceFilter, err := b.maybeAttachResourceFilter(ctx, orgID, sb, query, start, end, variables)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -556,7 +556,7 @@ func (b *logQueryStatementBuilder) buildScalarQuery(
|
||||
var allGroupByArgs []any
|
||||
|
||||
for _, gb := range query.GroupBy {
|
||||
expr, args, err := querybuilder.CollisionHandledFinalExpr(ctx, start, end, &gb.TelemetryFieldKey, b.fm, b.cb, keys, telemetrytypes.FieldDataTypeString, b.jsonKeyToKey, bodyJSONEnabled)
|
||||
expr, args, err := querybuilder.CollisionHandledFinalExpr(ctx, orgID, start, end, &gb.TelemetryFieldKey, b.fm, b.cb, keys, telemetrytypes.FieldDataTypeString, b.jsonKeyToKey, bodyJSONEnabled)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -574,7 +574,7 @@ func (b *logQueryStatementBuilder) buildScalarQuery(
|
||||
for idx := range query.Aggregations {
|
||||
aggExpr := query.Aggregations[idx]
|
||||
rewritten, chArgs, err := b.aggExprRewriter.Rewrite(
|
||||
ctx, start, end, aggExpr.Expression,
|
||||
ctx, orgID, start, end, aggExpr.Expression,
|
||||
rateInterval,
|
||||
keys,
|
||||
)
|
||||
@@ -589,7 +589,7 @@ func (b *logQueryStatementBuilder) buildScalarQuery(
|
||||
sb.From(fmt.Sprintf("%s.%s", DBName, LogsV2TableName))
|
||||
|
||||
// Add filter conditions
|
||||
preparedWhereClause, err := b.addFilterCondition(ctx, sb, start, end, query, keys, variables, skipResourceFilter)
|
||||
preparedWhereClause, err := b.addFilterCondition(ctx, orgID, sb, start, end, query, keys, variables, skipResourceFilter)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -648,6 +648,7 @@ func (b *logQueryStatementBuilder) buildScalarQuery(
|
||||
// buildFilterCondition builds SQL condition from filter expression.
|
||||
func (b *logQueryStatementBuilder) addFilterCondition(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
sb *sqlbuilder.SelectBuilder,
|
||||
start, end uint64,
|
||||
query qbtypes.QueryBuilderQuery[qbtypes.LogAggregation],
|
||||
@@ -663,6 +664,7 @@ func (b *logQueryStatementBuilder) addFilterCondition(
|
||||
// add filter expression
|
||||
preparedWhereClause, err = querybuilder.PrepareWhereClause(query.Filter.Expression, querybuilder.FilterExprVisitorOpts{
|
||||
Context: ctx,
|
||||
OrgID: orgID,
|
||||
Logger: b.logger,
|
||||
FieldMapper: b.fm,
|
||||
ConditionBuilder: b.cb,
|
||||
@@ -713,6 +715,7 @@ func aggOrderBy(k qbtypes.OrderBy, q qbtypes.QueryBuilderQuery[qbtypes.LogAggreg
|
||||
|
||||
func (b *logQueryStatementBuilder) maybeAttachResourceFilter(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
sb *sqlbuilder.SelectBuilder,
|
||||
query qbtypes.QueryBuilderQuery[qbtypes.LogAggregation],
|
||||
start, end uint64,
|
||||
@@ -720,7 +723,7 @@ func (b *logQueryStatementBuilder) maybeAttachResourceFilter(
|
||||
) (cteSQL string, cteArgs []any, skipResourceFilter bool, err error) {
|
||||
|
||||
if b.skipResourceFingerprintEnabled {
|
||||
decision, err := b.resourceFilterResolver.Resolve(ctx, query, start, end, variables)
|
||||
decision, err := b.resourceFilterResolver.Resolve(ctx, orgID, query, start, end, variables)
|
||||
if err != nil {
|
||||
return "", nil, true, err
|
||||
}
|
||||
@@ -733,7 +736,7 @@ func (b *logQueryStatementBuilder) maybeAttachResourceFilter(
|
||||
}
|
||||
|
||||
stmt, err := b.resourceFilterResolver.StatementBuilder().Build(
|
||||
ctx, start, end, qbtypes.RequestTypeRaw, query, variables,
|
||||
ctx, orgID, start, end, qbtypes.RequestTypeRaw, query, variables,
|
||||
)
|
||||
if err != nil {
|
||||
return "", nil, true, err
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes/telemetrytypestest"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -237,7 +238,7 @@ func TestStatementBuilderTimeSeries(t *testing.T) {
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
|
||||
q, err := statementBuilder.Build(ctx, c.startTs, c.endTs, c.requestType, c.query, nil)
|
||||
q, err := statementBuilder.Build(ctx, valuer.UUID{}, c.startTs, c.endTs, c.requestType, c.query, nil)
|
||||
|
||||
if c.expectedErr != nil {
|
||||
require.Error(t, err)
|
||||
@@ -381,7 +382,7 @@ func TestStatementBuilderListQuery(t *testing.T) {
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
|
||||
q, err := statementBuilder.Build(ctx, 1747947419000, 1747983448000, c.requestType, c.query, c.variables)
|
||||
q, err := statementBuilder.Build(ctx, valuer.UUID{}, 1747947419000, 1747983448000, c.requestType, c.query, c.variables)
|
||||
|
||||
if c.expectedErr != nil {
|
||||
require.Error(t, err)
|
||||
@@ -530,7 +531,7 @@ func TestStatementBuilderListQueryResourceTests(t *testing.T) {
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
|
||||
q, err := statementBuilder.Build(ctx, 1747947419000, 1747983448000, c.requestType, c.query, nil)
|
||||
q, err := statementBuilder.Build(ctx, valuer.UUID{}, 1747947419000, 1747983448000, c.requestType, c.query, nil)
|
||||
|
||||
if c.expectedErr != nil {
|
||||
require.Error(t, err)
|
||||
@@ -609,7 +610,7 @@ func TestStatementBuilderTimeSeriesBodyGroupBy(t *testing.T) {
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
|
||||
q, err := statementBuilder.Build(ctx, 1747947419000, 1747983448000, c.requestType, c.query, nil)
|
||||
q, err := statementBuilder.Build(ctx, valuer.UUID{}, 1747947419000, 1747983448000, c.requestType, c.query, nil)
|
||||
|
||||
if c.expectedErrContains != "" {
|
||||
require.Error(t, err)
|
||||
@@ -707,7 +708,7 @@ func TestStatementBuilderListQueryServiceCollision(t *testing.T) {
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
|
||||
q, err := statementBuilder.Build(ctx, 1747947419000, 1747983448000, c.requestType, c.query, nil)
|
||||
q, err := statementBuilder.Build(ctx, valuer.UUID{}, 1747947419000, 1747983448000, c.requestType, c.query, nil)
|
||||
|
||||
if c.expectedErr != nil {
|
||||
require.Error(t, err)
|
||||
@@ -1079,7 +1080,7 @@ func TestStmtBuilderBodyField(t *testing.T) {
|
||||
100000,
|
||||
)
|
||||
|
||||
q, err := statementBuilder.Build(context.Background(), 1747947419000, 1747983448000, c.requestType, c.query, nil)
|
||||
q, err := statementBuilder.Build(context.Background(), valuer.UUID{}, 1747947419000, 1747983448000, c.requestType, c.query, nil)
|
||||
if c.expectedErr != nil {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), c.expectedErr.Error())
|
||||
@@ -1181,7 +1182,7 @@ func TestStmtBuilderBodyFullTextSearch(t *testing.T) {
|
||||
100000,
|
||||
)
|
||||
|
||||
q, err := statementBuilder.Build(context.Background(), 1747947419000, 1747983448000, c.requestType, c.query, nil)
|
||||
q, err := statementBuilder.Build(context.Background(), valuer.UUID{}, 1747947419000, 1747983448000, c.requestType, c.query, nil)
|
||||
if c.expectedErr != nil {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), c.expectedErr.Error())
|
||||
@@ -1220,7 +1221,7 @@ func TestSkipResourceFingerprintLogs(t *testing.T) {
|
||||
t.Run("disabled uses the legacy CTE", func(t *testing.T) {
|
||||
sb := newSkipResourceFingerprintLogsBuilder(t, nil, false, threshold)
|
||||
|
||||
stmt, err := sb.Build(context.Background(), startMs, endMs, qbtypes.RequestTypeRaw, query, nil)
|
||||
stmt, err := sb.Build(context.Background(), valuer.UUID{}, startMs, endMs, qbtypes.RequestTypeRaw, query, nil)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, stmt.Query, "__resource_filter AS (SELECT fingerprint")
|
||||
require.Contains(t, stmt.Query, "resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter)")
|
||||
@@ -1237,7 +1238,7 @@ func TestSkipResourceFingerprintLogs(t *testing.T) {
|
||||
|
||||
sb := newSkipResourceFingerprintLogsBuilder(t, mockStore, true, threshold)
|
||||
|
||||
stmt, err := sb.Build(context.Background(), startMs, endMs, qbtypes.RequestTypeRaw, query, nil)
|
||||
stmt, err := sb.Build(context.Background(), valuer.UUID{}, startMs, endMs, qbtypes.RequestTypeRaw, query, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Contains(t, stmt.Query, "__resource_filter AS (SELECT fingerprint")
|
||||
@@ -1257,7 +1258,7 @@ func TestSkipResourceFingerprintLogs(t *testing.T) {
|
||||
|
||||
sb := newSkipResourceFingerprintLogsBuilder(t, mockStore, true, threshold)
|
||||
|
||||
stmt, err := sb.Build(context.Background(), startMs, endMs, qbtypes.RequestTypeRaw, query, nil)
|
||||
stmt, err := sb.Build(context.Background(), valuer.UUID{}, startMs, endMs, qbtypes.RequestTypeRaw, query, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NotContains(t, stmt.Query, "__resource_filter AS")
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/querybuilder"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/huandu/go-sqlbuilder"
|
||||
)
|
||||
|
||||
@@ -21,6 +22,7 @@ func NewConditionBuilder(fm qbtypes.FieldMapper) *conditionBuilder {
|
||||
|
||||
func (c *conditionBuilder) ConditionFor(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
tsStart, tsEnd uint64,
|
||||
key *telemetrytypes.TelemetryFieldKey,
|
||||
fieldKeysForName []*telemetrytypes.TelemetryFieldKey,
|
||||
@@ -44,7 +46,7 @@ func (c *conditionBuilder) ConditionFor(
|
||||
|
||||
conds := make([]string, 0, len(keys))
|
||||
for _, k := range keys {
|
||||
cond, err := c.conditionForKey(ctx, tsStart, tsEnd, k, operator, value, sb)
|
||||
cond, err := c.conditionForKey(ctx, orgID, tsStart, tsEnd, k, operator, value, sb)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -55,6 +57,7 @@ func (c *conditionBuilder) ConditionFor(
|
||||
|
||||
func (c *conditionBuilder) conditionForKey(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
tsStart, tsEnd uint64,
|
||||
key *telemetrytypes.TelemetryFieldKey,
|
||||
operator qbtypes.FilterOperator,
|
||||
@@ -72,13 +75,13 @@ func (c *conditionBuilder) conditionForKey(
|
||||
value = querybuilder.FormatValueForContains(value)
|
||||
}
|
||||
|
||||
columns, err := c.fm.ColumnFor(ctx, tsStart, tsEnd, key)
|
||||
columns, err := c.fm.ColumnFor(ctx, orgID, tsStart, tsEnd, key)
|
||||
if err != nil {
|
||||
// if we don't have a column, we can't build a condition for related values
|
||||
return "", nil
|
||||
}
|
||||
|
||||
fieldExpression, err := c.fm.FieldFor(ctx, tsStart, tsEnd, key)
|
||||
fieldExpression, err := c.fm.FieldFor(ctx, orgID, tsStart, tsEnd, key)
|
||||
if err != nil {
|
||||
// if we don't have a table field name, we can't build a condition for related values
|
||||
return "", nil
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/huandu/go-sqlbuilder"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -53,7 +54,7 @@ func TestConditionFor(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
sb := sqlbuilder.NewSelectBuilder()
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cond, _, err := conditionBuilder.ConditionFor(ctx, 0, 0, &tc.key, []*telemetrytypes.TelemetryFieldKey{&tc.key}, tc.operator, tc.value, sb)
|
||||
cond, _, err := conditionBuilder.ConditionFor(ctx, valuer.UUID{}, 0, 0, &tc.key, []*telemetrytypes.TelemetryFieldKey{&tc.key}, tc.operator, tc.value, sb)
|
||||
sb.Where(cond...)
|
||||
|
||||
if tc.expectedError != nil {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/huandu/go-sqlbuilder"
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
@@ -29,6 +30,12 @@ var (
|
||||
type fieldMapper struct {
|
||||
}
|
||||
|
||||
// CandidateKeys returns nil: this mapper has no attribute-map fallback, so a context-missing
|
||||
// key stays unresolved and the caller errors.
|
||||
func (m *fieldMapper) CandidateKeys(_ context.Context, _ valuer.UUID, _ *telemetrytypes.TelemetryFieldKey, _ any, _ map[string][]*telemetrytypes.TelemetryFieldKey) []*telemetrytypes.TelemetryFieldKey {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewFieldMapper() qbtypes.FieldMapper {
|
||||
return &fieldMapper{}
|
||||
}
|
||||
@@ -43,7 +50,7 @@ func (m *fieldMapper) getColumn(_ context.Context, _, _ uint64, key *telemetryty
|
||||
return nil, qbtypes.ErrColumnNotFound
|
||||
}
|
||||
|
||||
func (m *fieldMapper) ColumnFor(ctx context.Context, tsStart, tsEnd uint64, key *telemetrytypes.TelemetryFieldKey) ([]*schema.Column, error) {
|
||||
func (m *fieldMapper) ColumnFor(ctx context.Context, _ valuer.UUID, tsStart, tsEnd uint64, key *telemetrytypes.TelemetryFieldKey) ([]*schema.Column, error) {
|
||||
columns, err := m.getColumn(ctx, tsStart, tsEnd, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -51,7 +58,7 @@ func (m *fieldMapper) ColumnFor(ctx context.Context, tsStart, tsEnd uint64, key
|
||||
return columns, nil
|
||||
}
|
||||
|
||||
func (m *fieldMapper) FieldFor(ctx context.Context, startNs, endNs uint64, key *telemetrytypes.TelemetryFieldKey) (string, error) {
|
||||
func (m *fieldMapper) FieldFor(ctx context.Context, _ valuer.UUID, startNs, endNs uint64, key *telemetrytypes.TelemetryFieldKey) (string, error) {
|
||||
columns, err := m.getColumn(ctx, startNs, endNs, key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -69,12 +76,13 @@ func (m *fieldMapper) FieldFor(ctx context.Context, startNs, endNs uint64, key *
|
||||
|
||||
func (m *fieldMapper) ColumnExpressionFor(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
startNs, endNs uint64,
|
||||
field *telemetrytypes.TelemetryFieldKey,
|
||||
keys map[string][]*telemetrytypes.TelemetryFieldKey,
|
||||
) (string, error) {
|
||||
|
||||
fieldExpression, err := m.FieldFor(ctx, startNs, endNs, field)
|
||||
fieldExpression, err := m.FieldFor(ctx, orgID, startNs, endNs, field)
|
||||
if errors.Is(err, qbtypes.ErrColumnNotFound) {
|
||||
// the key didn't have the right context to be added to the query
|
||||
// we try to use the context we know of
|
||||
@@ -84,7 +92,7 @@ func (m *fieldMapper) ColumnExpressionFor(
|
||||
if _, ok := attributeMetadataColumns[field.Name]; ok {
|
||||
// if it is, attach the column name directly
|
||||
field.FieldContext = telemetrytypes.FieldContextSpan
|
||||
fieldExpression, _ = m.FieldFor(ctx, startNs, endNs, field)
|
||||
fieldExpression, _ = m.FieldFor(ctx, orgID, startNs, endNs, field)
|
||||
} else {
|
||||
// - the context is not provided
|
||||
// - there are not keys for the field
|
||||
@@ -96,12 +104,12 @@ func (m *fieldMapper) ColumnExpressionFor(
|
||||
}
|
||||
} else if len(keysForField) == 1 {
|
||||
// we have a single key for the field, use it
|
||||
fieldExpression, _ = m.FieldFor(ctx, startNs, endNs, keysForField[0])
|
||||
fieldExpression, _ = m.FieldFor(ctx, orgID, startNs, endNs, keysForField[0])
|
||||
} else {
|
||||
// select any non-empty value from the keys
|
||||
args := []string{}
|
||||
for _, key := range keysForField {
|
||||
fieldExpression, _ = m.FieldFor(ctx, startNs, endNs, key)
|
||||
fieldExpression, _ = m.FieldFor(ctx, orgID, startNs, endNs, key)
|
||||
args = append(args, fmt.Sprintf("toString(%s) != '', toString(%s)", fieldExpression, fieldExpression))
|
||||
}
|
||||
fieldExpression = fmt.Sprintf("multiIf(%s, NULL)", strings.Join(args, ", "))
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
schema "github.com/SigNoz/signoz-otel-collector/cmd/signozschemamigrator/schema_migrator"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -128,7 +129,7 @@ func TestGetColumn(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
col, err := fm.ColumnFor(context.Background(), 0, 0, &tc.key)
|
||||
col, err := fm.ColumnFor(context.Background(), valuer.UUID{}, 0, 0, &tc.key)
|
||||
|
||||
if tc.expectedError != nil {
|
||||
assert.Equal(t, tc.expectedError, err)
|
||||
@@ -205,7 +206,7 @@ func TestGetFieldKeyName(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result, err := fm.FieldFor(ctx, tc.tsStart, tc.tsEnd, &tc.key)
|
||||
result, err := fm.FieldFor(ctx, valuer.UUID{}, tc.tsStart, tc.tsEnd, &tc.key)
|
||||
|
||||
if tc.expectedError != nil {
|
||||
assert.Equal(t, tc.expectedError, err)
|
||||
|
||||
@@ -379,7 +379,7 @@ func (t *telemetryMetaStore) logsTblStatementToFieldKeys(ctx context.Context) ([
|
||||
}
|
||||
|
||||
// getLogsKeys returns the keys from the spans that match the field selection criteria.
|
||||
func (t *telemetryMetaStore) getLogsKeys(ctx context.Context, fieldKeySelectors []*telemetrytypes.FieldKeySelector) ([]*telemetrytypes.TelemetryFieldKey, bool, error) {
|
||||
func (t *telemetryMetaStore) getLogsKeys(ctx context.Context, orgID valuer.UUID, fieldKeySelectors []*telemetrytypes.FieldKeySelector) ([]*telemetrytypes.TelemetryFieldKey, bool, error) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalLogs.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "metadata",
|
||||
@@ -426,8 +426,7 @@ func (t *telemetryMetaStore) getLogsKeys(ctx context.Context, fieldKeySelectors
|
||||
}
|
||||
|
||||
// body keys are gated behind the feature flag
|
||||
// TODO(Tushar): thread orgID here to evaluate correctly
|
||||
queryBodyTable = queryBodyTable && t.fl.BooleanOrEmpty(ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(valuer.UUID{}))
|
||||
queryBodyTable = queryBodyTable && t.fl.BooleanOrEmpty(ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(orgID))
|
||||
|
||||
// requestedFieldKeySelectors is the set of names the user explicitly asked for.
|
||||
// Used to ensure a name that is both a parent path AND a directly requested field still surfaces
|
||||
@@ -687,8 +686,7 @@ func (t *telemetryMetaStore) getLogsKeys(ctx context.Context, fieldKeySelectors
|
||||
}
|
||||
|
||||
// enrich body keys with promoted paths, indexes, and JSON access plans
|
||||
// TODO(Tushar): thread orgID here to evaluate correctly
|
||||
if t.fl.BooleanOrEmpty(ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(valuer.UUID{})) {
|
||||
if t.fl.BooleanOrEmpty(ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(orgID)) {
|
||||
if err := t.enrichJSONKeys(ctx, fieldKeySelectors, keys, parentTypes); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
@@ -1215,7 +1213,7 @@ func matchesSelectorName(selectorName, target string, matchType telemetrytypes.F
|
||||
}
|
||||
}
|
||||
|
||||
func (t *telemetryMetaStore) GetKeys(ctx context.Context, fieldKeySelector *telemetrytypes.FieldKeySelector) (map[string][]*telemetrytypes.TelemetryFieldKey, bool, error) {
|
||||
func (t *telemetryMetaStore) GetKeys(ctx context.Context, orgID valuer.UUID, fieldKeySelector *telemetrytypes.FieldKeySelector) (map[string][]*telemetrytypes.TelemetryFieldKey, bool, error) {
|
||||
var keys []*telemetrytypes.TelemetryFieldKey
|
||||
var complete = true
|
||||
var err error
|
||||
@@ -1232,7 +1230,7 @@ func (t *telemetryMetaStore) GetKeys(ctx context.Context, fieldKeySelector *tele
|
||||
if fieldKeySelector.Source == telemetrytypes.SourceAudit {
|
||||
keys, complete, err = t.getAuditKeys(ctx, selectors)
|
||||
} else {
|
||||
keys, complete, err = t.getLogsKeys(ctx, selectors)
|
||||
keys, complete, err = t.getLogsKeys(ctx, orgID, selectors)
|
||||
}
|
||||
case telemetrytypes.SignalMetrics:
|
||||
if fieldKeySelector.Source == telemetrytypes.SourceMeter {
|
||||
@@ -1249,7 +1247,7 @@ func (t *telemetryMetaStore) GetKeys(ctx context.Context, fieldKeySelector *tele
|
||||
keys = append(keys, tracesKeys...)
|
||||
|
||||
// get logs keys
|
||||
logsKeys, logsComplete, err := t.getLogsKeys(ctx, selectors)
|
||||
logsKeys, logsComplete, err := t.getLogsKeys(ctx, orgID, selectors)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
@@ -1279,7 +1277,7 @@ func (t *telemetryMetaStore) GetKeys(ctx context.Context, fieldKeySelector *tele
|
||||
return mapOfKeys, complete, nil
|
||||
}
|
||||
|
||||
func (t *telemetryMetaStore) GetKeysMulti(ctx context.Context, fieldKeySelectors []*telemetrytypes.FieldKeySelector) (map[string][]*telemetrytypes.TelemetryFieldKey, bool, error) {
|
||||
func (t *telemetryMetaStore) GetKeysMulti(ctx context.Context, orgID valuer.UUID, fieldKeySelectors []*telemetrytypes.FieldKeySelector) (map[string][]*telemetrytypes.TelemetryFieldKey, bool, error) {
|
||||
|
||||
logsSelectors := []*telemetrytypes.FieldKeySelector{}
|
||||
auditSelectors := []*telemetrytypes.FieldKeySelector{}
|
||||
@@ -1310,7 +1308,7 @@ func (t *telemetryMetaStore) GetKeysMulti(ctx context.Context, fieldKeySelectors
|
||||
}
|
||||
}
|
||||
|
||||
logsKeys, logsComplete, err := t.getLogsKeys(ctx, logsSelectors)
|
||||
logsKeys, logsComplete, err := t.getLogsKeys(ctx, orgID, logsSelectors)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
@@ -1357,15 +1355,15 @@ func (t *telemetryMetaStore) GetKeysMulti(ctx context.Context, fieldKeySelectors
|
||||
return mapOfKeys, complete, nil
|
||||
}
|
||||
|
||||
func (t *telemetryMetaStore) GetKey(ctx context.Context, fieldKeySelector *telemetrytypes.FieldKeySelector) ([]*telemetrytypes.TelemetryFieldKey, error) {
|
||||
keys, _, err := t.GetKeys(ctx, fieldKeySelector)
|
||||
func (t *telemetryMetaStore) GetKey(ctx context.Context, orgID valuer.UUID, fieldKeySelector *telemetrytypes.FieldKeySelector) ([]*telemetrytypes.TelemetryFieldKey, error) {
|
||||
keys, _, err := t.GetKeys(ctx, orgID, fieldKeySelector)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return keys[fieldKeySelector.Name], nil
|
||||
}
|
||||
|
||||
func (t *telemetryMetaStore) getRelatedValues(ctx context.Context, fieldValueSelector *telemetrytypes.FieldValueSelector) ([]string, bool, error) {
|
||||
func (t *telemetryMetaStore) getRelatedValues(ctx context.Context, orgID valuer.UUID, fieldValueSelector *telemetrytypes.FieldValueSelector) ([]string, bool, error) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: fieldValueSelector.Signal.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "metadata",
|
||||
@@ -1384,18 +1382,18 @@ func (t *telemetryMetaStore) getRelatedValues(ctx context.Context, fieldValueSel
|
||||
FieldDataType: fieldValueSelector.FieldDataType,
|
||||
}
|
||||
|
||||
selectColumn, err := t.fm.FieldFor(ctx, 0, 0, key)
|
||||
selectColumn, err := t.fm.FieldFor(ctx, orgID, 0, 0, key)
|
||||
|
||||
if err != nil {
|
||||
// we don't have a explicit column to select from the related metadata table
|
||||
// so we will select either from resource_attributes or attributes table
|
||||
// in that order
|
||||
resourceColumn, _ := t.fm.FieldFor(ctx, 0, 0, &telemetrytypes.TelemetryFieldKey{
|
||||
resourceColumn, _ := t.fm.FieldFor(ctx, orgID, 0, 0, &telemetrytypes.TelemetryFieldKey{
|
||||
Name: key.Name,
|
||||
FieldContext: telemetrytypes.FieldContextResource,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
})
|
||||
attributeColumn, _ := t.fm.FieldFor(ctx, 0, 0, &telemetrytypes.TelemetryFieldKey{
|
||||
attributeColumn, _ := t.fm.FieldFor(ctx, orgID, 0, 0, &telemetrytypes.TelemetryFieldKey{
|
||||
Name: key.Name,
|
||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
@@ -1410,7 +1408,7 @@ func (t *telemetryMetaStore) getRelatedValues(ctx context.Context, fieldValueSel
|
||||
for _, keySelector := range keySelectors {
|
||||
keySelector.Signal = fieldValueSelector.Signal
|
||||
}
|
||||
keys, _, err := t.GetKeysMulti(ctx, keySelectors)
|
||||
keys, _, err := t.GetKeysMulti(ctx, orgID, keySelectors)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
@@ -1446,20 +1444,20 @@ func (t *telemetryMetaStore) getRelatedValues(ctx context.Context, fieldValueSel
|
||||
|
||||
// search on attributes
|
||||
key.FieldContext = telemetrytypes.FieldContextAttribute
|
||||
attrConds, _, err := t.conditionBuilder.ConditionFor(ctx, 0, 0, key, []*telemetrytypes.TelemetryFieldKey{key}, qbtypes.FilterOperatorContains, fieldValueSelector.Value, sb)
|
||||
attrConds, _, err := t.conditionBuilder.ConditionFor(ctx, orgID, 0, 0, key, []*telemetrytypes.TelemetryFieldKey{key}, qbtypes.FilterOperatorContains, fieldValueSelector.Value, sb)
|
||||
if err == nil {
|
||||
conds = append(conds, attrConds...)
|
||||
}
|
||||
|
||||
// search on resource
|
||||
key.FieldContext = telemetrytypes.FieldContextResource
|
||||
resourceConds, _, err := t.conditionBuilder.ConditionFor(ctx, 0, 0, key, []*telemetrytypes.TelemetryFieldKey{key}, qbtypes.FilterOperatorContains, fieldValueSelector.Value, sb)
|
||||
resourceConds, _, err := t.conditionBuilder.ConditionFor(ctx, orgID, 0, 0, key, []*telemetrytypes.TelemetryFieldKey{key}, qbtypes.FilterOperatorContains, fieldValueSelector.Value, sb)
|
||||
if err == nil {
|
||||
conds = append(conds, resourceConds...)
|
||||
}
|
||||
key.FieldContext = origContext
|
||||
} else {
|
||||
keyConds, _, err := t.conditionBuilder.ConditionFor(ctx, 0, 0, key, []*telemetrytypes.TelemetryFieldKey{key}, qbtypes.FilterOperatorContains, fieldValueSelector.Value, sb)
|
||||
keyConds, _, err := t.conditionBuilder.ConditionFor(ctx, orgID, 0, 0, key, []*telemetrytypes.TelemetryFieldKey{key}, qbtypes.FilterOperatorContains, fieldValueSelector.Value, sb)
|
||||
if err == nil {
|
||||
conds = append(conds, keyConds...)
|
||||
}
|
||||
@@ -1513,8 +1511,8 @@ func (t *telemetryMetaStore) getRelatedValues(ctx context.Context, fieldValueSel
|
||||
return attributeValues, complete, nil
|
||||
}
|
||||
|
||||
func (t *telemetryMetaStore) GetRelatedValues(ctx context.Context, fieldValueSelector *telemetrytypes.FieldValueSelector) ([]string, bool, error) {
|
||||
return t.getRelatedValues(ctx, fieldValueSelector)
|
||||
func (t *telemetryMetaStore) GetRelatedValues(ctx context.Context, orgID valuer.UUID, fieldValueSelector *telemetrytypes.FieldValueSelector) ([]string, bool, error) {
|
||||
return t.getRelatedValues(ctx, orgID, fieldValueSelector)
|
||||
}
|
||||
|
||||
func (t *telemetryMetaStore) getSpanFieldValues(ctx context.Context, fieldValueSelector *telemetrytypes.FieldValueSelector) (*telemetrytypes.TelemetryFieldValues, bool, error) {
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/types/metrictypes"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/huandu/go-sqlbuilder"
|
||||
)
|
||||
|
||||
@@ -45,6 +46,7 @@ func NewMeterQueryStatementBuilder(
|
||||
|
||||
func (b *meterQueryStatementBuilder) Build(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
start uint64,
|
||||
end uint64,
|
||||
_ qbtypes.RequestType,
|
||||
@@ -52,18 +54,19 @@ func (b *meterQueryStatementBuilder) Build(
|
||||
variables map[string]qbtypes.VariableItem,
|
||||
) (*qbtypes.Statement, error) {
|
||||
keySelectors := telemetrymetrics.GetKeySelectors(query)
|
||||
keys, _, err := b.metadataStore.GetKeysMulti(ctx, keySelectors)
|
||||
keys, _, err := b.metadataStore.GetKeysMulti(ctx, orgID, keySelectors)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
start, end = querybuilder.AdjustedMetricTimeRange(start, end, uint64(query.StepInterval.Seconds()), query)
|
||||
|
||||
return b.buildPipelineStatement(ctx, start, end, query, keys, variables)
|
||||
return b.buildPipelineStatement(ctx, orgID, start, end, query, keys, variables)
|
||||
}
|
||||
|
||||
func (b *meterQueryStatementBuilder) buildPipelineStatement(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
start, end uint64,
|
||||
query qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation],
|
||||
keys map[string][]*telemetrytypes.TelemetryFieldKey,
|
||||
@@ -76,7 +79,7 @@ func (b *meterQueryStatementBuilder) buildPipelineStatement(
|
||||
|
||||
if qbtypes.CanShortCircuitDelta(query.Aggregations[0]) {
|
||||
// spatial_aggregation_cte directly for certain delta queries
|
||||
if frag, args, err := b.buildTemporalAggDeltaFastPath(ctx, start, end, query, keys, variables); err != nil {
|
||||
if frag, args, err := b.buildTemporalAggDeltaFastPath(ctx, orgID, start, end, query, keys, variables); err != nil {
|
||||
return nil, err
|
||||
} else if frag != "" {
|
||||
cteFragments = append(cteFragments, frag)
|
||||
@@ -84,7 +87,7 @@ func (b *meterQueryStatementBuilder) buildPipelineStatement(
|
||||
}
|
||||
} else {
|
||||
// temporal_aggregation_cte
|
||||
if frag, args, err := b.buildTemporalAggregationCTE(ctx, start, end, query, keys, variables); err != nil {
|
||||
if frag, args, err := b.buildTemporalAggregationCTE(ctx, orgID, start, end, query, keys, variables); err != nil {
|
||||
return nil, err
|
||||
} else if frag != "" {
|
||||
cteFragments = append(cteFragments, frag)
|
||||
@@ -106,6 +109,7 @@ func (b *meterQueryStatementBuilder) buildPipelineStatement(
|
||||
|
||||
func (b *meterQueryStatementBuilder) buildTemporalAggDeltaFastPath(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
start, end uint64,
|
||||
query qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation],
|
||||
keys map[string][]*telemetrytypes.TelemetryFieldKey,
|
||||
@@ -122,7 +126,7 @@ func (b *meterQueryStatementBuilder) buildTemporalAggDeltaFastPath(
|
||||
stepSec,
|
||||
))
|
||||
for _, g := range query.GroupBy {
|
||||
col, err := b.fm.ColumnExpressionFor(ctx, start, end, &g.TelemetryFieldKey, keys)
|
||||
col, err := b.fm.ColumnExpressionFor(ctx, orgID, start, end, &g.TelemetryFieldKey, keys)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
@@ -148,6 +152,7 @@ func (b *meterQueryStatementBuilder) buildTemporalAggDeltaFastPath(
|
||||
if query.Filter != nil && query.Filter.Expression != "" {
|
||||
filterWhere, err = querybuilder.PrepareWhereClause(query.Filter.Expression, querybuilder.FilterExprVisitorOpts{
|
||||
Context: ctx,
|
||||
OrgID: orgID,
|
||||
Logger: b.logger,
|
||||
FieldMapper: b.fm,
|
||||
ConditionBuilder: b.cb,
|
||||
@@ -177,19 +182,21 @@ func (b *meterQueryStatementBuilder) buildTemporalAggDeltaFastPath(
|
||||
|
||||
func (b *meterQueryStatementBuilder) buildTemporalAggregationCTE(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
start, end uint64,
|
||||
query qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation],
|
||||
keys map[string][]*telemetrytypes.TelemetryFieldKey,
|
||||
variables map[string]qbtypes.VariableItem,
|
||||
) (string, []any, error) {
|
||||
if query.Aggregations[0].Temporality == metrictypes.Delta {
|
||||
return b.buildTemporalAggDelta(ctx, start, end, query, keys, variables)
|
||||
return b.buildTemporalAggDelta(ctx, orgID, start, end, query, keys, variables)
|
||||
}
|
||||
return b.buildTemporalAggCumulativeOrUnspecified(ctx, start, end, query, keys, variables)
|
||||
return b.buildTemporalAggCumulativeOrUnspecified(ctx, orgID, start, end, query, keys, variables)
|
||||
}
|
||||
|
||||
func (b *meterQueryStatementBuilder) buildTemporalAggDelta(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
start, end uint64,
|
||||
query qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation],
|
||||
keys map[string][]*telemetrytypes.TelemetryFieldKey,
|
||||
@@ -208,7 +215,7 @@ func (b *meterQueryStatementBuilder) buildTemporalAggDelta(
|
||||
))
|
||||
|
||||
for _, g := range query.GroupBy {
|
||||
col, err := b.fm.ColumnExpressionFor(ctx, start, end, &g.TelemetryFieldKey, keys)
|
||||
col, err := b.fm.ColumnExpressionFor(ctx, orgID, start, end, &g.TelemetryFieldKey, keys)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
@@ -237,6 +244,7 @@ func (b *meterQueryStatementBuilder) buildTemporalAggDelta(
|
||||
if query.Filter != nil && query.Filter.Expression != "" {
|
||||
filterWhere, err = querybuilder.PrepareWhereClause(query.Filter.Expression, querybuilder.FilterExprVisitorOpts{
|
||||
Context: ctx,
|
||||
OrgID: orgID,
|
||||
Logger: b.logger,
|
||||
FieldMapper: b.fm,
|
||||
ConditionBuilder: b.cb,
|
||||
@@ -268,6 +276,7 @@ func (b *meterQueryStatementBuilder) buildTemporalAggDelta(
|
||||
|
||||
func (b *meterQueryStatementBuilder) buildTemporalAggCumulativeOrUnspecified(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
start, end uint64,
|
||||
query qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation],
|
||||
keys map[string][]*telemetrytypes.TelemetryFieldKey,
|
||||
@@ -284,7 +293,7 @@ func (b *meterQueryStatementBuilder) buildTemporalAggCumulativeOrUnspecified(
|
||||
stepSec,
|
||||
))
|
||||
for _, g := range query.GroupBy {
|
||||
col, err := b.fm.ColumnExpressionFor(ctx, start, end, &g.TelemetryFieldKey, keys)
|
||||
col, err := b.fm.ColumnExpressionFor(ctx, orgID, start, end, &g.TelemetryFieldKey, keys)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
@@ -307,6 +316,7 @@ func (b *meterQueryStatementBuilder) buildTemporalAggCumulativeOrUnspecified(
|
||||
if query.Filter != nil && query.Filter.Expression != "" {
|
||||
filterWhere, err = querybuilder.PrepareWhereClause(query.Filter.Expression, querybuilder.FilterExprVisitorOpts{
|
||||
Context: ctx,
|
||||
OrgID: orgID,
|
||||
Logger: b.logger,
|
||||
FieldMapper: b.fm,
|
||||
ConditionBuilder: b.cb,
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes/telemetrytypestest"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -181,7 +182,7 @@ func TestStatementBuilder(t *testing.T) {
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
|
||||
q, err := statementBuilder.Build(context.Background(), 1747947419000, 1747983448000, c.requestType, c.query, nil)
|
||||
q, err := statementBuilder.Build(context.Background(), valuer.UUID{}, 1747947419000, 1747983448000, c.requestType, c.query, nil)
|
||||
|
||||
if c.expectedErr != nil {
|
||||
require.Error(t, err)
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/querybuilder"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
|
||||
"github.com/huandu/go-sqlbuilder"
|
||||
)
|
||||
@@ -23,6 +24,7 @@ func NewConditionBuilder(fm qbtypes.FieldMapper) *conditionBuilder {
|
||||
|
||||
func (c *conditionBuilder) conditionFor(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
startNs uint64,
|
||||
endNs uint64,
|
||||
key *telemetrytypes.TelemetryFieldKey,
|
||||
@@ -35,7 +37,7 @@ func (c *conditionBuilder) conditionFor(
|
||||
value = querybuilder.FormatValueForContains(value)
|
||||
}
|
||||
|
||||
fieldExpression, err := c.fm.FieldFor(ctx, startNs, endNs, key)
|
||||
fieldExpression, err := c.fm.FieldFor(ctx, orgID, startNs, endNs, key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -143,6 +145,7 @@ func (c *conditionBuilder) conditionFor(
|
||||
|
||||
func (c *conditionBuilder) ConditionFor(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
startNs uint64,
|
||||
endNs uint64,
|
||||
key *telemetrytypes.TelemetryFieldKey,
|
||||
@@ -168,7 +171,7 @@ func (c *conditionBuilder) ConditionFor(
|
||||
|
||||
conds := make([]string, 0, len(keys))
|
||||
for _, k := range keys {
|
||||
cond, err := c.conditionForKey(ctx, startNs, endNs, k, operator, value, sb)
|
||||
cond, err := c.conditionForKey(ctx, orgID, startNs, endNs, k, operator, value, sb)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -179,6 +182,7 @@ func (c *conditionBuilder) ConditionFor(
|
||||
|
||||
func (c *conditionBuilder) conditionForKey(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
startNs uint64,
|
||||
endNs uint64,
|
||||
key *telemetrytypes.TelemetryFieldKey,
|
||||
@@ -186,7 +190,7 @@ func (c *conditionBuilder) conditionForKey(
|
||||
value any,
|
||||
sb *sqlbuilder.SelectBuilder,
|
||||
) (string, error) {
|
||||
condition, err := c.conditionFor(ctx, startNs, endNs, key, operator, value, sb)
|
||||
condition, err := c.conditionFor(ctx, orgID, startNs, endNs, key, operator, value, sb)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/huandu/go-sqlbuilder"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -234,7 +235,7 @@ func TestConditionFor(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
sb := sqlbuilder.NewSelectBuilder()
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cond, _, err := conditionBuilder.ConditionFor(ctx, 0, 0, &tc.key, []*telemetrytypes.TelemetryFieldKey{&tc.key}, tc.operator, tc.value, sb)
|
||||
cond, _, err := conditionBuilder.ConditionFor(ctx, valuer.UUID{}, 0, 0, &tc.key, []*telemetrytypes.TelemetryFieldKey{&tc.key}, tc.operator, tc.value, sb)
|
||||
sb.Where(cond...)
|
||||
|
||||
if tc.expectedError != nil {
|
||||
@@ -289,7 +290,7 @@ func TestConditionForMultipleKeys(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var err error
|
||||
for _, key := range tc.keys {
|
||||
cond, _, err := conditionBuilder.ConditionFor(ctx, 0, 0, &key, []*telemetrytypes.TelemetryFieldKey{&key}, tc.operator, tc.value, sb)
|
||||
cond, _, err := conditionBuilder.ConditionFor(ctx, valuer.UUID{}, 0, 0, &key, []*telemetrytypes.TelemetryFieldKey{&key}, tc.operator, tc.value, sb)
|
||||
sb.Where(cond...)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting condition for key %s: %v", key.Name, err)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
schema "github.com/SigNoz/signoz-otel-collector/cmd/signozschemamigrator/schema_migrator"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/huandu/go-sqlbuilder"
|
||||
)
|
||||
|
||||
@@ -37,6 +38,12 @@ var (
|
||||
|
||||
type fieldMapper struct{}
|
||||
|
||||
// CandidateKeys returns nil: metrics has no attribute-map fallback, so a context-missing
|
||||
// key stays unresolved and the caller errors.
|
||||
func (m *fieldMapper) CandidateKeys(_ context.Context, _ valuer.UUID, _ *telemetrytypes.TelemetryFieldKey, _ any, _ map[string][]*telemetrytypes.TelemetryFieldKey) []*telemetrytypes.TelemetryFieldKey {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewFieldMapper() qbtypes.FieldMapper {
|
||||
return &fieldMapper{}
|
||||
}
|
||||
@@ -65,7 +72,7 @@ func (m *fieldMapper) getColumn(_ context.Context, _, _ uint64, key *telemetryty
|
||||
return nil, qbtypes.ErrColumnNotFound
|
||||
}
|
||||
|
||||
func (m *fieldMapper) FieldFor(ctx context.Context, startNs, endNs uint64, key *telemetrytypes.TelemetryFieldKey) (string, error) {
|
||||
func (m *fieldMapper) FieldFor(ctx context.Context, _ valuer.UUID, startNs, endNs uint64, key *telemetrytypes.TelemetryFieldKey) (string, error) {
|
||||
columns, err := m.getColumn(ctx, startNs, endNs, key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -86,18 +93,19 @@ func (m *fieldMapper) FieldFor(ctx context.Context, startNs, endNs uint64, key *
|
||||
return columns[0].Name, nil
|
||||
}
|
||||
|
||||
func (m *fieldMapper) ColumnFor(ctx context.Context, tsStart, tsEnd uint64, key *telemetrytypes.TelemetryFieldKey) ([]*schema.Column, error) {
|
||||
func (m *fieldMapper) ColumnFor(ctx context.Context, _ valuer.UUID, tsStart, tsEnd uint64, key *telemetrytypes.TelemetryFieldKey) ([]*schema.Column, error) {
|
||||
return m.getColumn(ctx, tsStart, tsEnd, key)
|
||||
}
|
||||
|
||||
func (m *fieldMapper) ColumnExpressionFor(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
startNs, endNs uint64,
|
||||
field *telemetrytypes.TelemetryFieldKey,
|
||||
keys map[string][]*telemetrytypes.TelemetryFieldKey,
|
||||
) (string, error) {
|
||||
|
||||
fieldExpression, err := m.FieldFor(ctx, startNs, endNs, field)
|
||||
fieldExpression, err := m.FieldFor(ctx, orgID, startNs, endNs, field)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
schema "github.com/SigNoz/signoz-otel-collector/cmd/signozschemamigrator/schema_migrator"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -123,7 +124,7 @@ func TestGetColumn(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
col, err := fm.ColumnFor(ctx, 0, 0, &tc.key)
|
||||
col, err := fm.ColumnFor(ctx, valuer.UUID{}, 0, 0, &tc.key)
|
||||
|
||||
if tc.expectedError != nil {
|
||||
assert.Equal(t, tc.expectedError, err)
|
||||
@@ -207,7 +208,7 @@ func TestGetFieldKeyName(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result, err := fm.FieldFor(ctx, 0, 0, &tc.key)
|
||||
result, err := fm.FieldFor(ctx, valuer.UUID{}, 0, 0, &tc.key)
|
||||
|
||||
if tc.expectedError != nil {
|
||||
assert.Equal(t, tc.expectedError, err)
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes/telemetrytypestest"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -127,7 +128,7 @@ func TestReducedStatementBuilder(t *testing.T) {
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
got, err := sb.Build(context.Background(), start, end, qbtypes.RequestTypeTimeSeries, c.query, nil)
|
||||
got, err := sb.Build(context.Background(), valuer.UUID{}, start, end, qbtypes.RequestTypeTimeSeries, c.query, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, c.expected.Query, got.Query)
|
||||
require.Equal(t, c.expected.Args, got.Args)
|
||||
@@ -137,7 +138,7 @@ func TestReducedStatementBuilder(t *testing.T) {
|
||||
t.Run("buffer_recent_window", func(t *testing.T) {
|
||||
now := time.Now().UnixMilli()
|
||||
q := reducedQuery("test.metric", metrictypes.GaugeType, metrictypes.Unspecified, metrictypes.TimeAggregationLatest, metrictypes.SpaceAggregationSum)
|
||||
got, err := sb.Build(context.Background(), uint64(now-2*time.Hour.Milliseconds()), uint64(now), qbtypes.RequestTypeTimeSeries, q, nil)
|
||||
got, err := sb.Build(context.Background(), valuer.UUID{}, uint64(now-2*time.Hour.Milliseconds()), uint64(now), qbtypes.RequestTypeTimeSeries, q, nil)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, got.Query, "signoz_metrics.distributed_samples_v4_buffer")
|
||||
require.Contains(t, got.Query, "signoz_metrics.time_series_v4_buffer")
|
||||
@@ -148,7 +149,7 @@ func TestReducedStatementBuilder(t *testing.T) {
|
||||
t.Run("not_reduced", func(t *testing.T) {
|
||||
q := reducedQuery("test.metric", metrictypes.GaugeType, metrictypes.Unspecified, metrictypes.TimeAggregationLatest, metrictypes.SpaceAggregationSum)
|
||||
q.Aggregations[0].Reduced = false
|
||||
got, err := sb.Build(context.Background(), start, end, qbtypes.RequestTypeTimeSeries, q, nil)
|
||||
got, err := sb.Build(context.Background(), valuer.UUID{}, start, end, qbtypes.RequestTypeTimeSeries, q, nil)
|
||||
require.NoError(t, err)
|
||||
require.NotContains(t, got.Query, "UNION ALL")
|
||||
require.NotContains(t, got.Query, "reduced")
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/types/metrictypes"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/huandu/go-sqlbuilder"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
@@ -90,6 +91,7 @@ func GetKeySelectors(query qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation])
|
||||
|
||||
func (b *MetricQueryStatementBuilder) Build(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
start uint64,
|
||||
end uint64,
|
||||
_ qbtypes.RequestType,
|
||||
@@ -97,7 +99,7 @@ func (b *MetricQueryStatementBuilder) Build(
|
||||
variables map[string]qbtypes.VariableItem,
|
||||
) (*qbtypes.Statement, error) {
|
||||
keySelectors := GetKeySelectors(query)
|
||||
keys, _, err := b.metadataStore.GetKeysMulti(ctx, keySelectors)
|
||||
keys, _, err := b.metadataStore.GetKeysMulti(ctx, orgID, keySelectors)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -121,11 +123,12 @@ func (b *MetricQueryStatementBuilder) Build(
|
||||
|
||||
start, end = querybuilder.AdjustedMetricTimeRange(start, end, uint64(query.StepInterval.Seconds()), query)
|
||||
|
||||
return b.buildPipelineStatement(ctx, start, end, query, keys, variables)
|
||||
return b.buildPipelineStatement(ctx, orgID, start, end, query, keys, variables)
|
||||
}
|
||||
|
||||
func (b *MetricQueryStatementBuilder) buildPipelineStatement(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
start, end uint64,
|
||||
query qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation],
|
||||
keys map[string][]*telemetrytypes.TelemetryFieldKey,
|
||||
@@ -195,7 +198,7 @@ func (b *MetricQueryStatementBuilder) buildPipelineStatement(
|
||||
var timeSeriesCTEArgs []any
|
||||
var err error
|
||||
|
||||
if timeSeriesCTE, timeSeriesCTEArgs, err = b.buildTimeSeriesCTE(ctx, tsStart, tsEnd, query, keys, variables, tsTable); err != nil {
|
||||
if timeSeriesCTE, timeSeriesCTEArgs, err = b.buildTimeSeriesCTE(ctx, orgID, tsStart, tsEnd, query, keys, variables, tsTable); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -228,7 +231,7 @@ func (b *MetricQueryStatementBuilder) buildPipelineStatement(
|
||||
if agg.Reduced && !useBuffer {
|
||||
var tsCTE string
|
||||
var tsArgs []any
|
||||
if tsCTE, tsArgs, err = b.buildReducedTimeSeriesCTE(ctx, start, end, query, keys, variables); err != nil {
|
||||
if tsCTE, tsArgs, err = b.buildReducedTimeSeriesCTE(ctx, orgID, start, end, query, keys, variables); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if temporalFrag, temporalArgs, ok := b.buildReducedTemporalAggregationCTE(start, end, query, tsCTE, tsArgs); ok {
|
||||
@@ -270,6 +273,7 @@ func unionStatements(main, reduced *qbtypes.Statement, query qbtypes.QueryBuilde
|
||||
|
||||
func (b *MetricQueryStatementBuilder) buildReducedTimeSeriesCTE(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
start, end uint64,
|
||||
query qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation],
|
||||
keys map[string][]*telemetrytypes.TelemetryFieldKey,
|
||||
@@ -282,6 +286,7 @@ func (b *MetricQueryStatementBuilder) buildReducedTimeSeriesCTE(
|
||||
if query.Filter != nil && query.Filter.Expression != "" {
|
||||
preparedWhereClause, err = querybuilder.PrepareWhereClause(query.Filter.Expression, querybuilder.FilterExprVisitorOpts{
|
||||
Context: ctx,
|
||||
OrgID: orgID,
|
||||
Logger: b.logger,
|
||||
FieldMapper: b.fm,
|
||||
ConditionBuilder: b.cb,
|
||||
@@ -299,7 +304,7 @@ func (b *MetricQueryStatementBuilder) buildReducedTimeSeriesCTE(
|
||||
sb.From(fmt.Sprintf("%s.%s", DBName, TimeseriesV4ReducedLocalTableName))
|
||||
sb.Select("fingerprint")
|
||||
for _, g := range query.GroupBy {
|
||||
col, err := b.fm.ColumnExpressionFor(ctx, start, end, &g.TelemetryFieldKey, keys)
|
||||
col, err := b.fm.ColumnExpressionFor(ctx, orgID, start, end, &g.TelemetryFieldKey, keys)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
@@ -454,6 +459,7 @@ func (b *MetricQueryStatementBuilder) buildTemporalAggDeltaFastPath(
|
||||
|
||||
func (b *MetricQueryStatementBuilder) buildTimeSeriesCTE(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
start, end uint64,
|
||||
query qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation],
|
||||
keys map[string][]*telemetrytypes.TelemetryFieldKey,
|
||||
@@ -468,6 +474,7 @@ func (b *MetricQueryStatementBuilder) buildTimeSeriesCTE(
|
||||
if query.Filter != nil && query.Filter.Expression != "" {
|
||||
preparedWhereClause, err = querybuilder.PrepareWhereClause(query.Filter.Expression, querybuilder.FilterExprVisitorOpts{
|
||||
Context: ctx,
|
||||
OrgID: orgID,
|
||||
Logger: b.logger,
|
||||
FieldMapper: b.fm,
|
||||
ConditionBuilder: b.cb,
|
||||
@@ -486,7 +493,7 @@ func (b *MetricQueryStatementBuilder) buildTimeSeriesCTE(
|
||||
|
||||
sb.Select("fingerprint")
|
||||
for _, g := range query.GroupBy {
|
||||
col, err := b.fm.ColumnExpressionFor(ctx, start, end, &g.TelemetryFieldKey, keys)
|
||||
col, err := b.fm.ColumnExpressionFor(ctx, orgID, start, end, &g.TelemetryFieldKey, keys)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes/telemetrytypestest"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -245,7 +246,7 @@ func TestStatementBuilder(t *testing.T) {
|
||||
},
|
||||
expected: qbtypes.Statement{
|
||||
Query: "WITH __temporal_aggregation_cte AS (SELECT ts, `k8s.statefulset.name`, multiIf(row_number() OVER rate_window = 1, nan, (per_series_value - lagInFrame(per_series_value, 1) OVER rate_window) < 0, per_series_value / (ts - lagInFrame(ts, 1) OVER rate_window), (per_series_value - lagInFrame(per_series_value, 1) OVER rate_window) / (ts - lagInFrame(ts, 1) OVER rate_window)) AS per_series_value FROM (SELECT fingerprint, toStartOfInterval(toDateTime(intDiv(unix_milli, 1000)), toIntervalSecond(30)) AS ts, `k8s.statefulset.name`, max(value) AS per_series_value FROM signoz_metrics.distributed_samples_v4 AS points INNER JOIN (SELECT fingerprint, JSONExtractString(labels, 'k8s.statefulset.name') AS `k8s.statefulset.name` FROM signoz_metrics.time_series_v4_6hrs WHERE metric_name IN (?) AND unix_milli >= ? AND unix_milli <= ? AND LOWER(temporality) LIKE LOWER(?) AND __normalized = ? AND JSONExtractString(labels, 'k8s.statefulset.name') = ? GROUP BY fingerprint, `k8s.statefulset.name`) AS filtered_time_series ON points.fingerprint = filtered_time_series.fingerprint WHERE metric_name IN (?) AND unix_milli >= ? AND unix_milli < ? GROUP BY fingerprint, ts, `k8s.statefulset.name` ORDER BY fingerprint, ts) WINDOW rate_window AS (PARTITION BY fingerprint ORDER BY fingerprint, ts)), __spatial_aggregation_cte AS (SELECT ts, `k8s.statefulset.name`, sum(per_series_value) AS value FROM __temporal_aggregation_cte WHERE isNaN(per_series_value) = ? GROUP BY ts, `k8s.statefulset.name`) SELECT * FROM __spatial_aggregation_cte ORDER BY `k8s.statefulset.name`, ts",
|
||||
Args: []any{"signoz_calls_total", uint64(1747936800000), uint64(1747983420000), "cumulative", false, "my-statefulset", "signoz_calls_total", uint64(1747947360000), uint64(1747983420000), 0},
|
||||
Args: []any{"signoz_calls_total", uint64(1747936800000), uint64(1747983420000), "cumulative", false, "my-statefulset", "signoz_calls_total", uint64(1747947360000), uint64(1747983420000), 0},
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
@@ -283,7 +284,7 @@ func TestStatementBuilder(t *testing.T) {
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
|
||||
q, err := statementBuilder.Build(context.Background(), 1747947419000, 1747983448000, c.requestType, c.query, nil)
|
||||
q, err := statementBuilder.Build(context.Background(), valuer.UUID{}, 1747947419000, 1747983448000, c.requestType, c.query, nil)
|
||||
|
||||
if c.expectedErr != nil {
|
||||
require.Error(t, err)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/querybuilder"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/huandu/go-sqlbuilder"
|
||||
)
|
||||
|
||||
@@ -15,7 +16,10 @@ type defaultConditionBuilder struct {
|
||||
fm qbtypes.FieldMapper
|
||||
}
|
||||
|
||||
var _ qbtypes.ConditionBuilder = (*defaultConditionBuilder)(nil)
|
||||
var (
|
||||
_ qbtypes.ConditionBuilder = (*defaultConditionBuilder)(nil)
|
||||
_ qbtypes.ResolvingConditionBuilder = (*defaultConditionBuilder)(nil)
|
||||
)
|
||||
|
||||
func NewConditionBuilder(fm qbtypes.FieldMapper) *defaultConditionBuilder {
|
||||
return &defaultConditionBuilder{fm: fm}
|
||||
@@ -43,8 +47,11 @@ func keyIndexFilter(key *telemetrytypes.TelemetryFieldKey) any {
|
||||
return fmt.Sprintf(`%%%s%%`, key.Name)
|
||||
}
|
||||
|
||||
// ConditionFor builds resource-fingerprint conditions from the caller's pre-matched
|
||||
// fieldKeysForName. Resolution and the build live in conditionsForKeys.
|
||||
func (b *defaultConditionBuilder) ConditionFor(
|
||||
ctx context.Context,
|
||||
_ valuer.UUID,
|
||||
startNs uint64,
|
||||
endNs uint64,
|
||||
key *telemetrytypes.TelemetryFieldKey,
|
||||
@@ -53,6 +60,37 @@ func (b *defaultConditionBuilder) ConditionFor(
|
||||
value any,
|
||||
sb *sqlbuilder.SelectBuilder,
|
||||
) ([]string, []string, error) {
|
||||
return b.conditionsForKeys(ctx, startNs, endNs, key, fieldKeysForName, op, value, sb)
|
||||
}
|
||||
|
||||
// ConditionForKeys owns key resolution from the full metadata map so the where-clause
|
||||
// visitor need not pre-resolve; SkipResourceFilter is not applicable here (the fingerprint
|
||||
// table only stores resource attributes). See qbtypes.ResolvingConditionBuilder.
|
||||
func (b *defaultConditionBuilder) ConditionForKeys(
|
||||
ctx context.Context,
|
||||
_ valuer.UUID,
|
||||
startNs uint64,
|
||||
endNs uint64,
|
||||
key *telemetrytypes.TelemetryFieldKey,
|
||||
keys map[string][]*telemetrytypes.TelemetryFieldKey,
|
||||
_ qbtypes.ConditionBuilderOptions,
|
||||
op qbtypes.FilterOperator,
|
||||
value any,
|
||||
sb *sqlbuilder.SelectBuilder,
|
||||
) ([]string, []string, error) {
|
||||
return b.conditionsForKeys(ctx, startNs, endNs, key, querybuilder.MatchingFieldKeys(key, keys), op, value, sb)
|
||||
}
|
||||
|
||||
func (b *defaultConditionBuilder) conditionsForKeys(
|
||||
ctx context.Context,
|
||||
startNs uint64,
|
||||
endNs uint64,
|
||||
key *telemetrytypes.TelemetryFieldKey,
|
||||
matches []*telemetrytypes.TelemetryFieldKey,
|
||||
op qbtypes.FilterOperator,
|
||||
value any,
|
||||
sb *sqlbuilder.SelectBuilder,
|
||||
) ([]string, []string, error) {
|
||||
|
||||
// has/hasAny/hasAll/hasToken are logs-body-only functions; they never apply to the
|
||||
// resource fingerprint table, so skip them (the main query still evaluates them).
|
||||
@@ -60,7 +98,7 @@ func (b *defaultConditionBuilder) ConditionFor(
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
keys, warning := querybuilder.ResolveKeys(key, fieldKeysForName)
|
||||
keys, warning := querybuilder.ResolveKeys(key, matches)
|
||||
var warnings []string
|
||||
if warning != "" {
|
||||
warnings = append(warnings, warning)
|
||||
@@ -97,7 +135,7 @@ func (b *defaultConditionBuilder) conditionForKey(
|
||||
// as we store resource values as string
|
||||
formattedValue := querybuilder.FormatValueForContains(value)
|
||||
|
||||
columns, err := b.fm.ColumnFor(ctx, startNs, endNs, key)
|
||||
columns, err := b.fm.ColumnFor(ctx, valuer.UUID{}, startNs, endNs, key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -113,7 +151,7 @@ func (b *defaultConditionBuilder) conditionForKey(
|
||||
keyIdxFilter := sb.Like(column.Name, keyIndexFilter(key))
|
||||
valueForIndexFilter := valueForIndexFilter(op, key, value)
|
||||
|
||||
fieldName, err := b.fm.FieldFor(ctx, startNs, endNs, key)
|
||||
fieldName, err := b.fm.FieldFor(ctx, valuer.UUID{}, startNs, endNs, key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user