Compare commits

...

5 Commits

Author SHA1 Message Date
Vinicius Lourenço
72dd544288 Revert "refactor: replace antd Tabs with @signozhq/ui Tabs (#11392)" (#11507)
Some checks are pending
build-staging / prepare (push) Waiting to run
build-staging / js-build (push) Blocked by required conditions
build-staging / go-build (push) Blocked by required conditions
build-staging / staging (push) Blocked by required conditions
Release Drafter / update_release_draft (push) Waiting to run
This reverts commit da1b09c479.
2026-05-29 19:30:42 +00:00
Pandey
53b2b2f017 fix(telemetrystore): fix clickhouse connection-pool slot leak (acquire conn timeout) (#11506)
* fix(telemetrystore): upgrade clickhouse-go to v2.44.0 to fix connection-pool slot leak

clickhouse-go v2.43.0 introduced connection-pool slot leaks triggered by context
cancellation: acquire() failed to release the pool slot when idle.Get returned a
cancellation error (ClickHouse/clickhouse-go#1759), and batch.Close() never released
the connection when closeQuery() failed on a cancelled context
(ClickHouse/clickhouse-go#1795). Both leak slots until the pool is exhausted and every
query fails with 'acquire conn timeout'. Both are fixed in v2.44.0.

v2.44.0 adds HasData() to the driver.Rows interface, which the test mock did not
implement. Swap the mock to the SigNoz fork github.com/SigNoz/clickhouse-go-mock
v0.14.0, which implements HasData() and tracks v2.44.0.

* feat(telemetrystore): emit clickhouse connection-pool metrics

Register OTel observable gauges that report the clickhouse connection-pool stats
from driver.Stats() on each collection cycle:
signoz.telemetrystore.connection.{open,idle,max_open,max_idle}. Plotting open against
max_open makes pool saturation (and leaks like the one fixed in the previous commit)
directly observable in Prometheus.
2026-05-30 01:06:16 +05:30
Vikrant Gupta
b568f3e5cb chore(ci): remove unused frontend build variables (#11504) 2026-05-29 17:13:17 +00:00
Gaurav Tewari
516e490567 fix: different color in badge (#11492)
Co-authored-by: Gaurav Tewari <tewarig@users.noreply.github.com>
2026-05-29 13:59:19 +00:00
Vikrant Gupta
407d969cd3 feat(user): add support for token verification (#11496)
* feat(user): add support for token validation

* feat(user): update openapi specs

* feat(user): update verify endpoint

* feat(user): use binding package

* feat(user): update openapi specs
2026-05-29 13:35:30 +00:00
45 changed files with 662 additions and 122 deletions

View File

@@ -58,8 +58,6 @@ jobs:
run: |
mkdir -p frontend
echo 'CI=1' > frontend/.env
echo 'VITE_INTERCOM_APP_ID="${{ secrets.INTERCOM_APP_ID }}"' >> frontend/.env
echo 'VITE_SEGMENT_ID="${{ secrets.SEGMENT_ID }}"' >> frontend/.env
echo 'VITE_SENTRY_AUTH_TOKEN="${{ secrets.SENTRY_AUTH_TOKEN }}"' >> frontend/.env
echo 'VITE_SENTRY_ORG="${{ secrets.SENTRY_ORG }}"' >> frontend/.env
echo 'VITE_SENTRY_PROJECT_ID="${{ secrets.SENTRY_PROJECT_ID }}"' >> frontend/.env

View File

@@ -24,8 +24,6 @@ jobs:
- name: dotenv-frontend
working-directory: frontend
run: |
echo 'VITE_INTERCOM_APP_ID="${{ secrets.INTERCOM_APP_ID }}"' > .env
echo 'VITE_SEGMENT_ID="${{ secrets.SEGMENT_ID }}"' >> .env
echo 'VITE_SENTRY_AUTH_TOKEN="${{ secrets.SENTRY_AUTH_TOKEN }}"' >> .env
echo 'VITE_SENTRY_ORG="${{ secrets.SENTRY_ORG }}"' >> .env
echo 'VITE_SENTRY_PROJECT_ID="${{ secrets.SENTRY_PROJECT_ID }}"' >> .env

View File

@@ -7085,6 +7085,13 @@ components:
required:
- name
type: object
TypesPostableVerifyResetPasswordToken:
properties:
token:
type: string
required:
- token
type: object
TypesResetPasswordToken:
properties:
expiresAt:
@@ -14866,6 +14873,41 @@ paths:
summary: Readiness check
tags:
- health
/api/v2/reset_password_tokens/verify:
post:
deprecated: false
description: This endpoint verifies whether a reset password token exists and
is not expired
operationId: VerifyResetPasswordToken
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/TypesPostableVerifyResetPasswordToken'
responses:
"204":
description: No Content
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
summary: Verify a reset password token
tags:
- users
/api/v2/roles/{id}/users:
get:
deprecated: false

View File

@@ -26,7 +26,7 @@ import (
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
cmock "github.com/srikanthccv/ClickHouse-go-mock"
cmock "github.com/SigNoz/clickhouse-go-mock"
)
func TestManager_TestNotification_SendUnmatched_ThresholdRule(t *testing.T) {

View File

@@ -8325,6 +8325,13 @@ export interface TypesPostableRoleDTO {
name: string;
}
export interface TypesPostableVerifyResetPasswordTokenDTO {
/**
* @type string
*/
token: string;
}
export interface TypesResetPasswordTokenDTO {
/**
* @type string

View File

@@ -48,6 +48,7 @@ import type {
TypesPostableInviteDTO,
TypesPostableResetPasswordDTO,
TypesPostableRoleDTO,
TypesPostableVerifyResetPasswordTokenDTO,
TypesUpdatableUserDTO,
UpdateUserDeprecated200,
UpdateUserDeprecatedPathParameters,
@@ -947,6 +948,90 @@ export const useForgotPassword = <
> => {
return useMutation(getForgotPasswordMutationOptions(options));
};
/**
* This endpoint verifies whether a reset password token exists and is not expired
* @summary Verify a reset password token
*/
export const verifyResetPasswordToken = (
typesPostableVerifyResetPasswordTokenDTO?: BodyType<TypesPostableVerifyResetPasswordTokenDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<void>({
url: `/api/v2/reset_password_tokens/verify`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: typesPostableVerifyResetPasswordTokenDTO,
signal,
});
};
export const getVerifyResetPasswordTokenMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof verifyResetPasswordToken>>,
TError,
{ data?: BodyType<TypesPostableVerifyResetPasswordTokenDTO> },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof verifyResetPasswordToken>>,
TError,
{ data?: BodyType<TypesPostableVerifyResetPasswordTokenDTO> },
TContext
> => {
const mutationKey = ['verifyResetPasswordToken'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof verifyResetPasswordToken>>,
{ data?: BodyType<TypesPostableVerifyResetPasswordTokenDTO> }
> = (props) => {
const { data } = props ?? {};
return verifyResetPasswordToken(data);
};
return { mutationFn, ...mutationOptions };
};
export type VerifyResetPasswordTokenMutationResult = NonNullable<
Awaited<ReturnType<typeof verifyResetPasswordToken>>
>;
export type VerifyResetPasswordTokenMutationBody =
| BodyType<TypesPostableVerifyResetPasswordTokenDTO>
| undefined;
export type VerifyResetPasswordTokenMutationError =
ErrorType<RenderErrorResponseDTO>;
/**
* @summary Verify a reset password token
*/
export const useVerifyResetPasswordToken = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof verifyResetPasswordToken>>,
TError,
{ data?: BodyType<TypesPostableVerifyResetPasswordTokenDTO> },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof verifyResetPasswordToken>>,
TError,
{ data?: BodyType<TypesPostableVerifyResetPasswordTokenDTO> },
TContext
> => {
return useMutation(getVerifyResetPasswordTokenMutationOptions(options));
};
/**
* This endpoint returns the users having the role by role id
* @summary Get users by role id

View File

@@ -1,12 +0,0 @@
.route-tab-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 4px;
}
.route-tab-extra {
display: flex;
align-items: center;
}

View File

@@ -70,7 +70,7 @@ describe('RouteTab component', () => {
</Router>,
);
expect(history.location.pathname).toBe('/');
fireEvent.mouseDown(screen.getByRole('tab', { name: 'Tab2' }));
fireEvent.click(screen.getByRole('tab', { name: 'Tab2' }));
expect(history.location.pathname).toBe('/tab2');
});
@@ -87,7 +87,7 @@ describe('RouteTab component', () => {
/>
</Router>,
);
fireEvent.mouseDown(screen.getByRole('tab', { name: 'Tab2' }));
fireEvent.click(screen.getByRole('tab', { name: 'Tab2' }));
expect(onChangeHandler).toHaveBeenCalled();
});
});

View File

@@ -1,17 +1,10 @@
import './RouteTab.styles.scss';
import {
generatePath,
matchPath,
useLocation,
useParams,
} from 'react-router-dom';
import {
TabsContent,
TabsList,
TabsRoot,
TabsTrigger,
} from '@signozhq/ui/tabs';
import { Tabs, TabsProps } from 'antd';
import HeaderRightSection from 'components/HeaderRightSection/HeaderRightSection';
import { RouteTabProps } from './types';
@@ -23,13 +16,11 @@ interface Params {
function RouteTab({
routes,
activeKey,
defaultActiveKey,
onChangeHandler,
history,
showRightSection = true,
tabBarExtraContent,
hideTabBar = false,
}: RouteTabProps): JSX.Element {
showRightSection,
...rest
}: RouteTabProps & TabsProps): JSX.Element {
const params = useParams<Params>();
const location = useLocation();
@@ -55,38 +46,38 @@ function RouteTab({
}
};
const resolvedActiveKey = currentRoute?.key || activeKey;
const extraContent =
tabBarExtraContent ??
(showRightSection && (
<HeaderRightSection enableAnnouncements={false} enableShare enableFeedback />
));
const items = routes.map(({ Component, name, route, key }) => ({
label: name,
key,
tabKey: route,
children: <Component />,
}));
return (
<TabsRoot
value={resolvedActiveKey}
defaultValue={defaultActiveKey ?? resolvedActiveKey}
onValueChange={onChange}
>
{!hideTabBar && (
<div className="route-tab-header">
<TabsList>
{routes.map(({ name, key }) => (
<TabsTrigger key={key} value={key}>
{name}
</TabsTrigger>
))}
</TabsList>
{extraContent && <div className="route-tab-extra">{extraContent}</div>}
</div>
)}
{routes.map(({ key, Component }) => (
<TabsContent key={key} value={key}>
<Component />
</TabsContent>
))}
</TabsRoot>
<Tabs
onChange={onChange}
destroyInactiveTabPane
activeKey={currentRoute?.key || activeKey}
defaultActiveKey={currentRoute?.key || activeKey}
animated
items={items}
tabBarExtraContent={
showRightSection && (
<HeaderRightSection
enableAnnouncements={false}
enableShare
enableFeedback
/>
)
}
{...rest}
/>
);
}
RouteTab.defaultProps = {
onChangeHandler: undefined,
showRightSection: true,
};
export default RouteTab;

View File

@@ -1,5 +1,5 @@
import { TabsProps } from 'antd';
import { History } from 'history';
import { ReactNode } from 'react';
export type TabRoutes = {
name: React.ReactNode;
@@ -10,11 +10,8 @@ export type TabRoutes = {
export interface RouteTabProps {
routes: TabRoutes[];
activeKey: string | undefined;
defaultActiveKey?: string;
activeKey: TabsProps['activeKey'];
onChangeHandler?: (key: string) => void;
history: History<unknown>;
showRightSection?: boolean;
tabBarExtraContent?: ReactNode;
hideTabBar?: boolean;
showRightSection: boolean;
}

View File

@@ -0,0 +1,70 @@
.settings-tabs {
.ant-tabs-nav-list {
height: 32px;
flex-shrink: 0;
border-radius: 2px;
border: 1px solid var(--l1-border);
background: var(--l2-background);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
transition: opacity 0.1s !important;
.ant-tabs-tab + .ant-tabs-tab {
margin: 0px;
}
.ant-tabs-tab:not(:last-child) {
border-right: 1px solid var(--l1-border) !important;
}
.overview-btn {
width: 114px;
display: flex;
align-items: center;
justify-content: center;
}
.variables-btn {
width: 114px;
display: flex;
align-items: center;
justify-content: center;
}
.public-dashboard-btn {
width: 150px;
display: flex;
align-items: center;
justify-content: center;
&.disabled-btn {
opacity: 0.5;
cursor: not-allowed;
}
}
.ant-tabs-ink-bar {
display: none;
}
.ant-tabs-tab-active {
.overview-btn {
border-radius: 2px 0px 0px 2px;
background: var(--l1-border);
}
.variables-btn {
border-radius: 2px 0px 0px 2px;
background: var(--l1-border);
}
.public-dashboard-btn {
border-radius: 2px 0px 0px 2px;
background: var(--l1-border);
}
}
}
.ant-tabs-nav::before {
border-bottom: none;
}
}

View File

@@ -1,4 +1,4 @@
import { Tabs, TabItemProps } from '@signozhq/ui/tabs';
import { Button, Tabs, Tooltip } from 'antd';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import { Braces, Globe, Table } from '@signozhq/icons';
import { useAppContext } from 'providers/App/App';
@@ -9,6 +9,8 @@ import DashboardVariableSettings from './DashboardVariableSettings';
import GeneralDashboardSettings from './General';
import PublicDashboardSetting from './PublicDashboard';
import './DashboardSettingsContent.styles.scss';
function DashboardSettings({
variablesSettingsTabHandle,
}: {
@@ -19,26 +21,49 @@ function DashboardSettings({
const enablePublicDashboard = isCloudUser || isEnterpriseSelfHostedUser;
const publicDashboardItem: TabItemProps = {
const publicDashboardItem = {
label: (
<Tooltip
title={
user?.role !== USER_ROLES.ADMIN
? 'Only admins can publish / manage public dashboards'
: ''
}
placement="right"
>
<Button
type="text"
icon={<Globe size={14} />}
className={`public-dashboard-btn ${
user?.role !== USER_ROLES.ADMIN ? 'disabled-btn' : ''
}`}
>
Publish
</Button>
</Tooltip>
),
key: 'public-dashboard',
label: 'Publish',
prefixIcon: <Globe size={14} />,
children: <PublicDashboardSetting />,
disabled: user?.role !== USER_ROLES.ADMIN,
disabledReason: 'Only admins can publish / manage public dashboards',
};
const items: TabItemProps[] = [
const items = [
{
label: (
<Button type="text" icon={<Table size={14} />} className="overview-btn">
Overview
</Button>
),
key: 'general',
label: 'Overview',
prefixIcon: <Table size={14} />,
children: <GeneralDashboardSettings />,
},
{
label: (
<Button type="text" icon={<Braces size={14} />} className="variables-btn">
Variables
</Button>
),
key: 'variables',
label: 'Variables',
prefixIcon: <Braces size={14} />,
children: (
<DashboardVariableSettings
variablesSettingsTabHandle={variablesSettingsTabHandle}
@@ -48,7 +73,7 @@ function DashboardSettings({
...(enablePublicDashboard ? [publicDashboardItem] : []),
];
return <Tabs items={items} />;
return <Tabs items={items} animated className="settings-tabs" />;
}
export default DashboardSettings;

View File

@@ -1,4 +1,5 @@
import { Tabs, TabItemProps } from '@signozhq/ui/tabs';
import { Button, Tabs, TabsProps } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import ConfigureIcon from 'assets/Integrations/ConfigureIcon';
import { CableCar, Group } from '@signozhq/icons';
import { IntegrationDetailedProps } from 'types/api/integrations/types';
@@ -21,11 +22,18 @@ function IntegrationDetailContent(
): JSX.Element {
const { activeDetailTab, integrationData, integrationId, setActiveDetailTab } =
props;
const items: TabItemProps[] = [
const items: TabsProps['items'] = [
{
key: 'overview',
label: 'Overview',
prefixIcon: <CableCar size={14} />,
label: (
<Button
type="text"
className="integration-tab-btns"
icon={<CableCar size={14} />}
>
<Typography.Text className="typography">Overview</Typography.Text>
</Button>
),
children: (
<Overview
categories={integrationData.categories}
@@ -36,8 +44,15 @@ function IntegrationDetailContent(
},
{
key: 'configuration',
label: 'Configure',
prefixIcon: <ConfigureIcon />,
label: (
<Button
type="text"
className="integration-tab-btns"
icon={<ConfigureIcon />}
>
<Typography.Text className="typography">Configure</Typography.Text>
</Button>
),
children: (
<Configure
configuration={integrationData.configuration}
@@ -47,8 +62,15 @@ function IntegrationDetailContent(
},
{
key: 'dataCollected',
label: 'Data Collected',
prefixIcon: <Group size={14} />,
label: (
<Button
type="text"
className="integration-tab-btns"
icon={<Group size={14} />}
>
<Typography.Text className="typography">Data Collected</Typography.Text>
</Button>
),
children: (
<DataCollected
logsData={integrationData.data_collected.logs}
@@ -59,7 +81,11 @@ function IntegrationDetailContent(
];
return (
<div className="integration-detail-container">
<Tabs value={activeDetailTab} items={items} onChange={setActiveDetailTab} />
<Tabs
activeKey={activeDetailTab}
items={items}
onChange={setActiveDetailTab}
/>
</div>
);
}

View File

@@ -168,6 +168,45 @@
padding: 10px 16px;
border: 1px solid var(--l1-border);
background: var(--l1-background);
.integration-tab-btns {
display: flex;
align-items: center;
justify-content: center;
padding: 8px 8px 18px 8px !important;
.typography {
color: var(--l1-foreground);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
}
.integration-tab-btns:hover {
&.ant-btn-text {
background-color: unset !important;
}
}
.ant-tabs-nav-list {
gap: 24px;
}
.ant-tabs-nav {
padding: 0px !important;
}
.ant-tabs-tab {
padding: 0 !important;
}
.ant-tabs-tab + .ant-tabs-tab {
margin: 0px !important;
}
}
.uninstall-integration-bar {

View File

@@ -12,6 +12,7 @@ export const TagContainer = styled(Badge)`
`;
export const TagLabel = styled.span`
color: var(--foreground);
font-weight: 400;
font-size: 12px;
`;

View File

@@ -1,3 +1,26 @@
.service-route-tab {
margin-bottom: 64px;
.ant-tabs-nav {
&::before {
border-bottom: 1px solid var(--l1-border);
}
.ant-tabs-nav-wrap {
.ant-tabs-nav-list {
.ant-tabs-ink-bar {
background-color: var(--primary-background) !important;
}
.ant-tabs-tab {
font-size: 13px;
font-family: 'Inter';
color: var(--l1-foreground);
line-height: 20px;
letter-spacing: -0.07px;
gap: 10px;
}
.ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn {
color: var(--accent-primary);
}
}
}
}
}

View File

@@ -1,5 +1,5 @@
import { useParams } from 'react-router-dom';
import { Tabs, TabItemProps } from '@signozhq/ui/tabs';
import { Tabs, TabsProps } from 'antd';
import { QueryParams } from 'constants/query';
import DBCall from 'container/MetricsApplication/Tabs/DBCall';
import External from 'container/MetricsApplication/Tabs/External';
@@ -24,7 +24,7 @@ function MetricsApplication(): JSX.Element {
const urlQuery = useUrlQuery();
const { safeNavigate } = useSafeNavigate();
const items: TabItemProps[] = [
const items: TabsProps['items'] = [
{
label: TAB_KEY_VS_LABEL[MetricsApplicationTab.OVER_METRICS],
key: MetricsApplicationTab.OVER_METRICS,
@@ -53,8 +53,9 @@ function MetricsApplication(): JSX.Element {
<ApDexApplication />
<Tabs
items={items}
value={activeKey}
activeKey={activeKey}
className="service-route-tab"
destroyInactiveTabPane
onChange={onTabChange}
/>
</div>

View File

@@ -0,0 +1,9 @@
.pipeline-tabs {
.ant-tabs-content {
padding: 0 16px;
}
.ant-tabs-tabpane-hidden {
display: none !important;
}
}

View File

@@ -2,7 +2,8 @@ import { useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import * as Sentry from '@sentry/react';
import { Tabs, TabItemProps } from '@signozhq/ui/tabs';
import type { TabsProps } from 'antd';
import { Tabs } from 'antd';
import getPipeline from 'api/pipeline/get';
import Spinner from 'components/Spinner';
import ChangeHistory from 'container/PipelinePage/Layouts/ChangeHistory';
@@ -12,6 +13,8 @@ import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFall
import { SuccessResponse } from 'types/api';
import { Pipeline } from 'types/api/pipeline/def';
import './Pipelines.styles.scss';
const pipelineRefetchInterval = (
pipelineResponse: SuccessResponse<Pipeline> | undefined,
): number | false => {
@@ -43,7 +46,7 @@ function Pipelines(): JSX.Element {
refetchInterval: pipelineRefetchInterval,
});
const tabItems: TabItemProps[] = useMemo(
const tabItems: TabsProps['items'] = useMemo(
() => [
{
key: 'pipelines',
@@ -80,7 +83,11 @@ function Pipelines(): JSX.Element {
return (
<Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}>
<Tabs defaultValue="pipelines" items={tabItems} />
<Tabs
className="pipeline-tabs"
defaultActiveKey="pipelines"
items={tabItems}
/>
</Sentry.ErrorBoundary>
);
}

View File

@@ -340,7 +340,7 @@ function SettingsPage(): JSX.Element {
routes={routes}
activeKey={pathname}
history={history}
hideTabBar
tabBarStyle={{ display: 'none' }}
/>
</div>
</div>

View File

@@ -71,6 +71,88 @@
flex-direction: column;
gap: 25px;
padding-top: 16px;
.flamegraph-waterfall-toggle {
display: flex;
gap: 4px;
align-items: center;
justify-content: center;
height: 31px;
color: var(--l2-foreground);
padding: 5px 20px;
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
.ant-btn-icon {
margin-inline-end: 0px !important;
}
}
.span-list-toggle {
display: flex;
gap: 4px;
align-items: center;
justify-content: center;
height: 31px;
padding: 5px 20px;
color: var(--l2-foreground);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
.ant-btn-icon {
margin-inline-end: 0px !important;
}
}
.trace-visualisation-tabs {
.ant-tabs-tab {
border-radius: 2px 0px 0px 0px;
background: var(--l2-background);
border-radius: 2px 2px 0px 0px;
border: 1px solid var(--l1-border);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
height: 31px;
}
.ant-tabs-tab-active {
background-color: var(--l1-background);
.ant-btn {
color: var(--l1-foreground) !important;
}
}
.ant-tabs-tab + .ant-tabs-tab {
margin: 0px;
border-left: 0px;
}
.ant-tabs-ink-bar {
height: 1px !important;
background: var(--l1-background) !important;
}
.ant-tabs-nav-list {
transform: translate(15px, 0px) !important;
}
.ant-tabs-nav::before {
border-bottom: 1px solid var(--l1-border);
}
.ant-tabs-nav {
margin: 0px;
padding: 0px !important;
}
}
}
}
}

View File

@@ -5,7 +5,7 @@ import {
ResizablePanel,
ResizablePanelGroup,
} from '@signozhq/resizable';
import { Tabs, TabItemProps } from '@signozhq/ui/tabs';
import { Button, Tabs } from 'antd';
import FlamegraphImg from 'assets/TraceDetail/Flamegraph';
import cx from 'classnames';
import TraceFlamegraph from 'container/PaginatedTraceFlamegraph/PaginatedTraceFlamegraph';
@@ -86,11 +86,18 @@ function TraceDetailsV2(): JSX.Element {
}
}, [noData]);
const items: TabItemProps[] = [
const items = [
{
label: (
<Button
type="text"
icon={<FlamegraphImg />}
className="flamegraph-waterfall-toggle"
>
Flamegraph
</Button>
),
key: 'flamegraph',
label: 'Flamegraph',
prefixIcon: <FlamegraphImg />,
children: (
<>
<TraceFlamegraph
@@ -138,7 +145,11 @@ function TraceDetailsV2(): JSX.Element {
totalSpans={traceData?.payload?.totalSpansCount || 0}
notFound={noData}
/>
{!noData ? <Tabs items={items} /> : <NoData />}
{!noData ? (
<Tabs items={items} animated className="trace-visualisation-tabs" />
) : (
<NoData />
)}
</ResizablePanel>
<ResizableHandle withHandle className="resizable-handle" />

View File

@@ -22,6 +22,21 @@ $dark-theme: 'darkMode';
&__tabs {
margin-top: 148px;
.ant-tabs {
&-nav {
&::before {
border-color: var(--l1-border);
.#{$light-theme} & {
border-color: var(--l1-border);
}
}
}
&-nav-wrap {
justify-content: center;
}
}
}
&__modal {

View File

@@ -2,7 +2,7 @@
import React, { useCallback, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { useMutation } from 'react-query';
import type { TabItemProps } from '@signozhq/ui/tabs';
import type { TabsProps } from 'antd';
import {
Alert,
Button,
@@ -14,8 +14,8 @@ import {
Row,
Skeleton,
Space,
Tabs,
} from 'antd';
import { Tabs } from '@signozhq/ui/tabs';
import { Typography } from '@signozhq/ui/typography';
import logEvent from 'api/common/logEvent';
import updateCreditCardApi from 'api/v1/checkout/create';
@@ -154,7 +154,7 @@ export default function WorkspaceBlocked(): JSX.Element {
/>
));
const tabItems: TabItemProps[] = [
const tabItems: TabsProps['items'] = [
{
key: 'whyChooseSignoz',
label: t('whyChooseSignoz'),
@@ -398,8 +398,8 @@ export default function WorkspaceBlocked(): JSX.Element {
<div className="workspace-locked__tabs">
<Tabs
items={tabItems}
defaultValue="youAreInGoodCompany"
onChange={handleTabClick}
defaultActiveKey="youAreInGoodCompany"
onTabClick={handleTabClick}
/>
</div>
</>

View File

@@ -825,6 +825,5 @@ body.ai-assistant-panel-open {
// overrides
:root {
--input-focus-outline-width: 0;
--tab-list-primary-gap: 12px;
--radius-2: 4px;
}

4
go.mod
View File

@@ -5,8 +5,9 @@ go 1.25.7
require (
dario.cat/mergo v1.0.2
github.com/AfterShip/clickhouse-sql-parser v0.4.16
github.com/ClickHouse/clickhouse-go/v2 v2.43.0
github.com/ClickHouse/clickhouse-go/v2 v2.44.0
github.com/DATA-DOG/go-sqlmock v1.5.2
github.com/SigNoz/clickhouse-go-mock v0.14.0
github.com/SigNoz/govaluate v0.0.0-20240203125216-988004ccc7fd
github.com/SigNoz/signoz-otel-collector v0.144.3
github.com/antlr4-go/antlr/v4 v4.13.1
@@ -58,7 +59,6 @@ require (
github.com/smartystreets/goconvey v1.8.1
github.com/soheilhy/cmux v0.1.5
github.com/spf13/cobra v1.10.2
github.com/srikanthccv/ClickHouse-go-mock v0.13.0
github.com/stretchr/testify v1.11.1
github.com/swaggest/jsonschema-go v0.3.78
github.com/swaggest/rest v0.2.75

10
go.sum
View File

@@ -89,8 +89,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/ClickHouse/ch-go v0.71.0 h1:bUdZ/EZj/LcVHsMqaRUP2holqygrPWQKeMjc6nZoyRM=
github.com/ClickHouse/ch-go v0.71.0/go.mod h1:NwbNc+7jaqfY58dmdDUbG4Jl22vThgx1cYjBw0vtgXw=
github.com/ClickHouse/clickhouse-go/v2 v2.43.0 h1:fUR05TrF1GyvLDa/mAQjkx7KbgwdLRffs2n9O3WobtE=
github.com/ClickHouse/clickhouse-go/v2 v2.43.0/go.mod h1:o6jf7JM/zveWC/PP277BLxjHy5KjnGX/jfljhM4s34g=
github.com/ClickHouse/clickhouse-go/v2 v2.44.0 h1:9pxs5pRwIvhni5BDRPn/n5A8DeUod5TnBaeulFBX8EQ=
github.com/ClickHouse/clickhouse-go/v2 v2.44.0/go.mod h1:giJfUVlMkcfUEPVfRpt51zZaGEx9i17gCos8gBl392c=
github.com/Code-Hex/go-generics-cache v1.5.1 h1:6vhZGc5M7Y/YD8cIUcY8kcuQLB4cHR7U+0KMqAA0KcU=
github.com/Code-Hex/go-generics-cache v1.5.1/go.mod h1:qxcC9kRVrct9rHeiYpFWSoW1vxyillCVzX13KZG8dl4=
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
@@ -104,6 +104,8 @@ github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA4
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/SigNoz/clickhouse-go-mock v0.14.0 h1:yYoGMJk0UDVvLCk12dover/zoRIuznW3BfoqUhMoJSY=
github.com/SigNoz/clickhouse-go-mock v0.14.0/go.mod h1:tXmSXVLWz7N/yVLCianszmNIN4cHozXvwEXCOLzzqzI=
github.com/SigNoz/expr v1.17.7-beta h1:FyZkleM5dTQ0O6muQfwGpoH5A2ohmN/XTasRCO72gAA=
github.com/SigNoz/expr v1.17.7-beta/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
github.com/SigNoz/govaluate v0.0.0-20240203125216-988004ccc7fd h1:Bk43AsDYe0fhkbj57eGXx8H3ZJ4zhmQXBnrW523ktj8=
@@ -1062,8 +1064,8 @@ github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3A
github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw=
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
github.com/srikanthccv/ClickHouse-go-mock v0.13.0 h1:/b7DQphGkh29ocNtLh4DGmQxQYA0CfHz65Wy2zAH2GM=
github.com/srikanthccv/ClickHouse-go-mock v0.13.0/go.mod h1:LiiyBUdXNwB/1DE9rgK/8q9qjVYsTzg6WXQ/3mU3TeY=
github.com/srikanthccv/ClickHouse-go-mock v0.12.0 h1:KUzaWTwuqMc2uf5FylM/oAcTFdE2DdZjvISm9V0/NAA=
github.com/srikanthccv/ClickHouse-go-mock v0.12.0/go.mod h1:1oUmLtXEXOyS0EEWVKlKEfLfv9y02agCMAvD3tVnhlo=
github.com/stackitcloud/stackit-sdk-go/core v0.23.0 h1:zPrOhf3Xe47rKRs1fg/AqKYUiJJRYjdcv+3qsS50mEs=
github.com/stackitcloud/stackit-sdk-go/core v0.23.0/go.mod h1:osMglDby4csGZ5sIfhNyYq1bS1TxIdPY88+skE/kkmI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=

View File

@@ -264,6 +264,23 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
return err
}
if err := router.Handle("/api/v2/reset_password_tokens/verify", handler.New(provider.authzMiddleware.OpenAccess(provider.userHandler.VerifyResetPasswordToken), handler.OpenAPIDef{
ID: "VerifyResetPasswordToken",
Tags: []string{"users"},
Summary: "Verify a reset password token",
Description: "This endpoint verifies whether a reset password token exists and is not expired",
Request: new(types.PostableVerifyResetPasswordToken),
RequestContentType: "application/json",
Response: nil,
ResponseContentType: "",
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
Deprecated: false,
SecuritySchemes: []handler.OpenAPISecurityScheme{},
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/resetPassword", handler.New(provider.authzMiddleware.OpenAccess(provider.userHandler.ResetPassword), handler.OpenAPIDef{
ID: "ResetPassword",
Tags: []string{"users"},

View File

@@ -226,6 +226,19 @@ func (module *getter) GetUsersByOrgIDAndRoleID(ctx context.Context, orgID valuer
return module.store.GetUsersByOrgIDAndRoleID(ctx, orgID, roleID)
}
func (module *getter) VerifyResetPasswordToken(ctx context.Context, token string) error {
resetPasswordToken, err := module.store.GetResetPasswordToken(ctx, token)
if err != nil {
return err
}
if resetPasswordToken.IsExpired() {
return errors.New(errors.TypeUnauthenticated, types.ErrCodeResetPasswordTokenExpired, "reset password token has expired")
}
return nil
}
func (module *getter) OnBeforeRoleDelete(ctx context.Context, orgID valuer.UUID, roleID valuer.UUID) error {
users, err := module.GetUsersByOrgIDAndRoleID(ctx, orgID, roleID)
if err != nil {

View File

@@ -410,6 +410,25 @@ func (handler *handler) CreateResetPasswordToken(w http.ResponseWriter, r *http.
render.Success(w, http.StatusCreated, token)
}
func (handler *handler) VerifyResetPasswordToken(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
req := new(types.PostableVerifyResetPasswordToken)
if err := binding.JSON.BindBody(r.Body, req); err != nil {
render.Error(w, err)
return
}
err := handler.getter.VerifyResetPasswordToken(ctx, req.Token)
if err != nil {
render.Error(w, err)
return
}
render.Success(w, http.StatusNoContent, nil)
}
func (handler *handler) ResetPassword(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()

View File

@@ -537,7 +537,7 @@ func (module *setter) UpdatePasswordByResetPasswordToken(ctx context.Context, to
}
if resetPasswordToken.IsExpired() {
return errors.New(errors.TypeUnauthenticated, errors.CodeUnauthenticated, "reset password token has expired")
return errors.New(errors.TypeUnauthenticated, types.ErrCodeResetPasswordTokenExpired, "reset password token has expired")
}
password, err := module.store.GetPassword(ctx, resetPasswordToken.PasswordID)

View File

@@ -94,6 +94,9 @@ type Getter interface {
// OnBeforeRoleDelete checks if any users are assigned to the role and rejects deletion if so.
OnBeforeRoleDelete(ctx context.Context, orgID valuer.UUID, roleID valuer.UUID) error
// VerifyResetPasswordToken checks if a reset password token exists and is not expired.
VerifyResetPasswordToken(ctx context.Context, token string) error
}
type Handler interface {
@@ -121,6 +124,7 @@ type Handler interface {
GetResetPasswordTokenDeprecated(http.ResponseWriter, *http.Request)
GetResetPasswordToken(http.ResponseWriter, *http.Request)
CreateResetPasswordToken(http.ResponseWriter, *http.Request)
VerifyResetPasswordToken(http.ResponseWriter, *http.Request)
ResetPassword(http.ResponseWriter, *http.Request)
ChangePassword(http.ResponseWriter, *http.Request)
ForgotPassword(http.ResponseWriter, *http.Request)

View File

@@ -6,7 +6,7 @@ import (
"testing"
"github.com/SigNoz/signoz/pkg/telemetrystore/telemetrystoretest"
cmock "github.com/srikanthccv/ClickHouse-go-mock"
cmock "github.com/SigNoz/clickhouse-go-mock"
"github.com/stretchr/testify/require"
"github.com/DATA-DOG/go-sqlmock"

View File

@@ -5,7 +5,7 @@ import (
"testing"
"time"
cmock "github.com/srikanthccv/ClickHouse-go-mock"
cmock "github.com/SigNoz/clickhouse-go-mock"
"github.com/SigNoz/signoz/pkg/flagger/flaggertest"
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"

View File

@@ -26,7 +26,7 @@ import (
"github.com/SigNoz/signoz/pkg/telemetrystore"
"github.com/SigNoz/signoz/pkg/telemetrystore/telemetrystoretest"
"github.com/SigNoz/signoz/pkg/valuer"
cmock "github.com/srikanthccv/ClickHouse-go-mock"
cmock "github.com/SigNoz/clickhouse-go-mock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

View File

@@ -26,7 +26,7 @@ import (
"github.com/SigNoz/signoz/pkg/telemetrystore"
"github.com/SigNoz/signoz/pkg/telemetrystore/telemetrystoretest"
"github.com/SigNoz/signoz/pkg/valuer"
cmock "github.com/srikanthccv/ClickHouse-go-mock"
cmock "github.com/SigNoz/clickhouse-go-mock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

View File

@@ -25,7 +25,7 @@ import (
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
cmock "github.com/srikanthccv/ClickHouse-go-mock"
cmock "github.com/SigNoz/clickhouse-go-mock"
)
func TestManager_TestNotification_SendUnmatched_ThresholdRule(t *testing.T) {

View File

@@ -10,7 +10,7 @@ import (
"github.com/stretchr/testify/require"
pql "github.com/prometheus/prometheus/promql"
cmock "github.com/srikanthccv/ClickHouse-go-mock"
cmock "github.com/SigNoz/clickhouse-go-mock"
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
"github.com/SigNoz/signoz/pkg/prometheus"

View File

@@ -11,7 +11,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
cmock "github.com/srikanthccv/ClickHouse-go-mock"
cmock "github.com/SigNoz/clickhouse-go-mock"
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
"github.com/SigNoz/signoz/pkg/telemetrystore"

View File

@@ -13,7 +13,7 @@ import (
"github.com/SigNoz/signoz/pkg/telemetrystore/telemetrystoretest"
"github.com/SigNoz/signoz/pkg/telemetrytraces"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
cmock "github.com/srikanthccv/ClickHouse-go-mock"
cmock "github.com/SigNoz/clickhouse-go-mock"
"github.com/stretchr/testify/assert"
"github.com/SigNoz/signoz/pkg/flagger/flaggertest"
"github.com/stretchr/testify/require"

View File

@@ -15,7 +15,7 @@ import (
"github.com/SigNoz/signoz/pkg/telemetrystore/telemetrystoretest"
"github.com/SigNoz/signoz/pkg/telemetrytraces"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
cmock "github.com/srikanthccv/ClickHouse-go-mock"
cmock "github.com/SigNoz/clickhouse-go-mock"
"github.com/stretchr/testify/assert"
"github.com/SigNoz/signoz/pkg/flagger/flaggertest"
"github.com/stretchr/testify/require"

View File

@@ -7,6 +7,7 @@ import (
"github.com/ClickHouse/clickhouse-go/v2/lib/driver"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/telemetrystore"
"go.opentelemetry.io/otel/metric"
)
type provider struct {
@@ -49,6 +50,23 @@ func New(ctx context.Context, providerSettings factory.ProviderSettings, config
hooks[i] = hook
}
metrics, err := newMetrics(settings.Meter())
if err != nil {
return nil, err
}
_, err = settings.Meter().RegisterCallback(func(_ context.Context, observer metric.Observer) error {
stats := chConn.Stats()
observer.ObserveInt64(metrics.open, int64(stats.Open))
observer.ObserveInt64(metrics.idle, int64(stats.Idle))
observer.ObserveInt64(metrics.maxOpen, int64(stats.MaxOpenConns))
observer.ObserveInt64(metrics.maxIdle, int64(stats.MaxIdleConns))
return nil
}, metrics.open, metrics.idle, metrics.maxOpen, metrics.maxIdle)
if err != nil {
return nil, err
}
return &provider{
settings: settings,
clickHouseConn: chConn,

View File

@@ -0,0 +1,48 @@
package clickhousetelemetrystore
import (
"github.com/SigNoz/signoz/pkg/errors"
"go.opentelemetry.io/otel/metric"
)
type metrics struct {
open metric.Int64ObservableGauge
idle metric.Int64ObservableGauge
maxOpen metric.Int64ObservableGauge
maxIdle metric.Int64ObservableGauge
}
func newMetrics(meter metric.Meter) (*metrics, error) {
var errs error
open, err := meter.Int64ObservableGauge("signoz.telemetrystore.connection.open", metric.WithDescription("Open is the current number of open connections to the telemetry store."))
if err != nil {
errs = errors.Join(errs, err)
}
idle, err := meter.Int64ObservableGauge("signoz.telemetrystore.connection.idle", metric.WithDescription("Idle is the current number of idle connections in the telemetry store pool."))
if err != nil {
errs = errors.Join(errs, err)
}
maxOpen, err := meter.Int64ObservableGauge("signoz.telemetrystore.connection.max_open", metric.WithDescription("MaxOpen is the configured maximum number of open connections to the telemetry store."))
if err != nil {
errs = errors.Join(errs, err)
}
maxIdle, err := meter.Int64ObservableGauge("signoz.telemetrystore.connection.max_idle", metric.WithDescription("MaxIdle is the configured maximum number of idle connections in the telemetry store pool."))
if err != nil {
errs = errors.Join(errs, err)
}
if errs != nil {
return nil, errs
}
return &metrics{
open: open,
idle: idle,
maxOpen: maxOpen,
maxIdle: maxIdle,
}, nil
}

View File

@@ -4,7 +4,7 @@ import (
"github.com/ClickHouse/clickhouse-go/v2"
"github.com/DATA-DOG/go-sqlmock"
"github.com/SigNoz/signoz/pkg/telemetrystore"
cmock "github.com/srikanthccv/ClickHouse-go-mock"
cmock "github.com/SigNoz/clickhouse-go-mock"
)
var _ telemetrystore.TelemetryStore = (*Provider)(nil)

View File

@@ -21,10 +21,15 @@ var (
ErrCodeResetPasswordTokenAlreadyExists = errors.MustNewCode("reset_password_token_already_exists")
ErrCodePasswordNotFound = errors.MustNewCode("password_not_found")
ErrCodeResetPasswordTokenNotFound = errors.MustNewCode("reset_password_token_not_found")
ErrCodeResetPasswordTokenExpired = errors.MustNewCode("reset_password_token_expired")
ErrCodePasswordAlreadyExists = errors.MustNewCode("password_already_exists")
ErrCodeIncorrectPassword = errors.MustNewCode("incorrect_password")
)
type PostableVerifyResetPasswordToken struct {
Token string `json:"token" required:"true"`
}
type PostableResetPassword struct {
Password string `json:"password"`
Token string `json:"token"`