Compare commits

..

6 Commits

Author SHA1 Message Date
nikhilmantri0902
16937b4093 chore: variable name update: 2026-06-03 12:37:52 +05:30
nikhilmantri0902
ec13802915 chore: combining two functions into a common flow 2026-06-03 11:51:30 +05:30
nikhilmantri0902
ca14a46471 chore: build fix 2026-06-03 11:14:17 +05:30
Nikhil Mantri
219f2965fe Merge branch 'main' into metricsExplorer/get_stats_fast_path 2026-06-03 10:51:25 +05:30
nikhilmantri0902
05e84b04cc chore: rearrange and readable 2026-05-26 18:16:35 +05:30
nikhilmantri0902
def414c7bb chore: added fastPath 2026-05-26 17:38:03 +05:30
40 changed files with 207 additions and 488 deletions

View File

@@ -190,7 +190,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz:
!!merge <<: *db-depend
image: signoz/signoz:v0.127.0
image: signoz/signoz:v0.126.1
ports:
- "8080:8080" # signoz port
# - "6060:6060" # pprof port
@@ -213,7 +213,7 @@ services:
retries: 3
otel-collector:
!!merge <<: *db-depend
image: signoz/signoz-otel-collector:v0.144.5
image: signoz/signoz-otel-collector:v0.144.4
entrypoint:
- /bin/sh
command:
@@ -241,7 +241,7 @@ services:
replicas: 3
signoz-telemetrystore-migrator:
!!merge <<: *db-depend
image: signoz/signoz-otel-collector:v0.144.5
image: signoz/signoz-otel-collector:v0.144.4
environment:
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_DSN=tcp://clickhouse:9000
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_CLUSTER=cluster

View File

@@ -117,7 +117,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz:
!!merge <<: *db-depend
image: signoz/signoz:v0.127.0
image: signoz/signoz:v0.126.1
ports:
- "8080:8080" # signoz port
volumes:
@@ -139,7 +139,7 @@ services:
retries: 3
otel-collector:
!!merge <<: *db-depend
image: signoz/signoz-otel-collector:v0.144.5
image: signoz/signoz-otel-collector:v0.144.4
entrypoint:
- /bin/sh
command:
@@ -167,7 +167,7 @@ services:
replicas: 3
signoz-telemetrystore-migrator:
!!merge <<: *db-depend
image: signoz/signoz-otel-collector:v0.144.5
image: signoz/signoz-otel-collector:v0.144.4
environment:
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_DSN=tcp://clickhouse:9000
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_CLUSTER=cluster

View File

@@ -181,7 +181,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz:
!!merge <<: *db-depend
image: signoz/signoz:${VERSION:-v0.127.0}
image: signoz/signoz:${VERSION:-v0.126.1}
container_name: signoz
ports:
- "8080:8080" # signoz port
@@ -204,7 +204,7 @@ services:
retries: 3
otel-collector:
!!merge <<: *db-depend
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.5}
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.4}
container_name: signoz-otel-collector
entrypoint:
- /bin/sh
@@ -229,7 +229,7 @@ services:
- "4318:4318" # OTLP HTTP receiver
signoz-telemetrystore-migrator:
!!merge <<: *db-depend
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.5}
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.4}
container_name: signoz-telemetrystore-migrator
environment:
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_DSN=tcp://clickhouse:9000

View File

@@ -109,7 +109,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz:
!!merge <<: *db-depend
image: signoz/signoz:${VERSION:-v0.127.0}
image: signoz/signoz:${VERSION:-v0.126.1}
container_name: signoz
ports:
- "8080:8080" # signoz port
@@ -132,7 +132,7 @@ services:
retries: 3
otel-collector:
!!merge <<: *db-depend
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.5}
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.4}
container_name: signoz-otel-collector
entrypoint:
- /bin/sh
@@ -157,7 +157,7 @@ services:
- "4318:4318" # OTLP HTTP receiver
signoz-telemetrystore-migrator:
!!merge <<: *db-depend
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.5}
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.4}
container_name: signoz-telemetrystore-migrator
environment:
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_DSN=tcp://clickhouse:9000

View File

