mirror of
https://github.com/SigNoz/signoz.git
synced 2026-06-02 23:20:34 +01:00
Compare commits
5 Commits
chore/upda
...
refactor/c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04e2caaaaf | ||
|
|
0963ff08cd | ||
|
|
e43aeb8e24 | ||
|
|
9074208b09 | ||
|
|
571e23910e |
@@ -64,16 +64,16 @@ web:
|
||||
settings:
|
||||
posthog:
|
||||
# Whether to enable PostHog in web.
|
||||
enabled: true
|
||||
enabled: false
|
||||
appcues:
|
||||
# Whether to enable Appcues in web.
|
||||
enabled: true
|
||||
enabled: false
|
||||
sentry:
|
||||
# Whether to enable Sentry in web.
|
||||
enabled: true
|
||||
enabled: false
|
||||
pylon:
|
||||
# Whether to enable Pylon in web.
|
||||
enabled: true
|
||||
enabled: false
|
||||
|
||||
##################### Cache #####################
|
||||
cache:
|
||||
|
||||
@@ -870,14 +870,6 @@ components:
|
||||
- timestampMillis
|
||||
- data
|
||||
type: object
|
||||
CloudintegrationtypesAssets:
|
||||
properties:
|
||||
dashboards:
|
||||
items:
|
||||
$ref: '#/components/schemas/CloudintegrationtypesDashboard'
|
||||
nullable: true
|
||||
type: array
|
||||
type: object
|
||||
CloudintegrationtypesAzureAccountConfig:
|
||||
properties:
|
||||
deploymentRegion:
|
||||
@@ -1025,17 +1017,6 @@ components:
|
||||
- ingestionUrl
|
||||
- ingestionKey
|
||||
type: object
|
||||
CloudintegrationtypesDashboard:
|
||||
properties:
|
||||
definition:
|
||||
$ref: '#/components/schemas/DashboardtypesStorableDashboardData'
|
||||
description:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
title:
|
||||
type: string
|
||||
type: object
|
||||
CloudintegrationtypesDataCollected:
|
||||
properties:
|
||||
logs:
|
||||
@@ -1209,7 +1190,7 @@ components:
|
||||
CloudintegrationtypesService:
|
||||
properties:
|
||||
assets:
|
||||
$ref: '#/components/schemas/CloudintegrationtypesAssets'
|
||||
$ref: '#/components/schemas/CloudintegrationtypesServiceAssets'
|
||||
cloudIntegrationService:
|
||||
$ref: '#/components/schemas/CloudintegrationtypesCloudIntegrationService'
|
||||
dataCollected:
|
||||
@@ -1222,8 +1203,6 @@ components:
|
||||
type: string
|
||||
supportedSignals:
|
||||
$ref: '#/components/schemas/CloudintegrationtypesSupportedSignals'
|
||||
telemetryCollectionStrategy:
|
||||
$ref: '#/components/schemas/CloudintegrationtypesTelemetryCollectionStrategy'
|
||||
title:
|
||||
type: string
|
||||
required:
|
||||
@@ -1234,9 +1213,17 @@ components:
|
||||
- assets
|
||||
- supportedSignals
|
||||
- dataCollected
|
||||
- telemetryCollectionStrategy
|
||||
- cloudIntegrationService
|
||||
type: object
|
||||
CloudintegrationtypesServiceAssets:
|
||||
properties:
|
||||
dashboards:
|
||||
items:
|
||||
$ref: '#/components/schemas/CloudintegrationtypesServiceDashboard'
|
||||
type: array
|
||||
required:
|
||||
- dashboards
|
||||
type: object
|
||||
CloudintegrationtypesServiceConfig:
|
||||
properties:
|
||||
aws:
|
||||
@@ -1244,6 +1231,15 @@ components:
|
||||
azure:
|
||||
$ref: '#/components/schemas/CloudintegrationtypesAzureServiceConfig'
|
||||
type: object
|
||||
CloudintegrationtypesServiceDashboard:
|
||||
properties:
|
||||
description:
|
||||
type: string
|
||||
integrationDashboard:
|
||||
$ref: '#/components/schemas/CloudintegrationtypesStorableIntegrationDashboard'
|
||||
title:
|
||||
type: string
|
||||
type: object
|
||||
CloudintegrationtypesServiceID:
|
||||
enum:
|
||||
- alb
|
||||
@@ -1278,6 +1274,23 @@ components:
|
||||
- icon
|
||||
- enabled
|
||||
type: object
|
||||
CloudintegrationtypesStorableIntegrationDashboard:
|
||||
properties:
|
||||
createdAt:
|
||||
format: date-time
|
||||
type: string
|
||||
dashboardId:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
provider:
|
||||
type: string
|
||||
slug:
|
||||
type: string
|
||||
updatedAt:
|
||||
format: date-time
|
||||
type: string
|
||||
type: object
|
||||
CloudintegrationtypesSupportedSignals:
|
||||
properties:
|
||||
logs:
|
||||
@@ -1285,13 +1298,6 @@ components:
|
||||
metrics:
|
||||
type: boolean
|
||||
type: object
|
||||
CloudintegrationtypesTelemetryCollectionStrategy:
|
||||
properties:
|
||||
aws:
|
||||
$ref: '#/components/schemas/CloudintegrationtypesAWSTelemetryCollectionStrategy'
|
||||
azure:
|
||||
$ref: '#/components/schemas/CloudintegrationtypesAzureTelemetryCollectionStrategy'
|
||||
type: object
|
||||
CloudintegrationtypesUpdatableAccount:
|
||||
properties:
|
||||
config:
|
||||
@@ -6572,6 +6578,15 @@ components:
|
||||
nullable: true
|
||||
type: array
|
||||
type: object
|
||||
SpantypesOtelSpanRef:
|
||||
properties:
|
||||
refType:
|
||||
type: string
|
||||
spanId:
|
||||
type: string
|
||||
traceId:
|
||||
type: string
|
||||
type: object
|
||||
SpantypesPostableSpanMapper:
|
||||
properties:
|
||||
config:
|
||||
@@ -6835,6 +6850,10 @@ components:
|
||||
type: string
|
||||
parent_span_id:
|
||||
type: string
|
||||
references:
|
||||
items:
|
||||
$ref: '#/components/schemas/SpantypesOtelSpanRef'
|
||||
type: array
|
||||
resource:
|
||||
additionalProperties:
|
||||
type: string
|
||||
@@ -6860,6 +6879,8 @@ components:
|
||||
type: string
|
||||
trace_state:
|
||||
type: string
|
||||
required:
|
||||
- references
|
||||
type: object
|
||||
TagtypesPostableTag:
|
||||
properties:
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
export function getCurrentEnvironment():
|
||||
| 'production'
|
||||
| 'staging'
|
||||
| 'self-host' {
|
||||
const host = document.location.host;
|
||||
|
||||
if (host.endsWith('staging.signoz.cloud')) {
|
||||
return 'staging';
|
||||
}
|
||||
|
||||
if (host.endsWith('signoz.cloud')) {
|
||||
return 'production';
|
||||
}
|
||||
|
||||
return 'self-host';
|
||||
}
|
||||
@@ -43,7 +43,6 @@ import defaultRoutes, {
|
||||
LIST_LICENSES,
|
||||
SUPPORT_ROUTE,
|
||||
} from './routes';
|
||||
import { getCurrentEnvironment } from 'AppRoutes/getCurrentEnvironment';
|
||||
|
||||
function App(): JSX.Element {
|
||||
const themeConfig = useThemeConfig();
|
||||
@@ -349,19 +348,22 @@ function App(): JSX.Element {
|
||||
!isSentryInitialized &&
|
||||
(window.signozBootData?.settings?.sentry.enabled ?? true)
|
||||
) {
|
||||
const sentryEnvironment = getCurrentEnvironment();
|
||||
|
||||
Sentry.init({
|
||||
dsn: process.env.SENTRY_DSN,
|
||||
tunnel: process.env.TUNNEL_URL,
|
||||
environment: sentryEnvironment,
|
||||
environment: 'production',
|
||||
integrations: [
|
||||
Sentry.browserTracingIntegration(),
|
||||
Sentry.replayIntegration({
|
||||
maskAllText: false,
|
||||
blockAllMedia: false,
|
||||
}),
|
||||
],
|
||||
tracesSampleRate: 0, // Ref: https://github.com/SigNoz/platform-pod/issues/2393#issuecomment-4603658055
|
||||
// Performance Monitoring
|
||||
tracesSampleRate: 1.0, // Capture 100% of the transactions
|
||||
// Set 'tracePropagationTargets' to control for which URLs distributed tracing should be enabled
|
||||
tracePropagationTargets: [],
|
||||
// Session Replay
|
||||
replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production.
|
||||
replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur.
|
||||
beforeSend(event) {
|
||||
|
||||
@@ -2457,33 +2457,6 @@ export interface CloudintegrationtypesAccountDTO {
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
export interface DashboardtypesStorableDashboardDataDTO {
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface CloudintegrationtypesDashboardDTO {
|
||||
definition?: DashboardtypesStorableDashboardDataDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
id?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface CloudintegrationtypesAssetsDTO {
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
dashboards?: CloudintegrationtypesDashboardDTO[] | null;
|
||||
}
|
||||
|
||||
export interface CloudintegrationtypesAzureConnectionArtifactDTO {
|
||||
/**
|
||||
* @type string
|
||||
@@ -2866,6 +2839,54 @@ export interface CloudintegrationtypesPostableAgentCheckInDTO {
|
||||
providerAccountId?: string;
|
||||
}
|
||||
|
||||
export interface CloudintegrationtypesStorableIntegrationDashboardDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
createdAt?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
dashboardId?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
id?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
provider?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
slug?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
export interface CloudintegrationtypesServiceDashboardDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
description?: string;
|
||||
integrationDashboard?: CloudintegrationtypesStorableIntegrationDashboardDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface CloudintegrationtypesServiceAssetsDTO {
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
dashboards: CloudintegrationtypesServiceDashboardDTO[];
|
||||
}
|
||||
|
||||
export interface CloudintegrationtypesSupportedSignalsDTO {
|
||||
/**
|
||||
* @type boolean
|
||||
@@ -2877,13 +2898,8 @@ export interface CloudintegrationtypesSupportedSignalsDTO {
|
||||
metrics?: boolean;
|
||||
}
|
||||
|
||||
export interface CloudintegrationtypesTelemetryCollectionStrategyDTO {
|
||||
aws?: CloudintegrationtypesAWSTelemetryCollectionStrategyDTO;
|
||||
azure?: CloudintegrationtypesAzureTelemetryCollectionStrategyDTO;
|
||||
}
|
||||
|
||||
export interface CloudintegrationtypesServiceDTO {
|
||||
assets: CloudintegrationtypesAssetsDTO;
|
||||
assets: CloudintegrationtypesServiceAssetsDTO;
|
||||
cloudIntegrationService: CloudintegrationtypesCloudIntegrationServiceDTO | null;
|
||||
dataCollected: CloudintegrationtypesDataCollectedDTO;
|
||||
/**
|
||||
@@ -2899,7 +2915,6 @@ export interface CloudintegrationtypesServiceDTO {
|
||||
*/
|
||||
overview: string;
|
||||
supportedSignals: CloudintegrationtypesSupportedSignalsDTO;
|
||||
telemetryCollectionStrategy: CloudintegrationtypesTelemetryCollectionStrategyDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
@@ -3705,6 +3720,10 @@ export interface DashboardtypesCustomVariableSpecDTO {
|
||||
customValue: string;
|
||||
}
|
||||
|
||||
export interface DashboardtypesStorableDashboardDataDTO {
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export enum DashboardtypesSourceDTO {
|
||||
user = 'user',
|
||||
system = 'system',
|
||||
@@ -7768,6 +7787,21 @@ export interface SpantypesGettableTraceAggregationsDTO {
|
||||
aggregations: SpantypesSpanAggregationResultDTO[];
|
||||
}
|
||||
|
||||
export interface SpantypesOtelSpanRefDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
refType?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
spanId?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
traceId?: string;
|
||||
}
|
||||
|
||||
export type SpantypesWaterfallSpanDTOAttributesAnyOf = {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
@@ -7862,6 +7896,10 @@ export interface SpantypesWaterfallSpanDTO {
|
||||
* @type string
|
||||
*/
|
||||
parent_span_id?: string;
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
references: SpantypesOtelSpanRefDTO[];
|
||||
/**
|
||||
* @type object,null
|
||||
*/
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Dot } from '@signozhq/icons';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { TooltipSimple } from '@signozhq/ui/tooltip';
|
||||
import Noz from 'components/Noz/Noz';
|
||||
import { NOZ_TOOLTIP_TITLE } from 'components/Noz/Noz.constants';
|
||||
import { Popover } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { AIAssistantEvents } from 'container/AIAssistant/events';
|
||||
@@ -109,7 +110,7 @@ function HeaderRightSection({
|
||||
</span>
|
||||
) : null}
|
||||
|
||||
<TooltipSimple title="Noz">
|
||||
<TooltipSimple title={NOZ_TOOLTIP_TITLE}>
|
||||
<Button
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
|
||||
2
frontend/src/components/Noz/Noz.constants.ts
Normal file
2
frontend/src/components/Noz/Noz.constants.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
/** Shared hover copy for every Noz entry point (header, floating trigger, sidebar). */
|
||||
export const NOZ_TOOLTIP_TITLE = 'Noz, your AI teammate';
|
||||
@@ -5,6 +5,7 @@ import { TooltipSimple } from '@signozhq/ui/tooltip';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import ROUTES from 'constants/routes';
|
||||
import Noz from 'components/Noz/Noz';
|
||||
import { NOZ_TOOLTIP_TITLE } from 'components/Noz/Noz.constants';
|
||||
|
||||
import { AIAssistantEvents, AIAssistantOpenSource } from '../events';
|
||||
import { normalizePage } from '../hooks/useAIAssistantAnalyticsContext';
|
||||
@@ -42,16 +43,15 @@ export default function AIAssistantTrigger(): JSX.Element | null {
|
||||
}
|
||||
|
||||
return (
|
||||
<TooltipSimple title="Noz">
|
||||
<TooltipSimple title={NOZ_TOOLTIP_TITLE}>
|
||||
<Button
|
||||
variant="solid"
|
||||
color="primary"
|
||||
className={`${styles.trigger} noz-wave`}
|
||||
onClick={handleOpen}
|
||||
aria-label="Open Noz"
|
||||
>
|
||||
<Noz size={24} />
|
||||
</Button>
|
||||
prefix={<Noz size={24} />}
|
||||
/>
|
||||
</TooltipSimple>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -55,7 +55,6 @@ const buildServiceDetailsResponse = (
|
||||
},
|
||||
},
|
||||
},
|
||||
telemetryCollectionStrategy: { aws: {} },
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -53,6 +53,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.aws-service-dashboard-item-disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.aws-service-dashboard-item-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
/* eslint-disable sonarjs/cognitive-complexity */
|
||||
import type { KeyboardEvent, MouseEvent } from 'react';
|
||||
import { TooltipSimple } from '@signozhq/ui/tooltip';
|
||||
import {
|
||||
CloudintegrationtypesDashboardDTO,
|
||||
CloudintegrationtypesServiceDashboardDTO,
|
||||
CloudintegrationtypesServiceDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||
@@ -8,6 +10,9 @@ import { withBasePath } from 'utils/basePath';
|
||||
|
||||
import './ServiceDashboards.styles.scss';
|
||||
|
||||
const DISABLED_TOOLTIP =
|
||||
'Enable metrics collection for this service to view this dashboard.';
|
||||
|
||||
function ServiceDashboards({
|
||||
service,
|
||||
isInteractive = true,
|
||||
@@ -25,68 +30,85 @@ function ServiceDashboards({
|
||||
<div className="aws-service-dashboards">
|
||||
<div className="aws-service-dashboards-title">Dashboards</div>
|
||||
<div className="aws-service-dashboards-items">
|
||||
{dashboards.map((dashboard: CloudintegrationtypesDashboardDTO) => {
|
||||
if (!dashboard.id) {
|
||||
return null;
|
||||
}
|
||||
{dashboards.map(
|
||||
(dashboard: CloudintegrationtypesServiceDashboardDTO, index: number) => {
|
||||
const dashboardId = dashboard.integrationDashboard?.dashboardId;
|
||||
const isEnabled = Boolean(dashboardId) && isInteractive;
|
||||
const itemKey = dashboardId || `${dashboard.title}-${index}`;
|
||||
const dashboardUrl = dashboardId ? `/dashboard/${dashboardId}` : '';
|
||||
|
||||
const dashboardUrl = `/dashboard/${dashboard.id}`;
|
||||
const handleClick = (event: MouseEvent<HTMLDivElement>): void => {
|
||||
if (!isEnabled) {
|
||||
return;
|
||||
}
|
||||
if (event.metaKey || event.ctrlKey) {
|
||||
window.open(
|
||||
withBasePath(dashboardUrl),
|
||||
'_blank',
|
||||
'noopener,noreferrer',
|
||||
);
|
||||
return;
|
||||
}
|
||||
safeNavigate(dashboardUrl);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
key={dashboard.id}
|
||||
className={`aws-service-dashboard-item ${
|
||||
isInteractive ? 'aws-service-dashboard-item-clickable' : ''
|
||||
}`}
|
||||
role={isInteractive ? 'button' : undefined}
|
||||
tabIndex={isInteractive ? 0 : -1}
|
||||
onClick={(event): void => {
|
||||
if (!isInteractive) {
|
||||
return;
|
||||
}
|
||||
if (event.metaKey || event.ctrlKey) {
|
||||
window.open(
|
||||
withBasePath(dashboardUrl),
|
||||
'_blank',
|
||||
'noopener,noreferrer',
|
||||
);
|
||||
return;
|
||||
}
|
||||
const handleAuxClick = (event: MouseEvent<HTMLDivElement>): void => {
|
||||
if (!isEnabled) {
|
||||
return;
|
||||
}
|
||||
if (event.button === 1) {
|
||||
window.open(
|
||||
withBasePath(dashboardUrl),
|
||||
'_blank',
|
||||
'noopener,noreferrer',
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>): void => {
|
||||
if (!isEnabled) {
|
||||
return;
|
||||
}
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault();
|
||||
safeNavigate(dashboardUrl);
|
||||
}}
|
||||
onAuxClick={(event): void => {
|
||||
if (!isInteractive) {
|
||||
return;
|
||||
}
|
||||
if (event.button === 1) {
|
||||
window.open(
|
||||
withBasePath(dashboardUrl),
|
||||
'_blank',
|
||||
'noopener,noreferrer',
|
||||
);
|
||||
}
|
||||
}}
|
||||
onKeyDown={(event): void => {
|
||||
if (!isInteractive) {
|
||||
return;
|
||||
}
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault();
|
||||
safeNavigate(dashboardUrl);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="aws-service-dashboard-item-content">
|
||||
<div className="aws-service-dashboard-item-title">
|
||||
{dashboard.title}
|
||||
</div>
|
||||
<div className="aws-service-dashboard-item-description">
|
||||
{dashboard.description}
|
||||
}
|
||||
};
|
||||
|
||||
const card = (
|
||||
<div
|
||||
className={`aws-service-dashboard-item ${
|
||||
isEnabled ? 'aws-service-dashboard-item-clickable' : ''
|
||||
} ${!dashboardId ? 'aws-service-dashboard-item-disabled' : ''}`}
|
||||
role={isEnabled ? 'button' : undefined}
|
||||
tabIndex={isEnabled ? 0 : -1}
|
||||
aria-disabled={!dashboardId}
|
||||
onClick={handleClick}
|
||||
onAuxClick={handleAuxClick}
|
||||
onKeyDown={handleKeyDown}
|
||||
>
|
||||
<div className="aws-service-dashboard-item-content">
|
||||
<div className="aws-service-dashboard-item-title">
|
||||
{dashboard.title}
|
||||
</div>
|
||||
<div className="aws-service-dashboard-item-description">
|
||||
{dashboard.description}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
);
|
||||
|
||||
if (!dashboardId) {
|
||||
return (
|
||||
<TooltipSimple key={itemKey} title={DISABLED_TOOLTIP} arrow>
|
||||
{card}
|
||||
</TooltipSimple>
|
||||
);
|
||||
}
|
||||
|
||||
return <div key={itemKey}>{card}</div>;
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -5,7 +5,6 @@ import { Pin, PinOff } from '@signozhq/icons';
|
||||
|
||||
import { SidebarItem } from '../sideNav.types';
|
||||
|
||||
import './NavItem.styles.scss';
|
||||
import './NavItem.styles.scss';
|
||||
|
||||
export default function NavItem({
|
||||
@@ -27,7 +26,7 @@ export default function NavItem({
|
||||
showIcon?: boolean;
|
||||
dataTestId?: string;
|
||||
}): JSX.Element {
|
||||
const { label, icon, isBeta, isNew, isEarlyAccess } = item;
|
||||
const { label, icon, isBeta, isNew, isEarlyAccess, tooltip } = item;
|
||||
|
||||
const handleTogglePinClick = (
|
||||
event: React.MouseEvent<SVGSVGElement, MouseEvent>,
|
||||
@@ -36,7 +35,7 @@ export default function NavItem({
|
||||
onTogglePin?.(item);
|
||||
};
|
||||
|
||||
return (
|
||||
const navItem = (
|
||||
<div
|
||||
className={cx(
|
||||
'nav-item',
|
||||
@@ -107,6 +106,15 @@ export default function NavItem({
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
// Only non-pinnable items set `tooltip`; it would nest with the pin tooltip.
|
||||
return tooltip ? (
|
||||
<Tooltip title={tooltip} placement="right">
|
||||
{navItem}
|
||||
</Tooltip>
|
||||
) : (
|
||||
navItem
|
||||
);
|
||||
}
|
||||
|
||||
NavItem.defaultProps = {
|
||||
|
||||
@@ -45,6 +45,7 @@ import {
|
||||
} from './sideNav.types';
|
||||
import { Style } from '@signozhq/design-tokens';
|
||||
import Noz from 'components/Noz/Noz';
|
||||
import { NOZ_TOOLTIP_TITLE } from 'components/Noz/Noz.constants';
|
||||
|
||||
export const getStartedMenuItem = {
|
||||
key: ROUTES.GET_STARTED,
|
||||
@@ -97,6 +98,7 @@ export const aiAssistantMenuItem = {
|
||||
icon: <Noz size={16} />,
|
||||
itemKey: 'ai-assistant',
|
||||
isEarlyAccess: true,
|
||||
tooltip: NOZ_TOOLTIP_TITLE,
|
||||
};
|
||||
|
||||
export const shortcutMenuItem = {
|
||||
|
||||
@@ -15,6 +15,8 @@ export interface SidebarItem {
|
||||
isBeta?: boolean;
|
||||
isNew?: boolean;
|
||||
isEarlyAccess?: boolean;
|
||||
/** Hover copy for the whole item row (e.g. Noz's early-access tagline). */
|
||||
tooltip?: ReactNode;
|
||||
isPinned?: boolean;
|
||||
children?: SidebarItem[];
|
||||
isExternal?: boolean;
|
||||
|
||||
@@ -22,6 +22,7 @@ import styles from './AnalyticsPanel.module.scss';
|
||||
interface AnalyticsPanelProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onTabChange: (tab: string) => void;
|
||||
}
|
||||
|
||||
const PANEL_WIDTH = 350;
|
||||
@@ -32,6 +33,7 @@ const PANEL_MARGIN_BOTTOM = 50;
|
||||
function AnalyticsPanel({
|
||||
isOpen,
|
||||
onClose,
|
||||
onTabChange,
|
||||
}: AnalyticsPanelProps): JSX.Element | null {
|
||||
const aggregations = useTraceStore((s) => s.aggregations);
|
||||
const colorByFieldName = useTraceStore((s) => s.colorByField.name);
|
||||
@@ -118,7 +120,7 @@ function AnalyticsPanel({
|
||||
/>
|
||||
|
||||
<div className={styles.body}>
|
||||
<TabsRoot defaultValue="exec-time">
|
||||
<TabsRoot defaultValue="exec-time" onValueChange={onTabChange}>
|
||||
<TabsList variant="secondary">
|
||||
<TabsTrigger value="exec-time" variant="secondary">
|
||||
% exec time
|
||||
|
||||
@@ -31,7 +31,12 @@ import Events from 'container/SpanDetailsDrawer/Events/Events';
|
||||
import SpanLogs from 'container/SpanDetailsDrawer/SpanLogs/SpanLogs';
|
||||
import { useSpanContextLogs } from 'container/SpanDetailsDrawer/SpanLogs/useSpanContextLogs';
|
||||
import dayjs from 'dayjs';
|
||||
import {
|
||||
TraceDetailEventKeys,
|
||||
TraceDetailEvents,
|
||||
} from 'pages/TraceDetailsV3/events';
|
||||
import { useMigratePinnedAttributes } from 'pages/TraceDetailsV3/hooks/useMigratePinnedAttributes';
|
||||
import { useTraceDetailLogEvent } from 'pages/TraceDetailsV3/hooks/useTraceDetailLogEvent';
|
||||
import {
|
||||
getSpanAttribute,
|
||||
getSpanDisplayData,
|
||||
@@ -86,6 +91,16 @@ function SpanDetailsContent({
|
||||
}): JSX.Element {
|
||||
const FIVE_MINUTES_IN_MS = 5 * 60 * 1000;
|
||||
const spanAttributeActions = useSpanAttributeActions();
|
||||
const logTraceEvent = useTraceDetailLogEvent('v3', selectedSpan.trace_id);
|
||||
const handleTabChange = useCallback(
|
||||
(tab: string): void => {
|
||||
logTraceEvent(TraceDetailEvents.SpanPanelTabChanged, {
|
||||
[TraceDetailEventKeys.Tab]: tab,
|
||||
[TraceDetailEventKeys.SpanId]: selectedSpan.span_id,
|
||||
});
|
||||
},
|
||||
[logTraceEvent, selectedSpan.span_id],
|
||||
);
|
||||
const percentile = useSpanPercentile(selectedSpan);
|
||||
const linkedSpans = useLinkedSpans((selectedSpan as any).references);
|
||||
|
||||
@@ -376,7 +391,7 @@ function SpanDetailsContent({
|
||||
|
||||
<div className={styles.tabsSection}>
|
||||
{/* Step 9: ContentTabs */}
|
||||
<TabsRoot defaultValue="overview">
|
||||
<TabsRoot defaultValue="overview" onValueChange={handleTabChange}>
|
||||
<TabsList variant="secondary">
|
||||
<TabsTrigger value="overview" variant="secondary">
|
||||
<Bookmark size={14} /> Overview
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import {
|
||||
@@ -29,6 +29,8 @@ import KeyValueLabel from 'periscope/components/KeyValueLabel';
|
||||
import { TraceDetailV2URLProps } from 'types/api/trace/getTraceV2';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import { TraceDetailEventKeys, TraceDetailEvents } from '../events';
|
||||
import { useTraceDetailLogEvent } from '../hooks/useTraceDetailLogEvent';
|
||||
import { useTraceStore } from '../stores/traceStore';
|
||||
import AnalyticsPanel from '../SpanDetailsPanel/AnalyticsPanel/AnalyticsPanel';
|
||||
import Filters from '../TraceWaterfall/TraceWaterfallStates/Success/Filters/Filters';
|
||||
@@ -90,11 +92,35 @@ function TraceDetailsHeader({
|
||||
const previewFields = useTraceStore((s) => s.previewFields);
|
||||
const setPreviewFields = useTraceStore((s) => s.setPreviewFields);
|
||||
|
||||
const logTraceEvent = useTraceDetailLogEvent('v3', traceID || '');
|
||||
const pageLoadedAtRef = useRef(Date.now());
|
||||
|
||||
const handleSwitchToOldView = useCallback((): void => {
|
||||
logTraceEvent(TraceDetailEvents.ViewSwitched, {
|
||||
[TraceDetailEventKeys.From]: 'v3',
|
||||
[TraceDetailEventKeys.To]: 'v2',
|
||||
[TraceDetailEventKeys.DwellMs]: Date.now() - pageLoadedAtRef.current,
|
||||
});
|
||||
setLocalStorageKey(LOCALSTORAGE.TRACE_DETAILS_PREFER_OLD_VIEW, 'true');
|
||||
const oldUrl = `/trace-old/${traceID}${window.location.search}`;
|
||||
history.replace(oldUrl);
|
||||
}, [traceID]);
|
||||
}, [traceID, logTraceEvent]);
|
||||
|
||||
const handleToggleAnalytics = useCallback((): void => {
|
||||
logTraceEvent(TraceDetailEvents.AnalyticsPanelToggled, {
|
||||
[TraceDetailEventKeys.Open]: !isAnalyticsOpen,
|
||||
});
|
||||
setIsAnalyticsOpen((prev) => !prev);
|
||||
}, [logTraceEvent, isAnalyticsOpen]);
|
||||
|
||||
const handleAnalyticsTabChange = useCallback(
|
||||
(tab: string): void => {
|
||||
logTraceEvent(TraceDetailEvents.AnalyticsTabChanged, {
|
||||
[TraceDetailEventKeys.Tab]: tab,
|
||||
});
|
||||
},
|
||||
[logTraceEvent],
|
||||
);
|
||||
|
||||
const handlePreviousBtnClick = useCallback((): void => {
|
||||
if (hasInAppHistory()) {
|
||||
@@ -167,7 +193,7 @@ function TraceDetailsHeader({
|
||||
size="icon"
|
||||
color="secondary"
|
||||
aria-label="Analytics"
|
||||
onClick={(): void => setIsAnalyticsOpen((prev) => !prev)}
|
||||
onClick={handleToggleAnalytics}
|
||||
>
|
||||
<ChartPie size={14} />
|
||||
</Button>
|
||||
@@ -245,6 +271,7 @@ function TraceDetailsHeader({
|
||||
<AnalyticsPanel
|
||||
isOpen={isAnalyticsOpen}
|
||||
onClose={(): void => setIsAnalyticsOpen(false)}
|
||||
onTabChange={handleAnalyticsTabChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
38
frontend/src/pages/TraceDetailsV3/events.ts
Normal file
38
frontend/src/pages/TraceDetailsV3/events.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
export enum TraceDetailEvents {
|
||||
DataLoaded = 'Trace Detail: Data loaded',
|
||||
ViewSwitched = 'Trace Detail: View switched',
|
||||
FlameGraphToggled = 'Trace Detail: Flame graph toggled',
|
||||
WaterfallToggled = 'Trace Detail: Waterfall toggled',
|
||||
AnalyticsPanelToggled = 'Trace Detail: Analytics panel toggled',
|
||||
AnalyticsTabChanged = 'Trace Detail: Analytics tab changed',
|
||||
SpanPanelTabChanged = 'Trace Detail: Span panel tab changed',
|
||||
}
|
||||
|
||||
export enum TraceDetailEventKeys {
|
||||
// Injected on every event by useTraceDetailLogEvent
|
||||
View = 'view',
|
||||
TraceId = 'traceId',
|
||||
// Data loaded — trace shape
|
||||
TotalSpansCount = 'totalSpansCount',
|
||||
NumServices = 'numServices',
|
||||
TraceDurationMs = 'traceDurationMs',
|
||||
HadErrors = 'hadErrors',
|
||||
FlamegraphSampled = 'flamegraphSampled',
|
||||
// Data loaded — persisted settings
|
||||
SpanPanelVariant = 'spanPanelVariant',
|
||||
ColorByField = 'colorByField',
|
||||
PreviewFieldsCount = 'previewFieldsCount',
|
||||
EntryPreferOldView = 'entryPreferOldView',
|
||||
// View switched
|
||||
From = 'from',
|
||||
To = 'to',
|
||||
DwellMs = 'dwellMs',
|
||||
// Toggles / tabs
|
||||
Expanded = 'expanded',
|
||||
Open = 'open',
|
||||
Tab = 'tab',
|
||||
// Span panel tab changed
|
||||
SpanId = 'spanId',
|
||||
}
|
||||
|
||||
export type TraceDetailView = 'v2' | 'v3';
|
||||
@@ -0,0 +1,88 @@
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
|
||||
import { TraceDetailEvents } from '../../events';
|
||||
import { useTraceDetailLogEvent } from '../useTraceDetailLogEvent';
|
||||
|
||||
const logEventMock = jest.fn();
|
||||
|
||||
jest.mock('api/common/logEvent', () => ({
|
||||
__esModule: true,
|
||||
default: (...args: unknown[]): void => logEventMock(...args),
|
||||
}));
|
||||
|
||||
describe('useTraceDetailLogEvent', () => {
|
||||
beforeEach(() => {
|
||||
logEventMock.mockClear();
|
||||
});
|
||||
|
||||
it('injects view and traceId on every event', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTraceDetailLogEvent('v3', 'trace-123'),
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current(TraceDetailEvents.DataLoaded, { totalSpansCount: 42 });
|
||||
});
|
||||
|
||||
expect(logEventMock).toHaveBeenCalledTimes(1);
|
||||
expect(logEventMock).toHaveBeenCalledWith(TraceDetailEvents.DataLoaded, {
|
||||
view: 'v3',
|
||||
traceId: 'trace-123',
|
||||
totalSpansCount: 42,
|
||||
});
|
||||
});
|
||||
|
||||
it('injects view and traceId even when no attributes are passed', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTraceDetailLogEvent('v2', 'trace-456'),
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current(TraceDetailEvents.ViewSwitched);
|
||||
});
|
||||
|
||||
expect(logEventMock).toHaveBeenCalledWith(TraceDetailEvents.ViewSwitched, {
|
||||
view: 'v2',
|
||||
traceId: 'trace-456',
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps a stable callback identity and emits the latest traceId', () => {
|
||||
const { result, rerender } = renderHook(
|
||||
({ traceId }) => useTraceDetailLogEvent('v3', traceId),
|
||||
{ initialProps: { traceId: 'trace-1' } },
|
||||
);
|
||||
|
||||
const firstIdentity = result.current;
|
||||
rerender({ traceId: 'trace-2' });
|
||||
|
||||
expect(result.current).toBe(firstIdentity);
|
||||
|
||||
act(() => {
|
||||
result.current(TraceDetailEvents.SpanPanelTabChanged, { spanId: 's1' });
|
||||
});
|
||||
expect(logEventMock).toHaveBeenCalledWith(
|
||||
TraceDetailEvents.SpanPanelTabChanged,
|
||||
{
|
||||
view: 'v3',
|
||||
traceId: 'trace-2',
|
||||
spanId: 's1',
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('never throws if logEvent throws (analytics must not break the UI)', () => {
|
||||
logEventMock.mockImplementationOnce(() => {
|
||||
throw new Error('network down');
|
||||
});
|
||||
const { result } = renderHook(() =>
|
||||
useTraceDetailLogEvent('v3', 'trace-123'),
|
||||
);
|
||||
|
||||
expect(() => {
|
||||
act(() => {
|
||||
result.current(TraceDetailEvents.DataLoaded);
|
||||
});
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
import { useCallback, useRef } from 'react';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
|
||||
import {
|
||||
TraceDetailEventKeys,
|
||||
TraceDetailEvents,
|
||||
TraceDetailView,
|
||||
} from '../events';
|
||||
|
||||
export type TraceDetailLogEvent = (
|
||||
event: TraceDetailEvents,
|
||||
attributes?: Record<string, unknown>,
|
||||
) => void;
|
||||
|
||||
export function useTraceDetailLogEvent(
|
||||
view: TraceDetailView,
|
||||
traceId: string,
|
||||
): TraceDetailLogEvent {
|
||||
const contextRef = useRef({ view, traceId });
|
||||
contextRef.current = { view, traceId };
|
||||
|
||||
return useCallback(
|
||||
(
|
||||
event: TraceDetailEvents,
|
||||
attributes: Record<string, unknown> = {},
|
||||
): void => {
|
||||
try {
|
||||
void logEvent(event, {
|
||||
[TraceDetailEventKeys.View]: contextRef.current.view,
|
||||
[TraceDetailEventKeys.TraceId]: contextRef.current.traceId,
|
||||
...attributes,
|
||||
});
|
||||
} catch {
|
||||
// No-op. Logging must never throw into the UI.
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
}
|
||||
@@ -20,7 +20,10 @@ import {
|
||||
} from 'types/api/trace/getTraceV3';
|
||||
|
||||
import { COLOR_BY_FIELDS } from './constants';
|
||||
import { TraceDetailEventKeys, TraceDetailEvents } from './events';
|
||||
import { useTraceDetailLogEvent } from './hooks/useTraceDetailLogEvent';
|
||||
import TraceStoreSync from './stores/TraceStoreSync';
|
||||
import { useTraceStore } from './stores/traceStore';
|
||||
import { AGGREGATIONS } from './utils/aggregations';
|
||||
import { SpanDetailVariant } from './SpanDetailsPanel/constants';
|
||||
import SpanDetailsPanel from './SpanDetailsPanel/SpanDetailsPanel';
|
||||
@@ -56,6 +59,14 @@ function TraceDetailsV3(): JSX.Element {
|
||||
const selectedSpanId = urlQuery.get('spanId') || undefined;
|
||||
const { safeNavigate } = useSafeNavigate();
|
||||
|
||||
const logTraceEvent = useTraceDetailLogEvent('v3', traceId || '');
|
||||
// Tracks which traceId the load event already fired for, so navigating
|
||||
// between traces (the route component stays mounted) re-fires it once each.
|
||||
const dataLoadedFiredForRef = useRef('');
|
||||
const colorByField = useTraceStore((s) => s.colorByField);
|
||||
const previewFieldsCount = useTraceStore((s) => s.previewFields.length);
|
||||
const userPrefsReady = useTraceStore((s) => s.userPreferences !== null);
|
||||
|
||||
const handleSpanDetailsClose = useCallback((): void => {
|
||||
urlQuery.delete('spanId');
|
||||
safeNavigate({ search: urlQuery.toString() });
|
||||
@@ -154,6 +165,46 @@ function TraceDetailsV3(): JSX.Element {
|
||||
allSpansRef.current = allSpans;
|
||||
}, [allSpans]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!traceId ||
|
||||
dataLoadedFiredForRef.current === traceId ||
|
||||
!userPrefsReady
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const payload = traceData?.payload;
|
||||
if (!payload?.spans?.length) {
|
||||
return;
|
||||
}
|
||||
dataLoadedFiredForRef.current = traceId;
|
||||
const numServices = new Set(payload.spans.map((s) => s['service.name'])).size;
|
||||
logTraceEvent(TraceDetailEvents.DataLoaded, {
|
||||
[TraceDetailEventKeys.TotalSpansCount]: totalSpansCount,
|
||||
[TraceDetailEventKeys.NumServices]: numServices,
|
||||
[TraceDetailEventKeys.TraceDurationMs]:
|
||||
payload.endTimestampMillis - payload.startTimestampMillis,
|
||||
[TraceDetailEventKeys.HadErrors]: (payload.totalErrorSpansCount || 0) > 0,
|
||||
[TraceDetailEventKeys.FlamegraphSampled]:
|
||||
totalSpansCount > FLAMEGRAPH_SPAN_LIMIT,
|
||||
[TraceDetailEventKeys.SpanPanelVariant]:
|
||||
getLocalStorageKey(LOCALSTORAGE.TRACE_DETAILS_SPAN_DETAILS_POSITION) ||
|
||||
SpanDetailVariant.DOCKED_RIGHT,
|
||||
[TraceDetailEventKeys.ColorByField]: colorByField.name,
|
||||
[TraceDetailEventKeys.PreviewFieldsCount]: previewFieldsCount,
|
||||
[TraceDetailEventKeys.EntryPreferOldView]:
|
||||
getLocalStorageKey(LOCALSTORAGE.TRACE_DETAILS_PREFER_OLD_VIEW) === 'true',
|
||||
});
|
||||
}, [
|
||||
traceId,
|
||||
userPrefsReady,
|
||||
traceData,
|
||||
totalSpansCount,
|
||||
colorByField,
|
||||
previewFieldsCount,
|
||||
logTraceEvent,
|
||||
]);
|
||||
|
||||
// Frontend mode: expand all parents by default when full data arrives
|
||||
useEffect(() => {
|
||||
if (isFullDataLoaded && allSpans.length > 0) {
|
||||
@@ -233,6 +284,12 @@ function TraceDetailsV3(): JSX.Element {
|
||||
const [activeKeys, setActiveKeys] = useState<string[]>(['flame', 'waterfall']);
|
||||
|
||||
const handleCollapseChange = (key: string): void => {
|
||||
logTraceEvent(
|
||||
key === 'flame'
|
||||
? TraceDetailEvents.FlameGraphToggled
|
||||
: TraceDetailEvents.WaterfallToggled,
|
||||
{ [TraceDetailEventKeys.Expanded]: !activeKeys.includes(key) },
|
||||
);
|
||||
setActiveKeys((prev) =>
|
||||
prev.includes(key) ? prev.filter((k) => k !== key) : [...prev, key],
|
||||
);
|
||||
|
||||
@@ -4,6 +4,7 @@ import { ChevronDown, Copy } from '@signozhq/icons';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { DropdownMenuSimple as Dropdown } from '@signozhq/ui/dropdown-menu';
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { JsonView } from 'periscope/components/JsonView';
|
||||
import { PrettyView } from 'periscope/components/PrettyView';
|
||||
import { PrettyViewProps } from 'periscope/components/PrettyView';
|
||||
@@ -12,6 +13,8 @@ import './DataViewer.styles.scss';
|
||||
|
||||
type ViewMode = 'pretty' | 'json';
|
||||
|
||||
const VIEW_MODE_CHANGED_EVENT = 'Data Viewer: View mode changed';
|
||||
|
||||
const VIEW_MODE_OPTIONS: { label: string; value: ViewMode }[] = [
|
||||
{ label: 'Pretty', value: 'pretty' },
|
||||
{ label: 'JSON', value: 'json' },
|
||||
@@ -34,6 +37,20 @@ function DataViewer({
|
||||
|
||||
const jsonString = useMemo(() => JSON.stringify(data, null, 2), [data]);
|
||||
|
||||
const handleViewModeChange = (value: string): void => {
|
||||
const next = value as ViewMode;
|
||||
setViewMode(next);
|
||||
try {
|
||||
logEvent(VIEW_MODE_CHANGED_EVENT, {
|
||||
viewMode: next,
|
||||
path: window.location.pathname,
|
||||
drawerKey,
|
||||
});
|
||||
} catch {
|
||||
// No op
|
||||
}
|
||||
};
|
||||
|
||||
const handleCopy = (): void => {
|
||||
const text = JSON.stringify(data, null, 2);
|
||||
setCopy(text);
|
||||
@@ -56,7 +73,7 @@ function DataViewer({
|
||||
{
|
||||
type: 'radio-group',
|
||||
value: viewMode,
|
||||
onChange: (value): void => setViewMode(value as ViewMode),
|
||||
onChange: handleViewModeChange,
|
||||
children: VIEW_MODE_OPTIONS.map((opt) => ({
|
||||
type: 'radio',
|
||||
key: opt.value,
|
||||
|
||||
@@ -74,7 +74,7 @@ func (s *traceStore) GetTraceSpans(ctx context.Context, traceID string, summary
|
||||
events, status_message, status_code_string, kind_string, parent_span_id,
|
||||
flags, is_remote, trace_state, status_code,
|
||||
db_name, db_operation, http_method, http_url, http_host,
|
||||
external_http_method, external_http_url, response_status_code
|
||||
external_http_method, external_http_url, response_status_code, links as references
|
||||
FROM %s.%s
|
||||
WHERE trace_id=? AND ts_bucket_start>=? AND ts_bucket_start<=?
|
||||
ORDER BY timestamp ASC, name ASC`,
|
||||
@@ -130,7 +130,7 @@ func (s *traceStore) GetTraceSpansByIDs(ctx context.Context, traceID string, sta
|
||||
"events", "status_message", "status_code_string", "kind_string", "parent_span_id",
|
||||
"flags", "is_remote", "trace_state", "status_code",
|
||||
"db_name", "db_operation", "http_method", "http_url", "http_host",
|
||||
"external_http_method", "external_http_url", "response_status_code",
|
||||
"external_http_method", "external_http_url", "response_status_code", "links as references",
|
||||
)
|
||||
sb.From(fmt.Sprintf("%s.%s", spantypes.TraceDB, spantypes.TraceTable))
|
||||
ids := make([]any, len(spanIDs))
|
||||
|
||||
@@ -54,6 +54,12 @@ type Event struct {
|
||||
IsError bool `json:"isError,omitempty"`
|
||||
}
|
||||
|
||||
type OtelSpanRef struct {
|
||||
TraceId string `json:"traceId,omitempty"`
|
||||
SpanId string `json:"spanId,omitempty"`
|
||||
RefType string `json:"refType,omitempty"`
|
||||
}
|
||||
|
||||
// WaterfallSpan represents the span in waterfall response,
|
||||
// this uses snake_case keys for response as a special case since these
|
||||
// keys can be directly used to query spans and client need to know the actual fields.
|
||||
@@ -74,6 +80,7 @@ type WaterfallSpan struct {
|
||||
TimeUnix uint64 `json:"time_unix"`
|
||||
TraceID string `json:"trace_id"`
|
||||
TraceState string `json:"trace_state"`
|
||||
References []OtelSpanRef `json:"references" required:"true" nullable:"false"`
|
||||
|
||||
// Calculated fields https://signoz.io/docs/traces-management/guides/derived-fields-spans
|
||||
DBName string `json:"db_name,omitempty"`
|
||||
@@ -128,6 +135,7 @@ type StorableSpan struct {
|
||||
ExternalHTTPMethod string `ch:"external_http_method"`
|
||||
ExternalHTTPURL string `ch:"external_http_url"`
|
||||
ResponseStatusCode string `ch:"response_status_code"`
|
||||
References string `ch:"references"`
|
||||
}
|
||||
|
||||
// MinimalSpan with only the fields needed to build the parent-child tree.
|
||||
@@ -285,6 +293,14 @@ func (item *StorableSpan) UnmarshalledEvents() []Event {
|
||||
return events
|
||||
}
|
||||
|
||||
func (item *StorableSpan) UnmarshalledRefs() []OtelSpanRef {
|
||||
refs := []OtelSpanRef{}
|
||||
if err := json.Unmarshal([]byte(item.References), &refs); err != nil {
|
||||
return nil // skip malformed values
|
||||
}
|
||||
return refs
|
||||
}
|
||||
|
||||
func (item *StorableSpan) ToWaterfallSpan(traceID string) *WaterfallSpan {
|
||||
resources := make(map[string]string)
|
||||
maps.Copy(resources, item.ResourcesString)
|
||||
@@ -318,6 +334,7 @@ func (item *StorableSpan) ToWaterfallSpan(traceID string) *WaterfallSpan {
|
||||
Children: make([]*WaterfallSpan, 0),
|
||||
TimeUnix: uint64(item.StartTime.UnixNano()),
|
||||
ServiceName: item.ServiceName,
|
||||
References: item.UnmarshalledRefs(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,16 +54,16 @@ func newConfig() factory.Config {
|
||||
Directory: "/etc/signoz/web",
|
||||
Settings: SettingsConfig{
|
||||
Posthog: PosthogConfig{
|
||||
Enabled: true,
|
||||
Enabled: false,
|
||||
},
|
||||
Appcues: AppcuesConfig{
|
||||
Enabled: true,
|
||||
Enabled: false,
|
||||
},
|
||||
Sentry: SentryConfig{
|
||||
Enabled: true,
|
||||
Enabled: false,
|
||||
},
|
||||
Pylon: PylonConfig{
|
||||
Enabled: true,
|
||||
Enabled: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user