Compare commits

..

1 Commits

Author SHA1 Message Date
Abhi Kumar
8e01b0b293 chore: antd button -> signozui button migration 2026-05-18 21:09:02 +05:30
342 changed files with 3204 additions and 3652 deletions

View File

@@ -80,15 +80,6 @@ func (ah *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
Route: "",
})
fineGrainedAuthz := ah.Signoz.Flagger.BooleanOrEmpty(ctx, flagger.FeatureUseFineGrainedAuthz, evalCtx)
featureSet = append(featureSet, &licensetypes.Feature{
Name: valuer.NewString(flagger.FeatureUseFineGrainedAuthz.String()),
Active: fineGrainedAuthz,
Usage: 0,
UsageLimit: -1,
Route: "",
})
if constants.IsDotMetricsEnabled {
for idx, feature := range featureSet {
if feature.Name == licensetypes.DotMetricsEnabled {

View File

@@ -34,7 +34,7 @@ export default defineConfig({
signal: true,
useOperationIdAsQueryKey: false,
},
useDates: false,
useDates: true,
useNamedParameters: true,
enumGenerationType: 'enum',
mutator: {

View File

@@ -9,7 +9,7 @@ export interface AlertmanagertypesChannelDTO {
* @type string
* @format date-time
*/
createdAt?: string;
createdAt?: Date;
/**
* @type string
*/
@@ -34,7 +34,7 @@ export interface AlertmanagertypesChannelDTO {
* @type string
* @format date-time
*/
updatedAt?: string;
updatedAt?: Date;
}
export interface ModelLabelSetDTO {
@@ -62,7 +62,7 @@ export interface AlertmanagertypesDeprecatedGettableAlertDTO {
* @type string
* @format date-time
*/
endsAt?: string;
endsAt?: Date;
/**
* @type string
*/
@@ -80,7 +80,7 @@ export interface AlertmanagertypesDeprecatedGettableAlertDTO {
* @type string
* @format date-time
*/
startsAt?: string;
startsAt?: Date;
status?: TypesAlertStatusDTO;
}
@@ -97,7 +97,7 @@ export interface AlertmanagertypesGettableRoutePolicyDTO {
* @type string
* @format date-time
*/
createdAt: string;
createdAt: Date;
/**
* @type string,null
*/
@@ -127,7 +127,7 @@ export interface AlertmanagertypesGettableRoutePolicyDTO {
* @type string
* @format date-time
*/
updatedAt: string;
updatedAt: Date;
/**
* @type string,null
*/
@@ -1834,7 +1834,7 @@ export interface AuthtypesGettableAuthDomainDTO {
* @type string
* @format date-time
*/
createdAt?: string;
createdAt?: Date;
/**
* @type string
*/
@@ -1851,7 +1851,7 @@ export interface AuthtypesGettableAuthDomainDTO {
* @type string
* @format date-time
*/
updatedAt?: string;
updatedAt?: Date;
}
export interface AuthtypesGettableTokenDTO {
@@ -2009,7 +2009,7 @@ export interface AuthtypesRoleDTO {
* @type string
* @format date-time
*/
createdAt?: string;
createdAt?: Date;
/**
* @type string
*/
@@ -2034,7 +2034,7 @@ export interface AuthtypesRoleDTO {
* @type string
* @format date-time
*/
updatedAt?: string;
updatedAt?: Date;
}
export interface AuthtypesSessionContextDTO {
@@ -2062,7 +2062,7 @@ export interface AuthtypesUserRoleDTO {
* @type string
* @format date-time
*/
createdAt: string;
createdAt: Date;
/**
* @type string
*/
@@ -2076,7 +2076,7 @@ export interface AuthtypesUserRoleDTO {
* @type string
* @format date-time
*/
updatedAt: string;
updatedAt: Date;
/**
* @type string
*/
@@ -2088,7 +2088,7 @@ export interface AuthtypesUserWithRolesDTO {
* @type string
* @format date-time
*/
createdAt?: string;
createdAt?: Date;
/**
* @type string
*/
@@ -2117,7 +2117,7 @@ export interface AuthtypesUserWithRolesDTO {
* @type string
* @format date-time
*/
updatedAt?: string;
updatedAt?: Date;
/**
* @type array,null
*/
@@ -2284,7 +2284,7 @@ export interface CloudintegrationtypesAccountDTO {
* @type string
* @format date-time
*/
createdAt?: string;
createdAt?: Date;
/**
* @type string
*/
@@ -2305,12 +2305,12 @@ export interface CloudintegrationtypesAccountDTO {
* @type string,null
* @format date-time
*/
removedAt: string | null;
removedAt: Date | null;
/**
* @type string
* @format date-time
*/
updatedAt?: string;
updatedAt?: Date;
}
export interface DashboardtypesStorableDashboardDataDTO {
@@ -2441,7 +2441,7 @@ export type CloudintegrationtypesCloudIntegrationServiceDTOAnyOf = {
* @type string
* @format date-time
*/
createdAt?: string;
createdAt?: Date;
/**
* @type string
*/
@@ -2451,7 +2451,7 @@ export type CloudintegrationtypesCloudIntegrationServiceDTOAnyOf = {
* @type string
* @format date-time
*/
updatedAt?: string;
updatedAt?: Date;
};
/**
@@ -2645,12 +2645,12 @@ export interface CloudintegrationtypesGettableAgentCheckInDTO {
* @type string,null
* @format date-time
*/
removed_at: string | null;
removed_at: Date | null;
/**
* @type string,null
* @format date-time
*/
removedAt: string | null;
removedAt: Date | null;
}
export interface CloudintegrationtypesServiceMetadataDTO {
@@ -2885,7 +2885,7 @@ export interface DashboardtypesDashboardDTO {
* @type string
* @format date-time
*/
createdAt?: string;
createdAt?: Date;
/**
* @type string
*/
@@ -2907,7 +2907,7 @@ export interface DashboardtypesDashboardDTO {
* @type string
* @format date-time
*/
updatedAt?: string;
updatedAt?: Date;
/**
* @type string
*/
@@ -3089,7 +3089,7 @@ export interface GatewaytypesLimitDTO {
* @type string
* @format date-time
*/
created_at?: string;
created_at?: Date;
/**
* @type string
*/
@@ -3111,7 +3111,7 @@ export interface GatewaytypesLimitDTO {
* @type string
* @format date-time
*/
updated_at?: string;
updated_at?: Date;
}
export interface GatewaytypesIngestionKeyDTO {
@@ -3119,12 +3119,12 @@ export interface GatewaytypesIngestionKeyDTO {
* @type string
* @format date-time
*/
created_at?: string;
created_at?: Date;
/**
* @type string
* @format date-time
*/
expires_at?: string;
expires_at?: Date;
/**
* @type string
*/
@@ -3145,7 +3145,7 @@ export interface GatewaytypesIngestionKeyDTO {
* @type string
* @format date-time
*/
updated_at?: string;
updated_at?: Date;
/**
* @type string
*/
@@ -3169,7 +3169,7 @@ export interface GatewaytypesPostableIngestionKeyDTO {
* @type string
* @format date-time
*/
expires_at?: string;
expires_at?: Date;
/**
* @type string
*/
@@ -4439,7 +4439,7 @@ export interface LlmpricingruletypesLLMPricingRuleDTO {
* @type string
* @format date-time
*/
createdAt?: string;
createdAt?: Date;
/**
* @type string
*/
@@ -4478,13 +4478,13 @@ export interface LlmpricingruletypesLLMPricingRuleDTO {
* @type string,null
* @format date-time
*/
syncedAt?: string | null;
syncedAt?: Date | null;
unit: LlmpricingruletypesLLMPricingRuleUnitDTO;
/**
* @type string
* @format date-time
*/
updatedAt?: string;
updatedAt?: Date;
/**
* @type string
*/
@@ -5710,7 +5710,7 @@ export interface Querybuildertypesv5RawRowDTO {
* @type string
* @format date-time
*/
timestamp?: string;
timestamp?: Date;
}
export interface Querybuildertypesv5RawDataDTO {
@@ -6179,7 +6179,7 @@ export interface RuletypesRecurrenceDTO {
* @type string,null
* @format date-time
*/
endTime?: string | null;
endTime?: Date | null;
/**
* @type array,null
*/
@@ -6189,7 +6189,7 @@ export interface RuletypesRecurrenceDTO {
* @type string
* @format date-time
*/
startTime: string;
startTime: Date;
}
export interface RuletypesScheduleDTO {
@@ -6197,13 +6197,13 @@ export interface RuletypesScheduleDTO {
* @type string
* @format date-time
*/
endTime?: string;
endTime?: Date;
recurrence?: RuletypesRecurrenceDTO;
/**
* @type string
* @format date-time
*/
startTime?: string;
startTime?: Date;
/**
* @type string
*/
@@ -6219,7 +6219,7 @@ export interface RuletypesPlannedMaintenanceDTO {
* @type string
* @format date-time
*/
createdAt?: string;
createdAt?: Date;
/**
* @type string
*/
@@ -6243,7 +6243,7 @@ export interface RuletypesPlannedMaintenanceDTO {
* @type string
* @format date-time
*/
updatedAt?: string;
updatedAt?: Date;
/**
* @type string
*/
@@ -6406,7 +6406,7 @@ export interface RuletypesRuleDTO {
* @type string
* @format date-time
*/
createdAt?: string;
createdAt?: Date;
/**
* @type string
*/
@@ -6455,7 +6455,7 @@ export interface RuletypesRuleDTO {
* @type string
* @format date-time
*/
updatedAt?: string;
updatedAt?: Date;
/**
* @type string
*/
@@ -6474,7 +6474,7 @@ export interface ServiceaccounttypesGettableFactorAPIKeyDTO {
* @type string
* @format date-time
*/
createdAt?: string;
createdAt?: Date;
/**
* @type integer
* @minimum 0
@@ -6488,7 +6488,7 @@ export interface ServiceaccounttypesGettableFactorAPIKeyDTO {
* @type string
* @format date-time
*/
lastObservedAt: string;
lastObservedAt: Date;
/**
* @type string
*/
@@ -6501,7 +6501,7 @@ export interface ServiceaccounttypesGettableFactorAPIKeyDTO {
* @type string
* @format date-time
*/
updatedAt?: string;
updatedAt?: Date;
}
export interface ServiceaccounttypesGettableFactorAPIKeyWithKeyDTO {
@@ -6546,7 +6546,7 @@ export interface ServiceaccounttypesServiceAccountDTO {
* @type string
* @format date-time
*/
createdAt?: string;
createdAt?: Date;
/**
* @type string
*/
@@ -6571,7 +6571,7 @@ export interface ServiceaccounttypesServiceAccountDTO {
* @type string
* @format date-time
*/
updatedAt?: string;
updatedAt?: Date;
}
export interface ServiceaccounttypesServiceAccountRoleDTO {
@@ -6579,7 +6579,7 @@ export interface ServiceaccounttypesServiceAccountRoleDTO {
* @type string
* @format date-time
*/
createdAt?: string;
createdAt?: Date;
/**
* @type string
*/
@@ -6597,7 +6597,7 @@ export interface ServiceaccounttypesServiceAccountRoleDTO {
* @type string
* @format date-time
*/
updatedAt?: string;
updatedAt?: Date;
}
export interface ServiceaccounttypesServiceAccountWithRolesDTO {
@@ -6605,7 +6605,7 @@ export interface ServiceaccounttypesServiceAccountWithRolesDTO {
* @type string
* @format date-time
*/
createdAt?: string;
createdAt?: Date;
/**
* @type string
*/
@@ -6634,7 +6634,7 @@ export interface ServiceaccounttypesServiceAccountWithRolesDTO {
* @type string
* @format date-time
*/
updatedAt?: string;
updatedAt?: Date;
}
export interface ServiceaccounttypesUpdatableFactorAPIKeyDTO {
@@ -6676,7 +6676,7 @@ export interface SpantypesSpanMapperGroupDTO {
* @type string
* @format date-time
*/
createdAt?: string;
createdAt?: Date;
/**
* @type string
*/
@@ -6701,7 +6701,7 @@ export interface SpantypesSpanMapperGroupDTO {
* @type string
* @format date-time
*/
updatedAt?: string;
updatedAt?: Date;
/**
* @type string
*/
@@ -6770,7 +6770,7 @@ export interface SpantypesSpanMapperDTO {
* @type string
* @format date-time
*/
createdAt?: string;
createdAt?: Date;
/**
* @type string
*/
@@ -6796,7 +6796,7 @@ export interface SpantypesSpanMapperDTO {
* @type string
* @format date-time
*/
updatedAt?: string;
updatedAt?: Date;
/**
* @type string
*/
@@ -7163,7 +7163,7 @@ export interface TypesDeprecatedUserDTO {
* @type string
* @format date-time
*/
createdAt?: string;
createdAt?: Date;
/**
* @type string
*/
@@ -7196,7 +7196,7 @@ export interface TypesDeprecatedUserDTO {
* @type string
* @format date-time
*/
updatedAt?: string;
updatedAt?: Date;
}
export interface TypesIdentifiableDTO {
@@ -7211,7 +7211,7 @@ export interface TypesInviteDTO {
* @type string
* @format date-time
*/
createdAt?: string;
createdAt?: Date;
/**
* @type string
*/
@@ -7244,7 +7244,7 @@ export interface TypesInviteDTO {
* @type string
* @format date-time
*/
updatedAt?: string;
updatedAt?: Date;
}
export interface TypesOrganizationDTO {
@@ -7256,7 +7256,7 @@ export interface TypesOrganizationDTO {
* @type string
* @format date-time
*/
createdAt?: string;
createdAt?: Date;
/**
* @type string
*/
@@ -7278,7 +7278,7 @@ export interface TypesOrganizationDTO {
* @type string
* @format date-time
*/
updatedAt?: string;
updatedAt?: Date;
}
export interface TypesPostableInviteDTO {
@@ -7345,7 +7345,7 @@ export interface TypesResetPasswordTokenDTO {
* @type string
* @format date-time
*/
expiresAt?: string;
expiresAt?: Date;
/**
* @type string
*/
@@ -7372,7 +7372,7 @@ export interface TypesUserDTO {
* @type string
* @format date-time
*/
createdAt?: string;
createdAt?: Date;
/**
* @type string
*/
@@ -7401,7 +7401,7 @@ export interface TypesUserDTO {
* @type string
* @format date-time
*/
updatedAt?: string;
updatedAt?: Date;
}
export interface ZeustypesHostDTO {

View File

@@ -5,7 +5,6 @@ import { useSelector } from 'react-redux';
import { Loader, Search } from '@signozhq/icons';
import { Color } from '@signozhq/design-tokens';
import {
Button,
Flex,
Input,
InputRef,
@@ -17,6 +16,7 @@ import {
Tooltip,
} from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { Button } from '@signozhq/ui/button';
import type { FilterDropdownProps } from 'antd/lib/table/interface';
import logEvent from 'api/common/logEvent';
import {
@@ -105,9 +105,8 @@ const getColumnSearchProps = (
/>
<Space>
<Button
type="primary"
size="small"
onClick={(): void => handleSearch(selectedKeys as string[], confirm)}
size="sm"
>
<Flex align="center" gap={4}>
<Search size="md" />
@@ -116,17 +115,19 @@ const getColumnSearchProps = (
</Button>
<Button
onClick={(): void => clearFilters && handleReset(clearFilters, confirm)}
size="small"
style={{ width: 90 }}
size="sm"
variant="outlined"
color="secondary"
>
Reset
</Button>
<Button
type="link"
size="small"
onClick={(): void => {
close();
}}
size="sm"
variant="link"
>
close
</Button>

View File

@@ -1,7 +1,7 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { useMutation } from 'react-query';
import { Check, ChevronsDown, ScrollText, X } from '@signozhq/icons';
import { Button, Flex, Modal } from 'antd';
import { Flex, Modal } from 'antd';
import updateUserPreference from 'api/v1/user/preferences/name/update';
import cx from 'classnames';
import { USER_PREFERENCES } from 'constants/userPreferences';
@@ -14,6 +14,7 @@ import { UserPreference } from 'types/api/preferences/preference';
import ChangelogRenderer from './components/ChangelogRenderer';
import './ChangelogModal.styles.scss';
import { Button } from '@signozhq/ui/button';
interface Props {
changelog: ChangelogSchema;
@@ -115,13 +116,13 @@ function ChangelogModal({ changelog, onClose }: Props): JSX.Element {
>
{!isCloudUser && (
<div className="changelog-modal-footer-ctas">
<Button type="default" onClick={onClose}>
<Button onClick={onClose} variant="outlined" color="secondary">
<Flex align="center" gap="4px">
<X size="md" />
Skip for now
</Flex>
</Button>
<Button type="primary" onClick={onClickUpdateWorkspace}>
<Button onClick={onClickUpdateWorkspace}>
<Flex align="center" gap="4px">
<Check size="md" />
Update my workspace

View File

@@ -1,8 +1,9 @@
import { useState } from 'react';
import { useMutation } from 'react-query';
import { useLocation } from 'react-router-dom';
import { Button, Modal } from 'antd';
import { Modal } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { Button } from '@signozhq/ui/button';
import logEvent from 'api/common/logEvent';
import updateCreditCardApi from 'api/v1/checkout/create';
import { useNotifications } from 'hooks/useNotifications';
@@ -72,6 +73,8 @@ export default function ChatSupportGateway(): JSX.Element {
setIsAddCreditCardModalOpen(true);
}}
variant="outlined"
color="secondary"
>
<MessageSquareText size={24} />
</Button>
@@ -90,19 +93,19 @@ export default function ChatSupportGateway(): JSX.Element {
key="cancel"
onClick={(): void => setIsAddCreditCardModalOpen(false)}
className="cancel-btn"
icon={<X size={16} />}
variant="outlined"
color="secondary"
prefix={<X size={16} />}
>
Cancel
</Button>,
<Button
key="submit"
type="primary"
icon={<CreditCard size={16} />}
size="middle"
loading={isLoadingBilling}
disabled={isLoadingBilling}
onClick={handleAddCreditCard}
className="add-credit-card-btn"
prefix={<CreditCard size={16} />}
>
Add Credit Card
</Button>,

View File

@@ -1,5 +1,5 @@
import { Calendar } from '@signozhq/ui/calendar';
import { Button } from 'antd';
import { Button } from '@signozhq/ui/button';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import dayjs from 'dayjs';
import { Calendar as CalendarIcon, Check, X } from '@signozhq/icons';
@@ -78,18 +78,20 @@ function CalendarContainer({
<div className="calendar-actions">
<Button
type="primary"
className="periscope-btn secondary cancel-btn"
className="cancel-btn"
onClick={onCancel}
icon={<X size={12} />}
prefix={<X size={12} />}
variant="outlined"
color="secondary"
>
Cancel
</Button>
<Button
type="primary"
className="periscope-btn primary apply-btn"
className="apply-btn"
onClick={onApply}
icon={<Check size={12} />}
prefix={<Check size={12} />}
variant="solid"
color="primary"
>
Apply
</Button>

View File

@@ -108,7 +108,7 @@
}
.info-text:hover {
&.ant-btn-text {
& {
background-color: unset !important;
}
}

View File

@@ -8,7 +8,6 @@ import {
} from 'react';
import { useLocation } from 'react-router-dom';
import { Color } from '@signozhq/design-tokens';
import { Button } from 'antd';
import logEvent from 'api/common/logEvent';
import cx from 'classnames';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
@@ -32,6 +31,7 @@ import TimezonePicker from './TimezonePicker';
import { Timezone } from './timezoneUtils';
import './CustomTimePicker.styles.scss';
import { Button } from '@signozhq/ui/button';
const TO_MILLISECONDS_FACTOR = 1000_000;
@@ -177,13 +177,13 @@ function CustomTimePickerPopoverContent({
<div className="relative-date-time-section">
{options.map((option) => (
<Button
type="text"
className="time-btns"
key={option.label + option.value}
onClick={(): void => {
handleExitLiveLogs();
onSelectHandler(option.label, option.value);
}}
variant="ghost"
>
{option.label}
</Button>
@@ -249,15 +249,14 @@ function CustomTimePickerPopoverContent({
{isLogsExplorerPage && isLogsListView && (
<Button
className={cx('data-time-live', isLiveLogsEnabled ? 'active' : '')}
type="text"
onClick={handleGoLive}
variant="ghost"
>
Live
</Button>
)}
{options.map((option) => (
<Button
type="text"
key={option.label + option.value}
onClick={(e: React.MouseEvent<HTMLButtonElement>): void => {
e.stopPropagation();
@@ -271,6 +270,7 @@ function CustomTimePickerPopoverContent({
? option.value === 'custom' && !isLiveLogsEnabled && 'active'
: selectedTime === option.value && !isLiveLogsEnabled && 'active',
)}
variant="ghost"
>
<span className="time-label">{option.label}</span>
@@ -370,11 +370,12 @@ function CustomTimePickerPopoverContent({
<div className="timezone-container__right">
<Button
type="text"
size="small"
className="periscope-btn text timezone-change-button"
className="timezone-change-button"
onClick={handleTimezoneHintClick}
icon={<PenLine size={10} />}
size="sm"
variant="ghost"
prefix={<PenLine size={10} />}
color="none"
>
Change Timezone
</Button>

View File

@@ -1,6 +1,7 @@
import { useCallback, useMemo, useState } from 'react';
import { Button, Popover, Radio, Tooltip } from 'antd';
import { Popover, Radio, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { Button } from '@signozhq/ui/button';
import { TelemetryFieldKey } from 'api/v5/v5';
import { useExportRawData } from 'hooks/useDownloadOptionsMenu/useDownloadOptionsMenu';
import { Download, LoaderCircle } from '@signozhq/icons';
@@ -104,12 +105,11 @@ export default function DownloadOptionsMenu({
)}
<Button
type="primary"
icon={<Download size={16} />}
onClick={handleExport}
className="export-button"
disabled={isDownloading}
loading={isDownloading}
prefix={<Download size={16} />}
>
Export
</Button>
@@ -137,16 +137,18 @@ export default function DownloadOptionsMenu({
>
<Tooltip title="Download" placement="top">
<Button
className="periscope-btn ghost"
icon={
data-testid={`periscope-btn-download-${dataSource}`}
disabled={isDownloading}
variant="outlined"
color="secondary"
size="icon"
prefix={
isDownloading ? (
<LoaderCircle size={14} className="animate-spin" />
) : (
<Download size={14} />
)
}
data-testid={`periscope-btn-download-${dataSource}`}
disabled={isDownloading}
/>
</Tooltip>
</Popover>

View File

@@ -1,8 +1,9 @@
import { useState } from 'react';
import { Ellipsis } from '@signozhq/icons';
import { Button, Dropdown, MenuProps } from 'antd';
import { Dropdown, MenuProps } from 'antd';
import './DropDown.styles.scss';
import { Button } from '@signozhq/ui/button';
function DropDown({
element,
@@ -31,12 +32,12 @@ function DropDown({
open={isDdOpen}
>
<Button
type="link"
className={`dropdown-button`}
onClick={(e): void => {
e.preventDefault();
setDdOpen(true);
}}
variant="link"
>
<Ellipsis className="dropdown-icon" size={16} />
</Button>

View File

@@ -59,7 +59,7 @@ function getDeleteTooltip(
function getInviteButtonLabel(
isLoading: boolean,
existingToken: { expiresAt?: string } | undefined,
existingToken: { expiresAt?: Date } | undefined,
isExpired: boolean,
notFound: boolean,
): string {

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { Color } from '@signozhq/design-tokens';
import { Button, Modal, Tag } from 'antd';
import { Modal, Tag } from 'antd';
import { CircleAlert, X } from '@signozhq/icons';
import KeyValueLabel from 'periscope/components/KeyValueLabel';
import { useAppContext } from 'providers/App/App';
@@ -9,6 +9,7 @@ import APIError from 'types/api/error';
import ErrorContent from './components/ErrorContent';
import './ErrorModal.styles.scss';
import { Button } from '@signozhq/ui/button';
type Props = {
error: APIError;
@@ -73,10 +74,11 @@ function ErrorModal({
<div className="error-modal__version-placeholder" />
)}
<Button
type="default"
className="close-button"
onClick={handleClose}
data-testid="close-button"
variant="outlined"
color="secondary"
>
<X size={16} color={Color.BG_VANILLA_400} />
</Button>

View File

@@ -1,16 +1,8 @@
import { useState } from 'react';
import { useCopyToClipboard } from 'react-use';
import {
Button,
Col,
Dropdown,
MenuProps,
Popover,
Row,
Select,
Space,
} from 'antd';
import { Col, Dropdown, MenuProps, Popover, Row, Select, Space } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { Button } from '@signozhq/ui/button';
import axios from 'axios';
import TextToolTip from 'components/TextToolTip';
import { SOMETHING_WENT_WRONG } from 'constants/api';
@@ -159,7 +151,6 @@ function ExplorerCard({
],
};
const saveButtonType = isQueryUpdated ? 'default' : 'primary';
const saveButtonIcon = isQueryUpdated ? null : <Save size="md" />;
const showSaveView = false;
@@ -210,7 +201,7 @@ function ExplorerCard({
</Space>
)}
{isQueryUpdated && (
<Button type="primary" icon={<Save />} onClick={onUpdateQueryHandler}>
<Button onClick={onUpdateQueryHandler} prefix={<Save />}>
Save changes
</Button>
)}
@@ -230,9 +221,10 @@ function ExplorerCard({
onOpenChange={handleOpenChange}
>
<Button
type={saveButtonType}
icon={saveButtonIcon}
data-testid="traces-save-view-action"
variant="outlined"
color="secondary"
prefix={saveButtonIcon ?? undefined}
>
{isQueryUpdated
? SaveButtonText.SAVE_AS_NEW_VIEW

View File

@@ -1,176 +0,0 @@
import { useCallback, useMemo, useState } from 'react';
import { toast } from '@signozhq/ui/sonner';
import { Button } from '@signozhq/ui/button';
import { Input } from '@signozhq/ui/input';
import useDebouncedFn from 'hooks/useDebouncedFunction';
import { Check, TableColumnsSplit, X } from '@signozhq/icons';
import { FloatingPanel } from 'periscope/components/FloatingPanel';
import { TelemetryFieldKey } from 'types/api/v5/queryRange';
import { DataSource } from 'types/common/queryBuilder';
import AddedFields from './AddedFields';
import OtherFields from './OtherFields';
import styles from './FieldsSelector.module.scss';
const DEFAULT_PANEL_WIDTH = 350;
const DEFAULT_PANEL_HEIGHT_OFFSET = 100;
const DEFAULT_PANEL_RIGHT_INSET = 100;
const DEFAULT_PANEL_TOP_INSET = 50;
interface FieldsSelectorProps {
isOpen: boolean;
title: string;
fields: TelemetryFieldKey[];
onFieldsChange: (fields: TelemetryFieldKey[]) => void;
onClose: () => void;
signal: DataSource;
maxFields?: number;
width?: number;
height?: number;
defaultPosition?: { x: number; y: number };
}
function FieldsSelector({
isOpen,
title,
fields,
onFieldsChange,
onClose,
signal,
maxFields,
width = DEFAULT_PANEL_WIDTH,
height,
defaultPosition,
}: FieldsSelectorProps): JSX.Element | null {
if (!isOpen) {
return null;
}
const resolvedHeight =
height ?? window.innerHeight - DEFAULT_PANEL_HEIGHT_OFFSET;
const resolvedPosition = defaultPosition ?? {
x: window.innerWidth - width - DEFAULT_PANEL_RIGHT_INSET,
y: DEFAULT_PANEL_TOP_INSET,
};
const [draftFields, setDraftFields] = useState<TelemetryFieldKey[]>(fields);
const [inputValue, setInputValue] = useState('');
const [debouncedInputValue, setDebouncedInputValue] = useState('');
const debouncedUpdate = useDebouncedFn((value) => {
setDebouncedInputValue(value as string);
}, 400);
const handleInputChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>): void => {
const value = e.target.value.trim().toLowerCase();
setInputValue(value);
debouncedUpdate(value);
},
[debouncedUpdate],
);
const handleAdd = useCallback(
(field: TelemetryFieldKey): void => {
if (maxFields !== undefined && draftFields.length >= maxFields) {
return;
}
if (draftFields.some((f) => f.name === field.name)) {
return;
}
setDraftFields((prev) => [...prev, field]);
},
[draftFields, maxFields],
);
const handleSave = useCallback((): void => {
onFieldsChange(draftFields);
toast.success('Saved successfully', {
position: 'top-right',
});
onClose();
}, [draftFields, onFieldsChange, onClose]);
const handleDiscard = useCallback((): void => {
setDraftFields(fields);
}, [fields]);
const hasUnsavedChanges = useMemo(
() =>
!(
draftFields.length === fields.length &&
draftFields.every((f, i) => f.name === fields[i]?.name)
),
[draftFields, fields],
);
const isAtLimit = maxFields !== undefined && draftFields.length >= maxFields;
return (
<FloatingPanel
isOpen
width={width}
height={resolvedHeight}
defaultPosition={resolvedPosition}
enableResizing={false}
>
<div className={styles.root}>
<div className={styles.header}>
<div className={styles.title}>
<TableColumnsSplit size={16} />
{title}
</div>
<X className={styles.closeIcon} size={16} onClick={onClose} />
</div>
<section>
<Input
className={styles.searchInput}
type="text"
value={inputValue}
placeholder="Search for a field..."
onChange={handleInputChange}
/>
</section>
<AddedFields
inputValue={inputValue}
fields={draftFields}
onFieldsChange={setDraftFields}
maxFields={maxFields}
/>
<OtherFields
signal={signal}
debouncedInputValue={debouncedInputValue}
addedFields={draftFields}
onAdd={handleAdd}
isAtLimit={isAtLimit}
/>
{hasUnsavedChanges && (
<div className={styles.footer}>
<Button
variant="outlined"
color="secondary"
onClick={handleDiscard}
prefix={<X width={14} height={14} />}
>
Discard
</Button>
<Button
variant="solid"
color="primary"
onClick={handleSave}
prefix={<Check width={14} height={14} />}
>
Save changes
</Button>
</div>
)}
</div>
</FloatingPanel>
);
}
export default FieldsSelector;

View File

@@ -1 +0,0 @@
export { default } from './FieldsSelector';

View File

@@ -1,8 +1,9 @@
import { useCallback, useEffect, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { toast } from '@signozhq/ui/sonner';
import { Button, Input, Radio, RadioChangeEvent } from 'antd';
import { Input, Radio, RadioChangeEvent } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { Button } from '@signozhq/ui/button';
import logEvent from 'api/common/logEvent';
import { handleContactSupport } from 'container/Integrations/utils';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
@@ -125,11 +126,11 @@ function FeedbackModal({ onClose }: { onClose: () => void }): JSX.Element {
<div className="feedback-modal-content-footer">
<Button
className="periscope-btn primary"
type="primary"
onClick={handleSubmit}
loading={isLoading}
disabled={feedback.length === 0}
variant="solid"
color="primary"
>
Submit
</Button>

View File

@@ -5,8 +5,6 @@ import { Button } from '@signozhq/ui/button';
import { TooltipSimple } from '@signozhq/ui/tooltip';
import { Popover } from 'antd';
import logEvent from 'api/common/logEvent';
import { AIAssistantEvents } from 'container/AIAssistant/events';
import { normalizePage } from 'container/AIAssistant/hooks/useAIAssistantAnalyticsContext';
import {
openAIAssistant,
useAIAssistantStore,
@@ -52,14 +50,6 @@ function HeaderRightSection({
setOpenAnnouncementsModal(false);
}, [location.pathname]);
const handleOpenAIAssistant = useCallback((): void => {
void logEvent(AIAssistantEvents.Opened, {
source: 'header',
currentPage: normalizePage(location.pathname),
});
openAIAssistant();
}, [location.pathname]);
const handleOpenShareURLModal = useCallback((): void => {
logEvent('Share: Clicked', {
page: location.pathname,
@@ -111,7 +101,7 @@ function HeaderRightSection({
<Button
variant="solid"
color="secondary"
onClick={handleOpenAIAssistant}
onClick={openAIAssistant}
aria-label={
showHeaderPendingBadge
? pendingUserInputCount === 1
@@ -166,12 +156,12 @@ function HeaderRightSection({
variant="ghost"
size="icon"
aria-label="Announcements"
prefix={<Inbox size={14} />}
onClick={(): void => {
logEvent('Announcements: Clicked', {
page: location.pathname,
});
}}
prefix={<Inbox size={14} />}
/>
</Popover>
)}

View File

@@ -4,8 +4,9 @@ import { useSelector } from 'react-redux';
import { matchPath, useLocation } from 'react-router-dom';
import { useCopyToClipboard } from 'react-use';
import { Color } from '@signozhq/design-tokens';
import { Button, Switch } from 'antd';
import { Switch } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { Button } from '@signozhq/ui/button';
import logEvent from 'api/common/logEvent';
import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes';
@@ -155,9 +156,10 @@ function ShareURLModal(): JSX.Element {
</div>
<Button
className="periscope-btn secondary"
onClick={handleCopyURL}
icon={isURLCopied ? <Check size={14} /> : <Link2 size={14} />}
variant="outlined"
color="secondary"
prefix={isURLCopied ? <Check size={14} /> : <Link2 size={14} />}
>
Copy page link
</Button>

View File

@@ -1,8 +1,8 @@
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Color } from '@signozhq/design-tokens';
import { Button } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { Button } from '@signozhq/ui/button';
import logEvent from 'api/common/logEvent';
import { useNotifications } from 'hooks/useNotifications';
import { CircleCheckBig, HandPlatter } from '@signozhq/icons';
@@ -57,17 +57,18 @@ export default function WaitlistFragment({
</Typography.Text>
<Button
className="periscope-btn join-waitlist-btn"
type="default"
className="join-waitlist-btn"
loading={isSubmitting}
icon={
onClick={handleJoinWaitlist}
variant="outlined"
color="secondary"
prefix={
isSuccess ? (
<CircleCheckBig size={16} color={Color.BG_FOREST_500} />
) : (
<HandPlatter size={16} />
)
}
onClick={handleJoinWaitlist}
>
Get early access
</Button>

View File

@@ -1,6 +1,7 @@
import { useState } from 'react';
import { Button, Input } from 'antd';
import { Input } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { Button } from '@signozhq/ui/button';
import cx from 'classnames';
import { X } from '@signozhq/icons';
@@ -55,9 +56,12 @@ function InputWithLabel({
{labelAfter && <Typography.Text className="label">{label}</Typography.Text>}
{onClose && (
<Button
className="periscope-btn ghost close-btn"
icon={closeIcon || <X size={16} />}
className="close-btn"
onClick={onClose}
variant="outlined"
color="secondary"
size="icon"
prefix={(closeIcon as JSX.Element) || <X size={16} />}
/>
)}
</div>

View File

@@ -2,7 +2,7 @@
color: var(--bg-amber-500);
border-color: var(--bg-amber-500);
> .ant-btn:hover {
> button:hover {
color: var(--bg-amber-400) !important;
border-color: var(--bg-amber-300) !important;
}

View File

@@ -1,8 +1,9 @@
import { useMemo, useState } from 'react';
import { useMutation } from 'react-query';
import { useLocation } from 'react-router-dom';
import { Button, Modal, Tooltip } from 'antd';
import { Modal, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { Button } from '@signozhq/ui/button';
import logEvent from 'api/common/logEvent';
import updateCreditCardApi from 'api/v1/checkout/create';
import cx from 'classnames';
@@ -170,7 +171,9 @@ function LaunchChatSupport({
<Button
className={cx('periscope-btn', 'facing-issue-button', className)}
onClick={handleFacingIssuesClick}
icon={<CircleHelp size={14} />}
variant="outlined"
color="secondary"
prefix={<CircleHelp size={14} />}
>
{buttonText || 'Facing issues?'}
</Button>
@@ -189,19 +192,19 @@ function LaunchChatSupport({
key="cancel"
onClick={(): void => setIsAddCreditCardModalOpen(false)}
className="cancel-btn"
icon={<X size={16} />}
variant="outlined"
color="secondary"
prefix={<X size={16} />}
>
Cancel
</Button>,
<Button
key="submit"
type="primary"
icon={<CreditCard size={16} />}
size="middle"
loading={isLoadingBilling}
disabled={isLoadingBilling}
onClick={handleAddCreditCard}
className="add-credit-card-btn"
prefix={<CreditCard size={16} />}
>
Add Credit Card
</Button>,

View File

@@ -1,9 +1,9 @@
import { Color } from '@signozhq/design-tokens';
import { Button } from 'antd';
import { ArrowUpRight } from '@signozhq/icons';
import { openInNewTab } from 'utils/navigation';
import './LearnMore.styles.scss';
import { Button } from '@signozhq/ui/button';
type LearnMoreProps = {
text?: string;
@@ -19,7 +19,7 @@ function LearnMore({ text, url, onClick }: LearnMoreProps): JSX.Element {
}
};
return (
<Button type="link" className="learn-more" onClick={handleClick}>
<Button className="learn-more" onClick={handleClick} variant="link">
<div className="learn-more__text">{text}</div>
<ArrowUpRight size={16} color={Color.BG_ROBIN_400} />
</Button>

View File

@@ -17,7 +17,7 @@
.log-detail-drawer__title-right {
display: flex;
align-items: center;
.ant-btn {
button {
display: flex;
align-items: center;
}

View File

@@ -166,7 +166,7 @@
border-left: 1px solid var(--l1-border) !important;
}
.ant-btn-default {
button {
border: none;
box-shadow: none;
}

View File

@@ -9,7 +9,7 @@
border: 1px solid var(--l1-border);
background: var(--l2-background);
.ant-btn-default {
button {
border: none;
box-shadow: none;
padding: 9px;

View File

@@ -1,8 +1,9 @@
import { memo, MouseEventHandler } from 'react';
import { Link, TextSelect } from '@signozhq/icons';
import { Button, Tooltip } from 'antd';
import { Tooltip } from 'antd';
import './LogLinesActionButtons.styles.scss';
import { Button } from '@signozhq/ui/button';
export interface LogLinesActionButtonsProps {
handleShowContext: MouseEventHandler<HTMLElement>;
@@ -19,18 +20,22 @@ function LogLinesActionButtons({
<div className={`log-line-action-buttons ${customClassName}`}>
<Tooltip title="Show in Context">
<Button
size="small"
icon={<TextSelect size={14} />}
className="show-context-btn"
onClick={handleShowContext}
size="sm"
variant="outlined"
color="secondary"
prefix={<TextSelect size={14} />}
/>
</Tooltip>
<Tooltip title="Copy Link">
<Button
size="small"
icon={<Link size={14} />}
onClick={onLogCopy}
className="copy-log-btn"
size="sm"
variant="outlined"
color="secondary"
prefix={<Link size={14} />}
/>
</Tooltip>
</div>

View File

@@ -1,6 +1,7 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { Button, Input, InputNumber, Popover, Tooltip } from 'antd';
import { Input, InputNumber, Popover, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { Button } from '@signozhq/ui/button';
import type { DefaultOptionType } from 'antd/es/select';
import cx from 'classnames';
import { LogViewMode } from 'container/LogsTable';
@@ -223,7 +224,7 @@ function OptionsMenu({
<Button
onClick={(): void => setIsFontSizeOptionsOpen(false)}
className="back-btn"
type="text"
variant="ghost"
>
<ChevronLeft size={14} className="icon" />
<Typography.Text className="text">Select font size</Typography.Text>
@@ -235,7 +236,7 @@ function OptionsMenu({
setFontSizeValue(FontSize.SMALL);
}}
className="option-btn"
type="text"
variant="ghost"
>
<Typography.Text className="text">{FontSize.SMALL}</Typography.Text>
{fontSizeValue === FontSize.SMALL && (
@@ -247,7 +248,7 @@ function OptionsMenu({
setFontSizeValue(FontSize.MEDIUM);
}}
className="option-btn"
type="text"
variant="ghost"
>
<Typography.Text className="text">{FontSize.MEDIUM}</Typography.Text>
{fontSizeValue === FontSize.MEDIUM && (
@@ -259,7 +260,7 @@ function OptionsMenu({
setFontSizeValue(FontSize.LARGE);
}}
className="option-btn"
type="text"
variant="ghost"
>
<Typography.Text className="text">{FontSize.LARGE}</Typography.Text>
{fontSizeValue === FontSize.LARGE && (
@@ -338,10 +339,10 @@ function OptionsMenu({
<div className="title">Font Size</div>
<Button
className="value"
type="text"
onClick={(): void => {
setIsFontSizeOptionsOpen(true);
}}
variant="ghost"
>
<Typography.Text className="font-value">{fontSizeValue}</Typography.Text>
<ChevronRight size={14} className="icon" />
@@ -472,9 +473,11 @@ function LogsFormatOptionsMenu({
>
<Tooltip title="Options">
<Button
className="periscope-btn ghost"
icon={<SlidersVertical size="md" />}
data-testid="periscope-btn-format-options"
variant="outlined"
color="secondary"
size="icon"
prefix={<SlidersVertical size={14} />}
/>
</Tooltip>
</Popover>

View File

@@ -1,5 +1,4 @@
import { useEffect, useMemo, useState } from 'react';
import { Button } from 'antd';
import cx from 'classnames';
import { useOnboardingStatus } from 'hooks/messagingQueue/useOnboardingStatus';
import { Bolt, FolderTree } from '@signozhq/icons';
@@ -8,6 +7,7 @@ import { MessagingQueueHealthCheckService } from 'pages/MessagingQueues/Messagin
import AttributeCheckList from './AttributeCheckList';
import './MessagingQueueHealthCheck.styles.scss';
import { Button } from '@signozhq/ui/button';
interface MessagingQueueHealthCheckProps {
serviceToInclude: string[];
@@ -94,7 +94,9 @@ function MessagingQueueHealthCheck({
'config-btn',
missingConfiguration ? 'missing-config-btn' : '',
)}
icon={<Bolt size={12} />}
variant="outlined"
color="secondary"
prefix={<Bolt size={12} />}
>
<div className="config-btn-content">
{missingConfiguration

View File

@@ -18,8 +18,9 @@ import {
RefreshCw,
} from '@signozhq/icons';
import { Color } from '@signozhq/design-tokens';
import { Button, Checkbox, Select } from 'antd';
import { Checkbox, Select } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { Button } from '@signozhq/ui/button';
import cx from 'classnames';
import TextToolTip from 'components/TextToolTip/TextToolTip';
import { SOMETHING_WENT_WRONG } from 'constants/api';
@@ -767,11 +768,11 @@ const CustomMultiSelect: React.FC<CustomMultiSelectProps> = ({
<div className="option-badge">{capitalize(option.type)}</div>
)}
{option.value && ensureValidOption(option.value) && (
<Button type="text" className="only-btn">
<Button className="only-btn" variant="ghost">
{currentToggleTagValue({ option: option.value })}
</Button>
)}
<Button type="text" className="toggle-btn">
<Button className="toggle-btn" variant="ghost">
Toggle
</Button>
</div>

View File

@@ -13,12 +13,12 @@ import { javascript } from '@codemirror/lang-javascript';
import { copilot } from '@uiw/codemirror-theme-copilot';
import { githubLight } from '@uiw/codemirror-theme-github';
import CodeMirror, { EditorView, keymap } from '@uiw/react-codemirror';
import { Button } from 'antd';
import { Having } from 'api/v5/v5';
import { useQueryBuilderV2Context } from 'components/QueryBuilderV2/QueryBuilderV2Context';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { ChevronUp } from '@signozhq/icons';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { Button } from '@signozhq/ui/button';
const havingOperators = [
{
@@ -368,9 +368,12 @@ function HavingFilter({
}}
/>
<Button
className="close-btn periscope-btn ghost"
icon={<ChevronUp size={16} />}
className="close-btn"
onClick={onClose}
variant="outlined"
color="secondary"
size="icon"
prefix={<ChevronUp size={16} />}
/>
</div>
</div>

View File

@@ -1,5 +1,5 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { Button, Radio, RadioChangeEvent, Tooltip } from 'antd';
import { 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';
@@ -17,6 +17,7 @@ import HavingFilter from './HavingFilter/HavingFilter';
import { buildDefaultLegendFromGroupBy } from './utils';
import './QueryAddOns.styles.scss';
import { Button } from '@signozhq/ui/button';
interface AddOn {
icon: React.ReactNode;
@@ -370,9 +371,12 @@ function QueryAddOns({
/>
</div>
<Button
className="close-btn periscope-btn ghost"
icon={<ChevronUp size={16} />}
className="close-btn"
onClick={(): void => handleRemoveView('group_by')}
variant="outlined"
color="secondary"
size="icon"
prefix={<ChevronUp size={16} />}
/>
</div>
</div>
@@ -455,9 +459,12 @@ function QueryAddOns({
</div>
{!isListViewPanel && (
<Button
className="close-btn periscope-btn ghost"
icon={<ChevronUp size={16} />}
className="close-btn"
onClick={(): void => handleRemoveView('order_by')}
variant="outlined"
color="secondary"
size="icon"
prefix={<ChevronUp size={16} />}
/>
)}
</div>
@@ -488,9 +495,12 @@ function QueryAddOns({
</div>
<Button
className="close-btn periscope-btn ghost"
icon={<ChevronUp size={16} />}
className="close-btn"
onClick={(): void => handleRemoveView('reduce_to')}
variant="outlined"
color="secondary"
size="icon"
prefix={<ChevronUp size={16} />}
/>
</div>
</div>

View File

@@ -23,7 +23,7 @@ import CodeMirror, {
ViewPlugin,
ViewUpdate,
} from '@uiw/react-codemirror';
import { Button, Popover, Tooltip } from 'antd';
import { Popover, Tooltip } from 'antd';
import { getKeySuggestions } from 'api/querySuggestions/getKeySuggestions';
import { QUERY_BUILDER_KEY_TYPES } from 'constants/antlrQueryConstants';
import { QueryBuilderKeys } from 'constants/queryBuilder';
@@ -36,6 +36,7 @@ import { TracesAggregatorOperator } from 'types/common/queryBuilder';
import { useQueryBuilderV2Context } from '../../QueryBuilderV2Context';
import './QueryAggregation.styles.scss';
import { Button } from '@signozhq/ui/button';
const chipDecoration = Decoration.mark({
class: 'chip-decorator',
@@ -720,9 +721,10 @@ function QueryAggregationSelect({
overlayClassName="query-aggregation-error-popover"
>
<Button
type="text"
icon={<TriangleAlert size={14} color={Color.BG_CHERRY_500} />}
className="periscope-btn ghost query-aggregation-error-btn"
className="query-aggregation-error-btn"
variant="ghost"
size="icon"
prefix={<TriangleAlert size={14} color={Color.BG_CHERRY_500} />}
/>
</Popover>
</div>

View File

@@ -1,6 +1,7 @@
import { useMemo } from 'react';
import { Button, Tooltip } from 'antd';
import { Tooltip } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { Button } from '@signozhq/ui/button';
import WarningPopover from 'components/WarningPopover/WarningPopover';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
@@ -56,9 +57,11 @@ function TraceOperatorSection({
}
>
<Button
className="add-trace-operator-button periscope-btn"
icon={<DraftingCompass size={16} />}
className="add-trace-operator-button"
onClick={(): void => addTraceOperator?.()}
variant="outlined"
color="secondary"
prefix={<DraftingCompass size={16} />}
>
<div className="qb-trace-operator-button-container-text">
Add Trace Matching
@@ -92,9 +95,12 @@ export default function QueryFooter({
<div className="qb-add-new-query">
<Tooltip title={<div style={{ textAlign: 'center' }}>Add New Query</div>}>
<Button
className="add-new-query-button periscope-btn "
icon={<Plus size={16} />}
className="add-new-query-button"
onClick={addNewBuilderQuery}
variant="outlined"
color="secondary"
size="icon"
prefix={<Plus size={16} />}
/>
</Tooltip>
</div>
@@ -118,9 +124,11 @@ export default function QueryFooter({
}
>
<Button
className="add-formula-button periscope-btn "
icon={<Sigma size={16} />}
className="add-formula-button"
onClick={addNewFormula}
variant="outlined"
color="secondary"
prefix={<Sigma size={16} />}
>
Add Formula
</Button>

View File

@@ -14,7 +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, Tag, Tooltip } from 'antd';
import { Card, Collapse, Popover, Tag, Tooltip } from 'antd';
import { getKeySuggestions } from 'api/querySuggestions/getKeySuggestions';
import { getValueSuggestions } from 'api/querySuggestions/getValueSuggestion';
import cx from 'classnames';
@@ -49,6 +49,7 @@ import { queryExamples } from './constants';
import { combineInitialAndUserExpression } from './utils';
import './QuerySearch.styles.scss';
import { Button } from '@signozhq/ui/button';
const { Panel } = Collapse;
@@ -1484,15 +1485,16 @@ function QuerySearch({
>
{validation.isValid ? (
<Button
type="text"
icon={<CircleCheck size="md" />}
variant="ghost"
size="icon"
className="periscope-btn ghost"
prefix={<CircleCheck size={14} />}
/>
) : (
<Button
type="text"
icon={<TriangleAlert size={14} color={Color.BG_CHERRY_500} />}
className="periscope-btn ghost"
variant="ghost"
size="icon"
prefix={<TriangleAlert size={14} color={Color.BG_CHERRY_500} />}
/>
)}
</Popover>

View File

@@ -1,5 +1,6 @@
import { useCallback } from 'react';
import { Button, Tooltip } from 'antd';
import { Tooltip } from 'antd';
import { Button } from '@signozhq/ui/button';
import { Typography } from '@signozhq/ui/typography';
import cx from 'classnames';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
@@ -108,7 +109,7 @@ export default function TraceOperator({
)}
</div>
<Tooltip title="Remove Trace Operator" placement="topLeft">
<Button className="periscope-btn ghost" onClick={removeTraceOperator}>
<Button onClick={removeTraceOperator} variant="outlined" color="secondary">
<Trash2 size={14} />
</Button>
</Tooltip>

View File

@@ -15,7 +15,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, Popover } from 'antd';
import { Popover } from 'antd';
import cx from 'classnames';
import {
TRACE_OPERATOR_OPERATORS,
@@ -34,6 +34,7 @@ import { getInvolvedQueriesInTraceOperator } from './utils/utils';
import '../QuerySearch/QuerySearch.styles.scss';
import { CircleCheck, TriangleAlert } from '@signozhq/icons';
import { Button } from '@signozhq/ui/button';
// Custom extension to stop events
const stopEventsExtension = EditorView.domEventHandlers({
@@ -465,15 +466,15 @@ function TraceOperatorEditor({
>
{validation.isValid ? (
<Button
type="text"
icon={<CircleCheck size="md" />}
className="periscope-btn ghost"
variant="ghost"
size="icon"
prefix={<CircleCheck size={14} />}
/>
) : (
<Button
type="text"
icon={<TriangleAlert size={14} color={Color.BG_CHERRY_500} />}
className="periscope-btn ghost"
variant="ghost"
size="icon"
prefix={<TriangleAlert size={14} color={Color.BG_CHERRY_500} />}
/>
)}
</Popover>

View File

@@ -1,6 +1,7 @@
/* eslint-disable sonarjs/no-identical-functions */
import { Fragment, useMemo, useState } from 'react';
import { Button, Checkbox, Input, Skeleton } from 'antd';
import { Checkbox, Input, Skeleton } from 'antd';
import { Button } from '@signozhq/ui/button';
import { Typography } from '@signozhq/ui/typography';
import cx from 'classnames';
import { removeKeysFromExpression } from 'components/QueryBuilderV2/utils';
@@ -660,14 +661,14 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
{String(value)}
</Typography.Text>
)}
<Button type="text" className="only-btn">
<Button className="only-btn" variant="ghost">
{isSomeFilterPresentForCurrentAttribute
? currentFilterState[value] && !isMultipleValuesTrueForTheKey
? 'All'
: 'Only'
: 'Only'}
</Button>
<Button type="text" className="toggle-btn">
<Button className="toggle-btn" variant="ghost">
Toggle
</Button>
</div>

View File

@@ -1,7 +1,7 @@
import { Color } from '@signozhq/design-tokens';
import { Button } from 'antd';
import EmptyQuickFilterIcon from 'assets/CustomIcons/EmptyQuickFilterIcon';
import { ArrowUpRight } from '@signozhq/icons';
import { Button } from '@signozhq/ui/button';
const QUICK_FILTER_DOC_PATHS: Record<string, string> = {
severity_text: 'severity-text',
@@ -39,9 +39,9 @@ function LogsQuickFilterEmptyState({
</div>
</div>
<Button
type="link"
className="go-to-docs__button"
onClick={handleLearnMoreClick}
variant="link"
>
<div className="go-to-docs__button-text">Learn more</div>
<ArrowUpRight size={14} color={Color.BG_ROBIN_400} />

View File

@@ -1,5 +1,5 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import { Button, Collapse } from 'antd';
import { Collapse } from 'antd';
import {
IQuickFiltersConfig,
QuickFiltersSource,
@@ -21,6 +21,7 @@ import { Query, TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
import { v4 as uuid } from 'uuid';
import './Duration.styles.scss';
import { Button } from '@signozhq/ui/button';
export type FilterType = Record<
AllTraceFilterKeys,
@@ -299,9 +300,9 @@ function Duration({
/>
{activeKeys.includes('durationNano') && (
<Button
type="link"
onClick={onClearHandler}
data-testid="collapse-duration-clearBtn"
variant="link"
>
Clear All
</Button>

View File

@@ -14,10 +14,10 @@ import {
verticalListSortingStrategy,
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { Button } from 'antd';
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
import { GripVertical } from '@signozhq/icons';
import { Filter as FilterType } from 'types/api/quickFilters/getCustomFilters';
import { Button } from '@signozhq/ui/button';
function SortableFilter({
filter,
@@ -50,11 +50,13 @@ function SortableFilter({
</div>
{allowRemove && (
<Button
className="remove-filter-btn periscope-btn"
size="small"
className="remove-filter-btn"
onClick={(): void => {
onRemove(filter as FilterType);
}}
size="sm"
variant="outlined"
color="secondary"
>
Remove
</Button>

View File

@@ -1,5 +1,5 @@
import { useMemo } from 'react';
import { Button, Skeleton } from 'antd';
import { Skeleton } from 'antd';
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
import { SIGNAL_DATA_SOURCE_MAP } from 'components/QuickFilters/QuickFiltersSettings/constants';
import { SignalType } from 'components/QuickFilters/types';
@@ -12,6 +12,7 @@ import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { QueryKeyDataSuggestionsProps } from 'types/api/querySuggestions/types';
import { Filter as FilterType } from 'types/api/quickFilters/getCustomFilters';
import { DataSource } from 'types/common/queryBuilder';
import { Button } from '@signozhq/ui/button';
function OtherFiltersSkeleton(): JSX.Element {
return (
@@ -146,9 +147,11 @@ function OtherFilters({
<div key={filter.key} className="qf-filter-item other-filters-item">
<div className="qf-filter-key">{filter.key}</div>
<Button
className="add-filter-btn periscope-btn"
size="small"
className="add-filter-btn"
onClick={(): void => handleAddFilter(filter as FilterType)}
size="sm"
variant="outlined"
color="secondary"
>
Add
</Button>

View File

@@ -1,5 +1,5 @@
import { useMemo } from 'react';
import { Button, Input } from 'antd';
import { Input } from 'antd';
import { Check, TableColumnsSplit, X } from '@signozhq/icons';
import { Filter as FilterType } from 'types/api/quickFilters/getCustomFilters';
@@ -9,6 +9,7 @@ import useQuickFilterSettings from './hooks/useQuickFilterSettings';
import OtherFilters from './OtherFilters';
import './QuickFiltersSettings.styles.scss';
import { Button } from '@signozhq/ui/button';
function QuickFiltersSettings({
signal,
@@ -86,17 +87,17 @@ function QuickFiltersSettings({
{hasUnsavedChanges && (
<div className="qf-footer">
<Button
type="default"
onClick={handleDiscardChanges}
icon={<X size={16} />}
variant="outlined"
color="secondary"
prefix={<X size={16} />}
>
Discard
</Button>
<Button
type="primary"
onClick={handleSaveChanges}
icon={<Check size={16} />}
loading={isUpdatingCustomFilters}
prefix={<Check size={16} />}
>
Save changes
</Button>

View File

@@ -1,16 +1,15 @@
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Button, Tooltip } from 'antd';
import { Tooltip } from 'antd';
import refreshPaymentStatus from 'api/v3/licenses/put';
import cx from 'classnames';
import { RefreshCcw } from '@signozhq/icons';
import { useAppContext } from 'providers/App/App';
import { Button } from '@signozhq/ui/button';
function RefreshPaymentStatus({
btnShape,
type,
}: {
btnShape?: 'default' | 'round' | 'circle';
type?: 'button' | 'text' | 'tooltip';
}): JSX.Element {
const { t } = useTranslation(['failedPayment']);
@@ -35,12 +34,12 @@ function RefreshPaymentStatus({
<span className="refresh-payment-status-btn-wrapper">
<Tooltip title={type === 'tooltip' ? t('refreshPaymentStatus') : ''}>
<Button
type={type === 'text' ? 'text' : 'default'}
shape={btnShape}
className={cx('periscope-btn', { text: type === 'text' })}
onClick={handleRefreshPaymentStatus}
icon={<RefreshCcw size={14} />}
loading={isLoading}
variant="outlined"
color="secondary"
prefix={<RefreshCcw size={14} />}
>
{type !== 'tooltip' ? t('refreshPaymentStatus') : ''}
</Button>
@@ -49,7 +48,6 @@ function RefreshPaymentStatus({
);
}
RefreshPaymentStatus.defaultProps = {
btnShape: 'default',
type: 'button',
};

View File

@@ -4,7 +4,7 @@ import type {
TableColumnsType as ColumnsType,
TableColumnType as ColumnType,
} from 'antd';
import { Button, Dropdown, Flex, MenuProps, Switch } from 'antd';
import { Dropdown, Flex, MenuProps, Switch } from 'antd';
import logEvent from 'api/common/logEvent';
import LaunchChatSupport from 'components/LaunchChatSupport/LaunchChatSupport';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
@@ -21,6 +21,7 @@ import {
} from './utils';
import './DynamicColumnTable.syles.scss';
import { Button } from '@signozhq/ui/button';
function DynamicColumnTable({
tablesource,
@@ -133,9 +134,11 @@ function DynamicColumnTable({
>
<Button
className="dynamicColumnTable-button filter-btn"
size="middle"
icon={<SlidersHorizontal size={14} />}
data-testid="additional-filters-button"
variant="outlined"
color="secondary"
size="icon"
prefix={<SlidersHorizontal size={14} />}
/>
</Dropdown>
)}

View File

@@ -144,7 +144,6 @@ function RolesSelect(props: RolesSelectProps): JSX.Element {
loading={loading}
notFoundContent={notFoundContent}
options={options}
optionFilterProp="label"
optionRender={(option): JSX.Element => (
<Checkbox
checked={value.includes(option.value as string)}
@@ -163,7 +162,6 @@ function RolesSelect(props: RolesSelectProps): JSX.Element {
return (
<Select
id={id}
showSearch
value={value || undefined}
onChange={onChange}
placeholder={placeholder}
@@ -172,7 +170,6 @@ function RolesSelect(props: RolesSelectProps): JSX.Element {
loading={loading}
notFoundContent={notFoundContent}
options={options}
optionFilterProp="label"
getPopupContainer={getPopupContainer}
disabled={disabled}
/>

View File

@@ -28,7 +28,7 @@ const mockKey: ServiceaccounttypesGettableFactorAPIKeyDTO = {
id: 'key-1',
name: 'Original Key Name',
expiresAt: 0,
lastObservedAt: null as unknown as string,
lastObservedAt: null as unknown as Date,
serviceAccountId: 'sa-1',
};

View File

@@ -29,14 +29,14 @@ const keys: ServiceaccounttypesGettableFactorAPIKeyDTO[] = [
id: 'key-1',
name: 'Production Key',
expiresAt: 0,
lastObservedAt: null as unknown as string,
lastObservedAt: null as unknown as Date,
serviceAccountId: 'sa-1',
},
{
id: 'key-2',
name: 'Staging Key',
expiresAt: 1924905600, // 2030-12-31
lastObservedAt: '2026-03-10T10:00:00Z',
lastObservedAt: new Date('2026-03-10T10:00:00Z'),
serviceAccountId: 'sa-1',
},
];

View File

@@ -74,7 +74,7 @@
}
.ant-modal-footer {
.ant-btn {
button {
display: flex;
align-items: center;
gap: 4px;

View File

@@ -1,9 +1,10 @@
import React, { Dispatch, SetStateAction, useState } from 'react';
import { Check, Plus, X } from '@signozhq/icons';
import { Button, Flex, Tag } from 'antd';
import { Flex, Tag } from 'antd';
import Input from 'components/Input';
import './Tags.styles.scss';
import { Button } from '@signozhq/ui/button';
function Tags({ tags, setTags }: AddTagsProps): JSX.Element {
const [inputValue, setInputValue] = useState<string>('');
@@ -71,19 +72,19 @@ function Tags({ tags, setTags }: AddTagsProps): JSX.Element {
<div className="confirm-cancel-actions">
<Button
type="primary"
className="periscope-btn"
size="small"
icon={<Check size={14} />}
onClick={handleInputConfirm}
size="sm"
prefix={<Check size={14} />}
variant="outlined"
color="secondary"
/>
<Button
type="primary"
className="periscope-btn"
size="small"
icon={<X size={14} />}
onClick={hideInput}
size="sm"
prefix={<X size={14} />}
variant="outlined"
color="secondary"
/>
</div>
</div>
@@ -91,15 +92,14 @@ function Tags({ tags, setTags }: AddTagsProps): JSX.Element {
{!inputVisible && (
<Button
type="primary"
size="small"
style={{
fontSize: '11px',
}}
onClick={showInput}
size="sm"
prefix={<Plus size={14} />}
>
<Flex justify="center" align="center" gap={4}>
<Plus size="md" />
New Tag
</Flex>
</Button>

View File

@@ -1,6 +1,7 @@
import { Dispatch, SetStateAction, useCallback, useMemo } from 'react';
import { ChevronDown, Globe } from '@signozhq/icons';
import { Button, Dropdown } from 'antd';
import { Dropdown } from 'antd';
import { Button } from '@signozhq/ui/button';
import { Typography } from '@signozhq/ui/typography';
import TimeItems, {
timePreferance,
@@ -40,7 +41,7 @@ function TimePreference({
className="time-selection-target"
trigger={['click']}
>
<Button>
<Button variant="outlined" color="secondary">
<div className="button-selected-text">
<Globe size={14} />
<Typography.Text className="selected-value">

View File

@@ -10,5 +10,4 @@ export enum FeatureKeys {
ONBOARDING_V3 = 'onboarding_v3',
DOT_METRICS_ENABLED = 'dot_metrics_enabled',
USE_JSON_BODY = 'use_json_body',
USE_FINE_GRAINED_AUTHZ = 'use_fine_grained_authz',
}

View File

@@ -108,7 +108,4 @@ export const REACT_QUERY_KEY = {
// Dashboard Grid Card Query Keys
DASHBOARD_GRID_CARD_QUERY_RANGE: 'DASHBOARD_GRID_CARD_QUERY_RANGE',
// Fields Selector Query Keys
GET_FIELDS_SELECTOR_SUGGESTIONS: 'GET_FIELDS_SELECTOR_SUGGESTIONS',
} as const;

View File

@@ -1,20 +1,13 @@
import { useCallback, useEffect, useState } from 'react';
import { createPortal } from 'react-dom';
import { useHistory, useLocation } from 'react-router-dom';
import { useHistory } from 'react-router-dom';
import { Button } from '@signozhq/ui/button';
import { TooltipSimple } from '@signozhq/ui/tooltip';
import ROUTES from 'constants/routes';
import { History, Maximize2, Minus, Plus, Sparkles, X } from '@signozhq/icons';
import logEvent from 'api/common/logEvent';
import HistorySidebar from '../components/ConversationsList';
import ConversationView from '../ConversationView';
import { AIAssistantEvents } from '../events';
import {
normalizePage,
useAIAssistantAnalyticsContext,
} from '../hooks/useAIAssistantAnalyticsContext';
import { useAIAssistantStore } from '../store/useAIAssistantStore';
import { VariantContext } from '../VariantContext';
@@ -31,7 +24,6 @@ import styles from './AIAssistantModal.module.scss';
// eslint-disable-next-line sonarjs/cognitive-complexity
export default function AIAssistantModal(): JSX.Element | null {
const history = useHistory();
const { pathname } = useLocation();
const [showHistory, setShowHistory] = useState(false);
const isOpen = useAIAssistantStore((s) => s.isModalOpen);
@@ -44,7 +36,6 @@ export default function AIAssistantModal(): JSX.Element | null {
const startNewConversation = useAIAssistantStore(
(s) => s.startNewConversation,
);
const analyticsCtx = useAIAssistantAnalyticsContext();
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent): void => {
@@ -64,10 +55,6 @@ export default function AIAssistantModal(): JSX.Element | null {
} else {
startNewConversation();
setShowHistory(false);
void logEvent(AIAssistantEvents.Opened, {
source: 'shortcut',
currentPage: normalizePage(pathname),
});
openModal();
}
return;
@@ -81,7 +68,7 @@ export default function AIAssistantModal(): JSX.Element | null {
window.addEventListener('keydown', handleKeyDown);
return (): void => window.removeEventListener('keydown', handleKeyDown);
}, [isOpen, openModal, closeModal, startNewConversation, pathname]);
}, [isOpen, openModal, closeModal, startNewConversation]);
// ── Handlers ────────────────────────────────────────────────────────────────
@@ -90,28 +77,15 @@ export default function AIAssistantModal(): JSX.Element | null {
return;
}
closeModal();
// Router state tells AIAssistantPage to skip its mount-time Opened fire:
// the assistant was already open in the modal, so this is a surface
// switch, not a new open.
history.push(
ROUTES.AI_ASSISTANT.replace(':conversationId', activeConversationId),
{ fromInApp: true },
);
}, [activeConversationId, closeModal, history]);
const handleNew = useCallback(() => {
void logEvent(AIAssistantEvents.NewChatClicked, {
...analyticsCtx,
// useAIAssistantAnalyticsContext() runs above this component's
// VariantContext.Provider, so the hook reports the default 'page'
// mode. Override here: the modal collapses to 'sidepane' in our
// taxonomy alongside the drawer.
mode: 'sidepane',
source: 'header',
});
startNewConversation();
setShowHistory(false);
}, [startNewConversation, analyticsCtx]);
}, [startNewConversation]);
const handleHistorySelect = useCallback(() => {
setShowHistory(false);

View File

@@ -5,12 +5,8 @@ import { TooltipSimple } from '@signozhq/ui/tooltip';
import ROUTES from 'constants/routes';
import { History, Maximize2, Plus, Sparkles, X } from '@signozhq/icons';
import logEvent from 'api/common/logEvent';
import ConversationsList from '../components/ConversationsList';
import ConversationView from '../ConversationView';
import { AIAssistantEvents } from '../events';
import { useAIAssistantAnalyticsContext } from '../hooks/useAIAssistantAnalyticsContext';
import { useAIAssistantStore } from '../store/useAIAssistantStore';
import { VariantContext } from '../VariantContext';
@@ -36,35 +32,21 @@ export default function AIAssistantPanel(): JSX.Element | null {
const startNewConversation = useAIAssistantStore(
(s) => s.startNewConversation,
);
const analyticsCtx = useAIAssistantAnalyticsContext();
const handleExpand = useCallback(() => {
if (!activeConversationId) {
return;
}
closeDrawer();
// Router state tells AIAssistantPage to skip its mount-time Opened fire:
// the assistant was already open in the drawer, so this is a surface
// switch, not a new open.
history.push(
ROUTES.AI_ASSISTANT.replace(':conversationId', activeConversationId),
{ fromInApp: true },
);
}, [activeConversationId, closeDrawer, history]);
const handleNew = useCallback(() => {
void logEvent(AIAssistantEvents.NewChatClicked, {
...analyticsCtx,
// useAIAssistantAnalyticsContext() runs above this component's
// VariantContext.Provider, so the hook reports the default 'page'
// mode. Override here: this handler only runs when the drawer
// itself is mounted, which is unambiguously the sidepane surface.
mode: 'sidepane',
source: 'header',
});
startNewConversation();
setShowHistory(false);
}, [startNewConversation, analyticsCtx]);
}, [startNewConversation]);
// When user picks a conversation from the list, close the sidebar
const handleHistorySelect = useCallback(() => {

View File

@@ -1,13 +1,9 @@
import { useCallback } from 'react';
import { matchPath, useLocation } from 'react-router-dom';
import { Button } from '@signozhq/ui/button';
import { TooltipSimple } from '@signozhq/ui/tooltip';
import logEvent from 'api/common/logEvent';
import ROUTES from 'constants/routes';
import { Bot } from '@signozhq/icons';
import { AIAssistantEvents } from '../events';
import { normalizePage } from '../hooks/useAIAssistantAnalyticsContext';
import {
openAIAssistant,
useAIAssistantStore,
@@ -29,14 +25,6 @@ export default function AIAssistantTrigger(): JSX.Element | null {
exact: true,
});
const handleOpen = useCallback((): void => {
void logEvent(AIAssistantEvents.Opened, {
source: 'icon',
currentPage: normalizePage(pathname),
});
openAIAssistant();
}, [pathname]);
if (isDrawerOpen || isModalOpen || isFullScreenPage) {
return null;
}
@@ -47,7 +35,7 @@ export default function AIAssistantTrigger(): JSX.Element | null {
variant="solid"
color="primary"
className={styles.trigger}
onClick={handleOpen}
onClick={openAIAssistant}
aria-label="Open AI Assistant"
>
<Bot size={20} />

View File

@@ -1,15 +1,11 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useLocation } from 'react-router-dom';
import cx from 'classnames';
import logEvent from 'api/common/logEvent';
import ChatInput, { autoContextKey } from '../components/ChatInput';
import ConversationSkeleton from '../components/ConversationSkeleton';
import VirtualizedMessages from '../components/VirtualizedMessages';
import { AIAssistantEvents } from '../events';
import { getAutoContexts } from '../getAutoContexts';
import { useAIAssistantAnalyticsContext } from '../hooks/useAIAssistantAnalyticsContext';
import { useAIAssistantStore } from '../store/useAIAssistantStore';
import { MessageAttachment } from '../types';
import { MessageContext } from '../../../api/ai-assistant/chat';
@@ -43,7 +39,6 @@ export default function ConversationView({
);
const sendMessage = useAIAssistantStore((s) => s.sendMessage);
const cancelStream = useAIAssistantStore((s) => s.cancelStream);
const analyticsCtx = useAIAssistantAnalyticsContext(conversationId);
// Auto-derived contexts come from the route the user is currently looking
// at (dashboard detail, service metrics, an explorer, …). Skip when the
@@ -87,50 +82,14 @@ export default function ConversationView({
attachments?: MessageAttachment[],
contexts?: MessageContext[],
) => {
const hasAuto = contexts?.some((c) => c.source === 'auto') ?? false;
const hasManual = contexts?.some((c) => c.source === 'mention') ?? false;
let contextType: 'manual' | 'auto' | 'both' | undefined;
if (hasAuto && hasManual) {
contextType = 'both';
} else if (hasAuto) {
contextType = 'auto';
} else if (hasManual) {
contextType = 'manual';
}
void logEvent(AIAssistantEvents.MessageSent, {
...analyticsCtx,
queryLength: text.length,
hasContext: hasAuto || hasManual,
contextType,
respondingToClarification: Boolean(pendingClarificationHere),
});
void sendMessage(text, attachments, contexts);
},
[sendMessage, analyticsCtx, pendingClarificationHere],
[sendMessage],
);
// Wall-clock timestamp of the current streaming start, used to compute
// `secondsSinceStart` on Cancel clicked. Cleared whenever streaming ends.
const streamStartedAtRef = useRef<number | null>(null);
useEffect(() => {
if (!isStreamingHere) {
streamStartedAtRef.current = null;
return;
}
if (streamStartedAtRef.current === null) {
streamStartedAtRef.current = Date.now();
}
}, [isStreamingHere]);
const handleCancel = useCallback(() => {
const startedAt = streamStartedAtRef.current;
void logEvent(AIAssistantEvents.CancelClicked, {
threadId: analyticsCtx.threadId,
secondsSinceStart:
startedAt !== null ? Math.round((Date.now() - startedAt) / 1000) : null,
});
cancelStream(conversationId);
}, [cancelStream, conversationId, analyticsCtx.threadId]);
}, [cancelStream, conversationId]);
const messages = conversation?.messages ?? [];
const showDisclaimer = messages.length > 0;
@@ -175,7 +134,6 @@ export default function ConversationView({
conversationId={conversationId}
messages={messages}
isStreaming={isStreamingHere}
onSendSuggestedPrompt={(text): void => handleSend(text)}
/>
{showDisclaimer && (
<div className={disclaimerClass} role="note" aria-live="polite">

View File

@@ -41,68 +41,12 @@ import {
Undo,
} from '@signozhq/icons';
import logEvent from 'api/common/logEvent';
import { AIAssistantEvents, SuggestedPromptCategory } from '../../events';
import { useAIAssistantAnalyticsContext } from '../../hooks/useAIAssistantAnalyticsContext';
import { useAIAssistantStore } from '../../store/useAIAssistantStore';
import styles from './ActionsSection.module.scss';
interface ActionsSectionProps {
actions: MessageActionDTO[];
/** ID of the assistant message these actions belong to — used in analytics. */
messageId: string;
}
/**
* Resource-type strings the backend uses for `open_resource` and rollback
* actions. Centralized here so the route/module lookups below stay in sync.
*/
const ResourceType = {
dashboard: 'dashboard',
alert: 'alert',
service: 'service',
saved_view: 'saved_view',
logs_explorer: 'logs_explorer',
traces_explorer: 'traces_explorer',
metrics_explorer: 'metrics_explorer',
} as const;
/** Maps an open_resource action's resourceType to its product module name. */
function targetModuleForResource(resourceType: string): string | null {
switch (resourceType) {
case ResourceType.dashboard:
return 'dashboards';
case ResourceType.alert:
return 'alerts';
case ResourceType.service:
return 'apm';
case ResourceType.saved_view:
return 'savedViews';
case ResourceType.logs_explorer:
return 'logs';
case ResourceType.traces_explorer:
return 'traces';
case ResourceType.metrics_explorer:
return 'metrics';
default:
return null;
}
}
/** Maps an apply_filter signal to its product module name. */
function targetModuleForSignal(signal: ApplyFilterSignalDTO): string | null {
switch (signal) {
case ApplyFilterSignalDTO.logs:
return 'logs';
case ApplyFilterSignalDTO.traces:
return 'traces';
case ApplyFilterSignalDTO.metrics:
return 'metrics';
default:
return null;
}
}
type ChipState = 'idle' | 'loading' | 'success' | 'error';
@@ -150,23 +94,23 @@ function resourceRoute(
resourceId: string,
): string | null {
switch (resourceType) {
case ResourceType.dashboard:
case 'dashboard':
return ROUTES.DASHBOARD.replace(':dashboardId', resourceId);
case ResourceType.alert: {
case 'alert': {
const params = new URLSearchParams({ [QueryParams.ruleId]: resourceId });
return `${ROUTES.EDIT_ALERTS}?${params.toString()}`;
}
case ResourceType.service:
case 'service':
return ROUTES.SERVICE_METRICS.replace(':servicename', resourceId);
case ResourceType.saved_view:
case 'saved_view':
// No detail route — saved views land on the list page.
// Caller may provide signal-aware metadata in future; default to logs.
return ROUTES.LOGS_SAVE_VIEWS;
case ResourceType.logs_explorer:
case 'logs_explorer':
return ROUTES.LOGS_EXPLORER;
case ResourceType.traces_explorer:
case 'traces_explorer':
return ROUTES.TRACES_EXPLORER;
case ResourceType.metrics_explorer:
case 'metrics_explorer':
return ROUTES.METRICS_EXPLORER_EXPLORER;
default:
return null;
@@ -280,24 +224,6 @@ function actionKey(action: MessageActionDTO, index: number): string {
: `${action.kind}:${action.label}:${index}`;
}
/**
* Resolves the prompt to send for a follow_up action. The chip's `label` is
* the short display text (e.g. "Python setup"); the real prompt lives in
* `input.intent` per the schema doc. Falls back to label defensively so a
* malformed server payload doesn't drop the click silently. Both branches
* are trimmed so whitespace-only payloads don't become whitespace messages.
*/
function followUpIntent(action: MessageActionDTO): string {
const intent = action.input?.intent;
if (typeof intent === 'string') {
const trimmed = intent.trim();
if (trimmed.length > 0) {
return trimmed;
}
}
return action.label.trim();
}
/** Maps a signal to its target explorer route. */
function explorerRouteForSignal(signal: ApplyFilterSignalDTO): string | null {
switch (signal) {
@@ -427,12 +353,10 @@ function rollbackCall(
*/
export default function ActionsSection({
actions,
messageId,
}: ActionsSectionProps): JSX.Element | null {
const history = useHistory();
const { pathname } = useLocation();
const sendMessage = useAIAssistantStore((s) => s.sendMessage);
const { threadId, page, mode } = useAIAssistantAnalyticsContext();
const { redirectWithQueryBuilderData, handleSetQueryData } = useQueryBuilder();
// Per-chip click state, keyed by chip key (see `key` below). Persists
@@ -506,39 +430,13 @@ export default function ActionsSection({
switch (action.kind) {
case MessageActionKindDTO.open_docs: {
if (action.url) {
void logEvent(AIAssistantEvents.DocOpened, {
threadId,
messageId,
docPath: action.url,
});
openInNewTab(action.url);
}
break;
}
case MessageActionKindDTO.follow_up: {
const intent = followUpIntent(action);
if (intent) {
// Fire SuggestedPromptClicked + MessageSent so analytics can compute
// both the click-through rate against follow-ups offered *and* keep
// the unified send funnel intact. `category` distinguishes server-
// emitted follow-ups from the empty-state grid. `promptId` stays the
// label so dashboards group identical chip texts together regardless
// of the dynamic intent payload.
void logEvent(AIAssistantEvents.SuggestedPromptClicked, {
threadId,
messageId,
promptId: action.label,
category: SuggestedPromptCategory.FollowUp,
});
void logEvent(AIAssistantEvents.MessageSent, {
threadId,
page,
mode,
queryLength: intent.length,
hasContext: false,
respondingToClarification: false,
});
void sendMessage(intent);
if (action.label) {
void sendMessage(action.label);
}
break;
}
@@ -546,12 +444,6 @@ export default function ActionsSection({
if (action.resourceType && action.resourceId) {
const path = resourceRoute(action.resourceType, action.resourceId);
if (path) {
void logEvent(AIAssistantEvents.ResourceOpened, {
threadId,
messageId,
targetModule: targetModuleForResource(action.resourceType),
resourceId: action.resourceId,
});
history.push(path);
}
}
@@ -564,13 +456,6 @@ export default function ActionsSection({
break;
}
case MessageActionKindDTO.apply_filter: {
if (action.signal) {
void logEvent(AIAssistantEvents.ApplyFilterClicked, {
threadId,
messageId,
targetModule: targetModuleForSignal(action.signal),
});
}
applyFilter(action, {
history,
pathname,

View File

@@ -1,64 +0,0 @@
// Collapsed activity summary — one row that hides the underlying
// thinking + tool-call steps. Reuses the same quiet treatment as
// ThinkingStep / ToolCallStep so it sits flush in the assistant bubble.
.group {
width: 100%;
font-size: 12px;
}
.header {
display: flex;
align-items: center;
gap: 8px;
padding: 4px 0;
background: transparent;
border: none;
cursor: pointer;
color: var(--l3-foreground);
user-select: none;
transition: color 0.12s ease;
width: 100%;
text-align: left;
&:hover {
color: var(--l1-foreground);
}
}
.icon {
flex-shrink: 0;
color: var(--accent-primary);
&.spin {
animation: activityGroupPulse 1.4s ease-in-out infinite;
}
}
@keyframes activityGroupPulse {
0%,
100% {
opacity: 0.55;
}
50% {
opacity: 1;
}
}
.label {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-weight: 400;
}
.chevron {
flex-shrink: 0;
color: inherit;
}
.body {
display: flex;
flex-direction: column;
padding: 2px 0 4px;
}

View File

@@ -1,134 +0,0 @@
import { useEffect, useId, useRef, useState } from 'react';
import cx from 'classnames';
import { ChevronDown, ChevronRight, Sparkles } from '@signozhq/icons';
import { StreamingToolCall } from '../../types';
import ThinkingStep from '../ThinkingStep';
import ToolCallStep from '../ToolCallStep';
import styles from './ActivityGroup.module.scss';
export type ActivityItem =
| { kind: 'thinking'; content: string }
| { kind: 'tool'; toolCall: StreamingToolCall };
interface ActivityGroupProps {
items: ActivityItem[];
/**
* True while this group is the trailing activity in an ongoing stream —
* the underlying steps may still be growing or a tool may still be
* running. Drives the live "Working…" label and elapsed-time tick.
*/
isLive?: boolean;
}
function formatElapsed(ms: number): string {
const seconds = Math.round(ms / 1000);
if (seconds < 60) {
return `${seconds}s`;
}
const m = Math.floor(seconds / 60);
const s = seconds % 60;
return s === 0 ? `${m}m` : `${m}m ${s}s`;
}
/**
* Single collapsed summary row that hides a run of thinking + tool-call steps.
* Expands to show each underlying step inline.
*/
export default function ActivityGroup({
items,
isLive = false,
}: ActivityGroupProps): JSX.Element {
const [expanded, setExpanded] = useState(false);
const bodyId = useId();
// Captures the moment this live phase started. Re-stamped on every
// false→true transition so a stream that pauses on
// approval/clarification and resumes doesn't roll the user's wait time
// into the elapsed counter.
const startedAtRef = useRef<number>(Date.now());
const wasLiveRef = useRef<boolean>(isLive);
const [elapsed, setElapsed] = useState(0);
useEffect(() => {
if (isLive && !wasLiveRef.current) {
// Entering a fresh live phase — reset the clock.
startedAtRef.current = Date.now();
setElapsed(0);
}
wasLiveRef.current = isLive;
if (!isLive) {
return undefined;
}
// Tick once per second — the display is integer-second precision, so
// faster ticks would just re-render the bubble for no visible change.
const id = window.setInterval(() => {
setElapsed(Date.now() - startedAtRef.current);
}, 1000);
return (): void => window.clearInterval(id);
}, [isLive]);
const stepCount = items.length;
const stepLabel = stepCount === 1 ? '1 step' : `${stepCount} steps`;
let summary: string;
if (isLive) {
// Suppress the elapsed token until ≥ 1s — the first tick fires after
// 1s anyway, and showing "0s" or "<1s" briefly adds noise.
summary =
elapsed >= 1000
? `Working… · ${formatElapsed(elapsed)} · ${stepLabel}`
: `Working… · ${stepLabel}`;
} else {
summary = `Worked through ${stepLabel}`;
}
const toggle = (): void => setExpanded((v) => !v);
return (
<div className={styles.group}>
<button
type="button"
className={styles.header}
onClick={toggle}
aria-expanded={expanded}
aria-controls={bodyId}
>
<Sparkles
size={12}
className={cx(styles.icon, { [styles.spin]: isLive })}
/>
<span className={styles.label}>{summary}</span>
{expanded ? (
<ChevronDown size={12} className={styles.chevron} />
) : (
<ChevronRight size={12} className={styles.chevron} />
)}
</button>
{expanded && (
<div id={bodyId} className={styles.body}>
{/* eslint-disable react/no-array-index-key */}
{items.map((item, i) => {
// A thinking step is live only while it's the trailing item in
// a trailing live group — once any later event (text or tool)
// arrives, the pass is done.
const isLastItem = i === items.length - 1;
return item.kind === 'thinking' ? (
<ThinkingStep
key={i}
content={item.content}
isLive={isLive && isLastItem}
/>
) : (
<ToolCallStep key={i} toolCall={item.toolCall} />
);
})}
{/* eslint-enable react/no-array-index-key */}
</div>
)}
</div>
);
}

View File

@@ -1,2 +0,0 @@
export { default } from './ActivityGroup';
export type { ActivityItem } from './ActivityGroup';

View File

@@ -5,17 +5,13 @@ import { Badge } from '@signozhq/ui/badge';
import { Button } from '@signozhq/ui/button';
import { Input } from '@signozhq/ui/input';
import { Popover, PopoverContent, PopoverTrigger } from '@signozhq/ui/popover';
import { toast } from '@signozhq/ui/sonner';
import { TooltipSimple } from '@signozhq/ui/tooltip';
import type { UploadFile } from 'antd';
import getSessionStorage from 'api/browser/sessionstorage/get';
import setSessionStorage from 'api/browser/sessionstorage/set';
import {
getListRulesQueryKey,
useListRules,
} from 'api/generated/services/rules';
import type { ListRules200 } from 'api/generated/services/sigNoz.schemas';
import logEvent from 'api/common/logEvent';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import { useGetAllDashboard } from 'hooks/dashboard/useGetAllDashboard';
import { useQueryService } from 'hooks/useQueryService';
@@ -26,8 +22,6 @@ import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { GlobalReducer } from 'types/reducer/globalTime';
import { AIAssistantEvents, getBrowserInfo } from '../../events';
import { useAIAssistantAnalyticsContext } from '../../hooks/useAIAssistantAnalyticsContext';
import { useSpeechRecognition } from '../../hooks/useSpeechRecognition';
import { MessageAttachment } from '../../types';
import { MessageContext } from '../../../../api/ai-assistant/chat';
@@ -143,8 +137,6 @@ function autoContextCategory(ctx: MessageContext): string {
const MAX_INPUT_LENGTH = 20000;
const WARNING_THRESHOLD = 15000;
const HOME_SERVICES_INTERVAL = 30 * 60 * 1000;
/** sessionStorage key for the "voice input failed this tab" flag. */
const VOICE_UNAVAILABLE_KEY = 'ai-assistant-voice-unavailable';
const CONTEXT_CATEGORIES = ['Dashboards', 'Alerts', 'Services'] as const;
@@ -376,28 +368,6 @@ export default function ChatInput({
// ── Voice input ────────────────────────────────────────────────────────────
const analyticsCtx = useAIAssistantAnalyticsContext();
// Captured at the start of a voice session, consumed when it ends.
// Tracks both the trigger (button vs. PTT shortcut) and the wall-clock
// start time so we can attribute `durationMs` on the Voice input used
// event regardless of which control ended the session.
const voiceStartedAtRef = useRef<number | null>(null);
const voiceSourceRef = useRef<'button' | 'shortcut' | null>(null);
// Set to true after a `network`, `not-allowed`, or `not-supported` failure
// so we hide the mic button for the rest of the tab session — silent
// retries don't help, and Chromium derivatives without the Google Speech
// API key always fail with `network` no matter how many times the user
// clicks. Persisted to sessionStorage so a page reload doesn't surface the
// button again (closing the tab still resets, in case the user fixed
// permissions or switched browsers).
const [voiceUnavailable, setVoiceUnavailable] = useState(
() => getSessionStorage(VOICE_UNAVAILABLE_KEY) === 'true',
);
const markVoiceUnavailable = useCallback((): void => {
setVoiceUnavailable(true);
setSessionStorage(VOICE_UNAVAILABLE_KEY, 'true');
}, []);
const {
isListening,
isSupported,
@@ -418,81 +388,9 @@ export default function ChatInput({
setText(capText(committedTextRef.current + separator + transcriptText));
}
},
onError: (error) => {
// Guard against double-fire: Chrome can fire `onerror` more than
// once per session when `continuous = true` (it retries internally
// before giving up). Only fire the analytics event for the first
// error in a given session — voiceSourceRef being null means we've
// already handled it.
const source = voiceSourceRef.current;
if (source === null) {
return;
}
voiceStartedAtRef.current = null;
voiceSourceRef.current = null;
void logEvent(AIAssistantEvents.VoiceInputFailed, {
...analyticsCtx,
...getBrowserInfo(),
source,
errorType: error,
});
if (error === 'network') {
markVoiceUnavailable();
toast.error('Voice input unavailable in this browser', {
description:
'This browser cannot reach the speech recognition service. Try Google Chrome or Microsoft Edge.',
});
} else if (error === 'not-allowed') {
markVoiceUnavailable();
toast.error('Microphone access denied', {
description:
'Grant microphone permission in your browser settings to use voice input.',
});
} else if (error === 'not-supported') {
markVoiceUnavailable();
toast.error('Voice input is not supported in this browser.');
}
// `no-speech` is benign (just silence) — don't toast or hide.
},
});
const showMic = isSupported && micPermission !== 'denied' && !voiceUnavailable;
const startVoiceInput = useCallback(
(source: 'button' | 'shortcut') => {
// Defense in depth: the button is hidden when `voiceUnavailable` is
// true, but the PTT shortcut listener can still call us. Bailing here
// keeps a single source of truth and prevents repeat `Voice input
// failed` events in the same session.
if (voiceUnavailable) {
return;
}
voiceStartedAtRef.current = Date.now();
voiceSourceRef.current = source;
start();
},
[start, voiceUnavailable],
);
const fireVoiceInputEvent = useCallback(
(outcome: 'sent' | 'discarded') => {
const startedAt = voiceStartedAtRef.current;
const source = voiceSourceRef.current;
voiceStartedAtRef.current = null;
voiceSourceRef.current = null;
if (startedAt === null || source === null) {
return;
}
void logEvent(AIAssistantEvents.VoiceInputUsed, {
...analyticsCtx,
...getBrowserInfo(),
source,
outcome,
durationMs: Date.now() - startedAt,
});
},
[analyticsCtx],
);
const showMic = isSupported && micPermission !== 'denied';
// Stop recording and immediately send whatever is in the textarea.
const handleStopAndSend = useCallback(async () => {
@@ -500,17 +398,15 @@ export default function ChatInput({
committedTextRef.current = capText(text);
// Stop recognition without triggering onTranscript again (would double-append).
discard();
fireVoiceInputEvent('sent');
await handleSend();
}, [text, discard, handleSend, capText, fireVoiceInputEvent]);
}, [text, discard, handleSend, capText]);
// Stop recording and revert the textarea to what it was before voice started.
const handleDiscard = useCallback(() => {
discard();
fireVoiceInputEvent('discarded');
setText(committedTextRef.current);
textareaRef.current?.focus();
}, [discard, fireVoiceInputEvent]);
}, [discard]);
// ── Push-to-talk (Cmd/Ctrl + Shift + Space) ────────────────────────────────
// Hold the combo to record; release Space to submit. We track which key
@@ -519,7 +415,7 @@ export default function ChatInput({
// "session active" ref so a held key only calls `start()` once.
const pttActiveRef = useRef(false);
useEffect(() => {
if (!isSupported || micPermission === 'denied' || voiceUnavailable) {
if (!isSupported || micPermission === 'denied') {
return undefined;
}
@@ -536,7 +432,7 @@ export default function ChatInput({
return; // ignore auto-repeat
}
pttActiveRef.current = true;
startVoiceInput('shortcut');
start();
};
const handleKeyUp = (e: KeyboardEvent): void => {
@@ -570,10 +466,9 @@ export default function ChatInput({
}, [
isSupported,
micPermission,
voiceUnavailable,
disabled,
isStreaming,
startVoiceInput,
start,
handleStopAndSend,
]);
@@ -1008,7 +903,7 @@ export default function ChatInput({
<Button
variant="ghost"
size="icon"
onClick={(): void => startVoiceInput('button')}
onClick={start}
disabled={disabled}
aria-label="Start voice input"
className={styles.micBtn}

View File

@@ -9,7 +9,6 @@ import {
SelectItem,
SelectTrigger,
} from '@signozhq/ui/select';
import logEvent from 'api/common/logEvent';
import { ClarificationFieldTypeDTO } from 'api/ai-assistant/sigNozAIAssistantAPI.schemas';
import type {
ClarificationEventDTO,
@@ -17,8 +16,6 @@ import type {
} from 'api/ai-assistant/sigNozAIAssistantAPI.schemas';
import { CircleHelp, Send, X } from '@signozhq/icons';
import { AIAssistantEvents } from '../../events';
import { useAIAssistantAnalyticsContext } from '../../hooks/useAIAssistantAnalyticsContext';
import { useAIAssistantStore } from '../../store/useAIAssistantStore';
import styles from './ClarificationForm.module.scss';
@@ -47,8 +44,6 @@ export default function ClarificationForm({
const isStreaming = useAIAssistantStore(
(s) => s.streams[conversationId]?.isStreaming ?? false,
);
const { threadId, page, mode } =
useAIAssistantAnalyticsContext(conversationId);
const fields = clarification.fields ?? [];
const initialAnswers = Object.fromEntries(
@@ -65,18 +60,6 @@ export default function ClarificationForm({
const handleSubmit = async (): Promise<void> => {
setSubmitted(true);
// Approximate queryLength as the JSON encoding of the form answers — the
// clarification API doesn't render a single user-visible string, but the
// JSON size is a reasonable stand-in for "how much did the user provide".
const queryLength = JSON.stringify(answers).length;
void logEvent(AIAssistantEvents.MessageSent, {
threadId,
page,
mode,
queryLength,
hasContext: false,
respondingToClarification: true,
});
await submitClarification(
conversationId,
clarification.clarificationId,
@@ -86,10 +69,6 @@ export default function ClarificationForm({
const handleCancel = (): void => {
setCancelled(true);
void logEvent(AIAssistantEvents.CancelClicked, {
threadId,
secondsSinceStart: null,
});
cancelStream(conversationId);
};

View File

@@ -5,9 +5,6 @@ import { Input } from '@signozhq/ui/input';
import { TooltipSimple } from '@signozhq/ui/tooltip';
import { Plus, Search } from '@signozhq/icons';
import logEvent from 'api/common/logEvent';
import { AIAssistantEvents } from '../../events';
import { useAIAssistantStore } from '../../store/useAIAssistantStore';
import { Conversation } from '../../types';
import { useVariant } from '../../VariantContext';
@@ -139,17 +136,6 @@ export default function ConversationsList({
const handleSelect = (id: string): void => {
const conv = conversations[id];
// Skip re-selecting the currently active thread — Notion-style click on
// the highlighted row in the history list shouldn't inflate the funnel.
const isReselectingActive = id === activeConversationId;
if (conv?.threadId && !isReselectingActive) {
void logEvent(AIAssistantEvents.ThreadOpenedFromHistory, {
threadId: conv.threadId,
threadAgeDays: Math.floor(
(Date.now() - conv.createdAt) / (24 * 60 * 60 * 1000),
),
});
}
if (conv?.threadId) {
// Always load from backend — refreshes messages and reconnects
// to active execution if the thread is still busy.

View File

@@ -1,4 +1,4 @@
import React, { useMemo } from 'react';
import React from 'react';
import cx from 'classnames';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
@@ -9,7 +9,6 @@ import '../blocks';
import { useVariant } from '../../VariantContext';
import { Message, MessageBlock } from '../../types';
import ActionsSection from '../ActionsSection';
import ActivityGroup, { ActivityItem } from '../ActivityGroup';
import { RichCodeBlock } from '../blocks';
import { MessageContext } from '../MessageContext';
import MessageFeedback from '../MessageFeedback';
@@ -41,73 +40,38 @@ function SmartPre({ children }: { children?: React.ReactNode }): JSX.Element {
const MD_PLUGINS = [remarkGfm];
const MD_COMPONENTS = { code: RichCodeBlock, pre: SmartPre };
type RenderGroup =
| { kind: 'text'; content: string }
| { kind: 'activity'; items: ActivityItem[] };
/**
* Partition message blocks into render groups so consecutive thinking and
* tool_call blocks collapse into a single ActivityGroup row. Text blocks
* stand alone, mirroring the streaming view.
*/
function groupBlocks(blocks: MessageBlock[]): RenderGroup[] {
const groups: RenderGroup[] = [];
for (const block of blocks) {
if (block.type === 'text') {
groups.push({ kind: 'text', content: block.content });
continue;
}
const item: ActivityItem =
block.type === 'thinking'
? { kind: 'thinking', content: block.content }
: {
kind: 'tool',
// Persisted blocks are always complete.
toolCall: {
toolName: block.toolName,
input: block.toolInput,
result: block.result,
done: true,
displayText: block.displayText,
},
};
const last = groups[groups.length - 1];
if (last?.kind === 'activity') {
last.items.push(item);
} else {
groups.push({ kind: 'activity', items: [item] });
}
/** Renders a single MessageBlock by type. */
function renderBlock(block: MessageBlock, index: number): JSX.Element {
switch (block.type) {
case 'thinking':
return <ThinkingStep key={index} content={block.content} />;
case 'tool_call':
// Blocks in a persisted message are always complete — done is always true.
return (
<ToolCallStep
key={index}
toolCall={{
toolName: block.toolName,
input: block.toolInput,
result: block.result,
done: true,
displayText: block.displayText,
}}
/>
);
case 'text':
default:
return (
<ReactMarkdown
key={index}
className={styles.markdown}
remarkPlugins={MD_PLUGINS}
components={MD_COMPONENTS}
>
{block.content}
</ReactMarkdown>
);
}
return groups;
}
function renderActivityItem(item: ActivityItem, index: number): JSX.Element {
return item.kind === 'thinking' ? (
<ThinkingStep key={index} content={item.content} />
) : (
<ToolCallStep key={index} toolCall={item.toolCall} />
);
}
function renderGroup(group: RenderGroup, index: number): JSX.Element {
if (group.kind === 'text') {
return (
<ReactMarkdown
key={index}
className={styles.markdown}
remarkPlugins={MD_PLUGINS}
components={MD_COMPONENTS}
>
{group.content}
</ReactMarkdown>
);
}
// A lone activity item doesn't need a "Worked through 1 step" wrapper —
// render it bare so the chevron on the underlying step does the disclosure.
if (group.items.length === 1) {
return renderActivityItem(group.items[0], index);
}
return <ActivityGroup key={index} items={group.items} />;
}
interface MessageBubbleProps {
@@ -126,14 +90,6 @@ export default function MessageBubble({
const isUser = message.role === 'user';
const hasBlocks = !isUser && message.blocks && message.blocks.length > 0;
// Recompute groups only when the blocks array identity changes — store
// updates that don't touch this message's blocks should not re-render the
// underlying ThinkingStep/ToolCallStep children.
const groups = useMemo(
() => (hasBlocks ? groupBlocks(message.blocks!) : []),
[hasBlocks, message.blocks],
);
const messageClass = cx(
styles.message,
isUser ? styles.user : styles.assistant,
@@ -173,7 +129,7 @@ export default function MessageBubble({
) : hasBlocks ? (
<MessageContext.Provider value={{ messageId: message.id }}>
{/* eslint-disable-next-line react/no-array-index-key */}
{groups.map((g, i) => renderGroup(g, i))}
{message.blocks!.map((block, i) => renderBlock(block, i))}
</MessageContext.Provider>
) : (
<MessageContext.Provider value={{ messageId: message.id }}>
@@ -188,7 +144,7 @@ export default function MessageBubble({
)}
{!isUser && message.actions && message.actions.length > 0 && (
<ActionsSection actions={message.actions} messageId={message.id} />
<ActionsSection actions={message.actions} />
)}
</div>
</div>

View File

@@ -8,10 +8,6 @@ import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import { Check, Copy, RefreshCw, ThumbsDown, ThumbsUp } from '@signozhq/icons';
import { useTimezone } from 'providers/Timezone';
import logEvent from 'api/common/logEvent';
import { AIAssistantEvents } from '../../events';
import { useAIAssistantAnalyticsContext } from '../../hooks/useAIAssistantAnalyticsContext';
import { useAIAssistantStore } from '../../store/useAIAssistantStore';
import { FeedbackRating, Message } from '../../types';
@@ -58,7 +54,6 @@ export default function MessageFeedback({
const submitMessageFeedback = useAIAssistantStore(
(s) => s.submitMessageFeedback,
);
const { threadId } = useAIAssistantAnalyticsContext();
const { formatTimezoneAdjustedTimestamp } = useTimezone();
@@ -96,21 +91,10 @@ export default function MessageFeedback({
}, [message.createdAt]);
const handleCopy = useCallback((): void => {
void logEvent(AIAssistantEvents.MessageCopied, {
role: message.role,
messageId: message.id,
hadToolCalls: Boolean(message.blocks?.some((b) => b.type === 'tool_call')),
});
copyToClipboard(message.content);
setCopied(true);
setTimeout(() => setCopied(false), 1500);
}, [
copyToClipboard,
message.content,
message.id,
message.role,
message.blocks,
]);
}, [copyToClipboard, message.content]);
const handleVote = useCallback(
(rating: FeedbackRating): void => {
@@ -123,31 +107,20 @@ export default function MessageFeedback({
return;
}
setVote(rating);
void logEvent(AIAssistantEvents.FeedbackSubmitted, {
messageId: message.id,
threadId,
rating: 'up',
hasComment: false,
commentLength: 0,
});
submitMessageFeedback(message.id, rating);
},
[vote, message.id, submitMessageFeedback, threadId],
[vote, message.id, submitMessageFeedback],
);
const handleSubmitNegative = useCallback((): void => {
setVote('negative');
setIsNegativeDialogOpen(false);
const trimmed = negativeComment.trim();
void logEvent(AIAssistantEvents.FeedbackSubmitted, {
messageId: message.id,
threadId,
rating: 'down',
hasComment: trimmed.length > 0,
commentLength: trimmed.length,
});
submitMessageFeedback(message.id, 'negative', trimmed || undefined);
}, [message.id, negativeComment, submitMessageFeedback, threadId]);
submitMessageFeedback(
message.id,
'negative',
negativeComment.trim() || undefined,
);
}, [message.id, negativeComment, submitMessageFeedback]);
return (
<>

View File

@@ -1,4 +1,4 @@
import React, { useMemo } from 'react';
import React from 'react';
import cx from 'classnames';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
@@ -10,7 +10,6 @@ import type {
import { useVariant } from '../../VariantContext';
import { StreamingEventItem } from '../../types';
import ActivityGroup, { ActivityItem } from '../ActivityGroup';
import ApprovalCard from '../ApprovalCard';
import { RichCodeBlock } from '../blocks';
import ClarificationForm from '../ClarificationForm';
@@ -34,63 +33,6 @@ function SmartPre({ children }: { children?: React.ReactNode }): JSX.Element {
const MD_PLUGINS = [remarkGfm];
const MD_COMPONENTS = { code: RichCodeBlock, pre: SmartPre };
type RenderGroup =
| { kind: 'text'; content: string }
| { kind: 'activity'; items: ActivityItem[]; isTrailing: boolean };
/**
* Partition the streaming event timeline into render groups: runs of
* consecutive thinking/tool events fold into a single activity group, text
* events stay standalone. The last group is flagged as trailing so the
* caller can drive a "live" indicator on it.
*
* Invariant relied on by the ActivityGroup elapsed-time timer: once a
* group exists at a given array index, later events only extend its
* `items` — they never shrink the array or re-key existing groups. That
* keeps each ActivityGroup React instance stable across re-renders so the
* timer's `wasLive` → `isLive` re-stamp captures the right transition.
*/
function groupStreamingEvents(events: StreamingEventItem[]): RenderGroup[] {
const groups: RenderGroup[] = [];
for (const event of events) {
if (event.kind === 'text') {
groups.push({ kind: 'text', content: event.content });
continue;
}
const item: ActivityItem =
event.kind === 'thinking'
? { kind: 'thinking', content: event.content }
: { kind: 'tool', toolCall: event.toolCall };
const last = groups[groups.length - 1];
if (last?.kind === 'activity') {
last.items.push(item);
} else {
groups.push({ kind: 'activity', items: [item], isTrailing: false });
}
}
const last = groups[groups.length - 1];
if (last?.kind === 'activity') {
last.isTrailing = true;
}
return groups;
}
/**
* Renders a single activity item bare — used when an activity group has
* only one item, so the user doesn't see a "Worked through 1 step" wrapper
* around a single chevron row.
*/
function renderBareActivity(
item: ActivityItem,
index: number,
isLive: boolean,
): JSX.Element {
if (item.kind === 'thinking') {
return <ThinkingStep key={index} content={item.content} isLive={isLive} />;
}
return <ToolCallStep key={index} toolCall={item.toolCall} />;
}
/** Human-readable labels for execution status codes shown before any events arrive. */
const STATUS_LABEL: Record<string, string> = {
queued: 'Queued…',
@@ -137,11 +79,6 @@ export default function StreamingMessage({
[messageStyles.compact]: isCompact,
});
// Recompute groups only when the events array identity changes. The
// streaming reducer pushes new entries into the same array reference
// once per tick, so this naturally invalidates as events arrive.
const groups = useMemo(() => groupStreamingEvents(events), [events]);
return (
<div className={messageClass}>
<div className={messageStyles.bubble}>
@@ -152,31 +89,24 @@ export default function StreamingMessage({
{isEmpty && !statusLabel && <TypingDots />}
{/* eslint-disable react/no-array-index-key */}
{/* Runs of consecutive thinking + tool events collapse into a
single ActivityGroup; text events render inline between
them. The trailing group is "live" while streaming is
active and not blocked on the user. */}
{groups.map((group, i) => {
if (group.kind === 'text') {
return (
<ReactMarkdown
key={i}
className={messageStyles.markdown}
remarkPlugins={MD_PLUGINS}
components={MD_COMPONENTS}
>
{group.content}
</ReactMarkdown>
);
{/* Events rendered in arrival order: text, thinking, and tool calls interleaved */}
{events.map((event, i) => {
if (event.kind === 'tool') {
return <ToolCallStep key={i} toolCall={event.toolCall} />;
}
const groupIsLive = group.isTrailing && !isWaitingOnUser;
// Bare-render a lone activity item — the chevron on the
// underlying step is enough disclosure without wrapping it in
// a "Worked through 1 step" summary row.
if (group.items.length === 1) {
return renderBareActivity(group.items[0], i, groupIsLive);
if (event.kind === 'thinking') {
return <ThinkingStep key={i} content={event.content} />;
}
return <ActivityGroup key={i} items={group.items} isLive={groupIsLive} />;
return (
<ReactMarkdown
key={i}
className={messageStyles.markdown}
remarkPlugins={MD_PLUGINS}
components={MD_COMPONENTS}
>
{event.content}
</ReactMarkdown>
);
})}
{/* eslint-enable react/no-array-index-key */}

View File

@@ -5,28 +5,16 @@ import styles from './ThinkingStep.module.scss';
interface ThinkingStepProps {
content: string;
/**
* True only while this thinking pass is still in flight — i.e. the latest
* event in an active stream. Persisted (history) blocks and any thinking
* step followed by a later event default to false and read as
* "Thought for a few seconds". The phrase is intentionally vague: the API
* doesn't persist precise timing, so we'd be inconsistent across fresh
* vs reloaded threads if we tried to show seconds.
*/
isLive?: boolean;
}
/** Collapsible thinking row — chevron + label, content in the expanded body. */
export default function ThinkingStep({
content,
isLive = false,
}: ThinkingStepProps): JSX.Element {
const [expanded, setExpanded] = useState(false);
const toggle = (): void => setExpanded((v) => !v);
const label = isLive ? 'Thinking…' : 'Thought for a few seconds';
return (
<div className={styles.row}>
<div className={styles.header} onClick={toggle}>
@@ -35,7 +23,7 @@ export default function ThinkingStep({
) : (
<ChevronRight size={12} className={styles.chevron} />
)}
<span className={styles.label}>{label}</span>
<span className={styles.label}>Thinking</span>
</div>
{expanded && (

View File

@@ -4,9 +4,6 @@ import { Button } from '@signozhq/ui/button';
import { TooltipSimple } from '@signozhq/ui/tooltip';
import { Check, Copy } from '@signozhq/icons';
import logEvent from 'api/common/logEvent';
import { AIAssistantEvents } from '../../events';
import { Message } from '../../types';
import styles from './UserMessageActions.module.scss';
@@ -28,15 +25,10 @@ export default function UserMessageActions({
const [, copyToClipboard] = useCopyToClipboard();
const handleCopy = useCallback((): void => {
void logEvent(AIAssistantEvents.MessageCopied, {
role: message.role,
messageId: message.id,
hadToolCalls: false,
});
copyToClipboard(message.content);
setCopied(true);
setTimeout(() => setCopied(false), 1500);
}, [copyToClipboard, message.content, message.id, message.role]);
}, [copyToClipboard, message.content]);
return (
<div className={styles.actions}>

View File

@@ -10,10 +10,6 @@ import {
Sparkles,
} from '@signozhq/icons';
import logEvent from 'api/common/logEvent';
import { AIAssistantEvents, SuggestedPromptCategory } from '../../events';
import { useAIAssistantAnalyticsContext } from '../../hooks/useAIAssistantAnalyticsContext';
import { useAIAssistantStore } from '../../store/useAIAssistantStore';
import { Message, StreamingEventItem } from '../../types';
import MessageBubble from '../MessageBubble';
@@ -50,24 +46,17 @@ interface VirtualizedMessagesProps {
conversationId: string;
messages: Message[];
isStreaming: boolean;
/**
* Called when a user clicks an empty-state suggested prompt. Routed
* through the parent so analytics (Message sent) fire with the same
* page/mode/context attribution as a normal send.
*/
onSendSuggestedPrompt: (text: string) => void;
}
export default function VirtualizedMessages({
conversationId,
messages,
isStreaming,
onSendSuggestedPrompt,
}: VirtualizedMessagesProps): JSX.Element {
const sendMessage = useAIAssistantStore((s) => s.sendMessage);
const regenerateAssistantMessage = useAIAssistantStore(
(s) => s.regenerateAssistantMessage,
);
const { threadId } = useAIAssistantAnalyticsContext(conversationId);
const streamingStatus = useAIAssistantStore(
(s) => s.streams[conversationId]?.streamingStatus ?? '',
);
@@ -96,13 +85,9 @@ export default function VirtualizedMessages({
if (isStreaming) {
return;
}
void logEvent(AIAssistantEvents.RegenerateClicked, {
messageId,
threadId,
});
void regenerateAssistantMessage(conversationId, messageId);
},
[conversationId, isStreaming, regenerateAssistantMessage, threadId],
[conversationId, isStreaming, regenerateAssistantMessage],
);
// Scroll all the way to the actual bottom — including the 64px of bottom
@@ -161,11 +146,7 @@ export default function VirtualizedMessages({
color="secondary"
className={styles.emptyChip}
onClick={(): void => {
void logEvent(AIAssistantEvents.SuggestedPromptClicked, {
promptId: s.text,
category: SuggestedPromptCategory.EmptyState,
});
onSendSuggestedPrompt(s.text);
sendMessage(s.text);
}}
prefix={<s.icon size={14} />}
>

View File

@@ -1,10 +1,7 @@
import cx from 'classnames';
import { Button } from '@signozhq/ui/button';
import logEvent from 'api/common/logEvent';
import { Check, X } from '@signozhq/icons';
import { AIAssistantEvents } from '../../../events';
import { useAIAssistantAnalyticsContext } from '../../../hooks/useAIAssistantAnalyticsContext';
import { useAIAssistantStore } from '../../../store/useAIAssistantStore';
import { useMessageContext } from '../../MessageContext';
@@ -40,7 +37,6 @@ export default function ConfirmBlock({
const answeredBlocks = useAIAssistantStore((s) => s.answeredBlocks);
const markBlockAnswered = useAIAssistantStore((s) => s.markBlockAnswered);
const sendMessage = useAIAssistantStore((s) => s.sendMessage);
const { threadId, page, mode } = useAIAssistantAnalyticsContext();
// Durable answered state — survives re-renders/remounts
const answeredChoice = messageId ? answeredBlocks[messageId] : undefined;
@@ -51,14 +47,6 @@ export default function ConfirmBlock({
if (messageId) {
markBlockAnswered(messageId, choice);
}
void logEvent(AIAssistantEvents.MessageSent, {
threadId,
page,
mode,
queryLength: responseText.length,
hasContext: false,
respondingToClarification: false,
});
sendMessage(responseText);
};

View File

@@ -1,11 +1,8 @@
import { useState } from 'react';
import cx from 'classnames';
import { Button } from '@signozhq/ui/button';
import logEvent from 'api/common/logEvent';
import { Checkbox, Radio } from 'antd';
import { AIAssistantEvents } from '../../../events';
import { useAIAssistantAnalyticsContext } from '../../../hooks/useAIAssistantAnalyticsContext';
import { useAIAssistantStore } from '../../../store/useAIAssistantStore';
import { useMessageContext } from '../../MessageContext';
@@ -39,7 +36,6 @@ export default function InteractiveQuestion({
const answeredBlocks = useAIAssistantStore((s) => s.answeredBlocks);
const markBlockAnswered = useAIAssistantStore((s) => s.markBlockAnswered);
const sendMessage = useAIAssistantStore((s) => s.sendMessage);
const { threadId, page, mode } = useAIAssistantAnalyticsContext();
// Persist selected state locally only for the pending (not-yet-submitted) case
const [selected, setSelected] = useState<string[]>([]);
@@ -56,14 +52,6 @@ export default function InteractiveQuestion({
if (messageId) {
markBlockAnswered(messageId, answer);
}
void logEvent(AIAssistantEvents.MessageSent, {
threadId,
page,
mode,
queryLength: answer.length,
hasContext: false,
respondingToClarification: false,
});
sendMessage(answer);
};

View File

@@ -1,81 +0,0 @@
/**
* Analytics event names for the AI Assistant feature. Backend-emitted events
* (Execution finished, Approval resolved, Resource mutated, Clarification
* requested, Limit hit) are not declared here — they fire from the AI service.
*/
export interface BrowserInfo {
browserName: string;
browserVersion: string;
}
type NavigatorWithBrandHints = Navigator & {
userAgentData?: { brands: { brand: string; version: string }[] };
brave?: { isBrave: () => Promise<boolean> };
};
/**
* We mainly need to distinguish Chrome / Edge (Speech API works) from Chromium
* derivatives (no Google API key → voice fails with `network`). UA sniffing is
* the source of truth for derivative identification; `userAgentData` is used
* only as a fast happy path for Chrome / Edge. Brave needs its own probe — it
* advertises Chrome in both UA and brand hints.
*/
export function getBrowserInfo(): BrowserInfo {
if (typeof navigator === 'undefined') {
return { browserName: 'unknown', browserVersion: 'unknown' };
}
const nav = navigator as NavigatorWithBrandHints;
const ua = nav.userAgent;
// Order matters: derivatives put "Chrome" in their UA; Chrome puts "Safari".
const matchers: { name: string; re: RegExp }[] = [
{ name: 'Edge', re: /Edg(?:e|A|iOS)?\/([\d.]+)/ },
{ name: 'Opera', re: /OPR\/([\d.]+)/ },
{ name: 'Vivaldi', re: /Vivaldi\/([\d.]+)/ },
{ name: 'Chrome', re: /Chrome\/([\d.]+)/ },
{ name: 'Firefox', re: /Firefox\/([\d.]+)/ },
{ name: 'Safari', re: /Version\/([\d.]+).*Safari/ },
];
let browserName = 'unknown';
let browserVersion = 'unknown';
for (const { name, re } of matchers) {
const m = ua.match(re);
if (m) {
browserName = name;
browserVersion = m[1];
break;
}
}
// Brave hides as Chrome in UA + brand hints; its probe is the only tell.
if (nav.brave?.isBrave) {
browserName = 'Brave';
}
return { browserName, browserVersion };
}
export const SuggestedPromptCategory = {
FollowUp: 'follow_up',
EmptyState: 'empty_state',
} as const;
export type SuggestedPromptCategory =
(typeof SuggestedPromptCategory)[keyof typeof SuggestedPromptCategory];
export enum AIAssistantEvents {
Opened = 'AI Assistant: Opened',
MessageSent = 'AI Assistant: Message sent',
SuggestedPromptClicked = 'AI Assistant: Suggested prompt clicked',
CancelClicked = 'AI Assistant: Cancel clicked',
RegenerateClicked = 'AI Assistant: Regenerate clicked',
MessageCopied = 'AI Assistant: Message copied',
FeedbackSubmitted = 'AI Assistant: Feedback submitted',
ResourceOpened = 'AI Assistant: Resource opened',
DocOpened = 'AI Assistant: Doc opened',
ApplyFilterClicked = 'AI Assistant: Apply filter clicked',
ThreadOpenedFromHistory = 'AI Assistant: Thread opened from history',
VoiceInputUsed = 'AI Assistant: Voice input used',
VoiceInputFailed = 'AI Assistant: Voice input failed',
NewChatClicked = 'AI Assistant: New chat clicked',
}

View File

@@ -1,60 +0,0 @@
import { matchPath, useLocation } from 'react-router-dom';
import ROUTES from 'constants/routes';
import { useAIAssistantStore } from '../store/useAIAssistantStore';
import { useVariant } from '../VariantContext';
export interface AIAssistantAnalyticsContext {
/** Backend thread ID for the resolved conversation; undefined before the first send. */
threadId: string | undefined;
/**
* Normalised route template for the current page (e.g. `/dashboard/:dashboardId`).
* Falls back to the raw pathname for routes not in ROUTES. We normalise to keep
* analytics cardinality bounded and avoid leaking customer identifiers
* (dashboard IDs, service names, trace IDs, conversation IDs) into the event.
*/
page: string;
/** Surface the assistant is rendered on. `panel` / `modal` collapse to `sidepane`. */
mode: 'sidepane' | 'full_screen';
}
// Pre-sorted longest-first so more specific templates match before their
// less specific siblings (e.g. `/services/:s/top-level-operations` wins
// over `/services/:s`). Module-level — ROUTES is static.
const ROUTE_TEMPLATES = Object.values(ROUTES).sort(
(a, b) => b.length - a.length,
);
export function normalizePage(pathname: string): string {
for (const template of ROUTE_TEMPLATES) {
if (matchPath(pathname, { path: template, exact: true })) {
return template;
}
}
return pathname;
}
/**
* Shared base attributes for AI Assistant analytics events (Message sent,
* Cancel clicked, Feedback submitted, Resource/Doc/Apply filter, …).
*
* Pass `conversationId` when the caller is scoped to a specific
* conversation (e.g. `ClarificationForm`, `VirtualizedMessages`); omit
* to fall back to the store's active conversation.
*/
export function useAIAssistantAnalyticsContext(
conversationId?: string,
): AIAssistantAnalyticsContext {
const { pathname } = useLocation();
const variant = useVariant();
const threadId = useAIAssistantStore((s) => {
const id = conversationId ?? s.activeConversationId;
return id ? s.conversations[id]?.threadId : undefined;
});
return {
threadId,
page: normalizePage(pathname),
mode: variant === 'page' ? 'full_screen' : 'sidepane',
};
}

View File

@@ -1,7 +1,6 @@
import { useMemo, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { Color } from '@signozhq/design-tokens';
import { Button } from 'antd';
import { useIsDarkMode } from 'hooks/useDarkMode';
import history from 'lib/history';
import { ArrowRight } from '@signozhq/icons';
@@ -11,6 +10,7 @@ import { TopContributorsCardProps } from './types';
import ViewAllDrawer from './ViewAllDrawer';
import './TopContributorsCard.styles.scss';
import { Button } from '@signozhq/ui/button';
function TopContributorsCard({
topContributorsData,
@@ -52,7 +52,11 @@ function TopContributorsCard({
<div className="top-contributors-card__header">
<div className="title">top contributors</div>
{topContributorsData.length > 3 && (
<Button type="text" className="view-all" onClick={toggleViewAllDrawer}>
<Button
className="view-all"
onClick={toggleViewAllDrawer}
variant="ghost"
>
<div className="label">View all</div>
<div className="icon">
<ArrowRight

View File

@@ -1,7 +1,6 @@
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { generatePath } from 'react-router-dom';
import { Button } from 'antd';
import type { ColumnsType } from 'antd/lib/table';
import { ResizeTable } from 'components/ResizeTable';
import ROUTES from 'constants/routes';
@@ -12,6 +11,7 @@ import { useAppContext } from 'providers/App/App';
import { Channels } from 'types/api/channels/getAll';
import Delete from './Delete';
import { Button } from '@signozhq/ui/button';
function AlertChannels({ allChannels }: AlertChannelsProps): JSX.Element {
const { t } = useTranslation(['channels']);
@@ -51,7 +51,7 @@ function AlertChannels({ allChannels }: AlertChannelsProps): JSX.Element {
width: 80,
render: (id: string): JSX.Element => (
<>
<Button onClick={(): void => onClickEditHandler(id)} type="link">
<Button onClick={(): void => onClickEditHandler(id)} variant="link">
{t('column_channel_edit')}
</Button>
<Delete id={id} notifications={notifications} />

View File

@@ -1,10 +1,10 @@
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useQueryClient } from 'react-query';
import { Button } from 'antd';
import type { NotificationInstance } from 'antd/es/notification/interface';
import deleteChannel from 'api/channels/delete';
import APIError from 'types/api/error';
import { Button } from '@signozhq/ui/button';
function Delete({ notifications, id }: DeleteProps): JSX.Element {
const { t } = useTranslation(['channels']);
@@ -38,8 +38,8 @@ function Delete({ notifications, id }: DeleteProps): JSX.Element {
<Button
loading={loading}
disabled={loading}
type="link"
onClick={onClickHandler}
variant="link"
>
Delete
</Button>

View File

@@ -1,3 +1,4 @@
import { Button } from '@signozhq/ui/button';
import { Typography } from '@signozhq/ui/typography';
import { useCallback, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -5,7 +6,7 @@ import { useQueries } from 'react-query';
// eslint-disable-next-line no-restricted-imports
import { useSelector } from 'react-redux';
import { Link, useLocation } from 'react-router-dom';
import { Button, Card, Input, Space, TableProps, Tooltip, Flex } from 'antd';
import { Card, Input, Space, TableProps, Tooltip, Flex } from 'antd';
import { Search } from '@signozhq/icons';
import type { ColumnType, TablePaginationConfig } from 'antd/es/table';
import type { FilterValue, SorterResult } from 'antd/es/table/interface';
@@ -266,9 +267,8 @@ function AllErrors(): JSX.Element {
onPressEnter={handleSearch(confirm, String(selectedKeys[0]), filterKey)}
/>
<Button
type="primary"
onClick={handleSearch(confirm, String(selectedKeys[0]), filterKey)}
size="small"
size="sm"
>
<Flex align="center" justify="center" gap={4}>
<Search size="md" />

View File

@@ -706,7 +706,7 @@
.views-tabs {
color: var(--l2-foreground);
.ant-btn {
button {
box-shadow: none;
position: relative;
}

View File

@@ -2,7 +2,8 @@ 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, Divider, Drawer, Radio } from 'antd';
import { Divider, Drawer, Radio } from 'antd';
import { Button } from '@signozhq/ui/button';
import { Typography } from '@signozhq/ui/typography';
import type { RadioChangeEvent } from 'antd/lib';
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
@@ -24,6 +25,7 @@ import EndPointDetails from './EndPointDetails';
import TopErrors from './TopErrors';
import './DomainDetails.styles.scss';
import ButtonGroup from 'periscope/components/ButtonGroup/ButtonGroup';
const TimeRangeOffset = 1000000000;
@@ -176,7 +178,7 @@ function DomainDetails({
modalInitialStartTime={modalTimeRange.startTime * 1000}
modalInitialEndTime={modalTimeRange.endTime * 1000}
/>
<Button.Group className="domain-details-drawer-header-ctas">
<ButtonGroup className="domain-details-drawer-header-ctas">
<Button
className="domain-navigate-cta"
onClick={(): void => {
@@ -185,9 +187,12 @@ function DomainDetails({
setEndPointsGroupBy([]);
setSelectedView(VIEW_TYPES.ALL_ENDPOINTS);
}}
icon={<ArrowUp size={16} />}
disabled={selectedDomainIndex === 0}
title="Previous domain"
variant="outlined"
color="secondary"
size="icon"
prefix={<ArrowUp size={16} />}
/>
<Button
className="domain-navigate-cta"
@@ -197,11 +202,14 @@ function DomainDetails({
setEndPointsGroupBy([]);
setSelectedView(VIEW_TYPES.ALL_ENDPOINTS);
}}
icon={<ArrowDown size={16} />}
disabled={selectedDomainIndex === domainListLength - 1}
title="Next domain"
variant="outlined"
color="secondary"
size="icon"
prefix={<ArrowDown size={16} />}
/>
</Button.Group>
</ButtonGroup>
</div>
</div>
}

View File

@@ -1,4 +1,4 @@
import { Button } from 'antd';
import { Button } from '@signozhq/ui/button';
import { Typography } from '@signozhq/ui/typography';
import { RotateCw } from '@signozhq/icons';
@@ -22,7 +22,9 @@ function ErrorState({ refetch }: { refetch: () => void }): JSX.Element {
<Button
className="refresh-cta"
onClick={(): void => refetch()}
icon={<RotateCw size={16} />}
variant="outlined"
color="secondary"
prefix={<RotateCw size={16} />}
>
Refresh this panel
</Button>

View File

@@ -1,7 +1,8 @@
import { useCallback, useMemo, useRef, useState } from 'react';
import { UseQueryResult } from 'react-query';
import { Color } from '@signozhq/design-tokens';
import { Button, Card, Skeleton } from 'antd';
import { Card, Skeleton } from 'antd';
import { Button } from '@signozhq/ui/button';
import { Typography } from '@signozhq/ui/typography';
import cx from 'classnames';
import { useNavigateToExplorer } from 'components/CeleryTask/useNavigateToExplorer';
@@ -29,6 +30,7 @@ import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import ErrorState from './ErrorState';
import { prepareStatusCodeBarChartsConfig } from './utils';
import ButtonGroup from 'periscope/components/ButtonGroup/ButtonGroup';
function StatusCodeBarCharts({
endPointStatusCodeBarChartsDataQuery,
@@ -247,23 +249,25 @@ function StatusCodeBarCharts({
<Card bordered className="endpoint-details-card">
<div className="header">
<Typography.Text>Call response status</Typography.Text>
<Button.Group className="views-tabs">
<ButtonGroup className="views-tabs">
<Button
value={0}
className={currentWidgetInfoIndex === 0 ? 'selected_view tab' : 'tab'}
disabled={false}
onClick={(): void => setCurrentWidgetInfoIndex(0)}
variant="outlined"
color="secondary"
>
Number of calls
</Button>
<Button
value={1}
className={currentWidgetInfoIndex === 1 ? 'selected_view tab' : 'tab'}
onClick={(): void => setCurrentWidgetInfoIndex(1)}
variant="outlined"
color="secondary"
>
Latency
</Button>
</Button.Group>
</ButtonGroup>
</div>
<div className="graph-container" ref={graphRef}>
{renderCardContent(endPointStatusCodeBarChartsDataQuery)}

View File

@@ -1,3 +1,4 @@
import { Button } from '@signozhq/ui/button';
import { Typography } from '@signozhq/ui/typography';
import { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
@@ -6,7 +7,6 @@ import { CircleCheck, CloudDownload } from '@signozhq/icons';
import { Color } from '@signozhq/design-tokens';
import {
Alert,
Button,
Card,
Col,
Flex,
@@ -445,12 +445,11 @@ export default function BillingContainer(): JSX.Element {
</Flex>
<Flex gap={8}>
<Button
type="default"
size="middle"
loading={isLoadingBilling || isLoadingManageBilling}
disabled={isLoading || isFetchingBillingData}
onClick={handleCsvDownload}
className="periscope-btn"
variant="outlined"
color="secondary"
>
<Flex align="center" justify="center" gap={4}>
<CloudDownload size="md" />
@@ -459,8 +458,6 @@ export default function BillingContainer(): JSX.Element {
</Button>
<Button
data-testid="header-billing-button"
type="primary"
size="middle"
loading={isLoadingBilling || isLoadingManageBilling}
disabled={isLoading}
onClick={handleBilling}
@@ -584,8 +581,6 @@ export default function BillingContainer(): JSX.Element {
<Col span={4} style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Button
data-testid="upgrade-plan-button"
type="primary"
size="middle"
loading={isLoadingBilling || isLoadingManageBilling}
onClick={handleBilling}
>

View File

@@ -1,11 +1,12 @@
import { memo, useMemo } from 'react';
import { ChevronLeft, ChevronRight } from '@signozhq/icons';
import { Button, Flex, Select } from 'antd';
import { Flex, Select } from 'antd';
import { DEFAULT_PER_PAGE_OPTIONS, Pagination } from 'hooks/queryPagination';
import { popupContainer } from 'utils/selectPopupContainer';
import { defaultSelectStyle } from './config';
import { Container } from './styles';
import { Button } from '@signozhq/ui/button';
function Controls({
offset = 0,
@@ -37,10 +38,10 @@ function Controls({
<Container>
<Button
loading={isLoading}
size="small"
type="link"
disabled={isPreviousDisabled}
onClick={handleNavigatePrevious}
size="sm"
variant="link"
>
<Flex align="center" gap="4px">
<ChevronLeft size={16} /> Previous
@@ -48,10 +49,10 @@ function Controls({
</Button>
<Button
loading={isLoading}
size="small"
type="link"
disabled={isNextDisabled}
onClick={handleNavigateNext}
size="sm"
variant="link"
>
<Flex align="center" gap="4px">
Next <ChevronRight size={16} />

View File

@@ -1,5 +1,5 @@
import { useQuery } from 'react-query';
import { Button, Tooltip } from 'antd';
import { Tooltip } from 'antd';
import getAllChannels from 'api/channels/getAll';
import classNames from 'classnames';
import { ChartLine } from '@signozhq/icons';
@@ -16,6 +16,7 @@ import AnomalyThreshold from './AnomalyThreshold';
import { ANOMALY_TAB_TOOLTIP, THRESHOLD_TAB_TOOLTIP } from './constants';
import './styles.scss';
import { Button } from '@signozhq/ui/button';
function AlertCondition(): JSX.Element {
const { alertType, setAlertType } = useCreateAlertState();
@@ -82,6 +83,8 @@ function AlertCondition(): JSX.Element {
handleAlertTypeChange(tab.value as AlertTypes);
}
}}
variant="outlined"
color="secondary"
>
{tab.icon}
{tab.label}

View File

@@ -1,5 +1,6 @@
import { useEffect } from 'react';
import { Button, Select, Tooltip } from 'antd';
import { Select, Tooltip } from 'antd';
import { Button } from '@signozhq/ui/button';
import { Typography } from '@signozhq/ui/typography';
import classNames from 'classnames';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
@@ -294,11 +295,11 @@ function AlertThreshold({
/>
))}
<Button
type="dashed"
icon={<Plus size={16} />}
onClick={addThreshold}
className="add-threshold-btn"
data-testid="add-threshold-button"
variant="dashed"
prefix={<Plus size={16} />}
>
Add Threshold
</Button>

View File

@@ -1,5 +1,6 @@
import { useMemo, useState } from 'react';
import { Button, Input, Select, Tooltip } from 'antd';
import { Input, Select, Tooltip } from 'antd';
import { Button } from '@signozhq/ui/button';
import { Typography } from '@signozhq/ui/typography';
import { CircleX, Trash } from '@signozhq/icons';
import { useAppContext } from 'providers/App/App';
@@ -9,6 +10,7 @@ import { AlertThresholdOperator } from '../context/types';
import { normalizeOperator } from '../utils';
import { ThresholdItemProps } from './types';
import { NotificationChannelsNotFoundContent } from './utils';
import ButtonGroup from 'periscope/components/ButtonGroup/ButtonGroup';
function ThresholdItem({
threshold,
@@ -166,16 +168,18 @@ function ThresholdItem({
/>
<Tooltip title="Remove recovery threshold">
<Button
type="default"
icon={<Trash size={16} />}
onClick={removeRecoveryThreshold}
className="icon-btn"
data-testid="remove-recovery-threshold-button"
variant="outlined"
color="secondary"
size="icon"
prefix={<Trash size={16} />}
/>
</Tooltip>
</>
)}
<Button.Group>
<ButtonGroup>
{/* TODO: Add recovery threshold back once the functionality is implemented */}
{/* {!showRecoveryThreshold && (
<Tooltip title="Add recovery threshold">
@@ -190,15 +194,17 @@ function ThresholdItem({
{showRemoveButton && (
<Tooltip title="Remove threshold">
<Button
type="default"
icon={<CircleX size={16} />}
onClick={(): void => removeThreshold(threshold.id)}
className="icon-btn"
data-testid="remove-threshold-button"
variant="outlined"
color="secondary"
size="icon"
prefix={<CircleX size={16} />}
/>
</Tooltip>
)}
</Button.Group>
</ButtonGroup>
</div>
</div>
</div>

View File

@@ -333,7 +333,7 @@
}
.condensed-evaluation-settings-container {
.ant-btn {
button {
display: flex;
align-items: center;
min-width: 240px;

View File

@@ -1,4 +1,5 @@
import { Button, Flex, SelectProps, Switch } from 'antd';
import { Flex, SelectProps, Switch } from 'antd';
import { Button } from '@signozhq/ui/button';
import { Typography } from '@signozhq/ui/typography';
import type { BaseOptionType, DefaultOptionType } from 'antd/es/select';
import { getInvolvedQueriesInTraceOperator } from 'components/QueryBuilderV2/QueryV2/TraceOperator/utils/utils';
@@ -388,10 +389,10 @@ export function NotificationChannelsNotFoundContent({
Create one
<Button
style={{ padding: '0 4px' }}
type="link"
onClick={(): void => {
openInNewTab(ROUTES.CHANNELS_NEW);
}}
variant="link"
>
here.
</Button>
@@ -400,7 +401,7 @@ export function NotificationChannelsNotFoundContent({
<Typography.Text>Please ask your admin to create one.</Typography.Text>
)}
</Flex>
<Button type="text" onClick={refreshChannels}>
<Button onClick={refreshChannels} variant="ghost">
Refresh
</Button>
</Flex>
@@ -429,10 +430,10 @@ export function RoutingPolicyBanner({
}}
/>
<Button
type="link"
className="view-routing-policies-button"
data-testid="view-routing-policies-button"
onClick={(): void => safeNavigate(ROUTING_POLICIES_ROUTE)}
variant="link"
>
View Routing Policies
<ArrowRight size={14} />

View File

@@ -50,7 +50,7 @@
color: var(--l2-foreground);
}
.ant-btn {
button {
display: flex;
gap: 4px;
align-items: center;

View File

@@ -1,10 +1,11 @@
import { useMemo } from 'react';
import { Button } from 'antd';
import { Button } from '@signozhq/ui/button';
import { Typography } from '@signozhq/ui/typography';
import { useCreateAlertState } from 'container/CreateAlertV2/context';
import { INITIAL_ADVANCED_OPTIONS_STATE } from 'container/CreateAlertV2/context/constants';
import { IEditCustomScheduleProps } from 'container/CreateAlertV2/EvaluationSettings/types';
import { Calendar1, Pencil, Trash } from '@signozhq/icons';
import ButtonGroup from 'periscope/components/ButtonGroup/ButtonGroup';
function EditCustomSchedule({
setIsEvaluationCadenceDetailsVisible,
@@ -80,23 +81,24 @@ function EditCustomSchedule({
<div className="edit-custom-schedule">
{displayText}
<div className="button-row">
<Button.Group>
<Button type="default" onClick={handleEdit}>
<ButtonGroup>
<Button onClick={handleEdit} variant="outlined" color="secondary">
<Pencil size={12} />
<Typography.Text>Edit custom schedule</Typography.Text>
</Button>
<Button type="default" onClick={handlePreview}>
<Button onClick={handlePreview} variant="outlined" color="secondary">
<Calendar1 size={12} />
<Typography.Text>Preview</Typography.Text>
</Button>
<Button
data-testid="discard-button"
type="default"
onClick={handleDiscard}
variant="outlined"
color="secondary"
>
<Trash size={12} />
</Button>
</Button.Group>
</ButtonGroup>
</div>
</div>
);

View File

@@ -1,5 +1,6 @@
import { useEffect, useMemo, useState } from 'react';
import { Button, DatePicker, Input, Select } from 'antd';
import { DatePicker, Input, Select } from 'antd';
import { Button } from '@signozhq/ui/button';
import { Typography } from '@signozhq/ui/typography';
import classNames from 'classnames';
import { useCreateAlertState } from 'container/CreateAlertV2/context';
@@ -311,6 +312,8 @@ function EvaluationCadenceDetails({
onClick={(): void => {
handleChangeTab(tab.value as 'editor' | 'rrule');
}}
variant="outlined"
color="secondary"
>
{tab.icon}
{tab.label}
@@ -321,14 +324,10 @@ function EvaluationCadenceDetails({
{activeTab === 'editor' && EditorView}
{activeTab === 'rrule' && RRuleView}
<div className="buttons-row">
<Button type="default" onClick={handleDiscard}>
<Button onClick={handleDiscard} variant="outlined" color="secondary">
Discard
</Button>
<Button
type="primary"
onClick={handleSaveCustomSchedule}
disabled={disableSaveButton}
>
<Button onClick={handleSaveCustomSchedule} disabled={disableSaveButton}>
Save Custom Schedule
</Button>
</div>

View File

@@ -26,8 +26,8 @@
}
}
.ant-btn-group {
.ant-btn {
.periscope-btn-group {
button {
border: 1px solid var(--l1-border);
color: var(--l2-foreground);
font-size: 13px;

View File

@@ -1,5 +1,5 @@
import { useState } from 'react';
import { Button, Popover } from 'antd';
import { Popover } from 'antd';
import { ChevronDown, ChevronUp } from '@signozhq/icons';
import { useCreateAlertState } from '../context';
@@ -7,6 +7,7 @@ import EvaluationWindowPopover from './EvaluationWindowPopover';
import { getEvaluationWindowTypeText, getTimeframeText } from './utils';
import './styles.scss';
import { Button } from '@signozhq/ui/button';
function EvaluationSettings(): JSX.Element {
const { evaluationWindow, setEvaluationWindow } = useCreateAlertState();
@@ -29,7 +30,11 @@ function EvaluationSettings(): JSX.Element {
trigger="click"
showArrow={false}
>
<Button data-testid="evaluation-settings-button">
<Button
data-testid="evaluation-settings-button"
variant="outlined"
color="secondary"
>
<div className="evaluate-alert-conditions-button-left">
{getTimeframeText(evaluationWindow)}
</div>

View File

@@ -1,4 +1,4 @@
import { Button } from 'antd';
import { Button } from '@signozhq/ui/button';
import { Typography } from '@signozhq/ui/typography';
import classNames from 'classnames';
import { Check } from '@signozhq/icons';
@@ -98,7 +98,7 @@ function EvaluationWindowPopover({
<Typography.Text>
{getRollingWindowDescription(evaluationWindow.timeframe)}
</Typography.Text>
<Button type="link">Read the docs</Button>
<Button variant="link">Read the docs</Button>
</div>
);
}
@@ -112,7 +112,7 @@ function EvaluationWindowPopover({
<Typography.Text>
{getCumulativeWindowDescription(evaluationWindow.timeframe)}
</Typography.Text>
<Button type="link">Read the docs</Button>
<Button variant="link">Read the docs</Button>
</div>
);
}

View File

@@ -22,7 +22,7 @@
border-top: 1px dashed var(--l1-border);
}
.ant-btn {
button {
display: flex;
align-items: center;
justify-content: space-between;
@@ -156,7 +156,7 @@
color: var(--muted-foreground);
}
.ant-btn {
button {
width: fit-content;
}
}
@@ -171,7 +171,7 @@
padding: 16px;
}
.ant-btn {
button {
background-color: var(--l3-background);
border: 1px solid var(--l1-border);
color: var(--l2-foreground);

View File

@@ -17,13 +17,13 @@
gap: 12px;
}
.ant-btn {
button {
display: flex;
align-items: center;
gap: 8px;
}
.ant-btn-default {
button {
background-color: var(--l1-background);
border: 1px solid var(--l1-border);
color: var(--l2-foreground);

View File

@@ -42,7 +42,7 @@
}
.notification-message-header-actions {
.ant-btn {
button {
display: flex;
align-items: center;
justify-content: center;

View File

@@ -1,6 +1,5 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useQueryClient } from 'react-query';
import { Button } from 'antd';
import classNames from 'classnames';
import { YAxisSource } from 'components/YAxisUnitSelector/types';
import { QueryParams } from 'constants/query';
@@ -26,6 +25,7 @@ import ChartPreview from './ChartPreview';
import { buildAlertDefForChartPreview } from './utils';
import './styles.scss';
import { Button } from '@signozhq/ui/button';
function QuerySection(): JSX.Element {
const {
@@ -149,6 +149,8 @@ function QuerySection(): JSX.Element {
onClick={(): void => {
setAlertType(tab.value as AlertTypes);
}}
variant="outlined"
color="secondary"
>
{tab.icon}
{tab.label}

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