Compare commits

..

5 Commits

Author SHA1 Message Date
Nikhil Soni
d2d2ee01d4 fix(apm): use dynamic stepInterval for API monitoring time range on endpoints page
Replace hardcoded stepInterval: 300 (5 min) with the computed step value in
all four onViewAPIMonitoringPopupClick calls in External.tsx. Also fix the
endTime formula in util.ts — endTime should be `timestamp` (bucket end), not
`timestamp + stepInterval`, so the destination time range covers exactly the
clicked bar bucket [bucket_start, bucket_end].

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 16:58:43 +05:30
Vikrant Gupta
3ffb5bd43b feat(web): add support web settings (#11444)
* feat(web): add support web settings in index.html

* feat(web): remove settings from global config

* feat(web): fix openapi schemas

* feat(web): fix formatting issues

* feat(web): fix formatting issues

* feat(web): remove frontend script changes

* feat(web): remove the redundant test

* feat(web): update defaults
2026-05-25 09:24:12 +00:00
Nityananda Gohain
67324edb7e feat: opamp integration signozspanmapper (#11335)
* feat: opamp integration signozspanmapper

* fix: update go.mod

* fix: minor changes

* fix: keep action as a part of source

* fix: update go.mod

* fix: address comments

* fix: revert changes
2026-05-25 08:30:27 +00:00
Nikhil Soni
9ba57d323d refactor: merge tracedetail typse with spantypes (#11417)
* refactor: merge tracedetail typse with spantypes

* chore: update openapi specs
2026-05-25 06:52:34 +00:00
Tushar Vats
09f4ba33c9 fix: handle body json for default view (#11443) 2026-05-25 06:51:07 +00:00
99 changed files with 1526 additions and 907 deletions

View File

@@ -60,6 +60,14 @@ 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,6 +5641,19 @@ 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
@@ -5655,6 +5668,50 @@ 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:
@@ -5682,6 +5739,50 @@ 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:
@@ -5812,6 +5913,78 @@ 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
@@ -5904,179 +6077,6 @@ 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/TracedetailtypesPostableWaterfall'
$ref: '#/components/schemas/SpantypesPostableWaterfall'
responses:
"200":
content:
@@ -18904,7 +18904,7 @@ paths:
schema:
properties:
data:
$ref: '#/components/schemas/TracedetailtypesGettableWaterfallTrace'
$ref: '#/components/schemas/SpantypesGettableWaterfallTrace'
status:
type: string
required:

View File

@@ -19,7 +19,6 @@ const BANNED_COMPONENTS = {
Switch: 'Use @signozhq/ui/switch Switch instead of antd Switch.',
Badge: 'Use @signozhq/ui/badge instead of antd Badge.',
Progress: 'Use @signozhq/ui/progress instead of antd Progress.',
Tag: 'Use @signozhq/ui/badge Bagde instead of antd Tag.',
};
export default {

View File

@@ -6655,6 +6655,28 @@ 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',
@@ -6721,6 +6743,232 @@ 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',
@@ -6770,6 +7018,31 @@ 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;
/**
@@ -6878,281 +7151,6 @@ 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
@@ -9232,7 +9230,7 @@ export type GetWaterfallPathParameters = {
traceID: string;
};
export type GetWaterfall200 = {
data: TracedetailtypesGettableWaterfallTraceDTO;
data: SpantypesGettableWaterfallTraceDTO;
/**
* @type string
*/

View File

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

View File

@@ -9,7 +9,7 @@ import {
useState,
} from 'react';
import { Color } from '@signozhq/design-tokens';
import { Select, Tooltip } from 'antd';
import { Select, Tag, Tooltip } from 'antd';
import {
OPERATORS,
QUERY_BUILDER_OPERATORS_BY_TYPES,
@@ -37,7 +37,7 @@ import { validationMapper } from 'hooks/queryBuilder/useIsValidTag';
import { operatorTypeMapper } from 'hooks/queryBuilder/useOperatorType';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { isArray, isEmpty, isEqual, isObject } from 'lodash-es';
import { ChevronDown, ChevronUp, X } from '@signozhq/icons';
import { ChevronDown, ChevronUp } from '@signozhq/icons';
import type { BaseSelectRef } from 'rc-select';
import {
BaseAutocompleteData,
@@ -51,7 +51,6 @@ import { popupContainer } from 'utils/selectPopupContainer';
import { v4 as uuid } from 'uuid';
import './ClientSideQBSearch.styles.scss';
import { Badge } from '@signozhq/ui/badge';
export interface AttributeKey {
key: string;
@@ -548,7 +547,11 @@ function ClientSideQBSearch(
return (
<span className="qb-search-bar-tokenised-tags">
<Badge color="vanilla" className={tagDetails?.key?.type || ''}>
<Tag
closable={!searchValue && closable}
onClose={onCloseHandler}
className={tagDetails?.key?.type || ''}
>
<Tooltip title={chipValue}>
<TypographyText
$isInNin={isInNin}
@@ -563,15 +566,7 @@ function ClientSideQBSearch(
{chipValue}
</TypographyText>
</Tooltip>
{!searchValue && closable && (
<X
size={12}
className="close-icon"
style={{ cursor: 'pointer' }}
onClick={onCloseHandler}
/>
)}
</Badge>
</Tag>
</span>
);
};

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { Color } from '@signozhq/design-tokens';
import { Button, Modal } from 'antd';
import { Button, Modal, Tag } from 'antd';
import { CircleAlert, X } from '@signozhq/icons';
import KeyValueLabel from 'periscope/components/KeyValueLabel';
import { useAppContext } from 'providers/App/App';
@@ -9,7 +9,6 @@ import APIError from 'types/api/error';
import ErrorContent from './components/ErrorContent';
import './ErrorModal.styles.scss';
import { Badge } from '@signozhq/ui/badge';
type Props = {
error: APIError;
@@ -46,17 +45,14 @@ function ErrorModal({
return (
<>
{!triggerComponent ? (
<span
<Tag
className="error-modal__trigger"
role="button"
tabIndex={0}
icon={<CircleAlert size={14} color={Color.BG_CHERRY_500} />}
color="error"
onClick={(): void => setVisible(true)}
onKeyDown={undefined}
>
<Badge color="error">
<CircleAlert size={14} color={Color.BG_CHERRY_500} /> error
</Badge>
</span>
error
</Tag>
) : (
React.cloneElement(triggerComponent, {
onClick: () => setVisible(true),

View File

@@ -5,7 +5,10 @@ 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 { getSanitizedLogBody } from 'container/LogDetailedView/utils';
import {
getBodyDisplayString,
getSanitizedLogBody,
} from 'container/LogDetailedView/utils';
import { FontSize } from 'container/OptionsMenu/types';
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
import { useIsDarkMode } from 'hooks/useDarkMode';
@@ -196,7 +199,7 @@ function ListLogView({
{updatedSelecedFields.some((field) => field.name === 'body') && (
<LogGeneralField
fieldKey="Log"
fieldValue={flattenLogData.body}
fieldValue={getBodyDisplayString(logData.body)}
linesPerRow={linesPerRow}
fontSize={fontSize}
/>

View File

@@ -14,8 +14,7 @@ import { Color } from '@signozhq/design-tokens';
import { copilot } from '@uiw/codemirror-theme-copilot';
import { githubLight } from '@uiw/codemirror-theme-github';
import CodeMirror, { EditorView, keymap, Prec } from '@uiw/react-codemirror';
import { Button, Card, Collapse, Popover, Tooltip } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Button, Card, Collapse, Popover, Tag, Tooltip } from 'antd';
import { getKeySuggestions } from 'api/querySuggestions/getKeySuggestions';
import { getValueSuggestions } from 'api/querySuggestions/getValueSuggestion';
import cx from 'classnames';
@@ -665,26 +664,26 @@ function QuerySearch({
// Helper function to render a badge for the current context mode
const renderContextBadge = (): JSX.Element => {
if (!editingMode) {
return <Badge color="vanilla">Unknown</Badge>;
return <Tag>Unknown</Tag>;
}
switch (editingMode) {
case 'key':
return <Badge color="robin">Key</Badge>;
return <Tag color="blue">Key</Tag>;
case 'operator':
return <Badge color="sakura">Operator</Badge>;
return <Tag color="purple">Operator</Tag>;
case 'value':
return <Badge color="forest">Value</Badge>;
return <Tag color="green">Value</Tag>;
case 'conjunction':
return <Badge color="amber">Conjunction</Badge>;
return <Tag color="orange">Conjunction</Tag>;
case 'function':
return <Badge color="aqua">Function</Badge>;
return <Tag color="cyan">Function</Tag>;
case 'parenthesis':
return <Badge color="sakura">Parenthesis</Badge>;
return <Tag color="magenta">Parenthesis</Tag>;
case 'bracketList':
return <Badge color="cherry">Bracket List</Badge>;
return <Tag color="red">Bracket List</Tag>;
default:
return <Badge color="vanilla">Unknown</Badge>;
return <Tag>Unknown</Tag>;
}
};
@@ -1305,37 +1304,34 @@ function QuerySearch({
Currently editing: {renderContextBadge()}
{queryContext?.keyToken && (
<span className="triplet-info">
Key: <Badge color="vanilla">{queryContext.keyToken}</Badge>
Key: <Tag>{queryContext.keyToken}</Tag>
</span>
)}
{queryContext?.operatorToken && (
<span className="triplet-info">
Operator: <Badge color="vanilla">{queryContext.operatorToken}</Badge>
Operator: <Tag>{queryContext.operatorToken}</Tag>
</span>
)}
{queryContext?.valueToken && (
<span className="triplet-info">
Value: <Badge color="vanilla">{queryContext.valueToken}</Badge>
Value: <Tag>{queryContext.valueToken}</Tag>
</span>
)}
{queryContext?.currentPair && (
<span className="triplet-info query-pair-info">
Current pair: <Badge color="robin">{queryContext.currentPair.key}</Badge>
<Badge color="sakura">{queryContext.currentPair.operator}</Badge>
Current pair: <Tag color="blue">{queryContext.currentPair.key}</Tag>
<Tag color="purple">{queryContext.currentPair.operator}</Tag>
{queryContext.currentPair.value && (
<Badge color="forest">{queryContext.currentPair.value}</Badge>
<Tag color="green">{queryContext.currentPair.value}</Tag>
)}
<Badge
color={queryContext.currentPair.isComplete ? 'success' : 'warning'}
>
<Tag color={queryContext.currentPair.isComplete ? 'success' : 'warning'}>
{queryContext.currentPair.isComplete ? 'Complete' : 'Incomplete'}
</Badge>
</Tag>
</span>
)}
{queryContext?.queryPairs && queryContext.queryPairs.length > 0 && (
<span className="triplet-info">
Total pairs:{' '}
<Badge color="robin">{queryContext.queryPairs.length}</Badge>
Total pairs: <Tag color="blue">{queryContext.queryPairs.length}</Tag>
</span>
)}
</div>

View File

@@ -1,5 +1,4 @@
import { Popover } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Popover, Tag } from 'antd';
import { LabelColumnProps } from './TableRenderer.types';
import TagWithToolTip from './TagWithToolTip';
@@ -7,7 +6,7 @@ import { getLabelAndValueContent } from './utils';
import './LabelColumn.styles.scss';
function LabelColumn({ labels, value }: LabelColumnProps): JSX.Element {
function LabelColumn({ labels, value, color }: LabelColumnProps): JSX.Element {
const newLabels = labels.length > 3 ? labels.slice(0, 3) : labels;
const remainingLabels = labels.length > 3 ? labels.slice(3) : [];
@@ -15,7 +14,7 @@ function LabelColumn({ labels, value }: LabelColumnProps): JSX.Element {
<div className="label-column">
{newLabels.map(
(label: string): JSX.Element => (
<TagWithToolTip key={label} label={label} value={value} />
<TagWithToolTip key={label} label={label} color={color} value={value} />
),
)}
{remainingLabels.length > 0 && (
@@ -27,9 +26,9 @@ function LabelColumn({ labels, value }: LabelColumnProps): JSX.Element {
{labels.map(
(label: string): JSX.Element => (
<div key={label}>
<Badge className="label-column--tag" color="vanilla">
<Tag className="label-column--tag" color={color}>
{getLabelAndValueContent(label, value && value[label])}
</Badge>
</Tag>
</div>
),
)}
@@ -37,9 +36,9 @@ function LabelColumn({ labels, value }: LabelColumnProps): JSX.Element {
}
trigger="hover"
>
<Badge className="label-column--tag" color="vanilla">
<Tag className="label-column--tag" color={color}>
+{remainingLabels.length}
</Badge>
</Tag>
</Popover>
)}
</div>

View File

@@ -1,17 +1,20 @@
import { Tooltip } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Tag, Tooltip } from 'antd';
import { getLabelRenderingValue } from './utils';
function TagWithToolTip({ label, value }: TagWithToolTipProps): JSX.Element {
function TagWithToolTip({
label,
value,
color,
}: TagWithToolTipProps): JSX.Element {
const tooltipTitle =
value && value[label] ? `${label}: ${value[label]}` : label;
return (
<div key={label}>
<Tooltip title={tooltipTitle}>
<Badge className="label-column--tag" color="vanilla">
<Tag className="label-column--tag" color={color}>
{getLabelRenderingValue(label, value && value[label])}
</Badge>
</Tag>
</Tooltip>
</div>
);
@@ -19,6 +22,7 @@ function TagWithToolTip({ label, value }: TagWithToolTipProps): JSX.Element {
type TagWithToolTipProps = {
label: string;
color?: string;
value?: {
[key: string]: string;
};
@@ -26,6 +30,7 @@ type TagWithToolTipProps = {
TagWithToolTip.defaultProps = {
value: undefined,
color: undefined,
};
export default TagWithToolTip;

View File

@@ -14,6 +14,11 @@
.ant-form-item {
margin-bottom: 0;
}
.ant-tag {
margin-right: 0;
background: var(--card);
}
}
.add-tag-container {

View File

@@ -1,7 +1,6 @@
import React, { Dispatch, SetStateAction, useState } from 'react';
import { Check, Plus, X } from '@signozhq/icons';
import { Button, Flex } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Button, Flex, Tag } from 'antd';
import Input from 'components/Input';
import './Tags.styles.scss';
@@ -47,14 +46,14 @@ function Tags({ tags, setTags }: AddTagsProps): JSX.Element {
return (
<div className="tags-container">
{tags.map<React.ReactNode>((tag) => (
<Badge key={tag} color="vanilla" style={{ userSelect: 'none' }}>
<Tag
key={tag}
closable
style={{ userSelect: 'none' }}
onClose={(): void => handleClose(tag)}
>
<span>{tag}</span>
<X
size={12}
style={{ cursor: 'pointer', marginInlineStart: 4 }}
onClick={(): void => handleClose(tag)}
/>
</Badge>
</Tag>
))}
{inputVisible && (

View File

@@ -1,8 +1,7 @@
import { ReactNode } from 'react';
import { Color } from '@signozhq/design-tokens';
import { TableColumnType as ColumnType, Tooltip } from 'antd';
import { TableColumnType as ColumnType, Tag, Tooltip } from 'antd';
import { Progress } from '@signozhq/ui/progress';
import { Badge } from '@signozhq/ui/badge';
import { convertFiltersToExpressionWithExistingQuery } from 'components/QueryBuilderV2/utils';
import {
FiltersType,
@@ -973,9 +972,13 @@ export const getEndPointsColumnsConfig = (
})()}
{isGroupedByAttribute
? text.split(',').map((value) => (
<Badge key={value} color="vanilla" className="endpoint-group-tag-item">
<Tag
key={value}
color={Color.BG_SLATE_100}
className="endpoint-group-tag-item"
>
{value === '' ? '<no-value>' : value}
</Badge>
</Tag>
))
: endPointName}
</div>

View File

@@ -14,8 +14,8 @@ import {
Skeleton,
Table,
TableColumnsType as ColumnsType,
Tag,
} from 'antd';
import { Badge } from '@signozhq/ui/badge';
import getUsage, { UsageResponsePayloadProps } from 'api/billing/getUsage';
import logEvent from 'api/common/logEvent';
import updateCreditCardApi from 'api/v1/checkout/create';
@@ -434,7 +434,7 @@ export default function BillingContainer(): JSX.Element {
<Flex vertical>
<Typography.Title level={5} style={{ marginTop: 2, fontWeight: 500 }}>
{isCloudUserVal ? t('teams_cloud') : t('teams')}{' '}
{isFreeTrial ? <Badge color="success"> Free Trial </Badge> : ''}
{isFreeTrial ? <Tag color="success"> Free Trial </Tag> : ''}
</Typography.Title>
{!isLoading && !isFetchingBillingData && !showGracePeriodMessage ? (

View File

@@ -1,7 +1,6 @@
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { Row } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Row, Tag } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import logEvent from 'api/common/logEvent';
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
@@ -67,7 +66,13 @@ function SelectAlertType({ onSelect }: SelectAlertTypeProps): JSX.Element {
<AlertTypeCard
key={option.selection}
title={option.title}
extra={option.isBeta ? <Badge color="robin">Beta</Badge> : undefined}
extra={
option.isBeta ? (
<Tag bordered={false} color="geekblue">
Beta
</Tag>
) : undefined
}
onClick={(e): void => {
onSelect(option.selection, isModifierKeyPressed(e));
}}

View File

@@ -16,8 +16,7 @@ import {
Plus,
X,
} from '@signozhq/icons';
import { Button, Card, Input, Modal, Popover, Tooltip } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Button, Card, Input, Modal, Popover, Tag, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import logEvent from 'api/common/logEvent';
import ConfigureIcon from 'assets/Integrations/ConfigureIcon';
@@ -507,9 +506,9 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
{(tags?.length || 0) > 0 && (
<div className="dashboard-tags">
{tags?.map((tag) => (
<Badge key={tag} className="tag" color="vanilla">
<Tag key={tag} className="tag">
{tag}
</Badge>
</Tag>
))}
</div>
)}

View File

@@ -359,7 +359,7 @@
flex-flow: wrap;
gap: 8px;
[data-slot='badge'] {
.ant-tag {
height: 30px;
color: var(--l1-foreground);
font-family: 'Space Mono';

View File

@@ -5,8 +5,7 @@ import { useQuery } from 'react-query';
import { useSelector } from 'react-redux';
import { orange } from '@ant-design/colors';
import { Color } from '@signozhq/design-tokens';
import { Button, Collapse, Input, Select } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Button, Collapse, Input, Select, Tag } from 'antd';
import { Switch } from '@signozhq/ui/switch';
import { Typography } from '@signozhq/ui/typography';
import dashboardVariablesQuery from 'api/dashboard/variables/dashboardVariablesQuery';
@@ -543,9 +542,9 @@ function VariableItem({
}}
>
Dynamic
<Badge color="robin" className="sidenav-beta-tag">
<Tag bordered={false} className="sidenav-beta-tag" color="geekblue">
Beta
</Badge>
</Tag>
</Button>
<Button
type="text"
@@ -600,9 +599,9 @@ function VariableItem({
}}
>
Query
<Badge color="amber" className="sidenav-beta-tag">
<Tag bordered={false} className="sidenav-beta-tag" color="warning">
Not Recommended
</Badge>
</Tag>
<div onClick={(e): void => e.stopPropagation()}>
<TextToolTip
text="Learn why we don't recommend"
@@ -734,9 +733,7 @@ function VariableItem({
<Typography style={{ color: orange[5] }}>{errorPreview}</Typography>
) : (
map(previewValues, (value, idx) => (
<Badge key={`${value}${idx}`} color="vanilla">
{value.toString()}
</Badge>
<Tag key={`${value}${idx}`}>{value.toString()}</Tag>
))
)}
</div>

View File

@@ -1,5 +1,4 @@
import { Dispatch, SetStateAction, useState } from 'react';
import { X } from '@signozhq/icons';
import { Col, Tooltip } from 'antd';
import Input from 'components/Input';
@@ -61,7 +60,12 @@ function AddTags({ tags, setTags }: AddTagsProps): JSX.Element {
const isLongTag = tag.length > 20;
const tagElem = (
<NewTagContainer key={tag} color="vanilla" className="tag-container">
<NewTagContainer
closable
key={tag}
onClose={(): void => handleClose(tag)}
className="tag-container"
>
<span
onDoubleClick={(e): void => {
setEditInputIndex(index);
@@ -71,11 +75,6 @@ function AddTags({ tags, setTags }: AddTagsProps): JSX.Element {
>
{isLongTag ? `${tag.slice(0, 20)}...` : tag}
</span>
<X
size={12}
style={{ cursor: 'pointer', marginInlineStart: 4 }}
onClick={(): void => handleClose(tag)}
/>
</NewTagContainer>
);

View File

@@ -1,5 +1,4 @@
import { Col } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Col, Tag } from 'antd';
import styled from 'styled-components';
export const TagsContainer = styled.div`
@@ -9,7 +8,7 @@ export const TagsContainer = styled.div`
gap: 6px;
`;
export const NewTagContainer = styled(Badge)`
export const NewTagContainer = styled(Tag)`
&&& {
display: flex;
justify-content: space-between;

View File

@@ -1,4 +1,3 @@
import { X } from '@signozhq/icons';
import { QueryChipContainer, QueryChipItem } from './styles';
import { ILabelRecord } from './types';
@@ -14,15 +13,11 @@ export default function QueryChip({
const { key, value } = queryData;
return (
<QueryChipContainer>
<QueryChipItem color="vanilla">
<QueryChipItem
closable={key !== 'severity' && key !== 'description'}
onClose={(): void => onRemove(key)}
>
{key}: {value}
{key !== 'severity' && key !== 'description' && (
<X
size={12}
style={{ cursor: 'pointer', marginInlineStart: 4 }}
onClick={(): void => onRemove(key)}
/>
)}
</QueryChipItem>
</QueryChipContainer>
);

View File

@@ -1,5 +1,5 @@
import { grey } from '@ant-design/colors';
import { Badge } from '@signozhq/ui/badge';
import { Tag } from 'antd';
import styled from 'styled-components';
interface SearchContainerProps {
@@ -29,6 +29,6 @@ export const QueryChipContainer = styled.span`
}
`;
export const QueryChipItem = styled(Badge)`
export const QueryChipItem = styled(Tag)`
margin-right: 0.1rem;
`;

View File

@@ -1,7 +1,6 @@
import { useEffect, useState } from 'react';
import { Link, useLocation } from 'react-router-dom';
import { Button, Skeleton } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Button, Skeleton, Tag } from 'antd';
import logEvent from 'api/common/logEvent';
import { useListRules } from 'api/generated/services/rules';
import type { RuletypesRuleDTO } from 'api/generated/services/sigNoz.schemas';
@@ -178,14 +177,12 @@ export default function AlertRules({
</div>
<div className="alert-rule-item-description home-data-item-tag">
<Badge color="sienna" variant="outline">
{rule?.labels?.severity}
</Badge>
<Tag color={rule?.labels?.severity}>{rule?.labels?.severity}</Tag>
{rule.state === 'firing' && (
<Badge color="cherry" variant="outline" className="firing-tag">
<Tag color="red" className="firing-tag">
{rule.state}
</Badge>
</Tag>
)}
</div>
</div>

View File

@@ -1,7 +1,6 @@
import { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { Button, Skeleton } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Button, Skeleton, Tag } from 'antd';
import logEvent from 'api/common/logEvent';
import ROUTES from 'constants/routes';
import { useGetAllDashboard } from 'hooks/dashboard/useGetAllDashboard';
@@ -149,9 +148,9 @@ export default function Dashboards({
<div className="alert-rule-item-description home-data-item-tag">
{dashboard.data.tags?.map((tag) => (
<Badge color="sienna" variant="outline" key={tag}>
<Tag color={tag} key={tag}>
{tag}
</Badge>
</Tag>
))}
</div>
</div>

View File

@@ -574,7 +574,30 @@
.home-data-item-tag {
display: flex;
gap: 6px;
.ant-tag {
display: flex;
padding: 2px 12px;
justify-content: center;
align-items: center;
gap: 4px;
border-radius: 20px;
border: 1px solid color-mix(in srgb, var(--bg-sienna-500) 20%, transparent);
background: color-mix(in srgb, var(--bg-sienna-500) 10%, transparent);
color: var(--bg-sienna-400);
text-align: center;
font-family: Inter;
font-size: 12px;
font-style: normal;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
.firing-tag {
color: var(--bg-sakura-500);
background: color-mix(in srgb, var(--danger-background) 10%, transparent);
}
}
&.services-list-container {

View File

@@ -1,7 +1,6 @@
import { useEffect, useMemo, useState } from 'react';
import { Link } from 'react-router-dom';
import { Button, Skeleton } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Button, Skeleton, Tag } from 'antd';
import logEvent from 'api/common/logEvent';
import { getViewDetailsUsingViewKey } from 'components/ExplorerCard/utils';
import ROUTES from 'constants/routes';
@@ -250,9 +249,9 @@ export default function SavedViews({
}
return (
<Badge color="sienna" key={tag}>
<Tag color={tag} key={tag}>
{tag}
</Badge>
</Tag>
);
})}
</div>

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { Color } from '@signozhq/design-tokens';
import { Badge } from '@signozhq/ui/badge';
import { Tag } from 'antd';
import { Progress } from '@signozhq/ui/progress';
import { Typography } from '@signozhq/ui/typography';
import {
@@ -52,14 +52,14 @@ export const hostDetailsMetadataConfig: K8sDetailsMetadataConfig<HostData>[] = [
label: 'STATUS',
getValue: (h): string => (h.active ? 'ACTIVE' : 'INACTIVE'),
render: (value, h): React.ReactNode => (
<Badge
variant="outline"
<Tag
className={`${infraHostsStyles.infraMonitoringTags} ${
h.active ? infraHostsStyles.tagsActive : infraHostsStyles.tagsInactive
}`}
bordered
>
{value}
</Badge>
</Tag>
),
},
{
@@ -67,9 +67,9 @@ export const hostDetailsMetadataConfig: K8sDetailsMetadataConfig<HostData>[] = [
getValue: (h): string => h.os || '-',
render: (value): React.ReactNode =>
value !== '-' ? (
<Badge variant="outline" className={infraHostsStyles.infraMonitoringTags}>
<Tag className={infraHostsStyles.infraMonitoringTags} bordered>
{value}
</Badge>
</Tag>
) : (
<Typography.Text>-</Typography.Text>
),

View File

@@ -1,6 +1,5 @@
import React from 'react';
import { Tooltip } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Tag, Tooltip } from 'antd';
import { HostData } from 'api/infraMonitoring/getHostLists';
import TanStackTable, { TableColumnDef } from 'components/TanStackTableView';
import { getGroupByEl } from 'container/InfraMonitoringK8s/Base/utils';
@@ -93,13 +92,14 @@ export const hostColumnsConfig: TableColumnDef<HostData>[] = [
cell: ({ value }): React.ReactNode => {
const active = value as boolean;
return (
<Badge
<Tag
bordered
className={`${styles.statusTag} ${
active ? styles.statusTagActive : styles.statusTagInactive
}`}
>
{active ? 'ACTIVE' : 'INACTIVE'}
</Badge>
</Tag>
);
},
},

View File

@@ -14,7 +14,7 @@
font-size: 12px;
}
:global([data-slot='badge'] .ant-typography) {
:global(.ant-tag .ant-typography) {
font-size: 12px;
}
}

View File

@@ -19,7 +19,7 @@
font-size: 12px;
}
:global([data-slot='badge'] .ant-typography) {
:global(.ant-tag .ant-typography) {
font-size: 12px;
}
}

View File

@@ -19,7 +19,7 @@
font-size: 12px;
}
:global([data-slot='badge'] .ant-typography) {
:global(.ant-tag .ant-typography) {
font-size: 12px;
}
}

View File

@@ -19,7 +19,7 @@
font-size: 12px;
}
:global([data-slot='badge'] .ant-typography) {
:global(.ant-tag .ant-typography) {
font-size: 12px;
}
}

View File

@@ -1,5 +1,4 @@
import { TableColumnsType as ColumnsType } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { TableColumnsType as ColumnsType, Tag } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import { getMs } from 'container/Trace/Filters/Panel/PanelBody/Duration/util';
@@ -94,9 +93,9 @@ export const getTraceListColumns = (
if (primaryKey === 'httpMethod' || primaryKey === 'responseStatusCode') {
return (
<BlockLink to={getTraceLink(itemData)} openInNewTab>
<Badge data-testid={key} color="sakura">
<Tag data-testid={key} color="magenta">
{getValueForKey(itemData, key)}
</Badge>
</Tag>
</BlockLink>
);
}

View File

@@ -213,7 +213,7 @@
font-size: 12px;
}
[data-slot='badge'] .ant-typography {
.ant-tag .ant-typography {
font-size: 12px;
}
}
@@ -349,7 +349,7 @@
font-size: 12px;
}
[data-slot='badge'] .ant-typography {
.ant-tag .ant-typography {
font-size: 12px;
}
}

View File

@@ -18,6 +18,7 @@ import {
Table,
TablePaginationConfig,
TableProps as AntDTableProps,
Tag,
Tooltip,
} from 'antd';
import { Switch } from '@signozhq/ui/switch';
@@ -1054,10 +1055,7 @@ function MultiIngestionSettings(): JSX.Element {
<div className="ingestion-key-tags">
{APIKey.tags.map((tag, index) => (
// eslint-disable-next-line react/no-array-index-key
<Badge key={`${tag}-${index}`} color="vanilla">
{' '}
{tag}{' '}
</Badge>
<Tag key={`${tag}-${index}`}> {tag} </Tag>
))}
</div>
</div>

View File

@@ -310,7 +310,9 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
return <Typography>-</Typography>;
}
return <LabelColumn labels={withOutSeverityKeys} value={value} />;
return (
<LabelColumn labels={withOutSeverityKeys} value={value} color="magenta" />
);
},
},
];

View File

@@ -1,46 +1,26 @@
import { Badge } from '@signozhq/ui/badge';
import { Tag } from 'antd';
import type { RuletypesRuleDTO } from 'api/generated/services/sigNoz.schemas';
function Status({ status }: StatusProps): JSX.Element {
switch (status) {
case 'inactive': {
return (
<Badge color="forest" variant="outline">
OK
</Badge>
);
return <Tag color="green">OK</Tag>;
}
case 'pending': {
return (
<Badge color="amber" variant="outline">
Pending
</Badge>
);
return <Tag color="orange">Pending</Tag>;
}
case 'firing': {
return (
<Badge color="cherry" variant="outline">
Firing
</Badge>
);
return <Tag color="red">Firing</Tag>;
}
case 'disabled': {
return (
<Badge color="vanilla" variant="outline">
Disabled
</Badge>
);
return <Tag>Disabled</Tag>;
}
default: {
return (
<Badge color="vanilla" variant="outline">
Unknown
</Badge>
);
return <Tag color="default">Unknown</Tag>;
}
}
}

View File

@@ -22,9 +22,9 @@ import {
Popover,
Skeleton,
Table,
Tag,
Tooltip,
} from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Switch } from '@signozhq/ui/switch';
import { Typography } from '@signozhq/ui/typography';
import type { TableProps } from 'antd/lib';
@@ -420,15 +420,15 @@ function DashboardsList(): JSX.Element {
{dashboard?.tags && dashboard.tags.length > 0 && (
<div className="dashboard-tags">
{dashboard.tags.slice(0, 3).map((tag) => (
<Badge className="tag" color="vanilla" key={tag}>
<Tag className="tag" key={tag}>
{tag}
</Badge>
</Tag>
))}
{dashboard.tags.length > 3 && (
<Badge className="tag" color="vanilla" key={dashboard.tags[3]}>
<Tag className="tag" key={dashboard.tags[3]}>
+ <span> {dashboard.tags.length - 3} </span>
</Badge>
</Tag>
)}
</div>
)}

View File

@@ -1,7 +1,7 @@
import { Badge } from '@signozhq/ui/badge';
import { Tag } from 'antd';
import styled from 'styled-components';
export const TagContainer = styled(Badge)`
export const TagContainer = styled(Tag)`
&&& {
border-color: var(--bg-slate-400);
border-radius: 0.25rem;

View File

@@ -84,7 +84,7 @@
padding-top: 12px;
}
[data-slot='badge'] {
.ant-tag-borderless {
border-radius: 2px;
background: color-mix(in srgb, var(--bg-robin-400) 8%, transparent);
}

View File

@@ -3,8 +3,7 @@ import MEditor, { EditorProps, Monaco } from '@monaco-editor/react';
import { Color } from '@signozhq/design-tokens';
import { Button } from '@signozhq/ui/button';
import { Switch } from '@signozhq/ui/switch';
import { Collapse, Divider, Input } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Collapse, Divider, Input, Tag } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { AddToQueryHOCProps } from 'components/Logs/AddToQueryHOC';
import { ChangeViewFunctionType } from 'container/ExplorerOptions/types';
@@ -105,11 +104,11 @@ function Overview({
{
key: '1',
label: (
<Badge color="vanilla">
<Tag bordered={false}>
<Typography.Text style={{ color: Color.BG_ROBIN_400 }}>
body
</Typography.Text>
</Badge>
</Tag>
),
children: (
<div className="logs-body-content">
@@ -143,7 +142,7 @@ function Overview({
</div>
</div>
),
// extra: <Badge className="tag" color="vanilla">JSON</Badge>,
// extra: <Tag className="tag">JSON</Tag>,
className: 'collapse-content',
},
]}
@@ -164,11 +163,11 @@ function Overview({
className="attribute-tab-header"
onClick={toogleAttributePanelOpenState}
>
<Badge color="vanilla">
<Tag bordered={false}>
<Typography.Text style={{ color: Color.BG_ROBIN_400 }}>
Attributes
</Typography.Text>
</Badge>
</Tag>
{isAttributesExpanded && (
<Button

View File

@@ -11,7 +11,7 @@
font-size: 12px;
}
[data-slot='badge'] .ant-typography {
.ant-tag .ant-typography {
font-size: 12px;
}
}

View File

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

View File

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

View File

@@ -1,7 +1,6 @@
import { useEffect, useState } from 'react';
import { useMutation } from 'react-query';
import { Radio, RadioChangeEvent } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Radio, RadioChangeEvent, Tag } from 'antd';
import { Switch } from '@signozhq/ui/switch';
import setLocalStorageApi from 'api/browser/localstorage/set';
import logEvent from 'api/common/logEvent';
@@ -65,7 +64,9 @@ function MySettings(): JSX.Element {
label: (
<div className="theme-option">
<Sun size={12} data-testid="light-theme-icon" /> Light{' '}
<Badge color="robin">Beta</Badge>
<Tag bordered={false} color="geekblue">
Beta
</Tag>
</div>
),
value: 'light',

View File

@@ -1,4 +1,4 @@
import { Badge } from '@signozhq/ui/badge';
import { Tag as AntDTag } from 'antd';
import styled from 'styled-components';
export const Container = styled.div`
@@ -20,6 +20,6 @@ export const PanelContainer = styled.div`
overflow-y: auto;
`;
export const Tag = styled(Badge)`
export const Tag = styled(AntDTag)`
margin: 0;
`;

View File

@@ -1,12 +1,12 @@
import { Badge } from '@signozhq/ui/badge';
import { Tag } from 'antd';
function Tags({ tags }: TagsProps): JSX.Element {
return (
<span>
{tags?.map((tag) => (
<Badge color="sakura" key={tag}>
<Tag color="magenta" key={tag}>
{tag}
</Badge>
</Tag>
))}
</span>
);

View File

@@ -1,8 +1,7 @@
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { CircleAlert, CircleX, X } from '@signozhq/icons';
import { Button, Input, InputRef, message, Modal, Tooltip } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { CircleAlert, CircleX } from '@signozhq/icons';
import { Button, Input, InputRef, message, Modal, Tag, Tooltip } from 'antd';
import { tagInputStyle } from '../PipelineListsView/config';
import { TagInputWrapper } from './styles';
@@ -91,7 +90,12 @@ function TagInput({
}
const isLongTag = tag.length > 20;
const tagElem = (
<Badge key={tag} color="vanilla" style={{ userSelect: 'none' }}>
<Tag
key={tag}
closable
style={{ userSelect: 'none' }}
onClose={handleClose(tag)}
>
<span
onDoubleClick={(e): void => {
setEditInputIndex(index);
@@ -101,12 +105,7 @@ function TagInput({
>
{isLongTag ? `${tag.slice(0, 20)}...` : tag}
</span>
<X
size={12}
style={{ cursor: 'pointer', marginInlineStart: 4 }}
onClick={handleClose(tag)}
/>
</Badge>
</Tag>
);
return isLongTag ? (
<Tooltip title={tag} key={tag}>

View File

@@ -4,20 +4,12 @@ exports[`PipelinePage container test should render Tags section 1`] = `
<DocumentFragment>
<span>
<span
class="_badge_1jqif_1"
data-capitalize="false"
data-color="sakura"
data-slot="badge"
data-variant="default"
class="ant-tag ant-tag-magenta css-dev-only-do-not-override-2i2tap"
>
server
</span>
<span
class="_badge_1jqif_1"
data-capitalize="false"
data-color="sakura"
data-slot="badge"
data-variant="default"
class="ant-tag ant-tag-magenta css-dev-only-do-not-override-2i2tap"
>
app
</span>

View File

@@ -53,6 +53,12 @@
margin-bottom: 8px;
overflow: auto;
max-height: 100px;
.ant-tag {
user-select: none;
height: 28px;
display: inline-flex;
align-items: center;
}
}
.ant-select {
@@ -89,8 +95,61 @@
}
.alert-rule-tags {
.ant-tag {
display: flex;
align-items: center;
border: 1px solid color-mix(in srgb, var(--bg-robin-400) 20%, transparent);
background: color-mix(in srgb, var(--bg-robin-400) 10%, transparent);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
padding-right: 0px;
border-right: 0;
color: var(--bg-robin-400);
.ant-tag-close-icon {
height: 28px;
width: 20px !important;
justify-content: center;
border-left: 1px solid
color-mix(in srgb, var(--bg-robin-400) 20%, transparent);
border-radius: 0px 2px 2px 0px;
background: color-mix(in srgb, var(--bg-robin-400) 30%, transparent);
border-right: 1px solid
color-mix(in srgb, var(--bg-robin-400) 20%, transparent);
margin-right: 0px;
svg {
fill: var(--primary-background);
}
}
}
.non-closable-tag {
padding-right: 7px;
border-right: 1px solid
color-mix(in srgb, var(--bg-robin-400) 20%, transparent);
}
.red-tag.non-closable-tag {
border-right: 1px solid
color-mix(in srgb, var(--bg-sakura-500) 20%, transparent) !important;
}
.red-tag {
border: 1px solid color-mix(in srgb, var(--bg-sakura-500) 20%, transparent);
background: color-mix(in srgb, var(--bg-sakura-500) 10%, transparent);
border-right: 0;
color: var(--bg-sakura-400);
.ant-tag-close-icon {
background: color-mix(in srgb, var(--bg-sakura-500) 30%, transparent);
border-left: 1px solid
color-mix(in srgb, var(--bg-sakura-500) 20%, transparent);
border-right: 1px solid
color-mix(in srgb, var(--bg-sakura-500) 20%, transparent);
svg {
fill: var(--bg-sakura-500);
}
}
}
}
@@ -198,6 +257,9 @@
line-height: 18px;
letter-spacing: -0.07px;
}
.ant-tag {
border-radius: 20px;
}
.action-btn {
display: flex;
@@ -252,6 +314,12 @@
width: 540px;
max-height: 100px;
overflow: auto;
.ant-tag {
height: 28px;
display: flex;
align-items: center;
}
}
.ant-collapse-content-active {
border-top: 0;

View File

@@ -311,7 +311,7 @@ export function PlannedDowntimeForm(
default:
return `Scheduled for ${formattedStartDate} starting at ${formattedStartTime}.`;
}
}, [formData, recurrenceType]);
}, [formData, recurrenceType, timezone]);
const endTimeText = useMemo((): string => {
const endTime = formData.endTime;
@@ -322,7 +322,7 @@ export function PlannedDowntimeForm(
const formattedEndTime = endTime.format(TIME_FORMAT);
const formattedEndDate = endTime.format(DATE_FORMAT);
return `Scheduled to end maintenance on ${formattedEndDate} at ${formattedEndTime}.`;
}, [formData, recurrenceType]);
}, [formData, recurrenceType, timezone]);
return (
<Modal

View File

@@ -1,8 +1,7 @@
import React, { ReactNode, useEffect } from 'react';
import { UseQueryResult } from 'react-query';
import { Color } from '@signozhq/design-tokens';
import { Collapse, Flex, Space, Table, TableProps, Tooltip } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Collapse, Flex, Space, Table, TableProps, Tag, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import type { DefaultOptionType } from 'antd/es/select';
import type {
@@ -16,7 +15,7 @@ import cx from 'classnames';
import dayjs from 'dayjs';
import { useNotifications } from 'hooks/useNotifications';
import { defaultTo } from 'lodash-es';
import { CalendarClock, PenLine, Trash2, X } from '@signozhq/icons';
import { CalendarClock, PenLine, Trash2 } from '@signozhq/icons';
import { useAppContext } from 'providers/App/App';
import { USER_ROLES } from 'types/roles';
@@ -49,10 +48,10 @@ export function AlertRuleTags(props: AlertRuleTagsProps): JSX.Element {
{selectedTags?.map((tag: DefaultOptionType, index: number) => {
const isLongTag = (tag?.label as string)?.length > 20;
const tagElem = (
<Badge
<Tag
key={tag.value}
color={index % 2 ? 'sakura' : 'robin'}
variant="outline"
onClose={(): void => handleClose?.(tag?.value)}
closable={closable}
className={cx(
{ 'red-tag': index % 2 },
{ 'non-closable-tag': !closable },
@@ -63,14 +62,7 @@ export function AlertRuleTags(props: AlertRuleTagsProps): JSX.Element {
? `${(tag?.label as string | null)?.slice(0, 20)}...`
: tag?.label}
</span>
{closable && (
<X
size={12}
style={{ cursor: 'pointer', marginInlineStart: 4 }}
onClick={(): void => handleClose?.(tag?.value)}
/>
)}
</Badge>
</Tag>
);
return isLongTag ? (
<Tooltip title={tag?.label} key={tag?.value}>
@@ -101,7 +93,7 @@ function HeaderComponent({
<Flex className="header-content" justify="space-between">
<Flex gap={8}>
<Typography>{name}</Typography>
<Badge color="vanilla">{duration}</Badge>
<Tag>{duration}</Tag>
</Flex>
{isCrudEnabled && (
@@ -163,7 +155,9 @@ export function CollapseListContent({
created_by_name ? (
<Flex gap={8}>
<Typography>{created_by_name}</Typography>
{created_by_email && <Badge color="vanilla">{created_by_email}</Badge>}
{created_by_email && (
<Tag style={{ borderRadius: 20 }}>{created_by_email}</Tag>
)}
</Flex>
) : (
'-'

View File

@@ -1,11 +1,11 @@
import { Badge } from '@signozhq/ui/badge';
import { Tag } from 'antd';
import styled from 'styled-components';
export const StyledText = styled.span`
cursor: pointer;
`;
export const StyledTag = styled(Badge)`
export const StyledTag = styled(Tag)`
margin-top: 0.125rem;
margin-bottom: 0.125rem;
padding-left: 0.5rem;

View File

@@ -1,5 +1,3 @@
import { X } from '@signozhq/icons';
import { HavingFilterTagProps } from './HavingFilterTag.interfaces';
import { StyledTag, StyledText } from './HavingFilterTag.styled';
@@ -14,17 +12,10 @@ export function HavingFilterTag({
};
return (
<StyledTag color="vanilla">
<StyledTag closable={closable} onClose={onClose}>
<span role="button" tabIndex={0} onClick={handleClick}>
<StyledText>{value}</StyledText>
</span>
{closable && (
<X
size={12}
style={{ cursor: 'pointer', marginInlineStart: 4 }}
onClick={onClose}
/>
)}
</StyledTag>
);
}

View File

@@ -9,8 +9,7 @@ import {
useState,
} from 'react';
import { useLocation } from 'react-router-dom';
import { Button, Select, Spin, Tooltip } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Button, Select, Spin, Tag, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import cx from 'classnames';
import {
@@ -38,7 +37,6 @@ import {
CornerDownLeft,
Filter,
Slash,
X,
} from '@signozhq/icons';
import type { BaseSelectRef } from 'rc-select';
import {
@@ -201,7 +199,7 @@ function QueryBuilderSearch({
const isDisabled = !!searchValue;
return (
<Badge color="vanilla">
<Tag closable={!searchValue && closable} onClose={onCloseHandler}>
<Tooltip title={chipValue}>
<TypographyText
$isInNin={isInNin}
@@ -215,14 +213,7 @@ function QueryBuilderSearch({
{chipValue}
</TypographyText>
</Tooltip>
{!searchValue && closable && (
<X
size={12}
style={{ cursor: 'pointer', marginInlineStart: 4 }}
onClick={onCloseHandler}
/>
)}
</Badge>
</Tag>
);
};

View File

@@ -1,5 +1,5 @@
import { Check } from '@signozhq/icons';
import { Badge } from '@signozhq/ui/badge';
import { Tag } from 'antd';
import styled from 'styled-components';
export const TypographyText = styled.span<{
@@ -22,7 +22,7 @@ export const StyledCheckOutlined = styled(Check)`
float: right;
`;
export const TagContainer = styled(Badge)`
export const TagContainer = styled(Tag)`
&&& {
display: inline-block;
border-radius: 3px;

View File

@@ -225,7 +225,7 @@
}
.qb-search-bar-tokenised-tags {
[data-slot='badge'] {
.ant-tag {
display: flex;
align-items: center;
border-radius: 2px 0px 0px 2px;
@@ -244,7 +244,7 @@
padding: 2px 6px;
}
.close-icon {
.ant-tag-close-icon {
display: flex;
align-items: center;
justify-content: center;
@@ -265,7 +265,7 @@
font-size: 14px;
}
.close-icon {
.ant-tag-close-icon {
background: color-mix(in srgb, var(--bg-aqua-400) 6%, transparent);
}
}
@@ -278,7 +278,7 @@
font-size: 14px;
}
.close-icon {
.ant-tag-close-icon {
background: color-mix(in srgb, var(--bg-sienna-400) 10%, transparent);
}
}
@@ -292,7 +292,7 @@
font-size: 14px;
}
.close-icon {
.ant-tag-close-icon {
background: color-mix(in srgb, var(--bg-robin-400) 10%, transparent);
}
}

View File

@@ -8,8 +8,7 @@ import {
useRef,
useState,
} from 'react';
import { Select, Spin, Tooltip } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Select, Spin, Tag, Tooltip } from 'antd';
import cx from 'classnames';
import {
DATA_TYPE_VS_ATTRIBUTE_VALUES_KEY,
@@ -39,7 +38,7 @@ import {
isUndefined,
unset,
} from 'lodash-es';
import { ChevronDown, ChevronUp, X } from '@signozhq/icons';
import { ChevronDown, ChevronUp } from '@signozhq/icons';
import type { BaseSelectRef } from 'rc-select';
import {
BaseAutocompleteData,
@@ -955,7 +954,11 @@ function QueryBuilderSearchV2(
return (
<span className="qb-search-bar-tokenised-tags">
<Badge color="vanilla" className={tagDetails?.key?.type || ''}>
<Tag
closable={!searchValue && closable}
onClose={onCloseHandler}
className={tagDetails?.key?.type || ''}
>
<Tooltip title={chipValue}>
<TypographyText
$isInNin={isInNin}
@@ -969,15 +972,7 @@ function QueryBuilderSearchV2(
{chipValue}
</TypographyText>
</Tooltip>
{!searchValue && closable && (
<X
size={12}
className="close-icon"
style={{ cursor: 'pointer' }}
onClick={onCloseHandler}
/>
)}
</Badge>
</Tag>
</span>
);
};

View File

@@ -10,7 +10,7 @@
font-size: 12px;
}
[data-slot='badge'] .ant-typography {
.ant-tag .ant-typography {
font-size: 12px;
}
}

View File

@@ -1,4 +1,3 @@
import { X } from '@signozhq/icons';
import {
convertMetricKeyToTrace,
getResourceDeploymentKeys,
@@ -21,19 +20,13 @@ function QueryChip({ queryData, onClose }: IQueryChipProps): JSX.Element {
return (
<QueryChipContainer>
<QueryChipItem color="vanilla">
{convertMetricKeyToTrace(queryData.tagKey)}
</QueryChipItem>
<QueryChipItem color="vanilla">{queryData.operator}</QueryChipItem>
<QueryChipItem color="vanilla">
<QueryChipItem>{convertMetricKeyToTrace(queryData.tagKey)}</QueryChipItem>
<QueryChipItem>{queryData.operator}</QueryChipItem>
<QueryChipItem
closable={queryData.tagKey !== getResourceDeploymentKeys(dotMetricsEnabled)}
onClose={onCloseHandler}
>
{queryData.tagValue.join(', ')}
{queryData.tagKey !== getResourceDeploymentKeys(dotMetricsEnabled) && (
<X
size={12}
style={{ cursor: 'pointer', marginInlineStart: 4 }}
onClick={onCloseHandler}
/>
)}
</QueryChipItem>
</QueryChipContainer>
);

View File

@@ -1,5 +1,5 @@
import { grey } from '@ant-design/colors';
import { Badge } from '@signozhq/ui/badge';
import { Tag } from 'antd';
import styled from 'styled-components';
export const SearchContainer = styled.div`
@@ -23,6 +23,6 @@ export const QueryChipContainer = styled.span`
}
`;
export const QueryChipItem = styled(Badge)`
export const QueryChipItem = styled(Tag)`
margin-right: 0.1rem;
`;

View File

@@ -1,6 +1,5 @@
import { Color } from '@signozhq/design-tokens';
import { Button, Collapse, Flex } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Button, Collapse, Flex, Tag } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import { PenLine, Trash2 } from '@signozhq/icons';
@@ -119,9 +118,7 @@ function PolicyListItemContent({
<Typography>Channels</Typography>
<div>
{routingPolicy.channels.map((channel) => (
<Badge key={channel} color="vanilla">
{channel}
</Badge>
<Tag key={channel}>{channel}</Tag>
))}
</div>
</div>

View File

@@ -1,5 +1,4 @@
import { Tooltip } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Tag, Tooltip } from 'antd';
import cx from 'classnames';
import { Pin, PinOff } from '@signozhq/icons';
@@ -59,17 +58,17 @@ export default function NavItem({
{isBeta && (
<div className="nav-item-beta">
<Badge color="robin" className="sidenav-beta-tag">
<Tag bordered={false} className="sidenav-beta-tag">
Beta
</Badge>
</Tag>
</div>
)}
{isNew && (
<div className="nav-item-new">
<Badge color="robin" className="sidenav-new-tag">
<Tag bordered={false} className="sidenav-new-tag">
New
</Badge>
</Tag>
</div>
)}

View File

@@ -1,8 +1,7 @@
import { HTMLAttributes } from 'react';
// eslint-disable-next-line no-restricted-imports
import { useDispatch, useSelector } from 'react-redux';
import { TableColumnsType as ColumnsType, TableProps } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { TableColumnsType as ColumnsType, TableProps, Tag } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { ResizeTable } from 'components/ResizeTable';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
@@ -71,7 +70,7 @@ function TraceTable(): JSX.Element {
if (value.length === 0) {
return <Typography>-</Typography>;
}
return <Badge color="sakura">{value}</Badge>;
return <Tag color="magenta">{value}</Tag>;
};
const columns: ColumnsType<TableType> = [

View File

@@ -1,6 +1,6 @@
import { Link } from 'react-router-dom';
import type { TableColumnsType as ColumnsType } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Tag } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { TelemetryFieldKey } from 'api/v5/v5';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
@@ -107,9 +107,9 @@ export const getListColumns = (
) {
return (
<BlockLink to={getTraceLink(item)} openInNewTab={false}>
<Badge data-testid={name} color="sakura" variant="outline">
<Tag data-testid={name} color="magenta">
{value}
</Badge>
</Tag>
</BlockLink>
);
}

View File

@@ -1,8 +1,6 @@
import { useCallback, useMemo, useRef } from 'react';
import { X } from '@signozhq/icons';
import type { SelectProps } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Tooltip } from 'antd';
import { Tag, Tooltip } from 'antd';
import type { BaseOptionType } from 'antd/es/select';
import { Alerts } from 'types/api/alerts/getTriggered';
@@ -85,16 +83,14 @@ function Filter({
const { closable, onClose, label } = props;
return (
<Badge color="sakura" style={{ marginRight: 3 }}>
<Tag
color="magenta"
closable={closable}
onClose={onClose}
style={{ marginRight: 3 }}
>
{label}
{closable && (
<X
size={12}
style={{ cursor: 'pointer', marginInlineStart: 4 }}
onClick={(): void => onClose()}
/>
)}
</Badge>
</Tag>
);
};

View File

@@ -1,4 +1,4 @@
import { Badge } from '@signozhq/ui/badge';
import { Tag } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import { useTimezone } from 'providers/Timezone';
@@ -51,7 +51,7 @@ function ExapandableRow({ allAlerts }: ExapandableRowProps): JSX.Element {
<TableCell minWidth="90px" overflowX="scroll">
<div>
{tags.map((e) => (
<Badge color="vanilla" key={e}>{`${e}:${labels[e]}`}</Badge>
<Tag key={e}>{`${e}:${labels[e]}`}</Tag>
))}
</div>
</TableCell>

View File

@@ -1,6 +1,6 @@
import { useState } from 'react';
import { SquareMinus, SquarePlus } from '@signozhq/icons';
import { Badge } from '@signozhq/ui/badge';
import { Tag } from 'antd';
import { Alerts } from 'types/api/alerts/getTriggered';
import ExapandableRow from './ExapandableRow';
@@ -26,9 +26,9 @@ function TableRowComponent({
</IconContainer>
<>
{tags.map((tag) => (
<Badge color="sakura" key={tag}>
<Tag color="magenta" key={tag}>
{tag}
</Badge>
</Tag>
))}
</>
</StatusContainer>

View File

@@ -59,7 +59,9 @@ function NoFilterTable({
return <Typography>-</Typography>;
}
return <LabelColumn labels={withOutSeverityKeys} value={labels} />;
return (
<LabelColumn labels={withOutSeverityKeys} value={labels} color="magenta" />
);
},
},
{

View File

@@ -1,21 +1,21 @@
import { Badge } from '@signozhq/ui/badge';
import { Tag } from 'antd';
function Severity({ severity }: SeverityProps): JSX.Element {
switch (severity) {
case 'unprocessed': {
return <Badge color="forest">UnProcessed</Badge>;
return <Tag color="green">UnProcessed</Tag>;
}
case 'active': {
return <Badge color="cherry">Firing</Badge>;
return <Tag color="red">Firing</Tag>;
}
case 'suppressed': {
return <Badge color="cherry">Suppressed</Badge>;
return <Tag color="red">Suppressed</Tag>;
}
default: {
return <Badge color="vanilla">Unknown Status</Badge>;
return <Tag color="default">Unknown Status</Tag>;
}
}
}

View File

@@ -1,5 +1,9 @@
import { Badge } from '@signozhq/ui/badge';
import { Tag } from 'antd';
export default function BetaTag(): JSX.Element {
return <Badge color="robin">Beta</Badge>;
return (
<Tag bordered={false} color="geekblue">
Beta
</Tag>
);
}

View File

@@ -1,7 +1,6 @@
import { useState } from 'react';
import { Color } from '@signozhq/design-tokens';
import { Button } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Button, Tag } from 'antd';
import { TimelineFilter } from 'container/AlertHistory/types';
import { Undo } from '@signozhq/icons';
@@ -66,7 +65,11 @@ function Tabs2({
>
{tab.label}
{tab.isBeta && <Badge color="robin">Beta</Badge>}
{tab.isBeta && (
<Tag bordered={false} color="geekblue">
Beta
</Tag>
)}
</Button>
))}
</Button.Group>

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/tracedetailtypes"
"github.com/SigNoz/signoz/pkg/types/spantypes"
"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(tracedetailtypes.PostableWaterfall),
Request: new(spantypes.PostableWaterfall),
RequestContentType: "application/json",
Response: new(tracedetailtypes.GettableWaterfallTrace),
Response: new(spantypes.GettableWaterfallTrace),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},

View File

@@ -2,8 +2,11 @@ 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"
)
@@ -34,11 +37,22 @@ func (module *module) UpdateGroup(ctx context.Context, orgID, id valuer.UUID, na
return err
}
group.Update(name, condition, enabled, updatedBy)
return module.store.UpdateGroup(ctx, group)
err = module.store.UpdateGroup(ctx, group)
if err != nil {
return err
}
agentConf.NotifyConfigUpdate(ctx)
return nil
}
func (module *module) DeleteGroup(ctx context.Context, orgID, id valuer.UUID) error {
return module.store.DeleteGroup(ctx, orgID, id)
err := module.store.DeleteGroup(ctx, orgID, id)
if err != nil {
return err
}
agentConf.NotifyConfigUpdate(ctx)
return nil
}
func (module *module) ListMappers(ctx context.Context, orgID, groupID valuer.UUID) ([]*spantypes.SpanMapper, error) {
@@ -54,7 +68,12 @@ func (module *module) CreateMapper(ctx context.Context, orgID, groupID valuer.UU
if _, err := module.store.GetGroup(ctx, orgID, groupID); err != nil {
return err
}
return module.store.CreateMapper(ctx, mapper)
err := module.store.CreateMapper(ctx, mapper)
if err != nil {
return err
}
agentConf.NotifyConfigUpdate(ctx)
return nil
}
func (module *module) UpdateMapper(ctx context.Context, orgID, groupID, id valuer.UUID, fieldContext spantypes.FieldContext, config *spantypes.SpanMapperConfig, enabled *bool, updatedBy string) error {
@@ -66,9 +85,72 @@ func (module *module) UpdateMapper(ctx context.Context, orgID, groupID, id value
return err
}
mapper.Update(fieldContext, config, enabled, updatedBy)
return module.store.UpdateMapper(ctx, mapper)
err = module.store.UpdateMapper(ctx, mapper)
if err != nil {
return err
}
agentConf.NotifyConfigUpdate(ctx)
return nil
}
func (module *module) DeleteMapper(ctx context.Context, orgID, groupID, id valuer.UUID) error {
return module.store.DeleteMapper(ctx, orgID, groupID, id)
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
}

View File

@@ -4,12 +4,16 @@ 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/tracedetailtypes"
"github.com/SigNoz/signoz/pkg/types/spantypes"
"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(tracedetailtypes.PostableWaterfall)
req := new(spantypes.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/tracedetailtypes"
"github.com/SigNoz/signoz/pkg/types/spantypes"
)
type module struct {
store tracedetailtypes.TraceStore
store spantypes.TraceStore
settings factory.ScopedProviderSettings
config tracedetail.Config
}
func NewModule(traceStore tracedetailtypes.TraceStore, providerSettings factory.ProviderSettings, cfg tracedetail.Config) *module {
func NewModule(traceStore spantypes.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 tracedetailtypes.TraceStore, providerSettings factory.
}
}
func (m *module) GetWaterfall(ctx context.Context, traceID string, req *tracedetailtypes.PostableWaterfall) (*tracedetailtypes.GettableWaterfallTrace, error) {
func (m *module) GetWaterfall(ctx context.Context, traceID string, req *spantypes.PostableWaterfall) (*spantypes.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 *tracedet
m.config.Waterfall.MaxDepthToAutoExpand,
)
aggregationResults := make([]tracedetailtypes.SpanAggregationResult, 0, len(req.Aggregations))
aggregationResults := make([]spantypes.SpanAggregationResult, 0, len(req.Aggregations))
for _, a := range req.Aggregations {
aggregationResults = append(aggregationResults, waterfallTrace.GetSpanAggregation(a.Aggregation, a.Field))
}
return tracedetailtypes.NewGettableWaterfallTrace(waterfallTrace, selectedSpans, uncollapsedSpans, selectedAllSpans, aggregationResults), nil
return spantypes.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) (*tracedetailtypes.WaterfallTrace, error) {
func (m *module) getTraceData(ctx context.Context, traceID string) (*spantypes.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) (*tracedetail
}
if len(spanItems) == 0 {
return nil, tracedetailtypes.ErrTraceNotFound
return nil, spantypes.ErrTraceNotFound
}
traceData := tracedetailtypes.NewWaterfallTraceFromSpans(spanItems)
traceData := spantypes.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/tracedetailtypes"
"github.com/SigNoz/signoz/pkg/types/spantypes"
)
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) (*tracedetailtypes.TraceSummary, error) {
func (s *traceStore) GetTraceSummary(ctx context.Context, traceID string) (*spantypes.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", tracedetailtypes.TraceDB, tracedetailtypes.TraceSummaryTable))
sb.From(fmt.Sprintf("%s.%s", spantypes.TraceDB, spantypes.TraceSummaryTable))
sb.Where(sb.E("trace_id", traceID))
sb.GroupBy("trace_id")
query, args := sb.BuildWithFlavor(sqlbuilder.ClickHouse)
var summary tracedetailtypes.TraceSummary
var summary spantypes.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, tracedetailtypes.ErrTraceNotFound
return nil, spantypes.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 *tracedetailtypes.TraceSummary) ([]tracedetailtypes.StorableSpan, error) {
func (s *traceStore) GetTraceSpans(ctx context.Context, traceID string, summary *spantypes.TraceSummary) ([]spantypes.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`,
tracedetailtypes.TraceDB, tracedetailtypes.TraceTable,
spantypes.TraceDB, spantypes.TraceTable,
)
var spanItems []tracedetailtypes.StorableSpan
var spanItems []spantypes.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/tracedetailtypes"
"github.com/SigNoz/signoz/pkg/types/spantypes"
"github.com/stretchr/testify/assert"
)
@@ -45,8 +45,8 @@ import (
// Helpers
// ─────────────────────────────────────────────────────────────────────────────
func mkSpan(id, service string, children ...*tracedetailtypes.WaterfallSpan) *tracedetailtypes.WaterfallSpan {
return &tracedetailtypes.WaterfallSpan{
func mkSpan(id, service string, children ...*spantypes.WaterfallSpan) *spantypes.WaterfallSpan {
return &spantypes.WaterfallSpan{
SpanID: id,
ServiceName: service,
Name: id + "-op",
@@ -54,7 +54,7 @@ func mkSpan(id, service string, children ...*tracedetailtypes.WaterfallSpan) *tr
}
}
func spanIDs(spans []*tracedetailtypes.WaterfallSpan) []string {
func spanIDs(spans []*spantypes.WaterfallSpan) []string {
ids := make([]string, len(spans))
for i, s := range spans {
ids[i] = s.SpanID
@@ -62,10 +62,10 @@ func spanIDs(spans []*tracedetailtypes.WaterfallSpan) []string {
return ids
}
func buildSpanMap(roots ...*tracedetailtypes.WaterfallSpan) map[string]*tracedetailtypes.WaterfallSpan {
m := map[string]*tracedetailtypes.WaterfallSpan{}
var walk func(*tracedetailtypes.WaterfallSpan)
walk = func(s *tracedetailtypes.WaterfallSpan) {
func buildSpanMap(roots ...*spantypes.WaterfallSpan) map[string]*spantypes.WaterfallSpan {
m := map[string]*spantypes.WaterfallSpan{}
var walk func(*spantypes.WaterfallSpan)
walk = func(s *spantypes.WaterfallSpan) {
m[s.SpanID] = s
for _, c := range s.Children {
walk(c)
@@ -80,8 +80,8 @@ func buildSpanMap(roots ...*tracedetailtypes.WaterfallSpan) map[string]*tracedet
}
// makeChain builds a linear trace: span0 → span1 → … → span(n-1).
func makeChain(n int) (*tracedetailtypes.WaterfallSpan, map[string]*tracedetailtypes.WaterfallSpan, []string) {
spans := make([]*tracedetailtypes.WaterfallSpan, n)
func makeChain(n int) (*spantypes.WaterfallSpan, map[string]*spantypes.WaterfallSpan, []string) {
spans := make([]*spantypes.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) (*tracedetailtypes.WaterfallSpan, map[string]*tracedetailt
return spans[0], buildSpanMap(spans[0]), uncollapsed
}
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)
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)
}
// ─────────────────────────────────────────────────────────────────────────────
@@ -107,7 +107,7 @@ func getWaterfallTrace(roots []*tracedetailtypes.WaterfallSpan, spanMap map[stri
func TestGetSelectedSpans_SpanOrdering(t *testing.T) {
tests := []struct {
name string
buildRoots func() ([]*tracedetailtypes.WaterfallSpan, map[string]*tracedetailtypes.WaterfallSpan)
buildRoots func() ([]*spantypes.WaterfallSpan, map[string]*spantypes.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() ([]*tracedetailtypes.WaterfallSpan, map[string]*tracedetailtypes.WaterfallSpan) {
buildRoots: func() ([]*spantypes.WaterfallSpan, map[string]*spantypes.WaterfallSpan) {
root := mkSpan("root", "svc",
mkSpan("child1", "svc", mkSpan("grandchild", "svc")),
mkSpan("child2", "svc"),
)
return []*tracedetailtypes.WaterfallSpan{root}, buildSpanMap(root)
return []*spantypes.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() ([]*tracedetailtypes.WaterfallSpan, map[string]*tracedetailtypes.WaterfallSpan) {
buildRoots: func() ([]*spantypes.WaterfallSpan, map[string]*spantypes.WaterfallSpan) {
root := mkSpan("root", "svc",
mkSpan("childA", "svc", mkSpan("grandchildA", "svc")),
mkSpan("childB", "svc", mkSpan("grandchildB", "svc")),
)
return []*tracedetailtypes.WaterfallSpan{root}, buildSpanMap(root)
return []*spantypes.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() ([]*tracedetailtypes.WaterfallSpan, map[string]*tracedetailtypes.WaterfallSpan) {
buildRoots: func() ([]*spantypes.WaterfallSpan, map[string]*spantypes.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 []*tracedetailtypes.WaterfallSpan{root}, buildSpanMap(root)
return []*spantypes.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() ([]*tracedetailtypes.WaterfallSpan, map[string]*tracedetailtypes.WaterfallSpan) {
buildRoots: func() ([]*spantypes.WaterfallSpan, map[string]*spantypes.WaterfallSpan) {
root := mkSpan("root", "svc",
mkSpan("child1", "svc"),
mkSpan("child2", "svc"),
)
return []*tracedetailtypes.WaterfallSpan{root}, buildSpanMap(root)
return []*spantypes.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() ([]*tracedetailtypes.WaterfallSpan, map[string]*tracedetailtypes.WaterfallSpan) {
buildRoots: func() ([]*spantypes.WaterfallSpan, map[string]*spantypes.WaterfallSpan) {
root := mkSpan("root", "svc",
mkSpan("parent", "svc",
mkSpan("selected", "svc"),
),
)
return []*tracedetailtypes.WaterfallSpan{root}, buildSpanMap(root)
return []*spantypes.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() ([]*tracedetailtypes.WaterfallSpan, map[string]*tracedetailtypes.WaterfallSpan) {
buildRoots: func() ([]*spantypes.WaterfallSpan, map[string]*spantypes.WaterfallSpan) {
root := mkSpan("root", "svc",
mkSpan("unrelated", "svc", mkSpan("unrelated-child", "svc")),
mkSpan("parent", "svc",
mkSpan("selected", "svc"),
),
)
return []*tracedetailtypes.WaterfallSpan{root}, buildSpanMap(root)
return []*spantypes.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() ([]*tracedetailtypes.WaterfallSpan, map[string]*tracedetailtypes.WaterfallSpan) {
buildRoots: func() ([]*spantypes.WaterfallSpan, map[string]*spantypes.WaterfallSpan) {
root := mkSpan("root", "svc", mkSpan("child", "svc"))
return []*tracedetailtypes.WaterfallSpan{root}, buildSpanMap(root)
return []*spantypes.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([]*tracedetailtypes.WaterfallSpan{root1, root2}, spanMap)
trace := getWaterfallTrace([]*spantypes.WaterfallSpan{root1, root2}, spanMap)
spans, _ := trace.GetSelectedSpans([]string{"root1", "root2"}, "root1", 500, 5)
traceRespnose := tracedetailtypes.NewGettableWaterfallTrace(trace, spans, nil, false, nil)
traceRespnose := spantypes.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() (*tracedetailtypes.WaterfallSpan, map[string]*tracedetailtypes.WaterfallSpan)
buildRoot func() (*spantypes.WaterfallSpan, map[string]*spantypes.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() (*tracedetailtypes.WaterfallSpan, map[string]*tracedetailtypes.WaterfallSpan) {
buildRoot: func() (*spantypes.WaterfallSpan, map[string]*spantypes.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() (*tracedetailtypes.WaterfallSpan, map[string]*tracedetailtypes.WaterfallSpan) {
buildRoot: func() (*spantypes.WaterfallSpan, map[string]*spantypes.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() (*tracedetailtypes.WaterfallSpan, map[string]*tracedetailtypes.WaterfallSpan) {
buildRoot: func() (*spantypes.WaterfallSpan, map[string]*spantypes.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() (*tracedetailtypes.WaterfallSpan, map[string]*tracedetailtypes.WaterfallSpan) {
buildRoot: func() (*spantypes.WaterfallSpan, map[string]*spantypes.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([]*tracedetailtypes.WaterfallSpan{root}, spanMap)
trace := getWaterfallTrace([]*spantypes.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([]*tracedetailtypes.WaterfallSpan{root}, spanMap)
trace := getWaterfallTrace([]*spantypes.WaterfallSpan{root}, spanMap)
spans, _ := trace.GetSelectedSpans([]string{"root", "child1"}, "root", 500, 5)
byID := map[string]*tracedetailtypes.WaterfallSpan{}
byID := map[string]*spantypes.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([]*tracedetailtypes.WaterfallSpan{root}, spanMap)
trace := getWaterfallTrace([]*spantypes.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([]*tracedetailtypes.WaterfallSpan{root}, spanMap)
trace := getWaterfallTrace([]*spantypes.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([]*tracedetailtypes.WaterfallSpan{root}, nil)
trace := getWaterfallTrace([]*spantypes.WaterfallSpan{root}, nil)
spans := trace.GetAllSpans()
traceResponse := tracedetailtypes.NewGettableWaterfallTrace(trace, spans, nil, true, nil)
traceResponse := spantypes.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/tracedetailtypes"
"github.com/SigNoz/signoz/pkg/types/spantypes"
)
// 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 *tracedetailtypes.PostableWaterfall) (*tracedetailtypes.GettableWaterfallTrace, error)
GetWaterfall(ctx context.Context, traceID string, req *spantypes.PostableWaterfall) (*spantypes.GettableWaterfallTrace, error)
}

View File

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

View File

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

View File

@@ -13,7 +13,9 @@ var (
ErrCodeMappingGroupAlreadyExists = errors.MustNewCode("span_attribute_mapping_group_already_exists")
)
// A group runs when any of the listed attribute/resource key patterns match.
// 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).
type SpanMapperGroupCondition struct {
Attributes []string `json:"attributes" required:"true" nullable:"true"`
Resource []string `json:"resource" required:"true" nullable:"true"`

View File

@@ -0,0 +1,140 @@
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

@@ -0,0 +1,198 @@
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,3 +21,9 @@ 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

@@ -0,0 +1,17 @@
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

@@ -0,0 +1,17 @@
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

@@ -0,0 +1,67 @@
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 tracedetailtypes
package spantypes
import (
"encoding/json"

View File

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

View File

@@ -1,9 +0,0 @@
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

@@ -14,6 +14,24 @@ 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 {
@@ -25,6 +43,14 @@ 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,6 +38,7 @@ func TestNewWithEnvProvider(t *testing.T) {
Enabled: false,
Index: def.Index,
Directory: def.Directory,
Settings: def.Settings,
}
assert.Equal(t, expected, actual)

View File

@@ -2,6 +2,8 @@ package routerweb
import (
"context"
"encoding/json"
"html/template"
"net/http"
"os"
"path/filepath"
@@ -42,8 +44,16 @@ 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()})
indexContents := web.NewIndex(ctx, logger, config.Index, raw, web.TemplateData{
BaseHref: globalConfig.ExternalPathTrailing(),
Settings: template.JS(settingsJSON),
})
return &provider{
config: config,

View File

@@ -2,6 +2,7 @@ package routerweb
import (
"context"
"encoding/json"
"io"
"net"
"net/http"
@@ -19,6 +20,11 @@ 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()
@@ -54,53 +60,79 @@ 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{},
expected: `<html><head><base href="/" /></head><body>Welcome to test data!!!</body></html>`,
webConfig: web.Config{Index: "valid_template.html", Directory: "testdata"},
expected: expectedHTML("/", emptySettings),
},
{
name: "RootBaseHrefAtNonExistentPath",
path: "/does-not-exist",
globalConfig: global.Config{},
expected: `<html><head><base href="/" /></head><body>Welcome to test data!!!</body></html>`,
webConfig: web.Config{Index: "valid_template.html", Directory: "testdata"},
expected: expectedHTML("/", emptySettings),
},
{
name: "RootBaseHrefAtDirectory",
path: "/assets",
globalConfig: global.Config{},
expected: `<html><head><base href="/" /></head><body>Welcome to test data!!!</body></html>`,
webConfig: web.Config{Index: "valid_template.html", Directory: "testdata"},
expected: expectedHTML("/", emptySettings),
},
{
name: "SubPathBaseHrefAtRoot",
path: "/",
globalConfig: global.Config{ExternalURL: &url.URL{Scheme: "https", Host: "example.com", Path: "/signoz"}},
expected: `<html><head><base href="/signoz/" /></head><body>Welcome to test data!!!</body></html>`,
webConfig: web.Config{Index: "valid_template.html", Directory: "testdata"},
expected: expectedHTML("/signoz/", emptySettings),
},
{
name: "SubPathBaseHrefAtNonExistentPath",
path: "/does-not-exist",
globalConfig: global.Config{ExternalURL: &url.URL{Scheme: "https", Host: "example.com", Path: "/signoz"}},
expected: `<html><head><base href="/signoz/" /></head><body>Welcome to test data!!!</body></html>`,
webConfig: web.Config{Index: "valid_template.html", Directory: "testdata"},
expected: expectedHTML("/signoz/", emptySettings),
},
{
name: "SubPathBaseHrefAtDirectory",
path: "/assets",
globalConfig: global.Config{ExternalURL: &url.URL{Scheme: "https", Host: "example.com", Path: "/signoz"}},
expected: `<html><head><base href="/signoz/" /></head><body>Welcome to test data!!!</body></html>`,
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},
}),
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
base := startServer(t, web.Config{Index: "valid_template.html", Directory: "testdata"}, testCase.globalConfig)
base := startServer(t, testCase.webConfig, 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>Welcome to test data!!!</body></html>
<html><head><base href="[[.BaseHref]]" /></head><body><script>window.signozBootData={settings:[[.Settings]]}</script>Welcome to test data!!!</body></html>

View File

@@ -11,8 +11,14 @@ 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