From 557a7120df83c0fc298eada3e97bf8d89d1067a3 Mon Sep 17 00:00:00 2001 From: Ashwin Bhatkal Date: Mon, 1 Jun 2026 14:18:50 +0530 Subject: [PATCH 1/3] feat: v2 dashboards list page (#11404) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add useIsDashboardV2 hook * feat(dashboards-list-v2): page shell * feat(dashboards-list-v2): loading / error / empty / no-results state components * feat(dashboards-list-v2): SearchBar input component * feat(dashboards-list-v2): CreateDashboardDropdown (new / import JSON entry) * feat(dashboards-list-v2): DashboardRow + per-row ActionsPopover with v2 delete * feat(dashboards-list-v2): ListHeader with sort/order + Configure Metadata trigger * feat(dashboards-list-v2): JSON import modal (Monaco editor + sample) * feat(dashboards-list-v2): Configure Metadata modal + persisted visible-columns store * feat(dashboards-list-v2): wire list to v2 API with pagination, URL state, debounced search * chore: park v2 dashboard pages until backend lands on main * refactor(dashboards-list-v2): extract lastUpdatedLabel into utils Dedupe the relative-time formatter that was copied into both DashboardRow and ConfigureMetadataModal. Single source in DashboardsListPageV2/utils.ts. * refactor(dashboards-list-v2): group state components under states/ Move EmptyState, ErrorState, LoadingState, and NoResultsState under components/states/. They're a coherent family (interchangeable view branches in the list orchestrator) and grouping them sets up shared styling extraction next. Pure relocation — git mv preserves history; only DashboardsList.tsx's imports change. * refactor(dashboards-list-v2): extract shared state wrapper styles Pull the dashed-border card layout, body text, and learn-more CTA into states/states.module.scss. EmptyState, ErrorState, and NoResultsState now compose from the shared base and only declare what differs (padding, gap, color, font-weight). LoadingState is a different shape (skeleton stack) and stays untouched. Cascaded properties are byte-equivalent — pure de-duplication. * refactor(dashboards-list-v2): port to @signozhq/ui primitives Replace antd Button/Input usages and bespoke + + + + + } + placement="bottomRight" + arrow={false} + rootClassName="dashboardActionsPopover" + trigger="click" + > + + + ); +} + +export default ActionsPopover; diff --git a/frontend/src/pages/DashboardsListPageV2/components/ActionsPopover/DeleteActionItem.tsx b/frontend/src/pages/DashboardsListPageV2/components/ActionsPopover/DeleteActionItem.tsx new file mode 100644 index 00000000000..209d38b4540 --- /dev/null +++ b/frontend/src/pages/DashboardsListPageV2/components/ActionsPopover/DeleteActionItem.tsx @@ -0,0 +1,122 @@ +import { useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useMutation, useQueryClient } from 'react-query'; +import { Modal, Tooltip } from 'antd'; +import { Button } from '@signozhq/ui/button'; +import { CircleAlert, Trash2 } from '@signozhq/icons'; +import { toast } from '@signozhq/ui/sonner'; +import { Divider } from '@signozhq/ui/divider'; +import { Typography } from '@signozhq/ui/typography'; +import deleteDashboard from 'api/v1/dashboards/id/delete'; +import { invalidateListDashboardsV2 } from 'api/generated/services/dashboard'; +import { useAppContext } from 'providers/App/App'; +import { useErrorModal } from 'providers/ErrorModalProvider'; +import APIError from 'types/api/error'; +import { USER_ROLES } from 'types/roles'; + +import styles from './ActionsPopover.module.scss'; + +interface Props { + dashboardId: string; + dashboardName: string; + createdBy: string; + isLocked: boolean; +} + +function DeleteActionItem({ + dashboardId, + dashboardName, + createdBy, + isLocked, +}: Props): JSX.Element { + const { t } = useTranslation(['dashboard']); + const { user } = useAppContext(); + const { showErrorModal } = useErrorModal(); + const queryClient = useQueryClient(); + const [modal, contextHolder] = Modal.useModal(); + + const isAuthor = user?.email === createdBy; + const isDisabled = isLocked || (user.role === USER_ROLES.VIEWER && !isAuthor); + + const { mutate: runDelete } = useMutation({ + mutationFn: () => deleteDashboard({ id: dashboardId }), + onSuccess: async () => { + toast.success( + t('dashboard:delete_dashboard_success', { name: dashboardName }), + ); + await invalidateListDashboardsV2(queryClient); + }, + onError: (error: APIError) => { + showErrorModal(error); + }, + }); + + const openConfirm = useCallback((): void => { + const { destroy } = modal.confirm({ + title: ( + + Are you sure you want to delete the + + {' '} + {dashboardName}{' '} + + dashboard? + + ), + icon: ( + + ), + okText: 'Delete', + okButtonProps: { + danger: true, + onClick: (e): void => { + e.preventDefault(); + e.stopPropagation(); + runDelete(undefined, { onSettled: () => destroy() }); + }, + }, + centered: true, + }); + }, [modal, dashboardName, runDelete]); + + const tooltip = ((): string => { + if (!isLocked) { + return ''; + } + if (user.role === USER_ROLES.ADMIN || isAuthor) { + return t('dashboard:locked_dashboard_delete_tooltip_admin_author'); + } + return t('dashboard:locked_dashboard_delete_tooltip_editor'); + })(); + + return ( + <> + + + + + {contextHolder} + + ); +} + +export default DeleteActionItem; diff --git a/frontend/src/pages/DashboardsListPageV2/components/ConfigureMetadataModal/ConfigureMetadataModal.module.scss b/frontend/src/pages/DashboardsListPageV2/components/ConfigureMetadataModal/ConfigureMetadataModal.module.scss new file mode 100644 index 00000000000..4678ac0ca90 --- /dev/null +++ b/frontend/src/pages/DashboardsListPageV2/components/ConfigureMetadataModal/ConfigureMetadataModal.module.scss @@ -0,0 +1,164 @@ +.content { + display: flex; + flex-direction: column; + gap: 14px; +} + +.preview { + display: flex; + padding: 12px 14.634px; + flex-direction: column; + align-items: flex-start; + gap: 7.317px; + border-radius: 4px; + border: 0.915px solid var(--l1-border); + background: var(--l2-background); +} + +.previewHeader { + display: flex; + gap: 10px; + align-items: center; +} + +.previewIcon { + height: 14px; + width: 14px; +} + +.previewTitle { + color: var(--l1-foreground); + font-family: Inter; + font-size: 12.805px; + font-style: normal; + font-weight: var(--font-weight-medium); + line-height: 18.293px; + letter-spacing: -0.064px; +} + +.previewDetails { + display: flex; + flex-direction: column; + gap: 8px; + width: 100%; +} + +.previewRow { + display: flex; + justify-content: space-between; + align-items: center; +} + +.formattedTime { + display: inline-flex; + gap: 8px; + align-items: center; + color: var(--l2-foreground); +} + +.formattedTimeText { + font-family: Inter; + font-size: 12.805px; + font-style: normal; + font-weight: var(--font-weight-normal); + line-height: 16.463px; + letter-spacing: -0.064px; + color: var(--l2-foreground); +} + +.user { + display: flex; + align-items: center; + gap: 8px; +} + +.userTag { + width: 12px; + height: 12px; + display: flex; + justify-content: center; + align-items: center; + color: var(--l2-foreground); + font-size: 8px; + font-style: normal; + font-weight: var(--font-weight-normal); + line-height: normal; + letter-spacing: -0.05px; + border-radius: 12.805px; + background-color: var(--l1-background); +} + +.userLabel { + color: var(--l2-foreground); + font-family: Inter; + font-size: 12.805px; + font-weight: var(--font-weight-normal); + line-height: 16.463px; + letter-spacing: -0.064px; +} + +.action { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0px 0px 0px 14.634px; +} + +.actionLeft { + display: flex; + gap: 10px; + align-items: center; +} + +.connectionLine { + border-top: 1px dashed var(--l1-border); + min-width: 20px; + flex-grow: 1; + margin: 0px 8px; +} + +.actionRight { + display: flex; + align-items: center; +} + +.saveChanges { + display: flex; + width: 100%; + height: 32px; + padding: 8px 16px; + justify-content: center; + align-items: center; + flex-shrink: 0; + border-radius: 2px; + border: 1px solid var(--l1-border); + background: var(--l1-border); +} + +:global(.configureMetadataModalRoot) { + :global(.ant-modal-content) { + width: 500px; + flex-shrink: 0; + border-radius: 4px; + border: 1px solid var(--l1-border); + background: var(--card); + box-shadow: 0px -4px 16px 2px rgba(0, 0, 0, 0.2); + padding: 0px; + } + + :global(.ant-modal-header) { + background: var(--card); + padding: 16px; + border-bottom: 1px solid var(--l1-border); + margin-bottom: 0px; + } + + :global(.ant-modal-body) { + padding: 14px 16px; + } + + :global(.ant-modal-footer) { + margin-top: 0px; + padding: 4px 16px 16px 16px; + } +} diff --git a/frontend/src/pages/DashboardsListPageV2/components/ConfigureMetadataModal/ConfigureMetadataModal.tsx b/frontend/src/pages/DashboardsListPageV2/components/ConfigureMetadataModal/ConfigureMetadataModal.tsx new file mode 100644 index 00000000000..0d6052e6e0a --- /dev/null +++ b/frontend/src/pages/DashboardsListPageV2/components/ConfigureMetadataModal/ConfigureMetadataModal.tsx @@ -0,0 +1,218 @@ +import { useEffect, useState } from 'react'; +import { Button, Modal } from 'antd'; +import { Typography } from '@signozhq/ui/typography'; +import { Switch } from '@signozhq/ui/switch'; +import { CalendarClock, Check, Clock4 } from '@signozhq/icons'; +import { get } from 'lodash-es'; +import { Base64Icons } from 'container/DashboardContainer/DashboardSettings/General/utils'; +import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats'; +import { useTimezone } from 'providers/Timezone'; + +import { lastUpdatedLabel, type DashboardListItem } from '../../utils'; +import { + DynamicColumns, + useDashboardsListVisibleColumnsStore, + type DashboardDynamicColumns, +} from './useDynamicColumns'; + +import styles from './ConfigureMetadataModal.module.scss'; + +interface Props { + open: boolean; + previewDashboard: DashboardListItem | undefined; + onClose: () => void; +} + +function ConfigureMetadataModal({ + open, + previewDashboard, + onClose, +}: Props): JSX.Element { + const { formatTimezoneAdjustedTimestamp } = useTimezone(); + + const storedColumns = useDashboardsListVisibleColumnsStore( + (s) => s.visibleColumns, + ); + const setStoredColumns = useDashboardsListVisibleColumnsStore( + (s) => s.setVisibleColumns, + ); + const [draftColumns, setDraftColumns] = + useState(storedColumns); + + useEffect(() => { + if (open) { + setDraftColumns(storedColumns); + } + }, [open, storedColumns]); + + const handleSave = (): void => { + setStoredColumns(draftColumns); + onClose(); + }; + + const previewImage = previewDashboard?.image || Base64Icons[0]; + const previewName = previewDashboard?.spec?.display?.name; + const previewCreatedBy = previewDashboard?.createdBy; + const previewUpdatedBy = previewDashboard?.updatedBy; + const previewUpdatedAt = previewDashboard?.updatedAt; + + const formattedCreatedAt = previewDashboard + ? formatTimezoneAdjustedTimestamp( + get(previewDashboard, 'createdAt', '') as string, + DATE_TIME_FORMATS.DASH_DATETIME_UTC, + ) + : ''; + + return ( + } + className={styles.saveChanges} + onClick={handleSave} + > + Save Changes + + } + rootClassName="configureMetadataModalRoot" + > +
+
+
+ dashboard-image + + {previewName} + +
+
+
+ {draftColumns.createdAt && ( + + + + {formattedCreatedAt} + + + )} + {draftColumns.createdBy && ( +
+ + {previewCreatedBy?.substring(0, 1).toUpperCase()} + + + {previewCreatedBy} + +
+ )} +
+
+ {draftColumns.updatedAt && ( + + + + {lastUpdatedLabel(previewUpdatedAt)} + + + )} + {draftColumns.updatedBy && ( +
+ + {previewUpdatedBy?.substring(0, 1).toUpperCase()} + + + {previewUpdatedBy} + +
+ )} +
+
+
+ +
+
+ + Created at +
+
+
+ + setDraftColumns((prev) => ({ + ...prev, + [DynamicColumns.CREATED_AT]: check, + })) + } + /> +
+
+
+
+ + Created by +
+
+
+ + setDraftColumns((prev) => ({ + ...prev, + [DynamicColumns.CREATED_BY]: check, + })) + } + /> +
+
+
+
+ + Updated at +
+
+
+ + setDraftColumns((prev) => ({ + ...prev, + [DynamicColumns.UPDATED_AT]: check, + })) + } + /> +
+
+
+
+ + Updated by +
+
+
+ + setDraftColumns((prev) => ({ + ...prev, + [DynamicColumns.UPDATED_BY]: check, + })) + } + /> +
+
+
+ + ); +} + +export default ConfigureMetadataModal; diff --git a/frontend/src/pages/DashboardsListPageV2/components/ConfigureMetadataModal/useDynamicColumns.ts b/frontend/src/pages/DashboardsListPageV2/components/ConfigureMetadataModal/useDynamicColumns.ts new file mode 100644 index 00000000000..fe16deb76a4 --- /dev/null +++ b/frontend/src/pages/DashboardsListPageV2/components/ConfigureMetadataModal/useDynamicColumns.ts @@ -0,0 +1,52 @@ +import { create } from 'zustand'; +import { persist } from 'zustand/middleware'; +import { LOCALSTORAGE } from 'constants/localStorage'; + +export interface DashboardDynamicColumns { + createdAt: boolean; + createdBy: boolean; + updatedAt: boolean; + updatedBy: boolean; +} + +export enum DynamicColumns { + CREATED_AT = 'createdAt', + CREATED_BY = 'createdBy', + UPDATED_AT = 'updatedAt', + UPDATED_BY = 'updatedBy', +} + +const DEFAULT_COLUMNS: DashboardDynamicColumns = { + createdAt: true, + createdBy: true, + updatedAt: false, + updatedBy: false, +}; + +interface DashboardsListVisibleColumnsState { + visibleColumns: DashboardDynamicColumns; + setVisibleColumns: (next: DashboardDynamicColumns) => void; +} + +export const useDashboardsListVisibleColumnsStore = + create()( + persist( + (set) => ({ + visibleColumns: DEFAULT_COLUMNS, + setVisibleColumns: (next): void => { + set({ visibleColumns: next }); + }, + }), + { + name: LOCALSTORAGE.DASHBOARDS_LIST_VISIBLE_COLUMNS, + merge: (persisted, current) => ({ + ...current, + visibleColumns: { + ...DEFAULT_COLUMNS, + ...((persisted as Partial) + ?.visibleColumns ?? {}), + }, + }), + }, + ), + ); diff --git a/frontend/src/pages/DashboardsListPageV2/components/CreateDashboardDropdown/CreateDashboardDropdown.module.scss b/frontend/src/pages/DashboardsListPageV2/components/CreateDashboardDropdown/CreateDashboardDropdown.module.scss new file mode 100644 index 00000000000..cce5b11fbe1 --- /dev/null +++ b/frontend/src/pages/DashboardsListPageV2/components/CreateDashboardDropdown/CreateDashboardDropdown.module.scss @@ -0,0 +1,34 @@ +.menuItem { + display: flex; + align-items: center; + gap: 8px; +} + +.templatesItem { + display: flex; + justify-content: space-between; + align-items: center; + gap: 8px; + width: 100%; +} + +.primaryButton { + padding: 6px 12px; +} + +.textButton { + display: flex; + width: 153px; + align-items: center; + height: 32px; + padding: 6px 12px; + justify-content: center; + gap: 6px; + border-radius: 2px; + background: var(--primary-background); + color: var(--l1-foreground); +} + +:global(.createDashboardMenuOverlay) { + width: 200px; +} diff --git a/frontend/src/pages/DashboardsListPageV2/components/CreateDashboardDropdown/CreateDashboardDropdown.tsx b/frontend/src/pages/DashboardsListPageV2/components/CreateDashboardDropdown/CreateDashboardDropdown.tsx new file mode 100644 index 00000000000..d1debd18c5e --- /dev/null +++ b/frontend/src/pages/DashboardsListPageV2/components/CreateDashboardDropdown/CreateDashboardDropdown.tsx @@ -0,0 +1,119 @@ +import { useMemo } from 'react'; +// eslint-disable-next-line signoz/no-antd-components -- TODO: migrate Dropdown to @signozhq/ui/dropdown-menu +import { Button, Dropdown, MenuProps } from 'antd'; +import cx from 'classnames'; +import logEvent from 'api/common/logEvent'; +import { + ExternalLink, + Github, + LayoutGrid, + Plus, + Radius, +} from '@signozhq/icons'; + +import styles from './CreateDashboardDropdown.module.scss'; + +interface Props { + canCreate: boolean; + onCreate: () => void; + onImportJSON: () => void; + variant?: 'primary' | 'text'; +} + +const TEMPLATES_HREF = + 'https://signoz.io/docs/dashboards/dashboard-templates/overview/'; + +function CreateDashboardDropdown({ + canCreate, + onCreate, + onImportJSON, + variant = 'primary', +}: Props): JSX.Element { + const items: MenuProps['items'] = useMemo(() => { + const menuItems: MenuProps['items'] = [ + { + key: 'import-json', + label: ( +
+ Import JSON +
+ ), + }, + { + key: 'view-templates', + label: ( + +
+
+ View templates +
+ +
+
+ ), + }, + ]; + + if (canCreate) { + menuItems.unshift({ + key: 'create-dashboard', + label: ( +
+ Create dashboard +
+ ), + }); + } + + return menuItems; + }, [canCreate, onCreate, onImportJSON]); + + return ( + + {variant === 'primary' ? ( + + ) : ( + + )} + + ); +} + +export default CreateDashboardDropdown; diff --git a/frontend/src/pages/DashboardsListPageV2/components/DashboardRow/DashboardRow.module.scss b/frontend/src/pages/DashboardsListPageV2/components/DashboardRow/DashboardRow.module.scss new file mode 100644 index 00000000000..2835c79195d --- /dev/null +++ b/frontend/src/pages/DashboardsListPageV2/components/DashboardRow/DashboardRow.module.scss @@ -0,0 +1,152 @@ +.row { + padding: 12px 16px 16px 16px; + border: 1px solid var(--l1-border); + border-top: none; + background: var(--l2-background); + cursor: pointer; +} + +.titleWithAction { + display: flex; + justify-content: space-between; + align-items: center; + gap: 8px; + min-height: 24px; +} + +.titleBlock { + display: flex; + align-items: center; + gap: 6px; + line-height: 20px; + flex: 1 1 auto; + min-width: 0; +} + +.titleLink { + display: flex; + align-items: center; + gap: 8px; +} + +.icon { + display: inline-block; + line-height: 20px; + height: 14px; + width: 14px; +} + +.title { + color: var(--l1-foreground); + font-size: var(--font-size-sm); + font-style: normal; + font-weight: var(--font-weight-medium); + line-height: 20px; + letter-spacing: -0.07px; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.tagsWithActions { + display: flex; + align-items: center; + flex: 0 1 auto; + min-width: 0; + justify-content: flex-end; +} + +.tags { + display: flex; + flex-wrap: wrap; + gap: 8px; + justify-content: flex-end; +} + +.tag { + display: flex; + padding: 4px 8px; + justify-content: center; + align-items: center; + gap: 4px; + height: 28px; + border-radius: 20px; + border: 1px solid color-mix(in srgb, var(--bg-sienna-500) 20%, transparent); + background: color-mix(in srgb, var(--bg-sienna-500) 10%, transparent); + color: var(--bg-sienna-400); + text-align: center; + font-family: Inter; + font-size: var(--font-size-sm); + font-style: normal; + font-weight: var(--font-weight-normal); + line-height: 20px; + letter-spacing: -0.07px; + margin-inline-end: 0px; +} + +.details { + margin-top: 12px; + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 12px 24px; +} + +.createdAt { + display: flex; + align-items: center; + gap: 6px; + color: var(--l2-foreground); + font-family: Inter; + font-size: var(--font-size-xs); + font-weight: var(--font-weight-normal); + line-height: 18px; + letter-spacing: -0.07px; +} + +.createdBy { + display: flex; + align-items: center; + gap: 8px; +} + +.avatar { + width: 14px; + height: 14px; + border-radius: 50px; + background: var(--l1-border); + display: flex; + justify-content: center; + align-items: center; +} + +.avatarText { + color: var(--l2-foreground); + font-size: 8px; + font-weight: var(--font-weight-normal); + line-height: normal; + letter-spacing: -0.05px; +} + +.byLabel { + color: var(--l2-foreground); + font-family: Inter; + font-size: var(--font-size-xs); + font-weight: var(--font-weight-normal); + line-height: 18px; + letter-spacing: -0.07px; +} + +.updatedBy { + display: flex; + align-items: center; + gap: 6px; +} + +:global(.titleTooltipOverlay) { + :global(.ant-tooltip-content) :global(.ant-tooltip-inner) { + max-height: 400px; + overflow: auto; + } +} diff --git a/frontend/src/pages/DashboardsListPageV2/components/DashboardRow/DashboardRow.tsx b/frontend/src/pages/DashboardsListPageV2/components/DashboardRow/DashboardRow.tsx new file mode 100644 index 00000000000..fd422d8e999 --- /dev/null +++ b/frontend/src/pages/DashboardsListPageV2/components/DashboardRow/DashboardRow.tsx @@ -0,0 +1,154 @@ +import { Tooltip } from 'antd'; +import { Typography } from '@signozhq/ui/typography'; +import { Badge } from '@signozhq/ui/badge'; +import { CalendarClock } from '@signozhq/icons'; +import logEvent from 'api/common/logEvent'; +import { generatePath } from 'react-router-dom'; +import { Base64Icons } from 'container/DashboardContainer/DashboardSettings/General/utils'; +import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats'; +import ROUTES from 'constants/routes'; +import { useSafeNavigate } from 'hooks/useSafeNavigate'; +import { useTimezone } from 'providers/Timezone'; +import { isModifierKeyPressed } from 'utils/app'; + +import type { DashboardListItem } from '../../utils'; +import { lastUpdatedLabel, tagsToStrings } from '../../utils'; +import ActionsPopover from '../ActionsPopover/ActionsPopover'; + +import styles from './DashboardRow.module.scss'; + +interface Props { + dashboard: DashboardListItem; + index: number; + canAct: boolean; + showUpdatedAt: boolean; + showUpdatedBy: boolean; +} + +function DashboardRow({ + dashboard, + index, + canAct, + showUpdatedAt, + showUpdatedBy, +}: Props): JSX.Element { + const { safeNavigate } = useSafeNavigate(); + const { formatTimezoneAdjustedTimestamp } = useTimezone(); + + const id = dashboard.id; + const name = dashboard.spec?.display?.name ?? ''; + const image = dashboard.image || Base64Icons[0]; + const createdBy = dashboard.createdBy ?? ''; + const updatedBy = dashboard.updatedBy ?? ''; + const createdAt = dashboard.createdAt ?? ''; + const updatedAt = dashboard.updatedAt ?? ''; + const isLocked = !!dashboard.locked; + const tags = tagsToStrings(dashboard.tags); + + const link = generatePath(ROUTES.DASHBOARD, { dashboardId: id }); + const formattedCreatedAt = formatTimezoneAdjustedTimestamp( + createdAt, + DATE_TIME_FORMATS.DASH_DATETIME_UTC, + ); + + const onClickHandler = (event: React.MouseEvent): void => { + event.stopPropagation(); + safeNavigate(link, { newTab: isModifierKeyPressed(event) }); + logEvent('Dashboard List: Clicked on dashboard', { + dashboardId: id, + dashboardName: name, + }); + }; + + return ( +
+
+
+ 50 ? name : ''} + placement="left" + overlayClassName="titleTooltipOverlay" + > +
+ dashboard-image + + {name} + +
+
+
+ +
+ {tags.length > 0 && ( +
+ {tags.slice(0, 3).map((tag) => ( + + {tag} + + ))} + {tags.length > 3 && ( + + + {tags.length - 3} + + )} +
+ )} +
+ + {canAct && ( + + )} +
+
+
+ + {formattedCreatedAt} +
+ + {createdBy && ( +
+
+ + {createdBy.substring(0, 1).toUpperCase()} + +
+ {createdBy} +
+ )} + + {showUpdatedAt && ( +
+ + {lastUpdatedLabel(updatedAt)} +
+ )} + + {updatedBy && showUpdatedBy && ( +
+ + Last Updated By - + +
+ + {updatedBy.substring(0, 1).toUpperCase()} + +
+ {updatedBy} +
+ )} +
+
+ ); +} + +export default DashboardRow; diff --git a/frontend/src/pages/DashboardsListPageV2/components/DashboardsList/DashboardsList.module.scss b/frontend/src/pages/DashboardsListPageV2/components/DashboardsList/DashboardsList.module.scss new file mode 100644 index 00000000000..afdcb983612 --- /dev/null +++ b/frontend/src/pages/DashboardsListPageV2/components/DashboardsList/DashboardsList.module.scss @@ -0,0 +1,96 @@ +.container { + margin-top: 30px; + margin-bottom: 30px; + display: flex; + justify-content: center; + width: 100%; +} + +.viewContent { + width: calc(100% - 30px); + max-width: 836px; + + :global(.ant-table-wrapper) :global(.ant-table-cell) { + padding: 0 !important; + border: none !important; + background: var(--l1-background) !important; + } + + :global(.ant-table-wrapper) + :global(.ant-table-tbody) + :global(.ant-table-row) + :global(.ant-table-cell) + > div { + // Row content is the only child of the td; it carries the borders. + } + + :global(.ant-table-wrapper) + :global(.ant-table-tbody) + :global(.ant-table-row:last-child) + :global(.ant-table-cell) + > div { + border-radius: 0 0 6px 6px; + } + + :global(.ant-pagination-item) { + display: flex; + justify-content: center; + align-items: center; + } + + :global(.ant-pagination-item) > a { + color: var(--l2-foreground); + font-size: var(--font-size-sm); + font-weight: var(--font-weight-normal); + line-height: 20px; + } + + :global(.ant-pagination-item-active) { + background-color: var(--primary-background); + } + + :global(.ant-pagination-item-active) > a { + color: var(--foreground) !important; + font-weight: var(--font-weight-medium); + } +} + +.titleContainer { + display: flex; + flex-direction: column; + gap: 4px; +} + +.title { + color: var(--l1-foreground); + font-size: var(--font-size-lg); + font-style: normal; + font-weight: var(--font-weight-normal); + line-height: 28px; + letter-spacing: -0.09px; +} + +.subtitle { + color: var(--l2-foreground); + font-size: var(--font-size-sm); + font-style: normal; + font-weight: var(--font-weight-normal); + line-height: 20px; + letter-spacing: -0.07px; +} + +.integrationsContainer { + margin: 16px 0; +} + +.integrationsContent { + max-width: 100%; + width: 100%; +} + +.toolbar { + display: flex; + align-items: center; + gap: 8px; + margin: 16px 0; +} diff --git a/frontend/src/pages/DashboardsListPageV2/components/DashboardsList/DashboardsList.tsx b/frontend/src/pages/DashboardsListPageV2/components/DashboardsList/DashboardsList.tsx new file mode 100644 index 00000000000..de1f09f421f --- /dev/null +++ b/frontend/src/pages/DashboardsListPageV2/components/DashboardsList/DashboardsList.tsx @@ -0,0 +1,277 @@ +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { generatePath } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; +import { Typography } from '@signozhq/ui/typography'; +import { AxiosError } from 'axios'; +import logEvent from 'api/common/logEvent'; +import { + createDashboardV2, + useListDashboardsV2, +} from 'api/generated/services/dashboard'; +import ROUTES from 'constants/routes'; +import { RequestDashboardBtn } from 'container/ListOfDashboard/RequestDashboardBtn'; +import useComponentPermission from 'hooks/useComponentPermission'; +import { toast } from '@signozhq/ui/sonner'; +import { useGetTenantLicense } from 'hooks/useGetTenantLicense'; +import { useSafeNavigate } from 'hooks/useSafeNavigate'; +import { useAppContext } from 'providers/App/App'; +import { useErrorModal } from 'providers/ErrorModalProvider'; +import APIError from 'types/api/error'; +import { toAPIError } from 'utils/errorUtils'; + +import { + usePage, + useSearch, + useSortColumn, + useSortOrder, + type SortColumn, + type SortOrder, +} from '../../hooks/useDashboardsListQueryParams'; +import type { DashboardListItem } from '../../utils'; +import ConfigureMetadataModal from '../ConfigureMetadataModal/ConfigureMetadataModal'; +import { useDashboardsListVisibleColumnsStore } from '../ConfigureMetadataModal/useDynamicColumns'; +import CreateDashboardDropdown from '../CreateDashboardDropdown/CreateDashboardDropdown'; +import ImportJSONModal from '../ImportJSONModal/ImportJSONModal'; +import ListHeader from '../ListHeader/ListHeader'; +import EmptyState from '../states/EmptyState/EmptyState'; +import ErrorState from '../states/ErrorState/ErrorState'; +import LoadingState from '../states/LoadingState/LoadingState'; +import NoResultsState from '../states/NoResultsState/NoResultsState'; +import SearchBar from '../SearchBar/SearchBar'; +import DashboardsListContent from './DashboardsListContent'; + +import styles from './DashboardsList.module.scss'; + +const PAGE_SIZE = 20; + +function DashboardsList(): JSX.Element { + const { safeNavigate } = useSafeNavigate(); + const { t } = useTranslation('dashboard'); + const { showErrorModal } = useErrorModal(); + const { isCloudUser } = useGetTenantLicense(); + + const { user } = useAppContext(); + const [action, canCreateNewDashboard] = useComponentPermission( + ['action', 'create_new_dashboards'], + user.role, + ); + + const [searchString, setSearchString] = useSearch(); + const [sortColumn, setSortColumn] = useSortColumn(); + const [sortOrder, setSortOrder] = useSortOrder(); + const [page, setPage] = usePage(); + + const [searchInput, setSearchInput] = useState(searchString); + + // Keep the local input in sync with external searchString changes + // (browser back/forward, deep link). User typing only mutates + // searchInput, so this won't fight with in-flight edits. + useEffect(() => { + setSearchInput(searchString); + }, [searchString]); + + const handleSubmitSearch = useCallback((): void => { + const next = searchInput.trim(); + if (next === searchString) { + return; + } + void setSearchString(next); + void setPage(1); + }, [searchInput, searchString, setSearchString, setPage]); + + const listParams = useMemo( + () => ({ + query: searchString.trim() || undefined, + sort: sortColumn, + order: sortOrder, + limit: PAGE_SIZE, + offset: (page - 1) * PAGE_SIZE, + }), + [searchString, sortColumn, sortOrder, page], + ); + + const { + data: response, + isLoading, + isFetching, + error, + refetch, + } = useListDashboardsV2(listParams, { query: { keepPreviousData: true } }); + + const apiError = useMemo( + () => (error ? toAPIError(error) : undefined), + [error], + ); + const errorHttpStatus = apiError?.getHttpStatusCode(); + const errorMessage = apiError?.getErrorMessage(); + + const dashboards = useMemo( + () => response?.data?.dashboards ?? [], + [response], + ); + const total = response?.data?.total ?? 0; + + const [isImportOpen, setIsImportOpen] = useState(false); + const [isConfigureOpen, setIsConfigureOpen] = useState(false); + const visibleColumns = useDashboardsListVisibleColumnsStore( + (s) => s.visibleColumns, + ); + + const [creating, setCreating] = useState(false); + + const handleCreateNew = useCallback(async (): Promise => { + try { + logEvent('Dashboard List: Create dashboard clicked', {}); + setCreating(true); + const created = await createDashboardV2({ + schemaVersion: 'v6', + // Backend requires `name` (immutable, server-side identifier); + // asking it to generate one keeps the UI's "new dashboard" flow. + generateName: true, + tags: null, + spec: { + display: { name: t('new_dashboard_title', { ns: 'dashboard' }) }, + }, + }); + safeNavigate( + generatePath(ROUTES.DASHBOARD, { dashboardId: created.data.id }), + ); + } catch (e) { + showErrorModal(e as APIError); + toast.error((e as AxiosError).toString() || 'Failed to create dashboard'); + } finally { + setCreating(false); + } + }, [safeNavigate, showErrorModal, t]); + + const handleImportToggle = useCallback((): void => { + logEvent('Dashboard List V2: Import JSON clicked', {}); + setIsImportOpen((s) => !s); + }, []); + + const onSortChange = useCallback( + (column: SortColumn): void => { + void setSortColumn(column); + void setPage(1); + }, + [setSortColumn, setPage], + ); + + const onOrderChange = useCallback( + (order: SortOrder): void => { + void setSortOrder(order); + void setPage(1); + }, + [setSortOrder, setPage], + ); + + const visitLoggedRef = useRef(false); + useEffect(() => { + if (!visitLoggedRef.current && !isLoading && response !== undefined) { + logEvent('Dashboard List V2: Page visited', { number: dashboards.length }); + visitLoggedRef.current = true; + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isLoading]); + + return ( +
+
+
+ Dashboards + + Create and manage dashboards for your workspace. + + {isCloudUser && ( +
+
+ +
+
+ )} +
+ + {isLoading ? ( + + ) : !error && dashboards.length === 0 && !searchString && page === 1 ? ( + + ) : null + } + /> + ) : ( + <> +
+ + {canCreateNewDashboard && ( + + )} +
+ + {error ? ( + { + refetch(); + }} + httpStatus={errorHttpStatus} + errorMessage={errorMessage} + /> + ) : dashboards.length === 0 ? ( + + ) : ( + <> + setIsConfigureOpen(true)} + /> + + + )} + + )} + + setIsImportOpen(false)} + /> + + setIsConfigureOpen(false)} + /> +
+
+ ); +} + +export default DashboardsList; diff --git a/frontend/src/pages/DashboardsListPageV2/components/DashboardsList/DashboardsListContent.tsx b/frontend/src/pages/DashboardsListPageV2/components/DashboardsList/DashboardsListContent.tsx new file mode 100644 index 00000000000..c6ba0921189 --- /dev/null +++ b/frontend/src/pages/DashboardsListPageV2/components/DashboardsList/DashboardsListContent.tsx @@ -0,0 +1,71 @@ +import { useMemo } from 'react'; +import { Table } from 'antd'; +import type { TableProps } from 'antd/lib'; + +import type { DashboardListItem } from '../../utils'; +import DashboardRow from '../DashboardRow/DashboardRow'; + +interface Props { + dashboards: DashboardListItem[]; + page: number; + pageSize: number; + total: number; + onPageChange: (page: number) => void; + canAct: boolean; + showUpdatedAt: boolean; + showUpdatedBy: boolean; + loading: boolean; +} + +function DashboardsListContent({ + dashboards, + page, + pageSize, + total, + onPageChange, + canAct, + showUpdatedAt, + showUpdatedBy, + loading, +}: Props): JSX.Element { + const columns: TableProps['columns'] = useMemo( + () => [ + { + title: 'Dashboards', + key: 'dashboard', + render: (_, dashboard, index): JSX.Element => ( + + ), + }, + ], + [canAct, showUpdatedAt, showUpdatedBy], + ); + + const paginationConfig = total > pageSize && { + pageSize, + showSizeChanger: false, + onChange: onPageChange, + current: page, + total, + hideOnSinglePage: true, + }; + + return ( + ({ ...d, key: d.id }))} + showSorterTooltip + loading={loading} + showHeader={false} + pagination={paginationConfig} + /> + ); +} + +export default DashboardsListContent; diff --git a/frontend/src/pages/DashboardsListPageV2/components/DashboardsList/index.tsx b/frontend/src/pages/DashboardsListPageV2/components/DashboardsList/index.tsx new file mode 100644 index 00000000000..03cc6ba5631 --- /dev/null +++ b/frontend/src/pages/DashboardsListPageV2/components/DashboardsList/index.tsx @@ -0,0 +1,3 @@ +import DashboardsList from './DashboardsList'; + +export default DashboardsList; diff --git a/frontend/src/pages/DashboardsListPageV2/components/ImportJSONModal/ImportJSONModal.module.scss b/frontend/src/pages/DashboardsListPageV2/components/ImportJSONModal/ImportJSONModal.module.scss new file mode 100644 index 00000000000..3e7238b1078 --- /dev/null +++ b/frontend/src/pages/DashboardsListPageV2/components/ImportJSONModal/ImportJSONModal.module.scss @@ -0,0 +1,73 @@ +.contentContainer { + display: flex; + flex-direction: column; +} + +.contentHeader { + padding: 16px; + display: flex; + align-items: center; + justify-content: space-between; + border-bottom: 1px solid var(--l1-border); +} + +.footer { + display: flex; + flex-direction: column; + gap: 8px; +} + +.jsonError { + display: flex; + align-items: center; + gap: 8px; +} + +.errorText { + color: var(--warning-background); +} + +.actions { + display: flex; + justify-content: space-between; + align-items: center; +} + +:global(.importJsonModalWrapper) { + :global(.ant-modal-content) { + border-radius: 4px; + border: 1px solid var(--l1-border); + background: linear-gradient( + 139deg, + color-mix(in srgb, var(--card) 80%, transparent) 0%, + color-mix(in srgb, var(--card) 90%, transparent) 98.68% + ); + box-shadow: 4px 10px 16px 2px rgba(0, 0, 0, 0.2); + backdrop-filter: blur(20px); + padding: 0; + } + + :global(.margin) { + background: linear-gradient( + 139deg, + color-mix(in srgb, var(--card) 80%, transparent) 0%, + color-mix(in srgb, var(--card) 90%, transparent) 98.68% + ); + backdrop-filter: blur(20px); + } + + :global(.view-lines) { + background: linear-gradient( + 139deg, + color-mix(in srgb, var(--card) 80%, transparent) 0%, + color-mix(in srgb, var(--card) 90%, transparent) 98.68% + ); + backdrop-filter: blur(20px); + } + + :global(.ant-modal-footer) { + margin-top: 0; + padding: 16px; + border-top: 1px solid var(--l1-border); + } +} diff --git a/frontend/src/pages/DashboardsListPageV2/components/ImportJSONModal/ImportJSONModal.tsx b/frontend/src/pages/DashboardsListPageV2/components/ImportJSONModal/ImportJSONModal.tsx new file mode 100644 index 00000000000..3a9d3200249 --- /dev/null +++ b/frontend/src/pages/DashboardsListPageV2/components/ImportJSONModal/ImportJSONModal.tsx @@ -0,0 +1,223 @@ +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { generatePath } from 'react-router-dom'; +import { red } from '@ant-design/colors'; +import MEditor, { Monaco } from '@monaco-editor/react'; +import { Color } from '@signozhq/design-tokens'; +import { Button, Flex, Modal, Upload, UploadProps } from 'antd'; +import { toast } from '@signozhq/ui/sonner'; +import { Typography } from '@signozhq/ui/typography'; +import { + CircleAlert, + ExternalLink, + Github, + MonitorDot, + MoveRight, + Sparkles, +} from '@signozhq/icons'; +import logEvent from 'api/common/logEvent'; +import { createDashboardV2 } from 'api/generated/services/dashboard'; +import ROUTES from 'constants/routes'; +import { useIsDarkMode } from 'hooks/useDarkMode'; +import { useSafeNavigate } from 'hooks/useSafeNavigate'; +import { useErrorModal } from 'providers/ErrorModalProvider'; +import APIError from 'types/api/error'; + +import sampleDashboard from './sampleDashboard.json'; + +import styles from './ImportJSONModal.module.scss'; +import { normalizeToPostable } from './ImportJSONModalUtils'; + +interface Props { + open: boolean; + onClose: () => void; +} + +function ImportJSONModal({ open, onClose }: Props): JSX.Element { + const { safeNavigate } = useSafeNavigate(); + const { t } = useTranslation(['dashboard', 'common']); + const [isUploadError, setIsUploadError] = useState(false); + const [isCreateError, setIsCreateError] = useState(false); + const [isCreating, setIsCreating] = useState(false); + const [editorValue, setEditorValue] = useState(''); + + const { showErrorModal } = useErrorModal(); + const isDarkMode = useIsDarkMode(); + + const handleUpload: UploadProps['onChange'] = (info) => { + const lastFile = info.fileList[info.fileList.length - 1]; + if (!lastFile?.originFileObj) { + return; + } + const reader = new FileReader(); + reader.onload = (event): void => { + try { + const target = event.target?.result; + if (!target) { + return; + } + const parsed = JSON.parse(target.toString()); + setEditorValue(JSON.stringify(parsed, null, 2)); + setIsUploadError(false); + } catch { + setIsUploadError(true); + } + }; + reader.readAsText(lastFile.originFileObj); + }; + + const handleImport = async (): Promise => { + try { + setIsCreating(true); + logEvent('Dashboard List V2: Import and next clicked', {}); + const parsed = JSON.parse(editorValue) as Record; + const payload = normalizeToPostable(parsed); + const response = await createDashboardV2(payload); + safeNavigate( + generatePath(ROUTES.DASHBOARD, { dashboardId: response.data.id }), + ); + logEvent('Dashboard List V2: New dashboard imported successfully', { + dashboardId: response.data?.id, + }); + } catch (error) { + showErrorModal(error as APIError); + setIsCreateError(true); + toast.error( + error instanceof Error ? error.message : t('error_loading_json'), + ); + } finally { + setIsCreating(false); + } + }; + + const handleClose = (): void => { + setIsUploadError(false); + setIsCreateError(false); + onClose(); + }; + + const setEditorTheme = (monaco: Monaco): void => { + monaco.editor.defineTheme('my-theme', { + base: 'vs-dark', + inherit: true, + rules: [ + { token: 'string.key.json', foreground: Color.BG_VANILLA_400 }, + { token: 'string.value.json', foreground: Color.BG_ROBIN_400 }, + ], + colors: { 'editor.background': Color.BG_INK_300 }, + }); + }; + + const renderError = (msg: string): JSX.Element => ( +
+ + {msg} +
+ ); + + return ( + + {isCreateError && renderError(t('error_loading_json'))} + {isUploadError && renderError(t('error_upload_json'))} + +
+ + false} + action="none" + > + + + + + + + + + +
+ + } + > +
+
+ {t('import_json')} +
+ setEditorValue(newValue || '')} + value={editorValue} + options={{ + scrollbar: { alwaysConsumeMouseWheel: false }, + minimap: { enabled: false }, + fontSize: 14, + fontFamily: 'Space Mono', + }} + theme={isDarkMode ? 'my-theme' : 'light'} + onMount={(_, monaco): void => { + document.fonts.ready.then(() => { + monaco.editor.remeasureFonts(); + }); + }} + beforeMount={setEditorTheme} + /> +
+
+ ); +} + +export default ImportJSONModal; diff --git a/frontend/src/pages/DashboardsListPageV2/components/ImportJSONModal/ImportJSONModalUtils.ts b/frontend/src/pages/DashboardsListPageV2/components/ImportJSONModal/ImportJSONModalUtils.ts new file mode 100644 index 00000000000..e712902f7c4 --- /dev/null +++ b/frontend/src/pages/DashboardsListPageV2/components/ImportJSONModal/ImportJSONModalUtils.ts @@ -0,0 +1,50 @@ +import { + DashboardtypesDashboardSpecDTO, + DashboardtypesPostableDashboardV2DTO, + TagtypesPostableTagDTO, +} from 'api/generated/services/sigNoz.schemas'; + +// Accept either a complete PostableDashboardV2 (flat shape with `spec` and +// top-level `name` / `image` / `tags` / `schemaVersion`) or a bare spec — wrap +// the latter with defaults so users can paste either shape that exists in the +// wild (e.g. testdata/perses.json is a bare spec). The legacy nested +// `{ metadata: { ... }, spec }` shape is also accepted and flattened. +// +// The backend requires `name` (immutable identifier); if the payload doesn't +// carry one, fall back to `generateName: true` so the server assigns one. +export function normalizeToPostable( + parsed: Record, +): DashboardtypesPostableDashboardV2DTO { + const hasSpec = 'spec' in parsed; + const legacyMeta = parsed.metadata as + | { + schemaVersion?: string; + name?: string; + image?: string; + tags?: TagtypesPostableTagDTO[] | null; + } + | undefined; + + const resolvedName = (parsed.name as string | undefined) ?? legacyMeta?.name; + + if (hasSpec) { + return { + schemaVersion: + (parsed.schemaVersion as string) || legacyMeta?.schemaVersion || 'v6', + ...(resolvedName ? { name: resolvedName } : { generateName: true }), + image: (parsed.image as string) ?? legacyMeta?.image, + tags: + (parsed.tags as TagtypesPostableTagDTO[] | null) ?? + legacyMeta?.tags ?? + null, + spec: parsed.spec as DashboardtypesDashboardSpecDTO, + }; + } + + return { + schemaVersion: 'v6', + generateName: true, + tags: null, + spec: parsed as unknown as DashboardtypesDashboardSpecDTO, + }; +} diff --git a/frontend/src/pages/DashboardsListPageV2/components/ImportJSONModal/sampleDashboard.json b/frontend/src/pages/DashboardsListPageV2/components/ImportJSONModal/sampleDashboard.json new file mode 100644 index 00000000000..1f10322e194 --- /dev/null +++ b/frontend/src/pages/DashboardsListPageV2/components/ImportJSONModal/sampleDashboard.json @@ -0,0 +1,154 @@ +{ + "display": { + "name": "NV dashboard with sections", + "description": "" + }, + "datasources": { + "SigNozDatasource": { + "default": true, + "plugin": { + "kind": "signoz/Datasource", + "spec": {} + } + } + }, + "panels": { + "b424e23b": { + "kind": "Panel", + "spec": { + "display": { + "name": "" + }, + "plugin": { + "kind": "signoz/NumberPanel", + "spec": { + "formatting": { + "unit": "s", + "decimalPrecision": "2" + } + } + }, + "queries": [ + { + "kind": "TimeSeriesQuery", + "spec": { + "plugin": { + "kind": "signoz/BuilderQuery", + "spec": { + "name": "A", + "signal": "metrics", + "aggregations": [ + { + "metricName": "container.cpu.time", + "reduceTo": "sum", + "spaceAggregation": "sum", + "timeAggregation": "rate" + } + ], + "filter": { + "expression": "" + } + } + } + } + } + ] + } + }, + "251df4d5": { + "kind": "Panel", + "spec": { + "display": { + "name": "" + }, + "plugin": { + "kind": "signoz/TimeSeriesPanel", + "spec": { + "visualization": { + "fillSpans": false + }, + "formatting": { + "unit": "recommendations", + "decimalPrecision": "2" + }, + "chartAppearance": { + "lineInterpolation": "spline", + "showPoints": false, + "lineStyle": "solid", + "fillMode": "none", + "spanGaps": {"fillOnlyBelow": true} + }, + "legend": { + "position": "bottom" + } + } + }, + "queries": [ + { + "kind": "TimeSeriesQuery", + "spec": { + "plugin": { + "kind": "signoz/BuilderQuery", + "spec": { + "name": "A", + "signal": "metrics", + "aggregations": [ + { + "metricName": "app_recommendations_counter", + "reduceTo": "sum", + "spaceAggregation": "sum", + "timeAggregation": "rate" + } + ], + "filter": { + "expression": "" + } + } + } + } + } + ] + } + } + }, + "layouts": [ + { + "kind": "Grid", + "spec": { + "display": { + "title": "Bravo" + }, + "items": [ + { + "x": 0, + "y": 0, + "width": 6, + "height": 6, + "content": { + "$ref": "#/spec/panels/b424e23b" + } + } + ] + } + }, + { + "kind": "Grid", + "spec": { + "display": { + "title": "Alpha" + }, + "items": [ + { + "x": 0, + "y": 0, + "width": 6, + "height": 6, + "content": { + "$ref": "#/spec/panels/251df4d5" + } + } + ] + } + } + ] +} \ No newline at end of file diff --git a/frontend/src/pages/DashboardsListPageV2/components/ListHeader/ListHeader.module.scss b/frontend/src/pages/DashboardsListPageV2/components/ListHeader/ListHeader.module.scss new file mode 100644 index 00000000000..afce1988a2b --- /dev/null +++ b/frontend/src/pages/DashboardsListPageV2/components/ListHeader/ListHeader.module.scss @@ -0,0 +1,182 @@ +.wrapper { + display: flex; + justify-content: space-between; + align-items: center; + padding: 16px; + height: 44px; + flex-shrink: 0; + border-radius: 6px 6px 0px 0px; + border: 1px solid var(--l1-border); + background: var(--l2-background); + box-shadow: 0px 4px 12px 0px rgba(0, 0, 0, 0.1); +} + +.label { + color: var(--l2-foreground); + font-family: Inter; + font-size: var(--font-size-sm); + font-style: normal; + font-weight: var(--font-weight-normal); + line-height: 20px; + letter-spacing: -0.07px; +} + +.rightActions { + display: flex; + gap: 4px; + color: white; +} + +// Shared trigger button for the sort + configure-group icons in the right +// actions cluster. Provides a square hover/active background so users know +// which icon they're targeting. +.iconTrigger { + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + padding: 0; + background: transparent; + border: none; + border-radius: 4px; + color: inherit; + cursor: pointer; + transition: background 120ms ease; + + &:hover, + &:focus-visible { + background: color-mix(in srgb, var(--l1-foreground) 12%, transparent); + outline: none; + } + + &:active, + &[aria-expanded='true'] { + background: color-mix(in srgb, var(--l1-foreground) 20%, transparent); + } +} + +.sortContent { + display: flex; + flex-direction: column; + align-items: flex-start; + width: 140px; +} + +.sortHeading { + color: var(--l3-foreground); + font-family: Inter; + font-size: 11px; + font-style: normal; + font-weight: var(--font-weight-semibold); + line-height: 18px; + letter-spacing: 0.88px; + text-transform: uppercase; + padding: 12px 18px 6px 14px; +} + +.sortDivider { + width: 100%; + height: 1px; + background: var(--l1-border); + margin: 4px 0; +} + +.sortButton { + text-align: start; + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + color: var(--l2-foreground); + font-family: Inter; + font-size: var(--font-size-sm); + font-style: normal; + font-weight: var(--font-weight-normal); + line-height: normal; + letter-spacing: 0.14px; + padding: 12px 18px 12px 14px; + height: auto; +} + +.configureContent { + display: flex; + flex-direction: column; + align-items: flex-start; + padding: 4px; +} + +.configureItem { + display: flex; + align-items: center; + gap: 10px; + width: 100%; + padding: 8px 12px; + background: transparent; + border: none; + border-radius: 4px; + color: var(--l2-foreground); + font-family: Inter; + font-size: var(--font-size-sm); + font-weight: var(--font-weight-normal); + line-height: normal; + letter-spacing: 0.14px; + text-align: left; + cursor: pointer; + transition: background 120ms ease; + + &:hover, + &:focus-visible { + background: color-mix(in srgb, var(--l1-foreground) 10%, transparent); + outline: none; + } + + &:active { + background: color-mix(in srgb, var(--l1-foreground) 18%, transparent); + } +} + +.configureIcon { + display: inline-flex; + width: 16px; + height: 16px; + flex: 0 0 16px; + align-items: center; + justify-content: center; +} + +:global(.sortDashboardsPopover) { + :global(.ant-popover-inner) { + display: flex; + padding: 0px; + align-items: center; + border-radius: 4px; + border: 1px solid var(--l1-border); + background: linear-gradient( + 139deg, + color-mix(in srgb, var(--card) 80%, transparent) 0%, + color-mix(in srgb, var(--card) 90%, transparent) 98.68% + ); + box-shadow: 4px 10px 16px 2px rgba(0, 0, 0, 0.2); + backdrop-filter: blur(20px); + gap: 16px; + } +} + +:global(.configureGroupPopover) { + :global(.ant-popover-inner) { + display: flex; + align-items: center; + border-radius: 4px; + padding: 0px; + border: 1px solid var(--l1-border); + background: linear-gradient( + 139deg, + color-mix(in srgb, var(--card) 80%, transparent) 0%, + color-mix(in srgb, var(--card) 90%, transparent) 98.68% + ); + box-shadow: 4px 10px 16px 2px rgba(0, 0, 0, 0.2); + backdrop-filter: blur(20px); + gap: 16px; + } +} diff --git a/frontend/src/pages/DashboardsListPageV2/components/ListHeader/ListHeader.tsx b/frontend/src/pages/DashboardsListPageV2/components/ListHeader/ListHeader.tsx new file mode 100644 index 00000000000..9bd5dbae4d4 --- /dev/null +++ b/frontend/src/pages/DashboardsListPageV2/components/ListHeader/ListHeader.tsx @@ -0,0 +1,145 @@ +import { Button, Popover, Tooltip } from 'antd'; +import { Typography } from '@signozhq/ui/typography'; +import { + ArrowDownWideNarrow, + Check, + Ellipsis, + HdmiPort, +} from '@signozhq/icons'; + +import type { + SortColumn, + SortOrder, +} from '../../hooks/useDashboardsListQueryParams'; + +import styles from './ListHeader.module.scss'; + +interface Props { + sortColumn: SortColumn; + onSortChange: (column: SortColumn) => void; + sortOrder: SortOrder; + onOrderChange: (order: SortOrder) => void; + onConfigureMetadata: () => void; +} + +function ListHeader({ + sortColumn, + onSortChange, + sortOrder, + onOrderChange, + onConfigureMetadata, +}: Props): JSX.Element { + return ( +
+ All Dashboards +
+ + + + Sort By + + + + +
+ Order + + +
+ } + rootClassName="sortDashboardsPopover" + placement="bottomRight" + arrow={false} + > + +
+
+ + +
+ } + rootClassName="configureGroupPopover" + placement="bottomRight" + arrow={false} + > + + + + + ); +} + +export default ListHeader; diff --git a/frontend/src/pages/DashboardsListPageV2/components/SearchBar/SearchBar.module.scss b/frontend/src/pages/DashboardsListPageV2/components/SearchBar/SearchBar.module.scss new file mode 100644 index 00000000000..8233fd141fc --- /dev/null +++ b/frontend/src/pages/DashboardsListPageV2/components/SearchBar/SearchBar.module.scss @@ -0,0 +1,24 @@ +.submit { + display: inline-flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; + padding: 0; + background: transparent; + border: none; + border-radius: 3px; + color: inherit; + cursor: pointer; + transition: background 120ms ease; + + &:hover, + &:focus-visible { + background: color-mix(in srgb, var(--l1-foreground) 12%, transparent); + outline: none; + } + + &:active { + background: color-mix(in srgb, var(--l1-foreground) 20%, transparent); + } +} diff --git a/frontend/src/pages/DashboardsListPageV2/components/SearchBar/SearchBar.tsx b/frontend/src/pages/DashboardsListPageV2/components/SearchBar/SearchBar.tsx new file mode 100644 index 00000000000..ce75f32ce5c --- /dev/null +++ b/frontend/src/pages/DashboardsListPageV2/components/SearchBar/SearchBar.tsx @@ -0,0 +1,49 @@ +import { ChangeEvent, KeyboardEvent, MouseEvent } from 'react'; +import { Input } from '@signozhq/ui/input'; +import { Color } from '@signozhq/design-tokens'; +import { CornerDownLeft, Search } from '@signozhq/icons'; + +import styles from './SearchBar.module.scss'; + +interface Props { + value: string; + onChange: (value: string) => void; + onSubmit: () => void; +} + +function SearchBar({ value, onChange, onSubmit }: Props): JSX.Element { + return ( + } + suffix={ + + } + value={value} + testId="dashboards-list-search" + onChange={(e: ChangeEvent): void => + onChange(e.target.value) + } + onBlur={onSubmit} + onKeyDown={(e: KeyboardEvent): void => { + if (e.key === 'Enter') { + onSubmit(); + } + }} + /> + ); +} + +export default SearchBar; diff --git a/frontend/src/pages/DashboardsListPageV2/components/states/EmptyState/EmptyState.module.scss b/frontend/src/pages/DashboardsListPageV2/components/states/EmptyState/EmptyState.module.scss new file mode 100644 index 00000000000..976ae93e20f --- /dev/null +++ b/frontend/src/pages/DashboardsListPageV2/components/states/EmptyState/EmptyState.module.scss @@ -0,0 +1,40 @@ +.wrapper { + composes: cardWrapper from '../states.module.scss'; + padding: 105px 141px; +} + +.image { + width: 32px; + height: 32px; +} + +.copy { + margin-top: 4px; +} + +.noDashboard { + composes: bodyText from '../states.module.scss'; + color: var(--l1-foreground); + font-weight: var(--font-weight-medium); +} + +.info { + composes: bodyText from '../states.module.scss'; + color: var(--l2-foreground); + font-weight: var(--font-weight-normal); +} + +.actions { + display: flex; + gap: 24px; + align-items: center; + margin-top: 24px; +} + +.learnMore { + composes: learnMoreLink from '../states.module.scss'; +} + +.learnMoreArrow { + composes: learnMoreArrow from '../states.module.scss'; +} diff --git a/frontend/src/pages/DashboardsListPageV2/components/states/EmptyState/EmptyState.tsx b/frontend/src/pages/DashboardsListPageV2/components/states/EmptyState/EmptyState.tsx new file mode 100644 index 00000000000..8eed8dcd51f --- /dev/null +++ b/frontend/src/pages/DashboardsListPageV2/components/states/EmptyState/EmptyState.tsx @@ -0,0 +1,54 @@ +import { ReactNode } from 'react'; +import { Button } from '@signozhq/ui/button'; +import { Typography } from '@signozhq/ui/typography'; +import { ArrowUpRight } from '@signozhq/icons'; +import logEvent from 'api/common/logEvent'; + +import dashboardsUrl from '@/assets/Icons/dashboards.svg'; + +import styles from './EmptyState.module.scss'; +import { openInNewTab } from 'utils/navigation'; + +interface Props { + createDropdown?: ReactNode; +} + +const LEARN_MORE_HREF = + 'https://signoz.io/docs/userguide/manage-dashboards?utm_source=product&utm_medium=dashboard-list-empty-state'; + +function EmptyState({ createDropdown }: Props): JSX.Element { + return ( +
+ dashboards +
+ + No dashboards yet.{' '} + + + Create a dashboard to start visualizing your data + +
+ + {createDropdown ? ( +
+ {createDropdown} + + +
+ ) : null} +
+ ); +} + +export default EmptyState; diff --git a/frontend/src/pages/DashboardsListPageV2/components/states/ErrorState/ErrorState.module.scss b/frontend/src/pages/DashboardsListPageV2/components/states/ErrorState/ErrorState.module.scss new file mode 100644 index 00000000000..6852a1156d4 --- /dev/null +++ b/frontend/src/pages/DashboardsListPageV2/components/states/ErrorState/ErrorState.module.scss @@ -0,0 +1,36 @@ +.wrapper { + composes: cardWrapper from '../states.module.scss'; + padding: 105px 141px; + gap: 4px; +} + +.img { + max-width: 100%; +} + +.errorText { + composes: bodyText from '../states.module.scss'; + color: var(--l1-foreground); + font-weight: var(--font-weight-medium); +} + +.errorDetail { + composes: bodyText from '../states.module.scss'; + color: var(--l2-foreground); + font-weight: var(--font-weight-normal); +} + +.actionButtons { + display: flex; + gap: 24px; + align-items: center; + margin-top: 20px; +} + +.learnMore { + composes: learnMoreLink from '../states.module.scss'; +} + +.learnMoreArrow { + composes: learnMoreArrow from '../states.module.scss'; +} diff --git a/frontend/src/pages/DashboardsListPageV2/components/states/ErrorState/ErrorState.tsx b/frontend/src/pages/DashboardsListPageV2/components/states/ErrorState/ErrorState.tsx new file mode 100644 index 00000000000..75547a23d8b --- /dev/null +++ b/frontend/src/pages/DashboardsListPageV2/components/states/ErrorState/ErrorState.tsx @@ -0,0 +1,81 @@ +import { Button } from '@signozhq/ui/button'; +import { Typography } from '@signozhq/ui/typography'; +import { ArrowUpRight, RotateCw } from '@signozhq/icons'; +import { handleContactSupport } from 'container/Integrations/utils'; + +import awwSnapUrl from '@/assets/Icons/awwSnap.svg'; + +import { formatQueryErrorMessage } from '../../../utils'; +import styles from './ErrorState.module.scss'; + +interface Props { + isCloudUser: boolean; + onRetry: () => void; + httpStatus?: number; + errorMessage?: string; +} + +const GENERIC_MESSAGE = + 'Something went wrong :/ Please retry or contact support.'; +const INVALID_QUERY_FALLBACK = 'Please review the syntax and try again.'; + +function ErrorState({ + isCloudUser, + onRetry, + httpStatus, + errorMessage, +}: Props): JSX.Element { + // 4xx responses are client errors — the same request will keep failing. + // Surface the BE-provided detail (e.g. DSL parse errors) and skip Retry. + const isClientError = + httpStatus !== undefined && httpStatus >= 400 && httpStatus < 500; + + const cleanedDetail = formatQueryErrorMessage(errorMessage); + + return ( +
+ something went wrong + + {isClientError ? ( + <> + + Invalid query + + + {cleanedDetail || INVALID_QUERY_FALLBACK} + + + ) : ( + + {GENERIC_MESSAGE} + + )} + +
+ {!isClientError && ( + + )} + + +
+
+ ); +} + +export default ErrorState; diff --git a/frontend/src/pages/DashboardsListPageV2/components/states/LoadingState/LoadingState.module.scss b/frontend/src/pages/DashboardsListPageV2/components/states/LoadingState/LoadingState.module.scss new file mode 100644 index 00000000000..82345e36586 --- /dev/null +++ b/frontend/src/pages/DashboardsListPageV2/components/states/LoadingState/LoadingState.module.scss @@ -0,0 +1,11 @@ +.wrapper { + display: flex; + flex-direction: column; + gap: 16px; + margin-top: 16px; +} + +.skeleton { + height: 125px; + width: 100%; +} diff --git a/frontend/src/pages/DashboardsListPageV2/components/states/LoadingState/LoadingState.tsx b/frontend/src/pages/DashboardsListPageV2/components/states/LoadingState/LoadingState.tsx new file mode 100644 index 00000000000..dcc098f44cb --- /dev/null +++ b/frontend/src/pages/DashboardsListPageV2/components/states/LoadingState/LoadingState.tsx @@ -0,0 +1,16 @@ +import { Skeleton } from 'antd'; + +import styles from './LoadingState.module.scss'; + +function LoadingState(): JSX.Element { + return ( +
+ + + + +
+ ); +} + +export default LoadingState; diff --git a/frontend/src/pages/DashboardsListPageV2/components/states/NoResultsState/NoResultsState.module.scss b/frontend/src/pages/DashboardsListPageV2/components/states/NoResultsState/NoResultsState.module.scss new file mode 100644 index 00000000000..c414aaf0c17 --- /dev/null +++ b/frontend/src/pages/DashboardsListPageV2/components/states/NoResultsState/NoResultsState.module.scss @@ -0,0 +1,5 @@ +.wrapper { + composes: cardWrapper from '../states.module.scss'; + padding: 105px 190px; + gap: 8px; +} diff --git a/frontend/src/pages/DashboardsListPageV2/components/states/NoResultsState/NoResultsState.tsx b/frontend/src/pages/DashboardsListPageV2/components/states/NoResultsState/NoResultsState.tsx new file mode 100644 index 00000000000..d637ca2ce72 --- /dev/null +++ b/frontend/src/pages/DashboardsListPageV2/components/states/NoResultsState/NoResultsState.tsx @@ -0,0 +1,22 @@ +import { Typography } from '@signozhq/ui/typography'; + +import emptyStateUrl from '@/assets/Icons/emptyState.svg'; + +import styles from './NoResultsState.module.scss'; + +interface Props { + searchString: string; +} + +function NoResultsState({ searchString }: Props): JSX.Element { + return ( +
+ img + + No dashboards found for {searchString}. Create a new dashboard? + +
+ ); +} + +export default NoResultsState; diff --git a/frontend/src/pages/DashboardsListPageV2/components/states/states.module.scss b/frontend/src/pages/DashboardsListPageV2/components/states/states.module.scss new file mode 100644 index 00000000000..ebb90999784 --- /dev/null +++ b/frontend/src/pages/DashboardsListPageV2/components/states/states.module.scss @@ -0,0 +1,34 @@ +// Shared building blocks for the dashboards-list view states. +// Composed via CSS-modules `composes:` from each state's own SCSS. + +.cardWrapper { + display: flex; + flex-direction: column; + height: 320px; + margin-top: 16px; + justify-content: center; + align-items: flex-start; + border-radius: 6px; + border: 1px dashed var(--l1-border); +} + +.bodyText { + font-family: Inter; + font-size: var(--font-size-sm); + font-style: normal; + line-height: 18px; + letter-spacing: -0.07px; +} + +.learnMoreLink { + composes: bodyText; + color: var(--bg-robin-400); + font-weight: var(--font-weight-medium); + padding: 0px; +} + +.learnMoreArrow { + margin-left: -20px; + color: var(--bg-robin-400); + cursor: pointer; +} diff --git a/frontend/src/pages/DashboardsListPageV2/hooks/useDashboardsListQueryParams.ts b/frontend/src/pages/DashboardsListPageV2/hooks/useDashboardsListQueryParams.ts new file mode 100644 index 00000000000..87e2c675dad --- /dev/null +++ b/frontend/src/pages/DashboardsListPageV2/hooks/useDashboardsListQueryParams.ts @@ -0,0 +1,36 @@ +import { + parseAsInteger, + parseAsString, + parseAsStringLiteral, + useQueryState, + type Options, + type UseQueryStateReturn, +} from 'nuqs'; + +export const SORT_COLUMNS = ['updated_at', 'created_at', 'name'] as const; +export type SortColumn = (typeof SORT_COLUMNS)[number]; + +export const SORT_ORDERS = ['asc', 'desc'] as const; +export type SortOrder = (typeof SORT_ORDERS)[number]; + +const opts: Options = { history: 'push' }; + +export const useSortColumn = (): UseQueryStateReturn => + useQueryState( + 'sort', + parseAsStringLiteral(SORT_COLUMNS) + .withDefault('updated_at') + .withOptions(opts), + ); + +export const useSortOrder = (): UseQueryStateReturn => + useQueryState( + 'order', + parseAsStringLiteral(SORT_ORDERS).withDefault('desc').withOptions(opts), + ); + +export const usePage = (): UseQueryStateReturn => + useQueryState('page', parseAsInteger.withDefault(1).withOptions(opts)); + +export const useSearch = (): UseQueryStateReturn => + useQueryState('search', parseAsString.withDefault('').withOptions(opts)); diff --git a/frontend/src/pages/DashboardsListPageV2/index.tsx b/frontend/src/pages/DashboardsListPageV2/index.tsx index 222dd45d854..78e64878f8e 100644 --- a/frontend/src/pages/DashboardsListPageV2/index.tsx +++ b/frontend/src/pages/DashboardsListPageV2/index.tsx @@ -1,9 +1,3 @@ -function DashboardsListPageV2(): JSX.Element { - return ( -
-

