Compare commits

..

22 Commits

Author SHA1 Message Date
Jatinderjit Singh
a2f4ce934d Merge branch 'main' into mute-rules 2026-05-27 17:39:00 +05:30
Jatinderjit Singh
c2419a8687 fix: refresh disabled-since time in DisabledBanner every 60s 2026-05-27 17:29:11 +05:30
Jatinderjit Singh
7c214be7ad fix: hide muted banner when mute expires 2026-05-27 17:15:35 +05:30
Jatinderjit Singh
27a2a0cf21 refactor: return all active mutes 2026-05-27 16:33:16 +05:30
Jatinderjit Singh
6ec3c0b8e0 fix: handle badge and banner for indefinite mutes 2026-05-27 11:29:34 +05:30
Jatinderjit Singh
fef84df331 feat(alerts): return activeMute in rule API responses
Backend computes the active mute window per rule (joining with planned
maintenance schedules) in ListRules and GetRuleByID, so the frontend no
longer needs a separate downtime-schedules fetch to determine mute state.

- Add ActiveMuteInfo struct to Rule (id, name, description,
  effectiveStartTime, effectiveEndTime); computed by findActiveMuteForRule
- Handlers for ListRules/GetRuleByID now fetch MaintenanceStore schedules
  and pass them to NewRule
- Regenerate OpenAPI spec and frontend types (RuletypesActiveMuteInfoDTO)
- useActiveMute: drop useListDowntimeSchedules, read activeMute from
  useGetRuleByID (cache-hit on detail page; one request on list page)
- useMuteAlertRule: also invalidate rule queries after muting so activeMute
  refreshes without a separate schedules refetch
- ListAlert: remove useListDowntimeSchedules + findActiveMuteForRule,
  read record.activeMute.effectiveEndTime directly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 20:56:47 +05:30
Jatinderjit Singh
98813660ed fix(alerts): send endTime as null for "Forever" mute
Previously, picking Forever set endTime to now + 10 years so the
schedule had a real (very distant) end. Send null instead so the
backend treats the mute as truly indefinite. The generated schema
narrows endTime to string, but the API accepts null — cast at the
call site with a comment.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 19:51:03 +05:30
Jatinderjit Singh
cd37fbfa71 fix(alerts): drop focus after closing mute popover via Esc / outside click
Pressing Escape promotes the most recent input to 'keyboard', so the
trigger button (Mute) showed a :focus-visible outline once the popover
closed. Blur the active element when closing via keyboard or outside
click so no leftover focus ring lingers. Tab-driven focus still shows
the indicator as expected.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 19:51:03 +05:30
Jatinderjit Singh
fca2da6b15 fix(alerts): close mute popover on outside click and Escape
Since the Popover uses trigger={[]} (controlled-only), antd no longer
attaches its own outside-click / Escape handlers. Add document-level
mousedown and keydown listeners while the popover is open, deferring
attachment by one tick so the click that opened it isn't counted as
an outside click.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 19:51:03 +05:30
Jatinderjit Singh
4a2907ad35 fix(alerts): show firing state + muted badge separately in rule list
Match the original handoff: Status column keeps showing the actual
rule state (Firing/OK/Pending/Disabled), and a separate inline
'MUTED · <countdown>' badge renders next to the rule name when an
active mute exists. Both signals stay visible.

- Revert Status.tsx to its original simple form (no muteEndTime).
- Extract the muted badge into MutedBadge.tsx and render it inline
  in the Alert Name column.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 19:51:03 +05:30
Jatinderjit Singh
6b04e17f64 fix(alerts): remove 'Muted/Disabled by <user>' from banners
Drop the createdBy/updatedBy attribution from both the muted and
disabled banners. MutedBanner keeps name + manage link;
DisabledBanner keeps the relative time.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 19:51:03 +05:30
Jatinderjit Singh
95864ebe58 fix(alerts): disable Mute pill when alert rule is disabled
Muting a disabled rule wouldn't change observable behavior — fires
aren't recorded, so there's nothing to suppress. Disabling the Mute
pill also brings cursor + hover affordances in line with the
non-interactive Active pill while muted: cursor: not-allowed and no
hover background (the SCSS already guards hover with :not(:disabled)).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 19:51:03 +05:30
Jatinderjit Singh
a0b9713a4f feat(alerts): add hover background to segmented pills
Surface clickable regions more clearly:
- Inactive pill hover: --bg-ink-200 background (dark) / --bg-vanilla-300
  (light mode).
- Active pill hovers darken slightly to their next shade — robin-600,
  amber-600, slate-200 — for tactile feedback without losing the
  state color.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 19:51:03 +05:30
Jatinderjit Singh
bded46ce95 fix(alerts): wrap segmented pill text in span for reliable color
The previous attempt set color on the .pill--active-muted block but
something in the cascade was leaving the bare text node light while
the icon (which had its own explicit rule) went dark. Wrap each pill's
label in <span class="alert-state-segmented__label"> and apply the
muted color to both the icon and label classes — both children now
have explicit color rules that don't depend on inheritance from the
button.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 19:51:03 +05:30
Jatinderjit Singh
b38d2963b5 fix(alerts): make muted segmented pill text legible
The base .alert-state-segmented__pill rule was winning over
.alert-state-segmented__pill--active-muted in the cascade for some
build configurations, leaving the "Mute" label rendered in
var(--bg-vanilla-400) (light gray) on amber. Bump specificity by
self-chaining the class (&.alert-state-segmented__pill--active-muted →
.pill.pill--active-muted) so the dark text color always wins, and
apply the color to the icon explicitly so it tracks the label.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 19:51:03 +05:30
Jatinderjit Singh
406800a2e5 Revert "fix(alerts): move toggle toast to per-call mutate callbacks"
This reverts commit 7dacb99536ca386a9b74d101f2f84fdd39ce6864.
2026-05-25 19:51:03 +05:30
Jatinderjit Singh
b10f45a59a Revert "fix(alerts): update rule cache directly instead of refetching"
This reverts commit adc9e0ff1162d7dcc1f82ccfd54ae3b1c428cc46.
2026-05-25 19:51:03 +05:30
Jatinderjit Singh
f75d166eb6 fix(alerts): update rule cache directly instead of refetching
invalidateGetRuleByID + refetchQueries caused EditRules to render its
<Spinner /> (isRefetching=true), which unmounted the whole form
subtree. The unmount cascade plus follow-up query resolutions (channels,
event, fields/keys) caused the success toast to briefly disappear and
re-enter from its animation again, visible as a flicker.

Patching the rule already returns the updated rule, so write it into
the query cache via setQueryData. The form sees the new state without
remounting, and the toast stays put.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 19:51:03 +05:30
Jatinderjit Singh
81f7694991 fix(alerts): move toggle toast to per-call mutate callbacks
Hook-level onSuccess on useMutation can re-fire when the
MutationObserver re-subscribes during context-driven re-renders (each
context update from setAlertRuleState re-renders all consumers of
useAlertRule, including ActionButtons, which re-instantiates the
mutation observer). Per-call callbacks passed to mutate() in
react-query v3 are guaranteed to run exactly once per mutate() call.

This was visible as the success toast appearing once per follow-up API
response after enable/disable.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 19:51:03 +05:30
Jatinderjit Singh
3858e70a4c fix(alerts): detach mute popover from segmented control anchor
Popover's Trigger wrapper could intercept clicks on the inner buttons,
causing the disable/enable toggle to fire twice (visible as a flickering
success toast). Render the segmented control standalone and anchor the
Popover to a separate invisible span positioned at the bottom-right of
the wrapper.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 19:51:03 +05:30
Jatinderjit Singh
9b87348839 fix(alerts): mute popover only opens via Mute pill, not Active/Disable
The Popover wrapper used trigger="click", which fired on any click in
the anchor (including Active and Disable pills). Switch to trigger={[]}
so the popover is controlled exclusively by the Mute handler.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 19:51:02 +05:30
Jatinderjit Singh
fda747f81e feat(alerts): add mute action wrapping planned downtime
Adds a Mute action on the alert rule page that creates a Planned
Downtime entry scoped to the rule, with a quick-duration popover and
full scheduler drawer.

- AlertStateSegmented control replaces the Active/Disabled switch with a
  three-state pill (Active / Mute / Disable). Active is disabled while
  muted; un-mute happens via the Planned Downtimes page.
- MutePopover offers quick durations (15m, 1h, 4h, 1d, 1w, Forever) plus
  a Name field.
- MuteSchedulerDrawer exposes the full Planned Downtime form for custom
  windows and recurrence.
- MutedBanner / DisabledBanner render an informative banner under the
  header for the corresponding states.
- Alert rules list shows a muted badge with countdown for each rule that
  has an active downtime.
- Lookup is frontend-only via listDowntimeSchedules + ruleId filter,
  with no backend changes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 19:51:02 +05:30
224 changed files with 3992 additions and 3336 deletions

View File

@@ -445,12 +445,7 @@ authz:
##################### Meter Reporter #####################
meterreporter:
# The interval between collection ticks. Minimum 10m, maximum 24h.
# The interval between collection ticks. Minimum 5m.
interval: 6h
# Whether to backfill sealed days from the license creation day.
backfill: true
# Random jitter applied to the first collect and to every subsequent cycle.
# The first collect fires at a random time in [0, jitter); each cycle then takes
# interval - random(0, jitter). Must be between 10m and interval. Defaults to
# min(interval, 2h) when unset.
jitter: 2h

View File

@@ -190,7 +190,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz:
!!merge <<: *db-depend
image: signoz/signoz:v0.126.1
image: signoz/signoz:v0.126.0
ports:
- "8080:8080" # signoz port
# - "6060:6060" # pprof port

View File

@@ -117,7 +117,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz:
!!merge <<: *db-depend
image: signoz/signoz:v0.126.1
image: signoz/signoz:v0.126.0
ports:
- "8080:8080" # signoz port
volumes:

View File

@@ -181,7 +181,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz:
!!merge <<: *db-depend
image: signoz/signoz:${VERSION:-v0.126.1}
image: signoz/signoz:${VERSION:-v0.126.0}
container_name: signoz
ports:
- "8080:8080" # signoz port

View File

@@ -109,7 +109,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz:
!!merge <<: *db-depend
image: signoz/signoz:${VERSION:-v0.126.1}
image: signoz/signoz:${VERSION:-v0.126.0}
container_name: signoz
ports:
- "8080:8080" # signoz port

View File

@@ -5945,6 +5945,22 @@ components:
- start
- end
type: object
RuletypesActiveMute:
properties:
description:
type: string
end:
format: date-time
nullable: true
type: string
id:
type: string
name:
type: string
start:
format: date-time
type: string
type: object
RuletypesAlertCompositeQuery:
properties:
panelType:
@@ -6226,6 +6242,11 @@ components:
additionalProperties:
type: string
type: object
mutes:
items:
$ref: '#/components/schemas/RuletypesActiveMute'
nullable: true
type: array
notificationSettings:
$ref: '#/components/schemas/RuletypesNotificationSettings'
preferredChannels:

View File

