Compare commits

..

14 Commits

Author SHA1 Message Date
nikhilmantri0902
44db9e202a chore: added changes based on comments 2026-05-26 12:20:33 +05:30
Nikhil Mantri
f9e704caa4 Merge branch 'main' into infraM/proper_align_start_end_time_custom_queries 2026-05-25 12:18:56 +05:30
nikhilmantri0902
21ac6505e9 chore: added todo and note 2026-05-25 12:13:46 +05:30
nikhilmantri0902
5cadf8582e chore: fix for surfacing meta for pods custom group by 2026-05-22 22:44:17 +05:30
nikhilmantri0902
711bbf49f1 chore: renamed variables 2026-05-22 16:49:04 +05:30
nikhilmantri0902
1cc48405e0 chore: removed function 2026-05-22 16:43:56 +05:30
nikhilmantri0902
e79715bf6d chore: comment correction 2026-05-22 16:31:09 +05:30
nikhilmantri0902
2ce86a3f9d chore: comment correction 2026-05-22 13:29:58 +05:30
nikhilmantri0902
639e3239a9 chore: added step flooring note 2026-05-22 13:26:13 +05:30
nikhilmantri0902
e83a23e6ab chore: formula correction 2026-05-22 13:10:46 +05:30
Nikhil Mantri
f0cff4ae96 Merge branch 'main' into infraM/proper_align_start_end_time_custom_queries 2026-05-22 12:21:39 +05:30
nikhilmantri0902
0667dc47cb chore: filter metric groups 2026-05-21 23:48:28 +05:30
nikhilmantri0902
4af267ee61 chore: filter metric groups 2026-05-21 23:44:52 +05:30
nikhilmantri0902
6f1090818c chore: updated logic and use centralized function in the module 2026-05-21 20:09:36 +05:30
41 changed files with 691 additions and 1258 deletions

View File

@@ -60,14 +60,6 @@ web:
index: index.html
# The directory containing the static build files.
directory: /etc/signoz/web
# Settings exposed to the web.
settings:
posthog:
# Whether to enable PostHog in web.
enabled: true
appcues:
# Whether to enable Appcues in web.
enabled: true
##################### Cache #####################
cache:

View File

@@ -5641,19 +5641,6 @@ components:
type: object
Sigv4SigV4Config:
type: object
SpantypesEvent:
properties:
attributeMap:
additionalProperties: {}
type: object
isError:
type: boolean
name:
type: string
timeUnixNano:
minimum: 0
type: integer
type: object
SpantypesFieldContext:
enum:
- attribute
@@ -5668,50 +5655,6 @@ components:
required:
- items
type: object
SpantypesGettableWaterfallTrace:
properties:
aggregations:
items:
$ref: '#/components/schemas/SpantypesSpanAggregationResult'
nullable: true
type: array
endTimestampMillis:
minimum: 0
type: integer
hasMissingSpans:
type: boolean
hasMore:
type: boolean
rootServiceEntryPoint:
type: string
rootServiceName:
type: string
serviceNameToTotalDurationMap:
additionalProperties:
minimum: 0
type: integer
nullable: true
type: object
spans:
items:
$ref: '#/components/schemas/SpantypesWaterfallSpan'
nullable: true
type: array
startTimestampMillis:
minimum: 0
type: integer
totalErrorSpansCount:
minimum: 0
type: integer
totalSpansCount:
minimum: 0
type: integer
uncollapsedSpans:
items:
type: string
nullable: true
type: array
type: object
SpantypesPostableSpanMapper:
properties:
config:
@@ -5739,50 +5682,6 @@ components:
- name
- condition
type: object
SpantypesPostableWaterfall:
properties:
aggregations:
items:
$ref: '#/components/schemas/SpantypesSpanAggregation'
nullable: true
type: array
limit:
minimum: 0
type: integer
selectedSpanId:
type: string
uncollapsedSpans:
items:
type: string
nullable: true
type: array
type: object
SpantypesSpanAggregation:
properties:
aggregation:
$ref: '#/components/schemas/SpantypesSpanAggregationType'
field:
$ref: '#/components/schemas/TelemetrytypesTelemetryFieldKey'
type: object
SpantypesSpanAggregationResult:
properties:
aggregation:
$ref: '#/components/schemas/SpantypesSpanAggregationType'
field:
$ref: '#/components/schemas/TelemetrytypesTelemetryFieldKey'
value:
additionalProperties:
minimum: 0
type: integer
nullable: true
type: object
type: object
SpantypesSpanAggregationType:
enum:
- span_count
- execution_time_percentage
- duration
type: string
SpantypesSpanMapper:
properties:
config:
@@ -5913,78 +5812,6 @@ components:
nullable: true
type: string
type: object
SpantypesWaterfallSpan:
properties:
attributes:
additionalProperties: {}
nullable: true
type: object
db_name:
type: string
db_operation:
type: string
duration_nano:
minimum: 0
type: integer
events:
items:
$ref: '#/components/schemas/SpantypesEvent'
nullable: true
type: array
external_http_method:
type: string
external_http_url:
type: string
flags:
minimum: 0
type: integer
has_children:
type: boolean
has_error:
type: boolean
http_host:
type: string
http_method:
type: string
http_url:
type: string
is_remote:
type: string
kind_string:
type: string
level:
minimum: 0
type: integer
name:
type: string
parent_span_id:
type: string
resource:
additionalProperties:
type: string
nullable: true
type: object
response_status_code:
type: string
span_id:
type: string
status_code:
type: integer
status_code_string:
type: string
status_message:
type: string
sub_tree_node_count:
minimum: 0
type: integer
time_unix:
minimum: 0
type: integer
trace_id:
type: string
trace_state:
type: string
type: object
TelemetrytypesFieldContext:
enum:
- metric
@@ -6077,6 +5904,179 @@ components:
TimeDuration:
format: int64
type: integer
TracedetailtypesEvent:
properties:
attributeMap:
additionalProperties: {}
type: object
isError:
type: boolean
name:
type: string
timeUnixNano:
minimum: 0
type: integer
type: object
TracedetailtypesGettableWaterfallTrace:
properties:
aggregations:
items:
$ref: '#/components/schemas/TracedetailtypesSpanAggregationResult'
nullable: true
type: array
endTimestampMillis:
minimum: 0
type: integer
hasMissingSpans:
type: boolean
hasMore:
type: boolean
rootServiceEntryPoint:
type: string
rootServiceName:
type: string
serviceNameToTotalDurationMap:
additionalProperties:
minimum: 0
type: integer
nullable: true
type: object
spans:
items:
$ref: '#/components/schemas/TracedetailtypesWaterfallSpan'
nullable: true
type: array
startTimestampMillis:
minimum: 0
type: integer
totalErrorSpansCount:
minimum: 0
type: integer
totalSpansCount:
minimum: 0
type: integer
uncollapsedSpans:
items:
type: string
nullable: true
type: array
type: object
TracedetailtypesPostableWaterfall:
properties:
aggregations:
items:
$ref: '#/components/schemas/TracedetailtypesSpanAggregation'
nullable: true
type: array
limit:
minimum: 0
type: integer
selectedSpanId:
type: string
uncollapsedSpans:
items:
type: string
nullable: true
type: array
type: object
TracedetailtypesSpanAggregation:
properties:
aggregation:
$ref: '#/components/schemas/TracedetailtypesSpanAggregationType'
field:
$ref: '#/components/schemas/TelemetrytypesTelemetryFieldKey'
type: object
TracedetailtypesSpanAggregationResult:
properties:
aggregation:
$ref: '#/components/schemas/TracedetailtypesSpanAggregationType'
field:
$ref: '#/components/schemas/TelemetrytypesTelemetryFieldKey'
value:
additionalProperties:
minimum: 0
type: integer
nullable: true
type: object
type: object
TracedetailtypesSpanAggregationType:
enum:
- span_count
- execution_time_percentage
- duration
type: string
TracedetailtypesWaterfallSpan:
properties:
attributes:
additionalProperties: {}
nullable: true
type: object
db_name:
type: string
db_operation:
type: string
duration_nano:
minimum: 0
type: integer
events:
items:
$ref: '#/components/schemas/TracedetailtypesEvent'
nullable: true
type: array
external_http_method:
type: string
external_http_url:
type: string
flags:
minimum: 0
type: integer
has_children:
type: boolean
has_error:
type: boolean
http_host:
type: string
http_method:
type: string
http_url:
type: string
is_remote:
type: string
kind_string:
type: string
level:
minimum: 0
type: integer
name:
type: string
parent_span_id:
type: string
resource:
additionalProperties:
type: string
nullable: true
type: object
response_status_code:
type: string
span_id:
type: string
status_code:
type: integer
status_code_string:
type: string
status_message:
type: string
sub_tree_node_count:
minimum: 0
type: integer
time_unix:
minimum: 0
type: integer
trace_id:
type: string
trace_state:
type: string
type: object
TypesAlertStatus:
properties:
inhibitedBy:
@@ -18896,7 +18896,7 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/SpantypesPostableWaterfall'
$ref: '#/components/schemas/TracedetailtypesPostableWaterfall'
responses:
"200":
content:
@@ -18904,7 +18904,7 @@ paths:
schema:
properties:
data:
$ref: '#/components/schemas/SpantypesGettableWaterfallTrace'
$ref: '#/components/schemas/TracedetailtypesGettableWaterfallTrace'
status:
type: string
required:

View File

