Compare commits

..

5 Commits

Author SHA1 Message Date
ahrefabhi
8f0afe5448 chore: disabled default props rule in eslint 2025-07-24 15:01:43 +05:30
ahrefabhi
76837b9ac8 chore: minor linter fix 2025-07-24 14:41:10 +05:30
ahrefabhi
d097fc7c4e chore: linter fixes 2025-07-24 14:26:26 +05:30
ahrefabhi
649ff2f0f7 chore: moved switch to routes in router api migeration 2025-07-24 11:15:33 +05:30
ahrefabhi
288fa9a1d7 refactor: updated routing components 2025-07-23 12:04:12 +05:30
53 changed files with 514 additions and 1012 deletions

View File

@@ -54,6 +54,7 @@ module.exports = {
},
],
'react/prop-types': 'off',
'react/require-default-props': 'off',
'@typescript-eslint/explicit-function-return-type': 'error',
'@typescript-eslint/no-var-requires': 'error',
'react/no-array-index-key': 'error',

View File

@@ -258,7 +258,6 @@
"cookie": "^0.7.1",
"serialize-javascript": "6.0.2",
"prismjs": "1.30.0",
"got": "11.8.5",
"stylus": "0.0.1-security"
"got": "11.8.5"
}
}

View File

@@ -12,6 +12,7 @@ import { LOCALSTORAGE } from 'constants/localStorage';
import ROUTES from 'constants/routes';
import AppLayout from 'container/AppLayout';
import { KeyboardHotkeysProvider } from 'hooks/hotkeys/useKeyboardHotkeys';
import { useAppRoutes } from 'hooks/useAppRoutes';
import { useThemeConfig } from 'hooks/useDarkMode';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import { NotificationProvider } from 'hooks/useNotifications';
@@ -27,19 +28,15 @@ import { DashboardProvider } from 'providers/Dashboard/Dashboard';
import { ErrorModalProvider } from 'providers/ErrorModalProvider';
import { QueryBuilderProvider } from 'providers/QueryBuilder';
import { Suspense, useCallback, useEffect, useState } from 'react';
import { Route, Router, Switch } from 'react-router-dom';
import { CompatRouter } from 'react-router-dom-v5-compat';
import { Router } from 'react-router-dom';
import { CompatRouter, Route, Routes } from 'react-router-dom-v5-compat';
import { LicenseStatus } from 'types/api/licensesV3/getActive';
import { Userpilot } from 'userpilot';
import { extractDomain } from 'utils/app';
import { Home } from './pageComponents';
import PrivateRoute from './Private';
import defaultRoutes, {
AppRoutes,
LIST_LICENSES,
SUPPORT_ROUTE,
} from './routes';
import { AppRoutes, LIST_LICENSES, SUPPORT_ROUTE } from './routes';
function App(): JSX.Element {
const themeConfig = useThemeConfig();
@@ -57,6 +54,7 @@ function App(): JSX.Element {
featureFlags,
org,
} = useAppContext();
const { routes: defaultRoutes } = useAppRoutes();
const [routes, setRoutes] = useState<AppRoutes[]>(defaultRoutes);
const { hostname, pathname } = window.location;
@@ -213,6 +211,7 @@ function App(): JSX.Element {
}
setRoutes(updatedRoutes);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
isLoggedInState,
user,
@@ -363,6 +362,17 @@ function App(): JSX.Element {
}
}
const renderRoutes = (routes: AppRoutes[]): JSX.Element[] | null => {
if (!routes || routes.length === 0) {
return null;
}
return routes.map(({ path, element: Component, children }: AppRoutes) => (
<Route key={`${path}`} path={path as string} element={<Component />}>
{children && renderRoutes(children)}
</Route>
));
};
return (
<Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}>
<ConfigProvider theme={themeConfig}>
@@ -379,18 +389,11 @@ function App(): JSX.Element {
<AlertRuleProvider>
<AppLayout>
<Suspense fallback={<Spinner size="large" tip="Loading..." />}>
<Switch>
{routes.map(({ path, component, exact }) => (
<Route
key={`${path}`}
exact={exact}
path={path}
component={component}
/>
))}
<Route exact path="/" component={Home} />
<Route path="*" component={NotFound} />
</Switch>
<Routes>
{routes && renderRoutes(routes)}
<Route path="/" element={<Home />} />
<Route path="*" element={<NotFound />} />
</Routes>
</Suspense>
</AppLayout>
</AlertRuleProvider>

View File

@@ -128,11 +128,23 @@ export const AlertOverview = Loadable(
);
export const CreateAlertChannelAlerts = Loadable(
() => import(/* webpackChunkName: "Create Channels" */ 'pages/Settings'),
() =>
import(/* webpackChunkName: "Create Channels" */ 'pages/CreateAlertChannels'),
);
export const AllAlertChannels = Loadable(
() => import(/* webpackChunkName: "All Channels" */ 'pages/Settings'),
export const AllAlertChannelsPage = Loadable(
() =>
import(
/* webpackChunkName: "All Alert Channels" */ 'pages/AllAlertChannelsSettings'
),
);
export const ChannelsEditPage = Loadable(
() =>
import(
/* webpackChunkName: "Channels Edit Page" */
'pages/ChannelsEdit'
),
);
export const AllErrors = Loadable(
@@ -147,25 +159,40 @@ export const StatusPage = Loadable(
() => import(/* webpackChunkName: "All Status" */ 'pages/Status'),
);
export const OrganizationSettings = Loadable(
() => import(/* webpackChunkName: "All Settings" */ 'pages/Settings'),
export const OrganizationSettingsPage = Loadable(
() =>
import(
/* webpackChunkName: "Organization Settings" */ 'pages/OrganizationSettings'
),
);
export const IngestionSettings = Loadable(
() => import(/* webpackChunkName: "Ingestion Settings" */ 'pages/Settings'),
() =>
import(
/* webpackChunkName: "Ingestion Settings" */ 'pages/IngestionSettings'
),
);
export const APIKeys = Loadable(
() => import(/* webpackChunkName: "All Settings" */ 'pages/Settings'),
() => import(/* webpackChunkName: "API Keys Settings" */ 'pages/APIKeys'),
);
export const MySettings = Loadable(
() => import(/* webpackChunkName: "All MySettings" */ 'pages/Settings'),
() => import(/* webpackChunkName: "All MySettings" */ 'pages/MySettings'),
);
export const GeneralSettings = Loadable(
() =>
import(
/* webpackChunkName: "General MySettings" */ 'pages/Settings/GeneralSettings'
),
);
export const CustomDomainSettings = Loadable(
() =>
import(/* webpackChunkName: "Custom Domain Settings" */ 'pages/Settings'),
import(
/* webpackChunkName: "Custom Domain Settings" */ 'pages/CustomDomainSettings'
),
);
export const Logs = Loadable(
@@ -217,7 +244,7 @@ export const LogsIndexToFields = Loadable(
);
export const BillingPage = Loadable(
() => import(/* webpackChunkName: "BillingPage" */ 'pages/Settings'),
() => import(/* webpackChunkName: "BillingPage" */ 'pages/Billing'),
);
export const SupportPage = Loadable(
@@ -244,7 +271,7 @@ export const WorkspaceAccessRestricted = Loadable(
);
export const ShortcutsPage = Loadable(
() => import(/* webpackChunkName: "ShortcutsPage" */ 'pages/Settings'),
() => import(/* webpackChunkName: "ShortcutsPage" */ 'pages/Shortcuts'),
);
export const InstalledIntegrations = Loadable(

View File

@@ -1,21 +1,28 @@
import ROUTES from 'constants/routes';
import MessagingQueues from 'pages/MessagingQueues';
import { RouteProps } from 'react-router-dom';
import React from 'react';
import { RouteProps } from 'react-router-dom-v5-compat';
import {
AlertHistory,
AlertOverview,
AllAlertChannels,
AllAlertChannelsPage,
AllErrors,
APIKeys,
ApiMonitoring,
BillingPage,
ChannelsEditPage,
CreateAlertChannelAlerts,
CreateNewAlerts,
CustomDomainSettings,
DashboardPage,
DashboardWidget,
EditRulesPage,
ErrorDetails,
GeneralSettings,
Home,
InfrastructureMonitoring,
IngestionSettings,
InstalledIntegrations,
LicensePage,
ListAllALertsPage,
@@ -26,10 +33,12 @@ import {
LogsIndexToFields,
LogsSaveViews,
MetricsExplorer,
MySettings,
NewDashboardPage,
OldLogsExplorer,
Onboarding,
OnboardingV2,
OrganizationSettingsPage,
OrgOnboarding,
PasswordReset,
PipelinePage,
@@ -38,6 +47,7 @@ import {
ServicesTablePage,
ServiceTopLevelOperationsPage,
SettingsPage,
ShortcutsPage,
SignupPage,
SomethingWentWrong,
StatusPage,
@@ -57,387 +67,388 @@ import {
const routes: AppRoutes[] = [
{
component: SignupPage,
element: SignupPage,
path: ROUTES.SIGN_UP,
exact: true,
isPrivate: false,
key: 'SIGN_UP',
},
{
path: ROUTES.GET_STARTED,
exact: false,
component: Onboarding,
element: Onboarding,
isPrivate: true,
key: 'GET_STARTED',
},
{
path: ROUTES.GET_STARTED_WITH_CLOUD,
exact: false,
component: OnboardingV2,
element: OnboardingV2,
isPrivate: true,
key: 'GET_STARTED_WITH_CLOUD',
},
{
path: ROUTES.HOME,
exact: true,
component: Home,
element: Home,
isPrivate: true,
key: 'HOME',
},
{
path: ROUTES.ONBOARDING,
exact: false,
component: OrgOnboarding,
element: OrgOnboarding,
isPrivate: true,
key: 'ONBOARDING',
},
{
component: LogsIndexToFields,
element: LogsIndexToFields,
path: ROUTES.LOGS_INDEX_FIELDS,
exact: true,
isPrivate: true,
key: 'LOGS_INDEX_FIELDS',
},
{
component: ServicesTablePage,
element: ServicesTablePage,
path: ROUTES.APPLICATION,
exact: true,
isPrivate: true,
key: 'APPLICATION',
},
{
path: ROUTES.SERVICE_METRICS,
exact: true,
component: ServiceMetricsPage,
element: ServiceMetricsPage,
isPrivate: true,
key: 'SERVICE_METRICS',
},
{
path: ROUTES.SERVICE_TOP_LEVEL_OPERATIONS,
exact: true,
component: ServiceTopLevelOperationsPage,
element: ServiceTopLevelOperationsPage,
isPrivate: true,
key: 'SERVICE_TOP_LEVEL_OPERATIONS',
},
{
path: ROUTES.SERVICE_MAP,
component: ServiceMapPage,
element: ServiceMapPage,
isPrivate: true,
exact: true,
key: 'SERVICE_MAP',
},
{
path: ROUTES.LOGS_SAVE_VIEWS,
component: LogsSaveViews,
element: LogsSaveViews,
isPrivate: true,
exact: true,
key: 'LOGS_SAVE_VIEWS',
},
{
path: ROUTES.TRACE_DETAIL,
exact: true,
component: TraceDetail,
element: TraceDetail,
isPrivate: true,
key: 'TRACE_DETAIL',
},
{
path: ROUTES.SETTINGS,
exact: false,
component: SettingsPage,
element: SettingsPage,
isPrivate: true,
key: 'SETTINGS',
children: [
{
path: '', // Route for /settings
element: GeneralSettings,
isPrivate: true,
key: 'SETTINGS',
},
{
path: 'my-settings',
element: MySettings,
isPrivate: true,
key: 'MY_SETTINGS',
},
{
path: 'custom-domain-settings',
element: CustomDomainSettings,
isPrivate: true,
key: 'CUSTOM_DOMAIN_SETTINGS',
},
{
path: 'org-settings',
element: OrganizationSettingsPage,
isPrivate: true,
key: 'ORG_SETTINGS',
},
{
path: 'channels',
element: AllAlertChannelsPage,
isPrivate: true,
key: 'ALL_CHANNELS',
},
{
path: 'ingestion-settings',
element: IngestionSettings,
isPrivate: true,
key: 'INGESTION_SETTINGS',
},
{
path: 'api-keys',
element: APIKeys,
isPrivate: true,
key: 'API_KEYS',
},
{
path: 'billing',
element: BillingPage,
isPrivate: true,
key: 'BILLING',
},
{
path: 'shortcuts',
element: ShortcutsPage,
isPrivate: true,
key: 'SHORTCUTS',
},
{
path: 'channels/new',
element: CreateAlertChannelAlerts,
isPrivate: true,
key: 'CHANNELS_NEW',
},
{
path: 'channels/edit/:channelId',
element: ChannelsEditPage,
isPrivate: true,
key: 'CHANNELS_EDIT',
},
],
},
{
path: ROUTES.USAGE_EXPLORER,
exact: true,
component: UsageExplorerPage,
element: UsageExplorerPage,
isPrivate: true,
key: 'USAGE_EXPLORER',
},
{
path: ROUTES.ALL_DASHBOARD,
exact: true,
component: DashboardPage,
element: DashboardPage,
isPrivate: true,
key: 'ALL_DASHBOARD',
},
{
path: ROUTES.DASHBOARD,
exact: true,
component: NewDashboardPage,
element: NewDashboardPage,
isPrivate: true,
key: 'DASHBOARD',
},
{
path: ROUTES.DASHBOARD_WIDGET,
exact: true,
component: DashboardWidget,
element: DashboardWidget,
isPrivate: true,
key: 'DASHBOARD_WIDGET',
},
{
path: ROUTES.EDIT_ALERTS,
exact: true,
component: EditRulesPage,
element: EditRulesPage,
isPrivate: true,
key: 'EDIT_ALERTS',
},
{
path: ROUTES.LIST_ALL_ALERT,
exact: true,
component: ListAllALertsPage,
element: ListAllALertsPage,
isPrivate: true,
key: 'LIST_ALL_ALERT',
},
{
path: ROUTES.ALERTS_NEW,
exact: true,
component: CreateNewAlerts,
element: CreateNewAlerts,
isPrivate: true,
key: 'ALERTS_NEW',
},
{
path: ROUTES.ALERT_HISTORY,
exact: true,
component: AlertHistory,
element: AlertHistory,
isPrivate: true,
key: 'ALERT_HISTORY',
},
{
path: ROUTES.ALERT_OVERVIEW,
exact: true,
component: AlertOverview,
element: AlertOverview,
isPrivate: true,
key: 'ALERT_OVERVIEW',
},
{
path: ROUTES.TRACE,
exact: true,
component: TraceFilter,
element: TraceFilter,
isPrivate: true,
key: 'TRACE',
},
{
path: ROUTES.TRACES_EXPLORER,
exact: true,
component: TracesExplorer,
element: TracesExplorer,
isPrivate: true,
key: 'TRACES_EXPLORER',
},
{
path: ROUTES.TRACES_SAVE_VIEWS,
exact: true,
component: TracesSaveViews,
element: TracesSaveViews,
isPrivate: true,
key: 'TRACES_SAVE_VIEWS',
},
{
path: ROUTES.TRACES_FUNNELS,
exact: true,
component: TracesFunnels,
element: TracesFunnels,
isPrivate: true,
key: 'TRACES_FUNNELS',
},
{
path: ROUTES.TRACES_FUNNELS_DETAIL,
exact: true,
component: TracesFunnelDetails,
element: TracesFunnelDetails,
isPrivate: true,
key: 'TRACES_FUNNELS_DETAIL',
},
{
path: ROUTES.CHANNELS_NEW,
exact: true,
component: CreateAlertChannelAlerts,
isPrivate: true,
key: 'CHANNELS_NEW',
},
{
path: ROUTES.ALL_CHANNELS,
exact: true,
component: AllAlertChannels,
isPrivate: true,
key: 'ALL_CHANNELS',
},
{
path: ROUTES.ALL_ERROR,
exact: true,
isPrivate: true,
component: AllErrors,
element: AllErrors,
key: 'ALL_ERROR',
},
{
path: ROUTES.ERROR_DETAIL,
exact: true,
component: ErrorDetails,
element: ErrorDetails,
isPrivate: true,
key: 'ERROR_DETAIL',
},
{
path: ROUTES.VERSION,
exact: true,
component: StatusPage,
element: StatusPage,
isPrivate: true,
key: 'VERSION',
},
{
path: ROUTES.LOGS,
exact: true,
component: Logs,
element: Logs,
key: 'LOGS',
isPrivate: true,
},
{
path: ROUTES.LOGS_EXPLORER,
exact: true,
component: LogsExplorer,
element: LogsExplorer,
key: 'LOGS_EXPLORER',
isPrivate: true,
},
{
path: ROUTES.OLD_LOGS_EXPLORER,
exact: true,
component: OldLogsExplorer,
element: OldLogsExplorer,
key: 'OLD_LOGS_EXPLORER',
isPrivate: true,
},
{
path: ROUTES.LIVE_LOGS,
exact: true,
component: LiveLogs,
element: LiveLogs,
key: 'LIVE_LOGS',
isPrivate: true,
},
{
path: ROUTES.LOGS_PIPELINES,
exact: true,
component: PipelinePage,
element: PipelinePage,
key: 'LOGS_PIPELINES',
isPrivate: true,
},
{
path: ROUTES.LOGIN,
exact: true,
component: Login,
element: Login,
isPrivate: false,
key: 'LOGIN',
},
{
path: ROUTES.UN_AUTHORIZED,
exact: true,
component: UnAuthorized,
element: UnAuthorized,
key: 'UN_AUTHORIZED',
isPrivate: true,
},
{
path: ROUTES.PASSWORD_RESET,
exact: true,
component: PasswordReset,
element: PasswordReset,
key: 'PASSWORD_RESET',
isPrivate: false,
},
{
path: ROUTES.SOMETHING_WENT_WRONG,
exact: true,
component: SomethingWentWrong,
element: SomethingWentWrong,
key: 'SOMETHING_WENT_WRONG',
isPrivate: false,
},
{
path: ROUTES.WORKSPACE_LOCKED,
exact: true,
component: WorkspaceBlocked,
element: WorkspaceBlocked,
isPrivate: true,
key: 'WORKSPACE_LOCKED',
},
{
path: ROUTES.WORKSPACE_SUSPENDED,
exact: true,
component: WorkspaceSuspended,
element: WorkspaceSuspended,
isPrivate: true,
key: 'WORKSPACE_SUSPENDED',
},
{
path: ROUTES.WORKSPACE_ACCESS_RESTRICTED,
exact: true,
component: WorkspaceAccessRestricted,
element: WorkspaceAccessRestricted,
isPrivate: true,
key: 'WORKSPACE_ACCESS_RESTRICTED',
},
{
path: ROUTES.INTEGRATIONS,
exact: true,
component: InstalledIntegrations,
element: InstalledIntegrations,
isPrivate: true,
key: 'INTEGRATIONS',
},
{
path: ROUTES.MESSAGING_QUEUES_KAFKA,
exact: true,
component: MessagingQueues,
element: MessagingQueues,
key: 'MESSAGING_QUEUES_KAFKA',
isPrivate: true,
},
{
path: ROUTES.MESSAGING_QUEUES_CELERY_TASK,
exact: true,
component: MessagingQueues,
element: MessagingQueues,
key: 'MESSAGING_QUEUES_CELERY_TASK',
isPrivate: true,
},
{
path: ROUTES.MESSAGING_QUEUES_OVERVIEW,
exact: true,
component: MessagingQueues,
element: MessagingQueues,
key: 'MESSAGING_QUEUES_OVERVIEW',
isPrivate: true,
},
{
path: ROUTES.MESSAGING_QUEUES_KAFKA_DETAIL,
exact: true,
component: MessagingQueues,
element: MessagingQueues,
key: 'MESSAGING_QUEUES_KAFKA_DETAIL',
isPrivate: true,
},
{
path: ROUTES.INFRASTRUCTURE_MONITORING_HOSTS,
exact: true,
component: InfrastructureMonitoring,
element: InfrastructureMonitoring,
key: 'INFRASTRUCTURE_MONITORING_HOSTS',
isPrivate: true,
},
{
path: ROUTES.INFRASTRUCTURE_MONITORING_KUBERNETES,
exact: true,
component: InfrastructureMonitoring,
element: InfrastructureMonitoring,
key: 'INFRASTRUCTURE_MONITORING_KUBERNETES',
isPrivate: true,
},
{
path: ROUTES.METRICS_EXPLORER,
exact: true,
component: MetricsExplorer,
element: MetricsExplorer,
key: 'METRICS_EXPLORER',
isPrivate: true,
},
{
path: ROUTES.METRICS_EXPLORER_EXPLORER,
exact: true,
component: MetricsExplorer,
element: MetricsExplorer,
key: 'METRICS_EXPLORER_EXPLORER',
isPrivate: true,
},
{
path: ROUTES.METRICS_EXPLORER_VIEWS,
exact: true,
component: MetricsExplorer,
element: MetricsExplorer,
key: 'METRICS_EXPLORER_VIEWS',
isPrivate: true,
},
{
path: ROUTES.API_MONITORING,
exact: true,
component: ApiMonitoring,
element: ApiMonitoring,
key: 'API_MONITORING',
isPrivate: true,
},
@@ -445,16 +456,14 @@ const routes: AppRoutes[] = [
export const SUPPORT_ROUTE: AppRoutes = {
path: ROUTES.SUPPORT,
exact: true,
component: SupportPage,
element: SupportPage,
key: 'SUPPORT',
isPrivate: true,
};
export const LIST_LICENSES: AppRoutes = {
path: ROUTES.LIST_LICENSES,
exact: true,
component: LicensePage,
element: LicensePage,
isPrivate: true,
key: 'LIST_LICENSES',
};
@@ -487,11 +496,13 @@ export const ROUTES_NOT_TO_BE_OVERRIDEN: string[] = [
];
export interface AppRoutes {
component: RouteProps['component'];
element:
| React.ComponentType<any>
| React.LazyExoticComponent<React.ComponentType<any>>;
path: RouteProps['path'];
exact: RouteProps['exact'];
isPrivate: boolean;
key: keyof typeof ROUTES;
children?: AppRoutes[];
}
export default routes;

View File

@@ -2,6 +2,7 @@
import './LogDetails.styles.scss';
import { Color, Spacing } from '@signozhq/design-tokens';
import Convert from 'ansi-to-html';
import { Button, Divider, Drawer, Radio, Tooltip, Typography } from 'antd';
import { RadioChangeEvent } from 'antd/lib';
import cx from 'classnames';
@@ -16,10 +17,12 @@ import JSONView from 'container/LogDetailedView/JsonView';
import Overview from 'container/LogDetailedView/Overview';
import {
aggregateAttributesResourcesToString,
getSanitizedLogBody,
escapeHtml,
removeEscapeCharacters,
unescapeString,
} from 'container/LogDetailedView/utils';
import { useOptionsMenu } from 'container/OptionsMenu';
import dompurify from 'dompurify';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useNotifications } from 'hooks/useNotifications';
@@ -43,11 +46,14 @@ import { AppState } from 'store/reducers';
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource, StringOperators } from 'types/common/queryBuilder';
import { GlobalReducer } from 'types/reducer/globalTime';
import { FORBID_DOM_PURIFY_TAGS } from 'utils/app';
import { RESOURCE_KEYS, VIEW_TYPES, VIEWS } from './constants';
import { LogDetailProps } from './LogDetail.interfaces';
import QueryBuilderSearchWrapper from './QueryBuilderSearchWrapper';
const convert = new Convert();
function LogDetail({
log,
onClose,
@@ -112,7 +118,11 @@ function LogDetail({
const htmlBody = useMemo(
() => ({
__html: getSanitizedLogBody(log?.body || '', { shouldEscapeHtml: true }),
__html: convert.toHtml(
dompurify.sanitize(unescapeString(escapeHtml(log?.body || '')), {
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
}),
),
}),
[log?.body],
);

View File

@@ -1,13 +1,15 @@
import './ListLogView.styles.scss';
import { blue } from '@ant-design/colors';
import Convert from 'ansi-to-html';
import { Typography } from 'antd';
import cx from 'classnames';
import LogDetail from 'components/LogDetail';
import { VIEW_TYPES } from 'components/LogDetail/constants';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import { getSanitizedLogBody } from 'container/LogDetailedView/utils';
import { escapeHtml, unescapeString } from 'container/LogDetailedView/utils';
import { FontSize } from 'container/OptionsMenu/types';
import dompurify from 'dompurify';
import { useActiveLog } from 'hooks/logs/useActiveLog';
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
import { useIsDarkMode } from 'hooks/useDarkMode';
@@ -18,6 +20,7 @@ import { useCallback, useMemo, useState } from 'react';
// interfaces
import { IField } from 'types/api/logs/fields';
import { ILog } from 'types/api/logs/log';
import { FORBID_DOM_PURIFY_TAGS } from 'utils/app';
// components
import AddToQueryHOC, { AddToQueryHOCProps } from '../AddToQueryHOC';
@@ -34,6 +37,8 @@ import {
} from './styles';
import { isValidLogField } from './util';
const convert = new Convert();
interface LogFieldProps {
fieldKey: string;
fieldValue: string;
@@ -52,7 +57,11 @@ function LogGeneralField({
}: LogFieldProps): JSX.Element {
const html = useMemo(
() => ({
__html: getSanitizedLogBody(fieldValue, { shouldEscapeHtml: true }),
__html: convert.toHtml(
dompurify.sanitize(unescapeString(escapeHtml(fieldValue)), {
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
}),
),
}),
[fieldValue],
);

View File

@@ -1,11 +1,13 @@
import './RawLogView.styles.scss';
import Convert from 'ansi-to-html';
import { DrawerProps } from 'antd';
import LogDetail from 'components/LogDetail';
import { VIEW_TYPES, VIEWS } from 'components/LogDetail/constants';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import { getSanitizedLogBody } from 'container/LogDetailedView/utils';
import { escapeHtml, unescapeString } from 'container/LogDetailedView/utils';
import LogsExplorerContext from 'container/LogsExplorerContext';
import dompurify from 'dompurify';
import { useActiveLog } from 'hooks/logs/useActiveLog';
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
// hooks
@@ -21,6 +23,7 @@ import {
useMemo,
useState,
} from 'react';
import { FORBID_DOM_PURIFY_TAGS } from 'utils/app';
import LogLinesActionButtons from '../LogLinesActionButtons/LogLinesActionButtons';
import LogStateIndicator from '../LogStateIndicator/LogStateIndicator';
@@ -29,6 +32,8 @@ import { getLogIndicatorType } from '../LogStateIndicator/utils';
import { RawLogContent, RawLogViewContainer } from './styles';
import { RawLogViewProps } from './types';
const convert = new Convert();
function RawLogView({
isActiveLog,
isReadOnly,
@@ -171,7 +176,11 @@ function RawLogView({
const html = useMemo(
() => ({
__html: getSanitizedLogBody(text, { shouldEscapeHtml: true }),
__html: convert.toHtml(
dompurify.sanitize(unescapeString(escapeHtml(text)), {
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
}),
),
}),
[text],
);

View File

@@ -1,14 +1,17 @@
import './useTableView.styles.scss';
import Convert from 'ansi-to-html';
import { Typography } from 'antd';
import { ColumnsType } from 'antd/es/table';
import cx from 'classnames';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import { getSanitizedLogBody } from 'container/LogDetailedView/utils';
import { unescapeString } from 'container/LogDetailedView/utils';
import dompurify from 'dompurify';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { FlatLogData } from 'lib/logs/flatLogData';
import { useTimezone } from 'providers/Timezone';
import { useMemo } from 'react';
import { FORBID_DOM_PURIFY_TAGS } from 'utils/app';
import LogStateIndicator from '../LogStateIndicator/LogStateIndicator';
import { getLogIndicatorTypeForTable } from '../LogStateIndicator/utils';
@@ -24,6 +27,8 @@ import {
UseTableViewResult,
} from './types';
const convert = new Convert();
export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
const {
logs,
@@ -144,7 +149,11 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
children: (
<TableBodyContent
dangerouslySetInnerHTML={{
__html: getSanitizedLogBody(field as string),
__html: convert.toHtml(
dompurify.sanitize(unescapeString(field as string), {
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
}),
),
}}
fontSize={fontSize}
linesPerRow={linesPerRow}

View File

@@ -47,5 +47,4 @@ export enum QueryParams {
destination = 'destination',
kindString = 'kindString',
tab = 'tab',
thresholds = 'thresholds',
}

View File

@@ -27,18 +27,23 @@ const ROUTES = {
ALERTS_NEW: '/alerts/new',
ALERT_HISTORY: '/alerts/history',
ALERT_OVERVIEW: '/alerts/overview',
ALL_CHANNELS: '/settings/channels',
CHANNELS_NEW: '/settings/channels/new',
CHANNELS_EDIT: '/settings/channels/edit/:channelId',
ALL_ERROR: '/exceptions',
ERROR_DETAIL: '/error-detail',
VERSION: '/status',
// Setting routes
SETTINGS: '/settings',
MY_SETTINGS: '/settings/my-settings',
ORG_SETTINGS: '/settings/org-settings',
CUSTOM_DOMAIN_SETTINGS: '/settings/custom-domain-settings',
API_KEYS: '/settings/api-keys',
INGESTION_SETTINGS: '/settings/ingestion-settings',
ALL_CHANNELS: '/settings/channels',
CHANNELS_NEW: '/settings/channels/new',
CHANNELS_EDIT: '/settings/channels/edit/:channelId',
BILLING: '/settings/billing',
SHORTCUTS: '/settings/shortcuts',
ALL_ERROR: '/exceptions',
ERROR_DETAIL: '/error-detail',
VERSION: '/status',
SOMETHING_WENT_WRONG: '/something-went-wrong',
UN_AUTHORIZED: '/un-authorized',
NOT_FOUND: '/not-found',
@@ -52,7 +57,6 @@ const ROUTES = {
LIST_LICENSES: '/licenses',
LOGS_INDEX_FIELDS: '/logs-explorer/index-fields',
TRACE_EXPLORER: '/trace-explorer',
BILLING: '/settings/billing',
SUPPORT: '/support',
LOGS_SAVE_VIEWS: '/logs/saved-views',
TRACES_SAVE_VIEWS: '/traces/saved-views',
@@ -60,7 +64,6 @@ const ROUTES = {
TRACES_FUNNELS_DETAIL: '/traces/funnels/:funnelId',
WORKSPACE_LOCKED: '/workspace-locked',
WORKSPACE_SUSPENDED: '/workspace-suspended',
SHORTCUTS: '/settings/shortcuts',
INTEGRATIONS: '/integrations',
MESSAGING_QUEUES_BASE: '/messaging-queues',
MESSAGING_QUEUES_KAFKA: '/messaging-queues/kafka',
@@ -80,4 +83,17 @@ const ROUTES = {
HOME_PAGE: '/',
} as const;
export const SETTINGS_NESTED_ROUTES = {
MY_SETTINGS: 'my-settings',
ORG_SETTINGS: 'org-settings',
CUSTOM_DOMAIN_SETTINGS: 'custom-domain-settings',
API_KEYS: 'api-keys',
INGESTION_SETTINGS: 'ingestion-settings',
ALL_CHANNELS: 'channels',
CHANNELS_NEW: 'channels/new',
CHANNELS_EDIT: 'channels/edit/:channelId',
BILLING: 'billing',
SHORTCUTS: 'shortcuts',
};
export default ROUTES;

View File

@@ -571,7 +571,7 @@ function CreateAlertChannels({
}
interface CreateAlertChannelsProps {
preType: ChannelType;
preType?: ChannelType;
}
export default CreateAlertChannels;

View File

@@ -1,5 +1,4 @@
import ROUTES from 'constants/routes';
import * as usePrefillAlertConditions from 'container/FormAlertRules/usePrefillAlertConditions';
import CreateAlertPage from 'pages/CreateAlert';
import { MemoryRouter, Route } from 'react-router-dom';
import { act, fireEvent, render } from 'tests/test-utils';
@@ -34,15 +33,6 @@ jest.mock('hooks/useSafeNavigate', () => ({
}),
}));
jest
.spyOn(usePrefillAlertConditions, 'usePrefillAlertConditions')
.mockReturnValue({
matchType: '3',
op: '1',
target: 100,
targetUnit: 'rpm',
});
let mockWindowOpen: jest.Mock;
window.ResizeObserver =

View File

@@ -1,5 +1,4 @@
import ROUTES from 'constants/routes';
import * as usePrefillAlertConditions from 'container/FormAlertRules/usePrefillAlertConditions';
import CreateAlertPage from 'pages/CreateAlert';
import { MemoryRouter, Route } from 'react-router-dom';
import { act, fireEvent, render } from 'tests/test-utils';
@@ -42,15 +41,6 @@ jest.mock('hooks/useSafeNavigate', () => ({
safeNavigate: jest.fn(),
}),
}));
jest
.spyOn(usePrefillAlertConditions, 'usePrefillAlertConditions')
.mockReturnValue({
matchType: '3',
op: '1',
target: 100,
targetUnit: 'rpm',
});
describe('Anomaly Alert Documentation Redirection', () => {
let mockWindowOpen: jest.Mock;

View File

@@ -3,7 +3,6 @@ import logEvent from 'api/common/logEvent';
import { ENTITY_VERSION_V4 } from 'constants/app';
import { QueryParams } from 'constants/query';
import FormAlertRules, { AlertDetectionTypes } from 'container/FormAlertRules';
import { ThresholdProps } from 'container/NewWidget/RightContainer/Threshold/types';
import { useGetCompositeQueryParam } from 'hooks/queryBuilder/useGetCompositeQueryParam';
import history from 'lib/history';
import { useEffect, useState } from 'react';
@@ -33,12 +32,6 @@ function CreateRules(): JSX.Element {
? AlertTypes.ANOMALY_BASED_ALERT
: queryParams.get(QueryParams.alertType);
const { thresholds } = (location.state as {
thresholds: ThresholdProps[];
}) || {
thresholds: null,
};
const compositeQuery = useGetCompositeQueryParam();
function getAlertTypeFromDataSource(): AlertTypes | null {
if (!compositeQuery) {
@@ -103,9 +96,7 @@ function CreateRules(): JSX.Element {
}
const generatedUrl = `${location.pathname}?${queryParams.toString()}`;
history.replace(generatedUrl, {
thresholds,
});
history.replace(generatedUrl);
};
useEffect(() => {

View File

@@ -390,7 +390,7 @@ function RuleOptions({
<Space direction="vertical" size="large">
{ruleType !== AlertDetectionTypes.ANOMALY_DETECTION_ALERT && (
<Space direction="horizontal" align="center">
<Form.Item noStyle>
<Form.Item noStyle name={['condition', 'target']}>
<InputNumber
addonBefore={t('field_threshold')}
value={alertDef?.condition?.target}

View File

@@ -1,125 +0,0 @@
import { renderHook } from '@testing-library/react';
import { usePrefillAlertConditions } from '../usePrefillAlertConditions';
const TEST_MAPPINGS = {
op: {
'>': '1',
'<': '2',
'=': '3',
},
matchType: {
avg: '3',
sum: '4',
},
};
jest.mock('react-router-dom-v5-compat', () => {
const mockThreshold1 = {
index: '0d11f426-a02e-48da-867c-b79c6ef1ff06',
isEditEnabled: false,
keyIndex: 1,
selectedGraph: 'graph',
thresholdColor: 'Orange',
thresholdFormat: 'Text',
thresholdLabel: 'Caution',
thresholdOperator: '>',
thresholdTableOptions: 'A',
thresholdUnit: 'rpm',
thresholdValue: 800,
};
const mockThreshold2 = {
index: 'edbe8ef2-fa54-4cb9-b343-7afe883bb714',
isEditEnabled: false,
keyIndex: 0,
selectedGraph: 'graph',
thresholdColor: 'Red',
thresholdFormat: 'Text',
thresholdLabel: 'Danger',
thresholdOperator: '<',
thresholdTableOptions: 'A',
thresholdUnit: 'rpm',
thresholdValue: 900,
};
return {
...jest.requireActual('react-router-dom-v5-compat'),
useLocation: jest.fn().mockReturnValue({
state: {
thresholds: [mockThreshold1, mockThreshold2],
},
}),
};
});
const mockStagedQuery = {
builder: {
queryData: [
{
reduceTo: 'avg',
},
],
},
};
describe('usePrefillAlertConditions', () => {
it('returns the correct matchType for a single query', () => {
const { result } = renderHook(() =>
usePrefillAlertConditions(mockStagedQuery as any),
);
expect(result.current.matchType).toBe(TEST_MAPPINGS.matchType.avg);
});
it('returns null matchType for a single query with unsupported time aggregation', () => {
const { result } = renderHook(() =>
usePrefillAlertConditions({
builder: { queryData: [{ reduceTo: 'p90' }] },
} as any),
);
expect(result.current.matchType).toBe(null);
});
it('returns the correct matchType for multiple queries with same time aggregation', () => {
const { result } = renderHook(() =>
usePrefillAlertConditions({
builder: {
queryData: [
{
reduceTo: 'avg',
},
{
reduceTo: 'avg',
},
],
},
} as any),
);
expect(result.current.matchType).toBe(TEST_MAPPINGS.matchType.avg);
});
it('returns null matchType for multiple queries with different time aggregation', () => {
const { result } = renderHook(() =>
usePrefillAlertConditions({
builder: {
queryData: [
{
reduceTo: 'avg',
},
{
reduceTo: 'sum',
},
],
},
} as any),
);
expect(result.current.matchType).toBe(null);
});
it('returns the correct op, target, targetUnit from the higher priority threshold for multiple thresholds', () => {
const { result } = renderHook(() =>
usePrefillAlertConditions(mockStagedQuery as any),
);
expect(result.current.op).toBe(TEST_MAPPINGS.op['<']);
expect(result.current.target).toBe(900);
expect(result.current.targetUnit).toBe('rpm');
});
});

View File

@@ -57,7 +57,6 @@ import {
StepContainer,
StepHeading,
} from './styles';
import { usePrefillAlertConditions } from './usePrefillAlertConditions';
import { getSelectedQueryOptions } from './utils';
export enum AlertDetectionTypes {
@@ -114,9 +113,6 @@ function FormAlertRules({
handleSetConfig,
redirectWithQueryBuilderData,
} = useQueryBuilder();
const { matchType, op, target, targetUnit } = usePrefillAlertConditions(
stagedQuery,
);
useEffect(() => {
handleSetConfig(panelType || PANEL_TYPES.TIME_SERIES, dataSource);
@@ -270,13 +266,6 @@ function FormAlertRules({
...initialValue,
broadcastToAll: !broadcastToSpecificChannels,
ruleType,
condition: {
...initialValue.condition,
matchType: matchType || initialValue.condition.matchType,
op: op || initialValue.condition.op,
target: target || initialValue.condition.target,
targetUnit: targetUnit || initialValue.condition.targetUnit,
},
});
setDetectionMethod(ruleType);

View File

@@ -1,87 +0,0 @@
import { ThresholdProps } from 'container/NewWidget/RightContainer/Threshold/types';
import { useMemo } from 'react';
import { useLocation } from 'react-router-dom-v5-compat';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
const THRESHOLD_COLORS_SORTING_ORDER = ['Red', 'Orange', 'Green', 'Blue'];
export const usePrefillAlertConditions = (
stagedQuery: Query | null,
): {
matchType: string | null;
op: string | null;
target: number | undefined;
targetUnit: string | undefined;
} => {
const location = useLocation();
// Extract and set match type
const reduceTo = useMemo(() => {
if (!stagedQuery) return null;
const isSameTimeAggregation = stagedQuery.builder.queryData.every(
(queryData) =>
queryData.reduceTo === stagedQuery.builder.queryData[0].reduceTo,
);
return isSameTimeAggregation
? stagedQuery.builder.queryData[0].reduceTo
: null;
}, [stagedQuery]);
const matchType = useMemo(() => {
switch (reduceTo) {
case 'avg':
return '3';
case 'sum':
return '4';
default:
return null;
}
}, [reduceTo]);
// Extract and set threshold operator, value and unit
const threshold = useMemo(() => {
const { thresholds } = (location.state as {
thresholds: ThresholdProps[];
}) || {
thresholds: null,
};
if (!thresholds || thresholds.length === 0) return null;
const sortedThresholds = thresholds.sort((a, b) => {
const aIndex = THRESHOLD_COLORS_SORTING_ORDER.indexOf(
a.thresholdColor || '',
);
const bIndex = THRESHOLD_COLORS_SORTING_ORDER.indexOf(
b.thresholdColor || '',
);
return aIndex - bIndex;
});
return sortedThresholds[0];
}, [location.state]);
const thresholdOperator = useMemo(() => {
const op = threshold?.thresholdOperator;
switch (op) {
case '>':
case '>=':
return '1';
case '<':
case '<=':
return '2';
case '=':
return '3';
default:
return null;
}
}, [threshold]);
const thresholdUnit = useMemo(() => threshold?.thresholdUnit, [threshold]);
const thresholdValue = useMemo(() => threshold?.thresholdValue, [threshold]);
return {
matchType,
op: thresholdOperator,
target: thresholdValue,
targetUnit: thresholdUnit,
};
};

View File

@@ -2,6 +2,7 @@
import './TableViewActions.styles.scss';
import { Color } from '@signozhq/design-tokens';
import Convert from 'ansi-to-html';
import { Button, Popover, Spin, Tooltip, Tree } from 'antd';
import GroupByIcon from 'assets/CustomIcons/GroupByIcon';
import cx from 'classnames';
@@ -11,19 +12,22 @@ import { OPERATORS } from 'constants/queryBuilder';
import ROUTES from 'constants/routes';
import { RESTRICTED_SELECTED_FIELDS } from 'container/LogsFilters/config';
import { MetricsType } from 'container/MetricsApplication/constant';
import dompurify from 'dompurify';
import { ArrowDownToDot, ArrowUpFromDot, Ellipsis } from 'lucide-react';
import { useTimezone } from 'providers/Timezone';
import React, { useCallback, useMemo, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { FORBID_DOM_PURIFY_TAGS } from 'utils/app';
import { DataType } from '../TableView';
import {
escapeHtml,
filterKeyForField,
getFieldAttributes,
getSanitizedLogBody,
parseFieldValue,
removeEscapeCharacters,
unescapeString,
} from '../utils';
import useAsyncJSONProcessing from './useAsyncJSONProcessing';
@@ -47,6 +51,8 @@ interface ITableViewActionsProps {
) => () => void;
}
const convert = new Convert();
// Memoized Tree Component
const MemoizedTree = React.memo<{ treeData: any[] }>(({ treeData }) => (
<Tree
@@ -140,7 +146,11 @@ export default function TableViewActions(
if (record.field !== 'body') return { __html: '' };
return {
__html: getSanitizedLogBody(record.value, { shouldEscapeHtml: true }),
__html: convert.toHtml(
dompurify.sanitize(unescapeString(escapeHtml(record.value)), {
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
}),
),
};
}, [record.field, record.value]);

View File

@@ -4,8 +4,6 @@ import { useEffect, useRef, useState } from 'react';
import { jsonToDataNodes, recursiveParseJSON } from '../utils';
const MAX_BODY_BYTES = 100 * 1024; // 100 KB
// Hook for async JSON processing
const useAsyncJSONProcessing = (
value: string,
@@ -33,12 +31,6 @@ const useAsyncJSONProcessing = (
return (): void => {};
}
// Avoid processing if the json is too large
const byteSize = new Blob([value]).size;
if (byteSize > MAX_BODY_BYTES) {
return (): void => {};
}
processingRef.current = true;
setJsonState({ isLoading: true, treeData: null, error: null });

View File

@@ -1,11 +1,6 @@
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import {
flattenObject,
getDataTypes,
getSanitizedLogBody,
recursiveParseJSON,
} from './utils';
import { flattenObject, getDataTypes, recursiveParseJSON } from './utils';
describe('recursiveParseJSON', () => {
it('should return an empty object if the input is not valid JSON', () => {
@@ -190,146 +185,3 @@ describe('Get Data Types utils', () => {
expect(getDataTypes([2.5, 3, 1])).toBe(DataTypes.ArrayFloat64);
});
});
describe('getSanitizedLogBody', () => {
it('should return sanitized HTML with default options (shouldEscapeHtml: false)', () => {
const input = '<script>alert("xss")</script>Hello World';
const result = getSanitizedLogBody(input);
// Should remove script tags and return sanitized HTML
expect(result).not.toContain('<script>');
expect(result).toContain('Hello World');
});
it('should escape HTML when shouldEscapeHtml is true', () => {
const input = '<script>alert("xss")</script>Hello World';
const result = getSanitizedLogBody(input, { shouldEscapeHtml: true });
// Should escape HTML entities
expect(result).toContain('&lt;script&gt;');
expect(result).toContain('&lt;/script&gt;');
expect(result).toContain('Hello World');
});
it('should handle ANSI color codes correctly', () => {
const input = '\x1b[32mHello\x1b[0m World';
const result = getSanitizedLogBody(input);
// Should convert ANSI codes to HTML spans
expect(result).toContain('<span');
expect(result).toContain('Hello');
expect(result).toContain('World');
});
it('should handle unescaped strings correctly', () => {
const input = 'Hello\\nWorld\\tTab';
const result = getSanitizedLogBody(input);
// Should unescape the string
expect(result).toContain('Hello');
expect(result).toContain('World');
});
it('should handle empty string input', () => {
const result = getSanitizedLogBody('');
expect(result).toBe('');
});
it('should handle null/undefined input gracefully', () => {
const result1 = getSanitizedLogBody(null as any);
const result2 = getSanitizedLogBody(undefined as any);
expect(result1).toBe('');
expect(result2).toBe('');
});
it('should handle special characters and entities', () => {
const input = '& < > " \' &amp; &lt; &gt;';
const result = getSanitizedLogBody(input, { shouldEscapeHtml: true });
// Should escape HTML entities
expect(result).toContain('&amp;');
expect(result).toContain('&lt;');
expect(result).toContain('&gt;');
expect(result).toContain('&quot;');
});
it('should handle complex HTML with mixed content', () => {
const input =
'<div><p>Hello <strong>World</strong></p><script>alert("xss")</script></div>';
const result = getSanitizedLogBody(input);
// Should keep safe HTML but remove script tags
expect(result).toContain('<div>');
expect(result).toContain('<p>');
expect(result).toContain('<strong>');
expect(result).toContain('Hello');
expect(result).toContain('World');
expect(result).not.toContain('<script>');
});
it('should handle JSON-like strings', () => {
const input = '{"key": "value", "nested": {"inner": "data"}}';
const result = getSanitizedLogBody(input);
// Should preserve JSON structure
expect(result).toContain('{');
expect(result).toContain('}');
expect(result).toContain('key');
expect(result).toContain('value');
});
it('should handle URLs and links', () => {
const input = 'Visit https://example.com for more info';
const result = getSanitizedLogBody(input);
// Should preserve the URL text
expect(result).toContain('https://example.com');
expect(result).toContain('Visit');
expect(result).toContain('info');
});
it('should handle error cases and return fallback', () => {
// Mock console.error to avoid noise in tests
const originalConsoleError = console.error;
console.error = jest.fn();
// Create a scenario that might cause an error
const input = 'Normal text';
const result = getSanitizedLogBody(input);
// Should return the processed text normally
expect(result).toContain('Normal text');
// Restore console.error
console.error = originalConsoleError;
});
it('should handle different escape scenarios correctly', () => {
const input1 = '<div>Hello</div>';
const result1 = getSanitizedLogBody(input1, { shouldEscapeHtml: false });
const result2 = getSanitizedLogBody(input1, { shouldEscapeHtml: true });
// Without escaping, should keep HTML structure
expect(result1).toContain('<div>');
expect(result1).toContain('</div>');
// With escaping, should escape HTML entities
expect(result2).toContain('&lt;div&gt;');
expect(result2).toContain('&lt;/div&gt;');
});
it('should handle ANSI codes with HTML escaping', () => {
const input = '\x1b[32mHello\x1b[0m <script>World</script>';
const result = getSanitizedLogBody(input, { shouldEscapeHtml: true });
// Should handle both ANSI codes and HTML escaping
expect(result).toContain('<span');
expect(result).toContain('Hello');
expect(result).toContain('&lt;script&gt;');
expect(result).toContain('World');
expect(result).toContain('&lt;/script&gt;');
});
});
//

View File

@@ -1,18 +1,13 @@
import Convert from 'ansi-to-html';
import { DataNode } from 'antd/es/tree';
import { MetricsType } from 'container/MetricsApplication/constant';
import dompurify from 'dompurify';
import { uniqueId } from 'lodash-es';
import { ILog, ILogAggregateAttributesResources } from 'types/api/logs/log';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { FORBID_DOM_PURIFY_TAGS } from 'utils/app';
import BodyTitleRenderer from './BodyTitleRenderer';
import { typeToArrayTypeMapper } from './config';
import { AnyObject, IFieldAttributes } from './LogDetailedView.types';
const convertInstance = new Convert();
export const recursiveParseJSON = (obj: string): Record<string, unknown> => {
try {
const value = JSON.parse(obj);
@@ -341,21 +336,3 @@ export function findKeyPath(
});
return finalPath;
}
export const getSanitizedLogBody = (
text: string,
options: { shouldEscapeHtml?: boolean } = {},
): string => {
const { shouldEscapeHtml = false } = options;
const escapedText = shouldEscapeHtml ? escapeHtml(text) : text;
try {
return convertInstance.toHtml(
dompurify.sanitize(unescapeString(escapedText), {
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
}),
);
} catch (error) {
console.error('Error sanitizing text', error, text);
return '{}';
}
};

View File

@@ -130,11 +130,7 @@ function RightContainer({
const selectedGraphType =
GraphTypes.find((e) => e.name === selectedGraph)?.display || '';
const onCreateAlertsHandler = useCreateAlerts(
selectedWidget,
'panelView',
thresholds,
);
const onCreateAlertsHandler = useCreateAlerts(selectedWidget, 'panelView');
const allowThreshold = panelTypeVsThreshold[selectedGraph];
const allowSoftMinMax = panelTypeVsSoftMinMax[selectedGraph];

View File

@@ -3,40 +3,37 @@ import './TopNav.styles.scss';
import { Col, Row, Space } from 'antd';
import ROUTES from 'constants/routes';
import { useMemo } from 'react';
import { matchPath, useHistory } from 'react-router-dom';
import { matchPath, useLocation } from 'react-router-dom-v5-compat';
import NewExplorerCTA from '../NewExplorerCTA';
import DateTimeSelector from './DateTimeSelectionV2';
import { routesToDisable, routesToSkip } from './DateTimeSelectionV2/config';
function TopNav(): JSX.Element | null {
const { location } = useHistory();
const location = useLocation();
console.log({
location,
match: matchPath(location.pathname, '/settings/channels/edit/:channelId'),
});
const isRouteToSkip = useMemo(
() =>
routesToSkip.some((route) =>
matchPath(location.pathname, { path: route, exact: true }),
),
() => routesToSkip.some((route) => matchPath(route, location.pathname)),
[location.pathname],
);
const isDisabled = useMemo(
() =>
routesToDisable.some((route) =>
matchPath(location.pathname, { path: route, exact: true }),
),
() => routesToDisable.some((route) => matchPath(route, location.pathname)),
[location.pathname],
);
const isSignUpPage = useMemo(
() => matchPath(location.pathname, { path: ROUTES.SIGN_UP, exact: true }),
() => matchPath(location.pathname, ROUTES.SIGN_UP),
[location.pathname],
);
const isNewAlertsLandingPage = useMemo(
() =>
matchPath(location.pathname, { path: ROUTES.ALERTS_NEW, exact: true }) &&
!location.search,
() => matchPath(location.pathname, ROUTES.ALERTS_NEW) && !location.search,
[location.pathname, location.search],
);

View File

@@ -5,7 +5,6 @@ import { DEFAULT_ENTITY_VERSION } from 'constants/app';
import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes';
import { MenuItemKeys } from 'container/GridCardLayout/WidgetHeader/contants';
import { ThresholdProps } from 'container/NewWidget/RightContainer/Threshold/types';
import { useNotifications } from 'hooks/useNotifications';
import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables';
import { prepareQueryRangePayload } from 'lib/dashboard/prepareQueryRangePayload';
@@ -20,11 +19,7 @@ import { Widgets } from 'types/api/dashboard/getAll';
import { GlobalReducer } from 'types/reducer/globalTime';
import { getGraphType } from 'utils/getGraphType';
const useCreateAlerts = (
widget?: Widgets,
caller?: string,
thresholds?: ThresholdProps[],
): VoidFunction => {
const useCreateAlerts = (widget?: Widgets, caller?: string): VoidFunction => {
const queryRangeMutation = useMutation(getQueryRangeFormat);
const { selectedTime: globalSelectedInterval } = useSelector<
@@ -71,17 +66,13 @@ const useCreateAlerts = (
widget?.query,
);
const url = `${ROUTES.ALERTS_NEW}?${
QueryParams.compositeQuery
}=${encodeURIComponent(JSON.stringify(updatedQuery))}&${
QueryParams.panelTypes
}=${widget.panelTypes}&version=${
selectedDashboard?.data.version || DEFAULT_ENTITY_VERSION
}`;
history.push(url, {
thresholds,
});
history.push(
`${ROUTES.ALERTS_NEW}?${QueryParams.compositeQuery}=${encodeURIComponent(
JSON.stringify(updatedQuery),
)}&${QueryParams.panelTypes}=${widget.panelTypes}&version=${
selectedDashboard?.data.version || DEFAULT_ENTITY_VERSION
}`,
);
},
onError: () => {
notifications.error({

View File

@@ -0,0 +1,49 @@
import routes, { AppRoutes } from 'AppRoutes/routes';
import { FeatureKeys } from 'constants/features';
import { getRoutes } from 'pages/Settings/utils';
import { useAppContext } from 'providers/App/App';
import { useMemo } from 'react';
import useComponentPermission from './useComponentPermission';
import { useGetTenantLicense } from './useGetTenantLicense';
export const useAppRoutes = (): { routes: AppRoutes[] } => {
const { user, featureFlags, trialInfo } = useAppContext();
const { isCloudUser, isEnterpriseSelfHostedUser } = useGetTenantLicense();
const [isCurrentOrgSettings] = useComponentPermission(
['current_org_settings'],
user.role,
);
const isGatewayEnabled =
featureFlags?.find((feature) => feature.name === FeatureKeys.GATEWAY)
?.active || false;
const isWorkspaceBlocked = trialInfo?.workSpaceBlock || false;
const tabRoutes = useMemo(
() =>
getRoutes(
user.role,
isCurrentOrgSettings,
isGatewayEnabled,
isWorkspaceBlocked,
isCloudUser,
isEnterpriseSelfHostedUser,
),
[
user.role,
isCurrentOrgSettings,
isGatewayEnabled,
isWorkspaceBlocked,
isCloudUser,
isEnterpriseSelfHostedUser,
],
);
console.log({ tabRoutes });
return { routes };
};

View File

@@ -0,0 +1,7 @@
import APIKeysSettingsContainer from 'container/APIKeys/APIKeys';
function APIKeysSettings(): JSX.Element {
return <APIKeysSettingsContainer />;
}
export default APIKeysSettings;

View File

@@ -0,0 +1,7 @@
import AllAlertChannelsSettingsContainer from 'container/AllAlertChannels';
function AllAlertChannelsSettings(): JSX.Element {
return <AllAlertChannelsSettingsContainer />;
}
export default AllAlertChannelsSettings;

View File

@@ -0,0 +1,7 @@
import CreateAlertChannelsContainer from 'container/CreateAlertChannels';
function CreateAlertChannelsSettings(): JSX.Element {
return <CreateAlertChannelsContainer />;
}
export default CreateAlertChannelsSettings;

View File

@@ -0,0 +1,6 @@
import CustomDomainSettingsContainer from 'container/CustomDomainSettings';
function CustomDomainSettings(): JSX.Element {
return <CustomDomainSettingsContainer />;
}
export default CustomDomainSettings;

View File

@@ -0,0 +1,7 @@
import IngestionSettingsContainer from 'container/IngestionSettings/MultiIngestionSettings';
function IngestionSettings(): JSX.Element {
return <IngestionSettingsContainer />;
}
export default IngestionSettings;

View File

@@ -0,0 +1,7 @@
import OrganizationSettingsContainer from 'container/OrganizationSettings';
function OrganizationSettings(): JSX.Element {
return <OrganizationSettingsContainer />;
}
export default OrganizationSettings;

View File

@@ -0,0 +1,7 @@
import GeneralSettingsContainer from 'container/GeneralSettings';
function GeneralSettings(): JSX.Element {
return <GeneralSettingsContainer />;
}
export default GeneralSettings;

View File

@@ -1,35 +1,25 @@
import './Settings.styles.scss';
import logEvent from 'api/common/logEvent';
import RouteTab from 'components/RouteTab';
import { FeatureKeys } from 'constants/features';
import ROUTES from 'constants/routes';
import { routeConfig } from 'container/SideNav/config';
import { getQueryString } from 'container/SideNav/helper';
import { settingsMenuItems as defaultSettingsMenuItems } from 'container/SideNav/menuItems';
import NavItem from 'container/SideNav/NavItem/NavItem';
import { SidebarItem } from 'container/SideNav/sideNav.types';
import useComponentPermission from 'hooks/useComponentPermission';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import history from 'lib/history';
import { Wrench } from 'lucide-react';
import { useAppContext } from 'providers/App/App';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useCallback, useEffect, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { Outlet } from 'react-router-dom-v5-compat';
import { USER_ROLES } from 'types/roles';
import { getRoutes } from './utils';
function SettingsPage(): JSX.Element {
const { pathname, search } = useLocation();
const {
user,
featureFlags,
trialInfo,
isFetchingActiveLicense,
} = useAppContext();
const { user, trialInfo, isFetchingActiveLicense } = useAppContext();
const { isCloudUser, isEnterpriseSelfHostedUser } = useGetTenantLicense();
const [settingsMenuItems, setSettingsMenuItems] = useState<SidebarItem[]>(
@@ -39,18 +29,6 @@ function SettingsPage(): JSX.Element {
const isAdmin = user.role === USER_ROLES.ADMIN;
const isEditor = user.role === USER_ROLES.EDITOR;
const isWorkspaceBlocked = trialInfo?.workSpaceBlock || false;
const [isCurrentOrgSettings] = useComponentPermission(
['current_org_settings'],
user.role,
);
const { t } = useTranslation(['routes']);
const isGatewayEnabled =
featureFlags?.find((feature) => feature.name === FeatureKeys.GATEWAY)
?.active || false;
// eslint-disable-next-line sonarjs/cognitive-complexity
useEffect(() => {
setSettingsMenuItems((prevItems) => {
@@ -162,28 +140,6 @@ function SettingsPage(): JSX.Element {
pathname,
]);
const routes = useMemo(
() =>
getRoutes(
user.role,
isCurrentOrgSettings,
isGatewayEnabled,
isWorkspaceBlocked,
isCloudUser,
isEnterpriseSelfHostedUser,
t,
),
[
user.role,
isCurrentOrgSettings,
isGatewayEnabled,
isWorkspaceBlocked,
isCloudUser,
isEnterpriseSelfHostedUser,
t,
],
);
const isCtrlMetaKey = (e: MouseEvent): boolean => e.ctrlKey || e.metaKey;
const openInNewTab = (path: string): void => {
@@ -261,12 +217,7 @@ function SettingsPage(): JSX.Element {
</div>
<div className="settings-page-content">
<RouteTab
routes={routes}
activeKey={pathname}
history={history}
tabBarStyle={{ display: 'none' }}
/>
<Outlet />
</div>
</div>
</div>

View File

@@ -1,22 +1,6 @@
import { RouteTabProps } from 'components/RouteTab/types';
import { TFunction } from 'i18next';
import ROUTES, { SETTINGS_NESTED_ROUTES } from 'constants/routes';
import { ROLES, USER_ROLES } from 'types/roles';
import {
alertChannels,
apiKeys,
billingSettings,
createAlertChannels,
customDomainSettings,
editAlertChannels,
generalSettings,
ingestionSettings,
keyboardShortcuts,
multiIngestionSettings,
mySettings,
organizationSettings,
} from './config';
export const getRoutes = (
userRole: ROLES | null,
isCurrentOrgSettings: boolean,
@@ -24,8 +8,7 @@ export const getRoutes = (
isWorkspaceBlocked: boolean,
isCloudUser: boolean,
isEnterpriseSelfHostedUser: boolean,
t: TFunction,
): RouteTabProps['routes'] => {
): string[] => {
const settings = [];
const isAdmin = userRole === USER_ROLES.ADMIN;
@@ -33,44 +16,47 @@ export const getRoutes = (
if (isWorkspaceBlocked && isAdmin) {
settings.push(
...organizationSettings(t),
...mySettings(t),
...billingSettings(t),
...keyboardShortcuts(t),
SETTINGS_NESTED_ROUTES.ORG_SETTINGS,
SETTINGS_NESTED_ROUTES.MY_SETTINGS,
SETTINGS_NESTED_ROUTES.BILLING,
SETTINGS_NESTED_ROUTES.SHORTCUTS,
);
return settings;
}
settings.push(...generalSettings(t));
settings.push(ROUTES.SETTINGS);
if (isCurrentOrgSettings) {
settings.push(...organizationSettings(t));
settings.push(SETTINGS_NESTED_ROUTES.ORG_SETTINGS);
}
if (isGatewayEnabled && (isAdmin || isEditor)) {
settings.push(...multiIngestionSettings(t));
settings.push(SETTINGS_NESTED_ROUTES.INGESTION_SETTINGS);
}
if (isCloudUser && !isGatewayEnabled) {
settings.push(...ingestionSettings(t));
settings.push(SETTINGS_NESTED_ROUTES.INGESTION_SETTINGS);
}
settings.push(...alertChannels(t));
settings.push(SETTINGS_NESTED_ROUTES.ALL_CHANNELS);
if (isAdmin) {
settings.push(...apiKeys(t));
settings.push(SETTINGS_NESTED_ROUTES.API_KEYS);
}
if ((isCloudUser || isEnterpriseSelfHostedUser) && isAdmin) {
settings.push(...customDomainSettings(t), ...billingSettings(t));
settings.push(
SETTINGS_NESTED_ROUTES.CUSTOM_DOMAIN_SETTINGS,
SETTINGS_NESTED_ROUTES.BILLING,
);
}
settings.push(
...mySettings(t),
...createAlertChannels(t),
...editAlertChannels(t),
...keyboardShortcuts(t),
SETTINGS_NESTED_ROUTES.MY_SETTINGS,
SETTINGS_NESTED_ROUTES.CHANNELS_NEW,
SETTINGS_NESTED_ROUTES.CHANNELS_EDIT,
SETTINGS_NESTED_ROUTES.SHORTCUTS,
);
return settings;

View File

@@ -7486,7 +7486,7 @@ debounce@^1.2.1:
resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5"
integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==
debug@2.6.9, debug@4, debug@4.3.4, debug@^3.2.6, debug@^3.2.7, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.3, debug@^4.3.4, debug@^4.3.6, debug@ngokevin/debug#noTimestamp:
debug@2.6.9, debug@4, debug@4.3.4, debug@^3.2.6, debug@^3.2.7, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@^4.3.6, debug@ngokevin/debug#noTimestamp:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
@@ -9122,7 +9122,7 @@ glob-to-regexp@^0.4.1:
resolved "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz"
integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4:
glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
version "7.2.3"
resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz"
integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
@@ -15249,7 +15249,7 @@ sass@1.66.1, sass@^1.58.3:
immutable "^4.0.0"
source-map-js ">=0.6.2 <2.0.0"
sax@>=0.6.0, sax@^1.2.4:
sax@>=0.6.0, sax@^1.2.4, sax@~1.2.4:
version "1.2.4"
resolved "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz"
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
@@ -16016,10 +16016,16 @@ stylis@^4.3.0:
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.3.4.tgz#ca5c6c4a35c4784e4e93a2a24dc4e9fa075250a4"
integrity sha512-osIBl6BGUmSfDkyH2mB7EFvCJntXDrLhKjHTRj/rK6xLH0yuPrHULDRQzKokSOD4VoorhtKpfcfW1GAntu8now==
stylus@0.0.1-security, stylus@^0.59.0:
version "0.0.1-security"
resolved "https://registry.yarnpkg.com/stylus/-/stylus-0.0.1-security.tgz#7fbf8df07e6c1202fce29d57fed2ecbf960e6a3b"
integrity sha512-qTLX5NJwuj5dqdJyi1eAjXGE4HfjWDoPLbSIpYWn/rAEdiyZnsngS/Yj0BRjM7wC41e/+spK4QCXFqz7LM0fFQ==
stylus@^0.59.0:
version "0.59.0"
resolved "https://registry.yarnpkg.com/stylus/-/stylus-0.59.0.tgz#a344d5932787142a141946536d6e24e6a6be7aa6"
integrity sha512-lQ9w/XIOH5ZHVNuNbWW8D822r+/wBSO/d6XvtyHLF7LW4KaCIDeVbvn5DF8fGCJAUCwVhVi/h6J0NUcnylUEjg==
dependencies:
"@adobe/css-tools" "^4.0.1"
debug "^4.3.2"
glob "^7.1.6"
sax "~1.2.4"
source-map "^0.7.3"
super-animejs@^3.1.0:
version "3.1.0"

View File

@@ -6054,90 +6054,3 @@ func (r *ClickHouseReader) SearchTraces(ctx context.Context, params *model.Searc
return &searchSpansResult, nil
}
func (r *ClickHouseReader) GetNormalizedStatus(
ctx context.Context,
orgID valuer.UUID,
metricNames []string,
) (map[string]bool, error) {
if len(metricNames) == 0 {
return map[string]bool{}, nil
}
result := make(map[string]bool, len(metricNames))
buildKey := func(name string) string {
return constants.NormalizedMetricsMapCacheKey + ":" + name
}
uncached := make([]string, 0, len(metricNames))
for _, m := range metricNames {
var status model.MetricsNormalizedMap
if err := r.cache.Get(ctx, orgID, buildKey(m), &status, true); err == nil {
result[m] = status.IsUnNormalized
} else {
uncached = append(uncached, m)
}
}
if len(uncached) == 0 {
return result, nil
}
placeholders := "'" + strings.Join(uncached, "', '") + "'"
q := fmt.Sprintf(
`SELECT metric_name, toUInt8(__normalized)
FROM %s.%s
WHERE metric_name IN (%s)
GROUP BY metric_name, __normalized`,
signozMetricDBName, signozTSTableNameV41Day, placeholders,
)
rows, err := r.db.Query(ctx, q)
if err != nil {
return nil, err
}
defer rows.Close()
// tmp[m] collects the set {0,1} for a metric name, truth table
tmp := make(map[string]map[uint8]struct{}, len(uncached))
for rows.Next() {
var (
name string
normalized uint8
)
if err := rows.Scan(&name, &normalized); err != nil {
return nil, err
}
if _, ok := tmp[name]; !ok {
tmp[name] = make(map[uint8]struct{}, 2)
}
tmp[name][normalized] = struct{}{}
}
if err := rows.Err(); err != nil {
return nil, err
}
for _, m := range uncached {
set := tmp[m]
switch {
case len(set) == 0:
return nil, fmt.Errorf("metric %q not found in ClickHouse", m)
case len(set) == 2:
result[m] = true
default:
_, hasUnnorm := set[0]
result[m] = hasUnnorm
}
status := model.MetricsNormalizedMap{
MetricName: m,
IsUnNormalized: result[m],
}
_ = r.cache.Set(ctx, orgID, buildKey(m), &status, 0)
}
return result, nil
}

View File

@@ -862,7 +862,7 @@ func Test_buildLogsQuery(t *testing.T) {
"from signoz_logs.distributed_logs_v2 where (timestamp >= 1680066360726210000 AND timestamp <= 1680066458000000000) AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) " +
"AND attributes_number['duration'] > 1000.000000 AND mapContains(attributes_number, 'duration') AND mapContains(attributes_number, 'duration') AND " +
"(resource_fingerprint GLOBAL IN (SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE (seen_at_ts_bucket_start >= 1680064560) AND (seen_at_ts_bucket_start <= 1680066458) " +
"AND simpleJSONExtractString(labels, 'service.name') = 'test' AND labels like '%service.name\":\"test%' AND ( (simpleJSONHas(labels, 'host') AND labels like '%host%') ))) " +
"AND simpleJSONExtractString(labels, 'service.name') = 'test' AND labels like '%service.name%test%' AND ( (simpleJSONHas(labels, 'host') AND labels like '%host%') ))) " +
"group by `host`,ts order by `host` desc",
},
}
@@ -962,7 +962,7 @@ func TestPrepareLogsQuery(t *testing.T) {
"where (timestamp >= 1680066360726000000 AND timestamp <= 1680066458000000000) AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) AND attributes_string['method'] = 'GET' " +
"AND mapContains(attributes_string, 'method') AND mapContains(attributes_string, 'user') AND mapContains(attributes_string, 'name') AND (resource_fingerprint GLOBAL IN " +
"(SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE (seen_at_ts_bucket_start >= 1680064560) AND (seen_at_ts_bucket_start <= 1680066458) AND simpleJSONExtractString(labels, 'service.name') = 'app' " +
"AND labels like '%service.name\":\"app%')) group by `user` order by value DESC) LIMIT 10",
"AND labels like '%service.name%app%')) group by `user` order by value DESC) LIMIT 10",
},
{
name: "Test TS with limit- second",
@@ -991,7 +991,7 @@ func TestPrepareLogsQuery(t *testing.T) {
"from signoz_logs.distributed_logs_v2 where (timestamp >= 1680066360726000000 AND timestamp <= 1680066458000000000) AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) AND " +
"attributes_string['method'] = 'GET' AND mapContains(attributes_string, 'method') AND mapContains(attributes_string, 'user') AND mapContains(attributes_string, 'name') AND " +
"(resource_fingerprint GLOBAL IN (SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE (seen_at_ts_bucket_start >= 1680064560) AND (seen_at_ts_bucket_start <= 1680066458) AND " +
"simpleJSONExtractString(labels, 'service.name') = 'app' AND labels like '%service.name\":\"app%')) AND (`user`) GLOBAL IN (#LIMIT_PLACEHOLDER) group by `user`,ts order by value DESC",
"simpleJSONExtractString(labels, 'service.name') = 'app' AND labels like '%service.name%app%')) AND (`user`) GLOBAL IN (#LIMIT_PLACEHOLDER) group by `user`,ts order by value DESC",
},
{
name: "Live Tail Query",

View File

@@ -3,7 +3,6 @@ package v2
import (
"context"
"fmt"
"github.com/prometheus/prometheus/promql/parser"
"strings"
"sync"
@@ -277,59 +276,3 @@ func (q *querier) runBuilderQuery(
Series: resultSeries,
}
}
// ValidateMetricNames function is used to print all those queries who are still using old normalized metrics and not new metrics.
func (q *querier) ValidateMetricNames(ctx context.Context, query *v3.CompositeQuery, orgID valuer.UUID) {
var metricNames []string
switch query.QueryType {
case v3.QueryTypePromQL:
for _, query := range query.PromQueries {
expr, err := parser.ParseExpr(query.Query)
if err != nil {
zap.L().Debug("error parsing promQL expression", zap.String("query", query.Query), zap.Error(err))
continue
}
parser.Inspect(expr, func(node parser.Node, path []parser.Node) error {
if vs, ok := node.(*parser.VectorSelector); ok {
for _, m := range vs.LabelMatchers {
if m.Name == "__name__" {
metricNames = append(metricNames, m.Value)
}
}
}
return nil
})
}
metrics, err := q.reader.GetNormalizedStatus(ctx, orgID, metricNames)
if err != nil {
zap.L().Debug("error getting corresponding normalized metrics", zap.Error(err))
return
}
for metricName, metricPresent := range metrics {
if metricPresent {
continue
} else {
zap.L().Warn("using normalized metric name", zap.String("metrics", metricName))
continue
}
}
case v3.QueryTypeBuilder:
for _, query := range query.BuilderQueries {
metricName := query.AggregateAttribute.Key
metricNames = append(metricNames, metricName)
}
metrics, err := q.reader.GetNormalizedStatus(ctx, orgID, metricNames)
if err != nil {
zap.L().Debug("error getting corresponding normalized metrics", zap.Error(err))
return
}
for metricName, metricPresent := range metrics {
if metricPresent {
continue
} else {
zap.L().Warn("using normalized metric name", zap.String("metrics", metricName))
continue
}
}
}
}

View File

@@ -506,9 +506,6 @@ func (q *querier) QueryRange(ctx context.Context, orgID valuer.UUID, params *v3.
var results []*v3.Result
var err error
var errQueriesByName map[string]error
if !q.testingMode && q.reader != nil {
q.ValidateMetricNames(ctx, params.CompositeQuery, orgID)
}
if params.CompositeQuery != nil {
switch params.CompositeQuery.QueryType {
case v3.QueryTypeBuilder:

View File

@@ -117,19 +117,17 @@ func buildResourceIndexFilter(key string, op v3.FilterOperator, value interface{
// add index filters
switch op {
case v3.FilterOperatorEqual:
return fmt.Sprintf("labels like '%%%s\":\"%s%%'", key, fmtValEscapedForContains)
return fmt.Sprintf("labels like '%%%s%%%s%%'", key, fmtValEscapedForContains)
case v3.FilterOperatorNotEqual:
return fmt.Sprintf("labels not like '%%%s\":\"%s%%'", key, fmtValEscapedForContains)
return fmt.Sprintf("labels not like '%%%s%%%s%%'", key, fmtValEscapedForContains)
case v3.FilterOperatorLike:
return fmt.Sprintf("lower(labels) like '%%%s%%%s%%'", key, fmtValEscapedLower)
case v3.FilterOperatorNotLike:
// cannot apply not contains x%y as y can be somewhere else
return ""
return fmt.Sprintf("lower(labels) not like '%%%s%%%s%%'", key, fmtValEscapedLower)
case v3.FilterOperatorContains:
return fmt.Sprintf("lower(labels) like '%%%s%%%s%%'", key, fmtValEscapedForContainsLower)
case v3.FilterOperatorNotContains:
// cannot apply not contains x%y as y can be somewhere else
return ""
return fmt.Sprintf("lower(labels) not like '%%%s%%%s%%'", key, fmtValEscapedForContainsLower)
case v3.FilterOperatorExists:
return fmt.Sprintf("lower(labels) like '%%%s%%'", key)
case v3.FilterOperatorNotExists:

View File

@@ -164,22 +164,31 @@ func Test_buildResourceIndexFilter(t *testing.T) {
want string
}{
{
name: "test eq",
name: "test contains",
args: args{
key: "service.name",
op: v3.FilterOperatorEqual,
value: `Application"`,
op: v3.FilterOperatorContains,
value: "application",
},
want: `labels like '%service.name":"Application\\\\"%'`,
want: `lower(labels) like '%service.name%application%'`,
},
{
name: "test not eq",
name: "test not contains",
args: args{
key: "service.name",
op: v3.FilterOperatorNotEqual,
value: `Application"`,
op: v3.FilterOperatorNotContains,
value: "application",
},
want: `labels not like '%service.name":"Application\\\\"%'`,
want: `lower(labels) not like '%service.name%application%'`,
},
{
name: "test contains with % and _",
args: args{
key: "service.name",
op: v3.FilterOperatorNotContains,
value: "application%_test",
},
want: `lower(labels) not like '%service.name%application\%\_test%'`,
},
{
name: "test like with % and _",
@@ -199,43 +208,6 @@ func Test_buildResourceIndexFilter(t *testing.T) {
},
want: `lower(labels) like '%service.name%application%_test%'`,
},
{
name: "test not like with % and _",
args: args{
key: "service.name",
op: v3.FilterOperatorLike,
value: "application%_test",
},
want: `lower(labels) like '%service.name%application%_test%'`,
},
{
name: "test contains",
args: args{
key: "service.name",
op: v3.FilterOperatorContains,
value: "application",
},
want: `lower(labels) like '%service.name%application%'`,
},
{
name: "test not contains",
args: args{
key: "service.name",
op: v3.FilterOperatorNotContains,
value: "application",
},
want: ``,
},
{
name: "test contains with % and _",
args: args{
key: "service.name",
op: v3.FilterOperatorNotContains,
value: "application%_test",
},
want: ``,
},
{
name: "test not regex",
args: args{
@@ -254,6 +226,15 @@ func Test_buildResourceIndexFilter(t *testing.T) {
},
want: `(labels not like '%"service.name":"Application"%' AND labels not like '%"service.name":"Test"%')`,
},
{
name: "test eq",
args: args{
key: "service.name",
op: v3.FilterOperatorEqual,
value: `Application"`,
},
want: `labels like '%service.name%Application\\\\"%'`,
},
{
name: "test exists",
args: args{
@@ -329,7 +310,7 @@ func Test_buildResourceFiltersFromFilterItems(t *testing.T) {
},
want: []string{
"simpleJSONExtractString(labels, 'service.name') = 'test'",
"labels like '%service.name\":\"test%'",
"labels like '%service.name%test%'",
},
wantErr: false,
},
@@ -361,7 +342,7 @@ func Test_buildResourceFiltersFromFilterItems(t *testing.T) {
},
want: []string{
"simpleJSONExtractString(labels, 'service.name') = 'test'",
"labels like '%service.name\":\"test%'",
"labels like '%service.name%test%'",
`simpleJSONExtractString(lower(labels), 'namespace') LIKE '%test1"%'`,
`lower(labels) like '%namespace%test1\\\\"%'`,
},
@@ -523,7 +504,7 @@ func Test_buildResourceSubQuery(t *testing.T) {
},
want: "(SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE " +
"(seen_at_ts_bucket_start >= 1680064560) AND (seen_at_ts_bucket_start <= 1680066458) AND " +
"simpleJSONExtractString(labels, 'service.name') = 'test' AND labels like '%service.name\":\"test%' " +
"simpleJSONExtractString(labels, 'service.name') = 'test' AND labels like '%service.name%test%' " +
"AND simpleJSONExtractString(lower(labels), 'namespace') LIKE '%test1%' AND lower(labels) like '%namespace%test1%' " +
"AND (simpleJSONHas(labels, 'cluster.name') AND labels like '%cluster.name%') AND " +
"( (simpleJSONHas(labels, 'host.name') AND labels like '%host.name%') ))",

View File

@@ -532,7 +532,7 @@ func Test_buildTracesQuery(t *testing.T) {
want: "SELECT resources_number['host'] as `host`, toFloat64(count()) as value from signoz_traces.distributed_signoz_index_v3 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " +
"AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) AND attributes_number['bytes'] > 100 AND " +
"(resource_fingerprint GLOBAL IN (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (seen_at_ts_bucket_start >= 1680064560) AND " +
"(seen_at_ts_bucket_start <= 1680066458) AND simpleJSONExtractString(labels, 'service.name') = 'myService' AND labels like '%service.name\":\"myService%' AND " +
"(seen_at_ts_bucket_start <= 1680066458) AND simpleJSONExtractString(labels, 'service.name') = 'myService' AND labels like '%service.name%myService%' AND " +
"( (simpleJSONHas(labels, 'host') AND labels like '%host%') ))) " +
"group by `host` order by `host` ASC",
},
@@ -614,7 +614,7 @@ func Test_buildTracesQuery(t *testing.T) {
},
},
want: "SELECT timestamp as timestamp_datetime, spanID, traceID, name as `name` from signoz_traces.distributed_signoz_index_v3 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " +
"AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) AND (resource_fingerprint GLOBAL IN (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (seen_at_ts_bucket_start >= 1680064560) AND (seen_at_ts_bucket_start <= 1680066458) AND simpleJSONExtractString(labels, 'service.name') = 'cartservice' AND labels like '%service.name\":\"cartservice%')) AND parent_span_id = '' order by timestamp ASC",
"AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) AND (resource_fingerprint GLOBAL IN (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (seen_at_ts_bucket_start >= 1680064560) AND (seen_at_ts_bucket_start <= 1680066458) AND simpleJSONExtractString(labels, 'service.name') = 'cartservice' AND labels like '%service.name%cartservice%')) AND parent_span_id = '' order by timestamp ASC",
},
{
name: "test noop list view-without ts",
@@ -649,7 +649,7 @@ func Test_buildTracesQuery(t *testing.T) {
},
want: "SELECT subQuery.serviceName as `subQuery.serviceName`, subQuery.name as `subQuery.name`, count() AS span_count, subQuery.durationNano as `subQuery.durationNano`, subQuery.traceID FROM " +
"(SELECT traceID AS dist_traceID, timestamp, ts_bucket_start FROM signoz_traces.distributed_signoz_index_v3 WHERE (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) AND attributes_string['method'] = 'GET' " +
"AND (resource_fingerprint GLOBAL IN (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (seen_at_ts_bucket_start >= 1680064560) AND (seen_at_ts_bucket_start <= 1680066458) AND simpleJSONExtractString(labels, 'service.name') = 'myService' AND labels like '%service.name\":\"myService%'))) as dist_table " +
"AND (resource_fingerprint GLOBAL IN (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (seen_at_ts_bucket_start >= 1680064560) AND (seen_at_ts_bucket_start <= 1680066458) AND simpleJSONExtractString(labels, 'service.name') = 'myService' AND labels like '%service.name%myService%'))) as dist_table " +
"INNER JOIN " +
"( SELECT * FROM (SELECT traceID, durationNano, serviceName, name FROM signoz_traces.signoz_index_v3 WHERE parentSpanID = '' AND (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) " +
"ORDER BY durationNano DESC LIMIT 1 BY traceID) AS inner_subquery ) AS subQuery ON dist_table.dist_traceID = subQuery.traceID " +
@@ -676,7 +676,7 @@ func Test_buildTracesQuery(t *testing.T) {
want: "SELECT serviceName, name, subQuery.span_count as span_count, durationNano, trace_id as traceID from signoz_traces.distributed_signoz_index_v3 GLOBAL INNER JOIN " +
"( SELECT * FROM (SELECT trace_id, count() as span_count FROM signoz_traces.signoz_index_v3 WHERE (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) " +
"AND attributes_string['method'] = 'GET' AND (resource_fingerprint GLOBAL IN (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE " +
"(seen_at_ts_bucket_start >= 1680064560) AND (seen_at_ts_bucket_start <= 1680066458) AND simpleJSONExtractString(labels, 'service.name') = 'myService' AND labels like '%service.name\":\"myService%')) " +
"(seen_at_ts_bucket_start >= 1680064560) AND (seen_at_ts_bucket_start <= 1680066458) AND simpleJSONExtractString(labels, 'service.name') = 'myService' AND labels like '%service.name%myService%')) " +
"GROUP BY trace_id ORDER BY span_count DESC LIMIT 1 BY trace_id LIMIT 100) AS inner_subquery ) AS subQuery ON signoz_traces.distributed_signoz_index_v3.trace_id = subQuery.trace_id " +
"WHERE parent_span_id = '' AND (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) " +
"ORDER BY subQuery.span_count DESC LIMIT 100 settings distributed_product_mode='allow', max_memory_usage=10000000000",
@@ -863,7 +863,7 @@ func TestPrepareTracesQuery(t *testing.T) {
want: "SELECT `function`,`service.name` from (SELECT `attribute_string_function` as `function`, `resource_string_service$$name` as `service.name`, toFloat64(count(distinct(name))) as value " +
"from signoz_traces.distributed_signoz_index_v3 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) " +
"AND attributes_number['line'] = 100 AND (resource_fingerprint GLOBAL IN (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE " +
"(seen_at_ts_bucket_start >= 1680064560) AND (seen_at_ts_bucket_start <= 1680066458) AND simpleJSONExtractString(labels, 'hostname') = 'server1' AND labels like '%hostname\":\"server1%' AND " +
"(seen_at_ts_bucket_start >= 1680064560) AND (seen_at_ts_bucket_start <= 1680066458) AND simpleJSONExtractString(labels, 'hostname') = 'server1' AND labels like '%hostname%server1%' AND " +
"( (simpleJSONHas(labels, 'service.name') AND labels like '%service.name%') ))) group by `function`,`service.name` order by value DESC) LIMIT 10",
},
{
@@ -905,7 +905,7 @@ func TestPrepareTracesQuery(t *testing.T) {
want: "SELECT `attribute_string_function` as `function`, serviceName as `serviceName`, toFloat64(count(distinct(name))) as value from signoz_traces.distributed_signoz_index_v3 " +
"where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) AND attributes_number['line'] = 100 " +
"AND (resource_fingerprint GLOBAL IN (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (seen_at_ts_bucket_start >= 1680064560) AND (seen_at_ts_bucket_start <= 1680066458) " +
"AND simpleJSONExtractString(labels, 'hostname') = 'server1' AND labels like '%hostname\":\"server1%')) AND (`function`,`serviceName`) GLOBAL IN (#LIMIT_PLACEHOLDER) group by `function`,`serviceName` order by value DESC",
"AND simpleJSONExtractString(labels, 'hostname') = 'server1' AND labels like '%hostname%server1%')) AND (`function`,`serviceName`) GLOBAL IN (#LIMIT_PLACEHOLDER) group by `function`,`serviceName` order by value DESC",
},
}

View File

@@ -3,7 +3,6 @@ package constants
import (
"maps"
"os"
"regexp"
"strconv"
"time"
@@ -34,12 +33,6 @@ var InviteEmailTemplate = GetOrDefaultEnv("INVITE_EMAIL_TEMPLATE", "/root/templa
var MetricsExplorerClickhouseThreads = GetOrDefaultEnvInt("METRICS_EXPLORER_CLICKHOUSE_THREADS", 8)
var UpdatedMetricsMetadataCachePrefix = GetOrDefaultEnv("METRICS_UPDATED_METADATA_CACHE_KEY", "UPDATED_METRICS_METADATA")
const NormalizedMetricsMapCacheKey = "NORMALIZED_METRICS_MAP_CACHE_KEY"
const NormalizedMetricsMapQueryThreads = 10
var NormalizedMetricsMapRegex = regexp.MustCompile(`[^a-zA-Z0-9]`)
var NormalizedMetricsMapQuantileRegex = regexp.MustCompile(`(?i)([._-]?quantile.*)$`)
// TODO(srikanthccv): remove after backfilling is done
func UseMetricsPreAggregation() bool {
return GetOrDefaultEnv("USE_METRICS_PRE_AGGREGATION", "true") == "true"

View File

@@ -130,7 +130,6 @@ type Reader interface {
GetUpdatedMetricsMetadata(ctx context.Context, orgID valuer.UUID, metricNames ...string) (map[string]*model.UpdateMetricsMetadata, *model.ApiError)
CheckForLabelsInMetric(ctx context.Context, metricName string, labels []string) (bool, *model.ApiError)
GetNormalizedStatus(ctx context.Context, orgID valuer.UUID, metricNames []string) (map[string]bool, error)
}
type Querier interface {

View File

@@ -1,15 +0,0 @@
package model
import "encoding/json"
type MetricsNormalizedMap struct {
MetricName string `json:"metricName"`
IsUnNormalized bool `json:"isUnNormalized"`
}
func (c *MetricsNormalizedMap) MarshalBinary() (data []byte, err error) {
return json.Marshal(c)
}
func (c *MetricsNormalizedMap) UnmarshalBinary(data []byte) error {
return json.Unmarshal(data, c)
}

View File

@@ -283,7 +283,7 @@ func (r *ThresholdRule) buildAndRunQuery(ctx context.Context, orgID valuer.UUID,
if hasLogsQuery {
// check if any enrichment is required for logs if yes then enrich them
if logsv3.EnrichmentRequired(params) {
logsFields, err := r.reader.GetLogFieldsFromNames(ctx, logsv3.GetFieldNames(params.CompositeQuery))
logsFields, err := r.reader.GetLogFields(ctx)
if err != nil {
return nil, err
}

View File

@@ -1320,6 +1320,7 @@ func TestThresholdRuleNoData(t *testing.T) {
for idx, c := range cases {
rows := cmock.NewRows(cols, c.values)
// We are testing the eval logic after the query is run
// so we don't care about the query string here
queryString := "SELECT any"
@@ -1513,11 +1514,11 @@ func TestThresholdRuleLogsLink(t *testing.T) {
attrMetaCols := make([]cmock.ColumnType, 0)
attrMetaCols = append(attrMetaCols, cmock.ColumnType{Name: "name", Type: "String"})
attrMetaCols = append(attrMetaCols, cmock.ColumnType{Name: "datatype", Type: "String"})
attrMetaCols = append(attrMetaCols, cmock.ColumnType{Name: "dataType", Type: "String"})
resourceMetaCols := make([]cmock.ColumnType, 0)
resourceMetaCols = append(resourceMetaCols, cmock.ColumnType{Name: "name", Type: "String"})
resourceMetaCols = append(resourceMetaCols, cmock.ColumnType{Name: "datatype", Type: "String"})
resourceMetaCols = append(resourceMetaCols, cmock.ColumnType{Name: "dataType", Type: "String"})
createTableCols := make([]cmock.ColumnType, 0)
createTableCols = append(createTableCols, cmock.ColumnType{Name: "statement", Type: "String"})
@@ -1530,12 +1531,12 @@ func TestThresholdRuleLogsLink(t *testing.T) {
for idx, c := range testCases {
attrMetaRows := cmock.NewRows(attrMetaCols, c.attrMetaValues)
telemetryStore.Mock().
ExpectSelect("SELECT DISTINCT name, datatype from signoz_logs.distributed_logs_attribute_keys where name in ('component','k8s.container.name') group by name, datatype").
ExpectSelect("SELECT DISTINCT name, datatype from signoz_logs.distributed_logs_attribute_keys group by name, datatype").
WillReturnRows(attrMetaRows)
resourceMetaRows := cmock.NewRows(resourceMetaCols, c.resourceMetaValues)
telemetryStore.Mock().
ExpectSelect("SELECT DISTINCT name, datatype from signoz_logs.distributed_logs_resource_keys where name in ('component','k8s.container.name') group by name, datatype").
ExpectSelect("SELECT DISTINCT name, datatype from signoz_logs.distributed_logs_resource_keys group by name, datatype").
WillReturnRows(resourceMetaRows)
createTableRows := cmock.NewRows(createTableCols, c.createTableValues)

View File

@@ -19,18 +19,14 @@ func NewConditionBuilder(fm qbtypes.FieldMapper) *defaultConditionBuilder {
return &defaultConditionBuilder{fm: fm}
}
func valueForIndexFilter(op qbtypes.FilterOperator, key *telemetrytypes.TelemetryFieldKey, value any) any {
func valueForIndexFilter(key *telemetrytypes.TelemetryFieldKey, value any) any {
switch v := value.(type) {
case string:
if op == qbtypes.FilterOperatorEqual || op == qbtypes.FilterOperatorNotEqual {
return fmt.Sprintf(`%%%s":"%s%%`, key.Name, v)
}
return fmt.Sprintf(`%%%s%%%s%%`, key.Name, v)
case []any:
// assuming array will always be for in and not in
values := make([]string, 0, len(v))
for _, v := range v {
values = append(values, fmt.Sprintf(`%%%s":"%s%%`, key.Name, v))
values = append(values, fmt.Sprintf(`%%%s%%%s%%`, key.Name, v))
}
return values
}
@@ -59,7 +55,7 @@ func (b *defaultConditionBuilder) ConditionFor(
}
keyIdxFilter := sb.Like(column.Name, keyIndexFilter(key))
valueForIndexFilter := valueForIndexFilter(op, key, value)
valueForIndexFilter := valueForIndexFilter(key, value)
fieldName, err := b.fm.FieldFor(ctx, key)
if err != nil {
@@ -94,9 +90,9 @@ func (b *defaultConditionBuilder) ConditionFor(
sb.ILike(column.Name, valueForIndexFilter),
), nil
case qbtypes.FilterOperatorNotLike, qbtypes.FilterOperatorNotILike:
// no index filter: as cannot apply `not contains x%y` as y can be somewhere else
return sb.And(
sb.NotILike(fieldName, value),
sb.NotILike(column.Name, valueForIndexFilter),
), nil
case qbtypes.FilterOperatorBetween:
@@ -183,9 +179,9 @@ func (b *defaultConditionBuilder) ConditionFor(
sb.ILike(column.Name, valueForIndexFilter),
), nil
case qbtypes.FilterOperatorNotContains:
// no index filter: as cannot apply `not contains x%y` as y can be somewhere else
return sb.And(
sb.NotILike(fieldName, fmt.Sprintf(`%%%s%%`, value)),
sb.NotILike(column.Name, valueForIndexFilter),
), nil
}
return "", qbtypes.ErrUnsupportedOperator

View File

@@ -31,7 +31,7 @@ func TestConditionBuilder(t *testing.T) {
op: querybuildertypesv5.FilterOperatorEqual,
value: "watch",
expected: "simpleJSONExtractString(labels, 'k8s.namespace.name') = ? AND labels LIKE ? AND labels LIKE ?",
expectedArgs: []any{"watch", "%k8s.namespace.name%", `%k8s.namespace.name":"watch%`},
expectedArgs: []any{"watch", "%k8s.namespace.name%", `%k8s.namespace.name%watch%`},
},
{
name: "string_not_equal",
@@ -42,7 +42,7 @@ func TestConditionBuilder(t *testing.T) {
op: querybuildertypesv5.FilterOperatorNotEqual,
value: "redis",
expected: "simpleJSONExtractString(labels, 'k8s.namespace.name') <> ? AND labels NOT LIKE ?",
expectedArgs: []any{"redis", `%k8s.namespace.name":"redis%`},
expectedArgs: []any{"redis", `%k8s.namespace.name%redis%`},
},
{
name: "string_like",
@@ -63,8 +63,8 @@ func TestConditionBuilder(t *testing.T) {
},
op: querybuildertypesv5.FilterOperatorNotLike,
value: "_mango%",
expected: "LOWER(simpleJSONExtractString(labels, 'k8s.namespace.name')) NOT LIKE LOWER(?)",
expectedArgs: []any{"_mango%"},
expected: "LOWER(simpleJSONExtractString(labels, 'k8s.namespace.name')) NOT LIKE LOWER(?) AND LOWER(labels) NOT LIKE LOWER(?)",
expectedArgs: []any{"_mango%", `%k8s.namespace.name%_mango%%`},
},
{
name: "string_contains",
@@ -85,8 +85,8 @@ func TestConditionBuilder(t *testing.T) {
},
op: querybuildertypesv5.FilterOperatorNotContains,
value: "banana",
expected: "LOWER(simpleJSONExtractString(labels, 'k8s.namespace.name')) NOT LIKE LOWER(?)",
expectedArgs: []any{"%banana%"},
expected: "LOWER(simpleJSONExtractString(labels, 'k8s.namespace.name')) NOT LIKE LOWER(?) AND LOWER(labels) NOT LIKE LOWER(?)",
expectedArgs: []any{"%banana%", `%k8s.namespace.name%banana%`},
},
{
name: "string_in",
@@ -97,7 +97,7 @@ func TestConditionBuilder(t *testing.T) {
op: querybuildertypesv5.FilterOperatorIn,
value: []any{"watch", "redis"},
expected: "(simpleJSONExtractString(labels, 'k8s.namespace.name') = ? OR simpleJSONExtractString(labels, 'k8s.namespace.name') = ?) AND labels LIKE ? AND (labels LIKE ? OR labels LIKE ?)",
expectedArgs: []any{"watch", "redis", "%k8s.namespace.name%", "%k8s.namespace.name\":\"watch%", "%k8s.namespace.name\":\"redis%"},
expectedArgs: []any{"watch", "redis", "%k8s.namespace.name%", "%k8s.namespace.name%watch%", "%k8s.namespace.name%redis%"},
},
{
name: "string_not_in",
@@ -108,7 +108,7 @@ func TestConditionBuilder(t *testing.T) {
op: querybuildertypesv5.FilterOperatorNotIn,
value: []any{"watch", "redis"},
expected: "(simpleJSONExtractString(labels, 'k8s.namespace.name') <> ? AND simpleJSONExtractString(labels, 'k8s.namespace.name') <> ?) AND (labels NOT LIKE ? AND labels NOT LIKE ?)",
expectedArgs: []any{"watch", "redis", "%k8s.namespace.name\":\"watch%", "%k8s.namespace.name\":\"redis%"},
expectedArgs: []any{"watch", "redis", "%k8s.namespace.name%watch%", "%k8s.namespace.name%redis%"},
},
{
name: "string_exists",

View File

@@ -69,7 +69,7 @@ func TestStatementBuilderTimeSeries(t *testing.T) {
},
expected: qbtypes.Statement{
Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), __limit_cte AS (SELECT toString(multiIf(mapContains(resources_string, 'service.name') = ?, resources_string['service.name'], NULL)) AS `service.name`, count() AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? GROUP BY `service.name` ORDER BY __result_0 DESC LIMIT ?) SELECT toStartOfInterval(fromUnixTimestamp64Nano(timestamp), INTERVAL 30 SECOND) AS ts, toString(multiIf(mapContains(resources_string, 'service.name') = ?, resources_string['service.name'], NULL)) AS `service.name`, count() AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? AND (`service.name`) GLOBAL IN (SELECT `service.name` FROM __limit_cte) GROUP BY ts, `service.name`",
Args: []any{"cartservice", "%service.name%", "%service.name\":\"cartservice%", uint64(1747945619), uint64(1747983448), true, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10, true, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448)},
Args: []any{"cartservice", "%service.name%", "%service.name%cartservice%", uint64(1747945619), uint64(1747983448), true, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10, true, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448)},
},
expectedErr: nil,
},
@@ -108,7 +108,7 @@ func TestStatementBuilderTimeSeries(t *testing.T) {
},
expected: qbtypes.Statement{
Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), __limit_cte AS (SELECT toString(multiIf(mapContains(resources_string, 'service.name') = ?, resources_string['service.name'], NULL)) AS `service.name`, count() AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? GROUP BY `service.name` ORDER BY `service.name` desc LIMIT ?) SELECT toStartOfInterval(fromUnixTimestamp64Nano(timestamp), INTERVAL 30 SECOND) AS ts, toString(multiIf(mapContains(resources_string, 'service.name') = ?, resources_string['service.name'], NULL)) AS `service.name`, count() AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? AND (`service.name`) GLOBAL IN (SELECT `service.name` FROM __limit_cte) GROUP BY ts, `service.name`",
Args: []any{"cartservice", "%service.name%", "%service.name\":\"cartservice%", uint64(1747945619), uint64(1747983448), true, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10, true, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448)},
Args: []any{"cartservice", "%service.name%", "%service.name%cartservice%", uint64(1747945619), uint64(1747983448), true, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10, true, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448)},
},
expectedErr: nil,
},
@@ -173,7 +173,7 @@ func TestStatementBuilderListQuery(t *testing.T) {
},
expected: qbtypes.Statement{
Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?) SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body, attributes_string, attributes_number, attributes_bool, resources_string, scope_string FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? LIMIT ?",
Args: []any{"cartservice", "%service.name%", "%service.name\":\"cartservice%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10},
Args: []any{"cartservice", "%service.name%", "%service.name%cartservice%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10},
},
expectedErr: nil,
},
@@ -201,7 +201,7 @@ func TestStatementBuilderListQuery(t *testing.T) {
},
expected: qbtypes.Statement{
Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?) SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body, attributes_string, attributes_number, attributes_bool, resources_string, scope_string FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? ORDER BY `attribute_string_materialized$$key$$name` AS `materialized.key.name` desc LIMIT ?",
Args: []any{"cartservice", "%service.name%", "%service.name\":\"cartservice%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10},
Args: []any{"cartservice", "%service.name%", "%service.name%cartservice%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10},
},
expectedErr: nil,
},

View File

@@ -60,7 +60,7 @@ func TestStatementBuilder(t *testing.T) {
},
expected: qbtypes.Statement{
Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), __limit_cte AS (SELECT toString(multiIf(mapContains(resources_string, 'service.name') = ?, resources_string['service.name'], NULL)) AS `service.name`, count() AS __result_0 FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? GROUP BY `service.name` ORDER BY __result_0 DESC LIMIT ?) SELECT toStartOfInterval(timestamp, INTERVAL 30 SECOND) AS ts, toString(multiIf(mapContains(resources_string, 'service.name') = ?, resources_string['service.name'], NULL)) AS `service.name`, count() AS __result_0 FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? AND (`service.name`) GLOBAL IN (SELECT `service.name` FROM __limit_cte) GROUP BY ts, `service.name`",
Args: []any{"redis-manual", "%service.name%", "%service.name\":\"redis-manual%", uint64(1747945619), uint64(1747983448), true, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10, true, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448)},
Args: []any{"redis-manual", "%service.name%", "%service.name%redis-manual%", uint64(1747945619), uint64(1747983448), true, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10, true, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448)},
},
expectedErr: nil,
},
@@ -91,7 +91,7 @@ func TestStatementBuilder(t *testing.T) {
},
expected: qbtypes.Statement{
Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), __limit_cte AS (SELECT toString(multiIf(attribute_string_http$$route <> ?, attribute_string_http$$route, NULL)) AS `httpRoute`, count() AS __result_0 FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? GROUP BY `httpRoute` ORDER BY __result_0 DESC LIMIT ?) SELECT toStartOfInterval(timestamp, INTERVAL 30 SECOND) AS ts, toString(multiIf(attribute_string_http$$route <> ?, attribute_string_http$$route, NULL)) AS `httpRoute`, count() AS __result_0 FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? AND (`httpRoute`) GLOBAL IN (SELECT `httpRoute` FROM __limit_cte) GROUP BY ts, `httpRoute`",
Args: []any{"redis-manual", "%service.name%", "%service.name\":\"redis-manual%", uint64(1747945619), uint64(1747983448), "", "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10, "", "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448)},
Args: []any{"redis-manual", "%service.name%", "%service.name%redis-manual%", uint64(1747945619), uint64(1747983448), "", "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10, "", "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448)},
},
expectedErr: nil,
},
@@ -158,7 +158,7 @@ func TestStatementBuilder(t *testing.T) {
},
expected: qbtypes.Statement{
Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), __limit_cte AS (SELECT toString(multiIf(mapContains(resources_string, 'service.name') = ?, resources_string['service.name'], NULL)) AS `service.name`, sum(multiIf(mapContains(attributes_number, 'metric.max_count') = ?, toFloat64(attributes_number['metric.max_count']), NULL)) AS __result_0 FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? GROUP BY `service.name` ORDER BY __result_0 DESC LIMIT ?) SELECT toStartOfInterval(timestamp, INTERVAL 30 SECOND) AS ts, toString(multiIf(mapContains(resources_string, 'service.name') = ?, resources_string['service.name'], NULL)) AS `service.name`, sum(multiIf(mapContains(attributes_number, 'metric.max_count') = ?, toFloat64(attributes_number['metric.max_count']), NULL)) AS __result_0 FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? AND (`service.name`) GLOBAL IN (SELECT `service.name` FROM __limit_cte) GROUP BY ts, `service.name`",
Args: []any{"redis-manual", "%service.name%", "%service.name\":\"redis-manual%", uint64(1747945619), uint64(1747983448), true, true, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10, true, true, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448)},
Args: []any{"redis-manual", "%service.name%", "%service.name%redis-manual%", uint64(1747945619), uint64(1747983448), true, true, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10, true, true, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448)},
},
expectedErr: nil,
},
@@ -187,7 +187,7 @@ func TestStatementBuilder(t *testing.T) {
},
expected: qbtypes.Statement{
Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), __limit_cte AS (SELECT toString(multiIf(mapContains(resources_string, 'service.name') = ?, resources_string['service.name'], NULL)) AS `service.name`, sum(multiIf(`attribute_number_cart$$items_count_exists` = ?, toFloat64(`attribute_number_cart$$items_count`), NULL)) AS __result_0 FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? GROUP BY `service.name` ORDER BY __result_0 DESC LIMIT ?) SELECT toStartOfInterval(timestamp, INTERVAL 30 SECOND) AS ts, toString(multiIf(mapContains(resources_string, 'service.name') = ?, resources_string['service.name'], NULL)) AS `service.name`, sum(multiIf(`attribute_number_cart$$items_count_exists` = ?, toFloat64(`attribute_number_cart$$items_count`), NULL)) AS __result_0 FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? AND (`service.name`) GLOBAL IN (SELECT `service.name` FROM __limit_cte) GROUP BY ts, `service.name`",
Args: []any{"redis-manual", "%service.name%", "%service.name\":\"redis-manual%", uint64(1747945619), uint64(1747983448), true, true, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10, true, true, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448)},
Args: []any{"redis-manual", "%service.name%", "%service.name%redis-manual%", uint64(1747945619), uint64(1747983448), true, true, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10, true, true, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448)},
},
expectedErr: nil,
},
@@ -218,7 +218,7 @@ func TestStatementBuilder(t *testing.T) {
},
expected: qbtypes.Statement{
Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), __limit_cte AS (SELECT toString(multiIf(response_status_code <> ?, response_status_code, NULL)) AS `responseStatusCode`, count() AS __result_0 FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? GROUP BY `responseStatusCode` ORDER BY __result_0 DESC LIMIT ?) SELECT toStartOfInterval(timestamp, INTERVAL 30 SECOND) AS ts, toString(multiIf(response_status_code <> ?, response_status_code, NULL)) AS `responseStatusCode`, count() AS __result_0 FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? AND (`responseStatusCode`) GLOBAL IN (SELECT `responseStatusCode` FROM __limit_cte) GROUP BY ts, `responseStatusCode`",
Args: []any{"redis-manual", "%service.name%", "%service.name\":\"redis-manual%", uint64(1747945619), uint64(1747983448), "", "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10, "", "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448)},
Args: []any{"redis-manual", "%service.name%", "%service.name%redis-manual%", uint64(1747945619), uint64(1747983448), "", "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10, "", "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448)},
},
expectedErr: nil,
},
@@ -249,7 +249,7 @@ func TestStatementBuilder(t *testing.T) {
},
expected: qbtypes.Statement{
Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), __limit_cte AS (SELECT toString(multiIf(response_status_code <> ?, response_status_code, NULL)) AS `responseStatusCode`, quantile(0.90)(multiIf(duration_nano <> ?, duration_nano, NULL)) AS __result_0 FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? GROUP BY `responseStatusCode` ORDER BY __result_0 DESC LIMIT ?) SELECT toStartOfInterval(timestamp, INTERVAL 30 SECOND) AS ts, toString(multiIf(response_status_code <> ?, response_status_code, NULL)) AS `responseStatusCode`, quantile(0.90)(multiIf(duration_nano <> ?, duration_nano, NULL)) AS __result_0 FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? AND (`responseStatusCode`) GLOBAL IN (SELECT `responseStatusCode` FROM __limit_cte) GROUP BY ts, `responseStatusCode`",
Args: []any{"redis-manual", "%service.name%", "%service.name\":\"redis-manual%", uint64(1747945619), uint64(1747983448), "", 0, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10, "", 0, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448)},
Args: []any{"redis-manual", "%service.name%", "%service.name%redis-manual%", uint64(1747945619), uint64(1747983448), "", 0, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10, "", 0, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448)},
},
expectedErr: nil,
},
@@ -344,7 +344,7 @@ func TestStatementBuilderListQuery(t *testing.T) {
},
expected: qbtypes.Statement{
Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?) SELECT name AS `name`, resources_string['service.name'] AS `service.name`, duration_nano AS `duration_nano`, `attribute_number_cart$$items_count` AS `cart.items_count`, timestamp AS `timestamp`, span_id AS `span_id`, trace_id AS `trace_id` FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? LIMIT ?",
Args: []any{"redis-manual", "%service.name%", "%service.name\":\"redis-manual%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10},
Args: []any{"redis-manual", "%service.name%", "%service.name%redis-manual%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10},
},
expectedErr: nil,
},
@@ -373,7 +373,7 @@ func TestStatementBuilderListQuery(t *testing.T) {
},
expected: qbtypes.Statement{
Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?) SELECT duration_nano AS `duration_nano`, name AS `name`, response_status_code AS `response_status_code`, `resource_string_service$$name` AS `service.name`, span_id AS `span_id`, timestamp AS `timestamp`, trace_id AS `trace_id` FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? ORDER BY attributes_string['user.id'] AS `user.id` desc LIMIT ?",
Args: []any{"redis-manual", "%service.name%", "%service.name\":\"redis-manual%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10},
Args: []any{"redis-manual", "%service.name%", "%service.name%redis-manual%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10},
},
expectedErr: nil,
},
@@ -417,7 +417,7 @@ func TestStatementBuilderListQuery(t *testing.T) {
},
expected: qbtypes.Statement{
Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?) SELECT name AS `name`, resource_string_service$$name AS `serviceName`, duration_nano AS `durationNano`, http_method AS `httpMethod`, response_status_code AS `responseStatusCode`, timestamp AS `timestamp`, span_id AS `span_id`, trace_id AS `trace_id` FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? LIMIT ?",
Args: []any{"redis-manual", "%service.name%", "%service.name\":\"redis-manual%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10},
Args: []any{"redis-manual", "%service.name%", "%service.name%redis-manual%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10},
},
expectedErr: nil,
},