Compare commits

...

3 Commits

Author SHA1 Message Date
swapnil-signoz
591a11d346 refactor: updating cloud integration service dashboard handling 2026-05-29 16:39:07 +05:30
Tushar Vats
edc1278769 fix(querybuilder): return PreparedWhereClause by value so warnings propagate when clause is empty (#11395)
* fix: propage dropped warnings from where clause visitor

* fix: added integration test

* fix: make py-fmt

* fix: remove stale comment
2026-05-29 10:09:09 +00:00
Yunus M
da1b09c479 refactor: replace antd Tabs with @signozhq/ui Tabs (#11392)
* refactor: replace antd Tabs with @signozhq/ui Tabs

Migrates Tabs usage from antd to the @signozhq/ui Tabs component across
dashboard settings, integration details, metrics application, pipelines,
trace detail, and workspace-locked pages. API differences (activeKey →
value, defaultActiveKey → defaultValue, TabsProps['items'] → TabItemProps[])
are updated to match the new component.

* refactor: enhance RouteTab component with new styling and functionality
2026-05-29 09:53:51 +00:00
39 changed files with 353 additions and 602 deletions

View File

@@ -2440,33 +2440,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
@@ -2849,6 +2822,28 @@ export interface CloudintegrationtypesPostableAgentCheckInDTO {
providerAccountId?: string;
}
export interface CloudintegrationtypesServiceDashboardDTO {
/**
* @type string
*/
description?: string;
/**
* @type string
*/
id?: string;
/**
* @type string
*/
title?: string;
}
export interface CloudintegrationtypesServiceAssetsDTO {
/**
* @type array,null
*/
dashboards: CloudintegrationtypesServiceDashboardDTO[] | null;
}
export interface CloudintegrationtypesSupportedSignalsDTO {
/**
* @type boolean
@@ -2860,13 +2855,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;
/**
@@ -2882,7 +2872,6 @@ export interface CloudintegrationtypesServiceDTO {
*/
overview: string;
supportedSignals: CloudintegrationtypesSupportedSignalsDTO;
telemetryCollectionStrategy: CloudintegrationtypesTelemetryCollectionStrategyDTO;
/**
* @type string
*/
@@ -3688,6 +3677,10 @@ export interface DashboardtypesCustomVariableSpecDTO {
customValue: string;
}
export interface DashboardtypesStorableDashboardDataDTO {
[key: string]: unknown;
}
export enum DashboardtypesSourceDTO {
user = 'user',
system = 'system',

View File

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

View File

@@ -1,10 +1,17 @@
import './RouteTab.styles.scss';
import {
generatePath,
matchPath,
useLocation,
useParams,
} from 'react-router-dom';
import { Tabs, TabsProps } from 'antd';
import {
TabsContent,
TabsList,
TabsRoot,
TabsTrigger,
} from '@signozhq/ui/tabs';
import HeaderRightSection from 'components/HeaderRightSection/HeaderRightSection';
import { RouteTabProps } from './types';
@@ -16,11 +23,13 @@ interface Params {
function RouteTab({
routes,
activeKey,
defaultActiveKey,
onChangeHandler,
history,
showRightSection,
...rest
}: RouteTabProps & TabsProps): JSX.Element {
showRightSection = true,
tabBarExtraContent,
hideTabBar = false,
}: RouteTabProps): JSX.Element {
const params = useParams<Params>();
const location = useLocation();
@@ -46,38 +55,38 @@ function RouteTab({
}
};
const items = routes.map(({ Component, name, route, key }) => ({
label: name,
key,
tabKey: route,
children: <Component />,
}));
const resolvedActiveKey = currentRoute?.key || activeKey;
const extraContent =
tabBarExtraContent ??
(showRightSection && (
<HeaderRightSection enableAnnouncements={false} enableShare enableFeedback />
));
return (
<Tabs
onChange={onChange}
destroyInactiveTabPane
activeKey={currentRoute?.key || activeKey}
defaultActiveKey={currentRoute?.key || activeKey}
animated
items={items}
tabBarExtraContent={
showRightSection && (
<HeaderRightSection
enableAnnouncements={false}
enableShare
enableFeedback
/>
)
}
{...rest}
/>
<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>
);
}
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,8 +10,11 @@ export type TabRoutes = {
export interface RouteTabProps {
routes: TabRoutes[];
activeKey: TabsProps['activeKey'];
activeKey: string | undefined;
defaultActiveKey?: string;
onChangeHandler?: (key: string) => void;
history: History<unknown>;
showRightSection: boolean;
showRightSection?: boolean;
tabBarExtraContent?: ReactNode;
hideTabBar?: boolean;
}

View File

@@ -1,70 +0,0 @@
.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 { Button, Tabs, Tooltip } from 'antd';
import { Tabs, TabItemProps } from '@signozhq/ui/tabs';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import { Braces, Globe, Table } from '@signozhq/icons';
import { useAppContext } from 'providers/App/App';
@@ -9,8 +9,6 @@ import DashboardVariableSettings from './DashboardVariableSettings';
import GeneralDashboardSettings from './General';
import PublicDashboardSetting from './PublicDashboard';
import './DashboardSettingsContent.styles.scss';
function DashboardSettings({
variablesSettingsTabHandle,
}: {
@@ -21,49 +19,26 @@ function DashboardSettings({
const enablePublicDashboard = isCloudUser || isEnterpriseSelfHostedUser;
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>
),
const publicDashboardItem: TabItemProps = {
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 = [
const items: TabItemProps[] = [
{
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}
@@ -73,7 +48,7 @@ function DashboardSettings({
...(enablePublicDashboard ? [publicDashboardItem] : []),
];
return <Tabs items={items} animated className="settings-tabs" />;
return <Tabs items={items} />;
}
export default DashboardSettings;

View File

@@ -55,7 +55,6 @@ const buildServiceDetailsResponse = (
},
},
},
telemetryCollectionStrategy: { aws: {} },
},
});

View File

@@ -1,6 +1,6 @@
/* eslint-disable sonarjs/cognitive-complexity */
import {
CloudintegrationtypesDashboardDTO,
CloudintegrationtypesServiceDashboardDTO,
CloudintegrationtypesServiceDTO,
} from 'api/generated/services/sigNoz.schemas';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
@@ -25,68 +25,67 @@ 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 isClickable = isInteractive && !!dashboard.id;
const dashboardUrl = dashboard.id ? `/dashboard/${dashboard.id}` : '';
const dashboardUrl = `/dashboard/${dashboard.id}`;
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;
}
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();
return (
<div
key={dashboard.id || `dashboard-${index}`}
className={`aws-service-dashboard-item ${
isClickable ? 'aws-service-dashboard-item-clickable' : ''
}`}
role={isClickable ? 'button' : undefined}
tabIndex={isClickable ? 0 : -1}
onClick={(event): void => {
if (!isClickable) {
return;
}
if (event.metaKey || event.ctrlKey) {
window.open(
withBasePath(dashboardUrl),
'_blank',
'noopener,noreferrer',
);
return;
}
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}
}}
onAuxClick={(event): void => {
if (!isClickable) {
return;
}
if (event.button === 1) {
window.open(
withBasePath(dashboardUrl),
'_blank',
'noopener,noreferrer',
);
}
}}
onKeyDown={(event): void => {
if (!isClickable) {
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}
</div>
</div>
</div>
</div>
);
})}
);
},
)}
</div>
</div>
);