Dashboards List Page V2

-
- ); -} +import DashboardsListPageV2 from './DashboardsListPageV2'; export default DashboardsListPageV2; diff --git a/frontend/src/pages/DashboardsListPageV2/utils.ts b/frontend/src/pages/DashboardsListPageV2/utils.ts new file mode 100644 index 00000000000..2a339e83ce2 --- /dev/null +++ b/frontend/src/pages/DashboardsListPageV2/utils.ts @@ -0,0 +1,52 @@ +import dayjs from 'dayjs'; +import { isEmpty } from 'lodash-es'; +import type { DashboardtypesGettableDashboardWithPinDTO } from 'api/generated/services/sigNoz.schemas'; + +export type DashboardListItem = DashboardtypesGettableDashboardWithPinDTO; + +export const tagsToStrings = ( + tags: { key: string; value: string }[] | null | undefined, +): string[] => + (tags ?? []).map((tag) => + tag.key === tag.value ? tag.key : `${tag.key}:${tag.value}`, + ); + +export const lastUpdatedLabel = (time: string | undefined): string => { + if (!time || isEmpty(time)) { + return 'No updates yet!'; + } + const diff = dayjs(); + const ref = dayjs(time); + const months = diff.diff(ref, 'months'); + if (months > 0) { + return `Last Updated ${months} months ago`; + } + const days = diff.diff(ref, 'days'); + if (days > 0) { + return `Last Updated ${days} days ago`; + } + const hours = diff.diff(ref, 'hours'); + if (hours > 0) { + return `Last Updated ${hours} hrs ago`; + } + const minutes = diff.diff(ref, 'minutes'); + if (minutes > 0) { + return `Last Updated ${minutes} mins ago`; + } + const seconds = diff.diff(ref, 'seconds'); + return `Last Updated ${seconds} sec ago`; +}; + +// Normalize BE query-parse error messages for display: +// - Drop the "invalid filter query:" prefix (the UI already says "Invalid query"). +// - Backticks → double quotes for the format hint that follows the em-dash. +// - Trim surrounding whitespace. +export const formatQueryErrorMessage = (raw: string | undefined): string => { + if (!raw) { + return ''; + } + return raw + .replace(/^invalid filter query:\s*/i, '') + .replace(/`([^`]+)`/g, '"$1"') + .trim(); +}; diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 5ded1bc00a5..615c8af82a6 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -48,7 +48,9 @@ "node_modules", "src/parser/*.ts", "src/parser/TraceOperatorParser/*.ts", - "orval.config.ts" + "orval.config.ts", + "src/pages/DashboardsListPageV2/**/*", + "src/pages/DashboardPageV2/**/*" ], "include": [ "./src", From 4b08ba1330a45cab3ac9b8308b284307f262a762 Mon Sep 17 00:00:00 2001 From: Tushar Vats Date: Mon, 1 Jun 2026 15:13:22 +0530 Subject: [PATCH 2/3] fix: qb warnings (#11518) --- pkg/querier/bucket_cache.go | 16 +- pkg/querier/querier.go | 26 ++ tests/fixtures/querier.py | 45 +- tests/integration/tests/querier/01_logs.py | 41 -- .../tests/querier/16_qb_warnings.py | 386 ++++++++++++++++++ 5 files changed, 457 insertions(+), 57 deletions(-) create mode 100644 tests/integration/tests/querier/16_qb_warnings.py diff --git a/pkg/querier/bucket_cache.go b/pkg/querier/bucket_cache.go index 21fca3aa985..5ffe721b263 100644 --- a/pkg/querier/bucket_cache.go +++ b/pkg/querier/bucket_cache.go @@ -679,21 +679,7 @@ func (bc *bucketCache) mergeAndDeduplicateBuckets(existing, fresh []*qbtypes.Cac // deduplicateWarnings removes duplicate warnings. func (bc *bucketCache) deduplicateWarnings(warnings []string) []string { - if len(warnings) == 0 { - return nil - } - - seen := make(map[string]bool, len(warnings)) - unique := make([]string, 0, len(warnings)) // Pre-allocate capacity - - for _, warning := range warnings { - if !seen[warning] { - seen[warning] = true - unique = append(unique, warning) - } - } - - return unique + return dedupeWarnings(warnings) } // trimResultToFluxBoundary trims the result to exclude data points beyond the flux boundary. diff --git a/pkg/querier/querier.go b/pkg/querier/querier.go index 331c90b7b10..4fe0438ecc9 100644 --- a/pkg/querier/querier.go +++ b/pkg/querier/querier.go @@ -642,6 +642,12 @@ func (q *querier) run( }, } + // Warnings can arrive duplicated: the bucket cache returns the cached + // portion's warnings alongside an identical warning emitted by every + // freshly-executed missing range (see mergeResults), and distinct queries + // can surface the same warning. Collapse exact duplicates before building + // the response. + warnings = dedupeWarnings(warnings) if len(warnings) != 0 { warns := make([]qbtypes.QueryWarnDataAdditional, len(warnings)) for i, warning := range warnings { @@ -1125,6 +1131,8 @@ func (q *querier) adjustStepInterval(queries []qbtypes.QueryEnvelope, start, end case qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation]: if qe.GetSource() == telemetrytypes.SourceMeter { clampStep(qe, meterRecommended, meterMin, &warnings) + // we don't want to return warnings for meter metrics. + warnings = nil } else { clampStep(qe, metricRecommended, metricMin, &warnings) } @@ -1140,3 +1148,21 @@ func (q *querier) adjustStepInterval(queries []qbtypes.QueryEnvelope, start, end } return warnings } + +// dedupeWarnings removes exact-duplicate warning messages while preserving the +// order of first occurrence. Returns nil for an empty input. Warning counts are +// tiny (a handful per request), so a linear scan beats the allocation and +// hashing overhead of a map. +func dedupeWarnings(warnings []string) []string { + if len(warnings) == 0 { + return nil + } + unique := make([]string, 0, len(warnings)) + // N^2 is faster than map-based deduping for small warning counts, and it preserves order of first occurrence without extra bookkeeping. + for _, warning := range warnings { + if !slices.Contains(unique, warning) { + unique = append(unique, warning) + } + } + return unique +} diff --git a/tests/fixtures/querier.py b/tests/fixtures/querier.py index aa8af6e1400..b8963cf71f6 100644 --- a/tests/fixtures/querier.py +++ b/tests/fixtures/querier.py @@ -28,6 +28,42 @@ def to_dict(self) -> dict: } +class RequestType: + RAW = "raw" + TIME_SERIES = "time_series" + SCALAR = "scalar" + TABLE = "table" + + +@dataclass +class Aggregation: + expression: str + alias: str | None = None + + def to_dict(self) -> dict: + agg: dict[str, Any] = {"expression": self.expression} + if self.alias: + agg["alias"] = self.alias + return agg + + +@dataclass +class MetricAggregation: + metric_name: str + time_aggregation: str + space_aggregation: str + temporality: str = "cumulative" + + def to_dict(self) -> dict: + agg: dict[str, Any] = { + "metricName": self.metric_name, + "timeAggregation": self.time_aggregation, + "spaceAggregation": self.space_aggregation, + "temporality": self.temporality, + } + return agg + + @dataclass class OrderBy: key: TelemetryFieldKey @@ -46,6 +82,8 @@ class BuilderQuery: filter_expression: str | None = None select_fields: list[TelemetryFieldKey] | None = None order: list[OrderBy] | None = None + aggregations: list[Aggregation | MetricAggregation] | None = None + step_interval: int | None = None def to_dict(self) -> dict: spec: dict[str, Any] = { @@ -62,6 +100,11 @@ def to_dict(self) -> dict: spec["selectFields"] = [f.to_dict() for f in self.select_fields] if self.order: spec["order"] = [o.to_dict() if hasattr(o, "to_dict") else o for o in self.order] + if self.aggregations: + spec["aggregations"] = [agg.to_dict() if hasattr(agg, "to_dict") else agg for agg in self.aggregations] + if self.step_interval is not None: + spec["stepInterval"] = self.step_interval + return {"type": "builder_query", "spec": spec} @@ -117,7 +160,7 @@ def make_query_request( end_ms: int, queries: list[dict], *, - request_type: str = "time_series", + request_type: str = RequestType.TIME_SERIES, format_options: dict | None = None, variables: dict | None = None, no_cache: bool = True, diff --git a/tests/integration/tests/querier/01_logs.py b/tests/integration/tests/querier/01_logs.py index a785fa6fbba..d552b42b250 100644 --- a/tests/integration/tests/querier/01_logs.py +++ b/tests/integration/tests/querier/01_logs.py @@ -15,7 +15,6 @@ build_group_by_field, build_logs_aggregation, build_order_by, - build_raw_query, build_scalar_query, find_named_result, index_series_by_label, @@ -2626,43 +2625,3 @@ def _count(start_ms: int, end_ms: int, trace_id: str) -> tuple[float, list[str]] orphan_count, orphan_warnings = _count(narrow_start_ms, now_ms, orphan_trace_id) assert orphan_count == 1, f"Expected count=1 for orphan trace_id aggregation, got {orphan_count} — query may have been incorrectly short-circuited" assert not any(outside_range_msg in m for m in orphan_warnings), f"Did not expect outside-range warning for orphan trace_id, got {orphan_warnings}" - - -def test_logs_list_ambigous_warnings( - signoz: types.SigNoz, - create_user_admin: None, # pylint: disable=unused-argument - get_token: Callable[[str, str], str], - insert_logs: Callable[[list[Logs]], None], -) -> None: - insert_logs( - [ - Logs( - timestamp=datetime.now(tz=UTC) - timedelta(seconds=1), - resources={ - "service.name": "java", - }, - attributes={ - "service.name": "java", - }, - body="This is a log message, coming from a java application", - severity_text="DEBUG", - ), - ] - ) - - response = make_query_request( - signoz, - get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD), - start_ms=int((datetime.now(tz=UTC) - timedelta(minutes=1)).timestamp() * 1000), - end_ms=int(datetime.now(tz=UTC).timestamp() * 1000), - request_type="raw", - queries=[build_raw_query(name="A", signal="logs", filter_expression='service.name = "java"')], - ) - - assert response.status_code == HTTPStatus.OK - assert response.json()["status"] == "success" - warning = response.json()["data"].get("warning", None) - assert warning is not None - assert warning["message"] == "Encountered warnings" - assert len(warning.get("warnings")) > 0 - assert any(["ambiguous" in w["message"] for w in warning.get("warnings")]) diff --git a/tests/integration/tests/querier/16_qb_warnings.py b/tests/integration/tests/querier/16_qb_warnings.py new file mode 100644 index 00000000000..c40090cf1a2 --- /dev/null +++ b/tests/integration/tests/querier/16_qb_warnings.py @@ -0,0 +1,386 @@ +from collections.abc import Callable +from datetime import UTC, datetime, timedelta +from http import HTTPStatus + +from fixtures import types +from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD +from fixtures.logs import Logs +from fixtures.meter import MeterSample, make_meter_samples +from fixtures.querier import ( + Aggregation, + BuilderQuery, + MetricAggregation, + RequestType, + make_query_request, +) + + +def test_resource_default_warning( + signoz: types.SigNoz, + create_user_admin: None, # pylint: disable=unused-argument + get_token: Callable[[str, str], str], + insert_logs: Callable[[list[Logs]], None], +) -> None: + insert_logs( + [ + Logs( + timestamp=datetime.now(tz=UTC), + resources={ + "service.name": "java", + }, + attributes={ + "service.name": "java", + }, + body="This is a log message, coming from a java application", + severity_text="DEBUG", + ), + ] + ) + + token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD) + + response = make_query_request( + signoz, + token, + start_ms=int((datetime.now(tz=UTC) - timedelta(minutes=20)).timestamp() * 1000), + end_ms=int(datetime.now(tz=UTC).timestamp() * 1000), + request_type=RequestType.RAW, + queries=[ + BuilderQuery( + name="A", + signal="logs", + filter_expression="service.name = 'java'", + ).to_dict() + ], + ) + + assert response.status_code == HTTPStatus.OK + assert response.json()["status"] == "success" + warning = response.json()["data"].get("warning", None) + assert warning is not None + assert warning["message"] == "Encountered warnings" + + expected_service_name_warning = ( + "Key `service.name` is ambiguous, found 2 different combinations of " + "field context / data type: [name=service.name,context=resource,datatype=string " + "name=service.name,context=attribute,datatype=string]. Using `resource` context " + "by default. To query attributes explicitly, use the fully qualified name " + "(e.g., 'attribute.service.name')" + ) + assert warning["warnings"] == [ + {"message": expected_service_name_warning}, + ] + + +def test_key_collision_warning( + signoz: types.SigNoz, + create_user_admin: None, # pylint: disable=unused-argument + get_token: Callable[[str, str], str], + insert_logs: Callable[[list[Logs]], None], +) -> None: + insert_logs( + [ + Logs( + timestamp=datetime.now(tz=UTC), + resources={ + "service.name": "java", + }, + attributes={ + "service.name": "java", + "http.status_code": 200, + }, + body="This is a log message, coming from a java application", + severity_text="DEBUG", + ), + Logs( + timestamp=datetime.now(tz=UTC), + resources={ + "service.name": "java", + }, + attributes={ + "service.name": "java", + "http.status_code": "200", + }, + body="This is a log message, coming from a java application", + severity_text="DEBUG", + ), + ] + ) + + token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD) + + response = make_query_request( + signoz, + token, + start_ms=int((datetime.now(tz=UTC) - timedelta(minutes=20)).timestamp() * 1000), + end_ms=int(datetime.now(tz=UTC).timestamp() * 1000), + request_type=RequestType.RAW, + queries=[ + BuilderQuery( + name="A", + signal="logs", + filter_expression="http.status_code = '200'", + ).to_dict() + ], + ) + + assert response.status_code == HTTPStatus.OK + assert response.json()["status"] == "success" + warning = response.json()["data"].get("warning", None) + assert warning is not None + assert warning["message"] == "Encountered warnings" + + expected_http_status_code_warning = "Key `http.status_code` is ambiguous, found 2 different combinations of field context / data type: [name=http.status_code,context=attribute,datatype=number name=http.status_code,context=attribute,datatype=string]." + assert warning["warnings"] == [ + {"message": expected_http_status_code_warning}, + ] + + +def test_deduped_warnings_for_single_query( + signoz: types.SigNoz, + create_user_admin: None, # pylint: disable=unused-argument + get_token: Callable[[str, str], str], + insert_logs: Callable[[list[Logs]], None], +) -> None: + insert_logs( + [ + *[ + Logs( + timestamp=datetime.now(tz=UTC) - timedelta(minutes=i * 10), + resources={ + "service.name": "java", + }, + attributes={ + "service.name": "java", + "http.status_code": 200, + }, + body="This is a log message, coming from a java application", + severity_text="DEBUG", + ) + for i in range(10) + ], + Logs( + timestamp=datetime.now(tz=UTC), + resources={ + "service.name": "java", + }, + attributes={ + "service.name": "java", + "http.status_code": "200", + }, + body="This is a log message, coming from a java application", + severity_text="DEBUG", + ), + ] + ) + + token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD) + + # `service.name` (resource vs attribute) and `http.status_code` (number vs + # string) are both ambiguous keys, so referencing them in the filter makes + # the querier emit an "is ambiguous" warning while *building* the query. + # + # This test targets the bucket-cache warning-merge path: the cache stores a + # query's warnings alongside its buckets, and on a partial cache hit + # executeWithCache merges the cached warnings with the warnings re-emitted by + # each freshly executed missing range (querier.mergeResults). Before the + # dedup fix the same message appeared once per executed range. + query = BuilderQuery( + name="A", + signal="logs", + step_interval=600, + filter_expression="service.name = 'java' and http.status_code = 200", + aggregations=[Aggregation(expression="count()")], + ).to_dict() + + # Anchor both windows to a single "now" so their step-aligned boundaries + # match. Both windows end well before the 5m flux interval so the results + # are cacheable. + now_ms = int(datetime.now(tz=UTC).timestamp() * 1000) + minute = 60 * 1000 + + # First request populates the cache for [-90m, -30m], storing the warnings. + first = make_query_request( + signoz, + token, + start_ms=now_ms - 90 * minute, + end_ms=now_ms - 30 * minute, + request_type=RequestType.TIME_SERIES, + queries=[query], + no_cache=False, + ) + assert first.status_code == HTTPStatus.OK + assert first.json()["status"] == "success" + + # Second request reuses the cached [-90m, -30m] buckets (which carry the + # cached warnings) and executes only the trailing [-30m, -20m] step fresh, + # which re-emits the same warnings — exercising the cache/fresh merge. + response = make_query_request( + signoz, + token, + start_ms=now_ms - 90 * minute, + end_ms=now_ms - 20 * minute, + request_type=RequestType.TIME_SERIES, + queries=[query], + no_cache=False, + ) + + assert response.status_code == HTTPStatus.OK + assert response.json()["status"] == "success" + warning = response.json()["data"].get("warning", None) + assert warning is not None + assert warning["message"] == "Encountered warnings" + + # Each ambiguity warning arrives from both the cached portion and the fresh + # missing range; after deduplication each distinct message appears once. + expected_service_name_warning = ( + "Key `service.name` is ambiguous, found 2 different combinations of " + "field context / data type: [name=service.name,context=resource,datatype=string " + "name=service.name,context=attribute,datatype=string]. Using `resource` context " + "by default. To query attributes explicitly, use the fully qualified name " + "(e.g., 'attribute.service.name')" + ) + expected_status_code_warning = "Key `http.status_code` is ambiguous, found 2 different combinations of field context / data type: [name=http.status_code,context=attribute,datatype=number name=http.status_code,context=attribute,datatype=string]." + assert warning["warnings"] == [ + {"message": expected_service_name_warning}, + {"message": expected_status_code_warning}, + ] + + +def test_deduped_warnings_for_multiple_queries( + signoz: types.SigNoz, + create_user_admin: None, # pylint: disable=unused-argument + get_token: Callable[[str, str], str], + insert_logs: Callable[[list[Logs]], None], +) -> None: + insert_logs( + [ + *[ + Logs( + timestamp=datetime.now(tz=UTC) - timedelta(minutes=i * 10), + resources={ + "service.name": "java", + }, + attributes={ + "service.name": "java", + "http.status_code": 200, + }, + body="This is a log message, coming from a java application", + severity_text="DEBUG", + ) + for i in range(10) + ], + Logs( + timestamp=datetime.now(tz=UTC), + resources={ + "service.name": "java", + }, + attributes={ + "service.name": "java", + "http.status_code": "200", + }, + body="This is a log message, coming from a java application", + severity_text="DEBUG", + ), + ] + ) + + token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD) + + # `service.name` (resource vs attribute) and `http.status_code` (number vs + # string) are both ambiguous keys, so referencing them in the filter makes + # the querier emit an "is ambiguous" warning while *building* the query. + query_1 = BuilderQuery( + name="A", + signal="logs", + step_interval=600, + filter_expression="service.name = 'java' and http.status_code = 200", + aggregations=[Aggregation(expression="count()")], + ).to_dict() + query_2 = BuilderQuery( + name="B", + signal="logs", + step_interval=600, + filter_expression="service.name != '_java' and http.status_code = 200", + aggregations=[Aggregation(expression="count()")], + ).to_dict() + + now_ms = int(datetime.now(tz=UTC).timestamp() * 1000) + minute = 60 * 1000 + + response = make_query_request( + signoz, + token, + start_ms=now_ms - 90 * minute, + end_ms=now_ms - 20 * minute, + request_type=RequestType.TIME_SERIES, + queries=[query_1, query_2], + no_cache=False, + ) + + assert response.status_code == HTTPStatus.OK + assert response.json()["status"] == "success" + warning = response.json()["data"].get("warning", None) + assert warning is not None + assert warning["message"] == "Encountered warnings" + + expected_service_name_warning = ( + "Key `service.name` is ambiguous, found 2 different combinations of " + "field context / data type: [name=service.name,context=resource,datatype=string " + "name=service.name,context=attribute,datatype=string]. Using `resource` context " + "by default. To query attributes explicitly, use the fully qualified name " + "(e.g., 'attribute.service.name')" + ) + expected_status_code_warning = "Key `http.status_code` is ambiguous, found 2 different combinations of field context / data type: [name=http.status_code,context=attribute,datatype=number name=http.status_code,context=attribute,datatype=string]." + assert warning["warnings"] == [ + {"message": expected_service_name_warning}, + {"message": expected_status_code_warning}, + ] + + +def test_no_warnings_for_meter_query( + signoz: types.SigNoz, + create_user_admin: None, # pylint: disable=unused-argument + get_token: Callable[[str, str], str], + insert_meter_samples: Callable[[list[MeterSample]], None], +) -> None: + # Meter queries deliberately suppress the step-interval clamp warning. The + # minimum allowed step for a meter is 1h, so a 10m (600s) step over this + # range would normally clamp and emit a warning for a regular metric — for a + # meter source the querier discards it. This asserts no warning leaks out. + now = datetime.now(tz=UTC).replace(second=0, microsecond=0) + metric_name = "signoz.meter.log.size" + insert_meter_samples( + make_meter_samples( + metric_name, + {"service": "test-service"}, + now, + count=60, + temporality="Delta", + type_="Sum", + is_monotonic=True, + ) + ) + + token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD) + + response = make_query_request( + signoz, + token, + start_ms=int((now - timedelta(minutes=20)).timestamp() * 1000), + end_ms=int(now.timestamp() * 1000), + request_type=RequestType.TIME_SERIES, + queries=[ + BuilderQuery( + name="A", + signal="metrics", + source="meter", + step_interval=600, + aggregations=[MetricAggregation(metric_name=metric_name, time_aggregation="sum", space_aggregation="sum")], + ).to_dict() + ], + ) + + assert response.status_code == HTTPStatus.OK + assert response.json()["status"] == "success" + assert "warning" not in response.json()["data"] From bfc50ee9c331b6c6810427e69a45bc65fb285573 Mon Sep 17 00:00:00 2001 From: Nityananda Gohain Date: Mon, 1 Jun 2026 15:17:56 +0530 Subject: [PATCH 3/3] chore: use distinct fingerprint in resource filter (#11521) * chore: use distinct fingerprint in resource filter * fix: use group by instead of distinct --- pkg/telemetryaudit/statement_builder_test.go | 4 +-- pkg/telemetrylogs/stmt_builder_test.go | 16 +++++----- .../statement_builder.go | 4 +++ .../statement_builder_test.go | 32 +++++++++---------- pkg/telemetrytraces/stmt_builder_test.go | 26 +++++++-------- .../trace_operator_cte_builder_test.go | 20 ++++++------ 6 files changed, 53 insertions(+), 49 deletions(-) diff --git a/pkg/telemetryaudit/statement_builder_test.go b/pkg/telemetryaudit/statement_builder_test.go index 01b9757b63a..07152472f63 100644 --- a/pkg/telemetryaudit/statement_builder_test.go +++ b/pkg/telemetryaudit/statement_builder_test.go @@ -127,7 +127,7 @@ func TestStatementBuilder(t *testing.T) { Limit: 100, }, expected: qbtypes.Statement{ - Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_audit.distributed_logs_resource WHERE ((simpleJSONExtractString(labels, 'signoz.audit.resource.kind') = ? AND labels LIKE ? AND labels LIKE ?) AND (simpleJSONExtractString(labels, 'signoz.audit.resource.id') = ? 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, event_name, attributes_string, attributes_number, attributes_bool, resource, scope_string FROM signoz_audit.distributed_logs WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? LIMIT ?", + Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_audit.distributed_logs_resource WHERE ((simpleJSONExtractString(labels, 'signoz.audit.resource.kind') = ? AND labels LIKE ? AND labels LIKE ?) AND (simpleJSONExtractString(labels, 'signoz.audit.resource.id') = ? AND labels LIKE ? AND labels LIKE ?)) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ? GROUP BY fingerprint) SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body, event_name, attributes_string, attributes_number, attributes_bool, resource, scope_string FROM signoz_audit.distributed_logs WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? LIMIT ?", Args: []any{"dashboard", "%signoz.audit.resource.kind%", "%signoz.audit.resource.kind\":\"dashboard%", "019b-5678-efgh-9012", "%signoz.audit.resource.id%", "%signoz.audit.resource.id\":\"019b-5678-efgh-9012%", uint64(1747945619), uint64(1747983448), "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448), 100}, }, }, @@ -144,7 +144,7 @@ func TestStatementBuilder(t *testing.T) { Limit: 100, }, expected: qbtypes.Statement{ - Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_audit.distributed_logs_resource WHERE (simpleJSONExtractString(labels, 'signoz.audit.resource.kind') = ? 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, event_name, attributes_string, attributes_number, attributes_bool, resource, scope_string FROM signoz_audit.distributed_logs WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND (`attribute_string_signoz$$audit$$action` = ? AND `attribute_string_signoz$$audit$$action_exists` = ?) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? LIMIT ?", + Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_audit.distributed_logs_resource WHERE (simpleJSONExtractString(labels, 'signoz.audit.resource.kind') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ? GROUP BY fingerprint) SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body, event_name, attributes_string, attributes_number, attributes_bool, resource, scope_string FROM signoz_audit.distributed_logs WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND (`attribute_string_signoz$$audit$$action` = ? AND `attribute_string_signoz$$audit$$action_exists` = ?) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? LIMIT ?", Args: []any{"dashboard", "%signoz.audit.resource.kind%", "%signoz.audit.resource.kind\":\"dashboard%", uint64(1747945619), uint64(1747983448), "delete", true, "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448), 100}, }, }, diff --git a/pkg/telemetrylogs/stmt_builder_test.go b/pkg/telemetrylogs/stmt_builder_test.go index 9ad236530e4..b4415fdcb86 100644 --- a/pkg/telemetrylogs/stmt_builder_test.go +++ b/pkg/telemetrylogs/stmt_builder_test.go @@ -55,7 +55,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(resource.`service.name`::String IS NOT NULL, resource.`service.name`::String, NULL)) AS `service.name`, countDistinct(multiIf(resource.`service.name`::String IS NOT NULL, resource.`service.name`::String, NULL)) AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? 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(resource.`service.name`::String IS NOT NULL, resource.`service.name`::String, NULL)) AS `service.name`, countDistinct(multiIf(resource.`service.name`::String IS NOT NULL, resource.`service.name`::String, NULL)) AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? AND (`service.name`) GLOBAL IN (SELECT `service.name` FROM __limit_cte) GROUP BY ts, `service.name`", + 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 <= ? GROUP BY fingerprint), __limit_cte AS (SELECT toString(multiIf(resource.`service.name`::String IS NOT NULL, resource.`service.name`::String, NULL)) AS `service.name`, countDistinct(multiIf(resource.`service.name`::String IS NOT NULL, resource.`service.name`::String, NULL)) AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? 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(resource.`service.name`::String IS NOT NULL, resource.`service.name`::String, NULL)) AS `service.name`, countDistinct(multiIf(resource.`service.name`::String IS NOT NULL, resource.`service.name`::String, NULL)) AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? 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(1705397400), uint64(1705485600), "1705399200000000000", uint64(1705397400), "1705485600000000000", uint64(1705485600), 10, "1705399200000000000", uint64(1705397400), "1705485600000000000", uint64(1705485600)}, }, expectedErr: nil, @@ -127,7 +127,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(resource.`service.name`::String IS NOT NULL, resource.`service.name`::String, 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 timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? 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(resource.`service.name`::String IS NOT NULL, resource.`service.name`::String, 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 timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? AND (`service.name`) GLOBAL IN (SELECT `service.name` FROM __limit_cte) GROUP BY ts, `service.name` ORDER BY `service.name` desc, ts desc", + 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 <= ? GROUP BY fingerprint), __limit_cte AS (SELECT toString(multiIf(resource.`service.name`::String IS NOT NULL, resource.`service.name`::String, 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 timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? 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(resource.`service.name`::String IS NOT NULL, resource.`service.name`::String, 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 timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? AND (`service.name`) GLOBAL IN (SELECT `service.name` FROM __limit_cte) GROUP BY ts, `service.name` ORDER BY `service.name` desc, ts desc", Args: []any{"cartservice", "%service.name%", "%service.name\":\"cartservice%", uint64(1705397400), uint64(1705485600), "1705399200000000000", uint64(1705397400), "1705485600000000000", uint64(1705485600), 10, "1705399200000000000", uint64(1705397400), "1705485600000000000", uint64(1705485600)}, }, expectedErr: nil, @@ -160,7 +160,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(`attribute_string_materialized$$key$$name_exists` = ?, `attribute_string_materialized$$key$$name`, NULL)) AS `materialized.key.name`, count() AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? GROUP BY `materialized.key.name` ORDER BY __result_0 DESC LIMIT ?) SELECT toStartOfInterval(fromUnixTimestamp64Nano(timestamp), INTERVAL 30 SECOND) AS ts, toString(multiIf(`attribute_string_materialized$$key$$name_exists` = ?, `attribute_string_materialized$$key$$name`, NULL)) AS `materialized.key.name`, count() AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? AND (`materialized.key.name`) GLOBAL IN (SELECT `materialized.key.name` FROM __limit_cte) GROUP BY ts, `materialized.key.name`", + 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 <= ? GROUP BY fingerprint), __limit_cte AS (SELECT toString(multiIf(`attribute_string_materialized$$key$$name_exists` = ?, `attribute_string_materialized$$key$$name`, NULL)) AS `materialized.key.name`, count() AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? GROUP BY `materialized.key.name` ORDER BY __result_0 DESC LIMIT ?) SELECT toStartOfInterval(fromUnixTimestamp64Nano(timestamp), INTERVAL 30 SECOND) AS ts, toString(multiIf(`attribute_string_materialized$$key$$name_exists` = ?, `attribute_string_materialized$$key$$name`, NULL)) AS `materialized.key.name`, count() AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? AND (`materialized.key.name`) GLOBAL IN (SELECT `materialized.key.name` FROM __limit_cte) GROUP BY ts, `materialized.key.name`", Args: []any{"cartservice", "%service.name%", "%service.name\":\"cartservice%", uint64(1705397400), uint64(1705485600), true, "1705399200000000000", uint64(1705397400), "1705485600000000000", uint64(1705485600), 10, true, "1705399200000000000", uint64(1705397400), "1705485600000000000", uint64(1705485600)}, }, }, @@ -253,7 +253,7 @@ func TestStatementBuilderListQuery(t *testing.T) { Limit: 10, }, 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 timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? LIMIT ?", + 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 <= ? GROUP BY fingerprint) 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 timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? LIMIT ?", Args: []any{"cartservice", "%service.name%", "%service.name\":\"cartservice%", uint64(1747945619), uint64(1747983448), "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448), 10}, }, expectedErr: nil, @@ -281,7 +281,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 timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? ORDER BY `attribute_string_materialized$$key$$name` AS `materialized.key.name` desc LIMIT ?", + 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 <= ? GROUP BY fingerprint) 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 timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? 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", uint64(1747945619), "1747983448000000000", uint64(1747983448), 10}, }, expectedErr: nil, @@ -424,7 +424,7 @@ func TestStatementBuilderListQueryResourceTests(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 match(LOWER(body), LOWER(?)) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? ORDER BY `attribute_string_materialized$$key$$name` AS `materialized.key.name` desc LIMIT ?", + 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 <= ? GROUP BY fingerprint) 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 match(LOWER(body), LOWER(?)) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? 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), "hello", "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448), 10}, }, expectedErr: nil, @@ -615,7 +615,7 @@ func TestStatementBuilderListQueryServiceCollision(t *testing.T) { Limit: 10, }, 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 (LOWER(body) LIKE LOWER(?)) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? LIMIT ?", + 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 <= ? GROUP BY fingerprint) 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 (LOWER(body) LIKE LOWER(?)) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? LIMIT ?", Args: []any{"cartservice", "%service.name%", "%service.name\":\"cartservice%", uint64(1747945619), uint64(1747983448), "%error%", "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448), 10}, }, expectedErr: nil, @@ -644,7 +644,7 @@ func TestStatementBuilderListQueryServiceCollision(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 LOWER(body) LIKE LOWER(?) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? ORDER BY `attribute_string_materialized$$key$$name` AS `materialized.key.name` desc LIMIT ?", + 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 <= ? GROUP BY fingerprint) 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 LOWER(body) LIKE LOWER(?) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? 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), "%error%", "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448), 10}, }, expectedErr: nil, diff --git a/pkg/telemetryresourcefilter/statement_builder.go b/pkg/telemetryresourcefilter/statement_builder.go index 37c9fc4ab3e..c2f2f35c452 100644 --- a/pkg/telemetryresourcefilter/statement_builder.go +++ b/pkg/telemetryresourcefilter/statement_builder.go @@ -116,6 +116,10 @@ func (b *resourceFilterStatementBuilder[T]) Build( return nil, nil //nolint:nilnil } + // Group by fingerprint instead of using DISTINCT; on ClickHouse GROUP BY + // parallelizes across multiple threads and is faster for deduplication. + q.GroupBy("fingerprint") + stmt, args := q.BuildWithFlavor(sqlbuilder.ClickHouse) return &qbtypes.Statement{ Query: stmt, diff --git a/pkg/telemetryresourcefilter/statement_builder_test.go b/pkg/telemetryresourcefilter/statement_builder_test.go index fd6ef052fea..290ed45fe13 100644 --- a/pkg/telemetryresourcefilter/statement_builder_test.go +++ b/pkg/telemetryresourcefilter/statement_builder_test.go @@ -119,7 +119,7 @@ func TestResourceFilterStatementBuilder_Traces(t *testing.T) { start: testStartNs, end: testEndNs, expected: &qbtypes.Statement{ - Query: "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 <= ?", + Query: "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 <= ? GROUP BY fingerprint", Args: []any{"redis-manual", "%service.name%", "%service.name\":\"redis-manual%", expectedBucketStart, expectedBucketEnd}, }, }, @@ -134,7 +134,7 @@ func TestResourceFilterStatementBuilder_Traces(t *testing.T) { start: testStartNs, end: testEndNs, expected: &qbtypes.Statement{ - Query: "SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE ((simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND (simpleJSONExtractString(labels, 'k8s.namespace.name') = ? AND labels LIKE ? AND labels LIKE ?)) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?", + Query: "SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE ((simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND (simpleJSONExtractString(labels, 'k8s.namespace.name') = ? AND labels LIKE ? AND labels LIKE ?)) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ? GROUP BY fingerprint", Args: []any{"redis-manual", "%service.name%", "%service.name\":\"redis-manual%", "production", "%k8s.namespace.name%", "%k8s.namespace.name\":\"production%", expectedBucketStart, expectedBucketEnd}, }, }, @@ -183,7 +183,7 @@ func TestResourceFilterStatementBuilder_Traces(t *testing.T) { start: testStartNs, end: testEndNs, expected: &qbtypes.Statement{ - Query: "SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (LOWER(simpleJSONExtractString(labels, 'service.name')) LIKE LOWER(?) AND labels LIKE ? AND LOWER(labels) LIKE LOWER(?)) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?", + Query: "SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (LOWER(simpleJSONExtractString(labels, 'service.name')) LIKE LOWER(?) AND labels LIKE ? AND LOWER(labels) LIKE LOWER(?)) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ? GROUP BY fingerprint", Args: []any{"redis%", "%service.name%", "%service.name%redis%%", expectedBucketStart, expectedBucketEnd}, }, }, @@ -198,7 +198,7 @@ func TestResourceFilterStatementBuilder_Traces(t *testing.T) { start: testStartNs, end: testEndNs, expected: &qbtypes.Statement{ - Query: "SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (simpleJSONHas(labels, 'service.name') = ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?", + Query: "SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (simpleJSONHas(labels, 'service.name') = ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ? GROUP BY fingerprint", Args: []any{true, "%service.name%", expectedBucketStart, expectedBucketEnd}, }, }, @@ -213,7 +213,7 @@ func TestResourceFilterStatementBuilder_Traces(t *testing.T) { start: testStartNs, end: testEndNs, expected: &qbtypes.Statement{ - Query: "SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (simpleJSONHas(labels, 'service.name') <> ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?", + Query: "SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (simpleJSONHas(labels, 'service.name') <> ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ? GROUP BY fingerprint", Args: []any{true, expectedBucketStart, expectedBucketEnd}, }, }, @@ -228,7 +228,7 @@ func TestResourceFilterStatementBuilder_Traces(t *testing.T) { start: testStartNs, end: testEndNs, expected: &qbtypes.Statement{ - Query: "SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE ((simpleJSONExtractString(labels, 'service.name') = ? OR simpleJSONExtractString(labels, 'service.name') = ?) AND labels LIKE ? AND (labels LIKE ? OR labels LIKE ?)) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?", + Query: "SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE ((simpleJSONExtractString(labels, 'service.name') = ? OR simpleJSONExtractString(labels, 'service.name') = ?) AND labels LIKE ? AND (labels LIKE ? OR labels LIKE ?)) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ? GROUP BY fingerprint", Args: []any{"redis", "postgres", "%service.name%", "%service.name\":\"redis%", "%service.name\":\"postgres%", expectedBucketStart, expectedBucketEnd}, }, }, @@ -243,7 +243,7 @@ func TestResourceFilterStatementBuilder_Traces(t *testing.T) { start: testStartNs, end: testEndNs, expected: &qbtypes.Statement{ - Query: "SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE ((simpleJSONExtractString(labels, 'service.name') <> ? AND simpleJSONExtractString(labels, 'service.name') <> ?) AND (labels NOT LIKE ? AND labels NOT LIKE ?)) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?", + Query: "SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE ((simpleJSONExtractString(labels, 'service.name') <> ? AND simpleJSONExtractString(labels, 'service.name') <> ?) AND (labels NOT LIKE ? AND labels NOT LIKE ?)) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ? GROUP BY fingerprint", Args: []any{"redis", "postgres", "%service.name\":\"redis%", "%service.name\":\"postgres%", expectedBucketStart, expectedBucketEnd}, }, }, @@ -258,7 +258,7 @@ func TestResourceFilterStatementBuilder_Traces(t *testing.T) { start: testStartNs, end: testEndNs, expected: &qbtypes.Statement{ - Query: "SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (LOWER(simpleJSONExtractString(labels, 'service.name')) LIKE LOWER(?) AND labels LIKE ? AND LOWER(labels) LIKE LOWER(?)) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?", + Query: "SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (LOWER(simpleJSONExtractString(labels, 'service.name')) LIKE LOWER(?) AND labels LIKE ? AND LOWER(labels) LIKE LOWER(?)) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ? GROUP BY fingerprint", Args: []any{"%redis%", "%service.name%", "%service.name%redis%", expectedBucketStart, expectedBucketEnd}, }, }, @@ -273,7 +273,7 @@ func TestResourceFilterStatementBuilder_Traces(t *testing.T) { start: testStartNs, end: testEndNs, expected: &qbtypes.Statement{ - Query: "SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (match(simpleJSONExtractString(labels, 'service.name'), ?) AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?", + Query: "SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (match(simpleJSONExtractString(labels, 'service.name'), ?) AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ? GROUP BY fingerprint", Args: []any{"redis.*", "%service.name%", expectedBucketStart, expectedBucketEnd}, }, }, @@ -288,7 +288,7 @@ func TestResourceFilterStatementBuilder_Traces(t *testing.T) { start: testStartNs, end: testEndNs, expected: &qbtypes.Statement{ - Query: "SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (simpleJSONExtractString(labels, 'service.name') <> ? AND labels NOT LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?", + Query: "SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (simpleJSONExtractString(labels, 'service.name') <> ? AND labels NOT LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ? GROUP BY fingerprint", Args: []any{"redis", "%service.name\":\"redis%", expectedBucketStart, expectedBucketEnd}, }, }, @@ -315,7 +315,7 @@ func TestResourceFilterStatementBuilder_Traces(t *testing.T) { start: testStartNs, end: 0, expected: &qbtypes.Statement{ - Query: "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 >= ?", + Query: "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 >= ? GROUP BY fingerprint", Args: []any{"redis", "%service.name%", "%service.name\":\"redis%", expectedBucketStart}, }, }, @@ -330,7 +330,7 @@ func TestResourceFilterStatementBuilder_Traces(t *testing.T) { start: testStartNs, end: testEndNs, expected: &qbtypes.Statement{ - Query: "SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE NOT (((simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?))) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?", + Query: "SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE NOT (((simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?))) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ? GROUP BY fingerprint", Args: []any{"redis", "%service.name%", "%service.name\":\"redis%", expectedBucketStart, expectedBucketEnd}, }, }, @@ -406,7 +406,7 @@ func TestResourceFilterStatementBuilder_Logs(t *testing.T) { start: testStartNs, end: testEndNs, expected: &qbtypes.Statement{ - Query: "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 <= ?", + Query: "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 <= ? GROUP BY fingerprint", Args: []any{"redis-manual", "%service.name%", "%service.name\":\"redis-manual%", expectedBucketStart, expectedBucketEnd}, }, }, @@ -443,7 +443,7 @@ func TestResourceFilterStatementBuilder_Logs(t *testing.T) { start: testStartNs, end: testEndNs, expected: &qbtypes.Statement{ - Query: "SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE ((simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND (simpleJSONExtractString(labels, 'k8s.namespace.name') = ? AND labels LIKE ? AND labels LIKE ?)) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?", + Query: "SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE ((simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND (simpleJSONExtractString(labels, 'k8s.namespace.name') = ? AND labels LIKE ? AND labels LIKE ?)) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ? GROUP BY fingerprint", Args: []any{"redis", "%service.name%", "%service.name\":\"redis%", "default", "%k8s.namespace.name%", "%k8s.namespace.name\":\"default%", expectedBucketStart, expectedBucketEnd}, }, }, @@ -461,7 +461,7 @@ func TestResourceFilterStatementBuilder_Logs(t *testing.T) { start: uint64(1769976178000000000), // These will give bucket start 1769974378 and end 1770062578 end: uint64(1770062578000000000), expected: &qbtypes.Statement{ - Query: "SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE ((simpleJSONExtractString(labels, 'env') = ? AND labels LIKE ? AND labels LIKE ?) AND (simpleJSONExtractString(labels, 'k8s.deployment.name') = ? AND labels LIKE ? AND labels LIKE ?)) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?", + Query: "SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE ((simpleJSONExtractString(labels, 'env') = ? AND labels LIKE ? AND labels LIKE ?) AND (simpleJSONExtractString(labels, 'k8s.deployment.name') = ? AND labels LIKE ? AND labels LIKE ?)) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ? GROUP BY fingerprint", Args: []any{"prod", "%env%", "%env\":\"prod%", "prod-deployment", "%k8s.deployment.name%", "%k8s.deployment.name\":\"prod-deployment%", uint64(1769974378), uint64(1770062578)}, }, }, @@ -606,7 +606,7 @@ func TestResourceFilterStatementBuilder_Variables(t *testing.T) { start: testStartNs, end: testEndNs, expected: &qbtypes.Statement{ - Query: "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 <= ?", + Query: "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 <= ? GROUP BY fingerprint", Args: []any{"redis-manual", "%service.name%", "%service.name\":\"redis-manual%", expectedBucketStart, expectedBucketEnd}, }, }, diff --git a/pkg/telemetrytraces/stmt_builder_test.go b/pkg/telemetrytraces/stmt_builder_test.go index 8c4a1395e59..d33ac281f00 100644 --- a/pkg/telemetrytraces/stmt_builder_test.go +++ b/pkg/telemetrytraces/stmt_builder_test.go @@ -47,7 +47,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(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) IS NOT NULL, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), 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 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(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) IS NOT NULL, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), 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 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`", + 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 <= ? GROUP BY fingerprint), __limit_cte AS (SELECT toString(multiIf(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) IS NOT NULL, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), 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 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(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) IS NOT NULL, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), 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 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), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448)}, }, expectedErr: nil, @@ -136,7 +136,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 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 timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? AND (`httpRoute`) GLOBAL IN (SELECT `httpRoute` FROM __limit_cte) GROUP BY ts, `httpRoute`", + 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 <= ? GROUP BY fingerprint), __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 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 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)}, }, expectedErr: nil, @@ -215,7 +215,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(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) IS NOT NULL, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), 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 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(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) IS NOT NULL, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), 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 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` ORDER BY ts desc", + 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 <= ? GROUP BY fingerprint), __limit_cte AS (SELECT toString(multiIf(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) IS NOT NULL, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), 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 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(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) IS NOT NULL, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), 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 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` ORDER BY ts desc", 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, @@ -244,7 +244,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(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) IS NOT NULL, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), 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 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(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) IS NOT NULL, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), 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 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`", + 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 <= ? GROUP BY fingerprint), __limit_cte AS (SELECT toString(multiIf(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) IS NOT NULL, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), 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 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(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) IS NOT NULL, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), 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 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)}, }, expectedErr: nil, @@ -283,7 +283,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(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) IS NOT NULL, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), 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 timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? GROUP BY `service.name` ORDER BY `service.name` desc LIMIT ?) SELECT toStartOfInterval(timestamp, INTERVAL 30 SECOND) AS ts, toString(multiIf(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) IS NOT NULL, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), 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 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` ORDER BY `service.name` desc, ts desc", + 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 <= ? GROUP BY fingerprint), __limit_cte AS (SELECT toString(multiIf(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) IS NOT NULL, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), 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 timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? GROUP BY `service.name` ORDER BY `service.name` desc LIMIT ?) SELECT toStartOfInterval(timestamp, INTERVAL 30 SECOND) AS ts, toString(multiIf(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) IS NOT NULL, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), 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 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` ORDER BY `service.name` desc, ts desc", 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, @@ -314,7 +314,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 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 timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? AND (`responseStatusCode`) GLOBAL IN (SELECT `responseStatusCode` FROM __limit_cte) GROUP BY ts, `responseStatusCode`", + 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 <= ? GROUP BY fingerprint), __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 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 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)}, }, expectedErr: nil, @@ -345,7 +345,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 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 timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? AND (`responseStatusCode`) GLOBAL IN (SELECT `responseStatusCode` FROM __limit_cte) GROUP BY ts, `responseStatusCode`", + 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 <= ? GROUP BY fingerprint), __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 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 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)}, }, expectedErr: nil, @@ -439,7 +439,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`, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) 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 timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? LIMIT ?", + 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 <= ? GROUP BY fingerprint) SELECT name AS `name`, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) 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 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}, }, expectedErr: nil, @@ -468,7 +468,7 @@ func TestStatementBuilderListQuery(t *testing.T) { Limit: 10, }, 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`, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) 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 timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? ORDER BY attributes_string['user.id'] AS `user.id` desc LIMIT ?", + 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 <= ? GROUP BY fingerprint) SELECT duration_nano AS `duration_nano`, name AS `name`, response_status_code AS `response_status_code`, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) 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 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}, }, expectedErr: nil, @@ -512,7 +512,7 @@ func TestStatementBuilderListQuery(t *testing.T) { Limit: 10, }, 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 timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? LIMIT ?", + 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 <= ? GROUP BY fingerprint) 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 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}, }, expectedErr: nil, @@ -556,7 +556,7 @@ func TestStatementBuilderListQuery(t *testing.T) { Limit: 10, }, 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`, multiIf(toString(`attribute_string_mixed$$materialization$$key`) != '', toString(`attribute_string_mixed$$materialization$$key`), toString(multiIf(resource.`mixed.materialization.key` IS NOT NULL, resource.`mixed.materialization.key`::String, mapContains(resources_string, 'mixed.materialization.key'), resources_string['mixed.materialization.key'], NULL)) != '', toString(multiIf(resource.`mixed.materialization.key` IS NOT NULL, resource.`mixed.materialization.key`::String, mapContains(resources_string, 'mixed.materialization.key'), resources_string['mixed.materialization.key'], NULL)), NULL) AS `mixed.materialization.key`, 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 timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? LIMIT ?", + 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 <= ? GROUP BY fingerprint) SELECT name AS `name`, resource_string_service$$name AS `serviceName`, duration_nano AS `durationNano`, http_method AS `httpMethod`, multiIf(toString(`attribute_string_mixed$$materialization$$key`) != '', toString(`attribute_string_mixed$$materialization$$key`), toString(multiIf(resource.`mixed.materialization.key` IS NOT NULL, resource.`mixed.materialization.key`::String, mapContains(resources_string, 'mixed.materialization.key'), resources_string['mixed.materialization.key'], NULL)) != '', toString(multiIf(resource.`mixed.materialization.key` IS NOT NULL, resource.`mixed.materialization.key`::String, mapContains(resources_string, 'mixed.materialization.key'), resources_string['mixed.materialization.key'], NULL)), NULL) AS `mixed.materialization.key`, 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 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}, }, expectedErr: nil, @@ -601,7 +601,7 @@ func TestStatementBuilderListQuery(t *testing.T) { Limit: 10, }, 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`, `attribute_string_mixed$$materialization$$key` AS `mixed.materialization.key`, 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 timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? LIMIT ?", + 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 <= ? GROUP BY fingerprint) SELECT name AS `name`, resource_string_service$$name AS `serviceName`, duration_nano AS `durationNano`, http_method AS `httpMethod`, `attribute_string_mixed$$materialization$$key` AS `mixed.materialization.key`, 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 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}, }, expectedErr: nil, @@ -807,7 +807,7 @@ func TestStatementBuilderTraceQuery(t *testing.T) { Limit: 10, }, 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 <= ?), __toe AS (SELECT trace_id FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __toe_duration_sorted AS (SELECT trace_id, duration_nano, resource_string_service$$name as `service.name`, name FROM signoz_traces.distributed_signoz_index_v3 WHERE parent_span_id = '' AND trace_id GLOBAL IN __toe AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? ORDER BY duration_nano DESC LIMIT 1 BY trace_id) SELECT __toe_duration_sorted.`service.name` AS `service.name`, __toe_duration_sorted.name AS `name`, count() AS span_count, __toe_duration_sorted.duration_nano AS `duration_nano`, __toe_duration_sorted.trace_id AS `trace_id` FROM __toe INNER JOIN __toe_duration_sorted ON __toe.trace_id = __toe_duration_sorted.trace_id GROUP BY trace_id, duration_nano, name, `service.name` ORDER BY duration_nano DESC LIMIT 1 BY trace_id LIMIT ? SETTINGS distributed_product_mode='allow', max_memory_usage=10000000000", + 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 <= ? GROUP BY fingerprint), __toe AS (SELECT trace_id FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __toe_duration_sorted AS (SELECT trace_id, duration_nano, resource_string_service$$name as `service.name`, name FROM signoz_traces.distributed_signoz_index_v3 WHERE parent_span_id = '' AND trace_id GLOBAL IN __toe AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? ORDER BY duration_nano DESC LIMIT 1 BY trace_id) SELECT __toe_duration_sorted.`service.name` AS `service.name`, __toe_duration_sorted.name AS `name`, count() AS span_count, __toe_duration_sorted.duration_nano AS `duration_nano`, __toe_duration_sorted.trace_id AS `trace_id` FROM __toe INNER JOIN __toe_duration_sorted ON __toe.trace_id = __toe_duration_sorted.trace_id GROUP BY trace_id, duration_nano, name, `service.name` ORDER BY duration_nano DESC LIMIT 1 BY trace_id LIMIT ? SETTINGS distributed_product_mode='allow', max_memory_usage=10000000000", Args: []any{"redis-manual", "%service.name%", "%service.name\":\"redis-manual%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10}, }, expectedErr: nil, diff --git a/pkg/telemetrytraces/trace_operator_cte_builder_test.go b/pkg/telemetrytraces/trace_operator_cte_builder_test.go index b2599ad31e5..54bf4e8f710 100644 --- a/pkg/telemetrytraces/trace_operator_cte_builder_test.go +++ b/pkg/telemetrytraces/trace_operator_cte_builder_test.go @@ -67,7 +67,7 @@ func TestTraceOperatorStatementBuilder(t *testing.T) { }, }, expected: qbtypes.Statement{ - Query: "WITH toDateTime64(1747947419000000000, 9) AS t_from, toDateTime64(1747983448000000000, 9) AS t_to, 1747945619 AS bucket_from, 1747983448 AS bucket_to, all_spans AS (SELECT *, resource_string_service$$name AS `service.name` FROM signoz_traces.distributed_signoz_index_v3 WHERE timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_A 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 <= ?), A AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_A) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_B 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 <= ?), B AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_B) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), A_DIR_DESC_B AS (SELECT p.* FROM A AS p INNER JOIN B AS c ON p.trace_id = c.trace_id AND p.span_id = c.parent_span_id) SELECT timestamp, trace_id, span_id, name, duration_nano, parent_span_id, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) AS `service.name` FROM A_DIR_DESC_B ORDER BY timestamp DESC LIMIT ? SETTINGS distributed_product_mode='allow', max_memory_usage=10000000000", + Query: "WITH toDateTime64(1747947419000000000, 9) AS t_from, toDateTime64(1747983448000000000, 9) AS t_to, 1747945619 AS bucket_from, 1747983448 AS bucket_to, all_spans AS (SELECT *, resource_string_service$$name AS `service.name` FROM signoz_traces.distributed_signoz_index_v3 WHERE timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_A 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 <= ? GROUP BY fingerprint), A AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_A) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_B 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 <= ? GROUP BY fingerprint), B AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_B) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), A_DIR_DESC_B AS (SELECT p.* FROM A AS p INNER JOIN B AS c ON p.trace_id = c.trace_id AND p.span_id = c.parent_span_id) SELECT timestamp, trace_id, span_id, name, duration_nano, parent_span_id, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) AS `service.name` FROM A_DIR_DESC_B ORDER BY timestamp DESC LIMIT ? SETTINGS distributed_product_mode='allow', max_memory_usage=10000000000", Args: []any{"1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), "frontend", "%service.name%", "%service.name\":\"frontend%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), "backend", "%service.name%", "%service.name\":\"backend%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10}, }, expectedErr: nil, @@ -104,7 +104,7 @@ func TestTraceOperatorStatementBuilder(t *testing.T) { }, }, expected: qbtypes.Statement{ - Query: "WITH toDateTime64(1747947419000000000, 9) AS t_from, toDateTime64(1747983448000000000, 9) AS t_to, 1747945619 AS bucket_from, 1747983448 AS bucket_to, all_spans AS (SELECT *, resource_string_service$$name AS `service.name` FROM signoz_traces.distributed_signoz_index_v3 WHERE timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_A 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 <= ?), A AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_A) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_B 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 <= ?), B AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_B) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), A_INDIR_DESC_B AS (WITH RECURSIVE up AS (SELECT d.trace_id, d.span_id, d.parent_span_id, 0 AS depth FROM B AS d UNION ALL SELECT p.trace_id, p.span_id, p.parent_span_id, up.depth + 1 FROM all_spans AS p JOIN up ON p.trace_id = up.trace_id AND p.span_id = up.parent_span_id WHERE up.depth < 100) SELECT DISTINCT a.* FROM A AS a GLOBAL INNER JOIN (SELECT DISTINCT trace_id, span_id FROM up WHERE depth > 0 ) AS ancestors ON ancestors.trace_id = a.trace_id AND ancestors.span_id = a.span_id) SELECT timestamp, trace_id, span_id, name, duration_nano, parent_span_id FROM A_INDIR_DESC_B ORDER BY timestamp DESC LIMIT ? SETTINGS distributed_product_mode='allow', max_memory_usage=10000000000", + Query: "WITH toDateTime64(1747947419000000000, 9) AS t_from, toDateTime64(1747983448000000000, 9) AS t_to, 1747945619 AS bucket_from, 1747983448 AS bucket_to, all_spans AS (SELECT *, resource_string_service$$name AS `service.name` FROM signoz_traces.distributed_signoz_index_v3 WHERE timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_A 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 <= ? GROUP BY fingerprint), A AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_A) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_B 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 <= ? GROUP BY fingerprint), B AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_B) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), A_INDIR_DESC_B AS (WITH RECURSIVE up AS (SELECT d.trace_id, d.span_id, d.parent_span_id, 0 AS depth FROM B AS d UNION ALL SELECT p.trace_id, p.span_id, p.parent_span_id, up.depth + 1 FROM all_spans AS p JOIN up ON p.trace_id = up.trace_id AND p.span_id = up.parent_span_id WHERE up.depth < 100) SELECT DISTINCT a.* FROM A AS a GLOBAL INNER JOIN (SELECT DISTINCT trace_id, span_id FROM up WHERE depth > 0 ) AS ancestors ON ancestors.trace_id = a.trace_id AND ancestors.span_id = a.span_id) SELECT timestamp, trace_id, span_id, name, duration_nano, parent_span_id FROM A_INDIR_DESC_B ORDER BY timestamp DESC LIMIT ? SETTINGS distributed_product_mode='allow', max_memory_usage=10000000000", Args: []any{"1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), "gateway", "%service.name%", "%service.name\":\"gateway%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), "database", "%service.name%", "%service.name\":\"database%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 5}, }, expectedErr: nil, @@ -141,7 +141,7 @@ func TestTraceOperatorStatementBuilder(t *testing.T) { }, }, expected: qbtypes.Statement{ - Query: "WITH toDateTime64(1747947419000000000, 9) AS t_from, toDateTime64(1747983448000000000, 9) AS t_to, 1747945619 AS bucket_from, 1747983448 AS bucket_to, all_spans AS (SELECT *, resource_string_service$$name AS `service.name` FROM signoz_traces.distributed_signoz_index_v3 WHERE timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_A 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 <= ?), A AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_A) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_B 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 <= ?), B AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_B) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), A_AND_B AS (SELECT l.* FROM A AS l INNER JOIN B AS r ON l.trace_id = r.trace_id) SELECT timestamp, trace_id, span_id, name, duration_nano, parent_span_id FROM A_AND_B ORDER BY timestamp DESC LIMIT ? SETTINGS distributed_product_mode='allow', max_memory_usage=10000000000", + Query: "WITH toDateTime64(1747947419000000000, 9) AS t_from, toDateTime64(1747983448000000000, 9) AS t_to, 1747945619 AS bucket_from, 1747983448 AS bucket_to, all_spans AS (SELECT *, resource_string_service$$name AS `service.name` FROM signoz_traces.distributed_signoz_index_v3 WHERE timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_A 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 <= ? GROUP BY fingerprint), A AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_A) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_B 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 <= ? GROUP BY fingerprint), B AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_B) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), A_AND_B AS (SELECT l.* FROM A AS l INNER JOIN B AS r ON l.trace_id = r.trace_id) SELECT timestamp, trace_id, span_id, name, duration_nano, parent_span_id FROM A_AND_B ORDER BY timestamp DESC LIMIT ? SETTINGS distributed_product_mode='allow', max_memory_usage=10000000000", Args: []any{"1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), "frontend", "%service.name%", "%service.name\":\"frontend%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), "backend", "%service.name%", "%service.name\":\"backend%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 15}, }, expectedErr: nil, @@ -178,7 +178,7 @@ func TestTraceOperatorStatementBuilder(t *testing.T) { }, }, expected: qbtypes.Statement{ - Query: "WITH toDateTime64(1747947419000000000, 9) AS t_from, toDateTime64(1747983448000000000, 9) AS t_to, 1747945619 AS bucket_from, 1747983448 AS bucket_to, all_spans AS (SELECT *, resource_string_service$$name AS `service.name` FROM signoz_traces.distributed_signoz_index_v3 WHERE timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_A 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 <= ?), A AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_A) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_B 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 <= ?), B AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_B) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), A_OR_B AS (SELECT * FROM A UNION DISTINCT SELECT * FROM B) SELECT timestamp, trace_id, span_id, name, duration_nano, parent_span_id FROM A_OR_B ORDER BY timestamp DESC LIMIT ? SETTINGS distributed_product_mode='allow', max_memory_usage=10000000000", + Query: "WITH toDateTime64(1747947419000000000, 9) AS t_from, toDateTime64(1747983448000000000, 9) AS t_to, 1747945619 AS bucket_from, 1747983448 AS bucket_to, all_spans AS (SELECT *, resource_string_service$$name AS `service.name` FROM signoz_traces.distributed_signoz_index_v3 WHERE timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_A 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 <= ? GROUP BY fingerprint), A AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_A) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_B 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 <= ? GROUP BY fingerprint), B AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_B) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), A_OR_B AS (SELECT * FROM A UNION DISTINCT SELECT * FROM B) SELECT timestamp, trace_id, span_id, name, duration_nano, parent_span_id FROM A_OR_B ORDER BY timestamp DESC LIMIT ? SETTINGS distributed_product_mode='allow', max_memory_usage=10000000000", Args: []any{"1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), "frontend", "%service.name%", "%service.name\":\"frontend%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), "backend", "%service.name%", "%service.name\":\"backend%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 20}, }, expectedErr: nil, @@ -215,7 +215,7 @@ func TestTraceOperatorStatementBuilder(t *testing.T) { }, }, expected: qbtypes.Statement{ - Query: "WITH toDateTime64(1747947419000000000, 9) AS t_from, toDateTime64(1747983448000000000, 9) AS t_to, 1747945619 AS bucket_from, 1747983448 AS bucket_to, all_spans AS (SELECT *, resource_string_service$$name AS `service.name` FROM signoz_traces.distributed_signoz_index_v3 WHERE timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_A 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 <= ?), A AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_A) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_B 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 <= ?), B AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_B) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), A_not_B AS (SELECT l.* FROM A AS l WHERE l.trace_id GLOBAL NOT IN (SELECT DISTINCT trace_id FROM B)) SELECT timestamp, trace_id, span_id, name, duration_nano, parent_span_id FROM A_not_B ORDER BY timestamp DESC LIMIT ? SETTINGS distributed_product_mode='allow', max_memory_usage=10000000000", + Query: "WITH toDateTime64(1747947419000000000, 9) AS t_from, toDateTime64(1747983448000000000, 9) AS t_to, 1747945619 AS bucket_from, 1747983448 AS bucket_to, all_spans AS (SELECT *, resource_string_service$$name AS `service.name` FROM signoz_traces.distributed_signoz_index_v3 WHERE timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_A 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 <= ? GROUP BY fingerprint), A AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_A) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_B 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 <= ? GROUP BY fingerprint), B AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_B) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), A_not_B AS (SELECT l.* FROM A AS l WHERE l.trace_id GLOBAL NOT IN (SELECT DISTINCT trace_id FROM B)) SELECT timestamp, trace_id, span_id, name, duration_nano, parent_span_id FROM A_not_B ORDER BY timestamp DESC LIMIT ? SETTINGS distributed_product_mode='allow', max_memory_usage=10000000000", Args: []any{"1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), "frontend", "%service.name%", "%service.name\":\"frontend%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), "backend", "%service.name%", "%service.name\":\"backend%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10}, }, expectedErr: nil, @@ -264,7 +264,7 @@ func TestTraceOperatorStatementBuilder(t *testing.T) { }, }, expected: qbtypes.Statement{ - Query: "WITH toDateTime64(1747947419000000000, 9) AS t_from, toDateTime64(1747983448000000000, 9) AS t_to, 1747945619 AS bucket_from, 1747983448 AS bucket_to, all_spans AS (SELECT *, resource_string_service$$name AS `service.name` FROM signoz_traces.distributed_signoz_index_v3 WHERE timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_A 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 <= ?), A AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_A) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_B 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 <= ?), B AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_B) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), A_DIR_DESC_B AS (SELECT p.* FROM A AS p INNER JOIN B AS c ON p.trace_id = c.trace_id AND p.span_id = c.parent_span_id) SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toString(multiIf(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) IS NOT NULL, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), NULL)) AS `service.name`, count() AS __result_0 FROM A_DIR_DESC_B GROUP BY ts, `service.name` ORDER BY ts desc SETTINGS distributed_product_mode='allow', max_memory_usage=10000000000", + Query: "WITH toDateTime64(1747947419000000000, 9) AS t_from, toDateTime64(1747983448000000000, 9) AS t_to, 1747945619 AS bucket_from, 1747983448 AS bucket_to, all_spans AS (SELECT *, resource_string_service$$name AS `service.name` FROM signoz_traces.distributed_signoz_index_v3 WHERE timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_A 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 <= ? GROUP BY fingerprint), A AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_A) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_B 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 <= ? GROUP BY fingerprint), B AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_B) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), A_DIR_DESC_B AS (SELECT p.* FROM A AS p INNER JOIN B AS c ON p.trace_id = c.trace_id AND p.span_id = c.parent_span_id) SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toString(multiIf(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) IS NOT NULL, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), NULL)) AS `service.name`, count() AS __result_0 FROM A_DIR_DESC_B GROUP BY ts, `service.name` ORDER BY ts desc SETTINGS distributed_product_mode='allow', max_memory_usage=10000000000", Args: []any{"1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), "frontend", "%service.name%", "%service.name\":\"frontend%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), "backend", "%service.name%", "%service.name\":\"backend%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448)}, }, expectedErr: nil, @@ -323,7 +323,7 @@ func TestTraceOperatorStatementBuilder(t *testing.T) { }, }, expected: qbtypes.Statement{ - Query: "WITH toDateTime64(1747947419000000000, 9) AS t_from, toDateTime64(1747983448000000000, 9) AS t_to, 1747945619 AS bucket_from, 1747983448 AS bucket_to, all_spans AS (SELECT *, resource_string_service$$name AS `service.name` FROM signoz_traces.distributed_signoz_index_v3 WHERE timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_A 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 <= ?), A AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_A) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), B AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? AND toFloat64(response_status_code) < ?), A_AND_B AS (SELECT l.* FROM A AS l INNER JOIN B AS r ON l.trace_id = r.trace_id) SELECT toString(multiIf(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) IS NOT NULL, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), NULL)) AS `service.name`, avg(multiIf(duration_nano <> ?, duration_nano, NULL)) AS __result_0 FROM A_AND_B GROUP BY `service.name` ORDER BY __result_0 desc SETTINGS distributed_product_mode='allow', max_memory_usage=10000000000", + Query: "WITH toDateTime64(1747947419000000000, 9) AS t_from, toDateTime64(1747983448000000000, 9) AS t_to, 1747945619 AS bucket_from, 1747983448 AS bucket_to, all_spans AS (SELECT *, resource_string_service$$name AS `service.name` FROM signoz_traces.distributed_signoz_index_v3 WHERE timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_A 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 <= ? GROUP BY fingerprint), A AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_A) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), B AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? AND toFloat64(response_status_code) < ?), A_AND_B AS (SELECT l.* FROM A AS l INNER JOIN B AS r ON l.trace_id = r.trace_id) SELECT toString(multiIf(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) IS NOT NULL, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), NULL)) AS `service.name`, avg(multiIf(duration_nano <> ?, duration_nano, NULL)) AS __result_0 FROM A_AND_B GROUP BY `service.name` ORDER BY __result_0 desc SETTINGS distributed_product_mode='allow', max_memory_usage=10000000000", Args: []any{"1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), "frontend", "%service.name%", "%service.name\":\"frontend%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), float64(400), 0}, }, expectedErr: nil, @@ -380,7 +380,7 @@ func TestTraceOperatorStatementBuilder(t *testing.T) { }, }, expected: qbtypes.Statement{ - Query: "WITH toDateTime64(1747947419000000000, 9) AS t_from, toDateTime64(1747983448000000000, 9) AS t_to, 1747945619 AS bucket_from, 1747983448 AS bucket_to, all_spans AS (SELECT *, resource_string_service$$name AS `service.name` FROM signoz_traces.distributed_signoz_index_v3 WHERE timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_A 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 <= ?), A AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_A) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_B 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 <= ?), B AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_B) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), A_DIR_DESC_B AS (SELECT p.* FROM A AS p INNER JOIN B AS c ON p.trace_id = c.trace_id AND p.span_id = c.parent_span_id), __resource_filter_C 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 <= ?), C AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_C) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_D 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 <= ?), D AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_D) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), C_DIR_DESC_D AS (SELECT p.* FROM C AS p INNER JOIN D AS c ON p.trace_id = c.trace_id AND p.span_id = c.parent_span_id), A_DIR_DESC_B_AND_C_DIR_DESC_D AS (SELECT l.* FROM A_DIR_DESC_B AS l INNER JOIN C_DIR_DESC_D AS r ON l.trace_id = r.trace_id) SELECT timestamp, trace_id, span_id, name, duration_nano, parent_span_id FROM A_DIR_DESC_B_AND_C_DIR_DESC_D ORDER BY timestamp DESC LIMIT ? SETTINGS distributed_product_mode='allow', max_memory_usage=10000000000", + Query: "WITH toDateTime64(1747947419000000000, 9) AS t_from, toDateTime64(1747983448000000000, 9) AS t_to, 1747945619 AS bucket_from, 1747983448 AS bucket_to, all_spans AS (SELECT *, resource_string_service$$name AS `service.name` FROM signoz_traces.distributed_signoz_index_v3 WHERE timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_A 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 <= ? GROUP BY fingerprint), A AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_A) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_B 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 <= ? GROUP BY fingerprint), B AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_B) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), A_DIR_DESC_B AS (SELECT p.* FROM A AS p INNER JOIN B AS c ON p.trace_id = c.trace_id AND p.span_id = c.parent_span_id), __resource_filter_C 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 <= ? GROUP BY fingerprint), C AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_C) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_D 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 <= ? GROUP BY fingerprint), D AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_D) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), C_DIR_DESC_D AS (SELECT p.* FROM C AS p INNER JOIN D AS c ON p.trace_id = c.trace_id AND p.span_id = c.parent_span_id), A_DIR_DESC_B_AND_C_DIR_DESC_D AS (SELECT l.* FROM A_DIR_DESC_B AS l INNER JOIN C_DIR_DESC_D AS r ON l.trace_id = r.trace_id) SELECT timestamp, trace_id, span_id, name, duration_nano, parent_span_id FROM A_DIR_DESC_B_AND_C_DIR_DESC_D ORDER BY timestamp DESC LIMIT ? SETTINGS distributed_product_mode='allow', max_memory_usage=10000000000", Args: []any{"1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), "frontend", "%service.name%", "%service.name\":\"frontend%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), "backend", "%service.name%", "%service.name\":\"backend%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), "auth", "%service.name%", "%service.name\":\"auth%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), "database", "%service.name%", "%service.name\":\"database%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 5}, }, expectedErr: nil, @@ -414,7 +414,7 @@ func TestTraceOperatorStatementBuilder(t *testing.T) { }, }, expected: qbtypes.Statement{ - Query: "WITH toDateTime64(1747947419000000000, 9) AS t_from, toDateTime64(1747983448000000000, 9) AS t_to, 1747945619 AS bucket_from, 1747983448 AS bucket_to, all_spans AS (SELECT *, resource_string_service$$name AS `service.name` FROM signoz_traces.distributed_signoz_index_v3 WHERE timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_A 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 <= ?), A AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_A) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_B 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 <= ?), B AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_B) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), A_INDIR_DESC_B AS (WITH RECURSIVE up AS (SELECT d.trace_id, d.span_id, d.parent_span_id, 0 AS depth FROM B AS d UNION ALL SELECT p.trace_id, p.span_id, p.parent_span_id, up.depth + 1 FROM all_spans AS p JOIN up ON p.trace_id = up.trace_id AND p.span_id = up.parent_span_id WHERE up.depth < 100) SELECT DISTINCT a.* FROM A AS a GLOBAL INNER JOIN (SELECT DISTINCT trace_id, span_id FROM up WHERE depth > 0 ) AS ancestors ON ancestors.trace_id = a.trace_id AND ancestors.span_id = a.span_id), __return_from_B AS (SELECT * FROM B WHERE trace_id IN (SELECT DISTINCT trace_id FROM A_INDIR_DESC_B)) SELECT timestamp, trace_id, span_id, name, duration_nano, parent_span_id FROM __return_from_B ORDER BY timestamp DESC LIMIT ? SETTINGS distributed_product_mode='allow', max_memory_usage=10000000000", + Query: "WITH toDateTime64(1747947419000000000, 9) AS t_from, toDateTime64(1747983448000000000, 9) AS t_to, 1747945619 AS bucket_from, 1747983448 AS bucket_to, all_spans AS (SELECT *, resource_string_service$$name AS `service.name` FROM signoz_traces.distributed_signoz_index_v3 WHERE timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_A 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 <= ? GROUP BY fingerprint), A AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_A) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_B 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 <= ? GROUP BY fingerprint), B AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_B) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), A_INDIR_DESC_B AS (WITH RECURSIVE up AS (SELECT d.trace_id, d.span_id, d.parent_span_id, 0 AS depth FROM B AS d UNION ALL SELECT p.trace_id, p.span_id, p.parent_span_id, up.depth + 1 FROM all_spans AS p JOIN up ON p.trace_id = up.trace_id AND p.span_id = up.parent_span_id WHERE up.depth < 100) SELECT DISTINCT a.* FROM A AS a GLOBAL INNER JOIN (SELECT DISTINCT trace_id, span_id FROM up WHERE depth > 0 ) AS ancestors ON ancestors.trace_id = a.trace_id AND ancestors.span_id = a.span_id), __return_from_B AS (SELECT * FROM B WHERE trace_id IN (SELECT DISTINCT trace_id FROM A_INDIR_DESC_B)) SELECT timestamp, trace_id, span_id, name, duration_nano, parent_span_id FROM __return_from_B ORDER BY timestamp DESC LIMIT ? SETTINGS distributed_product_mode='allow', max_memory_usage=10000000000", Args: []any{"1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), "gateway", "%service.name%", "%service.name\":\"gateway%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), "database", "%service.name%", "%service.name\":\"database%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10}, }, expectedErr: nil, @@ -456,7 +456,7 @@ func TestTraceOperatorStatementBuilder(t *testing.T) { }, }, expected: qbtypes.Statement{ - Query: "WITH toDateTime64(1747947419000000000, 9) AS t_from, toDateTime64(1747983448000000000, 9) AS t_to, 1747945619 AS bucket_from, 1747983448 AS bucket_to, all_spans AS (SELECT *, resource_string_service$$name AS `service.name` FROM signoz_traces.distributed_signoz_index_v3 WHERE timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_A 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 <= ?), A AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_A) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_B 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 <= ?), B AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_B) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), A_INDIR_DESC_B AS (WITH RECURSIVE up AS (SELECT d.trace_id, d.span_id, d.parent_span_id, 0 AS depth FROM B AS d UNION ALL SELECT p.trace_id, p.span_id, p.parent_span_id, up.depth + 1 FROM all_spans AS p JOIN up ON p.trace_id = up.trace_id AND p.span_id = up.parent_span_id WHERE up.depth < 100) SELECT DISTINCT a.* FROM A AS a GLOBAL INNER JOIN (SELECT DISTINCT trace_id, span_id FROM up WHERE depth > 0 ) AS ancestors ON ancestors.trace_id = a.trace_id AND ancestors.span_id = a.span_id), __resource_filter_C 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 <= ?), C AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_C) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), A_INDIR_DESC_B_AND_C AS (SELECT l.* FROM A_INDIR_DESC_B AS l INNER JOIN C AS r ON l.trace_id = r.trace_id), __return_from_C AS (SELECT * FROM C WHERE trace_id IN (SELECT DISTINCT trace_id FROM A_INDIR_DESC_B_AND_C)) SELECT timestamp, trace_id, span_id, name, duration_nano, parent_span_id FROM __return_from_C ORDER BY timestamp DESC LIMIT ? SETTINGS distributed_product_mode='allow', max_memory_usage=10000000000", + Query: "WITH toDateTime64(1747947419000000000, 9) AS t_from, toDateTime64(1747983448000000000, 9) AS t_to, 1747945619 AS bucket_from, 1747983448 AS bucket_to, all_spans AS (SELECT *, resource_string_service$$name AS `service.name` FROM signoz_traces.distributed_signoz_index_v3 WHERE timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_A 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 <= ? GROUP BY fingerprint), A AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_A) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_B 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 <= ? GROUP BY fingerprint), B AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_B) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), A_INDIR_DESC_B AS (WITH RECURSIVE up AS (SELECT d.trace_id, d.span_id, d.parent_span_id, 0 AS depth FROM B AS d UNION ALL SELECT p.trace_id, p.span_id, p.parent_span_id, up.depth + 1 FROM all_spans AS p JOIN up ON p.trace_id = up.trace_id AND p.span_id = up.parent_span_id WHERE up.depth < 100) SELECT DISTINCT a.* FROM A AS a GLOBAL INNER JOIN (SELECT DISTINCT trace_id, span_id FROM up WHERE depth > 0 ) AS ancestors ON ancestors.trace_id = a.trace_id AND ancestors.span_id = a.span_id), __resource_filter_C 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 <= ? GROUP BY fingerprint), C AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_C) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), A_INDIR_DESC_B_AND_C AS (SELECT l.* FROM A_INDIR_DESC_B AS l INNER JOIN C AS r ON l.trace_id = r.trace_id), __return_from_C AS (SELECT * FROM C WHERE trace_id IN (SELECT DISTINCT trace_id FROM A_INDIR_DESC_B_AND_C)) SELECT timestamp, trace_id, span_id, name, duration_nano, parent_span_id FROM __return_from_C ORDER BY timestamp DESC LIMIT ? SETTINGS distributed_product_mode='allow', max_memory_usage=10000000000", Args: []any{"1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), "gateway", "%service.name%", "%service.name\":\"gateway%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), "database", "%service.name%", "%service.name\":\"database%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), "auth", "%service.name%", "%service.name\":\"auth%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10}, }, expectedErr: nil,