@@ -6655,28 +6655,6 @@ export interface ServiceaccounttypesUpdatableFactorAPIKeyDTO {
name: string;
}
export type SpantypesEventDTOAttributeMap = { [key: string]: unknown };
export interface SpantypesEventDTO {
/**
* @type object
*/
attributeMap?: SpantypesEventDTOAttributeMap;
/**
* @type boolean
*/
isError?: boolean;
/**
* @type string
*/
name?: string;
/**
* @type integer
* @minimum 0
*/
timeUnixNano?: number;
}
export enum SpantypesFieldContextDTO {
attribute = 'attribute',
resource = 'resource',
@@ -6743,232 +6721,6 @@ export interface SpantypesGettableSpanMapperGroupsDTO {
items: SpantypesSpanMapperGroupDTO[];
}
export type SpantypesGettableWaterfallTraceDTOServiceNameToTotalDurationMapAnyOf =
{ [key: string]: number };
/**
* @nullable
*/
export type SpantypesGettableWaterfallTraceDTOServiceNameToTotalDurationMap =
SpantypesGettableWaterfallTraceDTOServiceNameToTotalDurationMapAnyOf | null;
export enum SpantypesSpanAggregationTypeDTO {
span_count = 'span_count',
execution_time_percentage = 'execution_time_percentage',
duration = 'duration',
}
export type SpantypesSpanAggregationResultDTOValueAnyOf = {
[key: string]: number;
};
/**
* @nullable
*/
export type SpantypesSpanAggregationResultDTOValue =
SpantypesSpanAggregationResultDTOValueAnyOf | null;
export interface SpantypesSpanAggregationResultDTO {
aggregation?: SpantypesSpanAggregationTypeDTO;
field?: TelemetrytypesTelemetryFieldKeyDTO;
/**
* @type object,null
*/
value?: SpantypesSpanAggregationResultDTOValue;
}
export type SpantypesWaterfallSpanDTOAttributesAnyOf = {
[key: string]: unknown;
};
/**
* @nullable
*/
export type SpantypesWaterfallSpanDTOAttributes =
SpantypesWaterfallSpanDTOAttributesAnyOf | null;
export type SpantypesWaterfallSpanDTOResourceAnyOf = { [key: string]: string };
/**
* @nullable
*/
export type SpantypesWaterfallSpanDTOResource =
SpantypesWaterfallSpanDTOResourceAnyOf | null;
export interface SpantypesWaterfallSpanDTO {
/**
* @type object,null
*/
attributes?: SpantypesWaterfallSpanDTOAttributes;
/**
* @type string
*/
db_name?: string;
/**
* @type string
*/
db_operation?: string;
/**
* @type integer
* @minimum 0
*/
duration_nano?: number;
/**
* @type array,null
*/
events?: SpantypesEventDTO[] | null;
/**
* @type string
*/
external_http_method?: string;
/**
* @type string
*/
external_http_url?: string;
/**
* @type integer
* @minimum 0
*/
flags?: number;
/**
* @type boolean
*/
has_children?: boolean;
/**
* @type boolean
*/
has_error?: boolean;
/**
* @type string
*/
http_host?: string;
/**
* @type string
*/
http_method?: string;
/**
* @type string
*/
http_url?: string;
/**
* @type string
*/
is_remote?: string;
/**
* @type string
*/
kind_string?: string;
/**
* @type integer
* @minimum 0
*/
level?: number;
/**
* @type string
*/
name?: string;
/**
* @type string
*/
parent_span_id?: string;
/**
* @type object,null
*/
resource?: SpantypesWaterfallSpanDTOResource;
/**
* @type string
*/
response_status_code?: string;
/**
* @type string
*/
span_id?: string;
/**
* @type integer
*/
status_code?: number;
/**
* @type string
*/
status_code_string?: string;
/**
* @type string
*/
status_message?: string;
/**
* @type integer
* @minimum 0
*/
sub_tree_node_count?: number;
/**
* @type integer
* @minimum 0
*/
time_unix?: number;
/**
* @type string
*/
trace_id?: string;
/**
* @type string
*/
trace_state?: string;
}
export interface SpantypesGettableWaterfallTraceDTO {
/**
* @type array,null
*/
aggregations?: SpantypesSpanAggregationResultDTO[] | null;
/**
* @type integer
* @minimum 0
*/
endTimestampMillis?: number;
/**
* @type boolean
*/
hasMissingSpans?: boolean;
/**
* @type boolean
*/
hasMore?: boolean;
/**
* @type string
*/
rootServiceEntryPoint?: string;
/**
* @type string
*/
rootServiceName?: string;
/**
* @type object,null
*/
serviceNameToTotalDurationMap?: SpantypesGettableWaterfallTraceDTOServiceNameToTotalDurationMap;
/**
* @type array,null
*/
spans?: SpantypesWaterfallSpanDTO[] | null;
/**
* @type integer
* @minimum 0
*/
startTimestampMillis?: number;
/**
* @type integer
* @minimum 0
*/
totalErrorSpansCount?: number;
/**
* @type integer
* @minimum 0
*/
totalSpansCount?: number;
/**
* @type array,null
*/
uncollapsedSpans?: string[] | null;
}
export enum SpantypesSpanMapperOperationDTO {
move = 'move',
copy = 'copy',
@@ -7018,31 +6770,6 @@ export interface SpantypesPostableSpanMapperGroupDTO {
name: string;
}
export interface SpantypesSpanAggregationDTO {
aggregation?: SpantypesSpanAggregationTypeDTO;
field?: TelemetrytypesTelemetryFieldKeyDTO;
}
export interface SpantypesPostableWaterfallDTO {
/**
* @type array,null
*/
aggregations?: SpantypesSpanAggregationDTO[] | null;
/**
* @type integer
* @minimum 0
*/
limit?: number;
/**
* @type string
*/
selectedSpanId?: string;
/**
* @type array,null
*/
uncollapsedSpans?: string[] | null;
}
export interface SpantypesSpanMapperDTO {
config: SpantypesSpanMapperConfigDTO;
/**
@@ -7151,6 +6878,281 @@ export interface TelemetrytypesGettableFieldValuesDTO {
values: TelemetrytypesTelemetryFieldValuesDTO;
}
export type TracedetailtypesEventDTOAttributeMap = { [key: string]: unknown };
export interface TracedetailtypesEventDTO {
/**
* @type object
*/
attributeMap?: TracedetailtypesEventDTOAttributeMap;
/**
* @type boolean
*/
isError?: boolean;
/**
* @type string
*/
name?: string;
/**
* @type integer
* @minimum 0
*/
timeUnixNano?: number;
}
export type TracedetailtypesGettableWaterfallTraceDTOServiceNameToTotalDurationMapAnyOf =
{ [key: string]: number };
/**
* @nullable
*/
export type TracedetailtypesGettableWaterfallTraceDTOServiceNameToTotalDurationMap =
TracedetailtypesGettableWaterfallTraceDTOServiceNameToTotalDurationMapAnyOf | null;
export enum TracedetailtypesSpanAggregationTypeDTO {
span_count = 'span_count',
execution_time_percentage = 'execution_time_percentage',
duration = 'duration',
}
export type TracedetailtypesSpanAggregationResultDTOValueAnyOf = {
[key: string]: number;
};
/**
* @nullable
*/
export type TracedetailtypesSpanAggregationResultDTOValue =
TracedetailtypesSpanAggregationResultDTOValueAnyOf | null;
export interface TracedetailtypesSpanAggregationResultDTO {
aggregation?: TracedetailtypesSpanAggregationTypeDTO;
field?: TelemetrytypesTelemetryFieldKeyDTO;
/**
* @type object,null
*/
value?: TracedetailtypesSpanAggregationResultDTOValue;
}
export type TracedetailtypesWaterfallSpanDTOAttributesAnyOf = {
[key: string]: unknown;
};
/**
* @nullable
*/
export type TracedetailtypesWaterfallSpanDTOAttributes =
TracedetailtypesWaterfallSpanDTOAttributesAnyOf | null;
export type TracedetailtypesWaterfallSpanDTOResourceAnyOf = {
[key: string]: string;
};
/**
* @nullable
*/
export type TracedetailtypesWaterfallSpanDTOResource =
TracedetailtypesWaterfallSpanDTOResourceAnyOf | null;
export interface TracedetailtypesWaterfallSpanDTO {
/**
* @type object,null
*/
attributes?: TracedetailtypesWaterfallSpanDTOAttributes;
/**
* @type string
*/
db_name?: string;
/**
* @type string
*/
db_operation?: string;
/**
* @type integer
* @minimum 0
*/
duration_nano?: number;
/**
* @type array,null
*/
events?: TracedetailtypesEventDTO[] | null;
/**
* @type string
*/
external_http_method?: string;
/**
* @type string
*/
external_http_url?: string;
/**
* @type integer
* @minimum 0
*/
flags?: number;
/**
* @type boolean
*/
has_children?: boolean;
/**
* @type boolean
*/
has_error?: boolean;
/**
* @type string
*/
http_host?: string;
/**
* @type string
*/
http_method?: string;
/**
* @type string
*/
http_url?: string;
/**
* @type string
*/
is_remote?: string;
/**
* @type string
*/
kind_string?: string;
/**
* @type integer
* @minimum 0
*/
level?: number;
/**
* @type string
*/
name?: string;
/**
* @type string
*/
parent_span_id?: string;
/**
* @type object,null
*/
resource?: TracedetailtypesWaterfallSpanDTOResource;
/**
* @type string
*/
response_status_code?: string;
/**
* @type string
*/
span_id?: string;
/**
* @type integer
*/
status_code?: number;
/**
* @type string
*/
status_code_string?: string;
/**
* @type string
*/
status_message?: string;
/**
* @type integer
* @minimum 0
*/
sub_tree_node_count?: number;
/**
* @type integer
* @minimum 0
*/
time_unix?: number;
/**
* @type string
*/
trace_id?: string;
/**
* @type string
*/
trace_state?: string;
}
export interface TracedetailtypesGettableWaterfallTraceDTO {
/**
* @type array,null
*/
aggregations?: TracedetailtypesSpanAggregationResultDTO[] | null;
/**
* @type integer
* @minimum 0
*/
endTimestampMillis?: number;
/**
* @type boolean
*/
hasMissingSpans?: boolean;
/**
* @type boolean
*/
hasMore?: boolean;
/**
* @type string
*/
rootServiceEntryPoint?: string;
/**
* @type string
*/
rootServiceName?: string;
/**
* @type object,null
*/
serviceNameToTotalDurationMap?: TracedetailtypesGettableWaterfallTraceDTOServiceNameToTotalDurationMap;
/**
* @type array,null
*/
spans?: TracedetailtypesWaterfallSpanDTO[] | null;
/**
* @type integer
* @minimum 0
*/
startTimestampMillis?: number;
/**
* @type integer
* @minimum 0
*/
totalErrorSpansCount?: number;
/**
* @type integer
* @minimum 0
*/
totalSpansCount?: number;
/**
* @type array,null
*/
uncollapsedSpans?: string[] | null;
}
export interface TracedetailtypesSpanAggregationDTO {
aggregation?: TracedetailtypesSpanAggregationTypeDTO;
field?: TelemetrytypesTelemetryFieldKeyDTO;
}
export interface TracedetailtypesPostableWaterfallDTO {
/**
* @type array,null
*/
aggregations?: TracedetailtypesSpanAggregationDTO[] | null;
/**
* @type integer
* @minimum 0
*/
limit?: number;
/**
* @type string
*/
selectedSpanId?: string;
/**
* @type array,null
*/
uncollapsedSpans?: string[] | null;
}
export interface TypesChangePasswordRequestDTO {
/**
* @type string
@@ -9230,7 +9232,7 @@ export type GetWaterfallPathParameters = {
traceID: string;
};
export type GetWaterfall200 = {
data: SpantypesGettableWaterfallTraceDTO;
data: TracedetailtypesGettableWaterfallTraceDTO;
/**
* @type string
*/

View File

@@ -15,7 +15,7 @@ import type {
GetWaterfall200,
GetWaterfallPathParameters,
RenderErrorResponseDTO,
SpantypesPostableWaterfallDTO,
TracedetailtypesPostableWaterfallDTO,
} from '../sigNoz.schemas';
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
@@ -27,14 +27,14 @@ import type { ErrorType, BodyType } from '../../../generatedAPIInstance';
*/
export const getWaterfall = (
{ traceID }: GetWaterfallPathParameters,
spantypesPostableWaterfallDTO?: BodyType<SpantypesPostableWaterfallDTO>,
tracedetailtypesPostableWaterfallDTO?: BodyType<TracedetailtypesPostableWaterfallDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<GetWaterfall200>({
url: `/api/v3/traces/${traceID}/waterfall`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: spantypesPostableWaterfallDTO,
data: tracedetailtypesPostableWaterfallDTO,
signal,
});
};
@@ -48,7 +48,7 @@ export const getGetWaterfallMutationOptions = <
TError,
{
pathParams: GetWaterfallPathParameters;
data?: BodyType<SpantypesPostableWaterfallDTO>;
data?: BodyType<TracedetailtypesPostableWaterfallDTO>;
},
TContext
>;
@@ -57,7 +57,7 @@ export const getGetWaterfallMutationOptions = <
TError,
{
pathParams: GetWaterfallPathParameters;
data?: BodyType<SpantypesPostableWaterfallDTO>;
data?: BodyType<TracedetailtypesPostableWaterfallDTO>;
},
TContext
> => {
@@ -74,7 +74,7 @@ export const getGetWaterfallMutationOptions = <
Awaited<ReturnType<typeof getWaterfall>>,
{
pathParams: GetWaterfallPathParameters;
data?: BodyType<SpantypesPostableWaterfallDTO>;
data?: BodyType<TracedetailtypesPostableWaterfallDTO>;
}
> = (props) => {
const { pathParams, data } = props ?? {};
@@ -89,7 +89,7 @@ export type GetWaterfallMutationResult = NonNullable<
Awaited<ReturnType<typeof getWaterfall>>
>;
export type GetWaterfallMutationBody =
| BodyType<SpantypesPostableWaterfallDTO>
| BodyType<TracedetailtypesPostableWaterfallDTO>
| undefined;
export type GetWaterfallMutationError = ErrorType<RenderErrorResponseDTO>;
@@ -105,7 +105,7 @@ export const useGetWaterfall = <
TError,
{
pathParams: GetWaterfallPathParameters;
data?: BodyType<SpantypesPostableWaterfallDTO>;
data?: BodyType<TracedetailtypesPostableWaterfallDTO>;
},
TContext
>;
@@ -114,7 +114,7 @@ export const useGetWaterfall = <
TError,
{
pathParams: GetWaterfallPathParameters;
data?: BodyType<SpantypesPostableWaterfallDTO>;
data?: BodyType<TracedetailtypesPostableWaterfallDTO>;
},
TContext
> => {

View File

@@ -5,10 +5,7 @@ import cx from 'classnames';
import { VIEW_TYPES } from 'components/LogDetail/constants';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import { ChangeViewFunctionType } from 'container/ExplorerOptions/types';
import {
getBodyDisplayString,
getSanitizedLogBody,
} from 'container/LogDetailedView/utils';
import { getSanitizedLogBody } from 'container/LogDetailedView/utils';
import { FontSize } from 'container/OptionsMenu/types';
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
import { useIsDarkMode } from 'hooks/useDarkMode';
@@ -199,7 +196,7 @@ function ListLogView({
{updatedSelecedFields.some((field) => field.name === 'body') && (
<LogGeneralField
fieldKey="Log"
fieldValue={getBodyDisplayString(logData.body)}
fieldValue={flattenLogData.body}
linesPerRow={linesPerRow}
fontSize={fontSize}
/>

View File

@@ -263,7 +263,7 @@ function External(): JSX.Element {
timestamp: selectedTimeStamp,
domainName: selectedData?.address || '',
isError: true,
stepInterval,
stepInterval: 300,
safeNavigate,
})}
/>
@@ -306,7 +306,7 @@ function External(): JSX.Element {
timestamp: selectedTimeStamp,
domainName: selectedData?.address,
isError: false,
stepInterval,
stepInterval: 300,
safeNavigate,
})}
/>
@@ -352,7 +352,7 @@ function External(): JSX.Element {
timestamp: selectedTimeStamp,
domainName: selectedData?.address,
isError: false,
stepInterval,
stepInterval: 300,
safeNavigate,
})}
/>
@@ -395,7 +395,7 @@ function External(): JSX.Element {
timestamp: selectedTimeStamp,
domainName: selectedData?.address,
isError: false,
stepInterval,
stepInterval: 300,
safeNavigate,
})}
/>

View File

@@ -151,7 +151,7 @@ export function onViewAPIMonitoringPopupClick({
safeNavigate,
}: OnViewAPIMonitoringPopupClickProps): (e?: React.MouseEvent) => void {
return (e?: React.MouseEvent): void => {
const endTime = timestamp;
const endTime = timestamp + (stepInterval || 60);
const startTime = timestamp - (stepInterval || 60);
const filters = {
items: [

View File

@@ -5,7 +5,7 @@ import (
"github.com/SigNoz/signoz/pkg/http/handler"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/spantypes"
"github.com/SigNoz/signoz/pkg/types/tracedetailtypes"
"github.com/gorilla/mux"
)
@@ -17,9 +17,9 @@ func (provider *provider) addTraceDetailRoutes(router *mux.Router) error {
Tags: []string{"tracedetail"},
Summary: "Get waterfall view for a trace",
Description: "Returns the waterfall view of spans for a given trace ID with tree structure, metadata, and windowed pagination",
Request: new(spantypes.PostableWaterfall),
Request: new(tracedetailtypes.PostableWaterfall),
RequestContentType: "application/json",
Response: new(spantypes.GettableWaterfallTrace),
Response: new(tracedetailtypes.GettableWaterfallTrace),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},

View File

@@ -149,6 +149,11 @@ func paginateWithBackfill(
groupBy []qbtypes.GroupByKey,
offset, limit int,
) []map[string]string {
// note: we took a stand here that we are NOT removing those metricGroups from the array that are not in metadataMap.
// we are relying on time adjustment logic from alignedMetricWindow. In future if a user complains about seeing metric groups
// with missing metadata, we can consider removing those groups from the metricGroups array here before paginating.
metricKeySet := make(map[string]bool, len(metricGroups))
for _, g := range metricGroups {
metricKeySet[g.compositeKey] = true
@@ -163,7 +168,7 @@ func paginateWithBackfill(
sort.Strings(metadataOnlyKeys)
totalMetric := len(metricGroups)
totalAll := totalMetric + len(metadataOnlyKeys)
totalAll := len(metadataMap)
end := offset + limit
if end > totalAll {
@@ -307,23 +312,57 @@ func parseFullQueryResponse(
return result
}
// buildSamplesTblFingerprintSubQuery returns a SelectBuilder that selects distinct fingerprints
// from the samples table for the given metric names andtime range.
func (m *module) buildSamplesTblFingerprintSubQuery(metricNames []string, startMs, endMs int64) *sqlbuilder.SelectBuilder {
samplesTableName := telemetrymetrics.WhichSamplesTableToUse(
uint64(startMs), uint64(endMs),
metrictypes.UnspecifiedType,
metrictypes.TimeAggregationUnspecified,
nil,
// alignedMetricWindow returns step-floored time bounds and the metric tables
// to use for the given window. The floor matches what the QB v5 metric
// querier does internally (see querybuilder.AdjustedMetricTimeRange).
// Please use the samplesAdjustedStartMs with samples table and tsAdjustedStartMs with ts tables.
// Both can use the same flooredEndMs.
func alignedMetricWindow(startMs, endMs int64) (
uint64, // samplesAdjustedStartMs
uint64, // flooredEndMs
uint64, // tsAdjustedStartMs
string, // distributedTSTable
string, // localTSTable
string, // distributedSamplesTable
string, // localSamplesTable
) {
samplesAdjustedStartMs := uint64(startMs)
flooredEndMs := uint64(endMs)
stepSecs := querybuilder.RecommendedStepIntervalForMetric(samplesAdjustedStartMs, flooredEndMs)
// note: this is the same flooring logic as in querybuilder.AdjustedMetricTimeRange. Duplicated code.
// TODO(nikhilmantri0902): if the querybuilder.AdjustMetricTimeRange logic changes, this needs to be updated too.
if stepSecs > 0 {
samplesAdjustedStartMs = samplesAdjustedStartMs - (samplesAdjustedStartMs % (stepSecs * 1000))
adjustStep := stepSecs
if adjustStep > 60 {
adjustStep = 60
}
flooredEndMs = flooredEndMs - (flooredEndMs % (adjustStep * 1000))
}
tsAdjustedStartMs, _, distributedTSTable, localTSTable := telemetrymetrics.WhichTSTableToUse(
samplesAdjustedStartMs, flooredEndMs, nil,
)
localSamplesTable := strings.TrimPrefix(samplesTableName, "distributed_")
distributedSamplesTable, localSamplesTable := telemetrymetrics.WhichSamplesTableToUse(
samplesAdjustedStartMs, flooredEndMs,
metrictypes.UnspecifiedType, metrictypes.TimeAggregationUnspecified, nil,
)
return samplesAdjustedStartMs, flooredEndMs, tsAdjustedStartMs, distributedTSTable, localTSTable, distributedSamplesTable, localSamplesTable
}
// buildSamplesTblFingerprintSubQuery returns a SelectBuilder that selects distinct fingerprints
// from the samples table for the given metric names and time range.
// Bounds must already be step-floored by the caller via alignedMetricWindow.
func (m *module) buildSamplesTblFingerprintSubQuery(metricNames []string, samplesTable string, flooredStart, flooredEnd uint64) *sqlbuilder.SelectBuilder {
fpSB := sqlbuilder.NewSelectBuilder()
fpSB.Select("DISTINCT fingerprint")
fpSB.From(fmt.Sprintf("%s.%s", telemetrymetrics.DBName, localSamplesTable))
fpSB.From(fmt.Sprintf("%s.%s", telemetrymetrics.DBName, samplesTable))
fpSB.Where(
fpSB.In("metric_name", sqlbuilder.List(metricNames)),
fpSB.GE("unix_milli", startMs),
fpSB.L("unix_milli", endMs),
fpSB.GE("unix_milli", flooredStart),
fpSB.L("unix_milli", flooredEnd),
)
return fpSB
}
@@ -454,12 +493,12 @@ func (m *module) getMetadata(
return nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "groupBy must not be empty")
}
// Pick the optimal timeseries table based on time range; also get adjusted start.
adjustedStart, adjustedEnd, distributedTableName, _ := telemetrymetrics.WhichTSTableToUse(
uint64(startMs), uint64(endMs), nil,
)
// Step-floor the window and pick the right tables — matches the bounds the
// QB v5 metric querier uses, so metadataMap covers the same universe the
// ranking sees (see alignedMetricWindow doc).
samplesStartMs, flooredEndMs, tsAdjustedStartMs, distributedTableName, _, _, localSamplesTable := alignedMetricWindow(startMs, endMs)
fpSB := m.buildSamplesTblFingerprintSubQuery(metricNames, startMs, endMs)
fpSB := m.buildSamplesTblFingerprintSubQuery(metricNames, localSamplesTable, samplesStartMs, flooredEndMs)
// Flatten groupBy keys to string names for SQL expressions and result scanning.
groupByCols := make([]string, len(groupBy))
@@ -494,8 +533,8 @@ func (m *module) getMetadata(
innerSB.From(fmt.Sprintf("%s.%s", telemetrymetrics.DBName, distributedTableName))
innerSB.Where(
innerSB.In("metric_name", sqlbuilder.List(metricNames)),
innerSB.GE("unix_milli", adjustedStart),
innerSB.L("unix_milli", adjustedEnd),
innerSB.GE("unix_milli", tsAdjustedStartMs),
innerSB.LE("unix_milli", flooredEndMs),
fmt.Sprintf("fingerprint IN (%s)", innerSB.Var(fpSB)),
)

View File

@@ -34,9 +34,8 @@ func (m *module) getPerGroupHostStatusCounts(
pageGroupsFilterExpr := buildPageGroupsFilterExpr(pageGroups)
filterExpr := mergeFilterExpressions(reqFilterExpr, pageGroupsFilterExpr)
adjustedStart, adjustedEnd, distributedTimeSeriesTableName, _ := telemetrymetrics.WhichTSTableToUse(
uint64(req.Start), uint64(req.End), nil,
)
// Step-floor bounds + resolve tables in one shot to match QB v5 querier.
samplesStartMs, flooredEndMs, tsAdjustedStartMs, distributedTimeSeriesTableName, _, _, localSamplesTable := alignedMetricWindow(req.Start, req.End)
hostNameExpr := fmt.Sprintf("JSONExtractString(labels, '%s')", inframonitoringtypes.HostNameAttrKey)
@@ -55,15 +54,15 @@ func (m *module) getPerGroupHostStatusCounts(
)
// Build a fingerprint subquery to restrict to fingerprints with actual sample
// data in the original time range (not the wider timeseries table window).
fpSB := m.buildSamplesTblFingerprintSubQuery(metricNames, req.Start, req.End)
// data in the floored time range.
fpSB := m.buildSamplesTblFingerprintSubQuery(metricNames, localSamplesTable, samplesStartMs, flooredEndMs)
sb.Select(selectCols...)
sb.From(fmt.Sprintf("%s.%s", telemetrymetrics.DBName, distributedTimeSeriesTableName))
sb.Where(
sb.In("metric_name", sqlbuilder.List(metricNames)),
sb.GE("unix_milli", adjustedStart),
sb.L("unix_milli", adjustedEnd),
sb.GE("unix_milli", tsAdjustedStartMs),
sb.LE("unix_milli", flooredEndMs),
fmt.Sprintf("fingerprint IN (%s)", sb.Var(fpSB)),
)

View File

@@ -9,7 +9,6 @@ import (
"github.com/SigNoz/signoz/pkg/querybuilder"
"github.com/SigNoz/signoz/pkg/telemetrymetrics"
"github.com/SigNoz/signoz/pkg/types/inframonitoringtypes"
"github.com/SigNoz/signoz/pkg/types/metrictypes"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/huandu/go-sqlbuilder"
@@ -190,15 +189,9 @@ func (m *module) getPerGroupNodeConditionCounts(
pageGroupsFilterExpr := buildPageGroupsFilterExpr(pageGroups)
mergedFilterExpr := mergeFilterExpressions(userFilterExpr, pageGroupsFilterExpr)
// Resolve tables. Same convention as pods.
adjustedStart, adjustedEnd, _, localTimeSeriesTable := telemetrymetrics.WhichTSTableToUse(
uint64(start), uint64(end), nil,
)
samplesTable := telemetrymetrics.WhichSamplesTableToUse(
uint64(start), uint64(end),
metrictypes.UnspecifiedType, metrictypes.TimeAggregationUnspecified, nil,
)
valueCol := telemetrymetrics.ValueColumnForSamplesTable(samplesTable)
// Step-floor bounds + resolve tables in one shot to match QB v5 querier.
samplesStartMs, flooredEndMs, tsAdjustedStartMs, _, localTimeSeriesTable, distributedSamplesTable, _ := alignedMetricWindow(start, end)
valueCol := telemetrymetrics.ValueColumnForSamplesTable(distributedSamplesTable)
// ----- timeSeriesFPs -----
timeSeriesFPs := sqlbuilder.NewSelectBuilder()
@@ -215,8 +208,8 @@ func (m *module) getPerGroupNodeConditionCounts(
timeSeriesFPs.From(fmt.Sprintf("%s.%s", telemetrymetrics.DBName, localTimeSeriesTable))
timeSeriesFPs.Where(
timeSeriesFPs.E("metric_name", nodeConditionMetricName),
timeSeriesFPs.GE("unix_milli", adjustedStart),
timeSeriesFPs.L("unix_milli", adjustedEnd),
timeSeriesFPs.GE("unix_milli", tsAdjustedStartMs),
timeSeriesFPs.LE("unix_milli", flooredEndMs),
)
if mergedFilterExpr != "" {
filterClause, err := m.buildFilterClause(ctx, &qbtypes.Filter{Expression: mergedFilterExpr}, start, end)
@@ -249,12 +242,12 @@ func (m *module) getPerGroupNodeConditionCounts(
latestConditionPerNode.Select(latestConditionPerNodeSelectCols...)
latestConditionPerNode.From(fmt.Sprintf(
"%s.%s AS samples INNER JOIN time_series_fps AS tsfp ON samples.fingerprint = tsfp.fingerprint",
telemetrymetrics.DBName, samplesTable,
telemetrymetrics.DBName, distributedSamplesTable,
))
latestConditionPerNode.Where(
latestConditionPerNode.E("samples.metric_name", nodeConditionMetricName),
latestConditionPerNode.GE("samples.unix_milli", start),
latestConditionPerNode.L("samples.unix_milli", end),
latestConditionPerNode.GE("samples.unix_milli", samplesStartMs),
latestConditionPerNode.L("samples.unix_milli", flooredEndMs),
"tsfp.node_name != ''",
)
latestConditionPerNode.GroupBy(latestConditionPerNodeGroupBy...)

View File

@@ -10,7 +10,6 @@ import (
"github.com/SigNoz/signoz/pkg/querybuilder"
"github.com/SigNoz/signoz/pkg/telemetrymetrics"
"github.com/SigNoz/signoz/pkg/types/inframonitoringtypes"
"github.com/SigNoz/signoz/pkg/types/metrictypes"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/huandu/go-sqlbuilder"
@@ -96,14 +95,16 @@ func buildPodRecords(
}
}
if attrs, ok := metadataMap[compositeKey]; ok && isPodUIDInGroupBy {
// the condition above ensures we deduce age only if pod uid is in group by because if
// it's not in group by then we might have multiple pod uids in the same group and hence then podAge wont make sense
if startTimeStr, exists := attrs[podStartTimeAttrKey]; exists && startTimeStr != "" {
if t, err := time.Parse(time.RFC3339, startTimeStr); err == nil {
startTimeMs := t.UnixMilli()
if startTimeMs > 0 {
record.PodAge = reqEnd - startTimeMs
if attrs, ok := metadataMap[compositeKey]; ok {
// podAge only makes sense when pod uid is in groupBy. Otherwise the
// group can contain multiple pods with different start times.
if isPodUIDInGroupBy {
if startTimeStr, exists := attrs[podStartTimeAttrKey]; exists && startTimeStr != "" {
if t, err := time.Parse(time.RFC3339, startTimeStr); err == nil {
startTimeMs := t.UnixMilli()
if startTimeMs > 0 {
record.PodAge = reqEnd - startTimeMs
}
}
}
}
@@ -209,15 +210,9 @@ func (m *module) getPerGroupPodPhaseCounts(
pageGroupsFilterExpr := buildPageGroupsFilterExpr(pageGroups)
mergedFilterExpr := mergeFilterExpressions(userFilterExpr, pageGroupsFilterExpr)
// Resolve tables. Same convention as hosts (distributed names from helpers).
adjustedStart, adjustedEnd, _, localTimeSeriesTable := telemetrymetrics.WhichTSTableToUse(
uint64(start), uint64(end), nil,
)
samplesTable := telemetrymetrics.WhichSamplesTableToUse(
uint64(start), uint64(end),
metrictypes.UnspecifiedType, metrictypes.TimeAggregationUnspecified, nil,
)
valueCol := telemetrymetrics.ValueColumnForSamplesTable(samplesTable)
// Step-floor bounds + resolve tables in one shot to match QB v5 querier.
samplesStartMs, flooredEndMs, tsAdjustedStart, _, localTimeSeriesTable, distributedSamplesTable, _ := alignedMetricWindow(start, end)
valueCol := telemetrymetrics.ValueColumnForSamplesTable(distributedSamplesTable)
// ----- timeSeriesFPs -----
timeSeriesFPs := sqlbuilder.NewSelectBuilder()
@@ -234,8 +229,8 @@ func (m *module) getPerGroupPodPhaseCounts(
timeSeriesFPs.From(fmt.Sprintf("%s.%s", telemetrymetrics.DBName, localTimeSeriesTable))
timeSeriesFPs.Where(
timeSeriesFPs.E("metric_name", podPhaseMetricName),
timeSeriesFPs.GE("unix_milli", adjustedStart),
timeSeriesFPs.L("unix_milli", adjustedEnd),
timeSeriesFPs.GE("unix_milli", tsAdjustedStart),
timeSeriesFPs.LE("unix_milli", flooredEndMs),
)
if mergedFilterExpr != "" {
filterClause, err := m.buildFilterClause(ctx, &qbtypes.Filter{Expression: mergedFilterExpr}, start, end)
@@ -267,12 +262,12 @@ func (m *module) getPerGroupPodPhaseCounts(
latestPhasePerPod.Select(latestPhasePerPodSelectCols...)
latestPhasePerPod.From(fmt.Sprintf(
"%s.%s AS samples INNER JOIN time_series_fps AS tsfp ON samples.fingerprint = tsfp.fingerprint",
telemetrymetrics.DBName, samplesTable,
telemetrymetrics.DBName, distributedSamplesTable,
))
latestPhasePerPod.Where(
latestPhasePerPod.E("samples.metric_name", podPhaseMetricName),
latestPhasePerPod.GE("samples.unix_milli", start),
latestPhasePerPod.L("samples.unix_milli", end),
latestPhasePerPod.GE("samples.unix_milli", samplesStartMs),
latestPhasePerPod.L("samples.unix_milli", flooredEndMs),
"tsfp.pod_uid != ''",
)
latestPhasePerPod.GroupBy(latestPhasePerPodGroupBy...)

View File

@@ -981,7 +981,7 @@ func (m *module) fetchMetricsStatsWithSamples(
ctx = m.withMetricsExplorerContext(ctx, "fetchMetricsStatsWithSamples")
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)
samplesTable, _ := telemetrymetrics.WhichSamplesTableToUse(uint64(req.Start), uint64(req.End), metrictypes.UnspecifiedType, metrictypes.TimeAggregationUnspecified, nil)
countExp := telemetrymetrics.CountExpressionForSamplesTable(samplesTable)
// Timeseries counts per metric
@@ -1155,7 +1155,7 @@ func (m *module) computeSamplesTreemap(ctx context.Context, req *metricsexplorer
ctx = m.withMetricsExplorerContext(ctx, "computeSamplesTreemap")
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)
samplesTable, _ := telemetrymetrics.WhichSamplesTableToUse(uint64(req.Start), uint64(req.End), metrictypes.UnspecifiedType, metrictypes.TimeAggregationUnspecified, nil)
countExp := telemetrymetrics.CountExpressionForSamplesTable(samplesTable)
candidateLimit := req.Limit + 50

View File

@@ -2,11 +2,8 @@ package implspanmapper
import (
"context"
"encoding/json"
"github.com/SigNoz/signoz/pkg/modules/spanmapper"
"github.com/SigNoz/signoz/pkg/query-service/agentConf"
"github.com/SigNoz/signoz/pkg/types/opamptypes"
"github.com/SigNoz/signoz/pkg/types/spantypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
@@ -37,22 +34,11 @@ func (module *module) UpdateGroup(ctx context.Context, orgID, id valuer.UUID, na
return err
}
group.Update(name, condition, enabled, updatedBy)
err = module.store.UpdateGroup(ctx, group)
if err != nil {
return err
}
agentConf.NotifyConfigUpdate(ctx)
return nil
return module.store.UpdateGroup(ctx, group)
}
func (module *module) DeleteGroup(ctx context.Context, orgID, id valuer.UUID) error {
err := module.store.DeleteGroup(ctx, orgID, id)
if err != nil {
return err
}
agentConf.NotifyConfigUpdate(ctx)
return nil
return module.store.DeleteGroup(ctx, orgID, id)
}
func (module *module) ListMappers(ctx context.Context, orgID, groupID valuer.UUID) ([]*spantypes.SpanMapper, error) {
@@ -68,12 +54,7 @@ func (module *module) CreateMapper(ctx context.Context, orgID, groupID valuer.UU
if _, err := module.store.GetGroup(ctx, orgID, groupID); err != nil {
return err
}
err := module.store.CreateMapper(ctx, mapper)
if err != nil {
return err
}
agentConf.NotifyConfigUpdate(ctx)
return nil
return module.store.CreateMapper(ctx, mapper)
}
func (module *module) UpdateMapper(ctx context.Context, orgID, groupID, id valuer.UUID, fieldContext spantypes.FieldContext, config *spantypes.SpanMapperConfig, enabled *bool, updatedBy string) error {
@@ -85,72 +66,9 @@ func (module *module) UpdateMapper(ctx context.Context, orgID, groupID, id value
return err
}
mapper.Update(fieldContext, config, enabled, updatedBy)
err = module.store.UpdateMapper(ctx, mapper)
if err != nil {
return err
}
agentConf.NotifyConfigUpdate(ctx)
return nil
return module.store.UpdateMapper(ctx, mapper)
}
func (module *module) DeleteMapper(ctx context.Context, orgID, groupID, id valuer.UUID) error {
err := module.store.DeleteMapper(ctx, orgID, groupID, id)
if err != nil {
return err
}
agentConf.NotifyConfigUpdate(ctx)
return nil
}
func (module *module) AgentFeatureType() agentConf.AgentFeatureType {
return spantypes.SpanAttrMappingFeatureType
}
func (module *module) RecommendAgentConfig(orgID valuer.UUID, currentConfYaml []byte, configVersion *opamptypes.AgentConfigVersion) ([]byte, string, error) {
ctx := context.Background()
enabled, err := module.listEnabledGroupsWithMappers(ctx, orgID)
if err != nil {
return nil, "", err
}
updatedConf, err := spantypes.GenerateCollectorConfigWithSpanMapperProcessor(currentConfYaml, enabled)
if err != nil {
return nil, "", err
}
serialized, err := json.Marshal(enabled)
if err != nil {
return nil, "", err
}
return updatedConf, string(serialized), nil
}
// listEnabledGroupsWithMappers returns groups with their mappers.
func (module *module) listEnabledGroupsWithMappers(ctx context.Context, orgID valuer.UUID) ([]*spantypes.SpanMapperGroupWithMappers, error) {
enabled := true
groups, err := module.store.ListGroups(ctx, orgID, &spantypes.ListSpanMapperGroupsQuery{Enabled: &enabled})
if err != nil {
return nil, err
}
out := make([]*spantypes.SpanMapperGroupWithMappers, 0, len(groups))
for _, g := range groups {
mappers, err := module.store.ListMappers(ctx, orgID, g.ID)
if err != nil {
return nil, err
}
enabledMappers := make([]*spantypes.SpanMapper, 0, len(mappers))
for _, m := range mappers {
if m.Enabled {
enabledMappers = append(enabledMappers, m)
}
}
if len(enabledMappers) == 0 {
continue
}
out = append(out, &spantypes.SpanMapperGroupWithMappers{Group: g, Mappers: enabledMappers})
}
return out, nil
return module.store.DeleteMapper(ctx, orgID, groupID, id)
}

View File

@@ -4,16 +4,12 @@ import (
"context"
"net/http"
"github.com/SigNoz/signoz/pkg/query-service/agentConf"
"github.com/SigNoz/signoz/pkg/types/spantypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
// Module defines the business logic for span attribute mapping groups and mappers.
type Module interface {
// Since this module interacts with OpAMP, it must implement the AgentFeature interface.
agentConf.AgentFeature
// Group operations
ListGroups(ctx context.Context, orgID valuer.UUID, q *spantypes.ListSpanMapperGroupsQuery) ([]*spantypes.SpanMapperGroup, error)
GetGroup(ctx context.Context, orgID, id valuer.UUID) (*spantypes.SpanMapperGroup, error)

View File

@@ -6,7 +6,7 @@ import (
"github.com/SigNoz/signoz/pkg/http/binding"
"github.com/SigNoz/signoz/pkg/http/render"
"github.com/SigNoz/signoz/pkg/modules/tracedetail"
"github.com/SigNoz/signoz/pkg/types/spantypes"
"github.com/SigNoz/signoz/pkg/types/tracedetailtypes"
"github.com/gorilla/mux"
)
@@ -19,7 +19,7 @@ func NewHandler(module tracedetail.Module) tracedetail.Handler {
}
func (h *handler) GetWaterfall(rw http.ResponseWriter, r *http.Request) {
req := new(spantypes.PostableWaterfall)
req := new(tracedetailtypes.PostableWaterfall)
if err := binding.JSON.BindBody(r.Body, req); err != nil {
render.Error(rw, err)
return

View File

@@ -5,16 +5,16 @@ import (
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/modules/tracedetail"
"github.com/SigNoz/signoz/pkg/types/spantypes"
"github.com/SigNoz/signoz/pkg/types/tracedetailtypes"
)
type module struct {
store spantypes.TraceStore
store tracedetailtypes.TraceStore
settings factory.ScopedProviderSettings
config tracedetail.Config
}
func NewModule(traceStore spantypes.TraceStore, providerSettings factory.ProviderSettings, cfg tracedetail.Config) *module {
func NewModule(traceStore tracedetailtypes.TraceStore, providerSettings factory.ProviderSettings, cfg tracedetail.Config) *module {
scopedProviderSettings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/modules/tracedetail/impltracedetail")
return &module{
config: cfg,
@@ -23,7 +23,7 @@ func NewModule(traceStore spantypes.TraceStore, providerSettings factory.Provide
}
}
func (m *module) GetWaterfall(ctx context.Context, traceID string, req *spantypes.PostableWaterfall) (*spantypes.GettableWaterfallTrace, error) {
func (m *module) GetWaterfall(ctx context.Context, traceID string, req *tracedetailtypes.PostableWaterfall) (*tracedetailtypes.GettableWaterfallTrace, error) {
waterfallTrace, err := m.getTraceData(ctx, traceID)
if err != nil {
return nil, err
@@ -37,16 +37,16 @@ func (m *module) GetWaterfall(ctx context.Context, traceID string, req *spantype
m.config.Waterfall.MaxDepthToAutoExpand,
)
aggregationResults := make([]spantypes.SpanAggregationResult, 0, len(req.Aggregations))
aggregationResults := make([]tracedetailtypes.SpanAggregationResult, 0, len(req.Aggregations))
for _, a := range req.Aggregations {
aggregationResults = append(aggregationResults, waterfallTrace.GetSpanAggregation(a.Aggregation, a.Field))
}
return spantypes.NewGettableWaterfallTrace(waterfallTrace, selectedSpans, uncollapsedSpans, selectedAllSpans, aggregationResults), nil
return tracedetailtypes.NewGettableWaterfallTrace(waterfallTrace, selectedSpans, uncollapsedSpans, selectedAllSpans, aggregationResults), nil
}
// getTraceData returns the waterfall cache for the given traceID with fallback on DB.
func (m *module) getTraceData(ctx context.Context, traceID string) (*spantypes.WaterfallTrace, error) {
func (m *module) getTraceData(ctx context.Context, traceID string) (*tracedetailtypes.WaterfallTrace, error) {
summary, err := m.store.GetTraceSummary(ctx, traceID)
if err != nil {
return nil, err
@@ -58,9 +58,9 @@ func (m *module) getTraceData(ctx context.Context, traceID string) (*spantypes.W
}
if len(spanItems) == 0 {
return nil, spantypes.ErrTraceNotFound
return nil, tracedetailtypes.ErrTraceNotFound
}
traceData := spantypes.NewWaterfallTraceFromSpans(spanItems)
traceData := tracedetailtypes.NewWaterfallTraceFromSpans(spanItems)
return traceData, nil
}

View File

@@ -9,7 +9,7 @@ import (
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/telemetrystore"
"github.com/SigNoz/signoz/pkg/types/spantypes"
"github.com/SigNoz/signoz/pkg/types/tracedetailtypes"
)
type traceStore struct {
@@ -20,28 +20,28 @@ func NewTraceStore(ts telemetrystore.TelemetryStore) *traceStore {
return &traceStore{telemetryStore: ts}
}
func (s *traceStore) GetTraceSummary(ctx context.Context, traceID string) (*spantypes.TraceSummary, error) {
func (s *traceStore) GetTraceSummary(ctx context.Context, traceID string) (*tracedetailtypes.TraceSummary, error) {
sb := sqlbuilder.NewSelectBuilder()
sb.Select("trace_id", "min(start) AS start", "max(end) AS end", "sum(num_spans) AS num_spans")
sb.From(fmt.Sprintf("%s.%s", spantypes.TraceDB, spantypes.TraceSummaryTable))
sb.From(fmt.Sprintf("%s.%s", tracedetailtypes.TraceDB, tracedetailtypes.TraceSummaryTable))
sb.Where(sb.E("trace_id", traceID))
sb.GroupBy("trace_id")
query, args := sb.BuildWithFlavor(sqlbuilder.ClickHouse)
var summary spantypes.TraceSummary
var summary tracedetailtypes.TraceSummary
err := s.telemetryStore.ClickhouseDB().QueryRow(ctx, query, args...).Scan(
&summary.TraceID, &summary.Start, &summary.End, &summary.NumSpans,
)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, spantypes.ErrTraceNotFound
return nil, tracedetailtypes.ErrTraceNotFound
}
return nil, errors.WrapInternalf(err, errors.CodeInternal, "error querying trace summary")
}
return &summary, nil
}
func (s *traceStore) GetTraceSpans(ctx context.Context, traceID string, summary *spantypes.TraceSummary) ([]spantypes.StorableSpan, error) {
func (s *traceStore) GetTraceSpans(ctx context.Context, traceID string, summary *tracedetailtypes.TraceSummary) ([]tracedetailtypes.StorableSpan, error) {
// DISTINCT ON (span_id) is ClickHouse-specific syntax not supported by sqlbuilder
query := fmt.Sprintf(`
SELECT DISTINCT ON (span_id)
@@ -55,9 +55,9 @@ func (s *traceStore) GetTraceSpans(ctx context.Context, traceID string, summary
FROM %s.%s
WHERE trace_id=? AND ts_bucket_start>=? AND ts_bucket_start<=?
ORDER BY timestamp ASC, name ASC`,
spantypes.TraceDB, spantypes.TraceTable,
tracedetailtypes.TraceDB, tracedetailtypes.TraceTable,
)
var spanItems []spantypes.StorableSpan
var spanItems []tracedetailtypes.StorableSpan
err := s.telemetryStore.ClickhouseDB().Select(
ctx, &spanItems, query,
traceID,

View File

@@ -37,7 +37,7 @@ import (
"fmt"
"testing"
"github.com/SigNoz/signoz/pkg/types/spantypes"
"github.com/SigNoz/signoz/pkg/types/tracedetailtypes"
"github.com/stretchr/testify/assert"
)
@@ -45,8 +45,8 @@ import (
// Helpers
// ─────────────────────────────────────────────────────────────────────────────
func mkSpan(id, service string, children ...*spantypes.WaterfallSpan) *spantypes.WaterfallSpan {
return &spantypes.WaterfallSpan{
func mkSpan(id, service string, children ...*tracedetailtypes.WaterfallSpan) *tracedetailtypes.WaterfallSpan {
return &tracedetailtypes.WaterfallSpan{
SpanID: id,
ServiceName: service,
Name: id + "-op",
@@ -54,7 +54,7 @@ func mkSpan(id, service string, children ...*spantypes.WaterfallSpan) *spantypes
}
}
func spanIDs(spans []*spantypes.WaterfallSpan) []string {
func spanIDs(spans []*tracedetailtypes.WaterfallSpan) []string {
ids := make([]string, len(spans))
for i, s := range spans {
ids[i] = s.SpanID
@@ -62,10 +62,10 @@ func spanIDs(spans []*spantypes.WaterfallSpan) []string {
return ids
}
func buildSpanMap(roots ...*spantypes.WaterfallSpan) map[string]*spantypes.WaterfallSpan {
m := map[string]*spantypes.WaterfallSpan{}
var walk func(*spantypes.WaterfallSpan)
walk = func(s *spantypes.WaterfallSpan) {
func buildSpanMap(roots ...*tracedetailtypes.WaterfallSpan) map[string]*tracedetailtypes.WaterfallSpan {
m := map[string]*tracedetailtypes.WaterfallSpan{}
var walk func(*tracedetailtypes.WaterfallSpan)
walk = func(s *tracedetailtypes.WaterfallSpan) {
m[s.SpanID] = s
for _, c := range s.Children {
walk(c)
@@ -80,8 +80,8 @@ func buildSpanMap(roots ...*spantypes.WaterfallSpan) map[string]*spantypes.Water
}
// makeChain builds a linear trace: span0 → span1 → … → span(n-1).
func makeChain(n int) (*spantypes.WaterfallSpan, map[string]*spantypes.WaterfallSpan, []string) {
spans := make([]*spantypes.WaterfallSpan, n)
func makeChain(n int) (*tracedetailtypes.WaterfallSpan, map[string]*tracedetailtypes.WaterfallSpan, []string) {
spans := make([]*tracedetailtypes.WaterfallSpan, n)
for i := n - 1; i >= 0; i-- {
if i == n-1 {
spans[i] = mkSpan(fmt.Sprintf("span%d", i), "svc")
@@ -96,8 +96,8 @@ func makeChain(n int) (*spantypes.WaterfallSpan, map[string]*spantypes.Waterfall
return spans[0], buildSpanMap(spans[0]), uncollapsed
}
func getWaterfallTrace(roots []*spantypes.WaterfallSpan, spanMap map[string]*spantypes.WaterfallSpan) *spantypes.WaterfallTrace {
return spantypes.NewWaterfallTrace(0, 0, uint64(len(spanMap)), 0, spanMap, nil, roots, false)
func getWaterfallTrace(roots []*tracedetailtypes.WaterfallSpan, spanMap map[string]*tracedetailtypes.WaterfallSpan) *tracedetailtypes.WaterfallTrace {
return tracedetailtypes.NewWaterfallTrace(0, 0, uint64(len(spanMap)), 0, spanMap, nil, roots, false)
}
// ─────────────────────────────────────────────────────────────────────────────
@@ -107,7 +107,7 @@ func getWaterfallTrace(roots []*spantypes.WaterfallSpan, spanMap map[string]*spa
func TestGetSelectedSpans_SpanOrdering(t *testing.T) {
tests := []struct {
name string
buildRoots func() ([]*spantypes.WaterfallSpan, map[string]*spantypes.WaterfallSpan)
buildRoots func() ([]*tracedetailtypes.WaterfallSpan, map[string]*tracedetailtypes.WaterfallSpan)
uncollapsedSpans []string
selectedSpanID string
wantSpanIDs []string
@@ -115,12 +115,12 @@ func TestGetSelectedSpans_SpanOrdering(t *testing.T) {
{
// Pre-order traversal is preserved: parent before children, siblings left-to-right.
name: "pre_order_traversal",
buildRoots: func() ([]*spantypes.WaterfallSpan, map[string]*spantypes.WaterfallSpan) {
buildRoots: func() ([]*tracedetailtypes.WaterfallSpan, map[string]*tracedetailtypes.WaterfallSpan) {
root := mkSpan("root", "svc",
mkSpan("child1", "svc", mkSpan("grandchild", "svc")),
mkSpan("child2", "svc"),
)
return []*spantypes.WaterfallSpan{root}, buildSpanMap(root)
return []*tracedetailtypes.WaterfallSpan{root}, buildSpanMap(root)
},
uncollapsedSpans: []string{"root", "child1"},
selectedSpanID: "root",
@@ -133,12 +133,12 @@ func TestGetSelectedSpans_SpanOrdering(t *testing.T) {
// ├─ childA (uncollapsed) → grandchildA ✓
// └─ childB (uncollapsed) → grandchildB ✓
name: "multiple_uncollapsed",
buildRoots: func() ([]*spantypes.WaterfallSpan, map[string]*spantypes.WaterfallSpan) {
buildRoots: func() ([]*tracedetailtypes.WaterfallSpan, map[string]*tracedetailtypes.WaterfallSpan) {
root := mkSpan("root", "svc",
mkSpan("childA", "svc", mkSpan("grandchildA", "svc")),
mkSpan("childB", "svc", mkSpan("grandchildB", "svc")),
)
return []*spantypes.WaterfallSpan{root}, buildSpanMap(root)
return []*tracedetailtypes.WaterfallSpan{root}, buildSpanMap(root)
},
uncollapsedSpans: []string{"root", "childA", "childB"},
selectedSpanID: "root",
@@ -154,7 +154,7 @@ func TestGetSelectedSpans_SpanOrdering(t *testing.T) {
// │ └─ grandchild2 ✓
// └─ childB ← selected (not expanded)
name: "manual_uncollapse",
buildRoots: func() ([]*spantypes.WaterfallSpan, map[string]*spantypes.WaterfallSpan) {
buildRoots: func() ([]*tracedetailtypes.WaterfallSpan, map[string]*tracedetailtypes.WaterfallSpan) {
root := mkSpan("root", "svc",
mkSpan("childA", "svc",
mkSpan("grandchild1", "svc", mkSpan("greatGrandchild", "svc")),
@@ -162,7 +162,7 @@ func TestGetSelectedSpans_SpanOrdering(t *testing.T) {
),
mkSpan("childB", "svc"),
)
return []*spantypes.WaterfallSpan{root}, buildSpanMap(root)
return []*tracedetailtypes.WaterfallSpan{root}, buildSpanMap(root)
},
uncollapsedSpans: []string{"childA"},
selectedSpanID: "childB",
@@ -171,12 +171,12 @@ func TestGetSelectedSpans_SpanOrdering(t *testing.T) {
{
// A collapsed span hides all children.
name: "collapsed_span_hides_children",
buildRoots: func() ([]*spantypes.WaterfallSpan, map[string]*spantypes.WaterfallSpan) {
buildRoots: func() ([]*tracedetailtypes.WaterfallSpan, map[string]*tracedetailtypes.WaterfallSpan) {
root := mkSpan("root", "svc",
mkSpan("child1", "svc"),
mkSpan("child2", "svc"),
)
return []*spantypes.WaterfallSpan{root}, buildSpanMap(root)
return []*tracedetailtypes.WaterfallSpan{root}, buildSpanMap(root)
},
uncollapsedSpans: []string{},
selectedSpanID: "root",
@@ -187,13 +187,13 @@ func TestGetSelectedSpans_SpanOrdering(t *testing.T) {
//
// root → parent → selected
name: "path_to_selected_is_uncollapsed",
buildRoots: func() ([]*spantypes.WaterfallSpan, map[string]*spantypes.WaterfallSpan) {
buildRoots: func() ([]*tracedetailtypes.WaterfallSpan, map[string]*tracedetailtypes.WaterfallSpan) {
root := mkSpan("root", "svc",
mkSpan("parent", "svc",
mkSpan("selected", "svc"),
),
)
return []*spantypes.WaterfallSpan{root}, buildSpanMap(root)
return []*tracedetailtypes.WaterfallSpan{root}, buildSpanMap(root)
},
uncollapsedSpans: []string{},
selectedSpanID: "selected",
@@ -206,14 +206,14 @@ func TestGetSelectedSpans_SpanOrdering(t *testing.T) {
// ├─ unrelated → unrelated-child (✗)
// └─ parent → selected
name: "siblings_not_expanded",
buildRoots: func() ([]*spantypes.WaterfallSpan, map[string]*spantypes.WaterfallSpan) {
buildRoots: func() ([]*tracedetailtypes.WaterfallSpan, map[string]*tracedetailtypes.WaterfallSpan) {
root := mkSpan("root", "svc",
mkSpan("unrelated", "svc", mkSpan("unrelated-child", "svc")),
mkSpan("parent", "svc",
mkSpan("selected", "svc"),
),
)
return []*spantypes.WaterfallSpan{root}, buildSpanMap(root)
return []*tracedetailtypes.WaterfallSpan{root}, buildSpanMap(root)
},
uncollapsedSpans: []string{},
selectedSpanID: "selected",
@@ -223,9 +223,9 @@ func TestGetSelectedSpans_SpanOrdering(t *testing.T) {
{
// An unknown selectedSpanID must not panic; returns a window from index 0.
name: "unknown_selected_span",
buildRoots: func() ([]*spantypes.WaterfallSpan, map[string]*spantypes.WaterfallSpan) {
buildRoots: func() ([]*tracedetailtypes.WaterfallSpan, map[string]*tracedetailtypes.WaterfallSpan) {
root := mkSpan("root", "svc", mkSpan("child", "svc"))
return []*spantypes.WaterfallSpan{root}, buildSpanMap(root)
return []*tracedetailtypes.WaterfallSpan{root}, buildSpanMap(root)
},
uncollapsedSpans: []string{},
selectedSpanID: "nonexistent",
@@ -257,10 +257,10 @@ func TestGetSelectedSpans_MultipleRoots(t *testing.T) {
root2 := mkSpan("root2", "svc-b", mkSpan("child2", "svc-b"))
spanMap := buildSpanMap(root1, root2)
trace := getWaterfallTrace([]*spantypes.WaterfallSpan{root1, root2}, spanMap)
trace := getWaterfallTrace([]*tracedetailtypes.WaterfallSpan{root1, root2}, spanMap)
spans, _ := trace.GetSelectedSpans([]string{"root1", "root2"}, "root1", 500, 5)
traceRespnose := spantypes.NewGettableWaterfallTrace(trace, spans, nil, false, nil)
traceRespnose := tracedetailtypes.NewGettableWaterfallTrace(trace, spans, nil, false, nil)
assert.Equal(t, []string{"root1", "child1", "root2", "child2"}, spanIDs(spans), "root1 subtree must precede root2 subtree")
assert.Equal(t, "svc-a", traceRespnose.RootServiceName, "metadata comes from first root")
@@ -274,7 +274,7 @@ func TestGetSelectedSpans_MultipleRoots(t *testing.T) {
func TestGetSelectedSpans_UncollapsedTracking(t *testing.T) {
tests := []struct {
name string
buildRoot func() (*spantypes.WaterfallSpan, map[string]*spantypes.WaterfallSpan)
buildRoot func() (*tracedetailtypes.WaterfallSpan, map[string]*tracedetailtypes.WaterfallSpan)
uncollapsedSpans []string
selectedSpanID string
wantSpanIDs []string
@@ -283,7 +283,7 @@ func TestGetSelectedSpans_UncollapsedTracking(t *testing.T) {
{
// The path-to-selected spans are returned in updatedUncollapsedSpans.
name: "path_returned_in_uncollapsed",
buildRoot: func() (*spantypes.WaterfallSpan, map[string]*spantypes.WaterfallSpan) {
buildRoot: func() (*tracedetailtypes.WaterfallSpan, map[string]*tracedetailtypes.WaterfallSpan) {
root := mkSpan("root", "svc",
mkSpan("parent", "svc",
mkSpan("selected", "svc"),
@@ -301,7 +301,7 @@ func TestGetSelectedSpans_UncollapsedTracking(t *testing.T) {
{
// Siblings of ancestors are not tracked as uncollapsed.
name: "siblings_not_in_uncollapsed",
buildRoot: func() (*spantypes.WaterfallSpan, map[string]*spantypes.WaterfallSpan) {
buildRoot: func() (*tracedetailtypes.WaterfallSpan, map[string]*tracedetailtypes.WaterfallSpan) {
root := mkSpan("root", "svc",
mkSpan("unrelated", "svc", mkSpan("unrelated-child", "svc")),
mkSpan("parent", "svc",
@@ -330,7 +330,7 @@ func TestGetSelectedSpans_UncollapsedTracking(t *testing.T) {
// └─ grandchildB (internal ✓)
// └─ leafB (leaf ✗)
name: "auto_expanded_spans_returned",
buildRoot: func() (*spantypes.WaterfallSpan, map[string]*spantypes.WaterfallSpan) {
buildRoot: func() (*tracedetailtypes.WaterfallSpan, map[string]*tracedetailtypes.WaterfallSpan) {
root := mkSpan("root", "svc",
mkSpan("childA", "svc",
mkSpan("grandchildA", "svc",
@@ -361,7 +361,7 @@ func TestGetSelectedSpans_UncollapsedTracking(t *testing.T) {
// If the selected span is already in uncollapsedSpans,
// it should appear exactly once in the result.
name: "duplicate_in_uncollapsed",
buildRoot: func() (*spantypes.WaterfallSpan, map[string]*spantypes.WaterfallSpan) {
buildRoot: func() (*tracedetailtypes.WaterfallSpan, map[string]*tracedetailtypes.WaterfallSpan) {
root := mkSpan("root", "svc",
mkSpan("selected", "svc", mkSpan("child", "svc")),
)
@@ -384,7 +384,7 @@ func TestGetSelectedSpans_UncollapsedTracking(t *testing.T) {
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
root, spanMap := tc.buildRoot()
trace := getWaterfallTrace([]*spantypes.WaterfallSpan{root}, spanMap)
trace := getWaterfallTrace([]*tracedetailtypes.WaterfallSpan{root}, spanMap)
spans, uncollapsed := trace.GetSelectedSpans(tc.uncollapsedSpans, tc.selectedSpanID, 500, 5)
if tc.wantSpanIDs != nil {
assert.Equal(t, tc.wantSpanIDs, spanIDs(spans))
@@ -412,10 +412,10 @@ func TestGetSelectedSpans_SpanMetadata(t *testing.T) {
mkSpan("child2", "svc"),
)
spanMap := buildSpanMap(root)
trace := getWaterfallTrace([]*spantypes.WaterfallSpan{root}, spanMap)
trace := getWaterfallTrace([]*tracedetailtypes.WaterfallSpan{root}, spanMap)
spans, _ := trace.GetSelectedSpans([]string{"root", "child1"}, "root", 500, 5)
byID := map[string]*spantypes.WaterfallSpan{}
byID := map[string]*tracedetailtypes.WaterfallSpan{}
for _, s := range spans {
byID[s.SpanID] = s
}
@@ -478,7 +478,7 @@ func TestGetSelectedSpans_Window(t *testing.T) {
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
root, spanMap, uncollapsed := makeChain(600)
trace := getWaterfallTrace([]*spantypes.WaterfallSpan{root}, spanMap)
trace := getWaterfallTrace([]*tracedetailtypes.WaterfallSpan{root}, spanMap)
spans, _ := trace.GetSelectedSpans(uncollapsed, tc.selectedSpanID, 500, 5)
assert.Equal(t, tc.wantLen, len(spans), "window size")
@@ -536,7 +536,7 @@ func TestGetSelectedSpans_DepthCountedFromSelectedSpan(t *testing.T) {
root := mkSpan("root", "svc", mkSpan("A", "svc", selected))
spanMap := buildSpanMap(root)
trace := getWaterfallTrace([]*spantypes.WaterfallSpan{root}, spanMap)
trace := getWaterfallTrace([]*tracedetailtypes.WaterfallSpan{root}, spanMap)
spans, _ := trace.GetSelectedSpans([]string{"selected"}, "selected", 500, 5)
ids := spanIDs(spans)
@@ -565,9 +565,9 @@ func TestGetAllSpans(t *testing.T) {
),
),
)
trace := getWaterfallTrace([]*spantypes.WaterfallSpan{root}, nil)
trace := getWaterfallTrace([]*tracedetailtypes.WaterfallSpan{root}, nil)
spans := trace.GetAllSpans()
traceResponse := spantypes.NewGettableWaterfallTrace(trace, spans, nil, true, nil)
traceResponse := tracedetailtypes.NewGettableWaterfallTrace(trace, spans, nil, true, nil)
assert.ElementsMatch(t, spanIDs(spans), []string{"root", "childA", "grandchildA", "leafA", "childB", "grandchildB", "leafB"})
assert.Equal(t, "svc", traceResponse.RootServiceName)
assert.Equal(t, "root-op", traceResponse.RootServiceEntryPoint)

View File

@@ -4,7 +4,7 @@ import (
"context"
"net/http"
"github.com/SigNoz/signoz/pkg/types/spantypes"
"github.com/SigNoz/signoz/pkg/types/tracedetailtypes"
)
// Handler exposes HTTP handlers for trace detail APIs.
@@ -14,5 +14,5 @@ type Handler interface {
// Module defines the business logic for trace detail operations.
type Module interface {
GetWaterfall(ctx context.Context, traceID string, req *spantypes.PostableWaterfall) (*spantypes.GettableWaterfallTrace, error)
GetWaterfall(ctx context.Context, traceID string, req *tracedetailtypes.PostableWaterfall) (*tracedetailtypes.GettableWaterfallTrace, error)
}

View File

@@ -240,7 +240,7 @@ func (b *MetricQueryStatementBuilder) buildTemporalAggDeltaFastPath(
sb.SelectMore(fmt.Sprintf("%s AS value", aggCol))
tbl := WhichSamplesTableToUse(start, end, query.Aggregations[0].Type, query.Aggregations[0].TimeAggregation, query.Aggregations[0].TableHints)
tbl, _ := WhichSamplesTableToUse(start, end, query.Aggregations[0].Type, query.Aggregations[0].TimeAggregation, query.Aggregations[0].TableHints)
sb.From(fmt.Sprintf("%s.%s AS points", DBName, tbl))
sb.JoinWithOption(sqlbuilder.InnerJoin, timeSeriesCTE, "points.fingerprint = filtered_time_series.fingerprint")
sb.Where(
@@ -369,7 +369,7 @@ func (b *MetricQueryStatementBuilder) buildTemporalAggDelta(
sb.SelectMore(fmt.Sprintf("%s AS per_series_value", aggCol))
tbl := WhichSamplesTableToUse(start, end, query.Aggregations[0].Type, query.Aggregations[0].TimeAggregation, query.Aggregations[0].TableHints)
tbl, _ := WhichSamplesTableToUse(start, end, query.Aggregations[0].Type, query.Aggregations[0].TimeAggregation, query.Aggregations[0].TableHints)
sb.From(fmt.Sprintf("%s.%s AS points", DBName, tbl))
sb.JoinWithOption(sqlbuilder.InnerJoin, timeSeriesCTE, "points.fingerprint = filtered_time_series.fingerprint")
sb.Where(
@@ -410,7 +410,7 @@ func (b *MetricQueryStatementBuilder) buildTemporalAggCumulativeOrUnspecified(
}
baseSb.SelectMore(fmt.Sprintf("%s AS per_series_value", aggCol))
tbl := WhichSamplesTableToUse(start, end, query.Aggregations[0].Type, query.Aggregations[0].TimeAggregation, query.Aggregations[0].TableHints)
tbl, _ := WhichSamplesTableToUse(start, end, query.Aggregations[0].Type, query.Aggregations[0].TimeAggregation, query.Aggregations[0].TableHints)
baseSb.From(fmt.Sprintf("%s.%s AS points", DBName, tbl))
baseSb.JoinWithOption(sqlbuilder.InnerJoin, timeSeriesCTE, "points.fingerprint = filtered_time_series.fingerprint")
baseSb.Where(
@@ -501,7 +501,7 @@ func (b *MetricQueryStatementBuilder) buildTemporalAggForMultipleTemporalities(
sb.SelectMore(expr)
}
tbl := WhichSamplesTableToUse(start, end, query.Aggregations[0].Type, query.Aggregations[0].TimeAggregation, query.Aggregations[0].TableHints)
tbl, _ := WhichSamplesTableToUse(start, end, query.Aggregations[0].Type, query.Aggregations[0].TimeAggregation, query.Aggregations[0].TableHints)
sb.From(fmt.Sprintf("%s.%s AS points", DBName, tbl))
sb.JoinWithOption(sqlbuilder.InnerJoin, timeSeriesCTE, "points.fingerprint = filtered_time_series.fingerprint")
sb.Where(

View File

@@ -135,44 +135,56 @@ func ValueColumnForSamplesTable(tableName string) string {
return "value"
}
// start and end are in milliseconds
// we have three tables for samples
// 1. distributed_samples_v4
// 2. distributed_samples_v4_agg_5m - for queries with time range above or equal to 1 day and less than 1 week
// 3. distributed_samples_v4_agg_30m - for queries with time range above or equal to 1 week
// if the `timeAggregation` is `count_distinct` we can't use the aggregated tables because they don't support it.
// WhichSamplesTableToUse returns the distributed and local samples table names
// (in that order) appropriate for the given window, metric type, and time aggregation.
//
// start and end are in milliseconds. We have three tables for samples:
// 1. distributed_samples_v4
// 2. distributed_samples_v4_agg_5m — for queries with time range >= 1 day and < 1 week
// 3. distributed_samples_v4_agg_30m — for queries with time range >= 1 week
//
// If the `timeAggregation` is `count_distinct` we can't use the aggregated tables
// because they don't support it.
func WhichSamplesTableToUse(
start, end uint64,
metricType metrictypes.Type,
timeAggregation metrictypes.TimeAggregation,
tableHints *metrictypes.MetricTableHints,
) string {
) (string, string) {
// if we have a hint for the table, we need to use it
// the hint will be used to override the default table selection logic
if tableHints != nil {
if tableHints.SamplesTableName != "" {
return tableHints.SamplesTableName
// the hint will be used to override the default table selection logic.
// SamplesTableName is the distributed name; derive the local via switch.
if tableHints != nil && tableHints.SamplesTableName != "" {
switch tableHints.SamplesTableName {
case SamplesV4TableName:
return SamplesV4TableName, SamplesV4LocalTableName
case SamplesV4Agg5mTableName:
return SamplesV4Agg5mTableName, SamplesV4Agg5mLocalTableName
case SamplesV4Agg30mTableName:
return SamplesV4Agg30mTableName, SamplesV4Agg30mLocalTableName
case ExpHistogramTableName:
return ExpHistogramTableName, ExpHistogramLocalTableName
}
return tableHints.SamplesTableName, tableHints.SamplesTableName
}
// we don't have any aggregated table for sketches (yet)
if metricType == metrictypes.ExpHistogramType {
return ExpHistogramLocalTableName
return ExpHistogramTableName, ExpHistogramLocalTableName
}
// if the time aggregation is count_distinct, we need to use the distributed_samples_v4 table
// because the aggregated tables don't support count_distinct
if timeAggregation == metrictypes.TimeAggregationCountDistinct {
return SamplesV4TableName
return SamplesV4TableName, SamplesV4LocalTableName
}
if end-start < oneDayInMilliseconds+offsetBucket {
return SamplesV4TableName
return SamplesV4TableName, SamplesV4LocalTableName
} else if end-start < oneWeekInMilliseconds+offsetBucket {
return SamplesV4Agg5mTableName
} else {
return SamplesV4Agg30mTableName
return SamplesV4Agg5mTableName, SamplesV4Agg5mLocalTableName
}
return SamplesV4Agg30mTableName, SamplesV4Agg30mLocalTableName
}
func AggregationColumnForSamplesTable(
@@ -182,7 +194,7 @@ func AggregationColumnForSamplesTable(
timeAggregation metrictypes.TimeAggregation,
tableHints *metrictypes.MetricTableHints,
) (string, error) {
tableName := WhichSamplesTableToUse(start, end, metricType, timeAggregation, tableHints)
tableName, _ := WhichSamplesTableToUse(start, end, metricType, timeAggregation, tableHints)
var aggregationColumn string
switch temporality {
case metrictypes.Delta:

View File

@@ -257,7 +257,10 @@ func (s SpaceAggregation) Percentile() float64 {
}
// MetricTableHints is a struct that contains tables to use instead of the derived tables
// from the start and end time, for internal use only when we need to override the derived tables.
//
// Convention :
// - TimeSeriesTableName: the LOCAL table name (e.g. "time_series_v4_1day").
// - SamplesTableName: the DISTRIBUTED table name (e.g. "distributed_samples_v4_agg_5m").
type MetricTableHints struct {
TimeSeriesTableName string
SamplesTableName string

View File

@@ -13,9 +13,7 @@ var (
ErrCodeMappingGroupAlreadyExists = errors.MustNewCode("span_attribute_mapping_group_already_exists")
)
// SpanMapperGroupCondition gates whether a group's rules run for a given span.
// A group runs when any attribute or resource key on the span CONTAINS one of
// the listed substrings (plain substring match — no glob syntax).
// A group runs when any of the listed attribute/resource key patterns match.
type SpanMapperGroupCondition struct {
Attributes []string `json:"attributes" required:"true" nullable:"true"`
Resource []string `json:"resource" required:"true" nullable:"true"`

View File

@@ -1,140 +0,0 @@
package spantypes
import (
"sort"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/query-service/agentConf"
"gopkg.in/yaml.v3"
)
const (
SpanAttrMappingFeatureType agentConf.AgentFeatureType = "span_attr_mapping"
ProcessorName = "signozspanmapper"
)
var (
ErrCodeInvalidCollectorConfig = errors.MustNewCode("invalid_collector_config")
ErrCodeBuildMappingProcessorConfig = errors.MustNewCode("build_mapping_processor_config")
)
type SpanMapperGroupWithMappers struct {
Group *SpanMapperGroup `json:"group"`
Mappers []*SpanMapper `json:"mappers"`
}
// spanMapperProcessorConfig is the collector config for signozspanmapper.
type spanMapperProcessorConfig struct {
Groups []spanMapperProcessorGroup `yaml:"groups" json:"groups"`
}
type spanMapperProcessorGroup struct {
ID string `yaml:"id" json:"id"`
ExistsAny spanMapperProcessorExistsAny `yaml:"exists_any" json:"exists_any"`
Attributes []spanMapperProcessorAttribute `yaml:"attributes" json:"attributes"`
}
type spanMapperProcessorExistsAny struct {
Attributes []string `yaml:"attributes,omitempty" json:"attributes,omitempty"`
Resource []string `yaml:"resource,omitempty" json:"resource,omitempty"`
}
type spanMapperProcessorAttribute struct {
Target string `yaml:"target" json:"target"`
Context string `yaml:"context,omitempty" json:"context,omitempty"`
Sources []spanMapperProcessorSource `yaml:"sources" json:"sources"`
}
type spanMapperProcessorSource struct {
Key string `yaml:"key" json:"key"`
Action string `yaml:"action,omitempty" json:"action,omitempty"`
}
func GenerateCollectorConfigWithSpanMapperProcessor(currentConfYaml []byte, groups []*SpanMapperGroupWithMappers) ([]byte, error) {
var collectorConf map[string]any
if err := yaml.Unmarshal(currentConfYaml, &collectorConf); err != nil {
return nil, errors.Wrapf(err, errors.TypeInvalidInput, ErrCodeInvalidCollectorConfig, "failed to unmarshal collector config")
}
// rare but don't do anything in this case, also means it's just comments.
if collectorConf == nil {
collectorConf = map[string]any{}
}
processors := map[string]any{}
if existing, ok := collectorConf["processors"]; ok && existing != nil {
p, ok := existing.(map[string]any)
if !ok {
return nil, errors.Newf(errors.TypeInvalidInput, ErrCodeInvalidCollectorConfig, "collector config 'processors' must be a mapping, got %T", existing)
}
processors = p
}
procConfig := buildProcessorConfig(groups)
processors[ProcessorName] = procConfig
collectorConf["processors"] = processors
out, err := yaml.Marshal(collectorConf)
if err != nil {
return nil, errors.Wrapf(err, errors.TypeInternal, ErrCodeBuildMappingProcessorConfig, "failed to marshal collector config")
}
return out, nil
}
func buildProcessorConfig(groups []*SpanMapperGroupWithMappers) *spanMapperProcessorConfig {
out := make([]spanMapperProcessorGroup, 0, len(groups))
for _, gm := range groups {
rules := make([]spanMapperProcessorAttribute, 0, len(gm.Mappers))
for _, m := range gm.Mappers {
rules = append(rules, buildAttributeRule(m))
}
out = append(out, spanMapperProcessorGroup{
ID: gm.Group.Name,
ExistsAny: spanMapperProcessorExistsAny{
Attributes: gm.Group.Condition.Attributes,
Resource: gm.Group.Condition.Resource,
},
Attributes: rules,
})
}
return &spanMapperProcessorConfig{Groups: out}
}
// buildAttributeRule maps a single SpanMapper to a collector attribute rule.
// Sources are sorted by Priority DESC (highest-priority first); read-from-
// resource sources are encoded via the "resource." prefix on the key. Each
// source carries its own action — "copy" is omitted to keep the emitted YAML
// compact, and only "move" is set explicitly.
func buildAttributeRule(m *SpanMapper) spanMapperProcessorAttribute {
sources := make([]SpanMapperSource, len(m.Config.Sources))
copy(sources, m.Config.Sources)
sort.SliceStable(sources, func(i, j int) bool { return sources[i].Priority > sources[j].Priority })
out := make([]spanMapperProcessorSource, 0, len(sources))
for _, s := range sources {
key := s.Key
if s.Context == FieldContextResource {
key = FieldContextResource.StringValue() + "." + s.Key
}
var action string
if s.Operation == SpanMapperOperationMove {
action = SpanMapperOperationMove.StringValue()
}
out = append(out, spanMapperProcessorSource{Key: key, Action: action})
}
ctx := FieldContextSpanAttribute
if m.FieldContext == FieldContextResource {
ctx = FieldContextResource
}
return spanMapperProcessorAttribute{
Target: m.Name,
Context: ctx.StringValue(),
Sources: out,
}
}

View File

@@ -1,198 +0,0 @@
package spantypes
import (
"os"
"path/filepath"
"testing"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
)
func TestGenerateCollectorConfigWithSpanMapperProcessor(t *testing.T) {
t.Parallel()
baseline := loadFixture(t, "collector_baseline.yaml")
tests := []struct {
name string
groups []*SpanMapperGroupWithMappers
want string
}{
{
name: "no_groups",
want: "collector_no_groups.yaml",
},
{
name: "with_groups",
groups: []*SpanMapperGroupWithMappers{
{
Group: newGroup("llm", []string{"model"}, []string{"service.name"}),
Mappers: []*SpanMapper{
newMapper("gen_ai.request.model", FieldContextResource,
attrSrc("gen_ai.llm.model", SpanMapperOperationCopy, 3),
attrSrc("llm.model", SpanMapperOperationCopy, 2),
resSrc("service.name", SpanMapperOperationCopy, 1),
),
newMapper("gen_ai.request.tokens", FieldContextSpanAttribute,
attrSrc("gen_ai.request_tokens", SpanMapperOperationCopy, 2),
attrSrc("llm.tokens", SpanMapperOperationCopy, 1),
),
newMapper("gen_ai.request.input", FieldContextSpanAttribute,
attrSrc("gen_ai.input", SpanMapperOperationMove, 2),
attrSrc("llm.input", SpanMapperOperationMove, 1),
),
},
},
{
Group: newGroup("agent", []string{"agent."}, nil),
Mappers: []*SpanMapper{
newMapper("gen_ai.agent.name", FieldContextSpanAttribute,
attrSrc("agent.name", SpanMapperOperationCopy, 2),
attrSrc("llm.agent.name", SpanMapperOperationCopy, 1),
),
newMapper("gen_ai.agent.id", FieldContextSpanAttribute,
attrSrc("gen_ai.agent.id", SpanMapperOperationCopy, 2),
attrSrc("llm.agent.id", SpanMapperOperationCopy, 1),
),
},
},
{
Group: newGroup("tool", []string{"agent."}, nil),
Mappers: []*SpanMapper{
newMapper("gen_ai.tool.name", FieldContextSpanAttribute,
attrSrc("ai.tool.name", SpanMapperOperationCopy, 2),
attrSrc("llm.tool.name", SpanMapperOperationCopy, 1),
),
},
},
},
want: "collector_with_groups.yaml",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
got, err := GenerateCollectorConfigWithSpanMapperProcessor(baseline, tc.groups)
require.NoError(t, err)
assertYAMLEqual(t, loadFixture(t, tc.want), got)
})
}
}
func TestGenerateCollectorConfigWithSpanMapperProcessor_Errors(t *testing.T) {
t.Parallel()
tests := []struct {
name string
in []byte
}{
{"processors_not_a_map", []byte("processors: not-a-map\n")},
{"malformed_yaml", []byte(": :")},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
_, err := GenerateCollectorConfigWithSpanMapperProcessor(tc.in, nil)
require.Error(t, err)
assert.True(t, errors.Ast(err, errors.TypeInvalidInput), "want TypeInvalidInput, got %v", err)
assert.True(t, errors.Asc(err, ErrCodeInvalidCollectorConfig), "want ErrCodeInvalidCollectorConfig, got %v", err)
})
}
}
func TestBuildAttributeRule(t *testing.T) {
t.Parallel()
tests := []struct {
name string
mapper *SpanMapper
want spanMapperProcessorAttribute
}{
{
name: "priority_sort_and_resource_prefix",
mapper: newMapper("gen_ai.request.model", FieldContextResource,
attrSrc("llm.model", SpanMapperOperationCopy, 20),
resSrc("service.name", SpanMapperOperationCopy, 10),
attrSrc("gen_ai.llm.model", SpanMapperOperationCopy, 30),
),
want: spanMapperProcessorAttribute{
Target: "gen_ai.request.model",
Context: FieldContextResource.StringValue(),
Sources: []spanMapperProcessorSource{
{Key: "gen_ai.llm.model"},
{Key: "llm.model"},
{Key: "resource.service.name"},
},
},
},
{
name: "per_source_actions",
mapper: newMapper("gen_ai.request.input", FieldContextSpanAttribute,
attrSrc("gen_ai.input", SpanMapperOperationMove, 20),
attrSrc("llm.input", SpanMapperOperationCopy, 10),
),
want: spanMapperProcessorAttribute{
Target: "gen_ai.request.input",
Context: FieldContextSpanAttribute.StringValue(),
Sources: []spanMapperProcessorSource{
{Key: "gen_ai.input", Action: SpanMapperOperationMove.StringValue()},
{Key: "llm.input"},
},
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
assert.Equal(t, tc.want, buildAttributeRule(tc.mapper))
})
}
}
func loadFixture(t *testing.T, name string) []byte {
t.Helper()
b, err := os.ReadFile(filepath.Join("testdata", name))
require.NoError(t, err)
return b
}
// assertYAMLEqual compares two YAML documents structurally so key order and
// slice formatting do not matter.
func assertYAMLEqual(t *testing.T, want, got []byte) {
t.Helper()
var w, g any
require.NoError(t, yaml.Unmarshal(want, &w))
require.NoError(t, yaml.Unmarshal(got, &g))
assert.Equal(t, w, g)
}
func newGroup(name string, attrs, res []string) *SpanMapperGroup {
return &SpanMapperGroup{
Name: name,
Condition: SpanMapperGroupCondition{Attributes: attrs, Resource: res},
Enabled: true,
}
}
func newMapper(name string, target FieldContext, sources ...SpanMapperSource) *SpanMapper {
return &SpanMapper{
Name: name,
FieldContext: target,
Config: SpanMapperConfig{Sources: sources},
Enabled: true,
}
}
func attrSrc(key string, op SpanMapperOperation, priority int) SpanMapperSource {
return SpanMapperSource{Key: key, Context: FieldContextSpanAttribute, Operation: op, Priority: priority}
}
func resSrc(key string, op SpanMapperOperation, priority int) SpanMapperSource {
return SpanMapperSource{Key: key, Context: FieldContextResource, Operation: op, Priority: priority}
}

View File

@@ -21,9 +21,3 @@ type SpanMapperStore interface {
UpdateMapper(ctx context.Context, mapper *SpanMapper) error
DeleteMapper(ctx context.Context, orgID, groupID, id valuer.UUID) error
}
// TraceStore defines the data access interface for trace detail queries.
type TraceStore interface {
GetTraceSummary(ctx context.Context, traceID string) (*TraceSummary, error)
GetTraceSpans(ctx context.Context, traceID string, summary *TraceSummary) ([]StorableSpan, error)
}

View File

@@ -1,17 +0,0 @@
receivers:
otlp:
protocols:
grpc:
processors:
signozspanmapper:
groups: []
batch: {}
exporters:
otlp:
endpoint: localhost:4317
service:
pipelines:
traces:
receivers: [otlp]
processors: [signozspanmapper, batch]
exporters: [otlp]

View File

@@ -1,17 +0,0 @@
receivers:
otlp:
protocols:
grpc:
processors:
signozspanmapper:
groups: []
batch: {}
exporters:
otlp:
endpoint: localhost:4317
service:
pipelines:
traces:
receivers: [otlp]
processors: [signozspanmapper, batch]
exporters: [otlp]

View File

@@ -1,67 +0,0 @@
receivers:
otlp:
protocols:
grpc:
processors:
signozspanmapper:
groups:
- id: llm
exists_any:
attributes:
- model
resource:
- service.name
attributes:
- target: gen_ai.request.model
context: resource
sources:
- key: gen_ai.llm.model
- key: llm.model
- key: resource.service.name
- target: gen_ai.request.tokens
context: attribute
sources:
- key: gen_ai.request_tokens
- key: llm.tokens
- target: gen_ai.request.input
context: attribute
sources:
- key: gen_ai.input
action: move
- key: llm.input
action: move
- id: agent
exists_any:
attributes:
- agent.
attributes:
- target: gen_ai.agent.name
context: attribute
sources:
- key: agent.name
- key: llm.agent.name
- target: gen_ai.agent.id
context: attribute
sources:
- key: gen_ai.agent.id
- key: llm.agent.id
- id: tool
exists_any:
attributes:
- agent.
attributes:
- target: gen_ai.tool.name
context: attribute
sources:
- key: ai.tool.name
- key: llm.tool.name
batch: {}
exporters:
otlp:
endpoint: localhost:4317
service:
pipelines:
traces:
receivers: [otlp]
processors: [signozspanmapper, batch]
exporters: [otlp]

View File

@@ -1,4 +1,4 @@
package spantypes
package tracedetailtypes
import (
"slices"

View File

@@ -1,4 +1,4 @@
package spantypes
package tracedetailtypes
import (
"testing"

View File

@@ -0,0 +1,9 @@
package tracedetailtypes
import "context"
// TraceStore defines the data access interface for trace detail queries.
type TraceStore interface {
GetTraceSummary(ctx context.Context, traceID string) (*TraceSummary, error)
GetTraceSpans(ctx context.Context, traceID string, summary *TraceSummary) ([]StorableSpan, error)
}

View File

@@ -1,4 +1,4 @@
package spantypes
package tracedetailtypes
import (
"encoding/json"

View File

@@ -1,4 +1,4 @@
package spantypes
package tracedetailtypes
import (
"encoding/json"

View File

@@ -14,24 +14,6 @@ type Config struct {
// The directory from which to serve the web files.
Directory string `mapstructure:"directory"`
// Settings that are exposed to the web.
Settings Settings `mapstructure:"settings"`
}
// Settings that are exposed to the web.
type Settings struct {
Posthog Posthog `mapstructure:"posthog"`
Appcues Appcues `mapstructure:"appcues"`
}
type Posthog struct {
Enabled bool `mapstructure:"enabled"`
}
type Appcues struct {
Enabled bool `mapstructure:"enabled"`
}
func NewConfigFactory() factory.ConfigFactory {
@@ -43,14 +25,6 @@ func newConfig() factory.Config {
Enabled: true,
Index: "index.html",
Directory: "/etc/signoz/web",
Settings: Settings{
Posthog: Posthog{
Enabled: true,
},
Appcues: Appcues{
Enabled: true,
},
},
}
}

View File

@@ -38,7 +38,6 @@ func TestNewWithEnvProvider(t *testing.T) {
Enabled: false,
Index: def.Index,
Directory: def.Directory,
Settings: def.Settings,
}
assert.Equal(t, expected, actual)

View File

@@ -2,8 +2,6 @@ package routerweb
import (
"context"
"encoding/json"
"html/template"
"net/http"
"os"
"path/filepath"
@@ -44,16 +42,8 @@ func New(ctx context.Context, settings factory.ProviderSettings, config web.Conf
return nil, errors.WrapInvalidInputf(err, errors.CodeInvalidInput, "cannot read %q in web directory", config.Index)
}
settingsJSON, err := json.Marshal(config.Settings)
if err != nil {
return nil, errors.WrapInternalf(err, errors.CodeInternal, "cannot marshal web settings to JSON")
}
logger := factory.NewScopedProviderSettings(settings, "github.com/SigNoz/signoz/pkg/web/routerweb").Logger()
indexContents := web.NewIndex(ctx, logger, config.Index, raw, web.TemplateData{
BaseHref: globalConfig.ExternalPathTrailing(),
Settings: template.JS(settingsJSON),
})
indexContents := web.NewIndex(ctx, logger, config.Index, raw, web.TemplateData{BaseHref: globalConfig.ExternalPathTrailing()})
return &provider{
config: config,

View File

@@ -2,7 +2,6 @@ package routerweb
import (
"context"
"encoding/json"
"io"
"net"
"net/http"
@@ -20,11 +19,6 @@ import (
"github.com/stretchr/testify/require"
)
func expectedHTML(baseHref string, settings web.Settings) string {
settingsJSON, _ := json.Marshal(settings)
return `<html><head><base href="` + baseHref + `" /></head><body><script>window.signozBootData={settings:` + string(settingsJSON) + `}</script>Welcome to test data!!!</body></html>`
}
func startServer(t *testing.T, config web.Config, globalConfig global.Config) string {
t.Helper()
@@ -60,79 +54,53 @@ func httpGet(t *testing.T, url string) string {
func TestServeTemplatedIndex(t *testing.T) {
t.Parallel()
emptySettings := web.Settings{}
testCases := []struct {
name string
path string
globalConfig global.Config
webConfig web.Config
expected string
}{
{
name: "RootBaseHrefAtRoot",
path: "/",
globalConfig: global.Config{},
webConfig: web.Config{Index: "valid_template.html", Directory: "testdata"},
expected: expectedHTML("/", emptySettings),
expected: `<html><head><base href="/" /></head><body>Welcome to test data!!!</body></html>`,
},
{
name: "RootBaseHrefAtNonExistentPath",
path: "/does-not-exist",
globalConfig: global.Config{},
webConfig: web.Config{Index: "valid_template.html", Directory: "testdata"},
expected: expectedHTML("/", emptySettings),
expected: `<html><head><base href="/" /></head><body>Welcome to test data!!!</body></html>`,
},
{
name: "RootBaseHrefAtDirectory",
path: "/assets",
globalConfig: global.Config{},
webConfig: web.Config{Index: "valid_template.html", Directory: "testdata"},
expected: expectedHTML("/", emptySettings),
expected: `<html><head><base href="/" /></head><body>Welcome to test data!!!</body></html>`,
},
{
name: "SubPathBaseHrefAtRoot",
path: "/",
globalConfig: global.Config{ExternalURL: &url.URL{Scheme: "https", Host: "example.com", Path: "/signoz"}},
webConfig: web.Config{Index: "valid_template.html", Directory: "testdata"},
expected: expectedHTML("/signoz/", emptySettings),
expected: `<html><head><base href="/signoz/" /></head><body>Welcome to test data!!!</body></html>`,
},
{
name: "SubPathBaseHrefAtNonExistentPath",
path: "/does-not-exist",
globalConfig: global.Config{ExternalURL: &url.URL{Scheme: "https", Host: "example.com", Path: "/signoz"}},
webConfig: web.Config{Index: "valid_template.html", Directory: "testdata"},
expected: expectedHTML("/signoz/", emptySettings),
expected: `<html><head><base href="/signoz/" /></head><body>Welcome to test data!!!</body></html>`,
},
{
name: "SubPathBaseHrefAtDirectory",
path: "/assets",
globalConfig: global.Config{ExternalURL: &url.URL{Scheme: "https", Host: "example.com", Path: "/signoz"}},
webConfig: web.Config{Index: "valid_template.html", Directory: "testdata"},
expected: expectedHTML("/signoz/", emptySettings),
},
{
name: "WithPopulatedSettings",
path: "/",
globalConfig: global.Config{},
webConfig: web.Config{
Index: "valid_template.html",
Directory: "testdata",
Settings: web.Settings{
Posthog: web.Posthog{Enabled: true},
Appcues: web.Appcues{Enabled: true},
},
},
expected: expectedHTML("/", web.Settings{
Posthog: web.Posthog{Enabled: true},
Appcues: web.Appcues{Enabled: true},
}),
expected: `<html><head><base href="/signoz/" /></head><body>Welcome to test data!!!</body></html>`,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
base := startServer(t, testCase.webConfig, testCase.globalConfig)
base := startServer(t, web.Config{Index: "valid_template.html", Directory: "testdata"}, testCase.globalConfig)
assert.Equal(t, testCase.expected, strings.TrimSuffix(httpGet(t, base+testCase.path), "\n"))
})

View File

@@ -1 +1 @@
<html><head><base href="[[.BaseHref]]" /></head><body><script>window.signozBootData={settings:[[.Settings]]}</script>Welcome to test data!!!</body></html>
<html><head><base href="[[.BaseHref]]" /></head><body>Welcome to test data!!!</body></html>

View File

@@ -11,14 +11,8 @@ import (
// Field names map to the HTML attributes they populate in the template:
// - BaseHref → <base href="[[.BaseHref]]" />
// - Settings → window.signozBootData = { settings: [[.Settings]] }
type TemplateData struct {
BaseHref string
// Settings is the pre-serialized JSON of web.Settings for injection into a
// <script> block. The template.JS type prevents html/template from
// HTML-escaping the value.
Settings template.JS
}
// If the template cannot be parsed or executed, the raw bytes are