View File

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

View File

@@ -168,45 +168,6 @@
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

@@ -1,26 +1,3 @@
.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, TabsProps } from 'antd';
import { Tabs, TabItemProps } from '@signozhq/ui/tabs';
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: TabsProps['items'] = [
const items: TabItemProps[] = [
{
label: TAB_KEY_VS_LABEL[MetricsApplicationTab.OVER_METRICS],
key: MetricsApplicationTab.OVER_METRICS,
@@ -53,9 +53,8 @@ function MetricsApplication(): JSX.Element {
<ApDexApplication />
<Tabs
items={items}
activeKey={activeKey}
value={activeKey}
className="service-route-tab"
destroyInactiveTabPane
onChange={onTabChange}
/>
</div>

View File

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

View File

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

View File

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

View File

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

View File

@@ -22,21 +22,6 @@ $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 { TabsProps } from 'antd';
import type { TabItemProps } from '@signozhq/ui/tabs';
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: TabsProps['items'] = [
const tabItems: TabItemProps[] = [
{
key: 'whyChooseSignoz',
label: t('whyChooseSignoz'),
@@ -398,8 +398,8 @@ export default function WorkspaceBlocked(): JSX.Element {
<div className="workspace-locked__tabs">
<Tabs
items={tabItems}
defaultActiveKey="youAreInGoodCompany"
onTabClick={handleTabClick}
defaultValue="youAreInGoodCompany"
onChange={handleTabClick}
/>
</div>
</>

View File

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

View File

@@ -403,7 +403,7 @@ func (m *module) buildFilterClause(ctx context.Context, filter *qbtypes.Filter,
return nil, err
}
if whereClause == nil || whereClause.WhereClause == nil {
if whereClause.IsEmpty() {
return sqlbuilder.NewWhereClause(), nil
}

View File

@@ -964,7 +964,7 @@ func (m *module) buildFilterClause(ctx context.Context, filter *qbtypes.Filter,
return nil, err
}
if whereClause == nil || whereClause.WhereClause == nil {
if whereClause.IsEmpty() {
return sqlbuilder.NewWhereClause(), nil
}

View File

@@ -510,9 +510,7 @@ func (s *store) buildFilterClause(ctx context.Context, filter qbtypes.Filter, st
if err != nil {
return nil, err
}
if prepared == nil || prepared.WhereClause == nil {
return nil, nil //nolint:nilnil
}
return prepared.WhereClause, nil
}

View File

@@ -206,7 +206,6 @@ func (v *exprVisitor) VisitFunctionExpr(fn *chparser.FunctionExpr) error {
dataType = telemetrytypes.FieldDataTypeFloat64
}
//
bodyJSONEnabled := v.flagger.BooleanOrEmpty(v.ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(valuer.UUID{}))
// Handle *If functions with predicate + values
@@ -231,8 +230,8 @@ func (v *exprVisitor) VisitFunctionExpr(fn *chparser.FunctionExpr) error {
if err != nil {
return err
}
// not possible for whereClause to be nil here but still adding a check.
if whereClause == nil {
// not possible for whereClause to be empty here but still adding a check.
if whereClause.IsEmpty() {
return errors.NewInvalidInputf(errors.CodeInvalidInput, "invalid predicate argument for %q: %q", name, origPred)
}

View File

@@ -96,8 +96,12 @@ type PreparedWhereClause struct {
WarningsDocURL string
}
func (p PreparedWhereClause) IsEmpty() bool {
return p.WhereClause == nil
}
// PrepareWhereClause generates a ClickHouse compatible WHERE clause from the filter query.
func PrepareWhereClause(query string, opts FilterExprVisitorOpts) (*PreparedWhereClause, error) {
func PrepareWhereClause(query string, opts FilterExprVisitorOpts) (PreparedWhereClause, error) {
// Setup the ANTLR parsing pipeline
input := antlr.NewInputStream(query)
@@ -148,7 +152,7 @@ func PrepareWhereClause(query string, opts FilterExprVisitorOpts) (*PreparedWher
}
}
return nil, combinedErrors.WithAdditional(additionals...).WithUrl(searchTroubleshootingGuideURL)
return PreparedWhereClause{}, combinedErrors.WithAdditional(additionals...).WithUrl(searchTroubleshootingGuideURL)
}
// Visit the parse tree with our ClickHouse visitor
@@ -166,18 +170,17 @@ func PrepareWhereClause(query string, opts FilterExprVisitorOpts) (*PreparedWher
if url == "" {
url = searchTroubleshootingGuideURL
}
return nil, combinedErrors.WithAdditional(visitor.errors...).WithUrl(url)
return PreparedWhereClause{}, combinedErrors.WithAdditional(visitor.errors...).WithUrl(url)
}
// Return nil so callers can skip the
// entire CTE/subquery rather than emitting WHERE clause that select all the rows
// Return empty where clause so callers can skip the WHERE clause
if cond == "" || cond == SkipConditionLiteral {
return nil, nil //nolint:nilnil
return PreparedWhereClause{WhereClause: nil, Warnings: visitor.warnings, WarningsDocURL: visitor.mainWarnURL}, nil
}
whereClause := sqlbuilder.NewWhereClause().AddWhereExpr(visitor.builder.Args, cond)
return &PreparedWhereClause{WhereClause: whereClause, Warnings: visitor.warnings, WarningsDocURL: visitor.mainWarnURL}, nil
return PreparedWhereClause{WhereClause: whereClause, Warnings: visitor.warnings, WarningsDocURL: visitor.mainWarnURL}, nil
}
// Visit dispatches to the specific visit method based on node type.

View File

@@ -874,7 +874,7 @@ func TestVisitComparison_AND(t *testing.T) {
assert.Equal(t, tt.wantErrRSB, err != nil, "resourceConditionBuilder: error expectation mismatch")
if err == nil {
var expr string
if result != nil {
if !result.IsEmpty() {
expr, _ = result.WhereClause.Build()
}
assert.Equal(t, tt.wantRSB, expr, "resourceConditionBuilder SQL mismatch:\n want: %s\n got: %s", tt.wantRSB, expr)
@@ -883,7 +883,7 @@ func TestVisitComparison_AND(t *testing.T) {
assert.Equal(t, tt.wantErrSB, err != nil, "conditionBuilder: error expectation mismatch")
if err == nil {
var expr string
if result != nil {
if !result.IsEmpty() {
expr, _ = result.WhereClause.Build()
}
assert.Equal(t, tt.wantSB, expr, "conditionBuilder SQL mismatch:\n want: %s\n got: %s", tt.wantSB, expr)
@@ -968,7 +968,7 @@ func TestVisitComparison_NOT(t *testing.T) {
assert.Equal(t, tt.wantErrRSB, err != nil, "resourceConditionBuilder: error expectation mismatch")
if err == nil {
var expr string
if result != nil {
if !result.IsEmpty() {
expr, _ = result.WhereClause.Build()
}
assert.Equal(t, tt.wantRSB, expr, "resourceConditionBuilder SQL mismatch:\n want: %s\n got: %s", tt.wantRSB, expr)
@@ -977,7 +977,7 @@ func TestVisitComparison_NOT(t *testing.T) {
assert.Equal(t, tt.wantErrSB, err != nil, "conditionBuilder: error expectation mismatch")
if err == nil {
var expr string
if result != nil {
if !result.IsEmpty() {
expr, _ = result.WhereClause.Build()
}
assert.Equal(t, tt.wantSB, expr, "conditionBuilder SQL mismatch:\n want: %s\n got: %s", tt.wantSB, expr)
@@ -1070,7 +1070,7 @@ func TestVisitComparison_OR(t *testing.T) {
assert.Equal(t, tt.wantErrRSB, err != nil, "resourceConditionBuilder: error expectation mismatch")
if err == nil {
var expr string
if result != nil {
if !result.IsEmpty() {
expr, _ = result.WhereClause.Build()
}
assert.Equal(t, tt.wantRSB, expr, "resourceConditionBuilder SQL mismatch:\n want: %s\n got: %s", tt.wantRSB, expr)
@@ -1079,7 +1079,7 @@ func TestVisitComparison_OR(t *testing.T) {
assert.Equal(t, tt.wantErrSB, err != nil, "conditionBuilder: error expectation mismatch")
if err == nil {
var expr string
if result != nil {
if !result.IsEmpty() {
expr, _ = result.WhereClause.Build()
}
assert.Equal(t, tt.wantSB, expr, "conditionBuilder SQL mismatch:\n want: %s\n got: %s", tt.wantSB, expr)
@@ -1151,7 +1151,7 @@ func TestVisitComparison_Precedence(t *testing.T) {
assert.Equal(t, tt.wantErrRSB, err != nil, "resourceConditionBuilder: error expectation mismatch")
if err == nil {
var expr string
if result != nil {
if !result.IsEmpty() {
expr, _ = result.WhereClause.Build()
}
assert.Equal(t, tt.wantRSB, expr, "resourceConditionBuilder SQL mismatch:\n want: %s\n got: %s", tt.wantRSB, expr)
@@ -1160,7 +1160,7 @@ func TestVisitComparison_Precedence(t *testing.T) {
assert.Equal(t, tt.wantErrSB, err != nil, "conditionBuilder: error expectation mismatch")
if err == nil {
var expr string
if result != nil {
if !result.IsEmpty() {
expr, _ = result.WhereClause.Build()
}
assert.Equal(t, tt.wantSB, expr, "conditionBuilder SQL mismatch:\n want: %s\n got: %s", tt.wantSB, expr)
@@ -1254,7 +1254,7 @@ func TestVisitComparison_Parens(t *testing.T) {
assert.Equal(t, tt.wantErrRSB, err != nil, "resourceConditionBuilder: error expectation mismatch")
if err == nil {
var expr string
if result != nil {
if !result.IsEmpty() {
expr, _ = result.WhereClause.Build()
}
assert.Equal(t, tt.wantRSB, expr, "resourceConditionBuilder SQL mismatch:\n want: %s\n got: %s", tt.wantRSB, expr)
@@ -1263,7 +1263,7 @@ func TestVisitComparison_Parens(t *testing.T) {
assert.Equal(t, tt.wantErrSB, err != nil, "conditionBuilder: error expectation mismatch")
if err == nil {
var expr string
if result != nil {
if !result.IsEmpty() {
expr, _ = result.WhereClause.Build()
}
assert.Equal(t, tt.wantSB, expr, "conditionBuilder SQL mismatch:\n want: %s\n got: %s", tt.wantSB, expr)
@@ -1409,7 +1409,7 @@ func TestVisitComparison_FullText(t *testing.T) {
assert.Equal(t, tt.wantErrRSB, err != nil, "resourceConditionBuilder: error expectation mismatch")
if err == nil {
var expr string
if result != nil {
if !result.IsEmpty() {
expr, _ = result.WhereClause.Build()
}
assert.Equal(t, tt.wantRSB, expr, "resourceConditionBuilder SQL mismatch:\n want: %s\n got: %s", tt.wantRSB, expr)
@@ -1418,7 +1418,7 @@ func TestVisitComparison_FullText(t *testing.T) {
assert.Equal(t, tt.wantErrSB, err != nil, "conditionBuilder: error expectation mismatch")
if err == nil {
var expr string
if result != nil {
if !result.IsEmpty() {
expr, _ = result.WhereClause.Build()
}
assert.Equal(t, tt.wantSB, expr, "conditionBuilder SQL mismatch:\n want: %s\n got: %s", tt.wantSB, expr)
@@ -1518,7 +1518,7 @@ func TestVisitComparison_AllVariable(t *testing.T) {
assert.Equal(t, tt.wantErrRSB, err != nil, "resourceConditionBuilder: error expectation mismatch")
if err == nil {
var expr string
if result != nil {
if !result.IsEmpty() {
expr, _ = result.WhereClause.Build()
}
assert.Equal(t, tt.wantRSB, expr, "resourceConditionBuilder SQL mismatch:\n want: %s\n got: %s", tt.wantRSB, expr)
@@ -1527,7 +1527,7 @@ func TestVisitComparison_AllVariable(t *testing.T) {
assert.Equal(t, tt.wantErrSB, err != nil, "conditionBuilder: error expectation mismatch")
if err == nil {
var expr string
if result != nil {
if !result.IsEmpty() {
expr, _ = result.WhereClause.Build()
}
assert.Equal(t, tt.wantSB, expr, "conditionBuilder SQL mismatch:\n want: %s\n got: %s", tt.wantSB, expr)
@@ -1597,7 +1597,7 @@ func TestVisitComparison_FunctionCalls(t *testing.T) {
assert.Equal(t, tt.wantErrRSB, err != nil, "resourceConditionBuilder: error expectation mismatch")
if err == nil {
var expr string
if result != nil {
if !result.IsEmpty() {
expr, _ = result.WhereClause.Build()
}
assert.Equal(t, tt.wantRSB, expr, "resourceConditionBuilder SQL mismatch:\n want: %s\n got: %s", tt.wantRSB, expr)
@@ -1606,7 +1606,7 @@ func TestVisitComparison_FunctionCalls(t *testing.T) {
assert.Equal(t, tt.wantErrSB, err != nil, "conditionBuilder: error expectation mismatch")
if err == nil {
var expr string
if result != nil {
if !result.IsEmpty() {
expr, _ = result.WhereClause.Build()
}
assert.Equal(t, tt.wantSB, expr, "conditionBuilder SQL mismatch:\n want: %s\n got: %s", tt.wantSB, expr)
@@ -1666,7 +1666,7 @@ func TestVisitComparison_UnknownKeys(t *testing.T) {
assert.Equal(t, tt.wantErrRSB, err != nil, "resourceConditionBuilder: error expectation mismatch")
if err == nil {
var expr string
if result != nil {
if !result.IsEmpty() {
expr, _ = result.WhereClause.Build()
}
assert.Equal(t, tt.wantRSB, expr, "resourceConditionBuilder SQL mismatch:\n want: %s\n got: %s", tt.wantRSB, expr)
@@ -1675,7 +1675,7 @@ func TestVisitComparison_UnknownKeys(t *testing.T) {
assert.Equal(t, tt.wantErrSB, err != nil, "conditionBuilder: error expectation mismatch")
if err == nil {
var expr string
if result != nil {
if !result.IsEmpty() {
expr, _ = result.WhereClause.Build()
}
assert.Equal(t, tt.wantSB, expr, "conditionBuilder SQL mismatch:\n want: %s\n got: %s", tt.wantSB, expr)
@@ -1758,7 +1758,7 @@ func TestVisitComparison_SkippableLiteralValues(t *testing.T) {
assert.Equal(t, tt.wantErrRSB, err != nil, "resourceConditionBuilder: error expectation mismatch")
if err == nil {
var expr string
if result != nil {
if !result.IsEmpty() {
expr, _ = result.WhereClause.Build()
}
assert.Equal(t, tt.wantRSB, expr, "resourceConditionBuilder SQL mismatch:\n want: %s\n got: %s", tt.wantRSB, expr)
@@ -1767,7 +1767,7 @@ func TestVisitComparison_SkippableLiteralValues(t *testing.T) {
assert.Equal(t, tt.wantErrSB, err != nil, "conditionBuilder: error expectation mismatch")
if err == nil {
var expr string
if result != nil {
if !result.IsEmpty() {
expr, _ = result.WhereClause.Build()
}
assert.Equal(t, tt.wantSB, expr, "conditionBuilder SQL mismatch:\n want: %s\n got: %s", tt.wantSB, expr)

View File

@@ -282,12 +282,10 @@ func (b *auditQueryStatementBuilder) buildListQuery(
finalArgs := querybuilder.PrependArgs(cteArgs, mainArgs)
stmt := &qbtypes.Statement{
Query: finalSQL,
Args: finalArgs,
}
if preparedWhereClause != nil {
stmt.Warnings = preparedWhereClause.Warnings
stmt.WarningsDocURL = preparedWhereClause.WarningsDocURL
Query: finalSQL,
Args: finalArgs,
Warnings: preparedWhereClause.Warnings,
WarningsDocURL: preparedWhereClause.WarningsDocURL,
}
return stmt, nil
@@ -422,12 +420,10 @@ func (b *auditQueryStatementBuilder) buildTimeSeriesQuery(
}
stmt := &qbtypes.Statement{
Query: finalSQL,
Args: finalArgs,
}
if preparedWhereClause != nil {
stmt.Warnings = preparedWhereClause.Warnings
stmt.WarningsDocURL = preparedWhereClause.WarningsDocURL
Query: finalSQL,
Args: finalArgs,
Warnings: preparedWhereClause.Warnings,
WarningsDocURL: preparedWhereClause.WarningsDocURL,
}
return stmt, nil
@@ -526,12 +522,10 @@ func (b *auditQueryStatementBuilder) buildScalarQuery(
finalArgs := querybuilder.PrependArgs(cteArgs, mainArgs)
stmt := &qbtypes.Statement{
Query: finalSQL,
Args: finalArgs,
}
if preparedWhereClause != nil {
stmt.Warnings = preparedWhereClause.Warnings
stmt.WarningsDocURL = preparedWhereClause.WarningsDocURL
Query: finalSQL,
Args: finalArgs,
Warnings: preparedWhereClause.Warnings,
WarningsDocURL: preparedWhereClause.WarningsDocURL,
}
return stmt, nil
@@ -544,8 +538,8 @@ func (b *auditQueryStatementBuilder) addFilterCondition(
query qbtypes.QueryBuilderQuery[qbtypes.LogAggregation],
keys map[string][]*telemetrytypes.TelemetryFieldKey,
variables map[string]qbtypes.VariableItem,
) (*querybuilder.PreparedWhereClause, error) {
var preparedWhereClause *querybuilder.PreparedWhereClause
) (querybuilder.PreparedWhereClause, error) {
var preparedWhereClause querybuilder.PreparedWhereClause
var err error
if query.Filter != nil && query.Filter.Expression != "" {
@@ -564,11 +558,11 @@ func (b *auditQueryStatementBuilder) addFilterCondition(
})
if err != nil {
return nil, err
return preparedWhereClause, err
}
}
if preparedWhereClause != nil {
if !preparedWhereClause.IsEmpty() {
sb.AddWhereClause(preparedWhereClause.WhereClause)
}

View File

@@ -173,7 +173,7 @@ func TestFilterExprLogsBodyJSON(t *testing.T) {
return
}
if clause == nil {
if clause.IsEmpty() {
t.Errorf("Expected clause for query: %s\n", tc.query)
return
}

View File

@@ -2403,7 +2403,7 @@ func TestFilterExprLogs(t *testing.T) {
return
}
if clause == nil {
if clause.IsEmpty() {
t.Errorf("Expected clause for query: %s\n", tc.query)
return
}
@@ -2524,7 +2524,7 @@ func TestFilterExprLogsConflictNegation(t *testing.T) {
return
}
if clause == nil {
if clause.IsEmpty() {
t.Errorf("Expected clause for query: %s\n", tc.query)
return
}

View File

@@ -348,12 +348,10 @@ func (b *logQueryStatementBuilder) buildListQuery(
finalArgs := querybuilder.PrependArgs(cteArgs, mainArgs)
stmt := &qbtypes.Statement{
Query: finalSQL,
Args: finalArgs,
}
if preparedWhereClause != nil {
stmt.Warnings = preparedWhereClause.Warnings
stmt.WarningsDocURL = preparedWhereClause.WarningsDocURL
Query: finalSQL,
Args: finalArgs,
Warnings: preparedWhereClause.Warnings,
WarningsDocURL: preparedWhereClause.WarningsDocURL,
}
return stmt, nil
@@ -506,12 +504,10 @@ func (b *logQueryStatementBuilder) buildTimeSeriesQuery(
}
stmt := &qbtypes.Statement{
Query: finalSQL,
Args: finalArgs,
}
if preparedWhereClause != nil {
stmt.Warnings = preparedWhereClause.Warnings
stmt.WarningsDocURL = preparedWhereClause.WarningsDocURL
Query: finalSQL,
Args: finalArgs,
Warnings: preparedWhereClause.Warnings,
WarningsDocURL: preparedWhereClause.WarningsDocURL,
}
return stmt, nil
@@ -627,12 +623,10 @@ func (b *logQueryStatementBuilder) buildScalarQuery(
finalArgs := querybuilder.PrependArgs(cteArgs, mainArgs)
stmt := &qbtypes.Statement{
Query: finalSQL,
Args: finalArgs,
}
if preparedWhereClause != nil {
stmt.Warnings = preparedWhereClause.Warnings
stmt.WarningsDocURL = preparedWhereClause.WarningsDocURL
Query: finalSQL,
Args: finalArgs,
Warnings: preparedWhereClause.Warnings,
WarningsDocURL: preparedWhereClause.WarningsDocURL,
}
return stmt, nil
@@ -646,9 +640,9 @@ func (b *logQueryStatementBuilder) addFilterCondition(
query qbtypes.QueryBuilderQuery[qbtypes.LogAggregation],
keys map[string][]*telemetrytypes.TelemetryFieldKey,
variables map[string]qbtypes.VariableItem,
) (*querybuilder.PreparedWhereClause, error) {
) (querybuilder.PreparedWhereClause, error) {
var preparedWhereClause *querybuilder.PreparedWhereClause
var preparedWhereClause querybuilder.PreparedWhereClause
var err error
// TODO(Tushar): thread orgID here to evaluate correctly
bodyJSONEnabled := b.fl.BooleanOrEmpty(ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(valuer.UUID{}))
@@ -671,11 +665,11 @@ func (b *logQueryStatementBuilder) addFilterCondition(
})
if err != nil {
return nil, err
return preparedWhereClause, err
}
}
if preparedWhereClause != nil {
if !preparedWhereClause.IsEmpty() {
sb.AddWhereClause(preparedWhereClause.WhereClause)
}

View File

@@ -237,8 +237,10 @@ func TestStatementBuilderListQuery(t *testing.T) {
name string
requestType qbtypes.RequestType
query qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]
variables map[string]qbtypes.VariableItem
expected qbtypes.Statement
expectedErr error
expectWarn bool
}{
{
name: "default list",
@@ -312,6 +314,22 @@ func TestStatementBuilderListQuery(t *testing.T) {
},
expectedErr: nil,
},
{
name: "filter skips entirely but emits LIKE-without-wildcards warning",
requestType: qbtypes.RequestTypeRaw,
query: qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]{
Signal: telemetrytypes.SignalLogs,
Filter: &qbtypes.Filter{
Expression: "message LIKE 'plain' OR message IN $env",
},
Limit: 10,
},
variables: map[string]qbtypes.VariableItem{
"env": {Type: qbtypes.DynamicVariableType, Value: "__all__"},
},
expectedErr: nil,
expectWarn: true,
},
}
ctx := context.Background()
@@ -340,16 +358,20 @@ func TestStatementBuilderListQuery(t *testing.T) {
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
q, err := statementBuilder.Build(ctx, 1747947419000, 1747983448000, c.requestType, c.query, nil)
q, err := statementBuilder.Build(ctx, 1747947419000, 1747983448000, c.requestType, c.query, c.variables)
if c.expectedErr != nil {
require.Error(t, err)
require.Contains(t, err.Error(), c.expectedErr.Error())
} else {
require.NoError(t, err)
require.Equal(t, c.expected.Query, q.Query)
require.Equal(t, c.expected.Args, q.Args)
require.Equal(t, c.expected.Warnings, q.Warnings)
if c.expectWarn {
require.NotEmpty(t, q.Warnings)
} else {
require.Equal(t, c.expected.Query, q.Query)
require.Equal(t, c.expected.Args, q.Args)
require.Equal(t, c.expected.Warnings, q.Warnings)
}
}
})
}

View File

@@ -1424,7 +1424,7 @@ func (t *telemetryMetaStore) getRelatedValues(ctx context.Context, fieldValueSel
if err != nil {
t.logger.WarnContext(ctx, "error parsing existing query for related values", errors.Attr(err))
}
if whereClause != nil {
if !whereClause.IsEmpty() {
sb.AddWhereClause(whereClause.WhereClause)
}
}

View File

@@ -111,7 +111,7 @@ func (b *meterQueryStatementBuilder) buildTemporalAggDeltaFastPath(
keys map[string][]*telemetrytypes.TelemetryFieldKey,
variables map[string]qbtypes.VariableItem,
) (string, []any, error) {
var filterWhere *querybuilder.PreparedWhereClause
var filterWhere querybuilder.PreparedWhereClause
var err error
stepSec := int64(query.StepInterval.Seconds())
@@ -161,7 +161,7 @@ func (b *meterQueryStatementBuilder) buildTemporalAggDeltaFastPath(
return "", nil, err
}
}
if filterWhere != nil {
if !filterWhere.IsEmpty() {
sb.AddWhereClause(filterWhere.WhereClause)
}
@@ -195,7 +195,7 @@ func (b *meterQueryStatementBuilder) buildTemporalAggDelta(
keys map[string][]*telemetrytypes.TelemetryFieldKey,
variables map[string]qbtypes.VariableItem,
) (string, []any, error) {
var filterWhere *querybuilder.PreparedWhereClause
var filterWhere querybuilder.PreparedWhereClause
var err error
stepSec := int64(query.StepInterval.Seconds())
@@ -250,7 +250,7 @@ func (b *meterQueryStatementBuilder) buildTemporalAggDelta(
return "", nil, err
}
}
if filterWhere != nil {
if !filterWhere.IsEmpty() {
sb.AddWhereClause(filterWhere.WhereClause)
}
@@ -273,7 +273,7 @@ func (b *meterQueryStatementBuilder) buildTemporalAggCumulativeOrUnspecified(
keys map[string][]*telemetrytypes.TelemetryFieldKey,
variables map[string]qbtypes.VariableItem,
) (string, []any, error) {
var filterWhere *querybuilder.PreparedWhereClause
var filterWhere querybuilder.PreparedWhereClause
var err error
stepSec := int64(query.StepInterval.Seconds())
@@ -320,7 +320,7 @@ func (b *meterQueryStatementBuilder) buildTemporalAggCumulativeOrUnspecified(
return "", nil, err
}
}
if filterWhere != nil {
if !filterWhere.IsEmpty() {
baseSb.AddWhereClause(filterWhere.WhereClause)
}

View File

@@ -264,7 +264,7 @@ func (b *MetricQueryStatementBuilder) buildTimeSeriesCTE(
) (string, []any, error) {
sb := sqlbuilder.NewSelectBuilder()
var preparedWhereClause *querybuilder.PreparedWhereClause
var preparedWhereClause querybuilder.PreparedWhereClause
var err error
if query.Filter != nil && query.Filter.Expression != "" {
@@ -311,7 +311,7 @@ func (b *MetricQueryStatementBuilder) buildTimeSeriesCTE(
sb.EQ("__normalized", false),
)
if preparedWhereClause != nil {
if !preparedWhereClause.IsEmpty() {
sb.AddWhereClause(preparedWhereClause.WhereClause)
}

View File

@@ -163,7 +163,7 @@ func (b *resourceFilterStatementBuilder[T]) addConditions(
if err != nil {
return false, err
}
if filterWhereClause == nil {
if filterWhereClause.IsEmpty() {
// this means all conditions evaluated to no-op (non-resource fields, unknown keys, skipped full-text/functions)
// the CTE would select all fingerprints, so skip it entirely
return true, nil

View File

@@ -353,12 +353,10 @@ func (b *traceQueryStatementBuilder) buildListQuery(
finalArgs := querybuilder.PrependArgs(cteArgs, mainArgs)
stmt := &qbtypes.Statement{
Query: finalSQL,
Args: finalArgs,
}
if preparedWhereClause != nil {
stmt.Warnings = preparedWhereClause.Warnings
stmt.WarningsDocURL = preparedWhereClause.WarningsDocURL
Query: finalSQL,
Args: finalArgs,
Warnings: preparedWhereClause.Warnings,
WarningsDocURL: preparedWhereClause.WarningsDocURL,
}
return stmt, nil
@@ -471,12 +469,10 @@ func (b *traceQueryStatementBuilder) buildTraceQuery(
finalArgs := querybuilder.PrependArgs(cteArgs, mainArgs)
stmt := &qbtypes.Statement{
Query: finalSQL,
Args: finalArgs,
}
if preparedWhereClause != nil {
stmt.Warnings = preparedWhereClause.Warnings
stmt.WarningsDocURL = preparedWhereClause.WarningsDocURL
Query: finalSQL,
Args: finalArgs,
Warnings: preparedWhereClause.Warnings,
WarningsDocURL: preparedWhereClause.WarningsDocURL,
}
return stmt, nil
@@ -622,12 +618,10 @@ func (b *traceQueryStatementBuilder) buildTimeSeriesQuery(
}
stmt := &qbtypes.Statement{
Query: finalSQL,
Args: finalArgs,
}
if preparedWhereClause != nil {
stmt.Warnings = preparedWhereClause.Warnings
stmt.WarningsDocURL = preparedWhereClause.WarningsDocURL
Query: finalSQL,
Args: finalArgs,
Warnings: preparedWhereClause.Warnings,
WarningsDocURL: preparedWhereClause.WarningsDocURL,
}
return stmt, nil
@@ -740,12 +734,10 @@ func (b *traceQueryStatementBuilder) buildScalarQuery(
finalArgs := querybuilder.PrependArgs(cteArgs, mainArgs)
stmt := &qbtypes.Statement{
Query: finalSQL,
Args: finalArgs,
}
if preparedWhereClause != nil {
stmt.Warnings = preparedWhereClause.Warnings
stmt.WarningsDocURL = preparedWhereClause.WarningsDocURL
Query: finalSQL,
Args: finalArgs,
Warnings: preparedWhereClause.Warnings,
WarningsDocURL: preparedWhereClause.WarningsDocURL,
}
return stmt, nil
@@ -759,9 +751,9 @@ func (b *traceQueryStatementBuilder) addFilterCondition(
query qbtypes.QueryBuilderQuery[qbtypes.TraceAggregation],
keys map[string][]*telemetrytypes.TelemetryFieldKey,
variables map[string]qbtypes.VariableItem,
) (*querybuilder.PreparedWhereClause, error) {
) (querybuilder.PreparedWhereClause, error) {
var preparedWhereClause *querybuilder.PreparedWhereClause
var preparedWhereClause querybuilder.PreparedWhereClause
var err error
if query.Filter != nil && query.Filter.Expression != "" {
@@ -779,11 +771,11 @@ func (b *traceQueryStatementBuilder) addFilterCondition(
})
if err != nil {
return nil, err
return preparedWhereClause, err
}
}
if preparedWhereClause != nil {
if !preparedWhereClause.IsEmpty() {
sb.AddWhereClause(preparedWhereClause.WhereClause)
}

View File

@@ -267,7 +267,7 @@ func (b *traceOperatorCTEBuilder) buildQueryCTE(ctx context.Context, queryName s
b.stmtBuilder.logger.ErrorContext(ctx, "Failed to prepare where clause", errors.Attr(err), slog.String("filter", query.Filter.Expression))
return "", err
}
if filterWhereClause != nil {
if !filterWhereClause.IsEmpty() {
b.stmtBuilder.logger.DebugContext(ctx, "Adding where clause", slog.Any("where_clause", filterWhereClause.WhereClause))
sb.AddWhereClause(filterWhereClause.WhereClause)
} else {

View File

@@ -15,6 +15,7 @@ from fixtures.querier import (
build_group_by_field,
build_logs_aggregation,
build_order_by,
build_raw_query,
build_scalar_query,
find_named_result,
index_series_by_label,
@@ -2625,3 +2626,43 @@ def test_logs_aggregation_filter_by_trace_id(
orphan_count, orphan_warnings = _count(narrow_start_ms, now_ms, orphan_trace_id)
assert orphan_count == 1, f"Expected count=1 for orphan trace_id aggregation, got {orphan_count} — query may have been incorrectly short-circuited"
assert not any(outside_range_msg in m for m in orphan_warnings), f"Did not expect outside-range warning for orphan trace_id, got {orphan_warnings}"
def test_logs_list_ambigous_warnings(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_logs: Callable[[list[Logs]], None],
) -> None:
insert_logs(
[
Logs(
timestamp=datetime.now(tz=UTC) - timedelta(seconds=1),
resources={
"service.name": "java",
},
attributes={
"service.name": "java",
},
body="This is a log message, coming from a java application",
severity_text="DEBUG",
),
]
)
response = make_query_request(
signoz,
get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD),
start_ms=int((datetime.now(tz=UTC) - timedelta(minutes=1)).timestamp() * 1000),
end_ms=int(datetime.now(tz=UTC).timestamp() * 1000),
request_type="raw",
queries=[build_raw_query(name="A", signal="logs", filter_expression='service.name = "java"')],
)
assert response.status_code == HTTPStatus.OK
assert response.json()["status"] == "success"
warning = response.json()["data"].get("warning", None)
assert warning is not None
assert warning["message"] == "Encountered warnings"
assert len(warning.get("warnings")) > 0
assert any(["ambiguous" in w["message"] for w in warning.get("warnings")])