Compare commits

..

2 Commits

Author SHA1 Message Date
Jatinderjit Singh
c73f5aad16 feat(rules)!: drop frontend source URL fallback for related logs/traces and generator URL
Always derive the host portion of the rule generator URL and the
related_logs / related_traces annotations from SIGNOZ_ALERTMANAGER_SIGNOZ_EXTERNAL__URL.
Self-hosted deployments that have not set the env var will see the
default (http://localhost:8080) in alert notifications, which makes the
need to configure it surface clearly. Revert this commit to restore the
prior fallback behavior.

Refs SigNoz/engineering-pod#5055

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 17:48:28 +05:30
Jatinderjit Singh
88620b567e feat(rules): use alertmanager external URL for related logs/traces and generator URL
Plumbs SIGNOZ_ALERTMANAGER_SIGNOZ_EXTERNAL__URL through the rule manager so
the host portion of related_logs / related_traces annotations and the
rule generator URL (used in Slack/Teams/etc. notifications) come from the
operator-configured external URL instead of the frontend window.location
captured at rule-creation time. Frontend-supplied source URL is retained
as a fallback when the env var has not been set so existing self-hosted
deployments keep working.

Refs SigNoz/engineering-pod#5055

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 17:45:31 +05:30
108 changed files with 579 additions and 1887 deletions

7
.github/CODEOWNERS vendored
View File

@@ -118,9 +118,6 @@ go.mod @therealpandey
/tests/integration/ @therealpandey
# e2e tests
/tests/e2e/ @AshwinBhatkal
# Flagger Owners
/pkg/flagger/ @therealpandey
@@ -165,7 +162,3 @@ go.mod @therealpandey
/frontend/src/lib/dashboard/ @SigNoz/pulse-frontend
/frontend/src/lib/dashboardVariables/ @SigNoz/pulse-frontend
/frontend/src/components/NewSelect/ @SigNoz/pulse-frontend
## Dashboard V2
/frontend/src/pages/DashboardPageV2/ @SigNoz/pulse-frontend
/frontend/src/pages/DashboardsListPageV2/ @SigNoz/pulse-frontend

View File

@@ -3,6 +3,7 @@ package main
import (
"context"
"log/slog"
"net/url"
"github.com/spf13/cobra"
@@ -118,8 +119,8 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
func(_ sqlstore.SQLStore, _ global.Global, _ zeus.Zeus, _ gateway.Gateway, _ licensing.Licensing, _ serviceaccount.Module, _ cloudintegration.Config) (cloudintegration.Module, error) {
return implcloudintegration.NewModule(), nil
},
func(c cache.Cache, am alertmanager.Alertmanager, ss sqlstore.SQLStore, ts telemetrystore.TelemetryStore, ms telemetrytypes.MetadataStore, p prometheus.Prometheus, og organization.Getter, rsh rulestatehistory.Module, q querier.Querier, qp queryparser.QueryParser) factory.NamedMap[factory.ProviderFactory[ruler.Ruler, ruler.Config]] {
return factory.MustNewNamedMap(signozruler.NewFactory(c, am, ss, ts, ms, p, og, rsh, q, qp, nil, nil))
func(c cache.Cache, am alertmanager.Alertmanager, ss sqlstore.SQLStore, ts telemetrystore.TelemetryStore, ms telemetrytypes.MetadataStore, p prometheus.Prometheus, og organization.Getter, rsh rulestatehistory.Module, q querier.Querier, qp queryparser.QueryParser, externalURL *url.URL) factory.NamedMap[factory.ProviderFactory[ruler.Ruler, ruler.Config]] {
return factory.MustNewNamedMap(signozruler.NewFactory(c, am, ss, ts, ms, p, og, rsh, q, qp, externalURL, nil, nil))
},
)
if err != nil {

View File

@@ -3,6 +3,7 @@ package main
import (
"context"
"log/slog"
"net/url"
"time"
"github.com/spf13/cobra"
@@ -181,8 +182,8 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
return implcloudintegration.NewModule(pkgcloudintegration.NewStore(sqlStore), global, zeus, gateway, licensing, serviceAccount, cloudProvidersMap, config)
},
func(c cache.Cache, am alertmanager.Alertmanager, ss sqlstore.SQLStore, ts telemetrystore.TelemetryStore, ms telemetrytypes.MetadataStore, p prometheus.Prometheus, og organization.Getter, rsh rulestatehistory.Module, q querier.Querier, qp queryparser.QueryParser) factory.NamedMap[factory.ProviderFactory[ruler.Ruler, ruler.Config]] {
return factory.MustNewNamedMap(signozruler.NewFactory(c, am, ss, ts, ms, p, og, rsh, q, qp, eerules.PrepareTaskFunc, eerules.TestNotification))
func(c cache.Cache, am alertmanager.Alertmanager, ss sqlstore.SQLStore, ts telemetrystore.TelemetryStore, ms telemetrytypes.MetadataStore, p prometheus.Prometheus, og organization.Getter, rsh rulestatehistory.Module, q querier.Querier, qp queryparser.QueryParser, externalURL *url.URL) factory.NamedMap[factory.ProviderFactory[ruler.Ruler, ruler.Config]] {
return factory.MustNewNamedMap(signozruler.NewFactory(c, am, ss, ts, ms, p, og, rsh, q, qp, externalURL, eerules.PrepareTaskFunc, eerules.TestNotification))
},
)
if err != nil {

View File

@@ -39,6 +39,7 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
baserules.WithQueryParser(opts.ManagerOpts.QueryParser),
baserules.WithMetadataStore(opts.ManagerOpts.MetadataStore),
baserules.WithRuleStateHistoryModule(opts.ManagerOpts.RuleStateHistoryModule),
baserules.WithExternalURL(opts.ManagerOpts.ExternalURL),
)
if err != nil {
@@ -63,6 +64,7 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
baserules.WithQueryParser(opts.ManagerOpts.QueryParser),
baserules.WithMetadataStore(opts.ManagerOpts.MetadataStore),
baserules.WithRuleStateHistoryModule(opts.ManagerOpts.RuleStateHistoryModule),
baserules.WithExternalURL(opts.ManagerOpts.ExternalURL),
)
if err != nil {
@@ -87,6 +89,7 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
baserules.WithQueryParser(opts.ManagerOpts.QueryParser),
baserules.WithMetadataStore(opts.ManagerOpts.MetadataStore),
baserules.WithRuleStateHistoryModule(opts.ManagerOpts.RuleStateHistoryModule),
baserules.WithExternalURL(opts.ManagerOpts.ExternalURL),
)
if err != nil {
return task, err
@@ -146,6 +149,7 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, error) {
baserules.WithSQLStore(opts.SQLStore),
baserules.WithQueryParser(opts.ManagerOpts.QueryParser),
baserules.WithMetadataStore(opts.ManagerOpts.MetadataStore),
baserules.WithExternalURL(opts.ManagerOpts.ExternalURL),
)
if err != nil {
@@ -167,6 +171,7 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, error) {
baserules.WithSQLStore(opts.SQLStore),
baserules.WithQueryParser(opts.ManagerOpts.QueryParser),
baserules.WithMetadataStore(opts.ManagerOpts.MetadataStore),
baserules.WithExternalURL(opts.ManagerOpts.ExternalURL),
)
if err != nil {
@@ -186,6 +191,7 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, error) {
baserules.WithSQLStore(opts.SQLStore),
baserules.WithQueryParser(opts.ManagerOpts.QueryParser),
baserules.WithMetadataStore(opts.ManagerOpts.MetadataStore),
baserules.WithExternalURL(opts.ManagerOpts.ExternalURL),
)
if err != nil {
slog.Error("failed to prepare a new anomaly rule for test", "name", alertname, errors.Attr(err))

View File

@@ -18,8 +18,6 @@ const BANNED_COMPONENTS = {
'Use @signozhq/ui/typography Typography instead of antd Typography.',
Switch: 'Use @signozhq/ui/switch Switch instead of antd Switch.',
Badge: 'Use @signozhq/ui/badge instead of antd Badge.',
Progress: 'Use @signozhq/ui/progress instead of antd Progress.',
Tag: 'Use @signozhq/ui/badge Bagde instead of antd Tag.',
};
export default {

View File

@@ -51,6 +51,13 @@
background: var(--l1-background);
}
.progress-container {
.ant-progress-bg {
height: 8px !important;
border-radius: 4px;
}
}
.ant-table-tbody > tr:hover > td {
background: color-mix(in srgb, var(--l1-foreground) 4%, transparent);
}

View File

@@ -9,13 +9,13 @@ import {
Flex,
Input,
InputRef,
Progress,
Space,
Spin,
TableColumnsType,
TableColumnType,
Tooltip,
} from 'antd';
import { Progress } from '@signozhq/ui/progress';
import { Typography } from '@signozhq/ui/typography';
import type { FilterDropdownProps } from 'antd/lib/table/interface';
import logEvent from 'api/common/logEvent';
@@ -59,7 +59,7 @@ function ProgressRender(item: string | number): JSX.Element {
<Progress
percent={percent}
strokeLinecap="butt"
showInfo
size="small"
strokeColor={((): string => {
const cpuPercent = percent;
if (cpuPercent >= 90) {

View File

@@ -9,7 +9,7 @@ import {
useState,
} from 'react';
import { Color } from '@signozhq/design-tokens';
import { Select, Tooltip } from 'antd';
import { Select, Tag, Tooltip } from 'antd';
import {
OPERATORS,
QUERY_BUILDER_OPERATORS_BY_TYPES,
@@ -37,7 +37,7 @@ import { validationMapper } from 'hooks/queryBuilder/useIsValidTag';
import { operatorTypeMapper } from 'hooks/queryBuilder/useOperatorType';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { isArray, isEmpty, isEqual, isObject } from 'lodash-es';
import { ChevronDown, ChevronUp, X } from '@signozhq/icons';
import { ChevronDown, ChevronUp } from '@signozhq/icons';
import type { BaseSelectRef } from 'rc-select';
import {
BaseAutocompleteData,
@@ -51,7 +51,6 @@ import { popupContainer } from 'utils/selectPopupContainer';
import { v4 as uuid } from 'uuid';
import './ClientSideQBSearch.styles.scss';
import { Badge } from '@signozhq/ui/badge';
export interface AttributeKey {
key: string;
@@ -548,7 +547,11 @@ function ClientSideQBSearch(
return (
<span className="qb-search-bar-tokenised-tags">
<Badge color="vanilla" className={tagDetails?.key?.type || ''}>
<Tag
closable={!searchValue && closable}
onClose={onCloseHandler}
className={tagDetails?.key?.type || ''}
>
<Tooltip title={chipValue}>
<TypographyText
$isInNin={isInNin}
@@ -563,15 +566,7 @@ function ClientSideQBSearch(
{chipValue}
</TypographyText>
</Tooltip>
{!searchValue && closable && (
<X
size={12}
className="close-icon"
style={{ cursor: 'pointer' }}
onClick={onCloseHandler}
/>
)}
</Badge>
</Tag>
</span>
);
};

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { Color } from '@signozhq/design-tokens';
import { Button, Modal } from 'antd';
import { Button, Modal, Tag } from 'antd';
import { CircleAlert, X } from '@signozhq/icons';
import KeyValueLabel from 'periscope/components/KeyValueLabel';
import { useAppContext } from 'providers/App/App';
@@ -9,7 +9,6 @@ import APIError from 'types/api/error';
import ErrorContent from './components/ErrorContent';
import './ErrorModal.styles.scss';
import { Badge } from '@signozhq/ui/badge';
type Props = {
error: APIError;
@@ -46,17 +45,14 @@ function ErrorModal({
return (
<>
{!triggerComponent ? (
<span
<Tag
className="error-modal__trigger"
role="button"
tabIndex={0}
icon={<CircleAlert size={14} color={Color.BG_CHERRY_500} />}
color="error"
onClick={(): void => setVisible(true)}
onKeyDown={undefined}
>
<Badge color="error">
<CircleAlert size={14} color={Color.BG_CHERRY_500} /> error
</Badge>
</span>
error
</Tag>
) : (
React.cloneElement(triggerComponent, {
onClick: () => setVisible(true),

View File

@@ -14,8 +14,7 @@ import { Color } from '@signozhq/design-tokens';
import { copilot } from '@uiw/codemirror-theme-copilot';
import { githubLight } from '@uiw/codemirror-theme-github';
import CodeMirror, { EditorView, keymap, Prec } from '@uiw/react-codemirror';
import { Button, Card, Collapse, Popover, Tooltip } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Button, Card, Collapse, Popover, Tag, Tooltip } from 'antd';
import { getKeySuggestions } from 'api/querySuggestions/getKeySuggestions';
import { getValueSuggestions } from 'api/querySuggestions/getValueSuggestion';
import cx from 'classnames';
@@ -665,26 +664,26 @@ function QuerySearch({
// Helper function to render a badge for the current context mode
const renderContextBadge = (): JSX.Element => {
if (!editingMode) {
return <Badge color="vanilla">Unknown</Badge>;
return <Tag>Unknown</Tag>;
}
switch (editingMode) {
case 'key':
return <Badge color="robin">Key</Badge>;
return <Tag color="blue">Key</Tag>;
case 'operator':
return <Badge color="sakura">Operator</Badge>;
return <Tag color="purple">Operator</Tag>;
case 'value':
return <Badge color="forest">Value</Badge>;
return <Tag color="green">Value</Tag>;
case 'conjunction':
return <Badge color="amber">Conjunction</Badge>;
return <Tag color="orange">Conjunction</Tag>;
case 'function':
return <Badge color="aqua">Function</Badge>;
return <Tag color="cyan">Function</Tag>;
case 'parenthesis':
return <Badge color="sakura">Parenthesis</Badge>;
return <Tag color="magenta">Parenthesis</Tag>;
case 'bracketList':
return <Badge color="cherry">Bracket List</Badge>;
return <Tag color="red">Bracket List</Tag>;
default:
return <Badge color="vanilla">Unknown</Badge>;
return <Tag>Unknown</Tag>;
}
};
@@ -1305,37 +1304,34 @@ function QuerySearch({
Currently editing: {renderContextBadge()}
{queryContext?.keyToken && (
<span className="triplet-info">
Key: <Badge color="vanilla">{queryContext.keyToken}</Badge>
Key: <Tag>{queryContext.keyToken}</Tag>
</span>
)}
{queryContext?.operatorToken && (
<span className="triplet-info">
Operator: <Badge color="vanilla">{queryContext.operatorToken}</Badge>
Operator: <Tag>{queryContext.operatorToken}</Tag>
</span>
)}
{queryContext?.valueToken && (
<span className="triplet-info">
Value: <Badge color="vanilla">{queryContext.valueToken}</Badge>
Value: <Tag>{queryContext.valueToken}</Tag>
</span>
)}
{queryContext?.currentPair && (
<span className="triplet-info query-pair-info">
Current pair: <Badge color="robin">{queryContext.currentPair.key}</Badge>
<Badge color="sakura">{queryContext.currentPair.operator}</Badge>
Current pair: <Tag color="blue">{queryContext.currentPair.key}</Tag>
<Tag color="purple">{queryContext.currentPair.operator}</Tag>
{queryContext.currentPair.value && (
<Badge color="forest">{queryContext.currentPair.value}</Badge>
<Tag color="green">{queryContext.currentPair.value}</Tag>
)}
<Badge
color={queryContext.currentPair.isComplete ? 'success' : 'warning'}
>
<Tag color={queryContext.currentPair.isComplete ? 'success' : 'warning'}>
{queryContext.currentPair.isComplete ? 'Complete' : 'Incomplete'}
</Badge>
</Tag>
</span>
)}
{queryContext?.queryPairs && queryContext.queryPairs.length > 0 && (
<span className="triplet-info">
Total pairs:{' '}
<Badge color="robin">{queryContext.queryPairs.length}</Badge>
Total pairs: <Tag color="blue">{queryContext.queryPairs.length}</Tag>
</span>
)}
</div>

View File

@@ -1,5 +1,4 @@
import { Popover } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Popover, Tag } from 'antd';
import { LabelColumnProps } from './TableRenderer.types';
import TagWithToolTip from './TagWithToolTip';
@@ -7,7 +6,7 @@ import { getLabelAndValueContent } from './utils';
import './LabelColumn.styles.scss';
function LabelColumn({ labels, value }: LabelColumnProps): JSX.Element {
function LabelColumn({ labels, value, color }: LabelColumnProps): JSX.Element {
const newLabels = labels.length > 3 ? labels.slice(0, 3) : labels;
const remainingLabels = labels.length > 3 ? labels.slice(3) : [];
@@ -15,7 +14,7 @@ function LabelColumn({ labels, value }: LabelColumnProps): JSX.Element {
<div className="label-column">
{newLabels.map(
(label: string): JSX.Element => (
<TagWithToolTip key={label} label={label} value={value} />
<TagWithToolTip key={label} label={label} color={color} value={value} />
),
)}
{remainingLabels.length > 0 && (
@@ -27,9 +26,9 @@ function LabelColumn({ labels, value }: LabelColumnProps): JSX.Element {
{labels.map(
(label: string): JSX.Element => (
<div key={label}>
<Badge className="label-column--tag" color="vanilla">
<Tag className="label-column--tag" color={color}>
{getLabelAndValueContent(label, value && value[label])}
</Badge>
</Tag>
</div>
),
)}
@@ -37,9 +36,9 @@ function LabelColumn({ labels, value }: LabelColumnProps): JSX.Element {
}
trigger="hover"
>
<Badge className="label-column--tag" color="vanilla">
<Tag className="label-column--tag" color={color}>
+{remainingLabels.length}
</Badge>
</Tag>
</Popover>
)}
</div>

View File

@@ -1,17 +1,20 @@
import { Tooltip } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Tag, Tooltip } from 'antd';
import { getLabelRenderingValue } from './utils';
function TagWithToolTip({ label, value }: TagWithToolTipProps): JSX.Element {
function TagWithToolTip({
label,
value,
color,
}: TagWithToolTipProps): JSX.Element {
const tooltipTitle =
value && value[label] ? `${label}: ${value[label]}` : label;
return (
<div key={label}>
<Tooltip title={tooltipTitle}>
<Badge className="label-column--tag" color="vanilla">
<Tag className="label-column--tag" color={color}>
{getLabelRenderingValue(label, value && value[label])}
</Badge>
</Tag>
</Tooltip>
</div>
);
@@ -19,6 +22,7 @@ function TagWithToolTip({ label, value }: TagWithToolTipProps): JSX.Element {
type TagWithToolTipProps = {
label: string;
color?: string;
value?: {
[key: string]: string;
};
@@ -26,6 +30,7 @@ type TagWithToolTipProps = {
TagWithToolTip.defaultProps = {
value: undefined,
color: undefined,
};
export default TagWithToolTip;

View File

@@ -14,6 +14,11 @@
.ant-form-item {
margin-bottom: 0;
}
.ant-tag {
margin-right: 0;
background: var(--card);
}
}
.add-tag-container {

View File

@@ -1,7 +1,6 @@
import React, { Dispatch, SetStateAction, useState } from 'react';
import { Check, Plus, X } from '@signozhq/icons';
import { Button, Flex } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Button, Flex, Tag } from 'antd';
import Input from 'components/Input';
import './Tags.styles.scss';
@@ -47,14 +46,14 @@ function Tags({ tags, setTags }: AddTagsProps): JSX.Element {
return (
<div className="tags-container">
{tags.map<React.ReactNode>((tag) => (
<Badge key={tag} color="vanilla" style={{ userSelect: 'none' }}>
<Tag
key={tag}
closable
style={{ userSelect: 'none' }}
onClose={(): void => handleClose(tag)}
>
<span>{tag}</span>
<X
size={12}
style={{ cursor: 'pointer', marginInlineStart: 4 }}
onClick={(): void => handleClose(tag)}
/>
</Badge>
</Tag>
))}
{inputVisible && (

View File

@@ -45,10 +45,6 @@
.contributors-row {
height: 80px;
}
.top-contributors-progress {
--progress-background: transparent;
}
&__content {
.ant-table {
&-cell {

View File

@@ -1,7 +1,6 @@
import { HTMLAttributes } from 'react';
import { Color } from '@signozhq/design-tokens';
import { Table, TableColumnsType as ColumnsType } from 'antd';
import { Progress } from '@signozhq/ui/progress';
import { Progress, Table, TableColumnsType as ColumnsType } from 'antd';
import logEvent from 'api/common/logEvent';
import { ConditionalAlertPopover } from 'container/AlertHistory/AlertPopover/AlertPopover';
import AlertLabels from 'pages/AlertDetails/AlertHeader/AlertLabels/AlertLabels';
@@ -52,8 +51,8 @@ function TopContributorsRows({
<Progress
percent={(count / totalCurrentTriggers) * 100}
showInfo={false}
trailColor="rgba(255, 255, 255, 0)"
strokeColor={Color.BG_ROBIN_500}
className="top-contributors-progress"
/>
</ConditionalAlertPopover>
),

View File

@@ -141,9 +141,12 @@
.progress-container {
width: 158px;
.ant-progress {
margin: 0;
span {
font-weight: 600;
.ant-progress-text {
font-weight: 600;
}
}
}

View File

@@ -1,8 +1,7 @@
import { useMemo } from 'react';
import { useQueries } from 'react-query';
import { Color } from '@signozhq/design-tokens';
import { Skeleton, Tooltip } from 'antd';
import { Progress } from '@signozhq/ui/progress';
import { Progress, Skeleton, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { ENTITY_VERSION_V5 } from 'constants/app';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
@@ -137,11 +136,12 @@ function DomainMetrics({
<Tooltip title={formattedDomainMetricsData.errorRate}>
{formattedDomainMetricsData.errorRate !== '-' ? (
<Progress
status="active"
percent={Number(
Number(formattedDomainMetricsData.errorRate).toFixed(2),
)}
strokeLinecap="butt"
showInfo
size="small"
strokeColor={((): string => {
const errorRatePercent = Number(
Number(formattedDomainMetricsData.errorRate).toFixed(2),

View File

@@ -1,8 +1,7 @@
import { useMemo } from 'react';
import { UseQueryResult } from 'react-query';
import { Color } from '@signozhq/design-tokens';
import { Skeleton, Tooltip } from 'antd';
import { Progress } from '@signozhq/ui/progress';
import { Progress, Skeleton, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import {
getDisplayValue,
@@ -81,9 +80,10 @@ function EndPointMetrics({
<Tooltip title={metricsData?.errorRate}>
{metricsData?.errorRate !== '-' ? (
<Progress
status="active"
percent={Number(Number(metricsData?.errorRate ?? 0).toFixed(2))}
strokeLinecap="butt"
showInfo
size="small"
strokeColor={((): string => {
const errorRatePercent = Number(
Number(metricsData?.errorRate ?? 0).toFixed(2),

View File

@@ -1,8 +1,6 @@
import { ReactNode } from 'react';
import { Color } from '@signozhq/design-tokens';
import { TableColumnType as ColumnType, Tooltip } from 'antd';
import { Progress } from '@signozhq/ui/progress';
import { Badge } from '@signozhq/ui/badge';
import { Progress, TableColumnType as ColumnType, Tag, Tooltip } from 'antd';
import { convertFiltersToExpressionWithExistingQuery } from 'components/QueryBuilderV2/utils';
import {
FiltersType,
@@ -259,9 +257,10 @@ export const columnsConfig: ColumnType<APIDomainsRowData>[] = [
errorRate === 'n/a' || errorRate === '-' ? 0 : errorRate;
return (
<Progress
status="active"
percent={Number((errorRateValue as number).toFixed(2))}
strokeLinecap="butt"
showInfo
size="small"
strokeColor={((): string => {
const errorRatePercent = Number((errorRateValue as number).toFixed(2));
if (errorRatePercent >= 90) {
@@ -973,9 +972,13 @@ export const getEndPointsColumnsConfig = (
})()}
{isGroupedByAttribute
? text.split(',').map((value) => (
<Badge key={value} color="vanilla" className="endpoint-group-tag-item">
<Tag
key={value}
color={Color.BG_SLATE_100}
className="endpoint-group-tag-item"
>
{value === '' ? '<no-value>' : value}
</Badge>
</Tag>
))
: endPointName}
</div>
@@ -1019,13 +1022,14 @@ export const getEndPointsColumnsConfig = (
className: `column`,
render: (errorRate: number | string): React.ReactNode => (
<Progress
status="active"
percent={Number(
(
(errorRate === 'n/a' || errorRate === '-' ? 0 : errorRate) as number
).toFixed(1),
)}
strokeLinecap="butt"
showInfo
size="small"
strokeColor={((): string => {
const errorRatePercent = Number((errorRate as number).toFixed(1));
if (errorRatePercent >= 90) {
@@ -2510,9 +2514,10 @@ export const dependentServicesColumns: ColumnType<DependentServicesData>[] = [
render: (errorPercentage: number | string): React.ReactNode =>
errorPercentage !== '-' ? (
<Progress
status="active"
percent={Number((errorPercentage as number).toFixed(2))}
strokeLinecap="butt"
showInfo
size="small"
strokeColor={((): string => {
const errorPercentagePercent = Number(
(errorPercentage as number).toFixed(2),
@@ -3017,13 +3022,14 @@ export const getAllEndpointsWidgetData = (
),
F1: (errorRate: any): ReactNode => (
<Progress
status="active"
percent={Number(
(
(errorRate === 'n/a' || errorRate === '-' ? 0 : errorRate) as number
).toFixed(2),
)}
strokeLinecap="butt"
showInfo
size="small"
strokeColor={((): string => {
const errorRatePercent = Number(
(

View File

@@ -14,8 +14,8 @@ import {
Skeleton,
Table,
TableColumnsType as ColumnsType,
Tag,
} from 'antd';
import { Badge } from '@signozhq/ui/badge';
import getUsage, { UsageResponsePayloadProps } from 'api/billing/getUsage';
import logEvent from 'api/common/logEvent';
import updateCreditCardApi from 'api/v1/checkout/create';
@@ -434,7 +434,7 @@ export default function BillingContainer(): JSX.Element {
<Flex vertical>
<Typography.Title level={5} style={{ marginTop: 2, fontWeight: 500 }}>
{isCloudUserVal ? t('teams_cloud') : t('teams')}{' '}
{isFreeTrial ? <Badge color="success"> Free Trial </Badge> : ''}
{isFreeTrial ? <Tag color="success"> Free Trial </Tag> : ''}
</Typography.Title>
{!isLoading && !isFetchingBillingData && !showGracePeriodMessage ? (

View File

@@ -1,7 +1,6 @@
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { Row } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Row, Tag } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import logEvent from 'api/common/logEvent';
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
@@ -67,7 +66,13 @@ function SelectAlertType({ onSelect }: SelectAlertTypeProps): JSX.Element {
<AlertTypeCard
key={option.selection}
title={option.title}
extra={option.isBeta ? <Badge color="robin">Beta</Badge> : undefined}
extra={
option.isBeta ? (
<Tag bordered={false} color="geekblue">
Beta
</Tag>
) : undefined
}
onClick={(e): void => {
onSelect(option.selection, isModifierKeyPressed(e));
}}

View File

@@ -16,8 +16,7 @@ import {
Plus,
X,
} from '@signozhq/icons';
import { Button, Card, Input, Modal, Popover, Tooltip } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Button, Card, Input, Modal, Popover, Tag, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import logEvent from 'api/common/logEvent';
import ConfigureIcon from 'assets/Integrations/ConfigureIcon';
@@ -507,9 +506,9 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
{(tags?.length || 0) > 0 && (
<div className="dashboard-tags">
{tags?.map((tag) => (
<Badge key={tag} className="tag" color="vanilla">
<Tag key={tag} className="tag">
{tag}
</Badge>
</Tag>
))}
</div>
)}

View File

@@ -359,7 +359,7 @@
flex-flow: wrap;
gap: 8px;
[data-slot='badge'] {
.ant-tag {
height: 30px;
color: var(--l1-foreground);
font-family: 'Space Mono';

View File

@@ -5,8 +5,7 @@ import { useQuery } from 'react-query';
import { useSelector } from 'react-redux';
import { orange } from '@ant-design/colors';
import { Color } from '@signozhq/design-tokens';
import { Button, Collapse, Input, Select } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Button, Collapse, Input, Select, Tag } from 'antd';
import { Switch } from '@signozhq/ui/switch';
import { Typography } from '@signozhq/ui/typography';
import dashboardVariablesQuery from 'api/dashboard/variables/dashboardVariablesQuery';
@@ -543,9 +542,9 @@ function VariableItem({
}}
>
Dynamic
<Badge color="robin" className="sidenav-beta-tag">
<Tag bordered={false} className="sidenav-beta-tag" color="geekblue">
Beta
</Badge>
</Tag>
</Button>
<Button
type="text"
@@ -600,9 +599,9 @@ function VariableItem({
}}
>
Query
<Badge color="amber" className="sidenav-beta-tag">
<Tag bordered={false} className="sidenav-beta-tag" color="warning">
Not Recommended
</Badge>
</Tag>
<div onClick={(e): void => e.stopPropagation()}>
<TextToolTip
text="Learn why we don't recommend"
@@ -734,9 +733,7 @@ function VariableItem({
<Typography style={{ color: orange[5] }}>{errorPreview}</Typography>
) : (
map(previewValues, (value, idx) => (
<Badge key={`${value}${idx}`} color="vanilla">
{value.toString()}
</Badge>
<Tag key={`${value}${idx}`}>{value.toString()}</Tag>
))
)}
</div>

View File

@@ -1,5 +1,4 @@
import { Dispatch, SetStateAction, useState } from 'react';
import { X } from '@signozhq/icons';
import { Col, Tooltip } from 'antd';
import Input from 'components/Input';
@@ -61,7 +60,12 @@ function AddTags({ tags, setTags }: AddTagsProps): JSX.Element {
const isLongTag = tag.length > 20;
const tagElem = (
<NewTagContainer key={tag} color="vanilla" className="tag-container">
<NewTagContainer
closable
key={tag}
onClose={(): void => handleClose(tag)}
className="tag-container"
>
<span
onDoubleClick={(e): void => {
setEditInputIndex(index);
@@ -71,11 +75,6 @@ function AddTags({ tags, setTags }: AddTagsProps): JSX.Element {
>
{isLongTag ? `${tag.slice(0, 20)}...` : tag}
</span>
<X
size={12}
style={{ cursor: 'pointer', marginInlineStart: 4 }}
onClick={(): void => handleClose(tag)}
/>
</NewTagContainer>
);

View File

@@ -1,5 +1,4 @@
import { Col } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Col, Tag } from 'antd';
import styled from 'styled-components';
export const TagsContainer = styled.div`
@@ -9,7 +8,7 @@ export const TagsContainer = styled.div`
gap: 6px;
`;
export const NewTagContainer = styled(Badge)`
export const NewTagContainer = styled(Tag)`
&&& {
display: flex;
justify-content: space-between;

View File

@@ -1,4 +1,3 @@
import { X } from '@signozhq/icons';
import { QueryChipContainer, QueryChipItem } from './styles';
import { ILabelRecord } from './types';
@@ -14,15 +13,11 @@ export default function QueryChip({
const { key, value } = queryData;
return (
<QueryChipContainer>
<QueryChipItem color="vanilla">
<QueryChipItem
closable={key !== 'severity' && key !== 'description'}
onClose={(): void => onRemove(key)}
>
{key}: {value}
{key !== 'severity' && key !== 'description' && (
<X
size={12}
style={{ cursor: 'pointer', marginInlineStart: 4 }}
onClick={(): void => onRemove(key)}
/>
)}
</QueryChipItem>
</QueryChipContainer>
);

View File

@@ -1,5 +1,5 @@
import { grey } from '@ant-design/colors';
import { Badge } from '@signozhq/ui/badge';
import { Tag } from 'antd';
import styled from 'styled-components';
interface SearchContainerProps {
@@ -29,6 +29,6 @@ export const QueryChipContainer = styled.span`
}
`;
export const QueryChipItem = styled(Badge)`
export const QueryChipItem = styled(Tag)`
margin-right: 0.1rem;
`;

View File

@@ -1,7 +1,6 @@
import { useEffect, useState } from 'react';
import { Link, useLocation } from 'react-router-dom';
import { Button, Skeleton } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Button, Skeleton, Tag } from 'antd';
import logEvent from 'api/common/logEvent';
import { useListRules } from 'api/generated/services/rules';
import type { RuletypesRuleDTO } from 'api/generated/services/sigNoz.schemas';
@@ -178,14 +177,12 @@ export default function AlertRules({
</div>
<div className="alert-rule-item-description home-data-item-tag">
<Badge color="sienna" variant="outline">
{rule?.labels?.severity}
</Badge>
<Tag color={rule?.labels?.severity}>{rule?.labels?.severity}</Tag>
{rule.state === 'firing' && (
<Badge color="cherry" variant="outline" className="firing-tag">
<Tag color="red" className="firing-tag">
{rule.state}
</Badge>
</Tag>
)}
</div>
</div>

View File

@@ -1,7 +1,6 @@
import { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { Button, Skeleton } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Button, Skeleton, Tag } from 'antd';
import logEvent from 'api/common/logEvent';
import ROUTES from 'constants/routes';
import { useGetAllDashboard } from 'hooks/dashboard/useGetAllDashboard';
@@ -149,9 +148,9 @@ export default function Dashboards({
<div className="alert-rule-item-description home-data-item-tag">
{dashboard.data.tags?.map((tag) => (
<Badge color="sienna" variant="outline" key={tag}>
<Tag color={tag} key={tag}>
{tag}
</Badge>
</Tag>
))}
</div>
</div>

View File

@@ -574,7 +574,30 @@
.home-data-item-tag {
display: flex;
gap: 6px;
.ant-tag {
display: flex;
padding: 2px 12px;
justify-content: center;
align-items: center;
gap: 4px;
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: 12px;
font-style: normal;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
.firing-tag {
color: var(--bg-sakura-500);
background: color-mix(in srgb, var(--danger-background) 10%, transparent);
}
}
&.services-list-container {

View File

@@ -1,7 +1,6 @@
import { useEffect, useMemo, useState } from 'react';
import { Link } from 'react-router-dom';
import { Button, Skeleton } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Button, Skeleton, Tag } from 'antd';
import logEvent from 'api/common/logEvent';
import { getViewDetailsUsingViewKey } from 'components/ExplorerCard/utils';
import ROUTES from 'constants/routes';
@@ -250,9 +249,9 @@ export default function SavedViews({
}
return (
<Badge color="sienna" key={tag}>
<Tag color={tag} key={tag}>
{tag}
</Badge>
</Tag>
);
})}
</div>

View File

@@ -39,5 +39,7 @@
width: 100% !important;
--progress-width: 100%;
.ant-progress-steps-outer {
width: 100% !important;
}
}

View File

@@ -1,4 +1,4 @@
import { Progress } from '@signozhq/ui/progress';
import { Progress } from 'antd';
import { ChecklistItem } from '../HomeChecklist/HomeChecklist';
@@ -15,7 +15,9 @@ function StepsProgress({
const totalChecklistItems = checklistItems.length;
const progress = (completedChecklistItems.length / totalChecklistItems) * 100;
const progress = Math.round(
(completedChecklistItems.length / totalChecklistItems) * 100,
);
return (
<div className="steps-progress-container">

View File

@@ -1,7 +1,6 @@
import React from 'react';
import { Color } from '@signozhq/design-tokens';
import { Badge } from '@signozhq/ui/badge';
import { Progress } from '@signozhq/ui/progress';
import { Progress, Tag } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import {
getHostLists,
@@ -52,14 +51,14 @@ export const hostDetailsMetadataConfig: K8sDetailsMetadataConfig<HostData>[] = [
label: 'STATUS',
getValue: (h): string => (h.active ? 'ACTIVE' : 'INACTIVE'),
render: (value, h): React.ReactNode => (
<Badge
variant="outline"
<Tag
className={`${infraHostsStyles.infraMonitoringTags} ${
h.active ? infraHostsStyles.tagsActive : infraHostsStyles.tagsInactive
}`}
bordered
>
{value}
</Badge>
</Tag>
),
},
{
@@ -67,9 +66,9 @@ export const hostDetailsMetadataConfig: K8sDetailsMetadataConfig<HostData>[] = [
getValue: (h): string => h.os || '-',
render: (value): React.ReactNode =>
value !== '-' ? (
<Badge variant="outline" className={infraHostsStyles.infraMonitoringTags}>
<Tag className={infraHostsStyles.infraMonitoringTags} bordered>
{value}
</Badge>
</Tag>
) : (
<Typography.Text>-</Typography.Text>
),
@@ -80,8 +79,8 @@ export const hostDetailsMetadataConfig: K8sDetailsMetadataConfig<HostData>[] = [
render: (value): React.ReactNode => (
<Progress
percent={Number(Number(value).toFixed(1))}
size="small"
strokeColor={getProgressColor(Number(value))}
showInfo
/>
),
},
@@ -91,8 +90,8 @@ export const hostDetailsMetadataConfig: K8sDetailsMetadataConfig<HostData>[] = [
render: (value): React.ReactNode => (
<Progress
percent={Number(Number(value).toFixed(1))}
size="small"
strokeColor={getMemoryProgressColor(Number(value))}
showInfo
/>
),
},

View File

@@ -1,6 +1,5 @@
import React from 'react';
import { Tooltip } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Tag, Tooltip } from 'antd';
import { HostData } from 'api/infraMonitoring/getHostLists';
import TanStackTable, { TableColumnDef } from 'components/TanStackTableView';
import { getGroupByEl } from 'container/InfraMonitoringK8s/Base/utils';
@@ -93,13 +92,14 @@ export const hostColumnsConfig: TableColumnDef<HostData>[] = [
cell: ({ value }): React.ReactNode => {
const active = value as boolean;
return (
<Badge
<Tag
bordered
className={`${styles.statusTag} ${
active ? styles.statusTagActive : styles.statusTagInactive
}`}
>
{active ? 'ACTIVE' : 'INACTIVE'}
</Badge>
</Tag>
);
},
},

View File

@@ -60,6 +60,11 @@
& > div {
width: 100%;
}
:global(.ant-progress-bg) {
height: 8px !important;
border-radius: 4px;
}
}
.progressBar {

View File

@@ -14,7 +14,7 @@
font-size: 12px;
}
:global([data-slot='badge'] .ant-typography) {
:global(.ant-tag .ant-typography) {
font-size: 12px;
}
}

View File

@@ -19,7 +19,7 @@
font-size: 12px;
}
:global([data-slot='badge'] .ant-typography) {
:global(.ant-tag .ant-typography) {
font-size: 12px;
}
}

View File

@@ -19,7 +19,7 @@
font-size: 12px;
}
:global([data-slot='badge'] .ant-typography) {
:global(.ant-tag .ant-typography) {
font-size: 12px;
}
}

View File

@@ -19,7 +19,7 @@
font-size: 12px;
}
:global([data-slot='badge'] .ant-typography) {
:global(.ant-tag .ant-typography) {
font-size: 12px;
}
}

View File

@@ -1,5 +1,4 @@
import { TableColumnsType as ColumnsType } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { TableColumnsType as ColumnsType, Tag } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import { getMs } from 'container/Trace/Filters/Panel/PanelBody/Duration/util';
@@ -94,9 +93,9 @@ export const getTraceListColumns = (
if (primaryKey === 'httpMethod' || primaryKey === 'responseStatusCode') {
return (
<BlockLink to={getTraceLink(itemData)} openInNewTab>
<Badge data-testid={key} color="sakura">
<Tag data-testid={key} color="magenta">
{getValueForKey(itemData, key)}
</Badge>
</Tag>
</BlockLink>
);
}

View File

@@ -103,8 +103,12 @@
.progress-container {
width: 158px;
span {
font-weight: 600;
.ant-progress {
margin: 0;
.ant-progress-text {
font-weight: 600;
}
}
}
@@ -213,7 +217,7 @@
font-size: 12px;
}
[data-slot='badge'] .ant-typography {
.ant-tag .ant-typography {
font-size: 12px;
}
}
@@ -288,6 +292,10 @@
}
.progress-container {
.ant-progress-bg {
height: 8px !important;
border-radius: 4px;
}
}
.ant-table-tbody > tr:hover > td {
@@ -349,7 +357,7 @@
font-size: 12px;
}
[data-slot='badge'] .ant-typography {
.ant-tag .ant-typography {
font-size: 12px;
}
}

View File

@@ -1,4 +1,4 @@
import { Progress } from '@signozhq/ui/progress';
import { Progress } from 'antd';
import TanStackTable from 'components/TanStackTableView';
import {
getMemoryProgressColor,
@@ -53,6 +53,7 @@ export function EntityProgressBar({
<Progress
percent={percentage}
strokeLinecap="butt"
size="small"
status="normal"
strokeColor={getStrokeColor(type, value)}
className={styles.progressBar}

View File

@@ -18,6 +18,7 @@ import {
Table,
TablePaginationConfig,
TableProps as AntDTableProps,
Tag,
Tooltip,
} from 'antd';
import { Switch } from '@signozhq/ui/switch';
@@ -1054,10 +1055,7 @@ function MultiIngestionSettings(): JSX.Element {
<div className="ingestion-key-tags">
{APIKey.tags.map((tag, index) => (
// eslint-disable-next-line react/no-array-index-key
<Badge key={`${tag}-${index}`} color="vanilla">
{' '}
{tag}{' '}
</Badge>
<Tag key={`${tag}-${index}`}> {tag} </Tag>
))}
</div>
</div>

View File

@@ -310,7 +310,9 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
return <Typography>-</Typography>;
}
return <LabelColumn labels={withOutSeverityKeys} value={value} />;
return (
<LabelColumn labels={withOutSeverityKeys} value={value} color="magenta" />
);
},
},
];

View File

@@ -1,46 +1,26 @@
import { Badge } from '@signozhq/ui/badge';
import { Tag } from 'antd';
import type { RuletypesRuleDTO } from 'api/generated/services/sigNoz.schemas';
function Status({ status }: StatusProps): JSX.Element {
switch (status) {
case 'inactive': {
return (
<Badge color="forest" variant="outline">
OK
</Badge>
);
return <Tag color="green">OK</Tag>;
}
case 'pending': {
return (
<Badge color="amber" variant="outline">
Pending
</Badge>
);
return <Tag color="orange">Pending</Tag>;
}
case 'firing': {
return (
<Badge color="cherry" variant="outline">
Firing
</Badge>
);
return <Tag color="red">Firing</Tag>;
}
case 'disabled': {
return (
<Badge color="vanilla" variant="outline">
Disabled
</Badge>
);
return <Tag>Disabled</Tag>;
}
default: {
return (
<Badge color="vanilla" variant="outline">
Unknown
</Badge>
);
return <Tag color="default">Unknown</Tag>;
}
}
}

View File

@@ -22,9 +22,9 @@ import {
Popover,
Skeleton,
Table,
Tag,
Tooltip,
} from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Switch } from '@signozhq/ui/switch';
import { Typography } from '@signozhq/ui/typography';
import type { TableProps } from 'antd/lib';
@@ -420,15 +420,15 @@ function DashboardsList(): JSX.Element {
{dashboard?.tags && dashboard.tags.length > 0 && (
<div className="dashboard-tags">
{dashboard.tags.slice(0, 3).map((tag) => (
<Badge className="tag" color="vanilla" key={tag}>
<Tag className="tag" key={tag}>
{tag}
</Badge>
</Tag>
))}
{dashboard.tags.length > 3 && (
<Badge className="tag" color="vanilla" key={dashboard.tags[3]}>
<Tag className="tag" key={dashboard.tags[3]}>
+ <span> {dashboard.tags.length - 3} </span>
</Badge>
</Tag>
)}
</div>
)}

View File

@@ -1,7 +1,7 @@
import { Badge } from '@signozhq/ui/badge';
import { Tag } from 'antd';
import styled from 'styled-components';
export const TagContainer = styled(Badge)`
export const TagContainer = styled(Tag)`
&&& {
border-color: var(--bg-slate-400);
border-radius: 0.25rem;

View File

@@ -84,7 +84,7 @@
padding-top: 12px;
}
[data-slot='badge'] {
.ant-tag-borderless {
border-radius: 2px;
background: color-mix(in srgb, var(--bg-robin-400) 8%, transparent);
}

View File

@@ -3,8 +3,7 @@ import MEditor, { EditorProps, Monaco } from '@monaco-editor/react';
import { Color } from '@signozhq/design-tokens';
import { Button } from '@signozhq/ui/button';
import { Switch } from '@signozhq/ui/switch';
import { Collapse, Divider, Input } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Collapse, Divider, Input, Tag } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { AddToQueryHOCProps } from 'components/Logs/AddToQueryHOC';
import { ChangeViewFunctionType } from 'container/ExplorerOptions/types';
@@ -105,11 +104,11 @@ function Overview({
{
key: '1',
label: (
<Badge color="vanilla">
<Tag bordered={false}>
<Typography.Text style={{ color: Color.BG_ROBIN_400 }}>
body
</Typography.Text>
</Badge>
</Tag>
),
children: (
<div className="logs-body-content">
@@ -143,7 +142,7 @@ function Overview({
</div>
</div>
),
// extra: <Badge className="tag" color="vanilla">JSON</Badge>,
// extra: <Tag className="tag">JSON</Tag>,
className: 'collapse-content',
},
]}
@@ -164,11 +163,11 @@ function Overview({
className="attribute-tab-header"
onClick={toogleAttributePanelOpenState}
>
<Badge color="vanilla">
<Tag bordered={false}>
<Typography.Text style={{ color: Color.BG_ROBIN_400 }}>
Attributes
</Typography.Text>
</Badge>
</Tag>
{isAttributesExpanded && (
<Button

View File

@@ -11,7 +11,7 @@
font-size: 12px;
}
[data-slot='badge'] .ant-typography {
.ant-tag .ant-typography {
font-size: 12px;
}
}

View File

@@ -142,6 +142,13 @@
}
}
.progress-container {
.ant-progress-bg {
height: 8px !important;
border-radius: 4px;
}
}
.ant-table-tbody > tr:hover > td {
background: color-mix(in srgb, var(--l1-foreground) 4%, transparent);
}

View File

@@ -1,7 +1,6 @@
import { useEffect, useState } from 'react';
import { useMutation } from 'react-query';
import { Radio, RadioChangeEvent } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Radio, RadioChangeEvent, Tag } from 'antd';
import { Switch } from '@signozhq/ui/switch';
import setLocalStorageApi from 'api/browser/localstorage/set';
import logEvent from 'api/common/logEvent';
@@ -65,7 +64,9 @@ function MySettings(): JSX.Element {
label: (
<div className="theme-option">
<Sun size={12} data-testid="light-theme-icon" /> Light{' '}
<Badge color="robin">Beta</Badge>
<Tag bordered={false} color="geekblue">
Beta
</Tag>
</div>
),
value: 'light',

View File

@@ -1,4 +1,4 @@
import { Badge } from '@signozhq/ui/badge';
import { Tag as AntDTag } from 'antd';
import styled from 'styled-components';
export const Container = styled.div`
@@ -20,6 +20,6 @@ export const PanelContainer = styled.div`
overflow-y: auto;
`;
export const Tag = styled(Badge)`
export const Tag = styled(AntDTag)`
margin: 0;
`;

View File

@@ -87,7 +87,12 @@
.service-progress-indicator {
width: fit-content;
--progress-width: 30px;
margin-inline-end: 0px !important;
margin-bottom: 0px !important;
.ant-progress-inner {
width: 30px;
}
}
.percent-value {

View File

@@ -1,7 +1,6 @@
import { useEffect, useMemo, useState } from 'react';
import { useParams } from 'react-router-dom';
import { Skeleton, Tooltip } from 'antd';
import { Progress } from '@signozhq/ui/progress';
import { Progress, Skeleton, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { AxiosError } from 'axios';
import Spinner from 'components/Spinner';

View File

@@ -1,12 +1,12 @@
import { Badge } from '@signozhq/ui/badge';
import { Tag } from 'antd';
function Tags({ tags }: TagsProps): JSX.Element {
return (
<span>
{tags?.map((tag) => (
<Badge color="sakura" key={tag}>
<Tag color="magenta" key={tag}>
{tag}
</Badge>
</Tag>
))}
</span>
);

View File

@@ -1,8 +1,7 @@
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { CircleAlert, CircleX, X } from '@signozhq/icons';
import { Button, Input, InputRef, message, Modal, Tooltip } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { CircleAlert, CircleX } from '@signozhq/icons';
import { Button, Input, InputRef, message, Modal, Tag, Tooltip } from 'antd';
import { tagInputStyle } from '../PipelineListsView/config';
import { TagInputWrapper } from './styles';
@@ -91,7 +90,12 @@ function TagInput({
}
const isLongTag = tag.length > 20;
const tagElem = (
<Badge key={tag} color="vanilla" style={{ userSelect: 'none' }}>
<Tag
key={tag}
closable
style={{ userSelect: 'none' }}
onClose={handleClose(tag)}
>
<span
onDoubleClick={(e): void => {
setEditInputIndex(index);
@@ -101,12 +105,7 @@ function TagInput({
>
{isLongTag ? `${tag.slice(0, 20)}...` : tag}
</span>
<X
size={12}
style={{ cursor: 'pointer', marginInlineStart: 4 }}
onClick={handleClose(tag)}
/>
</Badge>
</Tag>
);
return isLongTag ? (
<Tooltip title={tag} key={tag}>

View File

@@ -4,20 +4,12 @@ exports[`PipelinePage container test should render Tags section 1`] = `
<DocumentFragment>
<span>
<span
class="_badge_1jqif_1"
data-capitalize="false"
data-color="sakura"
data-slot="badge"
data-variant="default"
class="ant-tag ant-tag-magenta css-dev-only-do-not-override-2i2tap"
>
server
</span>
<span
class="_badge_1jqif_1"
data-capitalize="false"
data-color="sakura"
data-slot="badge"
data-variant="default"
class="ant-tag ant-tag-magenta css-dev-only-do-not-override-2i2tap"
>
app
</span>

View File

@@ -53,6 +53,12 @@
margin-bottom: 8px;
overflow: auto;
max-height: 100px;
.ant-tag {
user-select: none;
height: 28px;
display: inline-flex;
align-items: center;
}
}
.ant-select {
@@ -89,8 +95,61 @@
}
.alert-rule-tags {
.ant-tag {
display: flex;
align-items: center;
border: 1px solid color-mix(in srgb, var(--bg-robin-400) 20%, transparent);
background: color-mix(in srgb, var(--bg-robin-400) 10%, transparent);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
padding-right: 0px;
border-right: 0;
color: var(--bg-robin-400);
.ant-tag-close-icon {
height: 28px;
width: 20px !important;
justify-content: center;
border-left: 1px solid
color-mix(in srgb, var(--bg-robin-400) 20%, transparent);
border-radius: 0px 2px 2px 0px;
background: color-mix(in srgb, var(--bg-robin-400) 30%, transparent);
border-right: 1px solid
color-mix(in srgb, var(--bg-robin-400) 20%, transparent);
margin-right: 0px;
svg {
fill: var(--primary-background);
}
}
}
.non-closable-tag {
padding-right: 7px;
border-right: 1px solid
color-mix(in srgb, var(--bg-robin-400) 20%, transparent);
}
.red-tag.non-closable-tag {
border-right: 1px solid
color-mix(in srgb, var(--bg-sakura-500) 20%, transparent) !important;
}
.red-tag {
border: 1px solid color-mix(in srgb, var(--bg-sakura-500) 20%, transparent);
background: color-mix(in srgb, var(--bg-sakura-500) 10%, transparent);
border-right: 0;
color: var(--bg-sakura-400);
.ant-tag-close-icon {
background: color-mix(in srgb, var(--bg-sakura-500) 30%, transparent);
border-left: 1px solid
color-mix(in srgb, var(--bg-sakura-500) 20%, transparent);
border-right: 1px solid
color-mix(in srgb, var(--bg-sakura-500) 20%, transparent);
svg {
fill: var(--bg-sakura-500);
}
}
}
}
@@ -198,6 +257,9 @@
line-height: 18px;
letter-spacing: -0.07px;
}
.ant-tag {
border-radius: 20px;
}
.action-btn {
display: flex;
@@ -252,6 +314,12 @@
width: 540px;
max-height: 100px;
overflow: auto;
.ant-tag {
height: 28px;
display: flex;
align-items: center;
}
}
.ant-collapse-content-active {
border-top: 0;

View File

@@ -311,7 +311,7 @@ export function PlannedDowntimeForm(
default:
return `Scheduled for ${formattedStartDate} starting at ${formattedStartTime}.`;
}
}, [formData, recurrenceType]);
}, [formData, recurrenceType, timezone]);
const endTimeText = useMemo((): string => {
const endTime = formData.endTime;
@@ -322,7 +322,7 @@ export function PlannedDowntimeForm(
const formattedEndTime = endTime.format(TIME_FORMAT);
const formattedEndDate = endTime.format(DATE_FORMAT);
return `Scheduled to end maintenance on ${formattedEndDate} at ${formattedEndTime}.`;
}, [formData, recurrenceType]);
}, [formData, recurrenceType, timezone]);
return (
<Modal

View File

@@ -1,8 +1,7 @@
import React, { ReactNode, useEffect } from 'react';
import { UseQueryResult } from 'react-query';
import { Color } from '@signozhq/design-tokens';
import { Collapse, Flex, Space, Table, TableProps, Tooltip } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Collapse, Flex, Space, Table, TableProps, Tag, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import type { DefaultOptionType } from 'antd/es/select';
import type {
@@ -16,7 +15,7 @@ import cx from 'classnames';
import dayjs from 'dayjs';
import { useNotifications } from 'hooks/useNotifications';
import { defaultTo } from 'lodash-es';
import { CalendarClock, PenLine, Trash2, X } from '@signozhq/icons';
import { CalendarClock, PenLine, Trash2 } from '@signozhq/icons';
import { useAppContext } from 'providers/App/App';
import { USER_ROLES } from 'types/roles';
@@ -49,10 +48,10 @@ export function AlertRuleTags(props: AlertRuleTagsProps): JSX.Element {
{selectedTags?.map((tag: DefaultOptionType, index: number) => {
const isLongTag = (tag?.label as string)?.length > 20;
const tagElem = (
<Badge
<Tag
key={tag.value}
color={index % 2 ? 'sakura' : 'robin'}
variant="outline"
onClose={(): void => handleClose?.(tag?.value)}
closable={closable}
className={cx(
{ 'red-tag': index % 2 },
{ 'non-closable-tag': !closable },
@@ -63,14 +62,7 @@ export function AlertRuleTags(props: AlertRuleTagsProps): JSX.Element {
? `${(tag?.label as string | null)?.slice(0, 20)}...`
: tag?.label}
</span>
{closable && (
<X
size={12}
style={{ cursor: 'pointer', marginInlineStart: 4 }}
onClick={(): void => handleClose?.(tag?.value)}
/>
)}
</Badge>
</Tag>
);
return isLongTag ? (
<Tooltip title={tag?.label} key={tag?.value}>
@@ -101,7 +93,7 @@ function HeaderComponent({
<Flex className="header-content" justify="space-between">
<Flex gap={8}>
<Typography>{name}</Typography>
<Badge color="vanilla">{duration}</Badge>
<Tag>{duration}</Tag>
</Flex>
{isCrudEnabled && (
@@ -163,7 +155,9 @@ export function CollapseListContent({
created_by_name ? (
<Flex gap={8}>
<Typography>{created_by_name}</Typography>
{created_by_email && <Badge color="vanilla">{created_by_email}</Badge>}
{created_by_email && (
<Tag style={{ borderRadius: 20 }}>{created_by_email}</Tag>
)}
</Flex>
) : (
'-'

View File

@@ -1,11 +1,11 @@
import { Badge } from '@signozhq/ui/badge';
import { Tag } from 'antd';
import styled from 'styled-components';
export const StyledText = styled.span`
cursor: pointer;
`;
export const StyledTag = styled(Badge)`
export const StyledTag = styled(Tag)`
margin-top: 0.125rem;
margin-bottom: 0.125rem;
padding-left: 0.5rem;

View File

@@ -1,5 +1,3 @@
import { X } from '@signozhq/icons';
import { HavingFilterTagProps } from './HavingFilterTag.interfaces';
import { StyledTag, StyledText } from './HavingFilterTag.styled';
@@ -14,17 +12,10 @@ export function HavingFilterTag({
};
return (
<StyledTag color="vanilla">
<StyledTag closable={closable} onClose={onClose}>
<span role="button" tabIndex={0} onClick={handleClick}>
<StyledText>{value}</StyledText>
</span>
{closable && (
<X
size={12}
style={{ cursor: 'pointer', marginInlineStart: 4 }}
onClick={onClose}
/>
)}
</StyledTag>
);
}

View File

@@ -9,8 +9,7 @@ import {
useState,
} from 'react';
import { useLocation } from 'react-router-dom';
import { Button, Select, Spin, Tooltip } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Button, Select, Spin, Tag, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import cx from 'classnames';
import {
@@ -38,7 +37,6 @@ import {
CornerDownLeft,
Filter,
Slash,
X,
} from '@signozhq/icons';
import type { BaseSelectRef } from 'rc-select';
import {
@@ -201,7 +199,7 @@ function QueryBuilderSearch({
const isDisabled = !!searchValue;
return (
<Badge color="vanilla">
<Tag closable={!searchValue && closable} onClose={onCloseHandler}>
<Tooltip title={chipValue}>
<TypographyText
$isInNin={isInNin}
@@ -215,14 +213,7 @@ function QueryBuilderSearch({
{chipValue}
</TypographyText>
</Tooltip>
{!searchValue && closable && (
<X
size={12}
style={{ cursor: 'pointer', marginInlineStart: 4 }}
onClick={onCloseHandler}
/>
)}
</Badge>
</Tag>
);
};

View File

@@ -1,5 +1,5 @@
import { Check } from '@signozhq/icons';
import { Badge } from '@signozhq/ui/badge';
import { Tag } from 'antd';
import styled from 'styled-components';
export const TypographyText = styled.span<{
@@ -22,7 +22,7 @@ export const StyledCheckOutlined = styled(Check)`
float: right;
`;
export const TagContainer = styled(Badge)`
export const TagContainer = styled(Tag)`
&&& {
display: inline-block;
border-radius: 3px;

View File

@@ -225,7 +225,7 @@
}
.qb-search-bar-tokenised-tags {
[data-slot='badge'] {
.ant-tag {
display: flex;
align-items: center;
border-radius: 2px 0px 0px 2px;
@@ -244,7 +244,7 @@
padding: 2px 6px;
}
.close-icon {
.ant-tag-close-icon {
display: flex;
align-items: center;
justify-content: center;
@@ -265,7 +265,7 @@
font-size: 14px;
}
.close-icon {
.ant-tag-close-icon {
background: color-mix(in srgb, var(--bg-aqua-400) 6%, transparent);
}
}
@@ -278,7 +278,7 @@
font-size: 14px;
}
.close-icon {
.ant-tag-close-icon {
background: color-mix(in srgb, var(--bg-sienna-400) 10%, transparent);
}
}
@@ -292,7 +292,7 @@
font-size: 14px;
}
.close-icon {
.ant-tag-close-icon {
background: color-mix(in srgb, var(--bg-robin-400) 10%, transparent);
}
}

View File

@@ -8,8 +8,7 @@ import {
useRef,
useState,
} from 'react';
import { Select, Spin, Tooltip } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Select, Spin, Tag, Tooltip } from 'antd';
import cx from 'classnames';
import {
DATA_TYPE_VS_ATTRIBUTE_VALUES_KEY,
@@ -39,7 +38,7 @@ import {
isUndefined,
unset,
} from 'lodash-es';
import { ChevronDown, ChevronUp, X } from '@signozhq/icons';
import { ChevronDown, ChevronUp } from '@signozhq/icons';
import type { BaseSelectRef } from 'rc-select';
import {
BaseAutocompleteData,
@@ -955,7 +954,11 @@ function QueryBuilderSearchV2(
return (
<span className="qb-search-bar-tokenised-tags">
<Badge color="vanilla" className={tagDetails?.key?.type || ''}>
<Tag
closable={!searchValue && closable}
onClose={onCloseHandler}
className={tagDetails?.key?.type || ''}
>
<Tooltip title={chipValue}>
<TypographyText
$isInNin={isInNin}
@@ -969,15 +972,7 @@ function QueryBuilderSearchV2(
{chipValue}
</TypographyText>
</Tooltip>
{!searchValue && closable && (
<X
size={12}
className="close-icon"
style={{ cursor: 'pointer' }}
onClick={onCloseHandler}
/>
)}
</Badge>
</Tag>
</span>
);
};

View File

@@ -10,7 +10,7 @@
font-size: 12px;
}
[data-slot='badge'] .ant-typography {
.ant-tag .ant-typography {
font-size: 12px;
}
}

View File

@@ -1,4 +1,3 @@
import { X } from '@signozhq/icons';
import {
convertMetricKeyToTrace,
getResourceDeploymentKeys,
@@ -21,19 +20,13 @@ function QueryChip({ queryData, onClose }: IQueryChipProps): JSX.Element {
return (
<QueryChipContainer>
<QueryChipItem color="vanilla">
{convertMetricKeyToTrace(queryData.tagKey)}
</QueryChipItem>
<QueryChipItem color="vanilla">{queryData.operator}</QueryChipItem>
<QueryChipItem color="vanilla">
<QueryChipItem>{convertMetricKeyToTrace(queryData.tagKey)}</QueryChipItem>
<QueryChipItem>{queryData.operator}</QueryChipItem>
<QueryChipItem
closable={queryData.tagKey !== getResourceDeploymentKeys(dotMetricsEnabled)}
onClose={onCloseHandler}
>
{queryData.tagValue.join(', ')}
{queryData.tagKey !== getResourceDeploymentKeys(dotMetricsEnabled) && (
<X
size={12}
style={{ cursor: 'pointer', marginInlineStart: 4 }}
onClick={onCloseHandler}
/>
)}
</QueryChipItem>
</QueryChipContainer>
);

View File

@@ -1,5 +1,5 @@
import { grey } from '@ant-design/colors';
import { Badge } from '@signozhq/ui/badge';
import { Tag } from 'antd';
import styled from 'styled-components';
export const SearchContainer = styled.div`
@@ -23,6 +23,6 @@ export const QueryChipContainer = styled.span`
}
`;
export const QueryChipItem = styled(Badge)`
export const QueryChipItem = styled(Tag)`
margin-right: 0.1rem;
`;

View File

@@ -1,6 +1,5 @@
import { Color } from '@signozhq/design-tokens';
import { Button, Collapse, Flex } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Button, Collapse, Flex, Tag } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import { PenLine, Trash2 } from '@signozhq/icons';
@@ -119,9 +118,7 @@ function PolicyListItemContent({
<Typography>Channels</Typography>
<div>
{routingPolicy.channels.map((channel) => (
<Badge key={channel} color="vanilla">
{channel}
</Badge>
<Tag key={channel}>{channel}</Tag>
))}
</div>
</div>

View File

@@ -1,5 +1,4 @@
import { Tooltip } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Tag, Tooltip } from 'antd';
import cx from 'classnames';
import { Pin, PinOff } from '@signozhq/icons';
@@ -59,17 +58,17 @@ export default function NavItem({
{isBeta && (
<div className="nav-item-beta">
<Badge color="robin" className="sidenav-beta-tag">
<Tag bordered={false} className="sidenav-beta-tag">
Beta
</Badge>
</Tag>
</div>
)}
{isNew && (
<div className="nav-item-new">
<Badge color="robin" className="sidenav-new-tag">
<Tag bordered={false} className="sidenav-new-tag">
New
</Badge>
</Tag>
</div>
)}

View File

@@ -1,258 +0,0 @@
import {
act,
fireEvent,
render,
screen,
waitFor,
} from '@testing-library/react';
import DateTimeSelection from '../index';
import {
__resetSearchParamsGetter,
__setSearchParamsGetterForTest,
} from '../utils/getUnstableCurrentSearchParams';
import { queryClient, TestWrapper } from './testUtils';
const mockSafeNavigate = jest.fn();
jest.mock('hooks/useSafeNavigate', () => ({
useSafeNavigate: (): { safeNavigate: jest.Mock } => ({
safeNavigate: mockSafeNavigate,
}),
}));
jest.mock('container/NewExplorerCTA', () => ({
__esModule: true,
default: (): null => null,
}));
jest.mock('components/CustomTimePicker/CustomTimePicker', () => ({
__esModule: true,
default: ({
onSelect,
}: {
onSelect: (value: string) => void;
}): JSX.Element => (
<div data-testid="custom-time-picker">
<button
type="button"
data-testid="select-15m"
onClick={(): void => onSelect('15m')}
>
15m
</button>
<button
type="button"
data-testid="select-1h"
onClick={(): void => onSelect('1h')}
>
1h
</button>
<button
type="button"
data-testid="select-6h"
onClick={(): void => onSelect('6h')}
>
6h
</button>
<button
type="button"
data-testid="select-custom"
onClick={(): void => onSelect('custom')}
>
Custom
</button>
</div>
),
}));
describe('DateTimeSelectionV2 - Edge Cases', () => {
let currentSearchParams: URLSearchParams;
beforeEach(() => {
jest.clearAllMocks();
mockSafeNavigate.mockClear();
queryClient.clear();
});
afterEach(() => {
__resetSearchParamsGetter();
});
describe('Fresh Params at Navigation Time (Core Fix)', () => {
it('should read params at navigation time, not render time', async () => {
currentSearchParams = new URLSearchParams('relativeTime=30m');
__setSearchParamsGetterForTest(() => currentSearchParams);
render(
<TestWrapper
initialSearchParams="relativeTime=30m"
onUrlUpdate={(event): void => {
currentSearchParams = event.searchParams;
}}
>
<DateTimeSelection showAutoRefresh />
</TestWrapper>,
);
await waitFor(() => {
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
});
currentSearchParams = new URLSearchParams(
'relativeTime=30m&externalParam=addedLater',
);
mockSafeNavigate.mockClear();
act(() => {
fireEvent.click(screen.getByTestId('select-1h'));
});
await waitFor(() => {
expect(mockSafeNavigate).toHaveBeenCalled();
});
const navigatedUrl = mockSafeNavigate.mock.calls[
mockSafeNavigate.mock.calls.length - 1
][0] as string;
expect(navigatedUrl).toContain('relativeTime=1h');
expect(navigatedUrl).toContain('externalParam=addedLater');
});
it('should preserve multiple externally added params', async () => {
currentSearchParams = new URLSearchParams('relativeTime=30m');
__setSearchParamsGetterForTest(() => currentSearchParams);
render(
<TestWrapper initialSearchParams="relativeTime=30m">
<DateTimeSelection showAutoRefresh />
</TestWrapper>,
);
await waitFor(() => {
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
});
currentSearchParams = new URLSearchParams(
'relativeTime=30m&yAxisUnit=bytes&groupBy=host&view=table',
);
mockSafeNavigate.mockClear();
act(() => {
fireEvent.click(screen.getByTestId('select-6h'));
});
await waitFor(() => {
expect(mockSafeNavigate).toHaveBeenCalled();
});
const navigatedUrl = mockSafeNavigate.mock.calls[
mockSafeNavigate.mock.calls.length - 1
][0] as string;
expect(navigatedUrl).toContain('relativeTime=6h');
expect(navigatedUrl).toContain('yAxisUnit=bytes');
expect(navigatedUrl).toContain('groupBy=host');
expect(navigatedUrl).toContain('view=table');
});
});
describe('Empty and Special Values', () => {
it('should handle empty URL params gracefully', async () => {
currentSearchParams = new URLSearchParams('');
__setSearchParamsGetterForTest(() => currentSearchParams);
render(
<TestWrapper initialSearchParams="">
<DateTimeSelection showAutoRefresh />
</TestWrapper>,
);
await waitFor(() => {
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
});
mockSafeNavigate.mockClear();
act(() => {
fireEvent.click(screen.getByTestId('select-15m'));
});
await waitFor(() => {
expect(mockSafeNavigate).toHaveBeenCalled();
});
const navigatedUrl = mockSafeNavigate.mock.calls[
mockSafeNavigate.mock.calls.length - 1
][0] as string;
expect(navigatedUrl).toContain('relativeTime=15m');
});
it('should handle special characters in preserved params', async () => {
currentSearchParams = new URLSearchParams(
'relativeTime=30m&filter=name%3D%22test%22',
);
__setSearchParamsGetterForTest(() => currentSearchParams);
render(
<TestWrapper initialSearchParams="relativeTime=30m&filter=name%3D%22test%22">
<DateTimeSelection showAutoRefresh />
</TestWrapper>,
);
await waitFor(() => {
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
});
mockSafeNavigate.mockClear();
act(() => {
fireEvent.click(screen.getByTestId('select-1h'));
});
await waitFor(() => {
expect(mockSafeNavigate).toHaveBeenCalled();
});
const navigatedUrl = mockSafeNavigate.mock.calls[
mockSafeNavigate.mock.calls.length - 1
][0] as string;
expect(navigatedUrl).toContain('relativeTime=1h');
expect(navigatedUrl).toContain('filter=');
});
it('should not navigate when selecting custom (opens picker instead)', async () => {
currentSearchParams = new URLSearchParams('relativeTime=30m');
__setSearchParamsGetterForTest(() => currentSearchParams);
render(
<TestWrapper initialSearchParams="relativeTime=30m">
<DateTimeSelection showAutoRefresh />
</TestWrapper>,
);
await waitFor(() => {
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
});
mockSafeNavigate.mockClear();
act(() => {
fireEvent.click(screen.getByTestId('select-custom'));
});
await new Promise((resolve) => setTimeout(resolve, 100));
const customNavigationCalls = mockSafeNavigate.mock.calls.filter((call) => {
const url = call[0] as string;
return url.includes('startTime=') || url.includes('endTime=');
});
expect(customNavigationCalls).toHaveLength(0);
});
});
});

View File

@@ -1,180 +0,0 @@
import {
act,
fireEvent,
render,
screen,
waitFor,
} from '@testing-library/react';
import DateTimeSelection from '../index';
import {
__resetSearchParamsGetter,
__setSearchParamsGetterForTest,
} from '../utils/getUnstableCurrentSearchParams';
import { queryClient, TestWrapper } from './testUtils';
const mockSafeNavigate = jest.fn();
jest.mock('hooks/useSafeNavigate', () => ({
useSafeNavigate: (): { safeNavigate: jest.Mock } => ({
safeNavigate: mockSafeNavigate,
}),
}));
jest.mock('container/NewExplorerCTA', () => ({
__esModule: true,
default: (): null => null,
}));
let mockOnCustomDateHandler: ((range: [unknown, unknown]) => void) | null =
null;
let mockOnValidCustomDateChange: ((data: { timeStr: string }) => void) | null =
null;
jest.mock('components/CustomTimePicker/CustomTimePicker', () => ({
__esModule: true,
default: ({
onSelect,
onCustomDateHandler,
onValidCustomDateChange,
}: {
onSelect: (value: string) => void;
onCustomDateHandler?: (range: [unknown, unknown]) => void;
onValidCustomDateChange?: (data: { timeStr: string }) => void;
}): JSX.Element => {
mockOnCustomDateHandler = onCustomDateHandler || null;
mockOnValidCustomDateChange = onValidCustomDateChange || null;
return (
<div data-testid="custom-time-picker">
<button
type="button"
data-testid="select-1h"
onClick={(): void => onSelect('1h')}
>
1h
</button>
</div>
);
},
}));
describe('DateTimeSelectionV2 - Modal Mode', () => {
let currentSearchParams: URLSearchParams;
beforeEach(() => {
jest.clearAllMocks();
mockSafeNavigate.mockClear();
queryClient.clear();
mockOnCustomDateHandler = null;
mockOnValidCustomDateChange = null;
});
afterEach(() => {
__resetSearchParamsGetter();
});
it('should call onTimeChange instead of navigating for relative time', async () => {
currentSearchParams = new URLSearchParams('relativeTime=30m');
__setSearchParamsGetterForTest(() => currentSearchParams);
const mockOnTimeChange = jest.fn();
render(
<TestWrapper initialSearchParams="relativeTime=30m">
<DateTimeSelection
showAutoRefresh
isModalTimeSelection
onTimeChange={mockOnTimeChange}
/>
</TestWrapper>,
);
await waitFor(() => {
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
});
mockSafeNavigate.mockClear();
act(() => {
fireEvent.click(screen.getByTestId('select-1h'));
});
await waitFor(() => {
expect(mockOnTimeChange).toHaveBeenCalledWith('1h');
});
expect(mockSafeNavigate).not.toHaveBeenCalled();
});
it('should call onTimeChange with custom and timestamps for custom date', async () => {
currentSearchParams = new URLSearchParams('relativeTime=30m');
__setSearchParamsGetterForTest(() => currentSearchParams);
const mockOnTimeChange = jest.fn();
render(
<TestWrapper initialSearchParams="relativeTime=30m">
<DateTimeSelection
showAutoRefresh
isModalTimeSelection
onTimeChange={mockOnTimeChange}
/>
</TestWrapper>,
);
await waitFor(() => {
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
});
const startMoment = { toDate: (): Date => new Date(1700000000000) };
const endMoment = { toDate: (): Date => new Date(1700003600000) };
mockSafeNavigate.mockClear();
act(() => {
mockOnCustomDateHandler?.([startMoment, endMoment]);
});
await waitFor(() => {
expect(mockOnTimeChange).toHaveBeenCalledWith(
'custom',
[1700000000000, 1700003600000],
);
});
expect(mockSafeNavigate).not.toHaveBeenCalled();
});
it('should call onTimeChange for valid custom date string in modal', async () => {
currentSearchParams = new URLSearchParams('relativeTime=30m');
__setSearchParamsGetterForTest(() => currentSearchParams);
const mockOnTimeChange = jest.fn();
render(
<TestWrapper initialSearchParams="relativeTime=30m">
<DateTimeSelection
showAutoRefresh
isModalTimeSelection
onTimeChange={mockOnTimeChange}
/>
</TestWrapper>,
);
await waitFor(() => {
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
});
mockSafeNavigate.mockClear();
act(() => {
mockOnValidCustomDateChange?.({ timeStr: '4h' });
});
await waitFor(() => {
expect(mockOnTimeChange).toHaveBeenCalledWith('4h');
});
expect(mockSafeNavigate).not.toHaveBeenCalled();
});
});

View File

@@ -1,207 +0,0 @@
// eslint-disable-next-line no-restricted-imports
import { Provider } from 'react-redux';
import { MemoryRouter, Route } from 'react-router-dom';
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
import { QueryClient, QueryClientProvider } from 'react-query';
import {
act,
fireEvent,
render,
screen,
waitFor,
} from '@testing-library/react';
import { parseAsString, useQueryState } from 'nuqs';
import { AppContext } from 'providers/App/App';
import TimezoneProvider from 'providers/Timezone';
import { QueryBuilderProvider } from 'providers/QueryBuilder';
import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import store from 'store';
import { getAppContextMock } from 'tests/test-utils';
import { CompatRouter } from 'react-router-dom-v5-compat';
import DateTimeSelection from '../index';
import {
__resetSearchParamsGetter,
__setSearchParamsGetterForTest,
} from '../utils/getUnstableCurrentSearchParams';
const queryClient = new QueryClient({
defaultOptions: {
queries: { refetchOnWindowFocus: false, retry: false },
mutations: { retry: false },
},
});
const mockStore = configureStore([thunk]);
const mockSafeNavigate = jest.fn();
jest.mock('hooks/useSafeNavigate', () => ({
useSafeNavigate: (): { safeNavigate: jest.Mock } => ({
safeNavigate: mockSafeNavigate,
}),
}));
jest.mock('components/CustomTimePicker/CustomTimePicker', () => ({
__esModule: true,
default: ({
onSelect,
}: {
onSelect: (value: string) => void;
}): JSX.Element => (
<div data-testid="custom-time-picker">
<button
type="button"
data-testid="select-1h"
onClick={(): void => onSelect('1h')}
>
1h
</button>
</div>
),
}));
jest.mock('container/NewExplorerCTA', () => ({
__esModule: true,
default: (): null => null,
}));
function NuqsParamSetter({ paramValue }: { paramValue: string }): JSX.Element {
const [, setYAxisUnit] = useQueryState(
'yAxisUnit',
parseAsString.withDefault(''),
);
return (
<button
type="button"
data-testid="set-nuqs-param"
onClick={(): void => {
setYAxisUnit(paramValue);
}}
>
Set yAxisUnit
</button>
);
}
interface WrapperProps {
children: React.ReactNode;
initialSearchParams?: string;
initialPath?: string;
onUrlUpdate?: (event: { searchParams: URLSearchParams }) => void;
}
function TestWrapper({
children,
initialSearchParams = '',
initialPath = '/services',
onUrlUpdate,
}: WrapperProps): JSX.Element {
const initialEntry = initialSearchParams
? `${initialPath}?${initialSearchParams}`
: initialPath;
const mockedStore = mockStore({
...store.getState(),
app: {
...store.getState().app,
role: 'ADMIN',
user: {
userId: 'test-user-id',
email: 'test@signoz.io',
name: 'TestUser',
profilePictureURL: '',
accessJwt: '',
refreshJwt: '',
},
isLoggedIn: true,
},
});
return (
<MemoryRouter initialEntries={[initialEntry]}>
<CompatRouter>
<NuqsTestingAdapter
searchParams={initialSearchParams}
onUrlUpdate={onUrlUpdate}
>
<QueryClientProvider client={queryClient}>
<Provider store={mockedStore}>
<AppContext.Provider value={getAppContextMock('ADMIN')}>
<TimezoneProvider>
<QueryBuilderProvider>
<Route path="*">{children}</Route>
</QueryBuilderProvider>
</TimezoneProvider>
</AppContext.Provider>
</Provider>
</QueryClientProvider>
</NuqsTestingAdapter>
</CompatRouter>
</MemoryRouter>
);
}
describe('REGRESSION: DateTimeSelectionV2 preserves nuqs query params on time change', () => {
let currentSearchParams: URLSearchParams;
beforeEach(() => {
jest.clearAllMocks();
mockSafeNavigate.mockClear();
queryClient.clear();
// Initialize with test's initial search params
currentSearchParams = new URLSearchParams('relativeTime=30m');
__setSearchParamsGetterForTest(() => currentSearchParams);
});
afterEach(() => {
__resetSearchParamsGetter();
});
it('should preserve yAxisUnit param set via nuqs when changing time selection', async () => {
render(
<TestWrapper
initialSearchParams="relativeTime=30m"
onUrlUpdate={(event): void => {
// Sync nuqs URL updates to our mock getter
// This simulates how window.location.search would be updated in real browser
currentSearchParams = event.searchParams;
}}
>
<NuqsParamSetter paramValue="bytes" />
<DateTimeSelection showAutoRefresh />
</TestWrapper>,
);
await waitFor(() => {
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
});
act(() => {
fireEvent.click(screen.getByTestId('set-nuqs-param'));
});
await waitFor(() => {
expect(currentSearchParams.get('yAxisUnit')).toBe('bytes');
});
mockSafeNavigate.mockClear();
act(() => {
fireEvent.click(screen.getByTestId('select-1h'));
});
await waitFor(() => {
expect(mockSafeNavigate).toHaveBeenCalled();
});
const navigatedUrl = mockSafeNavigate.mock.calls[
mockSafeNavigate.mock.calls.length - 1
][0] as string;
expect(navigatedUrl).toContain('relativeTime=1h');
expect(navigatedUrl).toContain('yAxisUnit=bytes');
});
});

View File

@@ -1,133 +0,0 @@
import { render, screen, waitFor } from '@testing-library/react';
import ROUTES from 'constants/routes';
import DateTimeSelection from '../index';
import {
__resetSearchParamsGetter,
__setSearchParamsGetterForTest,
} from '../utils/getUnstableCurrentSearchParams';
import { queryClient, TestWrapper } from './testUtils';
const mockSafeNavigate = jest.fn();
jest.mock('hooks/useSafeNavigate', () => ({
useSafeNavigate: (): { safeNavigate: jest.Mock } => ({
safeNavigate: mockSafeNavigate,
}),
}));
jest.mock('container/NewExplorerCTA', () => ({
__esModule: true,
default: (): null => null,
}));
jest.mock('components/CustomTimePicker/CustomTimePicker', () => ({
__esModule: true,
default: (): JSX.Element => <div data-testid="custom-time-picker" />,
}));
describe('DateTimeSelectionV2 - Route-Specific Behavior', () => {
let currentSearchParams: URLSearchParams;
beforeEach(() => {
jest.clearAllMocks();
mockSafeNavigate.mockClear();
queryClient.clear();
});
afterEach(() => {
__resetSearchParamsGetter();
});
describe('Alert Pages', () => {
it('should set default time for alert overview when no time params', async () => {
currentSearchParams = new URLSearchParams('otherParam=value');
__setSearchParamsGetterForTest(() => currentSearchParams);
render(
<TestWrapper
initialSearchParams="otherParam=value"
initialPath={ROUTES.ALERT_OVERVIEW}
>
<DateTimeSelection showAutoRefresh defaultRelativeTime="6h" />
</TestWrapper>,
);
await waitFor(() => {
expect(mockSafeNavigate).toHaveBeenCalled();
});
const navigatedUrl = mockSafeNavigate.mock.calls[0][0] as string;
expect(navigatedUrl).toContain('relativeTime=6h');
expect(navigatedUrl).toContain('otherParam=value');
});
it('should set default time for alert history when no time params', async () => {
currentSearchParams = new URLSearchParams('filter=active');
__setSearchParamsGetterForTest(() => currentSearchParams);
render(
<TestWrapper
initialSearchParams="filter=active"
initialPath={ROUTES.ALERT_HISTORY}
>
<DateTimeSelection showAutoRefresh defaultRelativeTime="6h" />
</TestWrapper>,
);
await waitFor(() => {
expect(mockSafeNavigate).toHaveBeenCalled();
});
const navigatedUrl = mockSafeNavigate.mock.calls[0][0] as string;
expect(navigatedUrl).toContain('relativeTime=6h');
});
it('should NOT override existing time params on alert pages', async () => {
currentSearchParams = new URLSearchParams('relativeTime=1h&filter=active');
__setSearchParamsGetterForTest(() => currentSearchParams);
render(
<TestWrapper
initialSearchParams="relativeTime=1h&filter=active"
initialPath={ROUTES.ALERT_OVERVIEW}
>
<DateTimeSelection showAutoRefresh defaultRelativeTime="6h" />
</TestWrapper>,
);
await waitFor(() => {
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
});
const calls = mockSafeNavigate.mock.calls;
if (calls.length > 0) {
const lastUrl = calls[calls.length - 1][0] as string;
expect(lastUrl).toContain('relativeTime=1h');
}
});
});
describe('disableUrlSync Behavior', () => {
it('should not sync URL on mount when disableUrlSync is true', async () => {
currentSearchParams = new URLSearchParams('');
__setSearchParamsGetterForTest(() => currentSearchParams);
render(
<TestWrapper initialSearchParams="" initialPath="/services">
<DateTimeSelection showAutoRefresh disableUrlSync />
</TestWrapper>,
);
await waitFor(() => {
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
});
const syncCalls = mockSafeNavigate.mock.calls.filter((call) => {
const url = call[0] as string;
return url.includes('relativeTime=') && !url.includes('services?');
});
expect(syncCalls).toHaveLength(0);
});
});
});

View File

@@ -1,353 +0,0 @@
import {
act,
fireEvent,
render,
screen,
waitFor,
} from '@testing-library/react';
import DateTimeSelection from '../index';
import {
__resetSearchParamsGetter,
__setSearchParamsGetterForTest,
} from '../utils/getUnstableCurrentSearchParams';
import { queryClient, TestWrapper, createMockMoment } from './testUtils';
const mockSafeNavigate = jest.fn();
jest.mock('hooks/useSafeNavigate', () => ({
useSafeNavigate: (): { safeNavigate: jest.Mock } => ({
safeNavigate: mockSafeNavigate,
}),
}));
jest.mock('container/NewExplorerCTA', () => ({
__esModule: true,
default: (): null => null,
}));
let mockOnCustomDateHandler: ((range: [unknown, unknown]) => void) | null =
null;
let mockOnValidCustomDateChange: ((data: { timeStr: string }) => void) | null =
null;
jest.mock('components/CustomTimePicker/CustomTimePicker', () => ({
__esModule: true,
default: ({
onSelect,
onCustomDateHandler,
onValidCustomDateChange,
}: {
onSelect: (value: string) => void;
onCustomDateHandler?: (range: [unknown, unknown]) => void;
onValidCustomDateChange?: (data: { timeStr: string }) => void;
}): JSX.Element => {
mockOnCustomDateHandler = onCustomDateHandler || null;
mockOnValidCustomDateChange = onValidCustomDateChange || null;
return (
<div data-testid="custom-time-picker">
<button
type="button"
data-testid="select-15m"
onClick={(): void => onSelect('15m')}
>
15m
</button>
<button
type="button"
data-testid="select-1h"
onClick={(): void => onSelect('1h')}
>
1h
</button>
<button
type="button"
data-testid="select-6h"
onClick={(): void => onSelect('6h')}
>
6h
</button>
</div>
);
},
}));
describe('DateTimeSelectionV2 - Time Selection', () => {
let currentSearchParams: URLSearchParams;
beforeEach(() => {
jest.clearAllMocks();
mockSafeNavigate.mockClear();
queryClient.clear();
mockOnCustomDateHandler = null;
mockOnValidCustomDateChange = null;
});
afterEach(() => {
__resetSearchParamsGetter();
});
describe('Relative Time', () => {
it('should update relativeTime and remove startTime/endTime', async () => {
currentSearchParams = new URLSearchParams(
'startTime=1000&endTime=2000&otherParam=keep',
);
__setSearchParamsGetterForTest(() => currentSearchParams);
render(
<TestWrapper initialSearchParams="startTime=1000&endTime=2000&otherParam=keep">
<DateTimeSelection showAutoRefresh />
</TestWrapper>,
);
await waitFor(() => {
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
});
mockSafeNavigate.mockClear();
act(() => {
fireEvent.click(screen.getByTestId('select-15m'));
});
await waitFor(() => {
expect(mockSafeNavigate).toHaveBeenCalled();
});
const navigatedUrl = mockSafeNavigate.mock.calls[
mockSafeNavigate.mock.calls.length - 1
][0] as string;
expect(navigatedUrl).toContain('relativeTime=15m');
expect(navigatedUrl).not.toContain('startTime=');
expect(navigatedUrl).not.toContain('endTime=');
expect(navigatedUrl).toContain('otherParam=keep');
});
it('should remove activeLogId param on time change', async () => {
currentSearchParams = new URLSearchParams(
'relativeTime=30m&activeLogId=log123',
);
__setSearchParamsGetterForTest(() => currentSearchParams);
render(
<TestWrapper initialSearchParams="relativeTime=30m&activeLogId=log123">
<DateTimeSelection showAutoRefresh />
</TestWrapper>,
);
await waitFor(() => {
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
});
mockSafeNavigate.mockClear();
act(() => {
fireEvent.click(screen.getByTestId('select-1h'));
});
await waitFor(() => {
expect(mockSafeNavigate).toHaveBeenCalled();
});
const navigatedUrl = mockSafeNavigate.mock.calls[
mockSafeNavigate.mock.calls.length - 1
][0] as string;
expect(navigatedUrl).toContain('relativeTime=1h');
expect(navigatedUrl).not.toContain('activeLogId');
});
it('should update compositeQuery with new ID when present', async () => {
const compositeQuery = encodeURIComponent(
JSON.stringify({ id: 'old-id', builder: { queryData: [] } }),
);
currentSearchParams = new URLSearchParams(
`relativeTime=30m&compositeQuery=${compositeQuery}`,
);
__setSearchParamsGetterForTest(() => currentSearchParams);
render(
<TestWrapper
initialSearchParams={`relativeTime=30m&compositeQuery=${compositeQuery}`}
>
<DateTimeSelection showAutoRefresh />
</TestWrapper>,
);
await waitFor(() => {
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
});
mockSafeNavigate.mockClear();
act(() => {
fireEvent.click(screen.getByTestId('select-6h'));
});
await waitFor(() => {
expect(mockSafeNavigate).toHaveBeenCalled();
});
const navigatedUrl = mockSafeNavigate.mock.calls[
mockSafeNavigate.mock.calls.length - 1
][0] as string;
expect(navigatedUrl).toContain('compositeQuery=');
expect(navigatedUrl).not.toContain('old-id');
});
it('should preserve all non-time URL params', async () => {
currentSearchParams = new URLSearchParams(
'relativeTime=30m&param1=a&param2=b&param3=c',
);
__setSearchParamsGetterForTest(() => currentSearchParams);
render(
<TestWrapper initialSearchParams="relativeTime=30m&param1=a&param2=b&param3=c">
<DateTimeSelection showAutoRefresh />
</TestWrapper>,
);
await waitFor(() => {
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
});
mockSafeNavigate.mockClear();
act(() => {
fireEvent.click(screen.getByTestId('select-1h'));
});
await waitFor(() => {
expect(mockSafeNavigate).toHaveBeenCalled();
});
const navigatedUrl = mockSafeNavigate.mock.calls[
mockSafeNavigate.mock.calls.length - 1
][0] as string;
expect(navigatedUrl).toContain('relativeTime=1h');
expect(navigatedUrl).toContain('param1=a');
expect(navigatedUrl).toContain('param2=b');
expect(navigatedUrl).toContain('param3=c');
});
});
describe('Custom Date Range', () => {
it('should set startTime/endTime and remove relativeTime', async () => {
currentSearchParams = new URLSearchParams('relativeTime=30m&keepThis=yes');
__setSearchParamsGetterForTest(() => currentSearchParams);
render(
<TestWrapper initialSearchParams="relativeTime=30m&keepThis=yes">
<DateTimeSelection showAutoRefresh />
</TestWrapper>,
);
await waitFor(() => {
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
});
const startMoment = createMockMoment(1700000000000);
const endMoment = createMockMoment(1700003600000);
mockSafeNavigate.mockClear();
act(() => {
mockOnCustomDateHandler?.([startMoment, endMoment]);
});
await waitFor(() => {
expect(mockSafeNavigate).toHaveBeenCalled();
});
const navigatedUrl = mockSafeNavigate.mock.calls[
mockSafeNavigate.mock.calls.length - 1
][0] as string;
expect(navigatedUrl).toContain('startTime=1700000000000');
expect(navigatedUrl).toContain('endTime=1700003600000');
expect(navigatedUrl).not.toContain('relativeTime=');
expect(navigatedUrl).toContain('keepThis=yes');
});
it('should update compositeQuery when present for custom date', async () => {
const compositeQuery = encodeURIComponent(
JSON.stringify({ id: 'old-id', builder: { queryData: [] } }),
);
currentSearchParams = new URLSearchParams(
`relativeTime=30m&compositeQuery=${compositeQuery}`,
);
__setSearchParamsGetterForTest(() => currentSearchParams);
render(
<TestWrapper
initialSearchParams={`relativeTime=30m&compositeQuery=${compositeQuery}`}
>
<DateTimeSelection showAutoRefresh />
</TestWrapper>,
);
await waitFor(() => {
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
});
const startMoment = createMockMoment(1700000000000);
const endMoment = createMockMoment(1700003600000);
mockSafeNavigate.mockClear();
act(() => {
mockOnCustomDateHandler?.([startMoment, endMoment]);
});
await waitFor(() => {
expect(mockSafeNavigate).toHaveBeenCalled();
});
const navigatedUrl = mockSafeNavigate.mock.calls[
mockSafeNavigate.mock.calls.length - 1
][0] as string;
expect(navigatedUrl).toContain('compositeQuery=');
expect(navigatedUrl).not.toContain('old-id');
});
});
describe('Valid Custom Date String', () => {
it('should handle shorthand date format and preserve params', async () => {
currentSearchParams = new URLSearchParams('relativeTime=30m&filter=active');
__setSearchParamsGetterForTest(() => currentSearchParams);
render(
<TestWrapper initialSearchParams="relativeTime=30m&filter=active">
<DateTimeSelection showAutoRefresh />
</TestWrapper>,
);
await waitFor(() => {
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
});
mockSafeNavigate.mockClear();
act(() => {
mockOnValidCustomDateChange?.({ timeStr: '2h' });
});
await waitFor(() => {
expect(mockSafeNavigate).toHaveBeenCalled();
});
const navigatedUrl = mockSafeNavigate.mock.calls[
mockSafeNavigate.mock.calls.length - 1
][0] as string;
expect(navigatedUrl).toContain('relativeTime=2h');
expect(navigatedUrl).toContain('filter=active');
expect(navigatedUrl).not.toContain('startTime=');
expect(navigatedUrl).not.toContain('endTime=');
});
});
});

View File

@@ -1,95 +0,0 @@
// eslint-disable-next-line no-restricted-imports
import { Provider } from 'react-redux';
import { MemoryRouter, Route } from 'react-router-dom';
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
import { QueryClient, QueryClientProvider } from 'react-query';
import { AppContext } from 'providers/App/App';
import TimezoneProvider from 'providers/Timezone';
import { QueryBuilderProvider } from 'providers/QueryBuilder';
import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import store from 'store';
import { getAppContextMock } from 'tests/test-utils';
import { CompatRouter } from 'react-router-dom-v5-compat';
export const queryClient = new QueryClient({
defaultOptions: {
queries: { refetchOnWindowFocus: false, retry: false },
mutations: { retry: false },
},
});
export const mockStore = configureStore([thunk]);
interface WrapperProps {
children: React.ReactNode;
initialSearchParams?: string;
initialPath?: string;
onUrlUpdate?: (event: { searchParams: URLSearchParams }) => void;
}
export function TestWrapper({
children,
initialSearchParams = '',
initialPath = '/services',
onUrlUpdate,
}: WrapperProps): JSX.Element {
const initialEntry = initialSearchParams
? `${initialPath}?${initialSearchParams}`
: initialPath;
const mockedStore = mockStore({
...store.getState(),
app: {
...store.getState().app,
role: 'ADMIN',
user: {
userId: 'test-user-id',
email: 'test@signoz.io',
name: 'TestUser',
profilePictureURL: '',
accessJwt: '',
refreshJwt: '',
},
isLoggedIn: true,
},
});
return (
<MemoryRouter initialEntries={[initialEntry]}>
<CompatRouter>
<NuqsTestingAdapter
searchParams={initialSearchParams}
onUrlUpdate={onUrlUpdate}
>
<QueryClientProvider client={queryClient}>
<Provider store={mockedStore}>
<AppContext.Provider value={getAppContextMock('ADMIN')}>
<TimezoneProvider>
<QueryBuilderProvider>
<Route path="*">{children}</Route>
</QueryBuilderProvider>
</TimezoneProvider>
</AppContext.Provider>
</Provider>
</QueryClientProvider>
</NuqsTestingAdapter>
</CompatRouter>
</MemoryRouter>
);
}
export function createMockMoment(timestamp: number): {
toDate: () => Date;
toISOString: () => string;
format: () => string;
toString: () => string;
} {
const date = new Date(timestamp);
return {
toDate: (): Date => date,
toISOString: (): string => date.toISOString(),
format: (): string => date.toISOString(),
toString: (): string => date.toString(),
};
}

View File

@@ -2,7 +2,7 @@ import { useCallback, useEffect, useState } from 'react';
// eslint-disable-next-line no-restricted-imports
import { connect, useDispatch, useSelector } from 'react-redux';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import { useNavigationType } from 'react-router-dom-v5-compat';
import { useNavigationType, useSearchParams } from 'react-router-dom-v5-compat';
import { RefreshCw, Undo } from '@signozhq/icons';
import { Button } from 'antd';
import getLocalStorageKey from 'api/browser/localstorage/get';
@@ -20,6 +20,7 @@ import {
} from 'store/globalTime';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import { isValidShortHandDateTimeFormat } from 'lib/getMinMax';
import getTimeString from 'lib/getTimeString';
import { cloneDeep, isObject } from 'lodash-es';
@@ -53,7 +54,6 @@ import {
Time,
TimeRange,
} from './types';
import { getUnstableCurrentSearchParams } from './utils/getUnstableCurrentSearchParams';
import './DateTimeSelectionV2.styles.scss';
@@ -90,12 +90,10 @@ function DateTimeSelection({
const [hasSelectedTimeError, setHasSelectedTimeError] = useState(false);
const [isOpen, setIsOpen] = useState<boolean>(false);
const currentSearchParams = getUnstableCurrentSearchParams();
const searchStartTime = currentSearchParams.get(QueryParams.startTime);
const searchEndTime = currentSearchParams.get(QueryParams.endTime);
const relativeTimeFromUrl = currentSearchParams.get(QueryParams.relativeTime);
const hasTimeParamsInUrl =
(searchStartTime && searchEndTime) || relativeTimeFromUrl;
const urlQuery = useUrlQuery();
const searchStartTime = urlQuery.get('startTime');
const searchEndTime = urlQuery.get('endTime');
const relativeTimeFromUrl = urlQuery.get(QueryParams.relativeTime);
// Prioritize props for initial modal time, fallback to URL params
let initialModalStartTime = 0;
@@ -117,6 +115,8 @@ function DateTimeSelection({
);
const [modalEndTime, setModalEndTime] = useState<number>(initialModalEndTime);
const [searchParams] = useSearchParams();
// Effect to update modal time state when props change
useEffect(() => {
if (modalInitialStartTime !== undefined) {
@@ -323,15 +323,14 @@ function DateTimeSelection({
return;
}
const urlQuery = getUnstableCurrentSearchParams();
urlQuery.delete(QueryParams.startTime);
urlQuery.delete(QueryParams.endTime);
urlQuery.delete('startTime');
urlQuery.delete('endTime');
urlQuery.set(QueryParams.relativeTime, value);
// Remove Hidden Filters from URL query parameters on time change
urlQuery.delete(QueryParams.activeLogId);
if (urlQuery.has(QueryParams.compositeQuery)) {
if (searchParams.has(QueryParams.compositeQuery)) {
const updatedCompositeQuery = getUpdatedCompositeQuery();
urlQuery.set(QueryParams.compositeQuery, updatedCompositeQuery);
}
@@ -350,6 +349,8 @@ function DateTimeSelection({
getUpdatedCompositeQuery,
updateLocalStorageForRoutes,
updateTimeInterval,
urlQuery,
searchParams,
],
);
@@ -413,7 +414,6 @@ function DateTimeSelection({
updateLocalStorageForRoutes(JSON.stringify({ startTime, endTime }));
const urlQuery = getUnstableCurrentSearchParams();
urlQuery.set(
QueryParams.startTime,
startTime?.toDate().getTime().toString(),
@@ -421,7 +421,7 @@ function DateTimeSelection({
urlQuery.set(QueryParams.endTime, endTime?.toDate().getTime().toString());
urlQuery.delete(QueryParams.relativeTime);
if (urlQuery.has(QueryParams.compositeQuery)) {
if (searchParams.has(QueryParams.compositeQuery)) {
const updatedCompositeQuery = getUpdatedCompositeQuery();
urlQuery.set(QueryParams.compositeQuery, updatedCompositeQuery);
}
@@ -441,9 +441,8 @@ function DateTimeSelection({
updateTimeInterval(dateTimeStr);
updateLocalStorageForRoutes(dateTimeStr);
const urlQuery = getUnstableCurrentSearchParams();
urlQuery.delete(QueryParams.startTime);
urlQuery.delete(QueryParams.endTime);
urlQuery.delete('startTime');
urlQuery.delete('endTime');
urlQuery.set(QueryParams.relativeTime, dateTimeStr);
@@ -596,12 +595,13 @@ function DateTimeSelection({
// set the default relative time for alert history and overview pages if relative time is not specified
if (
!hasTimeParamsInUrl &&
(!urlQuery.has(QueryParams.startTime) ||
!urlQuery.has(QueryParams.endTime)) &&
!urlQuery.has(QueryParams.relativeTime) &&
(currentRoute === ROUTES.ALERT_OVERVIEW ||
currentRoute === ROUTES.ALERT_HISTORY)
) {
updateTimeInterval(defaultRelativeTime);
const urlQuery = getUnstableCurrentSearchParams();
urlQuery.set(QueryParams.relativeTime, defaultRelativeTime);
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
safeNavigate(generatedUrl);
@@ -625,10 +625,9 @@ function DateTimeSelection({
updateTimeInterval(updatedTime, [preStartTime, preEndTime]);
}
const urlQuery = getUnstableCurrentSearchParams();
if (updatedTime !== 'custom') {
urlQuery.delete(QueryParams.startTime);
urlQuery.delete(QueryParams.endTime);
urlQuery.delete('startTime');
urlQuery.delete('endTime');
urlQuery.set(QueryParams.relativeTime, updatedTime);
} else {
const startTime = preStartTime.toString();

View File

@@ -1,34 +0,0 @@
/**
* This was introduced to fix a sync bug between Nuqs and react-router-dom
*
* We are using the wrong adapter for nuqs because the correct one only supports v6/v7,
* and we are at version v5. This causes the nuqs/react-router-dom to be out of sync.
*
* We can revert this commit once we migrate react-router-dom to v6, or once we migrate
* to DateTimeSelectionV3
*/
/**
* This was created to help testing the regression introduced between nuqs/react-router-dom
*/
type SearchParamsGetter = () => URLSearchParams;
let getter: SearchParamsGetter = (): URLSearchParams =>
new URLSearchParams(window.location.search);
/**
* This function will return a fresh instance of URLSearchParams every time it's called.
*
* DO NOT USE IT FOR useEffect/useCallback dependencies, use Nuqs instead.
*/
export function getUnstableCurrentSearchParams(): URLSearchParams {
return getter();
}
// Testing helpers
export function __setSearchParamsGetterForTest(fn: SearchParamsGetter): void {
getter = fn;
}
export function __resetSearchParamsGetter(): void {
getter = (): URLSearchParams => new URLSearchParams(window.location.search);
}

View File

@@ -1,8 +1,7 @@
import { HTMLAttributes } from 'react';
// eslint-disable-next-line no-restricted-imports
import { useDispatch, useSelector } from 'react-redux';
import { TableColumnsType as ColumnsType, TableProps } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { TableColumnsType as ColumnsType, TableProps, Tag } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { ResizeTable } from 'components/ResizeTable';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
@@ -71,7 +70,7 @@ function TraceTable(): JSX.Element {
if (value.length === 0) {
return <Typography>-</Typography>;
}
return <Badge color="sakura">{value}</Badge>;
return <Tag color="magenta">{value}</Tag>;
};
const columns: ColumnsType<TableType> = [

View File

@@ -1,6 +1,6 @@
import { Link } from 'react-router-dom';
import type { TableColumnsType as ColumnsType } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Tag } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { TelemetryFieldKey } from 'api/v5/v5';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
@@ -107,9 +107,9 @@ export const getListColumns = (
) {
return (
<BlockLink to={getTraceLink(item)} openInNewTab={false}>
<Badge data-testid={name} color="sakura" variant="outline">
<Tag data-testid={name} color="magenta">
{value}
</Badge>
</Tag>
</BlockLink>
);
}

View File

@@ -1,8 +1,6 @@
import { useCallback, useMemo, useRef } from 'react';
import { X } from '@signozhq/icons';
import type { SelectProps } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Tooltip } from 'antd';
import { Tag, Tooltip } from 'antd';
import type { BaseOptionType } from 'antd/es/select';
import { Alerts } from 'types/api/alerts/getTriggered';
@@ -85,16 +83,14 @@ function Filter({
const { closable, onClose, label } = props;
return (
<Badge color="sakura" style={{ marginRight: 3 }}>
<Tag
color="magenta"
closable={closable}
onClose={onClose}
style={{ marginRight: 3 }}
>
{label}
{closable && (
<X
size={12}
style={{ cursor: 'pointer', marginInlineStart: 4 }}
onClick={(): void => onClose()}
/>
)}
</Badge>
</Tag>
);
};

View File

@@ -1,4 +1,4 @@
import { Badge } from '@signozhq/ui/badge';
import { Tag } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import { useTimezone } from 'providers/Timezone';
@@ -51,7 +51,7 @@ function ExapandableRow({ allAlerts }: ExapandableRowProps): JSX.Element {
<TableCell minWidth="90px" overflowX="scroll">
<div>
{tags.map((e) => (
<Badge color="vanilla" key={e}>{`${e}:${labels[e]}`}</Badge>
<Tag key={e}>{`${e}:${labels[e]}`}</Tag>
))}
</div>
</TableCell>

View File

@@ -1,6 +1,6 @@
import { useState } from 'react';
import { SquareMinus, SquarePlus } from '@signozhq/icons';
import { Badge } from '@signozhq/ui/badge';
import { Tag } from 'antd';
import { Alerts } from 'types/api/alerts/getTriggered';
import ExapandableRow from './ExapandableRow';
@@ -26,9 +26,9 @@ function TableRowComponent({
</IconContainer>
<>
{tags.map((tag) => (
<Badge color="sakura" key={tag}>
<Tag color="magenta" key={tag}>
{tag}
</Badge>
</Tag>
))}
</>
</StatusContainer>

View File

@@ -59,7 +59,9 @@ function NoFilterTable({
return <Typography>-</Typography>;
}
return <LabelColumn labels={withOutSeverityKeys} value={labels} />;
return (
<LabelColumn labels={withOutSeverityKeys} value={labels} color="magenta" />
);
},
},
{

View File

@@ -1,21 +1,21 @@
import { Badge } from '@signozhq/ui/badge';
import { Tag } from 'antd';
function Severity({ severity }: SeverityProps): JSX.Element {
switch (severity) {
case 'unprocessed': {
return <Badge color="forest">UnProcessed</Badge>;
return <Tag color="green">UnProcessed</Tag>;
}
case 'active': {
return <Badge color="cherry">Firing</Badge>;
return <Tag color="red">Firing</Tag>;
}
case 'suppressed': {
return <Badge color="cherry">Suppressed</Badge>;
return <Tag color="red">Suppressed</Tag>;
}
default: {
return <Badge color="vanilla">Unknown Status</Badge>;
return <Tag color="default">Unknown Status</Tag>;
}
}
}

View File

@@ -1,15 +1,4 @@
import { createBrowserHistory } from 'history';
import { getBasePath } from 'utils/basePath';
const history = createBrowserHistory({ basename: getBasePath() });
let inAppPushCount = 0;
history.listen((_, action) => {
if (action === 'PUSH') {
inAppPushCount += 1;
}
});
export const hasInAppHistory = (): boolean => inAppPushCount > 0;
export default history;
export default createBrowserHistory({ basename: getBasePath() });

View File

@@ -1,8 +0,0 @@
function DashboardPageV2(): JSX.Element {
return (
<div>
<h1>Dashboard Page V2</h1>
</div>
);
}
export default DashboardPageV2;

View File

@@ -1,9 +0,0 @@
function DashboardsListPageV2(): JSX.Element {
return (
<div>
<h1>Dashboards List Page V2</h1>
</div>
);
}
export default DashboardsListPageV2;

View File

@@ -16,7 +16,7 @@ import { LOCALSTORAGE } from 'constants/localStorage';
import ROUTES from 'constants/routes';
import { convertTimeToRelevantUnit } from 'container/TraceDetail/utils';
import dayjs from 'dayjs';
import history, { hasInAppHistory } from 'lib/history';
import history from 'lib/history';
import {
ArrowLeft,
CalendarClock,
@@ -96,7 +96,13 @@ function TraceDetailsHeader({
}, [traceID]);
const handlePreviousBtnClick = useCallback((): void => {
if (hasInAppHistory()) {
const isSpaNavigate =
document.referrer &&
// oxlint-disable-next-line signoz/no-raw-absolute-path
new URL(document.referrer).origin === window.location.origin;
const hasBackHistory = window.history.length > 1;
if (isSpaNavigate && hasBackHistory) {
history.goBack();
} else {
history.push(ROUTES.TRACES_EXPLORER);
@@ -124,7 +130,6 @@ function TraceDetailsHeader({
size="md"
className={styles.backBtn}
onClick={handlePreviousBtnClick}
aria-label="Back"
>
<ArrowLeft size={14} />
</Button>

View File

@@ -1,60 +0,0 @@
import { fireEvent, screen } from '@testing-library/react';
import ROUTES from 'constants/routes';
import { render } from 'tests/test-utils';
import TraceDetailsHeader from '../TraceDetailsHeader';
const mockGoBack = jest.fn();
const mockPush = jest.fn();
const mockHasInAppHistory = jest.fn();
jest.mock('lib/history', () => ({
__esModule: true,
default: {
goBack: (): void => mockGoBack(),
push: (path: string): void => mockPush(path),
replace: jest.fn(),
location: { pathname: '/', search: '' },
listen: (): (() => void) => (): void => undefined,
},
hasInAppHistory: (): boolean => mockHasInAppHistory(),
}));
const baseProps = {
filterMetadata: {
startTime: 0,
endTime: 1,
traceId: 'trace-123',
},
onFilteredSpansChange: jest.fn(),
isDataLoaded: false,
};
describe('TraceDetailsHeader back button', () => {
beforeEach(() => {
mockGoBack.mockClear();
mockPush.mockClear();
mockHasInAppHistory.mockReset();
});
it('calls history.goBack() when there is in-app SPA history', () => {
mockHasInAppHistory.mockReturnValue(true);
render(<TraceDetailsHeader {...baseProps} />);
fireEvent.click(screen.getByRole('button', { name: /back/i }));
expect(mockGoBack).toHaveBeenCalledTimes(1);
expect(mockPush).not.toHaveBeenCalled();
});
it('pushes to the traces explorer route when there is no in-app SPA history', () => {
mockHasInAppHistory.mockReturnValue(false);
render(<TraceDetailsHeader {...baseProps} />);
fireEvent.click(screen.getByRole('button', { name: /back/i }));
expect(mockPush).toHaveBeenCalledTimes(1);
expect(mockPush).toHaveBeenCalledWith(ROUTES.TRACES_EXPLORER);
expect(mockGoBack).not.toHaveBeenCalled();
});
});

View File

@@ -1,5 +1,9 @@
import { Badge } from '@signozhq/ui/badge';
import { Tag } from 'antd';
export default function BetaTag(): JSX.Element {
return <Badge color="robin">Beta</Badge>;
return (
<Tag bordered={false} color="geekblue">
Beta
</Tag>
);
}

View File

@@ -1,7 +1,6 @@
import { useState } from 'react';
import { Color } from '@signozhq/design-tokens';
import { Button } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Button, Tag } from 'antd';
import { TimelineFilter } from 'container/AlertHistory/types';
import { Undo } from '@signozhq/icons';
@@ -66,7 +65,11 @@ function Tabs2({
>
{tab.label}
{tab.isBeta && <Badge color="robin">Beta</Badge>}
{tab.isBeta && (
<Tag bordered={false} color="geekblue">
Beta
</Tag>
)}
</Button>
))}
</Button.Group>

View File

@@ -1,50 +0,0 @@
package impllogspipeline
import (
"context"
"github.com/SigNoz/signoz/pkg/modules/logspipeline"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/valuer"
)
const elementType = "log_pipelines"
type module struct {
sqlStore sqlstore.SQLStore
}
func NewModule(sqlStore sqlstore.SQLStore) logspipeline.Module {
return &module{sqlStore: sqlStore}
}
func (m *module) Collect(ctx context.Context, orgID valuer.UUID) (map[string]any, error) {
subq := m.sqlStore.BunDB().NewSelect().
TableExpr("agent_config_version").
ColumnExpr("MAX(version)").
Where("org_id = ?", orgID).
Where("element_type = ?", elementType)
var result struct {
Total int `bun:"total"`
Enabled int `bun:"enabled_count"`
}
err := m.sqlStore.BunDB().NewSelect().
TableExpr("agent_config_element AS e").
Join("JOIN agent_config_version AS v ON v.id = e.version_id").
Join("JOIN pipelines AS p ON p.id = e.element_id").
Where("v.org_id = ?", orgID).
Where("v.element_type = ?", elementType).
Where("v.version = (?)", subq).
ColumnExpr("COUNT(*) AS total").
ColumnExpr("SUM(CASE WHEN p.enabled THEN 1 ELSE 0 END) AS enabled_count").
Scan(ctx, &result)
if err != nil {
return nil, err
}
return map[string]any{
"logs_pipeline.total.count": result.Total,
"logs_pipeline.enabled.count": result.Enabled,
}, nil
}

View File

@@ -1,7 +0,0 @@
package logspipeline
import "github.com/SigNoz/signoz/pkg/statsreporter"
type Module interface {
statsreporter.StatsCollector
}

View File

@@ -4,6 +4,8 @@ import (
"context"
"fmt"
"log/slog"
"net/url"
"strings"
"sync"
"time"
@@ -23,7 +25,7 @@ type BaseRule struct {
id string
name string
orgID valuer.UUID
source string
externalURL *url.URL
handledRestart bool
// Type of the rule
@@ -138,6 +140,15 @@ func WithRuleStateHistoryModule(module rulestatehistory.Module) RuleOption {
}
}
// WithExternalURL injects the alertmanager external URL
// (SIGNOZ_ALERTMANAGER_SIGNOZ_EXTERNAL__URL) which is used as the host for
// related logs/traces links and the rule generator URL in alert notifications.
func WithExternalURL(externalURL *url.URL) RuleOption {
return func(r *BaseRule) {
r.externalURL = externalURL
}
}
func NewBaseRule(id string, orgID valuer.UUID, p *ruletypes.PostableRule, opts ...RuleOption) (*BaseRule, error) {
threshold, err := p.RuleCondition.Thresholds.GetRuleThreshold()
if err != nil {
@@ -152,7 +163,6 @@ func NewBaseRule(id string, orgID valuer.UUID, p *ruletypes.PostableRule, opts .
id: id,
orgID: orgID,
name: p.AlertName,
source: p.Source,
typ: p.AlertType,
ruleCondition: p.RuleCondition,
evalWindow: p.EvalWindow,
@@ -241,7 +251,18 @@ func (r *BaseRule) Annotations() ruletypes.Labels { return r.annotations }
func (r *BaseRule) PreferredChannels() []string { return r.preferredChannels }
func (r *BaseRule) GeneratorURL() string {
return ruletypes.PrepareRuleGeneratorURL(r.ID(), r.source)
return fmt.Sprintf("%s/alerts/edit?ruleId=%s", r.ExternalURLHost(), r.ID())
}
// ExternalURLHost returns the configured alertmanager external URL
// (SIGNOZ_ALERTMANAGER_SIGNOZ_EXTERNAL__URL), trimmed of any trailing slash.
// It is used as the host portion of rule-related URLs (generator URL and
// related logs/traces links) in alert notifications.
func (r *BaseRule) ExternalURLHost() string {
if r.externalURL == nil {
return ""
}
return strings.TrimRight(r.externalURL.String(), "/")
}
func (r *BaseRule) SelectedQuery(ctx context.Context) string {

Some files were not shown because too many files have changed in this diff Show More