@@ -349,7 +349,7 @@ function convertV5DataByType(
*/
// eslint-disable-next-line sonarjs/cognitive-complexity
export function convertV5ResponseToLegacy(
v5Response: SuccessResponse<MetricRangePayloadV5, QueryRangeRequestV5>,
v5Response: SuccessResponse<MetricRangePayloadV5>,
legendMap: Record<string, string>,
formatForWeb?: boolean,
): SuccessResponse<MetricRangePayloadV3> & { warning?: Warning } {
@@ -357,7 +357,7 @@ export function convertV5ResponseToLegacy(
const v5Data = payload?.data;
const aggregationPerQuery =
params?.compositeQuery?.queries
(params as QueryRangeRequestV5)?.compositeQuery?.queries
?.filter((query) => query.type === 'builder_query')
.reduce(
(acc, query) => {

View File

@@ -359,7 +359,8 @@ function CustomTimePickerPopoverContent({
<Clock
color={Color.BG_ROBIN_400}
className="timezone-container__clock-icon"
size={14}
height={12}
width={12}
/>
<span className="timezone__name">{timezone.name}</span>

View File

@@ -109,16 +109,6 @@ $custom-border-color: #2c3044;
color: color-mix(in srgb, var(--l2-foreground) 45%, transparent);
}
.ant-select-clear {
background-color: var(--l2-background);
color: var(--l2-foreground);
font-size: 12px;
&:hover {
color: var(--l1-foreground);
}
}
// Customize tags in multiselect (dark mode by default)
.ant-select-selection-item {
background-color: var(--l1-border);
@@ -402,9 +392,7 @@ $custom-border-color: #2c3044;
// Custom dropdown styles for multi-select
.custom-multiselect-dropdown {
padding: 8px 0 0 0;
// Tall enough to hold the react-virtuoso list (<=300px) + header/footer
// so only the list scrolls (avoids a second scrollbar on this container).
max-height: 410px;
max-height: 350px;
overflow-y: auto;
overflow-x: hidden;
scrollbar-width: thin;
@@ -495,12 +483,8 @@ $custom-border-color: #2c3044;
.option-checkbox {
width: 100%;
// @signozhq/ui Checkbox renders children inside a <label> that is
// content-sized by default. Make it fill the row (min-width: 0 lets it
// shrink) so the option text below can truncate instead of overflowing.
> label {
flex: 1 1 auto;
min-width: 0;
> span:not(.ant-checkbox) {
width: 100%;
}
.all-option-text {
@@ -517,12 +501,7 @@ $custom-border-color: #2c3044;
width: 100%;
.option-label-text {
flex: 1 1 auto;
min-width: 0;
margin-bottom: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.option-badge {
@@ -535,30 +514,26 @@ $custom-border-color: #2c3044;
}
}
// Size the buttons to the row's resting content height (20px) and fully
// override antd's default 32px Button box, so revealing them on hover
// never changes the row height.
.only-btn,
.only-btn {
display: none;
}
.toggle-btn {
display: none;
align-items: center;
justify-content: center;
height: 18px;
min-height: 0;
padding: 0 6px;
font-size: 12px;
line-height: 1;
border: none;
box-shadow: none;
}
&:hover {
background-color: unset;
}
.only-btn:hover {
background-color: unset;
}
.toggle-btn:hover {
background-color: unset;
}
.option-content:hover {
.only-btn {
display: flex;
align-items: center;
justify-content: center;
height: 21px;
}
.toggle-btn {
display: none;
@@ -573,6 +548,9 @@ $custom-border-color: #2c3044;
.option-checkbox:hover {
.toggle-btn {
display: flex;
align-items: center;
justify-content: center;
height: 21px;
}
.option-badge {
display: none;

View File

@@ -67,8 +67,8 @@
gap: 4px;
&--success {
background: color-mix(in srgb, var(--text-forest-500) 10%, transparent);
color: var(--text-forest-400);
background: color-mix(in srgb, var(--success-background) 10%, transparent);
color: var(--success-foreground);
}
&--error {

View File

@@ -153,7 +153,6 @@
font-size: 10px;
color: var(--l2-foreground);
margin-top: 4px;
display: block;
}
}

View File

@@ -47,10 +47,18 @@
}
.ant-tabs-tab-active {
.overview-btn,
.variables-btn,
.overview-btn {
border-radius: 2px 0px 0px 2px;
background: var(--l1-border);
}
.variables-btn {
border-radius: 2px 0px 0px 2px;
background: var(--l1-border);
}
.public-dashboard-btn {
color: var(--primary-background);
border-radius: 2px 0px 0px 2px;
background: var(--l1-border);
}
}

View File

@@ -127,15 +127,6 @@
align-items: center;
justify-content: center;
gap: 4px;
.sidenav-beta-tag {
margin-left: 4px;
}
div {
display: flex;
align-items: center;
}
}
.variable-type-btn + .variable-type-btn {
@@ -186,7 +177,6 @@
.multiple-values-section {
justify-content: space-between;
align-items: flex-start;
margin-bottom: 0;
.typography-variables {
@@ -203,7 +193,6 @@
.all-option-section {
justify-content: space-between;
align-items: flex-start;
margin-bottom: 0;
.typography-variables {

View File

@@ -518,6 +518,7 @@ function VariableItem({
size={14}
style={{
color: isDarkMode ? Color.BG_VANILLA_100 : Color.BG_INK_500,
marginTop: 1,
}}
/>
}
@@ -613,6 +614,7 @@ function VariableItem({
size={14}
style={{
color: isDarkMode ? Color.BG_VANILLA_100 : Color.BG_INK_500,
marginTop: 1,
}}
/>
}

View File

@@ -1,7 +1,8 @@
import { renderHook } from '@testing-library/react';
import { UseQueryResult } from 'react-query';
import { SuccessResponse } from 'types/api';
import { Widgets } from 'types/api/dashboard/getAll';
import { MetricQueryRangeSuccessResponse } from 'types/api/metrics/getQueryRange';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { usePanelContextMenu } from '../usePanelContextMenu';
@@ -46,7 +47,10 @@ const mockWidget = { id: 'w-1', query: {} } as unknown as Widgets;
const mockQueryResponse = {
data: undefined,
isLoading: false,
} as unknown as UseQueryResult<MetricQueryRangeSuccessResponse, Error>;
} as unknown as UseQueryResult<
SuccessResponse<MetricRangePayloadProps, unknown>,
Error
>;
describe('usePanelContextMenu', () => {
beforeEach(() => {

View File

@@ -10,13 +10,17 @@ import {
PopoverPosition,
useCoordinates,
} from 'periscope/components/ContextMenu';
import { SuccessResponse } from 'types/api';
import { Widgets } from 'types/api/dashboard/getAll';
import { MetricQueryRangeSuccessResponse } from 'types/api/metrics/getQueryRange';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { DataSource } from 'types/common/queryBuilder';
interface UseTimeSeriesContextMenuParams {
widget: Widgets;
queryResponse: UseQueryResult<MetricQueryRangeSuccessResponse, Error>;
queryResponse: UseQueryResult<
SuccessResponse<MetricRangePayloadProps, unknown>,
Error
>;
enableDrillDown?: boolean;
}

View File

@@ -19,7 +19,6 @@
}
.ant-btn-default {
border-color: transparent;
box-shadow: none;
}
}
.ant-tabs-tab-active {

View File

@@ -5,11 +5,9 @@ import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
import { RowData } from 'lib/query/createTableColumnsFromQuery';
import { OnClickPluginOpts } from 'lib/uPlotLib/plugins/onClickPlugin';
import { IDashboardVariables } from 'providers/Dashboard/store/dashboardVariables/dashboardVariablesStoreTypes';
import { SuccessResponse } from 'types/api';
import { Widgets } from 'types/api/dashboard/getAll';
import {
MetricQueryRangeSuccessResponse,
MetricRangePayloadProps,
} from 'types/api/metrics/getQueryRange';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { QueryData } from 'types/api/widgets/getQuery';
import uPlot from 'uplot';
@@ -23,7 +21,10 @@ export interface GraphVisibilityLegendEntryProps {
export interface WidgetGraphComponentProps {
widget: Widgets;
queryResponse: UseQueryResult<MetricQueryRangeSuccessResponse, Error>;
queryResponse: UseQueryResult<
SuccessResponse<MetricRangePayloadProps, unknown>,
Error
>;
errorMessage: string | undefined;
version?: string;
threshold?: ReactNode;

View File

@@ -1,7 +1,8 @@
import { UseQueryResult } from 'react-query';
import { ThresholdProps } from 'container/NewWidget/RightContainer/Threshold/types';
import { SuccessResponse } from 'types/api';
import { ContextLinksData, Widgets } from 'types/api/dashboard/getAll';
import { MetricQueryRangeSuccessResponse } from 'types/api/metrics/getQueryRange';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import uPlot from 'uplot';
export type GridValueComponentProps = {
@@ -12,7 +13,10 @@ export type GridValueComponentProps = {
thresholds?: ThresholdProps[];
// Context menu related props
widget?: Widgets;
queryResponse?: UseQueryResult<MetricQueryRangeSuccessResponse, Error>;
queryResponse?: UseQueryResult<
SuccessResponse<MetricRangePayloadProps, unknown>,
Error
>;
contextLinks?: ContextLinksData;
enableDrillDown?: boolean;
};

View File

@@ -28,8 +28,9 @@ import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
import GetMinMax from 'lib/getMinMax';
import getTimeString from 'lib/getTimeString';
import { UpdateTimeInterval } from 'store/actions';
import { SuccessResponse } from 'types/api';
import { Widgets } from 'types/api/dashboard/getAll';
import { MetricQueryRangeSuccessResponse } from 'types/api/metrics/getQueryRange';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { DataSource } from 'types/common/queryBuilder';
function WidgetGraph({
@@ -201,7 +202,10 @@ function WidgetGraph({
interface WidgetGraphProps {
selectedWidget: Widgets;
queryResponse: UseQueryResult<MetricQueryRangeSuccessResponse, Error>;
queryResponse: UseQueryResult<
SuccessResponse<MetricRangePayloadProps, unknown>,
Error
>;
setRequestData: Dispatch<SetStateAction<GetQueryResultsProps>>;
selectedGraph: PANEL_TYPES;
enableDrillDown?: boolean;

View File

@@ -1,12 +1,11 @@
import { QueryRangeRequestV5 } from 'api/v5/v5';
import { SuccessResponse } from 'types/api';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { Column, QueryData, QueryDataV3 } from 'types/api/widgets/getQuery';
// eslint-disable-next-line sonarjs/cognitive-complexity
export function populateMultipleResults(
responseData: SuccessResponse<MetricRangePayloadProps, QueryRangeRequestV5>,
): SuccessResponse<MetricRangePayloadProps, QueryRangeRequestV5> {
responseData: SuccessResponse<MetricRangePayloadProps, unknown>,
): SuccessResponse<MetricRangePayloadProps, unknown> {
const queryResults = responseData?.payload?.data?.newResult?.data?.result;
const allFormattedResults: QueryData[] = [];
@@ -67,19 +66,17 @@ export function populateMultipleResults(
}
// Create a copy instead of mutating the original
const updatedResponseData: SuccessResponse<
MetricRangePayloadProps,
QueryRangeRequestV5
> = {
...responseData,
payload: {
...responseData.payload,
data: {
...responseData.payload.data,
result: allFormattedResults,
const updatedResponseData: SuccessResponse<MetricRangePayloadProps, unknown> =
{
...responseData,
payload: {
...responseData.payload,
data: {
...responseData.payload.data,
result: allFormattedResults,
},
},
},
};
};
return updatedResponseData;
}

View File

@@ -52,6 +52,7 @@ import {
getSelectedWidgetIndex,
} from 'providers/Dashboard/util';
import { AppState } from 'store/reducers';
import { SuccessResponse } from 'types/api';
import {
ColumnUnit,
ContextLinksData,
@@ -60,7 +61,7 @@ import {
} from 'types/api/dashboard/getAll';
import { Props } from 'types/api/dashboard/update';
import { IField } from 'types/api/logs/fields';
import { MetricQueryRangeSuccessResponse } from 'types/api/metrics/getQueryRange';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { EQueryType } from 'types/common/dashboard';
import { DataSource } from 'types/common/queryBuilder';
import { GlobalReducer } from 'types/reducer/globalTime';
@@ -397,7 +398,7 @@ function NewWidget({
// State to hold query response for sharing between left and right containers
const [queryResponse, setQueryResponse] = useState<
UseQueryResult<MetricQueryRangeSuccessResponse, Error>
UseQueryResult<SuccessResponse<MetricRangePayloadProps, unknown>, Error>
>(null as any);
// request data should be handled by the parent and the child components should consume the same

View File

@@ -2,8 +2,9 @@ import { Dispatch, SetStateAction } from 'react';
import { UseQueryResult } from 'react-query';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
import { SuccessResponse, Warning } from 'types/api';
import { Dashboard, Widgets } from 'types/api/dashboard/getAll';
import { MetricQueryRangeSuccessResponse } from 'types/api/metrics/getQueryRange';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { timePreferance } from './RightContainer/timeItems';
@@ -28,7 +29,9 @@ export interface WidgetGraphProps {
setRequestData: Dispatch<SetStateAction<GetQueryResultsProps>>;
isLoadingPanelData: boolean;
setQueryResponse?: Dispatch<
SetStateAction<UseQueryResult<MetricQueryRangeSuccessResponse, Error>>
SetStateAction<
UseQueryResult<SuccessResponse<MetricRangePayloadProps, unknown>, Error>
>
>;
enableDrillDown?: boolean;
dashboardData: Dashboard | undefined;
@@ -36,7 +39,12 @@ export interface WidgetGraphProps {
}
export type WidgetGraphContainerProps = {
queryResponse: UseQueryResult<MetricQueryRangeSuccessResponse, Error>;
queryResponse: UseQueryResult<
SuccessResponse<MetricRangePayloadProps, unknown> & {
warning?: Warning;
},
Error
>;
setRequestData: Dispatch<SetStateAction<GetQueryResultsProps>>;
selectedGraph: PANEL_TYPES;
selectedWidget: Widgets;

View File

@@ -1,6 +1,7 @@
import { PANEL_TYPES } from 'constants/queryBuilder';
import GridTableComponent from 'container/GridTableComponent';
import { GRID_TABLE_CONFIG } from 'container/GridTableComponent/config';
import { QueryRangeRequestV5 } from 'types/api/v5/queryRange';
import { PanelWrapperProps } from './panelWrapper.types';
@@ -19,7 +20,7 @@ function TablePanelWrapper({
(queryResponse.data?.payload?.data?.result?.[0] as any)?.table || [];
const { thresholds } = widget;
const queryRangeRequest = queryResponse.data?.params;
const queryRangeRequest = queryResponse.data?.params as QueryRangeRequestV5;
return (
<GridTableComponent

View File

@@ -72,20 +72,8 @@
.alert-rule-scope {
margin-bottom: 12px;
// `.createForm label` styles field labels (font-weight 500, 14px,
// 6px bottom padding). Those bleed into the @signozhq/ui RadioGroup
// option labels, making them bold and vertically misaligned with the
// radio control. Reset them back to plain option-text styling.
label {
padding: 0;
font-weight: 400;
line-height: normal;
}
// Loosen the design-system default (grid gap 0.5rem) between options.
.silence-alerts-radio-group {
margin-top: 8px;
gap: 12px;
.ant-radio-wrapper {
color: var(--l1-foreground);
}
}
@@ -156,7 +144,10 @@
display: flex;
align-items: center;
gap: 8px;
margin-top: 12px;
.ant-btn {
margin-top: 8px;
}
}
.schedule-created-at {

View File

@@ -54,7 +54,8 @@ import {
} from './PlannedDowntimeutils';
import './PlannedDowntime.styles.scss';
import { RadioGroupItem, RadioGroup } from '@signozhq/ui/radio-group';
import { RadioGroupItem } from '@signozhq/ui/radio-group';
import { RadioGroup } from '@signozhq/ui/radio-group';
dayjs.locale('en');
dayjs.extend(utc);
@@ -470,7 +471,7 @@ export function PlannedDowntimeForm(
initialValue="specific"
className="alert-rule-scope"
>
<RadioGroup className="silence-alerts-radio-group">
<RadioGroup>
<RadioGroupItem value="all">All alert rules</RadioGroupItem>
<RadioGroupItem value="specific">Specific alert rules</RadioGroupItem>
</RadioGroup>

View File

@@ -2,8 +2,9 @@ import { useMemo } from 'react';
import { UseQueryResult } from 'react-query';
import { PANEL_TYPES } from 'constants/queryBuilder';
import useDashboardVarConfig from 'container/QueryTable/Drilldown/useDashboardVarConfig';
import { SuccessResponse } from 'types/api';
import { ContextLinksData } from 'types/api/dashboard/getAll';
import { MetricQueryRangeSuccessResponse } from 'types/api/metrics/getQueryRange';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { getTimeRange } from 'utils/getTimeRange';
@@ -43,7 +44,10 @@ const useAggregateDrilldown = ({
aggregateData: AggregateData | null;
contextLinks?: ContextLinksData;
panelType?: PANEL_TYPES;
queryRange?: UseQueryResult<MetricQueryRangeSuccessResponse, Error>;
queryRange?: UseQueryResult<
SuccessResponse<MetricRangePayloadProps, unknown>,
Error
>;
}): {
aggregateDrilldownConfig: {
header?: string | React.ReactNode;

View File

@@ -2,8 +2,9 @@ import { useMemo } from 'react';
import { UseQueryResult } from 'react-query';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { useGetCompositeQueryParam } from 'hooks/queryBuilder/useGetCompositeQueryParam';
import { SuccessResponse } from 'types/api';
import { ContextLinksData } from 'types/api/dashboard/getAll';
import { MetricQueryRangeSuccessResponse } from 'types/api/metrics/getQueryRange';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { isValidQueryName } from './drilldownUtils';
@@ -19,7 +20,10 @@ interface UseGraphContextMenuProps {
setSubMenu: (subMenu: string) => void;
contextLinks?: ContextLinksData;
panelType?: PANEL_TYPES;
queryRange?: UseQueryResult<MetricQueryRangeSuccessResponse, Error>;
queryRange?: UseQueryResult<
SuccessResponse<MetricRangePayloadProps, unknown>,
Error
>;
}
export function useGraphContextMenu({

View File

@@ -35,7 +35,10 @@
display: flex;
align-items: center;
gap: 8px;
margin-top: 12px;
.ant-btn {
margin-top: 8px;
}
}
.routing-policies-table {

View File

@@ -8,7 +8,7 @@
139deg,
color-mix(in srgb, var(--card) 80%, transparent) 0%,
color-mix(in srgb, var(--card) 90%, transparent) 98.68%
) !important;
);
box-shadow: 4px 10px 16px 2px rgba(0, 0, 0, 0.2);
backdrop-filter: blur(20px);
padding: 0;
@@ -34,9 +34,9 @@
}
.refresh-interval-text {
padding: 12px 14px 8px 14px !important;
padding: 12px 14px 8px 14px;
color: var(--muted-foreground);
font-size: 13px;
font-size: 11px;
font-style: normal;
font-weight: 500;
line-height: 18px; /* 163.636% */

View File

@@ -7,7 +7,8 @@ import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
import { AppState } from 'store/reducers';
import { MetricQueryRangeSuccessResponse } from 'types/api/metrics/getQueryRange';
import { SuccessResponse, Warning } from 'types/api';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { GlobalReducer } from 'types/reducer/globalTime';
@@ -18,13 +19,16 @@ export const useGetExplorerQueryRange = (
requestData: Query | null,
panelType: PANEL_TYPES | null,
version: string,
options?: UseQueryOptions<MetricQueryRangeSuccessResponse, Error>,
options?: UseQueryOptions<SuccessResponse<MetricRangePayloadProps>, Error>,
params?: Record<string, unknown>,
isDependentOnQB = true,
keyRef?: MutableRefObject<any>,
headers?: Record<string, string>,
selectedTimeInterval?: GetQueryResultsProps['globalSelectedInterval'],
): UseQueryResult<MetricQueryRangeSuccessResponse, Error> => {
): UseQueryResult<
SuccessResponse<MetricRangePayloadProps> & { warning?: Warning },
Error
> => {
const { isEnabledQuery } = useQueryBuilder();
const {
selectedTime: globalSelectedInterval,

View File

@@ -9,7 +9,7 @@
}
.ant-tabs-nav {
padding-left: 16px;
padding: 0 8px;
margin-bottom: 0px;
&::before {

View File

@@ -8,7 +8,7 @@ import {
IClickHouseQuery,
IPromQLQuery,
} from '../queryBuilder/queryBuilderData';
import { ExecStats, QueryRangeRequestV5 } from '../v5/queryRange';
import { ExecStats } from '../v5/queryRange';
import { QueryData, QueryDataV3 } from '../widgets/getQuery';
export type QueryRangePayload = {
@@ -39,16 +39,11 @@ export interface MetricRangePayloadProps {
meta?: ExecStats;
}
/** Query range success response. `params` is the request that produced the
* payload; `warning` and `meta` are lifted from the payload to the top level
* by `getQueryResults.ts` for consumer convenience. */
export interface MetricQueryRangeSuccessResponse extends SuccessResponse<
/** Query range success response including optional warning and meta */
export type MetricQueryRangeSuccessResponse = SuccessResponse<
MetricRangePayloadProps,
QueryRangeRequestV5
> {
warning?: Warning;
meta?: ExecStats;
}
unknown
> & { warning?: Warning; meta?: ExecStats };
export interface MetricRangePayloadV3 {
data: {

View File

@@ -1,7 +1,8 @@
import { UseQueryResult } from 'react-query';
import getStartEndRangeTime from 'lib/getStartEndRangeTime';
import store from 'store';
import { MetricQueryRangeSuccessResponse } from 'types/api/metrics/getQueryRange';
import { SuccessResponse } from 'types/api';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { QueryRangeRequestV5 } from 'types/api/v5/queryRange';
export const getTimeRangeFromQueryRangeRequest = (
@@ -27,9 +28,13 @@ export const getTimeRangeFromQueryRangeRequest = (
};
export const getTimeRange = (
widgetQueryRange?: UseQueryResult<MetricQueryRangeSuccessResponse, Error>,
widgetQueryRange?: UseQueryResult<
SuccessResponse<MetricRangePayloadProps, unknown>,
Error
>,
): Record<string, number> => {
const widgetParams = widgetQueryRange?.data?.params;
const widgetParams =
(widgetQueryRange?.data?.params as QueryRangeRequestV5) || null;
return getTimeRangeFromQueryRangeRequest(widgetParams);
};

View File

@@ -219,19 +219,7 @@ func (m *module) GetStats(ctx context.Context, orgID valuer.UUID, req *metricsex
return nil, err
}
filterWhereClause, err := m.buildFilterClause(ctx, req.Filter, req.Start, req.End)
if err != nil {
return nil, err
}
// Single query to get stats with samples, timeseries counts in required sorting order
metricStats, total, err := m.fetchMetricsStatsWithSamples(
ctx,
req,
filterWhereClause,
false,
req.OrderBy,
)
metricStats, total, err := m.fetchMetricsStatsWithSamples(ctx, req, false)
if err != nil {
return nil, err
}
@@ -974,15 +962,23 @@ func (m *module) buildFilterClause(ctx context.Context, filter *qbtypes.Filter,
func (m *module) fetchMetricsStatsWithSamples(
ctx context.Context,
req *metricsexplorertypes.StatsRequest,
filterWhereClause *sqlbuilder.WhereClause,
normalized bool,
orderBy *qbtypes.OrderBy,
) ([]metricsexplorertypes.Stat, uint64, error) {
ctx = m.withMetricsExplorerContext(ctx, "fetchMetricsStatsWithSamples")
hasFilter := req.Filter != nil && strings.TrimSpace(req.Filter.Expression) != ""
var filterWhereClause *sqlbuilder.WhereClause
if hasFilter {
var err error
filterWhereClause, err = m.buildFilterClause(ctx, req.Filter, req.Start, req.End)
if err != nil {
return nil, 0, err
}
}
start, end, distributedTsTable, localTsTable := telemetrymetrics.WhichTSTableToUse(uint64(req.Start), uint64(req.End), nil)
samplesTable, _ := telemetrymetrics.WhichSamplesTableToUse(uint64(req.Start), uint64(req.End), metrictypes.UnspecifiedType, metrictypes.TimeAggregationUnspecified, nil)
countExp := telemetrymetrics.CountExpressionForSamplesTable(samplesTable)
distributedSamplesTable, _ := telemetrymetrics.WhichSamplesTableToUse(uint64(req.Start), uint64(req.End), metrictypes.UnspecifiedType, metrictypes.TimeAggregationUnspecified, nil)
countExp := telemetrymetrics.CountExpressionForSamplesTable(distributedSamplesTable)
// Timeseries counts per metric
tsSB := sqlbuilder.NewSelectBuilder()
@@ -1005,7 +1001,7 @@ func (m *module) fetchMetricsStatsWithSamples(
"metric_name",
fmt.Sprintf("%s AS samples", countExp),
)
samplesSB.From(fmt.Sprintf("%s.%s", telemetrymetrics.DBName, samplesTable))
samplesSB.From(fmt.Sprintf("%s.%s", telemetrymetrics.DBName, distributedSamplesTable))
samplesSB.Where(samplesSB.Between("unix_milli", req.Start, req.End))
samplesSB.Where("NOT startsWith(metric_name, 'signoz')")
@@ -1013,6 +1009,8 @@ func (m *module) fetchMetricsStatsWithSamples(
sqlbuilder.CTEQuery("__time_series_counts").As(tsSB),
}
// Narrow samples scan. With filter: fingerprint IN (per-fingerprint label preds can't fold to metric_name).
// No filter (fast path): metric_name IN — aligns with samples table's leading sort key, orders of magnitude cheaper.
if filterWhereClause != nil {
fingerprintSB := sqlbuilder.NewSelectBuilder()
fingerprintSB.Select("fingerprint")
@@ -1025,6 +1023,15 @@ func (m *module) fetchMetricsStatsWithSamples(
ctes = append(ctes, sqlbuilder.CTEQuery("__filtered_fingerprints").As(fingerprintSB))
samplesSB.Where("fingerprint IN (SELECT fingerprint FROM __filtered_fingerprints)")
} else {
metricNamesSB := sqlbuilder.NewSelectBuilder()
metricNamesSB.Select("DISTINCT metric_name")
metricNamesSB.From(fmt.Sprintf("%s.%s", telemetrymetrics.DBName, localTsTable))
metricNamesSB.Where(metricNamesSB.Between("unix_milli", start, end))
metricNamesSB.Where("NOT startsWith(metric_name, 'signoz')")
metricNamesSB.Where(metricNamesSB.E("__normalized", normalized))
samplesSB.Where(fmt.Sprintf("metric_name IN (%s)", samplesSB.Var(metricNamesSB)))
}
samplesSB.GroupBy("metric_name")
@@ -1041,7 +1048,7 @@ func (m *module) fetchMetricsStatsWithSamples(
finalSB.JoinWithOption(sqlbuilder.FullOuterJoin, "__sample_counts s", "ts.metric_name = s.metric_name")
finalSB.Where("(COALESCE(ts.timeseries, 0) > 0 OR COALESCE(s.samples, 0) > 0)")
orderByColumn, orderDirection, err := getStatsOrderByColumn(orderBy)
orderByColumn, orderDirection, err := getStatsOrderByColumn(req.OrderBy)
if err != nil {
return nil, 0, err
}

View File

@@ -54,7 +54,7 @@ func (c Config) Validate() error {
if c.MaxConcurrentQueries <= 0 {
return errors.NewInvalidInputf(errors.CodeInvalidInput, "max_concurrent_queries must be positive, got %v", c.MaxConcurrentQueries)
}
if c.SkipResourceFingerprint.Enabled && c.SkipResourceFingerprint.Threshold <= 0 {
if c.SkipResourceFingerprint.Enabled && c.SkipResourceFingerprint.Threshold == 0 {
return errors.NewInvalidInputf(errors.CodeInvalidInput, "skip_resource_fingerprint.threshold must be > 0 when enabled")
}
return nil

View File

@@ -716,22 +716,6 @@ func aggOrderBy(k qbtypes.OrderBy, q qbtypes.QueryBuilderQuery[qbtypes.LogAggreg
return 0, false
}
// maybeAttachResourceFilter decides whether to pre-filter on the resource table.
//
// The resource table maps resource attributes (e.g. service.name) to fingerprints.
// When it helps, we look up the matching fingerprints there first and feed them to the
// main query as a CTE, so the main table only scans those fingerprints.
//
// There are three outcomes:
//
// 1. Skip it — the filter has nothing we can pre-resolve. This happens when the filter
// is empty,no resource filter, or when its resource conditions sit under an OR (e.g.
// `name='GET' OR service.name='abc'`), because then we can't reduce to a fixed set
// of fingerprints. The main query filters on resource attributes inline instead.
// 2. Skip it — too many fingerprints match (over the configured threshold), so the CTE
// would not be selective enough to be worth it. Again, filter inline on the main table.
// 3. Use it — attach the matching fingerprints as the __resource_filter CTE and join
// the main table on resource_fingerprint.
func (b *logQueryStatementBuilder) maybeAttachResourceFilter(
ctx context.Context,
sb *sqlbuilder.SelectBuilder,

View File

@@ -1230,7 +1230,7 @@ func TestSkipResourceFingerprintLogs(t *testing.T) {
mockStore := telemetrystoretest.New(telemetrystore.Config{}, &regexQueryMatcher{})
mock := mockStore.Mock()
mock.ExpectQueryRow(`SELECT uniq\(fingerprint\) FROM signoz_logs\.distributed_logs_v2_resource`).
mock.ExpectQueryRow(`SELECT count\(\) FROM \(SELECT fingerprint FROM signoz_logs\.distributed_logs_v2_resource`).
WillReturnRow(cmock.NewRow([]cmock.ColumnType{
{Name: "count", Type: "UInt64"},
}, []any{uint64(2)}))
@@ -1250,7 +1250,7 @@ func TestSkipResourceFingerprintLogs(t *testing.T) {
mockStore := telemetrystoretest.New(telemetrystore.Config{}, &regexQueryMatcher{})
mock := mockStore.Mock()
mock.ExpectQueryRow(`SELECT uniq\(fingerprint\) FROM signoz_logs\.distributed_logs_v2_resource`).
mock.ExpectQueryRow(`SELECT count\(\) FROM \(SELECT fingerprint FROM signoz_logs\.distributed_logs_v2_resource`).
WillReturnRow(cmock.NewRow([]cmock.ColumnType{
{Name: "count", Type: "UInt64"},
}, []any{threshold}))

View File

@@ -98,7 +98,17 @@ func (b *resourceFilterStatementBuilder[T]) Build(
query qbtypes.QueryBuilderQuery[T],
variables map[string]qbtypes.VariableItem,
) (*qbtypes.Statement, error) {
q, isNoOp, err := b.buildQuery(ctx, start, end, "fingerprint", query, variables)
q := sqlbuilder.NewSelectBuilder()
q.Select("fingerprint")
q.From(fmt.Sprintf("%s.%s", b.dbName, b.tableName))
keySelectors := b.getKeySelectors(query)
keys, _, err := b.metadataStore.GetKeysMulti(ctx, keySelectors)
if err != nil {
return nil, err
}
isNoOp, err := b.addConditions(ctx, q, start, end, query, keys, variables)
if err != nil {
return nil, err
}
@@ -117,37 +127,8 @@ func (b *resourceFilterStatementBuilder[T]) Build(
}, nil
}
// buildQuery selects selectExpr from the resource table and applies the filter and
// time conditions. isNoOp is true when the filter resolves to no resource conditions.
func (b *resourceFilterStatementBuilder[T]) buildQuery(
ctx context.Context,
start, end uint64,
selectExpr string,
query qbtypes.QueryBuilderQuery[T],
variables map[string]qbtypes.VariableItem,
) (*sqlbuilder.SelectBuilder, bool, error) {
q := sqlbuilder.NewSelectBuilder()
q.Select(selectExpr)
q.From(fmt.Sprintf("%s.%s", b.dbName, b.tableName))
keySelectors := b.getKeySelectors(query)
keys, _, err := b.metadataStore.GetKeysMulti(ctx, keySelectors)
if err != nil {
return nil, false, err
}
isNoOp, err := b.addConditions(ctx, q, start, end, query, keys, variables)
if err != nil {
return nil, false, err
}
return q, isNoOp, nil
}
// BuildCount returns a statement that counts the distinct fingerprints matching
// the resource filter. Returns (nil, nil) when the filter is a no-op.
//
// It uses uniq() rather than count() over a GROUP BY: uniq's approximation is well
// within tolerance for a threshold check and is ~2x faster with far less memory.
func (b *resourceFilterStatementBuilder[T]) BuildCount(
ctx context.Context,
start uint64,
@@ -155,18 +136,13 @@ func (b *resourceFilterStatementBuilder[T]) BuildCount(
query qbtypes.QueryBuilderQuery[T],
variables map[string]qbtypes.VariableItem,
) (*qbtypes.Statement, error) {
q, isNoOp, err := b.buildQuery(ctx, start, end, "uniq(fingerprint)", query, variables)
if err != nil {
inner, err := b.Build(ctx, start, end, qbtypes.RequestTypeRaw, query, variables)
if err != nil || inner == nil {
return nil, err
}
if isNoOp {
return nil, nil //nolint:nilnil
}
stmt, args := q.BuildWithFlavor(sqlbuilder.ClickHouse)
return &qbtypes.Statement{
Query: stmt,
Args: args,
Query: fmt.Sprintf("SELECT count() FROM (%s)", inner.Query),
Args: inner.Args,
}, nil
}

View File

@@ -818,22 +818,6 @@ func aggOrderBy(k qbtypes.OrderBy, q qbtypes.QueryBuilderQuery[qbtypes.TraceAggr
return 0, false
}
// maybeAttachResourceFilter decides whether to pre-filter on the resource table.
//
// The resource table maps resource attributes (e.g. service.name) to fingerprints.
// When it helps, we look up the matching fingerprints there first and feed them to the
// main query as a CTE, so the main table only scans those fingerprints.
//
// There are three outcomes:
//
// 1. Skip it — the filter has nothing we can pre-resolve. This happens when the filter
// is empty,no resource filter, or when its resource conditions sit under an OR (e.g.
// `name='GET' OR service.name='abc'`), because then we can't reduce to a fixed set
// of fingerprints. The main query filters on resource attributes inline instead.
// 2. Skip it — too many fingerprints match (over the configured threshold), so the CTE
// would not be selective enough to be worth it. Again, filter inline on the main table.
// 3. Use it — attach the matching fingerprints as the __resource_filter CTE and join
// the main table on resource_fingerprint.
func (b *traceQueryStatementBuilder) maybeAttachResourceFilter(
ctx context.Context,
sb *sqlbuilder.SelectBuilder,

View File

@@ -1629,7 +1629,7 @@ func TestSkipResourceFingerprint(t *testing.T) {
// Only the count query runs against the telemetry store; the CTE
// itself is embedded as SQL in the main query (no extra round trip).
mock.ExpectQueryRow(`SELECT uniq\(fingerprint\) FROM signoz_traces\.distributed_traces_v3_resource`).
mock.ExpectQueryRow(`SELECT count\(\) FROM \(SELECT fingerprint FROM signoz_traces\.distributed_traces_v3_resource`).
WillReturnRow(cmock.NewRow([]cmock.ColumnType{
{Name: "count", Type: "UInt64"},
}, []any{uint64(2)}))
@@ -1649,7 +1649,7 @@ func TestSkipResourceFingerprint(t *testing.T) {
mockStore := telemetrystoretest.New(telemetrystore.Config{}, &regexQueryMatcher{})
mock := mockStore.Mock()
mock.ExpectQueryRow(`SELECT uniq\(fingerprint\) FROM signoz_traces\.distributed_traces_v3_resource`).
mock.ExpectQueryRow(`SELECT count\(\) FROM \(SELECT fingerprint FROM signoz_traces\.distributed_traces_v3_resource`).
WillReturnRow(cmock.NewRow([]cmock.ColumnType{
{Name: "count", Type: "UInt64"},
}, []any{threshold}))

View File

@@ -1,18 +1,11 @@
"""
Transparency check for the skip_resource_fingerprint optimization (traces and logs).
The optimization changes how a query's resource conditions are resolved depending on
how selective they are, but must change only ClickHouse performance, never the rows.
Each test runs the same query against the primary instance (optimization on,
threshold=2) and `signoz_fingerprint` (optimization off) and asserts the responses
are identical, covering all three resolver outcomes:
- CTE: a filter matching fewer fingerprints than the threshold resolves through the
fingerprint CTE.
- Fallback: a filter matching at or above the threshold pushes resource conditions
onto the main spans/logs table instead.
- No-op: a filter with no resource conditions to pre-resolve (no resource field, or
resource fields only under an OR) filters inline on the main table.
At or above the configured fingerprint threshold the optimization pushes resource
conditions onto the main spans/logs table instead of the fingerprint CTE. That
rewrite must change ClickHouse performance, never the rows: each test runs the same
query against the primary instance (optimization on, threshold=2) and
`signoz_fingerprint` (optimization off) and asserts the responses are identical.
"""
from collections.abc import Callable
@@ -72,122 +65,6 @@ def test_skip_resource_fingerprint_traces_fallback_matches_fingerprint(
assert_identical_query_response(optimized, fingerprint)
def test_skip_resource_fingerprint_traces_cte_matches_fingerprint(
signoz: types.SigNoz,
signoz_fingerprint: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_traces: Callable[[list[Traces]], None],
) -> None:
"""A < 2-fingerprint filter resolves through the fingerprint CTE; rows must match the baseline."""
now = datetime.now(tz=UTC).replace(second=0, microsecond=0)
# One service shares the env (1 fingerprint < threshold 2 -> CTE); two spans on it
# exercise dedup. A second service in a different env must be excluded.
env = {"deployment.environment": "skip-cte"}
insert_traces(
[
Traces(timestamp=now - timedelta(seconds=10), resources={"service.name": "skip-cte-svc-a", **env}),
Traces(timestamp=now - timedelta(seconds=9), resources={"service.name": "skip-cte-svc-a", **env}),
Traces(timestamp=now - timedelta(seconds=8), resources={"service.name": "skip-cte-other", "deployment.environment": "skip-cte-other-env"}),
]
)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
query = BuilderQuery(
signal="traces",
limit=50,
order=[OrderBy(TelemetryFieldKey("timestamp"), "asc")],
filter_expression="deployment.environment = 'skip-cte'",
select_fields=[TelemetryFieldKey("service.name", "string", "resource")],
).to_dict()
start_ms = int((datetime.now(tz=UTC) - timedelta(minutes=5)).timestamp() * 1000)
end_ms = int(datetime.now(tz=UTC).timestamp() * 1000)
optimized = make_query_request(signoz, token, start_ms=start_ms, end_ms=end_ms, request_type="raw", queries=[query])
fingerprint = make_query_request(signoz_fingerprint, token, start_ms=start_ms, end_ms=end_ms, request_type="raw", queries=[query])
assert len(get_rows(optimized)) == 2
assert_identical_query_response(optimized, fingerprint)
def test_skip_resource_fingerprint_traces_or_filter_matches_fingerprint(
signoz: types.SigNoz,
signoz_fingerprint: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_traces: Callable[[list[Traces]], None],
) -> None:
"""A resource condition under an OR has no fixed fingerprint set, so it filters inline; rows must match the baseline."""
now = datetime.now(tz=UTC).replace(second=0, microsecond=0)
# `name = ... OR service.name = ...` can't reduce to a fingerprint set (no-op path).
# span-1 matches on name, span-2 on service.name, span-3 matches neither.
env = {"deployment.environment": "skip-or"}
insert_traces(
[
Traces(timestamp=now - timedelta(seconds=10), name="tr-or-name", resources={"service.name": "tr-or-svc-x", **env}),
Traces(timestamp=now - timedelta(seconds=9), name="tr-or-other", resources={"service.name": "tr-or-svc-a", **env}),
Traces(timestamp=now - timedelta(seconds=8), name="tr-or-other", resources={"service.name": "tr-or-svc-b", **env}),
]
)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
query = BuilderQuery(
signal="traces",
limit=50,
order=[OrderBy(TelemetryFieldKey("timestamp"), "asc")],
filter_expression="name = 'tr-or-name' OR service.name = 'tr-or-svc-a'",
select_fields=[TelemetryFieldKey("service.name", "string", "resource")],
).to_dict()
start_ms = int((datetime.now(tz=UTC) - timedelta(minutes=5)).timestamp() * 1000)
end_ms = int(datetime.now(tz=UTC).timestamp() * 1000)
optimized = make_query_request(signoz, token, start_ms=start_ms, end_ms=end_ms, request_type="raw", queries=[query])
fingerprint = make_query_request(signoz_fingerprint, token, start_ms=start_ms, end_ms=end_ms, request_type="raw", queries=[query])
assert len(get_rows(optimized)) == 2
assert_identical_query_response(optimized, fingerprint)
def test_skip_resource_fingerprint_traces_no_resource_filter_matches_fingerprint(
signoz: types.SigNoz,
signoz_fingerprint: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_traces: Callable[[list[Traces]], None],
) -> None:
"""A filter with no resource field has nothing to pre-resolve, so it filters inline; rows must match the baseline."""
now = datetime.now(tz=UTC).replace(second=0, microsecond=0)
# Filtering only on the span name (an intrinsic, non-resource field) is a no-op for
# the resolver; span-1 matches, span-2 does not.
env = {"deployment.environment": "skip-nr"}
insert_traces(
[
Traces(timestamp=now - timedelta(seconds=10), name="tr-nr-name", resources={"service.name": "tr-nr-svc-a", **env}),
Traces(timestamp=now - timedelta(seconds=9), name="tr-nr-other", resources={"service.name": "tr-nr-svc-b", **env}),
]
)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
query = BuilderQuery(
signal="traces",
limit=50,
order=[OrderBy(TelemetryFieldKey("timestamp"), "asc")],
filter_expression="name = 'tr-nr-name'",
select_fields=[TelemetryFieldKey("service.name", "string", "resource")],
).to_dict()
start_ms = int((datetime.now(tz=UTC) - timedelta(minutes=5)).timestamp() * 1000)
end_ms = int(datetime.now(tz=UTC).timestamp() * 1000)
optimized = make_query_request(signoz, token, start_ms=start_ms, end_ms=end_ms, request_type="raw", queries=[query])
fingerprint = make_query_request(signoz_fingerprint, token, start_ms=start_ms, end_ms=end_ms, request_type="raw", queries=[query])
assert len(get_rows(optimized)) == 1
assert_identical_query_response(optimized, fingerprint)
def test_skip_resource_fingerprint_logs_fallback_matches_fingerprint(
signoz: types.SigNoz,
signoz_fingerprint: types.SigNoz,
@@ -226,119 +103,3 @@ def test_skip_resource_fingerprint_logs_fallback_matches_fingerprint(
assert len(get_rows(optimized)) == 3
assert_identical_query_response(optimized, fingerprint)
def test_skip_resource_fingerprint_logs_cte_matches_fingerprint(
signoz: types.SigNoz,
signoz_fingerprint: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_logs: Callable[[list[Logs]], None],
) -> None:
"""A < 2-fingerprint filter resolves through the fingerprint CTE; rows must match the baseline."""
now = datetime.now(tz=UTC)
# One service shares the env (1 fingerprint < threshold 2 -> CTE); two logs on it
# exercise dedup. A second service in a different env must be excluded.
env = {"deployment.environment": "skip-logs-cte"}
insert_logs(
[
Logs(timestamp=now - timedelta(seconds=10), resources={"service.name": "skip-logs-cte-svc-a", **env}, body="a"),
Logs(timestamp=now - timedelta(seconds=9), resources={"service.name": "skip-logs-cte-svc-a", **env}, body="b"),
Logs(timestamp=now - timedelta(seconds=8), resources={"service.name": "skip-logs-cte-other", "deployment.environment": "skip-logs-cte-other-env"}, body="noise"),
]
)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
query = BuilderQuery(
signal="logs",
limit=50,
order=[OrderBy(TelemetryFieldKey("timestamp"), "asc")],
filter_expression="deployment.environment = 'skip-logs-cte'",
select_fields=[TelemetryFieldKey("service.name", "string", "resource"), TelemetryFieldKey("body")],
).to_dict()
start_ms = int((datetime.now(tz=UTC) - timedelta(minutes=5)).timestamp() * 1000)
end_ms = int(datetime.now(tz=UTC).timestamp() * 1000)
optimized = make_query_request(signoz, token, start_ms=start_ms, end_ms=end_ms, request_type="raw", queries=[query])
fingerprint = make_query_request(signoz_fingerprint, token, start_ms=start_ms, end_ms=end_ms, request_type="raw", queries=[query])
assert len(get_rows(optimized)) == 2
assert_identical_query_response(optimized, fingerprint)
def test_skip_resource_fingerprint_logs_or_filter_matches_fingerprint(
signoz: types.SigNoz,
signoz_fingerprint: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_logs: Callable[[list[Logs]], None],
) -> None:
"""A resource condition under an OR has no fixed fingerprint set, so it filters inline; rows must match the baseline."""
now = datetime.now(tz=UTC)
# `test.marker = ... OR service.name = ...` can't reduce to a fingerprint set (no-op path).
# log-1 matches on the attribute, log-2 on service.name, log-3 matches neither.
env = {"deployment.environment": "skip-logs-or"}
insert_logs(
[
Logs(timestamp=now - timedelta(seconds=10), resources={"service.name": "logs-or-svc-x", **env}, attributes={"test.marker": "logs-or-hit"}, body="a"),
Logs(timestamp=now - timedelta(seconds=9), resources={"service.name": "logs-or-svc-a", **env}, body="b"),
Logs(timestamp=now - timedelta(seconds=8), resources={"service.name": "logs-or-svc-b", **env}, body="noise"),
]
)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
query = BuilderQuery(
signal="logs",
limit=50,
order=[OrderBy(TelemetryFieldKey("timestamp"), "asc")],
filter_expression="test.marker = 'logs-or-hit' OR service.name = 'logs-or-svc-a'",
select_fields=[TelemetryFieldKey("service.name", "string", "resource"), TelemetryFieldKey("body")],
).to_dict()
start_ms = int((datetime.now(tz=UTC) - timedelta(minutes=5)).timestamp() * 1000)
end_ms = int(datetime.now(tz=UTC).timestamp() * 1000)
optimized = make_query_request(signoz, token, start_ms=start_ms, end_ms=end_ms, request_type="raw", queries=[query])
fingerprint = make_query_request(signoz_fingerprint, token, start_ms=start_ms, end_ms=end_ms, request_type="raw", queries=[query])
assert len(get_rows(optimized)) == 2
assert_identical_query_response(optimized, fingerprint)
def test_skip_resource_fingerprint_logs_no_resource_filter_matches_fingerprint(
signoz: types.SigNoz,
signoz_fingerprint: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_logs: Callable[[list[Logs]], None],
) -> None:
"""A filter with no resource field has nothing to pre-resolve, so it filters inline; rows must match the baseline."""
now = datetime.now(tz=UTC)
# Filtering only on an attribute (a non-resource field) is a no-op for the resolver;
# log-1 matches, log-2 does not.
env = {"deployment.environment": "skip-logs-nr"}
insert_logs(
[
Logs(timestamp=now - timedelta(seconds=10), resources={"service.name": "logs-nr-svc-a", **env}, attributes={"test.marker": "logs-nr-hit"}, body="a"),
Logs(timestamp=now - timedelta(seconds=9), resources={"service.name": "logs-nr-svc-b", **env}, body="b"),
]
)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
query = BuilderQuery(
signal="logs",
limit=50,
order=[OrderBy(TelemetryFieldKey("timestamp"), "asc")],
filter_expression="test.marker = 'logs-nr-hit'",
select_fields=[TelemetryFieldKey("service.name", "string", "resource"), TelemetryFieldKey("body")],
).to_dict()
start_ms = int((datetime.now(tz=UTC) - timedelta(minutes=5)).timestamp() * 1000)
end_ms = int(datetime.now(tz=UTC).timestamp() * 1000)
optimized = make_query_request(signoz, token, start_ms=start_ms, end_ms=end_ms, request_type="raw", queries=[query])
fingerprint = make_query_request(signoz_fingerprint, token, start_ms=start_ms, end_ms=end_ms, request_type="raw", queries=[query])
assert len(get_rows(optimized)) == 1
assert_identical_query_response(optimized, fingerprint)