@@ -17,15 +17,9 @@ const BANNED_COMPONENTS = {
Typography:
'Use @signozhq/ui/typography Typography instead of antd Typography.',
Switch: 'Use @signozhq/ui/switch Switch instead of antd Switch.',
Dropdown:
'Use @signozhq/ui DropdownMenuSimple (or the composable DropdownMenu primitives) from @signozhq/ui/dropdown-menu instead of antd Dropdown.',
Badge: 'Use @signozhq/ui/badge instead of antd Badge.',
Radio:
'Use @signozhq/ui/radio-group RadioGroup (dots) or @signozhq/ui/toggle-group ToggleGroup (segmented buttons) instead of antd Radio.',
Progress: 'Use @signozhq/ui/progress instead of antd Progress.',
Avatar: 'Use @signozhq/ui/avatar instead of antd Avatar.',
Divider: 'Use @signozhq/ui/divider Divider instead of antd Divider.',
Tag: 'Use @signozhq/ui/badge Bagde instead of antd Tag.',
};
export default {

View File

@@ -7051,6 +7051,31 @@ export interface RulestatehistorytypesGettableRuleStateWindowDTO {
state: RuletypesAlertStateDTO;
}
export interface RuletypesActiveMuteDTO {
/**
* @type string
*/
description?: string;
/**
* @type string,null
* @format date-time
*/
end?: string | null;
/**
* @type string
*/
id?: string;
/**
* @type string
*/
name?: string;
/**
* @type string
* @format date-time
*/
start?: string;
}
export enum RuletypesPanelTypeDTO {
value = 'value',
table = 'table',
@@ -7419,6 +7444,10 @@ export interface RuletypesRuleDTO {
* @type object
*/
labels?: RuletypesRuleDTOLabels;
/**
* @type array,null
*/
mutes?: RuletypesActiveMuteDTO[] | null;
notificationSettings?: RuletypesNotificationSettingsDTO;
/**
* @type array

View File

@@ -1,5 +1,4 @@
import { Breadcrumb } from 'antd';
import { Divider } from '@signozhq/ui/divider';
import { Breadcrumb, Divider } from 'antd';
import styles from './AlertBreadcrumb.module.scss';
import BreadcrumbItem, { BreadcrumbItemConfig } from './BreadcrumbItem';

View File

@@ -1,7 +1,6 @@
import { useState } from 'react';
import { Color, Spacing } from '@signozhq/design-tokens';
import { Drawer } from 'antd';
import { Divider } from '@signozhq/ui/divider';
import { Divider, Drawer } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import logEvent from 'api/common/logEvent';
import { PANEL_TYPES } from 'constants/queryBuilder';

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,
@@ -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,14 +547,10 @@ function ClientSideQBSearch(
return (
<span className="qb-search-bar-tokenised-tags">
<Badge
color="vanilla"
className={tagDetails?.key?.type || ''}
<Tag
closable={!searchValue && closable}
onClose={(e): void => {
e.preventDefault();
onCloseHandler();
}}
onClose={onCloseHandler}
className={tagDetails?.key?.type || ''}
>
<Tooltip title={chipValue}>
<TypographyText
@@ -571,7 +566,7 @@ function ClientSideQBSearch(
{chipValue}
</TypographyText>
</Tooltip>
</Badge>
</Tag>
</span>
);
};

View File

@@ -1,6 +1,5 @@
import { useCallback, useMemo, useState } from 'react';
import { Button, Popover, Tooltip } from 'antd';
import { RadioGroup, RadioGroupItem } from '@signozhq/ui/radio-group';
import { Button, Popover, Radio, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { TelemetryFieldKey } from 'api/v5/v5';
import { useExportRawData } from 'hooks/useDownloadOptionsMenu/useDownloadOptionsMenu';
@@ -64,30 +63,27 @@ export default function DownloadOptionsMenu({
>
<div className="export-format">
<Typography.Text className="title">FORMAT</Typography.Text>
<RadioGroup value={exportFormat} onChange={setExportFormat}>
<RadioGroupItem value={DownloadFormats.CSV}>csv</RadioGroupItem>
<RadioGroupItem value={DownloadFormats.JSONL}>jsonl</RadioGroupItem>
</RadioGroup>
<Radio.Group
value={exportFormat}
onChange={(e): void => setExportFormat(e.target.value)}
>
<Radio value={DownloadFormats.CSV}>csv</Radio>
<Radio value={DownloadFormats.JSONL}>jsonl</Radio>
</Radio.Group>
</div>
<div className="horizontal-line" />
<div className="row-limit">
<Typography.Text className="title">Number of Rows</Typography.Text>
<RadioGroup
value={String(rowLimit)}
onChange={(value): void => setRowLimit(Number(value))}
<Radio.Group
value={rowLimit}
onChange={(e): void => setRowLimit(e.target.value)}
>
<RadioGroupItem value={String(DownloadRowCounts.TEN_K)}>
10k
</RadioGroupItem>
<RadioGroupItem value={String(DownloadRowCounts.THIRTY_K)}>
30k
</RadioGroupItem>
<RadioGroupItem value={String(DownloadRowCounts.FIFTY_K)}>
50k
</RadioGroupItem>
</RadioGroup>
<Radio value={DownloadRowCounts.TEN_K}>10k</Radio>
<Radio value={DownloadRowCounts.THIRTY_K}>30k</Radio>
<Radio value={DownloadRowCounts.FIFTY_K}>50k</Radio>
</Radio.Group>
</div>
{dataSource !== DataSource.TRACES && (
@@ -96,12 +92,13 @@ export default function DownloadOptionsMenu({
<div className="columns-scope">
<Typography.Text className="title">Columns</Typography.Text>
<RadioGroup value={columnsScope} onChange={setColumnsScope}>
<RadioGroupItem value={DownloadColumnsScopes.ALL}>All</RadioGroupItem>
<RadioGroupItem value={DownloadColumnsScopes.SELECTED}>
Selected
</RadioGroupItem>
</RadioGroup>
<Radio.Group
value={columnsScope}
onChange={(e): void => setColumnsScope(e.target.value)}
>
<Radio value={DownloadColumnsScopes.ALL}>All</Radio>
<Radio value={DownloadColumnsScopes.SELECTED}>Selected</Radio>
</Radio.Group>
</div>
</>
)}

View File

@@ -0,0 +1,7 @@
.dropdown-button {
color: var(--l1-foreground);
}
.dropdown-icon {
font-size: 1.2rem;
}

View File

@@ -0,0 +1,51 @@
import { useState } from 'react';
import { Ellipsis } from '@signozhq/icons';
import { Button, Dropdown, MenuProps } from 'antd';
import './DropDown.styles.scss';
function DropDown({
element,
onDropDownItemClick,
}: {
element: JSX.Element[];
onDropDownItemClick?: MenuProps['onClick'];
}): JSX.Element {
const items: MenuProps['items'] = element.map(
(e: JSX.Element, index: number) => ({
label: e,
key: index,
}),
);
const [isDdOpen, setDdOpen] = useState<boolean>(false);
return (
<Dropdown
menu={{
items,
onMouseEnter: (): void => setDdOpen(true),
onMouseLeave: (): void => setDdOpen(false),
onClick: (item): void => onDropDownItemClick?.(item),
}}
open={isDdOpen}
>
<Button
type="link"
className={`dropdown-button`}
onClick={(e): void => {
e.preventDefault();
setDdOpen(true);
}}
>
<Ellipsis className="dropdown-icon" size={16} />
</Button>
</Dropdown>
);
}
DropDown.defaultProps = {
onDropDownItemClick: (): void => {},
};
export default DropDown;

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

@@ -1,7 +1,15 @@
import { useState } from 'react';
import { useCopyToClipboard } from 'react-use';
import { Button, Col, Popover, Row, Select, Space } from 'antd';
import { DropdownMenuSimple, type MenuProps } from '@signozhq/ui/dropdown-menu';
import {
Button,
Col,
Dropdown,
MenuProps,
Popover,
Row,
Select,
Space,
} from 'antd';
import { Typography } from '@signozhq/ui/typography';
import axios from 'axios';
import TextToolTip from 'components/TextToolTip';
@@ -233,9 +241,9 @@ function ExplorerCard({
</Popover>
<Share2 onClick={onCopyUrlHandler} size="md" />
{viewKey && (
<DropdownMenuSimple menu={moreOptionMenu}>
<Button type="text" size="small" icon={<Ellipsis size="md" />} />
</DropdownMenuSimple>
<Dropdown trigger={['click']} menu={moreOptionMenu}>
<Ellipsis size="md" />
</Dropdown>
)}
</Space>
</OffSetCol>

View File

@@ -1,8 +1,7 @@
import { useCallback, useEffect, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { toast } from '@signozhq/ui/sonner';
import { Button, Input } from 'antd';
import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
import { Button, Input, Radio, RadioChangeEvent } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import logEvent from 'api/common/logEvent';
import { handleContactSupport } from 'container/Integrations/utils';
@@ -102,12 +101,13 @@ function FeedbackModal({ onClose }: { onClose: () => void }): JSX.Element {
return (
<div className="feedback-modal-container">
<div className="feedback-modal-header">
<ToggleGroupSimple
type="single"
<Radio.Group
value={activeTab}
defaultValue={activeTab}
optionType="button"
className="feedback-modal-tabs"
onChange={setActiveTab}
items={items}
options={items}
onChange={(e: RadioChangeEvent): void => setActiveTab(e.target.value)}
/>
</div>
<div className="feedback-modal-content">

View File

@@ -158,23 +158,23 @@
font-weight: var(--font-weight-normal);
}
> button {
.tab {
border: 1px solid var(--l1-border);
width: 114px;
}
&::before {
background: var(--l1-border);
}
.tab::before {
background: var(--l1-border);
}
&[data-state='on'] {
background: var(--l3-background);
color: var(--l1-foreground);
border: 1px solid var(--l1-border);
.selected_view {
background: var(--l3-background);
color: var(--l1-foreground);
border: 1px solid var(--l1-border);
}
&::before {
background: var(--l1-border);
}
}
.selected_view::before {
background: var(--l1-border);
}
}

View File

@@ -4,10 +4,9 @@ import { useSelector } from 'react-redux'; // old code, TODO: fix this correctly
import { useCopyToClipboard, useLocation } from 'react-use';
import { Color, Spacing } from '@signozhq/design-tokens';
import { Button } from '@signozhq/ui/button';
import { Drawer, Tooltip } from 'antd';
import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
import { Divider } from '@signozhq/ui/divider';
import { Divider, Drawer, Radio, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import type { RadioChangeEvent } from 'antd/lib';
import cx from 'classnames';
import { LogType } from 'components/Logs/LogStateIndicator/LogStateIndicator';
import QuerySearch from 'components/QueryBuilderV2/QueryV2/QuerySearch/QuerySearch';
@@ -198,8 +197,8 @@ function LogDetailInner({
const LogJsonData = log ? aggregateAttributesResourcesToString(log) : '';
const handleModeChange = (value: string): void => {
setSelectedView(value as VIEWS);
const handleModeChange = (e: RadioChangeEvent): void => {
setSelectedView(e.target.value);
setIsEdit(false);
setIsFilterVisible(false);
};
@@ -453,50 +452,56 @@ function LogDetailInner({
</div>
<div className="tabs-and-search">
<ToggleGroupSimple
type="single"
<Radio.Group
className="views-tabs"
onChange={handleModeChange}
value={selectedView}
items={[
{
value: VIEW_TYPES.OVERVIEW,
label: (
<div className="view-title">
<Table size={14} />
Overview
</div>
),
},
{
value: VIEW_TYPES.JSON,
label: (
<div className="view-title">
<Braces size={14} />
JSON
</div>
),
},
{
value: VIEW_TYPES.CONTEXT,
label: (
<div className="view-title">
<TextSelect size={14} />
Context
</div>
),
},
{
value: VIEW_TYPES.INFRAMETRICS,
label: (
<div className="view-title">
<Histogram size="md" />
Metrics
</div>
),
},
]}
/>
>
<Radio.Button
className={
selectedView === VIEW_TYPES.OVERVIEW ? 'selected_view tab' : 'tab'
}
value={VIEW_TYPES.OVERVIEW}
>
<div className="view-title">
<Table size={14} />
Overview
</div>
</Radio.Button>
<Radio.Button
className={
selectedView === VIEW_TYPES.JSON ? 'selected_view tab' : 'tab'
}
value={VIEW_TYPES.JSON}
>
<div className="view-title">
<Braces size={14} />
JSON
</div>
</Radio.Button>
<Radio.Button
className={
selectedView === VIEW_TYPES.CONTEXT ? 'selected_view tab' : 'tab'
}
value={VIEW_TYPES.CONTEXT}
>
<div className="view-title">
<TextSelect size={14} />
Context
</div>
</Radio.Button>
<Radio.Button
className={
selectedView === VIEW_TYPES.INFRAMETRICS ? 'selected_view tab' : 'tab'
}
value={VIEW_TYPES.INFRAMETRICS}
>
<div className="view-title">
<Histogram size="md" />
Metrics
</div>
</Radio.Button>
</Radio.Group>
<div className="log-detail-drawer__actions">
{selectedView === VIEW_TYPES.CONTEXT && (

View File

@@ -18,8 +18,7 @@ import {
RefreshCw,
} from '@signozhq/icons';
import { Color } from '@signozhq/design-tokens';
import { Button, Select } from 'antd';
import { Checkbox } from '@signozhq/ui/checkbox';
import { Button, Checkbox, Select } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import cx from 'classnames';
import TextToolTip from 'components/TextToolTip/TextToolTip';
@@ -750,7 +749,7 @@ const CustomMultiSelect: React.FC<CustomMultiSelectProps> = ({
tabIndex={isActive ? 0 : -1}
>
<Checkbox
value={isSelected}
checked={isSelected}
className="option-checkbox"
onClick={(e): void => {
e.stopPropagation();
@@ -1585,7 +1584,7 @@ const CustomMultiSelect: React.FC<CustomMultiSelectProps> = ({
}}
>
<div style={{ display: 'flex', alignItems: 'center', width: '100%' }}>
<Checkbox value={allOptionsSelected} className="option-checkbox">
<Checkbox checked={allOptionsSelected} className="option-checkbox">
<div className="option-content">
<div className="all-option-text">ALL</div>
</div>

View File

@@ -2,13 +2,6 @@
.query-add-ons {
width: 100%;
.add-on-tab-title {
display: flex;
align-items: center;
justify-content: center;
gap: var(--margin-2);
}
}
.add-ons-list {
@@ -32,7 +25,7 @@
color: var(--l2-foreground);
}
> button {
.tab {
border: 1px solid var(--l1-border);
border-left: none;
min-width: 120px;
@@ -42,21 +35,21 @@
&:first-child {
border-left: 1px solid var(--l1-border);
}
}
&::before {
background: var(--l1-border);
}
.tab::before {
background: var(--l1-border);
}
&[data-state='on'] {
color: var(--text-robin-500);
border: 1px solid var(--l1-border);
.selected-view {
color: var(--text-robin-500);
border: 1px solid var(--l1-border);
display: none;
display: none;
}
&::before {
background: var(--l1-border);
}
}
.selected-view::before {
background: var(--l1-border);
}
}

View File

@@ -1,6 +1,5 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { Button, Tooltip } from 'antd';
import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
import { Button, Radio, RadioChangeEvent, Tooltip } from 'antd';
import InputWithLabel from 'components/InputWithLabel/InputWithLabel';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { GroupByFilter } from 'container/QueryBuilder/filters/GroupByFilter/GroupByFilter';
@@ -251,7 +250,8 @@ function QueryAddOns({
);
}, [panelType, isListViewPanel, query, showReduceTo]);
const handleOptionClick = (clickedAddOn: AddOn): void => {
const handleOptionClick = (e: RadioChangeEvent): void => {
const clickedAddOn = e.target.value as AddOn;
const isAlreadySelected = selectedViews.some(
(view) => view.key === clickedAddOn.key,
);
@@ -515,27 +515,15 @@ function QueryAddOns({
</div>
)}
<ToggleGroupSimple
type="multiple"
className="add-ons-tabs"
value={selectedViews.map((view) => view.key)}
onChange={(newKeys: string[]): void => {
const oldKeys = selectedViews.map((view) => view.key);
const toggledKey =
newKeys.find((k) => !oldKeys.includes(k)) ??
oldKeys.find((k) => !newKeys.includes(k));
if (!toggledKey) {
return;
}
const clickedAddOn = addOns.find((a) => a.key === toggledKey);
if (clickedAddOn) {
handleOptionClick(clickedAddOn);
}
}}
items={addOns.map((addOn) => ({
value: addOn.key,
label: (
<div className="add-ons-list">
<Radio.Group
className="add-ons-tabs"
onChange={handleOptionClick}
value={selectedViews}
>
{addOns.map((addOn) => (
<Tooltip
key={addOn.key}
title={
<TooltipContent
label={addOn.label}
@@ -546,17 +534,26 @@ function QueryAddOns({
placement="top"
mouseEnterDelay={0.5}
>
<span
className="add-on-tab-title"
data-testid={`query-add-on-${addOn.key}`}
<Radio.Button
className={
selectedViews.find((view) => view.key === addOn.key)
? 'selected-view tab'
: 'tab'
}
value={addOn}
>
{addOn.icon}
{addOn.label}
</span>
<div
className="add-on-tab-title"
data-testid={`query-add-on-${addOn.key}`}
>
{addOn.icon}
{addOn.label}
</div>
</Radio.Button>
</Tooltip>
),
}))}
/>
))}
</Radio.Group>
</div>
</div>
);
}

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

@@ -6,7 +6,7 @@ import {
useMemo,
useState,
} from 'react';
import { DropdownMenuSimple } from '@signozhq/ui/dropdown-menu';
import { Dropdown } from 'antd';
import cx from 'classnames';
import { ENTITY_VERSION_V4, ENTITY_VERSION_V5 } from 'constants/app';
import { PANEL_TYPES } from 'constants/queryBuilder';
@@ -195,7 +195,7 @@ export const QueryV2 = forwardRef(function QueryV2(
)}
{isMultiQueryAllowed && (
<DropdownMenuSimple
<Dropdown
className="query-actions-dropdown"
menu={{
items: [
@@ -217,10 +217,10 @@ export const QueryV2 = forwardRef(function QueryV2(
: []),
],
}}
align="end"
placement="bottomRight"
>
<Ellipsis size={16} />
</DropdownMenuSimple>
</Dropdown>
)}
</div>
</div>

View File

@@ -55,7 +55,6 @@
display: flex;
align-items: center;
gap: 8px;
min-height: 24px;
.checkbox-value-section {
display: flex;

View File

@@ -1,7 +1,6 @@
/* eslint-disable sonarjs/no-identical-functions */
import { Fragment, useMemo, useState } from 'react';
import { Button, Input, Skeleton } from 'antd';
import { Checkbox } from '@signozhq/ui/checkbox';
import { Button, Checkbox, Input, Skeleton } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import cx from 'classnames';
import { removeKeysFromExpression } from 'components/QueryBuilderV2/utils';
@@ -635,12 +634,10 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
)}
<div className="value">
<Checkbox
onChange={(checked): void =>
onChange(value, checked === true, false)
}
value={currentFilterState[value]}
onChange={(e): void => onChange(value, e.target.checked, false)}
checked={currentFilterState[value]}
disabled={isFilterDisabled}
className="check-box"
rootClassName="check-box"
/>
<div

View File

@@ -4,14 +4,14 @@ import type {
TableColumnsType as ColumnsType,
TableColumnType as ColumnType,
} from 'antd';
import { Button, Flex } from 'antd';
import { DropdownMenuSimple, type MenuItem } from '@signozhq/ui/dropdown-menu';
import { Button, Dropdown, Flex, MenuProps } from 'antd';
import { Switch } from '@signozhq/ui/switch';
import logEvent from 'api/common/logEvent';
import LaunchChatSupport from 'components/LaunchChatSupport/LaunchChatSupport';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import { SlidersHorizontal } from '@signozhq/icons';
import { popupContainer } from 'utils/selectPopupContainer';
import ResizeTable from './ResizeTable';
import { DynamicColumnTableProps } from './types';
@@ -84,9 +84,8 @@ function DynamicColumnTable({
);
};
const items: MenuItem[] =
const items: MenuProps['items'] =
dynamicColumns?.map((column, index) => ({
key: String(index),
label: (
<div
className="dynamicColumnsTable-items"
@@ -100,6 +99,8 @@ function DynamicColumnTable({
/>
</div>
),
key: index,
type: 'checkbox',
})) || [];
// Get current page from URL or default to 1
@@ -128,14 +129,18 @@ function DynamicColumnTable({
<Flex justify="flex-end" align="center" gap={8}>
{facingIssueBtn && <LaunchChatSupport {...facingIssueBtn} />}
{dynamicColumns && (
<DropdownMenuSimple menu={{ items }}>
<Dropdown
getPopupContainer={popupContainer}
menu={{ items }}
trigger={['click']}
>
<Button
className="dynamicColumnTable-button filter-btn"
size="middle"
icon={<SlidersHorizontal size={14} />}
data-testid="additional-filters-button"
/>
</DropdownMenuSimple>
</Dropdown>
)}
</Flex>

View File

@@ -1,6 +1,5 @@
import { CircleAlert, RefreshCw } from '@signozhq/icons';
import { Select } from 'antd';
import { Checkbox } from '@signozhq/ui/checkbox';
import { Checkbox, Select } from 'antd';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import { useListRoles } from 'api/generated/services/role';
import type { AuthtypesRoleDTO } from 'api/generated/services/sigNoz.schemas';
@@ -147,11 +146,12 @@ function RolesSelect(props: RolesSelectProps): JSX.Element {
options={options}
optionFilterProp="label"
optionRender={(option): JSX.Element => (
<div style={{ pointerEvents: 'none' }}>
<Checkbox value={value.includes(option.value as string)}>
{option.label}
</Checkbox>
</div>
<Checkbox
checked={value.includes(option.value as string)}
style={{ pointerEvents: 'none' }}
>
{option.label}
</Checkbox>
)}
getPopupContainer={getPopupContainer}
disabled={disabled}

View File

@@ -2,7 +2,7 @@ import type { Control, UseFormRegister } from 'react-hook-form';
import { Controller } from 'react-hook-form';
import { Button } from '@signozhq/ui/button';
import { Input } from '@signozhq/ui/input';
import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
import { ToggleGroup, ToggleGroupItem } from '@signozhq/ui/toggle-group';
import { DatePicker } from 'antd';
import AuthZTooltip from 'components/AuthZTooltip/AuthZTooltip';
import {
@@ -60,21 +60,30 @@ function KeyFormPhase({
name="expiryMode"
control={control}
render={({ field }): JSX.Element => (
<ToggleGroupSimple
<ToggleGroup
type="single"
value={field.value}
onChange={(val: string): void => {
onChange={(val): void => {
if (val) {
field.onChange(val);
}
}}
size="sm"
className="add-key-modal__expiry-toggle"
items={[
{ value: ExpiryMode.NONE, label: 'No Expiration' },
{ value: ExpiryMode.DATE, label: 'Set Expiration Date' },
]}
/>
>
<ToggleGroupItem
value={ExpiryMode.NONE}
className="add-key-modal__expiry-toggle-btn"
>
No Expiration
</ToggleGroupItem>
<ToggleGroupItem
value={ExpiryMode.DATE}
className="add-key-modal__expiry-toggle-btn"
>
Set Expiration Date
</ToggleGroupItem>
</ToggleGroup>
)}
/>
</div>

View File

@@ -4,7 +4,7 @@ import { LockKeyhole, Trash2, X } from '@signozhq/icons';
import { Badge } from '@signozhq/ui/badge';
import { Button } from '@signozhq/ui/button';
import { Input } from '@signozhq/ui/input';
import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
import { ToggleGroup, ToggleGroupItem } from '@signozhq/ui/toggle-group';
import { DatePicker } from 'antd';
import type { ServiceaccounttypesGettableFactorAPIKeyDTO } from 'api/generated/services/sigNoz.schemas';
import AuthZTooltip from 'components/AuthZTooltip/AuthZTooltip';
@@ -101,22 +101,31 @@ function EditKeyForm({
name="expiryMode"
control={control}
render={({ field }): JSX.Element => (
<ToggleGroupSimple
<ToggleGroup
type="single"
value={field.value}
onChange={(val: string): void => {
onChange={(val): void => {
if (val && canUpdate) {
field.onChange(val);
}
}}
size="sm"
disabled={!canUpdate}
className="edit-key-modal__expiry-toggle"
items={[
{ value: ExpiryMode.NONE, label: 'No Expiration' },
{ value: ExpiryMode.DATE, label: 'Set Expiration Date' },
]}
/>
>
<ToggleGroupItem
value={ExpiryMode.NONE}
disabled={!canUpdate}
className="edit-key-modal__expiry-toggle-btn"
>
No Expiration
</ToggleGroupItem>
<ToggleGroupItem
value={ExpiryMode.DATE}
disabled={!canUpdate}
className="edit-key-modal__expiry-toggle-btn"
>
Set Expiration Date
</ToggleGroupItem>
</ToggleGroup>
)}
/>
</div>

View File

@@ -4,7 +4,7 @@ import { Key, LayoutGrid, Plus, Trash2, X } from '@signozhq/icons';
import { Button } from '@signozhq/ui/button';
import { DrawerWrapper } from '@signozhq/ui/drawer';
import { toast } from '@signozhq/ui/sonner';
import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
import { ToggleGroup, ToggleGroupItem } from '@signozhq/ui/toggle-group';
import { Pagination, Skeleton } from 'antd';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import {
@@ -395,11 +395,11 @@ function ServiceAccountDrawer({
const drawerContent = (
<div className="sa-drawer__layout">
<div className="sa-drawer__tabs">
<ToggleGroupSimple
<ToggleGroup
type="single"
value={activeTab}
size="sm"
onChange={(val: string): void => {
onChange={(val): void => {
if (val) {
void setActiveTab(val as ServiceAccountDrawerTab);
if (val !== ServiceAccountDrawerTab.Keys) {
@@ -409,30 +409,25 @@ function ServiceAccountDrawer({
}
}}
className="sa-drawer__tab-group"
items={[
{
value: ServiceAccountDrawerTab.Overview,
label: (
<>
<LayoutGrid size={14} />
Overview
</>
),
},
{
value: ServiceAccountDrawerTab.Keys,
label: (
<>
<Key size={14} />
Keys
{keys.length > 0 && (
<span className="sa-drawer__tab-count">{keys.length}</span>
)}
</>
),
},
]}
/>
>
<ToggleGroupItem
value={ServiceAccountDrawerTab.Overview}
className="sa-drawer__tab"
>
<LayoutGrid size={14} />
Overview
</ToggleGroupItem>
<ToggleGroupItem
value={ServiceAccountDrawerTab.Keys}
className="sa-drawer__tab"
>
<Key size={14} />
Keys
{keys.length > 0 && (
<span className="sa-drawer__tab-count">{keys.length}</span>
)}
</ToggleGroupItem>
</ToggleGroup>
{activeTab === ServiceAccountDrawerTab.Keys && (
<AuthZTooltip
checks={[

View File

@@ -1,6 +1,12 @@
.signoz-radio-group {
.signoz-radio-group.ant-radio-group {
color: var(--l2-foreground);
&.ant-radio-group-disabled {
opacity: 0.5;
pointer-events: none;
cursor: not-allowed;
}
.view-title {
display: flex;
gap: var(--margin-2);
@@ -24,41 +30,43 @@
}
}
> button {
.tab {
border: 1px solid var(--l1-border);
background: var(--l1-background);
&:hover {
color: var(--l1-foreground);
}
&::before {
background: var(--l1-border);
}
}
&[data-state='on'] {
&,
&:hover {
background: var(--l3-background);
color: var(--l1-foreground);
border: 1px solid var(--l1-border);
}
&::before {
background: var(--l3-background);
border-left: 1px solid var(--l1-border);
}
.selected_view {
&,
&:hover {
background: var(--l3-background);
color: var(--l1-foreground);
border: 1px solid var(--l1-border);
}
&::before {
background: var(--l3-background);
border-left: 1px solid var(--l1-border);
}
}
&[disabled],
&[data-disabled] {
&.ant-radio-group-disabled {
.tab,
.selected_view {
background: var(--l2-background) !important;
border-color: var(--l1-border) !important;
color: var(--l1-foreground) !important;
}
&:hover {
background: var(--l2-background) !important;
border-color: var(--l1-border) !important;
color: var(--l1-foreground) !important;
}
.tab:hover,
.selected_view:hover {
background: var(--l2-background) !important;
border-color: var(--l1-border) !important;
color: var(--l1-foreground) !important;
}
}
}

View File

@@ -1,4 +1,4 @@
import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
import { Radio, RadioChangeEvent } from 'antd';
import './SignozRadioGroup.styles.scss';
@@ -11,7 +11,7 @@ interface Option {
interface SignozRadioGroupProps {
value: string;
options: Option[];
onChange: (value: string) => void;
onChange: (e: RadioChangeEvent) => void;
className?: string;
disabled?: boolean;
}
@@ -24,22 +24,26 @@ function SignozRadioGroup({
disabled = false,
}: SignozRadioGroupProps): JSX.Element {
return (
<ToggleGroupSimple
type="single"
<Radio.Group
value={value}
buttonStyle="solid"
className={`signoz-radio-group ${className}`}
onChange={onChange}
disabled={disabled}
items={options.map((option) => ({
value: option.value,
label: (
>
{options.map((option) => (
<Radio.Button
key={option.value}
value={option.value}
className={value === option.value ? 'selected_view tab' : 'tab'}
>
<div className="view-title-container">
{option.icon && <div className="icon-container">{option.icon}</div>}
{option.label}
</div>
),
}))}
/>
</Radio.Button>
))}
</Radio.Group>
);
}

View File

@@ -4,10 +4,13 @@ import {
ButtonProps,
Col,
ColProps,
Divider,
DividerProps,
Row,
RowProps,
Space,
SpaceProps,
TabsProps,
} from 'antd';
import {
Typography,
@@ -31,11 +34,21 @@ const StyledRow = styled(Row)<TStyledRow>`
${styledClass}
`;
type TStyledDivider = DividerProps & IStyledClass;
const StyledDivider = styled(Divider)<TStyledDivider>`
${styledClass}
`;
type TStyledSpace = SpaceProps & IStyledClass;
const StyledSpace = styled(Space)<TStyledSpace>`
${styledClass}
`;
type TStyledTabs = TabsProps & IStyledClass;
const StyledTabs = styled(Divider)<TStyledTabs>`
${styledClass}
`;
type TStyledButton = ButtonProps & IStyledClass;
const StyledButton = styled(Button)<TStyledButton>`
${styledClass}
@@ -66,7 +79,9 @@ export {
StyledButton,
StyledCol,
StyledDiv,
StyledDivider,
StyledRow,
StyledSpace,
StyledTabs,
StyledTypography,
};

View File

@@ -1,13 +1,12 @@
import { Popover } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Popover, Tag } from 'antd';
import { LabelColumnProps } from './TableRenderer.types';
import BadgeWithTooltip from './BadgeWithTooltip';
import TagWithToolTip from './TagWithToolTip';
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 => (
<BadgeWithTooltip 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,34 +1,36 @@
import { Tooltip } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Tag, Tooltip } from 'antd';
import { getLabelRenderingValue } from './utils';
function BadgeWithTooltip({
function TagWithToolTip({
label,
value,
}: BadgeWithTooltipProps): JSX.Element {
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>
);
}
type BadgeWithTooltipProps = {
type TagWithToolTipProps = {
label: string;
color?: string;
value?: {
[key: string]: string;
};
};
BadgeWithTooltip.defaultProps = {
TagWithToolTip.defaultProps = {
value: undefined,
color: undefined,
};
export default BadgeWithTooltip;
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,12 +1,11 @@
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 './Badges.styles.scss';
import './Tags.styles.scss';
function Badges({ tags, setTags }: AddTagsProps): JSX.Element {
function Tags({ tags, setTags }: AddTagsProps): JSX.Element {
const [inputValue, setInputValue] = useState<string>('');
const [inputVisible, setInputVisible] = useState<boolean>(false);
@@ -47,18 +46,14 @@ function Badges({ tags, setTags }: AddTagsProps): JSX.Element {
return (
<div className="tags-container">
{tags.map<React.ReactNode>((tag) => (
<Badge
<Tag
key={tag}
color="vanilla"
style={{ userSelect: 'none' }}
closable
onClose={(e): void => {
e.preventDefault();
handleClose(tag);
}}
style={{ userSelect: 'none' }}
onClose={(): void => handleClose(tag)}
>
{tag}
</Badge>
<span>{tag}</span>
</Tag>
))}
{inputVisible && (
@@ -118,4 +113,4 @@ interface AddTagsProps {
setTags: Dispatch<SetStateAction<string[]>>;
}
export default Badges;
export default Tags;

View File

@@ -1,7 +1,6 @@
import { Dispatch, SetStateAction, useCallback, useMemo } from 'react';
import { ChevronDown, Globe } from '@signozhq/icons';
import { DropdownMenuSimple } from '@signozhq/ui/dropdown-menu';
import { Button } from 'antd';
import { Button, Dropdown } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import TimeItems, {
timePreferance,
@@ -28,17 +27,20 @@ function TimePreference({
const menu = useMemo(
() => ({
items: menuItems.map((item) => ({
...item,
onClick: timeMenuItemOnChangeHandler,
})),
items: menuItems,
onClick: timeMenuItemOnChangeHandler,
}),
[timeMenuItemOnChangeHandler],
);
return (
<DropdownMenuSimple menu={menu} className="time-selection-menu">
<Button className="time-selection-target">
<Dropdown
menu={menu}
rootClassName="time-selection-menu"
className="time-selection-target"
trigger={['click']}
>
<Button>
<div className="button-selected-text">
<Globe size={14} />
<Typography.Text className="selected-value">
@@ -47,7 +49,7 @@ function TimePreference({
</div>
<ChevronDown size="md" />
</Button>
</DropdownMenuSimple>
</Dropdown>
);
}

View File

@@ -10,7 +10,7 @@ import {
DialogSubtitle,
DialogTitle,
} from '@signozhq/ui/dialog';
import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
import { ToggleGroup, ToggleGroupItem } from '@signozhq/ui/toggle-group';
import { TooltipSimple } from '@signozhq/ui/tooltip';
import type {
ApprovalEventDTO,
@@ -132,43 +132,45 @@ export default function ApprovalCard({
<div className={styles.diffModalBody}>
<p className={styles.diffModalSummary}>{approval.summary}</p>
<div className={styles.diffToolbarRow}>
<ToggleGroupSimple
<ToggleGroup
type="single"
size="sm"
value={viewMode}
onChange={(next: string): void => {
// Radix `single` group can emit '' when the active item
// is clicked again — preserve the current mode.
onChange={(next): void => {
// Radix `single` group can emit '' when the active item is clicked again.
if (next === 'split' || next === 'unified') {
setViewMode(next);
}
}}
items={[
{
value: 'split',
'aria-label': 'Split view',
label: <Columns2 size={12} />,
},
{
value: 'unified',
'aria-label': 'Unified view',
label: <List size={12} />,
},
]}
/>
<ToggleGroupSimple
>
<TooltipSimple title="Split view">
<ToggleGroupItem value="split" aria-label="Split view">
<Columns2 size={12} />
</ToggleGroupItem>
</TooltipSimple>
<TooltipSimple title="Unified view">
<ToggleGroupItem value="unified" aria-label="Unified view">
<List size={12} />
</ToggleGroupItem>
</TooltipSimple>
</ToggleGroup>
<ToggleGroup
type="multiple"
size="sm"
value={wrapText ? ['wrap'] : []}
onChange={(next: string[]): void => setWrapText(next.includes('wrap'))}
items={[
{
value: 'wrap',
'aria-label': wrapText ? 'Disable text wrap' : 'Wrap long lines',
label: <WrapText size={12} />,
},
]}
/>
onChange={(next): void => setWrapText(next.includes('wrap'))}
>
<TooltipSimple
title={wrapText ? 'Disable text wrap' : 'Wrap long lines'}
>
<ToggleGroupItem
value="wrap"
aria-label={wrapText ? 'Disable text wrap' : 'Wrap long lines'}
>
<WrapText size={12} />
</ToggleGroupItem>
</TooltipSimple>
</ToggleGroup>
</div>
{approval.diff && (
<DiffView

View File

@@ -2,8 +2,7 @@ import { useState } from 'react';
import cx from 'classnames';
import { Button } from '@signozhq/ui/button';
import logEvent from 'api/common/logEvent';
import { Checkbox } from '@signozhq/ui/checkbox';
import { RadioGroup, RadioGroupItem } from '@signozhq/ui/radio-group';
import { Checkbox, Radio } from 'antd';
import { AIAssistantEvents } from '../../../events';
import { useAIAssistantAnalyticsContext } from '../../../hooks/useAIAssistantAnalyticsContext';
@@ -82,43 +81,31 @@ export default function InteractiveQuestion({
{question && <p className={blockStyles.title}>{question}</p>}
{type === 'radio' ? (
<RadioGroup
<Radio.Group
className={styles.options}
onChange={(value): void => {
setSelected([value]);
handleSubmit([value]);
onChange={(e): void => {
setSelected([e.target.value]);
handleSubmit([e.target.value]);
}}
>
{normalized.map((opt) => (
<RadioGroupItem
key={opt.value}
value={opt.value}
className={styles.option}
>
<Radio key={opt.value} value={opt.value} className={styles.option}>
{opt.label}
</RadioGroupItem>
</Radio>
))}
</RadioGroup>
</Radio.Group>
) : (
<>
<div className={cx(styles.options, styles.checkbox)}>
<Checkbox.Group
className={cx(styles.options, styles.checkbox)}
onChange={(vals): void => setSelected(vals as string[])}
>
{normalized.map((opt) => (
<Checkbox
key={opt.value}
value={selected.includes(opt.value)}
onChange={(checked): void => {
setSelected((prev) =>
checked === true
? [...prev, opt.value]
: prev.filter((v) => v !== opt.value),
);
}}
className={styles.option}
>
<Checkbox key={opt.value} value={opt.value} className={styles.option}>
{opt.label}
</Checkbox>
))}
</div>
</Checkbox.Group>
<Button
variant="solid"
size="sm"

View File

@@ -1,6 +1,5 @@
import { useEffect, useRef, useState } from 'react';
import { Input } from 'antd';
import { Checkbox } from '@signozhq/ui/checkbox';
import { Checkbox, Input } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { useIsDarkMode } from 'hooks/useDarkMode';
import useDebouncedFn from 'hooks/useDebouncedFunction';
@@ -321,8 +320,10 @@ function AnomalyAlertEvaluationView({
{filteredSeriesKeys.length > 0 && (
<Checkbox
className="anomaly-alert-evaluation-view-series-list-item"
type="checkbox"
name="series"
value={selectedSeries === null}
value="all"
checked={selectedSeries === null}
onChange={(): void => handleSeriesChange(null)}
>
Show All
@@ -334,8 +335,10 @@ function AnomalyAlertEvaluationView({
<Checkbox
className="anomaly-alert-evaluation-view-series-list-item"
key={seriesKey}
type="checkbox"
name="series"
value={selectedSeries === seriesKey}
value={seriesKey}
checked={selectedSeries === seriesKey}
onChange={(): void => handleSeriesChange(seriesKey)}
>
<div

View File

@@ -161,6 +161,41 @@
justify-content: space-between;
align-items: center;
border-bottom: 1px solid var(--l1-border);
.views-tabs {
color: var(--l2-foreground);
.view-title {
display: flex;
gap: var(--margin-2);
align-items: center;
justify-content: center;
font-size: 14px;
font-style: normal;
font-weight: var(--font-weight-normal);
}
.tab {
padding: 0 32px;
background: var(--l3-background);
border: 1px solid var(--l1-border);
width: auto;
}
.tab::before {
background: var(--l1-border);
}
.selected_view {
background: none;
border: 1px solid var(--l1-border);
border-bottom: none;
}
.selected_view::before {
background: var(--l1-border);
}
}
}
}
@@ -664,6 +699,40 @@
left: 50%;
transform: translate(-50%, -50%);
}
.views-tabs {
color: var(--l2-foreground);
.ant-btn {
box-shadow: none;
position: relative;
}
.tab {
border: 1px solid var(--l1-border);
width: 114px;
z-index: 1;
}
.tab:hover {
z-index: 3;
}
.tab::before {
background: var(--l2-background);
}
.selected_view {
background: var(--l1-border);
color: var(--l1-foreground);
border: 1px solid var(--l1-border);
z-index: 2;
}
.selected_view::before {
background: var(--l2-background);
}
}
}
.top-services-content {

View File

@@ -2,10 +2,9 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
// eslint-disable-next-line no-restricted-imports
import { useSelector } from 'react-redux';
import { Spacing } from '@signozhq/design-tokens';
import { Button, Drawer } from 'antd';
import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
import { Divider } from '@signozhq/ui/divider';
import { Button, Divider, Drawer, Radio } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import type { RadioChangeEvent } from 'antd/lib';
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
import {
CustomTimeType,
@@ -56,9 +55,9 @@ function DomainDetails({
const [initialFiltersEndPointStats, setInitialFiltersEndPointStats] =
useState<IBuilderQuery['filters']>(domainListFilters);
const handleTabChange = (value: string): void => {
setSelectedView(value as VIEWS);
setParams({ selectedView: value });
const handleTabChange = (e: RadioChangeEvent): void => {
setSelectedView(e.target.value);
setParams({ selectedView: e.target.value });
};
const handleEndPointChange = (name: string): void => {
@@ -225,17 +224,38 @@ function DomainDetails({
timeRange={modalTimeRange}
/>
<div className="views-tabs-container">
<ToggleGroupSimple
type="single"
<Radio.Group
className="views-tabs"
onChange={handleTabChange}
value={selectedView}
size="lg"
items={[
{ value: VIEW_TYPES.ALL_ENDPOINTS, label: 'All Endpoints' },
{ value: VIEW_TYPES.ENDPOINT_STATS, label: 'Endpoint(s) Stats' },
{ value: VIEW_TYPES.TOP_ERRORS, label: 'Top 10 Errors' },
]}
/>
>
<Radio.Button
className={
selectedView === VIEW_TYPES.ALL_ENDPOINTS ? 'selected_view tab' : 'tab'
}
value={VIEW_TYPES.ALL_ENDPOINTS}
>
<div className="view-title">All Endpoints</div>
</Radio.Button>
<Radio.Button
className={
selectedView === VIEW_TYPES.ENDPOINT_STATS
? 'tab selected_view'
: 'tab'
}
value={VIEW_TYPES.ENDPOINT_STATS}
>
<div className="view-title">Endpoint(s) Stats</div>
</Radio.Button>
<Radio.Button
className={
selectedView === VIEW_TYPES.TOP_ERRORS ? 'tab selected_view' : 'tab'
}
value={VIEW_TYPES.TOP_ERRORS}
>
<div className="view-title">Top 10 Errors</div>
</Radio.Button>
</Radio.Group>
</div>
{selectedView === VIEW_TYPES.ALL_ENDPOINTS && (
<AllEndPoints

View File

@@ -21,17 +21,14 @@ import { useResizeObserver } from 'hooks/useDimensions';
import { useNotifications } from 'hooks/useNotifications';
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
import { LegendPosition } from 'lib/uPlotV2/components/types';
import { getStartAndEndTimesInMilliseconds } from 'pages/MessagingQueues/MessagingQueuesUtils';
import { useTimezone } from 'providers/Timezone';
import { SuccessResponse } from 'types/api';
import { Widgets } from 'types/api/dashboard/getAll';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import ErrorState from './ErrorState';
import {
getStepIntervalForQuery,
getTracesTimeRangeFromStepInterval,
prepareStatusCodeBarChartsConfig,
} from './utils';
import { prepareStatusCodeBarChartsConfig } from './utils';
function StatusCodeBarCharts({
endPointStatusCodeBarChartsDataQuery,
@@ -138,18 +135,6 @@ function StatusCodeBarCharts({
[domainName, filters],
);
const activeApiResponse = useMemo(
() =>
currentWidgetInfoIndex === 0
? formattedEndPointStatusCodeBarChartsDataPayload
: formattedEndPointStatusCodeLatencyBarChartsDataPayload,
[
currentWidgetInfoIndex,
formattedEndPointStatusCodeBarChartsDataPayload,
formattedEndPointStatusCodeLatencyBarChartsDataPayload,
],
);
const graphClickHandler = useCallback(
(
xValue: number,
@@ -159,14 +144,11 @@ function StatusCodeBarCharts({
metric?: { [key: string]: string },
queryData?: { queryName: string; inFocusOrNot: boolean },
): void => {
const TWO_AND_HALF_MINUTES_IN_MILLISECONDS = 2.5 * 60 * 1000; // 150,000 milliseconds
const customFilters = getCustomFiltersForBarChart(metric);
const stepInterval = getStepIntervalForQuery(
activeApiResponse,
queryData?.queryName,
);
const { start, end } = getTracesTimeRangeFromStepInterval(
const { start, end } = getStartAndEndTimesInMilliseconds(
xValue,
stepInterval,
TWO_AND_HALF_MINUTES_IN_MILLISECONDS,
);
handleGraphClick({
@@ -189,7 +171,6 @@ function StatusCodeBarCharts({
});
},
[
activeApiResponse,
widget,
navigateToExplorerPages,
navigateToExplorer,

View File

@@ -1,68 +0,0 @@
import {
getMinStepIntervalFromApiResponse,
getStepIntervalForQuery,
getTracesTimeRangeFromStepInterval,
} from '../utils';
describe('StatusCodeBarCharts utils', () => {
describe('getTracesTimeRangeFromStepInterval', () => {
const xValue = 1609459200; // seconds
it('keeps start at click time with a minimum 5 minute end range', () => {
const { start, end } = getTracesTimeRangeFromStepInterval(xValue, 60);
expect(start).toBe(xValue * 1000);
expect(end - start).toBe(5 * 60 * 1000);
expect(end).toBe(xValue * 1000 + 5 * 60 * 1000);
});
it('extends end when step interval is larger than 5 minutes', () => {
const stepInterval = 600; // 10 minutes
const { start, end } = getTracesTimeRangeFromStepInterval(
xValue,
stepInterval,
);
expect(start).toBe(xValue * 1000);
expect(end - start).toBe(10 * 60 * 1000);
expect(end).toBe(xValue * 1000 + 10 * 60 * 1000);
});
});
describe('getMinStepIntervalFromApiResponse', () => {
it('returns 60 when step intervals are missing', () => {
expect(getMinStepIntervalFromApiResponse({} as any)).toBe(60);
});
it('returns the minimum step interval from the response', () => {
const apiResponse = {
data: {
newResult: {
meta: {
stepIntervals: { A: 120, B: 60 },
},
},
},
};
expect(getMinStepIntervalFromApiResponse(apiResponse as any)).toBe(60);
});
});
describe('getStepIntervalForQuery', () => {
it('returns query-specific step interval when available', () => {
const apiResponse = {
data: {
newResult: {
meta: {
stepIntervals: { A: 120, B: 60 },
},
},
},
};
expect(getStepIntervalForQuery(apiResponse as any, 'A')).toBe(120);
expect(getStepIntervalForQuery(apiResponse as any, 'B')).toBe(60);
});
});
});

View File

@@ -13,65 +13,6 @@ import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { QueryData } from 'types/api/widgets/getQuery';
import { v4 } from 'uuid';
const DEFAULT_STEP_INTERVAL_SECONDS = 60;
const MIN_TRACES_TIME_RANGE_MINUTES = 5;
export function getMinStepIntervalFromApiResponse(
apiResponse: MetricRangePayloadProps,
): number {
const stepIntervals: ExecStats['stepIntervals'] = get(
apiResponse,
'data.newResult.meta.stepIntervals',
{},
);
const values = Object.values(stepIntervals).filter(
(value): value is number =>
typeof value === 'number' && Number.isFinite(value),
);
if (values.length === 0) {
return DEFAULT_STEP_INTERVAL_SECONDS;
}
return Math.min(...values);
}
export function getStepIntervalForQuery(
apiResponse: MetricRangePayloadProps,
queryName?: string,
): number {
const minStepInterval = getMinStepIntervalFromApiResponse(apiResponse);
if (!queryName) {
return minStepInterval;
}
const stepIntervals: ExecStats['stepIntervals'] = get(
apiResponse,
'data.newResult.meta.stepIntervals',
{},
);
return get(stepIntervals, queryName, minStepInterval) ?? minStepInterval;
}
export function getTracesTimeRangeFromStepInterval(
xValue: number,
stepIntervalSeconds: number,
): { start: number; end: number } {
const rangeMinutes = Math.max(
stepIntervalSeconds / 60,
MIN_TRACES_TIME_RANGE_MINUTES,
);
const rangeMs = rangeMinutes * 60 * 1000;
const start = Math.floor(xValue * 1000);
return {
start,
end: Math.ceil(start + rangeMs),
};
}
export const prepareStatusCodeBarChartsConfig = ({
timezone,
isDarkMode,
@@ -100,7 +41,7 @@ export const prepareStatusCodeBarChartsConfig = ({
'data.newResult.meta.stepIntervals',
{},
);
const minStepInterval = getMinStepIntervalFromApiResponse(apiResponse);
const minStepInterval = Math.min(...Object.values(stepIntervals));
const config = buildBaseConfig({
id: v4(),

View File

@@ -1,8 +1,7 @@
import { ReactNode } from 'react';
import { Color } from '@signozhq/design-tokens';
import { TableColumnType as ColumnType, Tooltip } from 'antd';
import { TableColumnType as ColumnType, Tag, Tooltip } from 'antd';
import { Progress } from '@signozhq/ui/progress';
import { Badge } from '@signozhq/ui/badge';
import { convertFiltersToExpressionWithExistingQuery } from 'components/QueryBuilderV2/utils';
import {
FiltersType,
@@ -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>

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

@@ -11,13 +11,8 @@ import {
} from '@signozhq/icons';
import { Button } from '@signozhq/ui/button';
import { Callout } from '@signozhq/ui/callout';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuTrigger,
} from '@signozhq/ui/dropdown-menu';
import { toast } from '@signozhq/ui/sonner';
import { Skeleton } from 'antd';
import { Dropdown, Skeleton } from 'antd';
import {
RenderErrorResponseDTO,
ZeustypesHostDTO,
@@ -205,15 +200,10 @@ export default function CustomDomainSettings(): JSX.Element {
!workspaceName ? 'workspace-name-hidden' : ''
}`}
>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="link" color="none" disabled={isFetchingHosts}>
<Link2 size={12} />
<span>{stripProtocol(activeHost?.url ?? '')}</span>
<ChevronDown size={12} />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<Dropdown
trigger={['click']}
disabled={isFetchingHosts}
dropdownRender={(): JSX.Element => (
<div className="workspace-url-dropdown">
<span className="workspace-url-dropdown-header">
All Workspace URLs
@@ -246,8 +236,14 @@ export default function CustomDomainSettings(): JSX.Element {
);
})}
</div>
</DropdownMenuContent>
</DropdownMenu>
)}
>
<Button variant="link" color="none">
<Link2 size={12} />
<span>{stripProtocol(activeHost?.url ?? '')}</span>
<ChevronDown size={12} />
</Button>
</Dropdown>
<span className="custom-domain-card-meta-timezone">
<Clock size={11} />
{timezone.offset}

View File

@@ -1,5 +1,4 @@
import { GetHosts200 } from 'api/generated/services/sigNoz.schemas';
import userEvent from '@testing-library/user-event';
import { rest, server } from 'mocks-server/server';
import { fireEvent, render, screen, waitFor } from 'tests/test-utils';
@@ -143,13 +142,12 @@ describe('CustomDomainSettings', () => {
});
it('shows all workspace URLs as links in the dropdown', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
render(<CustomDomainSettings />);
await screen.findByText(/custom-host\.test\.cloud/i);
// Open the URL dropdown
await user.click(
fireEvent.click(
screen.getByRole('button', { name: /custom-host\.test\.cloud/i }),
);

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,38 +1,4 @@
.badgesContainer {
display: flex;
align-items: center;
flex-flow: wrap;
gap: 6px;
}
.badgeContainer {
color: var(--bg-sienna-400);
font-family: 'Space Mono';
font-size: 13px;
font-style: normal;
font-weight: 500;
line-height: 20px;
letter-spacing: 0.52px;
height: 24px;
flex-shrink: 0;
border-radius: 50px;
border: 1px solid color-mix(in srgb, var(--bg-sienna-500) 20%, transparent);
background: color-mix(in srgb, var(--bg-sienna-500) 10%, transparent);
padding: 2px 8px;
display: flex;
justify-content: space-between;
align-items: center;
}
.inputContainer {
> div {
margin: 0;
}
padding-left: 0px !important;
padding-right: 0px !important;
}
.tagsInput {
.tags-input {
display: flex;
border: none;
padding: 0px;
@@ -42,7 +8,23 @@
flex-shrink: 0;
}
.editInput {
.tag-container {
color: var(--bg-sienna-400);
font-family: 'Space Mono';
font-size: 13px;
font-style: normal;
font-weight: 500;
line-height: 20px; /* 153.846% */
letter-spacing: 0.52px;
height: 24px;
flex-shrink: 0;
border-radius: 50px;
border: 1px solid color-mix(in srgb, var(--bg-sienna-500) 20%, transparent);
background: color-mix(in srgb, var(--bg-sienna-500) 10%, transparent);
padding: 2px 8px;
}
.edit-input {
.ant-form-item {
margin-bottom: 0px;
}

View File

@@ -1,9 +1,10 @@
import { Dispatch, SetStateAction, useState } from 'react';
import { Col, Tooltip } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import Input from 'components/Input';
import styles from './AddBadges.module.scss';
import { InputContainer, NewTagContainer, TagsContainer } from './styles';
import './AddTags.styles.scss';
function AddTags({ tags, setTags }: AddTagsProps): JSX.Element {
const [inputValue, setInputValue] = useState<string>('');
@@ -38,11 +39,11 @@ function AddTags({ tags, setTags }: AddTagsProps): JSX.Element {
};
return (
<div className={styles.badgesContainer}>
<TagsContainer>
{tags.map((tag, index) => {
if (editInputIndex === index) {
return (
<Col key={tag} lg={4} className={styles.editInput}>
<Col key={tag} lg={4} className="edit-input">
<Input
size="small"
value={editInputValue}
@@ -59,15 +60,11 @@ function AddTags({ tags, setTags }: AddTagsProps): JSX.Element {
const isLongTag = tag.length > 20;
const tagElem = (
<Badge
key={tag}
color="vanilla"
className={styles.badgeContainer}
<NewTagContainer
closable
onClose={(e): void => {
e.preventDefault();
handleClose(tag);
}}
key={tag}
onClose={(): void => handleClose(tag)}
className="tag-container"
>
<span
onDoubleClick={(e): void => {
@@ -78,7 +75,7 @@ function AddTags({ tags, setTags }: AddTagsProps): JSX.Element {
>
{isLongTag ? `${tag.slice(0, 20)}...` : tag}
</span>
</Badge>
</NewTagContainer>
);
return isLongTag ? (
@@ -90,11 +87,11 @@ function AddTags({ tags, setTags }: AddTagsProps): JSX.Element {
);
})}
<Col className={styles.inputContainer}>
<InputContainer>
<Input
type="text"
value={inputValue}
rootClassName={styles.tagsInput}
rootClassName="tags-input"
placeholder="Start typing your tag name"
onChangeHandler={(event): void =>
onChangeHandler(event.target.value, setInputValue)
@@ -102,8 +99,8 @@ function AddTags({ tags, setTags }: AddTagsProps): JSX.Element {
onBlurHandler={handleInputConfirm}
onPressEnterHandler={handleInputConfirm}
/>
</Col>
</div>
</InputContainer>
</TagsContainer>
);
}

View File

@@ -0,0 +1,30 @@
import { Col, Tag } from 'antd';
import styled from 'styled-components';
export const TagsContainer = styled.div`
display: flex;
align-items: center;
flex-flow: wrap;
gap: 6px;
`;
export const NewTagContainer = styled(Tag)`
&&& {
display: flex;
justify-content: space-between;
align-items: center;
height: 100%;
svg {
margin-right: 0.2rem;
}
}
`;
export const InputContainer = styled(Col)`
> div {
margin: 0;
}
padding-left: 0px !important;
padding-right: 0px !important;
`;

View File

@@ -1,9 +1,8 @@
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Col, Input, Select, Space, Tooltip } from 'antd';
import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
import { Col, Input, Radio, Select, Space, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import AddTags from 'container/DashboardContainer/DashboardSettings/General/AddBadges';
import AddTags from 'container/DashboardContainer/DashboardSettings/General/AddTags';
import { useDashboardCursorSyncMode } from 'hooks/dashboard/useDashboardCursorSyncMode';
import { useSyncTooltipFilterMode } from 'hooks/dashboard/useSyncTooltipFilterMode';
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
@@ -214,18 +213,18 @@ function GeneralDashboardSettings(): JSX.Element {
Sync crosshair and tooltip across all the dashboard panels
</Typography.Text>
</div>
<ToggleGroupSimple
type="single"
<Radio.Group
value={cursorSyncMode}
onChange={(value: string): void => {
setCursorSyncMode(value as DashboardCursorSync);
onChange={(e): void => {
setCursorSyncMode(e.target.value as DashboardCursorSync);
}}
items={[
{ value: DashboardCursorSync.None, label: 'No Sync' },
{ value: DashboardCursorSync.Crosshair, label: 'Crosshair' },
{ value: DashboardCursorSync.Tooltip, label: 'Tooltip' },
]}
/>
>
<Radio.Button value={DashboardCursorSync.None}>No Sync</Radio.Button>
<Radio.Button value={DashboardCursorSync.Crosshair}>
Crosshair
</Radio.Button>
<Radio.Button value={DashboardCursorSync.Tooltip}>Tooltip</Radio.Button>
</Radio.Group>
</div>
{cursorSyncMode === DashboardCursorSync.Tooltip && (
<div className={styles.crossPanelSyncRow}>
@@ -238,21 +237,21 @@ function GeneralDashboardSettings(): JSX.Element {
matching ones highlighted
</Typography.Text>
</div>
<ToggleGroupSimple
type="single"
<Radio.Group
value={syncTooltipFilterMode}
onChange={(value: string): void => {
onChange={(e): void => {
logEvent(Events.TOOLTIP_SYNC_MODE_CHANGED, {
path: getAbsoluteUrl(window.location.pathname),
mode: value,
mode: e.target.value,
});
setSyncTooltipFilterMode(value as SyncTooltipFilterMode);
setSyncTooltipFilterMode(e.target.value as SyncTooltipFilterMode);
}}
items={[
{ value: SyncTooltipFilterMode.All, label: 'All' },
{ value: SyncTooltipFilterMode.Filtered, label: 'Filtered' },
]}
/>
>
<Radio.Button value={SyncTooltipFilterMode.All}>All</Radio.Button>
<Radio.Button value={SyncTooltipFilterMode.Filtered}>
Filtered
</Radio.Button>
</Radio.Group>
</div>
)}
</Col>

View File

@@ -395,8 +395,8 @@ describe('Dynamic Variable Default Behavior', () => {
// Check if the checkbox exists (it should be unchecked initially)
const checkbox = allOptionContainer?.querySelector(
'[role="checkbox"]',
) as HTMLElement;
'input[type="checkbox"]',
) as HTMLInputElement;
expect(checkbox).toBeInTheDocument();
// Should call onValueUpdate with all values (ALL selection)
@@ -516,10 +516,10 @@ describe('Dynamic Variable Default Behavior', () => {
// Check if the checkbox for ALL option is checked
const checkbox = dropdownAllOption.querySelector(
'[role="checkbox"]',
) as HTMLElement;
'input[type="checkbox"]',
) as HTMLInputElement;
expect(checkbox).toBeInTheDocument();
expect(checkbox).toHaveAttribute('data-state', 'checked');
expect(checkbox.checked).toBe(true);
});
});
});

View File

@@ -196,10 +196,10 @@ describe('Panel Management Tests', () => {
expect(allOption).toHaveClass('selected');
const allCheckbox = allOption.querySelector(
'[role="checkbox"]',
) as HTMLElement;
'input[type="checkbox"]',
) as HTMLInputElement;
expect(allCheckbox).toBeInTheDocument();
expect(allCheckbox).toHaveAttribute('data-state', 'checked');
expect(allCheckbox.checked).toBe(true);
});
});

View File

@@ -160,8 +160,8 @@ describe('getChartManagerColumns', () => {
expect(renderFn).toBeDefined();
const { container } = render(renderFn!(null, tableDataSet[1], 1));
const checkbox = container.querySelector('[role="checkbox"]');
const checkbox = container.querySelector('input[type="checkbox"]');
expect(checkbox).toBeInTheDocument();
expect(checkbox).toHaveAttribute('data-state', 'checked'); // graphVisibilityState[1] is true
expect(checkbox).toBeChecked(); // graphVisibilityState[1] is true
});
});

View File

@@ -1,7 +1,6 @@
import { useState } from 'react';
import { CloudDownload } from '@signozhq/icons';
import { DropdownMenuSimple, type MenuProps } from '@signozhq/ui/dropdown-menu';
import { Button, Flex } from 'antd';
import { Button, Dropdown, MenuProps, Flex } from 'antd';
import { unparse } from 'papaparse';
import { DownloadProps } from './Download.types';
@@ -68,7 +67,7 @@ function Download({ data, isLoading, fileName }: DownloadProps): JSX.Element {
};
return (
<DropdownMenuSimple menu={menu}>
<Dropdown menu={menu} trigger={['click']}>
<Button
className="download-button"
loading={isLoading || isDownloading}
@@ -80,7 +79,7 @@ function Download({ data, isLoading, fileName }: DownloadProps): JSX.Element {
Download
</Flex>
</Button>
</DropdownMenuSimple>
</Dropdown>
);
}

View File

@@ -2,8 +2,7 @@ import { useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import { useLocation } from 'react-router-dom';
import { Button, Space } from 'antd';
import { Divider } from '@signozhq/ui/divider';
import { Button, Divider, Space } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import logEvent from 'api/common/logEvent';
import getNextPrevId from 'api/errors/getNextPrevId';

View File

@@ -22,13 +22,13 @@ import { Color } from '@signozhq/design-tokens';
import {
Button,
ColorPicker,
Divider,
Input,
Modal,
RefSelectProps,
Select,
Tooltip,
} from 'antd';
import { Divider } from '@signozhq/ui/divider';
import { Typography } from '@signozhq/ui/typography';
import getLocalStorageKey from 'api/browser/localstorage/get';
import setLocalStorageKey from 'api/browser/localstorage/set';

View File

@@ -0,0 +1,24 @@
import { QueryChipContainer, QueryChipItem } from './styles';
import { ILabelRecord } from './types';
interface QueryChipProps {
queryData: ILabelRecord;
onRemove: (id: string) => void;
}
export default function QueryChip({
queryData,
onRemove,
}: QueryChipProps): JSX.Element {
const { key, value } = queryData;
return (
<QueryChipContainer>
<QueryChipItem
closable={key !== 'severity' && key !== 'description'}
onClose={(): void => onRemove(key)}
>
{key}: {value}
</QueryChipItem>
</QueryChipContainer>
);
}

View File

@@ -7,8 +7,8 @@ import { map } from 'lodash-es';
import { Labels } from 'types/api/alerts/def';
import { v4 as uuid } from 'uuid';
import { Badge } from '@signozhq/ui/badge';
import { QueryChipContainer, QueryChipItem, SearchContainer } from './styles';
import QueryChip from './QueryChip';
import { QueryChipItem, SearchContainer } from './styles';
import { ILabelRecord } from './types';
import { createQuery, flattenLabels, prepareLabels } from './utils';
@@ -147,24 +147,12 @@ function LabelSelect({
<SearchContainer isDarkMode={isDarkMode} disabled={false}>
<div style={{ display: 'inline-flex', flexWrap: 'wrap' }}>
{queries.length > 0 &&
map(queries, (query): JSX.Element => {
const isClosable =
query.key !== 'severity' && query.key !== 'description';
return (
<QueryChipContainer key={query.key}>
<Badge
color="vanilla"
closable={isClosable}
onClose={(e): void => {
e.preventDefault();
handleClose(query.key);
}}
>
{query.key}: {query.value}
</Badge>
</QueryChipContainer>
);
})}
map(
queries,
(query): JSX.Element => (
<QueryChip key={query.key} queryData={query} onRemove={handleClose} />
),
)}
</div>
<div>
{map(staging, (item) => (

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 {
@@ -27,11 +27,8 @@ export const QueryChipContainer = styled.span`
background: ${grey.primary}44;
}
}
[data-slot='badge'] {
margin-right: 0.1rem;
}
`;
export const QueryChipItem = styled(Badge)`
export const QueryChipItem = styled(Tag)`
margin-right: 0.1rem;
`;

View File

@@ -1,4 +1,8 @@
import { Col, Input as InputComponent } from 'antd';
import {
Col,
Dropdown as DropDownComponent,
Input as InputComponent,
} from 'antd';
import { Typography as TypographyComponent } from '@signozhq/ui/typography';
import styled from 'styled-components';
@@ -30,6 +34,16 @@ export const ButtonContainer = styled.div`
}
`;
export const Dropdown = styled(DropDownComponent)`
&&& {
display: flex;
justify-content: center;
align-items: center;
max-width: 150px;
min-width: 150px;
}
`;
export const TextContainer = styled.div`
&&& {
min-width: 100px;

View File

@@ -1,6 +1,6 @@
import { grey } from '@ant-design/colors';
import { Checkbox } from '@signozhq/ui/checkbox';
import { CSSProperties } from 'react';
import { Checkbox, ConfigProvider } from 'antd';
import type { CheckboxChangeEvent } from 'antd/es/checkbox';
import { CheckBoxProps } from '../types';
@@ -11,22 +11,30 @@ function CustomCheckBox({
checkBoxOnChangeHandler,
disabled = false,
}: CheckBoxProps): JSX.Element {
const onChangeHandler = (e: CheckboxChangeEvent): void => {
checkBoxOnChangeHandler(e, index);
};
const color = data[index]?.stroke?.toString() || grey[0];
const isChecked = graphVisibilityState[index] || false;
const colorStyle = {
'--checkbox-checked-background': color,
'--checkbox-border-color': color,
} as CSSProperties;
return (
<span style={colorStyle}>
<ConfigProvider
theme={{
token: {
colorPrimary: color,
colorBorder: color,
colorBgContainer: color,
},
}}
>
<Checkbox
onChange={(checked): void => checkBoxOnChangeHandler(checked, index)}
value={isChecked}
onChange={onChangeHandler}
checked={isChecked}
disabled={disabled}
/>
</span>
</ConfigProvider>
);
}

View File

@@ -1,4 +1,5 @@
import { Dispatch, MutableRefObject, RefObject, SetStateAction } from 'react';
import type { CheckboxChangeEvent } from 'antd/es/checkbox';
import { ToggleGraphProps } from 'components/Graph/types';
import { UplotProps } from 'components/Uplot/Uplot';
import { PANEL_TYPES } from 'constants/queryBuilder';
@@ -76,10 +77,7 @@ export interface CheckBoxProps {
data: ExtendedChartDataset[];
index: number;
graphVisibilityState: boolean[];
checkBoxOnChangeHandler: (
checked: boolean | 'indeterminate',
index: number,
) => void;
checkBoxOnChangeHandler: (e: CheckboxChangeEvent, index: number) => void;
disabled?: boolean;
}

View File

@@ -1,7 +1,6 @@
// eslint-disable-next-line no-restricted-imports
import { Provider } from 'react-redux';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { fireEvent, render, screen } from '@testing-library/react';
import { PANEL_TYPES } from 'constants/queryBuilder';
import ROUTES from 'constants/routes';
import { AppProvider } from 'providers/App/App';
@@ -177,7 +176,6 @@ jest.mock('providers/Dashboard/store/useDashboardStore', () => ({
describe('WidgetGraphComponent', () => {
it('should show correct menu items when hovering over more options while loading', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
const { getByTestId, findByRole, getByText, container } = render(
<MockQueryClientProvider>
<ErrorModalProvider>
@@ -210,7 +208,7 @@ describe('WidgetGraphComponent', () => {
expect(skeleton).toBeInTheDocument();
const moreOptionsButton = getByTestId('widget-header-options');
await user.click(moreOptionsButton);
fireEvent.mouseEnter(moreOptionsButton);
const menu = await findByRole('menu');
expect(menu).toBeInTheDocument();

View File

@@ -54,17 +54,6 @@
visibility: visible;
}
// currently the width of the dropdown menu is set to 100% of the parent container,
// which is not desired. This is a workaround to unset that width and allow the dropdown menu to size based on its content.
// This is necessary because the dropdown menu can contain items with varying widths, and setting it to 100% can cause layout issues and make the menu look unbalanced.
// we should idealy fix this in the dropdown menu component itself, but for now this is a quick fix to ensure the dropdown menu looks correct in the widget header.
[data-radix-popper-content-wrapper]
[data-slot='dropdown-menu-content'].widget-header-dropdown
[data-slot='dropdown-menu-item'] {
width: unset !important;
}
.widget-api-actions {
padding-right: 0.25rem;
}

View File

@@ -467,7 +467,6 @@ describe('WidgetHeader', () => {
describe('Create Alerts Menu Item', () => {
it('renders Create Alerts menu item with external link icon when included in headerMenuList', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
render(
<WidgetHeader
title={TEST_WIDGET_TITLE}
@@ -484,7 +483,7 @@ describe('WidgetHeader', () => {
const moreOptionsIcon = await screen.findByTestId(WIDGET_HEADER_OPTIONS_ID);
expect(moreOptionsIcon).toBeInTheDocument();
await user.click(moreOptionsIcon);
await userEvent.hover(moreOptionsIcon);
await screen.findByText(CREATE_ALERTS_TEXT);
@@ -495,7 +494,6 @@ describe('WidgetHeader', () => {
});
it('Create Alerts menu item is enabled and clickable', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
const mockCreateAlertsHandler = jest.fn();
const useCreateAlerts = jest.requireMock(
'hooks/queryBuilder/useCreateAlerts',
@@ -519,12 +517,12 @@ describe('WidgetHeader', () => {
expect(useCreateAlerts).toHaveBeenCalledWith(mockWidget, 'dashboardView');
const moreOptionsIcon = await screen.findByTestId(WIDGET_HEADER_OPTIONS_ID);
await user.click(moreOptionsIcon);
await userEvent.hover(moreOptionsIcon);
const createAlertsMenuItem = await screen.findByText(CREATE_ALERTS_TEXT);
// Verify the menu item is clickable by actually clicking it
await user.click(createAlertsMenuItem);
await userEvent.click(createAlertsMenuItem);
expect(mockCreateAlertsHandler).toHaveBeenCalledTimes(1);
});
});

View File

@@ -15,8 +15,7 @@ import {
X,
} from '@signozhq/icons';
import { Color } from '@signozhq/design-tokens';
import { Button, Input, Tooltip } from 'antd';
import { DropdownMenuSimple } from '@signozhq/ui/dropdown-menu';
import { Button, Dropdown, Input, MenuProps, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import ErrorContent from 'components/ErrorModal/components/ErrorContent';
import ErrorPopover from 'components/ErrorPopover/ErrorPopover';
@@ -129,7 +128,7 @@ function WidgetHeader({
],
);
const onMenuItemSelectHandler = useCallback(
const onMenuItemSelectHandler: MenuProps['onClick'] = useCallback(
({ key }: { key: string }): void => {
if (isTWidgetOptions(key)) {
const functionToCall = keyMethodMapping[key];
@@ -189,8 +188,18 @@ function WidgetHeader({
{
key: MenuItemKeys.CreateAlerts,
icon: <Bell size="md" />,
label: MENUITEM_KEYS_VS_LABELS[MenuItemKeys.CreateAlerts],
rightIcon: <SquareArrowOutUpRight size="lg" />,
label: (
<span
style={{
display: 'flex',
alignItems: 'baseline',
justifyContent: 'space-between',
}}
>
{MENUITEM_KEYS_VS_LABELS[MenuItemKeys.CreateAlerts]}
<SquareArrowOutUpRight size={10} />
</span>
),
isVisible: headerMenuList?.includes(MenuItemKeys.CreateAlerts) || false,
disabled: false,
},
@@ -212,10 +221,8 @@ function WidgetHeader({
const menu = useMemo(
() => ({
items: updatedMenuList.map((item) => ({
...item,
onClick: onMenuItemSelectHandler,
})),
items: updatedMenuList,
onClick: onMenuItemSelectHandler,
}),
[updatedMenuList, onMenuItemSelectHandler],
);
@@ -314,12 +321,7 @@ function WidgetHeader({
/>
)}
{menu && Array.isArray(menu.items) && menu.items.length > 0 && (
<DropdownMenuSimple
menu={menu}
side="bottom"
align="end"
className="widget-header-dropdown"
>
<Dropdown menu={menu} trigger={['hover']} placement="bottomRight">
<Button
data-testid="widget-header-options"
className={`widget-header-more-options ${
@@ -327,7 +329,7 @@ function WidgetHeader({
}`}
icon={<EllipsisVertical size="md" />}
/>
</DropdownMenuSimple>
</Dropdown>
)}
</div>
</>

View File

@@ -6,7 +6,6 @@ export interface MenuItem {
key: MenuItemKeys;
icon: ReactNode;
label: ReactNode;
rightIcon?: ReactNode;
isVisible: boolean;
disabled: boolean;
danger?: boolean;

View File

@@ -1,9 +1,9 @@
import type { MenuItem as DropdownMenuItem } from '@signozhq/ui/dropdown-menu';
import type { MenuItemType } from 'antd/es/menu/hooks/useItems';
import { MenuItemKeys } from './contants';
import { MenuItem } from './types';
export const generateMenuList = (actions: MenuItem[]): DropdownMenuItem[] =>
export const generateMenuList = (actions: MenuItem[]): MenuItemType[] =>
actions
.filter((action: MenuItem) => action.isVisible)
.map(({ key, icon: Icon, label, disabled, ...rest }) => ({

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

@@ -1,6 +1,6 @@
import React from 'react';
import { Color } from '@signozhq/design-tokens';
import { Badge } from '@signozhq/ui/badge';
import { Tag } from 'antd';
import { Progress } from '@signozhq/ui/progress';
import { Typography } from '@signozhq/ui/typography';
import {
@@ -52,14 +52,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 +67,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>
),

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

@@ -8,10 +8,9 @@ import React, {
import { useQuery } from 'react-query';
// eslint-disable-next-line no-restricted-imports
import { Color, Spacing } from '@signozhq/design-tokens';
import { Button, Drawer, Tooltip } from 'antd';
import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
import { Divider } from '@signozhq/ui/divider';
import { Button, Divider, Drawer, Radio, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import type { RadioChangeEvent } from 'antd/lib';
import logEvent from 'api/common/logEvent';
import { combineInitialAndUserExpression } from 'components/QueryBuilderV2/QueryV2/QuerySearch/utils';
import { convertFiltersToExpression } from 'components/QueryBuilderV2/utils';
@@ -331,8 +330,8 @@ export default function K8sBaseDetails<T>({
}
}, [getMinMaxTime, selectedTime]);
const handleTabChange = (value: string): void => {
setSelectedView(value);
const handleTabChange = (e: RadioChangeEvent): void => {
setSelectedView(e.target.value);
setLogFiltersParam(null);
setTracesFiltersParam(null);
setEventsFiltersParam(null);
@@ -340,7 +339,7 @@ export default function K8sBaseDetails<T>({
entity: InfraMonitoringEvents.K8sEntity,
page: InfraMonitoringEvents.DetailedPage,
category: eventCategory,
view: value,
view: e.target.value,
});
};
@@ -521,101 +520,102 @@ export default function K8sBaseDetails<T>({
{!hideDetailViewTabs && (
<div className="views-tabs-container">
<ToggleGroupSimple
type="single"
<Radio.Group
className="views-tabs"
onChange={handleTabChange}
value={selectedView}
items={[
...(tabVisibility.showMetrics
? [
{
value: VIEW_TYPES.METRICS,
label: (
<div className="view-title">
<BarChart size={14} />
Metrics
</div>
),
},
]
: []),
...(tabVisibility.showLogs
? [
{
value: VIEW_TYPES.LOGS,
label: (
<div className="view-title">
<ScrollText size={14} />
Logs
</div>
),
},
]
: []),
...(tabVisibility.showTraces
? [
{
value: VIEW_TYPES.TRACES,
label: (
<div className="view-title">
<DraftingCompass size={14} />
Traces
</div>
),
},
]
: []),
...(tabVisibility.showEvents
? [
{
value: VIEW_TYPES.EVENTS,
label: (
<div className="view-title">
<ChevronsLeftRight size={14} />
Events
</div>
),
},
]
: []),
...(tabVisibility.showContainers
? [
{
value: VIEW_TYPES.CONTAINERS,
label: (
<div className="view-title">
<Package2 size={14} />
Containers
</div>
),
},
]
: []),
...(tabVisibility.showProcesses
? [
{
value: VIEW_TYPES.PROCESSES,
label: (
<div className="view-title">
<ChevronsLeftRight size={14} />
Processes
</div>
),
},
]
: []),
...(customTabs?.map((tab) => ({
value: tab.key,
label: (
<div className="view-title">
{tab.icon}
{tab.label}
</div>
),
})) ?? []),
]}
/>
>
{tabVisibility.showMetrics && (
<Radio.Button
className={
selectedView === VIEW_TYPES.METRICS ? 'selected_view tab' : 'tab'
}
value={VIEW_TYPES.METRICS}
>
<div className="view-title">
<BarChart size={14} />
Metrics
</div>
</Radio.Button>
)}
{tabVisibility.showLogs && (
<Radio.Button
className={
selectedView === VIEW_TYPES.LOGS ? 'selected_view tab' : 'tab'
}
value={VIEW_TYPES.LOGS}
>
<div className="view-title">
<ScrollText size={14} />
Logs
</div>
</Radio.Button>
)}
{tabVisibility.showTraces && (
<Radio.Button
className={
selectedView === VIEW_TYPES.TRACES ? 'selected_view tab' : 'tab'
}
value={VIEW_TYPES.TRACES}
>
<div className="view-title">
<DraftingCompass size={14} />
Traces
</div>
</Radio.Button>
)}
{tabVisibility.showEvents && (
<Radio.Button
className={
selectedView === VIEW_TYPES.EVENTS ? 'selected_view tab' : 'tab'
}
value={VIEW_TYPES.EVENTS}
>
<div className="view-title">
<ChevronsLeftRight size={14} />
Events
</div>
</Radio.Button>
)}
{tabVisibility.showContainers && (
<Radio.Button
className={
selectedView === VIEW_TYPES.CONTAINERS ? 'selected_view tab' : 'tab'
}
value={VIEW_TYPES.CONTAINERS}
>
<div className="view-title">
<Package2 size={14} />
Containers
</div>
</Radio.Button>
)}
{tabVisibility.showProcesses && (
<Radio.Button
className={
selectedView === VIEW_TYPES.PROCESSES ? 'selected_view tab' : 'tab'
}
value={VIEW_TYPES.PROCESSES}
>
<div className="view-title">
<ChevronsLeftRight size={14} />
Processes
</div>
</Radio.Button>
)}
{customTabs?.map((tab) => (
<Radio.Button
key={tab.key}
className={selectedView === tab.key ? 'selected_view tab' : 'tab'}
value={tab.key}
>
<div className="view-title">
{tab.icon}
{tab.label}
</div>
</Radio.Button>
))}
</Radio.Group>
{selectedView === VIEW_TYPES.LOGS && (
<Tooltip title="Go to Logs Explorer" placement="left">

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

@@ -152,23 +152,23 @@
font-weight: var(--font-weight-normal);
}
> button {
.tab {
border: 1px solid var(--l1-border);
width: 114px;
}
&::before {
background: var(--l1-border);
}
.tab::before {
background: var(--l1-border);
}
&[data-state='on'] {
background: var(--l1-border);
color: var(--l1-foreground);
border: 1px solid var(--l1-border);
.selected_view {
background: var(--l1-border);
color: var(--l1-foreground);
border: 1px solid var(--l1-border);
}
&::before {
background: var(--l1-border);
}
}
.selected_view::before {
background: var(--l1-border);
}
}
@@ -213,7 +213,7 @@
font-size: 12px;
}
[data-slot='badge'] .ant-typography {
.ant-tag .ant-typography {
font-size: 12px;
}
}
@@ -349,7 +349,7 @@
font-size: 12px;
}
[data-slot='badge'] .ant-typography {
.ant-tag .ant-typography {
font-size: 12px;
}
}

View File

@@ -18,6 +18,7 @@ import {
Table,
TablePaginationConfig,
TableProps as AntDTableProps,
Tag,
Tooltip,
} from 'antd';
import { Switch } from '@signozhq/ui/switch';
@@ -40,7 +41,7 @@ import {
} from 'api/generated/services/sigNoz.schemas';
import { AxiosError } from 'axios';
import { getYAxisFormattedValue } from 'components/Graph/yAxisConfig';
import Badges from 'components/Badges/Badges';
import Tags from 'components/Tags/Tags';
import { UniversalYAxisUnit } from 'components/YAxisUnitSelector/types';
import { SOMETHING_WENT_WRONG } from 'constants/api';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
@@ -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>
@@ -1836,7 +1834,7 @@ function MultiIngestionSettings(): JSX.Element {
</Form.Item>
<Form.Item name="tags" label="Tags">
<Badges tags={updatedTags} setTags={setUpdatedTags} />
<Tags tags={updatedTags} setTags={setUpdatedTags} />
</Form.Item>
<Form.Item
@@ -1926,7 +1924,7 @@ function MultiIngestionSettings(): JSX.Element {
</Form.Item>
<Form.Item name="tags" label="Tags">
<Badges tags={updatedTags} setTags={setUpdatedTags} />
<Tags tags={updatedTags} setTags={setUpdatedTags} />
</Form.Item>
</Form>
</Modal>

View File

@@ -1,5 +1,5 @@
import { Dispatch, SetStateAction } from 'react';
import { Checkbox } from '@signozhq/ui/checkbox';
import { Checkbox } from 'antd';
import { useRegionSelection } from 'hooks/integration/aws/useRegionSelection';
import { regions } from 'utils/regions';
@@ -21,18 +21,18 @@ export function RegionSelector({
setIncludeAllRegions,
});
const allSelected =
allRegionIds.length > 0 &&
allRegionIds.every((regionId) => selectedRegions.includes(regionId));
const someSelected =
selectedRegions.length > 0 && selectedRegions.length < allRegionIds.length;
return (
<div className="region-selector">
<div className="select-all">
<Checkbox
value={allSelected ? true : someSelected ? 'indeterminate' : false}
onChange={(checked): void => handleSelectAll(checked === true)}
checked={
allRegionIds.length > 0 &&
allRegionIds.every((regionId) => selectedRegions.includes(regionId))
}
indeterminate={
selectedRegions.length > 0 && selectedRegions.length < allRegionIds.length
}
onChange={(e): void => handleSelectAll(e.target.checked)}
>
Select All Regions
</Checkbox>
@@ -45,7 +45,7 @@ export function RegionSelector({
{region.subRegions.map((subRegion) => (
<Checkbox
key={subRegion.id}
value={selectedRegions.includes(subRegion.id)}
checked={selectedRegions.includes(subRegion.id)}
onChange={(): void => handleRegionSelect(subRegion.id)}
>
{subRegion.name}

View File

@@ -1,7 +1,6 @@
import React, { useCallback, useState } from 'react';
import { Plus } from '@signozhq/icons';
import { Button, Flex } from 'antd';
import { Divider } from '@signozhq/ui/divider';
import { Button, Divider, Flex } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import logEvent from 'api/common/logEvent';
import ROUTES from 'constants/routes';

View File

@@ -3,8 +3,7 @@ import { useTranslation } from 'react-i18next';
import { UseQueryResult } from 'react-query';
import { Button, Flex, Input } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { Ellipsis, Plus } from '@signozhq/icons';
import { DropdownMenuSimple } from '@signozhq/ui/dropdown-menu';
import { Plus } from '@signozhq/icons';
import type { ColumnsType } from 'antd/es/table/interface';
import logEvent from 'api/common/logEvent';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
@@ -16,6 +15,7 @@ import type {
} from 'api/generated/services/sigNoz.schemas';
import type { ErrorType } from 'api/generatedAPIInstance';
import { AxiosError } from 'axios';
import DropDown from 'components/DropDown/DropDown';
import {
DynamicColumnsKey,
TableDataSource,
@@ -43,6 +43,7 @@ import { isModifierKeyPressed } from 'utils/app';
import DeleteAlert from './DeleteAlert';
import { ColumnButton, SearchContainer } from './styles';
import MutedBadge from './TableComponents/MutedBadge';
import Status from './TableComponents/Status';
import ToggleAlertState from './ToggleAlertState';
import { alertActionLogEvent, filterAlerts } from './utils';
@@ -276,7 +277,14 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
onEditHandler(record, { newTab: isModifierKeyPressed(e) });
};
return <Typography.Link onClick={onClickHandler}>{value}</Typography.Link>;
const isMuted = Boolean(record.mutes?.length);
return (
<span className="alert-list-name-cell">
<Typography.Link onClick={onClickHandler}>{value}</Typography.Link>
{isMuted && <MutedBadge muteEndTime={record.mutes![0].end} />}
</span>
);
},
sortOrder: sortedInfo.columnKey === 'name' ? sortedInfo.order : null,
},
@@ -310,7 +318,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" />
);
},
},
];
@@ -321,67 +331,55 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
dataIndex: 'id',
key: 'action',
width: 10,
render: (id: RuletypesRuleDTO['id'], record): JSX.Element => {
const actionItems = [
<ToggleAlertState
key="1"
disabled={record.disabled ?? false}
setData={setData}
id={id ?? ''}
/>,
<ColumnButton
key="2"
onClick={(e: React.MouseEvent): void =>
onEditHandler(record, { newTab: isModifierKeyPressed(e) })
render: (id: RuletypesRuleDTO['id'], record): JSX.Element => (
<div data-testid="alert-actions">
<DropDown
onDropDownItemClick={(item): void =>
alertActionLogEvent(item.key, record)
}
type="link"
loading={editLoader}
>
Edit
</ColumnButton>,
<ColumnButton
key="3-new-tab"
onClick={(): void => onEditHandler(record, { newTab: true })}
type="link"
loading={editLoader}
>
Edit in New Tab
</ColumnButton>,
<ColumnButton
key="3-clone"
onClick={onCloneHandler(record)}
type="link"
loading={cloneLoader}
>
Clone
</ColumnButton>,
<DeleteAlert
key="4"
notifications={notificationsApi}
setData={setData}
id={id ?? ''}
/>,
];
return (
<div data-testid="alert-actions">
<DropdownMenuSimple
menu={{
items: actionItems.map((element, index) => ({
key: String(index),
label: element,
onClick: ({ key }): void => alertActionLogEvent(key, record),
})),
}}
>
<Button
element={[
<ToggleAlertState
key="1"
disabled={record.disabled ?? false}
setData={setData}
id={id ?? ''}
/>,
<ColumnButton
key="2"
onClick={(e: React.MouseEvent): void =>
onEditHandler(record, { newTab: isModifierKeyPressed(e) })
}
type="link"
style={{ color: 'var(--l1-foreground)' }}
icon={<Ellipsis size={16} />}
/>
</DropdownMenuSimple>
</div>
);
},
loading={editLoader}
>
Edit
</ColumnButton>,
<ColumnButton
key="3"
onClick={(): void => onEditHandler(record, { newTab: true })}
type="link"
loading={editLoader}
>
Edit in New Tab
</ColumnButton>,
<ColumnButton
key="3"
onClick={onCloneHandler(record)}
type="link"
loading={cloneLoader}
>
Clone
</ColumnButton>,
<DeleteAlert
key="4"
notifications={notificationsApi}
setData={setData}
id={id ?? ''}
/>,
]}
/>
</div>
),
});
}

View File

@@ -0,0 +1,20 @@
.alert-list-name-cell {
display: inline-flex;
align-items: center;
gap: 8px;
}
.alert-list-muted-badge {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 7px;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--bg-amber-500);
background: rgba(255, 205, 86, 0.12);
border: 1px solid rgba(255, 205, 86, 0.25);
border-radius: 4px;
}

View File

@@ -0,0 +1,43 @@
import { BellOff } from '@signozhq/icons';
import dayjs from 'dayjs';
import './MutedBadge.styles.scss';
const formatRemaining = (endTime: string | undefined | null): string | null => {
if (!endTime) {
return null;
}
const end = dayjs(endTime);
const now = dayjs();
const diffMs = end.diff(now);
if (diffMs <= 0) {
return null;
}
const totalMinutes = Math.floor(diffMs / 60000);
const days = Math.floor(totalMinutes / (60 * 24));
const hours = Math.floor((totalMinutes % (60 * 24)) / 60);
const minutes = totalMinutes % 60;
if (days > 0) {
return `${days}d ${hours}h`;
}
if (hours > 0) {
return `${hours}h ${minutes}m`;
}
return `${minutes}m`;
};
interface MutedBadgeProps {
muteEndTime: string | undefined | null;
}
function MutedBadge({ muteEndTime }: MutedBadgeProps): JSX.Element | null {
const remaining = formatRemaining(muteEndTime);
return (
<span className="alert-list-muted-badge">
<BellOff size={10} />
<span>MUTED{remaining ? ` · ${remaining}` : ''}</span>
</span>
);
}
export default MutedBadge;

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

@@ -12,18 +12,19 @@ import { useTranslation } from 'react-i18next';
import { generatePath } from 'react-router-dom';
import { useCopyToClipboard } from 'react-use';
import { Color } from '@signozhq/design-tokens';
import { DropdownMenuSimple, type MenuItem } from '@signozhq/ui/dropdown-menu';
import {
Button,
Dropdown,
Flex,
Input,
MenuProps,
Modal,
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';
@@ -419,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>
)}
@@ -552,7 +553,7 @@ function DashboardsList(): JSX.Element {
];
const getCreateDashboardItems = useMemo(() => {
const menuItems: MenuItem[] = [
const menuItems: MenuProps['items'] = [
{
label: (
<div
@@ -710,11 +711,11 @@ function DashboardsList(): JSX.Element {
{createNewDashboard && (
<section className="actions">
<DropdownMenuSimple
className="new-dashboard-menu"
<Dropdown
overlayClassName="new-dashboard-menu"
menu={{ items: getCreateDashboardItems }}
side="bottom"
align="end"
placement="bottomRight"
trigger={['click']}
>
<Button
type="text"
@@ -726,7 +727,7 @@ function DashboardsList(): JSX.Element {
>
New Dashboard
</Button>
</DropdownMenuSimple>
</Dropdown>
<Button
type="text"
className="learn-more"
@@ -755,11 +756,11 @@ function DashboardsList(): JSX.Element {
onChange={handleSearch}
/>
{createNewDashboard && (
<DropdownMenuSimple
className="new-dashboard-menu"
<Dropdown
overlayClassName="new-dashboard-menu"
menu={{ items: getCreateDashboardItems }}
side="bottom"
align="end"
placement="bottomRight"
trigger={['click']}
>
<Button
type="primary"
@@ -772,7 +773,7 @@ function DashboardsList(): JSX.Element {
>
New dashboard
</Button>
</DropdownMenuSimple>
</Dropdown>
)}
</div>

View File

@@ -1,8 +1,7 @@
import { memo, useMemo } from 'react';
// eslint-disable-next-line no-restricted-imports
import { useDispatch, useSelector } from 'react-redux';
import { Button, Flex } from 'antd';
import { Divider } from '@signozhq/ui/divider';
import { Button, Divider, Flex } from 'antd';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import Controls from 'container/Controls';
import Download from 'container/Download/Download';

View File

@@ -2,13 +2,7 @@ import { useCallback } from 'react';
import { useCopyToClipboard } from 'react-use';
import { orange } from '@ant-design/colors';
import { Settings } from '@signozhq/icons';
import {
type BaseMenuItem,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@signozhq/ui/dropdown-menu';
import { Dropdown, MenuProps } from 'antd';
import {
negateOperator,
OPERATORS,
@@ -141,38 +135,41 @@ function BodyTitleRenderer({
viewName,
]);
const onClickHandler = (key: string): void => {
const onClickHandler: MenuProps['onClick'] = (props): void => {
const mapper = {
[DROPDOWN_KEY.FILTER_IN]: filterHandler(true),
[DROPDOWN_KEY.FILTER_OUT]: filterHandler(false),
[DROPDOWN_KEY.GROUP_BY]: groupByHandler,
};
const handler = mapper[key];
const handler = mapper[props.key];
if (handler) {
handler();
}
};
const menuItems: BaseMenuItem[] = [
{
key: DROPDOWN_KEY.FILTER_IN,
label: `Filter for ${value}`,
},
{
key: DROPDOWN_KEY.FILTER_OUT,
label: `Filter out ${value}`,
},
...(isGroupBySupported
? [
{
key: DROPDOWN_KEY.GROUP_BY,
label: `Group by ${nodeKey}`,
},
]
: []),
];
const menu: MenuProps = {
items: [
{
key: DROPDOWN_KEY.FILTER_IN,
label: `Filter for ${value}`,
},
{
key: DROPDOWN_KEY.FILTER_OUT,
label: `Filter out ${value}`,
},
...(isGroupBySupported
? [
{
key: DROPDOWN_KEY.GROUP_BY,
label: `Group by ${nodeKey}`,
},
]
: []),
],
onClick: onClickHandler,
};
const handleNodeClick = useCallback(
(e: React.MouseEvent): void => {
@@ -221,23 +218,15 @@ function BodyTitleRenderer({
}}
onMouseDown={(e): void => e.preventDefault()}
>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Settings style={{ marginRight: 8 }} className="hover-reveal" />
</DropdownMenuTrigger>
<DropdownMenuContent>
<div data-log-detail-ignore="true">
{menuItems.map((item) => (
<DropdownMenuItem
key={item.key}
onSelect={(): void => onClickHandler(item.key as string)}
>
{item.label}
</DropdownMenuItem>
))}
</div>
</DropdownMenuContent>
</DropdownMenu>
<Dropdown
menu={menu}
trigger={['click']}
dropdownRender={(originNode): React.ReactNode => (
<div data-log-detail-ignore="true">{originNode}</div>
)}
>
<Settings style={{ marginRight: 8 }} className="hover-reveal" />
</Dropdown>
</span>
)}
{title.toString()}{' '}

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