Compare commits

..

8 Commits

Author SHA1 Message Date
Srikanth Chekuri
8a6de08530 Merge branch 'main' into issue_4522 2026-05-16 22:38:16 +05:30
nityanandagohain
04824cf2f2 fix: lint 2026-05-11 22:06:29 +05:30
Nityananda Gohain
384c649ef8 Merge branch 'main' into issue_4522 2026-05-11 22:03:43 +05:30
nityanandagohain
68693f8ffd fix: more updated 2026-05-11 22:03:05 +05:30
nityanandagohain
ca1f92f474 fix: get keys after modifying the selectkeys 2026-05-11 21:19:33 +05:30
nityanandagohain
1ed3d8fc8c fix: minor changes 2026-05-11 20:25:05 +05:30
nityanandagohain
196aa301c4 Merge remote-tracking branch 'origin/main' into issue_4522 2026-05-11 15:06:05 +05:30
nityanandagohain
51fcc22d8a feat: [traces] time aware dynamic field mapper 2026-05-08 18:11:15 +05:30
389 changed files with 3353 additions and 5446 deletions

View File

@@ -10,13 +10,6 @@ export default defineConfig({
signoz: {
input: {
target: '../docs/api/openapi.yml',
// Perses' `common.JSONRef` (used by `DashboardGridItem.content`) has a
// field tagged `json:"$ref"`, so our spec contains a property literally
// named `$ref`.
// Orval v8's validator (`@scalar/openapi-parser`) treats every `$ref` key
// as a JSON Reference and aborts with `INVALID_REFERENCE` when the value isn't a URI string.
// Safe to disable: yes, the spec is generated by `cmd/openapi.go` and gated by backend CI, not hand-edited.
unsafeDisableValidation: true,
},
output: {
target: './src/api/generated/services',

View File

@@ -144,18 +144,18 @@ const routes: AppRoutes[] = [
// /trace-old serves V3 (URL-only access). Flip the two `component`
// values back to release V3.
{
path: ROUTES.TRACE_DETAIL_OLD,
path: ROUTES.TRACE_DETAIL,
exact: true,
component: TraceDetail,
isPrivate: true,
key: 'TRACE_DETAIL_OLD',
key: 'TRACE_DETAIL',
},
{
path: ROUTES.TRACE_DETAIL,
path: ROUTES.TRACE_DETAIL_OLD,
exact: true,
component: TraceDetailV3,
isPrivate: true,
key: 'TRACE_DETAIL',
key: 'TRACE_DETAIL_OLD',
},
{
path: ROUTES.SETTINGS,

View File

@@ -3,6 +3,7 @@
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'pnpm generate:api'
* SigNoz
* OpenAPI spec version: 0.0.1
*/
import { useQuery } from 'react-query';
import type {

View File

@@ -3,6 +3,7 @@
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'pnpm generate:api'
* SigNoz
* OpenAPI spec version: 0.0.1
*/
import { useMutation, useQuery } from 'react-query';
import type {

View File

@@ -3,6 +3,7 @@
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'pnpm generate:api'
* SigNoz
* OpenAPI spec version: 0.0.1
*/
import { useMutation } from 'react-query';
import type {

View File

@@ -3,6 +3,7 @@
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'pnpm generate:api'
* SigNoz
* OpenAPI spec version: 0.0.1
*/
import { useMutation, useQuery } from 'react-query';
import type {

View File

@@ -3,6 +3,7 @@
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'pnpm generate:api'
* SigNoz
* OpenAPI spec version: 0.0.1
*/
import { useMutation, useQuery } from 'react-query';
import type {

View File

@@ -3,6 +3,7 @@
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'pnpm generate:api'
* SigNoz
* OpenAPI spec version: 0.0.1
*/
import { useMutation, useQuery } from 'react-query';
import type {

View File

@@ -3,6 +3,7 @@
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'pnpm generate:api'
* SigNoz
* OpenAPI spec version: 0.0.1
*/
import { useMutation, useQuery } from 'react-query';
import type {

View File

@@ -3,6 +3,7 @@
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'pnpm generate:api'
* SigNoz
* OpenAPI spec version: 0.0.1
*/
import { useQuery } from 'react-query';
import type {

View File

@@ -3,6 +3,7 @@
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'pnpm generate:api'
* SigNoz
* OpenAPI spec version: 0.0.1
*/
import { useQuery } from 'react-query';
import type {

View File

@@ -3,6 +3,7 @@
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'pnpm generate:api'
* SigNoz
* OpenAPI spec version: 0.0.1
*/
import { useMutation, useQuery } from 'react-query';
import type {

View File

@@ -3,6 +3,7 @@
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'pnpm generate:api'
* SigNoz
* OpenAPI spec version: 0.0.1
*/
import { useQuery } from 'react-query';
import type {

View File

@@ -3,6 +3,7 @@
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'pnpm generate:api'
* SigNoz
* OpenAPI spec version: 0.0.1
*/
import { useQuery } from 'react-query';
import type {

View File

@@ -3,6 +3,7 @@
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'pnpm generate:api'
* SigNoz
* OpenAPI spec version: 0.0.1
*/
import { useMutation } from 'react-query';
import type {

View File

@@ -3,6 +3,7 @@
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'pnpm generate:api'
* SigNoz
* OpenAPI spec version: 0.0.1
*/
import { useMutation, useQuery } from 'react-query';
import type {

View File

@@ -3,6 +3,7 @@
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'pnpm generate:api'
* SigNoz
* OpenAPI spec version: 0.0.1
*/
import { useMutation, useQuery } from 'react-query';
import type {

View File

@@ -3,6 +3,7 @@
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'pnpm generate:api'
* SigNoz
* OpenAPI spec version: 0.0.1
*/
import { useMutation, useQuery } from 'react-query';
import type {

View File

@@ -3,6 +3,7 @@
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'pnpm generate:api'
* SigNoz
* OpenAPI spec version: 0.0.1
*/
import { useMutation, useQuery } from 'react-query';
import type {

View File

@@ -3,6 +3,7 @@
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'pnpm generate:api'
* SigNoz
* OpenAPI spec version: 0.0.1
*/
import { useMutation, useQuery } from 'react-query';
import type {

View File

@@ -3,6 +3,7 @@
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'pnpm generate:api'
* SigNoz
* OpenAPI spec version: 0.0.1
*/
import { useMutation } from 'react-query';
import type {

View File

@@ -3,6 +3,7 @@
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'pnpm generate:api'
* SigNoz
* OpenAPI spec version: 0.0.1
*/
import { useMutation, useQuery } from 'react-query';
import type {

View File

@@ -3,6 +3,7 @@
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'pnpm generate:api'
* SigNoz
* OpenAPI spec version: 0.0.1
*/
import { useMutation, useQuery } from 'react-query';
import type {

View File

@@ -3,6 +3,7 @@
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'pnpm generate:api'
* SigNoz
* OpenAPI spec version: 0.0.1
*/
import { useMutation, useQuery } from 'react-query';
import type {

View File

@@ -3,6 +3,7 @@
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'pnpm generate:api'
* SigNoz
* OpenAPI spec version: 0.0.1
*/
import { useMutation, useQuery } from 'react-query';
import type {

View File

@@ -3,6 +3,7 @@
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'pnpm generate:api'
* SigNoz
* OpenAPI spec version: 0.0.1
*/
import { useMutation, useQuery } from 'react-query';
import type {

View File

@@ -3,6 +3,7 @@
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'pnpm generate:api'
* SigNoz
* OpenAPI spec version: 0.0.1
*/
export interface AlertmanagertypesChannelDTO {
/**

View File

@@ -3,6 +3,7 @@
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'pnpm generate:api'
* SigNoz
* OpenAPI spec version: 0.0.1
*/
import { useMutation, useQuery } from 'react-query';
import type {

View File

@@ -3,6 +3,7 @@
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'pnpm generate:api'
* SigNoz
* OpenAPI spec version: 0.0.1
*/
import { useMutation } from 'react-query';
import type {

View File

@@ -3,6 +3,7 @@
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'pnpm generate:api'
* SigNoz
* OpenAPI spec version: 0.0.1
*/
import { useMutation, useQuery } from 'react-query';
import type {

View File

@@ -3,6 +3,7 @@
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'pnpm generate:api'
* SigNoz
* OpenAPI spec version: 0.0.1
*/
import { useMutation, useQuery } from 'react-query';
import type {

View File

@@ -1,14 +0,0 @@
.wrapper {
cursor: not-allowed;
}
.errorContent {
background: var(--callout-error-background) !important;
border-color: var(--callout-error-border) !important;
backdrop-filter: blur(15px);
border-radius: 4px !important;
color: var(--foreground) !important;
font-style: normal;
font-weight: 400;
white-space: nowrap;
}

View File

@@ -1,145 +0,0 @@
import { ReactElement } from 'react';
import { render, screen } from 'tests/test-utils';
import { buildPermission } from 'hooks/useAuthZ/utils';
import type { AuthZObject, BrandedPermission } from 'hooks/useAuthZ/types';
import { useAuthZ } from 'hooks/useAuthZ/useAuthZ';
import AuthZTooltip from './AuthZTooltip';
jest.mock('hooks/useAuthZ/useAuthZ');
const mockUseAuthZ = useAuthZ as jest.MockedFunction<typeof useAuthZ>;
const noPermissions = {
isLoading: false,
isFetching: false,
error: null,
permissions: null,
refetchPermissions: jest.fn(),
};
const TestButton = (
props: React.ButtonHTMLAttributes<HTMLButtonElement>,
): ReactElement => (
<button type="button" {...props}>
Action
</button>
);
const createPerm = buildPermission(
'create',
'serviceaccount:*' as AuthZObject<'create'>,
);
const attachSAPerm = (id: string): BrandedPermission =>
buildPermission('attach', `serviceaccount:${id}` as AuthZObject<'attach'>);
const attachRolePerm = buildPermission(
'attach',
'role:*' as AuthZObject<'attach'>,
);
describe('AuthZTooltip — single check', () => {
it('renders child unchanged when permission is granted', () => {
mockUseAuthZ.mockReturnValue({
...noPermissions,
permissions: { [createPerm]: { isGranted: true } },
});
render(
<AuthZTooltip checks={[createPerm]}>
<TestButton />
</AuthZTooltip>,
);
expect(screen.getByRole('button', { name: 'Action' })).not.toBeDisabled();
});
it('disables child when permission is denied', () => {
mockUseAuthZ.mockReturnValue({
...noPermissions,
permissions: { [createPerm]: { isGranted: false } },
});
render(
<AuthZTooltip checks={[createPerm]}>
<TestButton />
</AuthZTooltip>,
);
expect(screen.getByRole('button', { name: 'Action' })).toBeDisabled();
});
it('disables child while loading', () => {
mockUseAuthZ.mockReturnValue({ ...noPermissions, isLoading: true });
render(
<AuthZTooltip checks={[createPerm]}>
<TestButton />
</AuthZTooltip>,
);
expect(screen.getByRole('button', { name: 'Action' })).toBeDisabled();
});
});
describe('AuthZTooltip — multi-check (checks array)', () => {
it('renders child enabled when all checks are granted', () => {
const sa = attachSAPerm('sa-1');
mockUseAuthZ.mockReturnValue({
...noPermissions,
permissions: {
[sa]: { isGranted: true },
[attachRolePerm]: { isGranted: true },
},
});
render(
<AuthZTooltip checks={[sa, attachRolePerm]}>
<TestButton />
</AuthZTooltip>,
);
expect(screen.getByRole('button', { name: 'Action' })).not.toBeDisabled();
});
it('disables child when first check is denied, second granted', () => {
const sa = attachSAPerm('sa-1');
mockUseAuthZ.mockReturnValue({
...noPermissions,
permissions: {
[sa]: { isGranted: false },
[attachRolePerm]: { isGranted: true },
},
});
render(
<AuthZTooltip checks={[sa, attachRolePerm]}>
<TestButton />
</AuthZTooltip>,
);
expect(screen.getByRole('button', { name: 'Action' })).toBeDisabled();
});
it('disables child when both checks are denied and lists denied permissions in data attr', () => {
const sa = attachSAPerm('sa-1');
mockUseAuthZ.mockReturnValue({
...noPermissions,
permissions: {
[sa]: { isGranted: false },
[attachRolePerm]: { isGranted: false },
},
});
render(
<AuthZTooltip checks={[sa, attachRolePerm]}>
<TestButton />
</AuthZTooltip>,
);
expect(screen.getByRole('button', { name: 'Action' })).toBeDisabled();
const wrapper = screen.getByRole('button', { name: 'Action' }).parentElement;
expect(wrapper?.getAttribute('data-denied-permissions')).toContain(sa);
expect(wrapper?.getAttribute('data-denied-permissions')).toContain(
attachRolePerm,
);
});
});

View File

@@ -1,85 +0,0 @@
import { ReactElement, cloneElement, useMemo } from 'react';
import {
TooltipRoot,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@signozhq/ui/tooltip';
import type { BrandedPermission } from 'hooks/useAuthZ/types';
import { useAuthZ } from 'hooks/useAuthZ/useAuthZ';
import { parsePermission } from 'hooks/useAuthZ/utils';
import styles from './AuthZTooltip.module.scss';
interface AuthZTooltipProps {
checks: BrandedPermission[];
children: ReactElement;
enabled?: boolean;
tooltipMessage?: string;
}
function formatDeniedMessage(
denied: BrandedPermission[],
override?: string,
): string {
if (override) {
return override;
}
const labels = denied.map((p) => {
const { relation, object } = parsePermission(p);
const resource = object.split(':')[0];
return `${relation} ${resource}`;
});
return labels.length === 1
? `You don't have ${labels[0]} permission`
: `You don't have ${labels.join(', ')} permissions`;
}
function AuthZTooltip({
checks,
children,
enabled = true,
tooltipMessage,
}: AuthZTooltipProps): JSX.Element {
const shouldCheck = enabled && checks.length > 0;
const { permissions, isLoading } = useAuthZ(checks, { enabled: shouldCheck });
const deniedPermissions = useMemo(() => {
if (!permissions) {
return [];
}
return checks.filter((p) => permissions[p]?.isGranted === false);
}, [checks, permissions]);
if (shouldCheck && isLoading) {
return (
<span className={styles.wrapper}>
{cloneElement(children, { disabled: true })}
</span>
);
}
if (!shouldCheck || deniedPermissions.length === 0) {
return children;
}
return (
<TooltipProvider>
<TooltipRoot>
<TooltipTrigger asChild>
<span
className={styles.wrapper}
data-denied-permissions={deniedPermissions.join(',')}
>
{cloneElement(children, { disabled: true })}
</span>
</TooltipTrigger>
<TooltipContent className={styles.errorContent}>
{formatDeniedMessage(deniedPermissions, tooltipMessage)}
</TooltipContent>
</TooltipRoot>
</TooltipProvider>
);
}
export default AuthZTooltip;

View File

@@ -5,6 +5,7 @@ import { useSelector } from 'react-redux';
import { Loader, Search } from '@signozhq/icons';
import { Color } from '@signozhq/design-tokens';
import {
Button,
Flex,
Input,
InputRef,
@@ -16,7 +17,6 @@ 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,8 +105,9 @@ 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" />
@@ -115,19 +116,17 @@ 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 { Flex, Modal } from 'antd';
import { Button, Flex, Modal } from 'antd';
import updateUserPreference from 'api/v1/user/preferences/name/update';
import cx from 'classnames';
import { USER_PREFERENCES } from 'constants/userPreferences';
@@ -14,7 +14,6 @@ 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;
@@ -116,13 +115,13 @@ function ChangelogModal({ changelog, onClose }: Props): JSX.Element {
>
{!isCloudUser && (
<div className="changelog-modal-footer-ctas">
<Button onClick={onClose} variant="outlined" color="secondary">
<Button type="default" onClick={onClose}>
<Flex align="center" gap="4px">
<X size="md" />
Skip for now
</Flex>
</Button>
<Button onClick={onClickUpdateWorkspace}>
<Button type="primary" onClick={onClickUpdateWorkspace}>
<Flex align="center" gap="4px">
<Check size="md" />
Update my workspace

View File

@@ -1,9 +1,8 @@
import { useState } from 'react';
import { useMutation } from 'react-query';
import { useLocation } from 'react-router-dom';
import { Modal } from 'antd';
import { Button, 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';
@@ -73,8 +72,6 @@ export default function ChatSupportGateway(): JSX.Element {
setIsAddCreditCardModalOpen(true);
}}
variant="outlined"
color="secondary"
>
<MessageSquareText size={24} />
</Button>
@@ -93,19 +90,19 @@ export default function ChatSupportGateway(): JSX.Element {
key="cancel"
onClick={(): void => setIsAddCreditCardModalOpen(false)}
className="cancel-btn"
variant="outlined"
color="secondary"
prefix={<X size={16} />}
icon={<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

@@ -2,8 +2,6 @@ import { Controller, useForm } from 'react-hook-form';
import { useQueryClient } from 'react-query';
import { X } from '@signozhq/icons';
import { Button } from '@signozhq/ui/button';
import AuthZTooltip from 'components/AuthZTooltip/AuthZTooltip';
import { SACreatePermission } from 'hooks/useAuthZ/permissions/service-account.permissions';
import { DialogFooter, DialogWrapper } from '@signozhq/ui/dialog';
import { Input } from '@signozhq/ui/input';
import { toast } from '@signozhq/ui/sonner';
@@ -134,19 +132,17 @@ function CreateServiceAccountModal(): JSX.Element {
Cancel
</Button>
<AuthZTooltip checks={[SACreatePermission]}>
<Button
type="submit"
// @ts-expect-error -- form prop not in @signozhq/ui Button type - TODO: Fix this - @SagarRajput
form="create-sa-form"
variant="solid"
color="primary"
loading={isSubmitting}
disabled={!isValid}
>
Create Service Account
</Button>
</AuthZTooltip>
<Button
type="submit"
// @ts-expect-error -- form prop not in @signozhq/ui Button type - TODO: Fix this - @SagarRajput
form="create-sa-form"
variant="solid"
color="primary"
loading={isSubmitting}
disabled={!isValid}
>
Create Service Account
</Button>
</DialogFooter>
</DialogWrapper>
);

View File

@@ -11,15 +11,6 @@ import {
import CreateServiceAccountModal from '../CreateServiceAccountModal';
jest.mock('components/AuthZTooltip/AuthZTooltip', () => ({
__esModule: true,
default: ({
children,
}: {
children: React.ReactElement;
}): React.ReactElement => children,
}));
jest.mock('@signozhq/ui/sonner', () => ({
...jest.requireActual('@signozhq/ui/sonner'),
toast: { success: jest.fn(), error: jest.fn() },
@@ -122,9 +113,7 @@ describe('CreateServiceAccountModal', () => {
getErrorMessage: expect.any(Function),
}),
);
const passedError = showErrorModal.mock.calls[0][0] as {
getErrorMessage: () => string;
};
const passedError = showErrorModal.mock.calls[0][0] as any;
expect(passedError.getErrorMessage()).toBe('Internal Server Error');
});
@@ -143,9 +132,6 @@ describe('CreateServiceAccountModal', () => {
await user.click(screen.getByRole('button', { name: /Cancel/i }));
await waitForElementToBeRemoved(dialog);
expect(
screen.queryByRole('dialog', { name: /New Service Account/i }),
).not.toBeInTheDocument();
});
it('shows "Name is required" after clearing the name field', async () => {
@@ -156,8 +142,6 @@ describe('CreateServiceAccountModal', () => {
await user.type(nameInput, 'Bot');
await user.clear(nameInput);
await expect(
screen.findByText('Name is required'),
).resolves.toBeInTheDocument();
await screen.findByText('Name is required');
});
});

View File

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

View File

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

View File

@@ -8,6 +8,7 @@ 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';
@@ -31,7 +32,6 @@ 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,14 +249,15 @@ 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();
@@ -270,7 +271,6 @@ function CustomTimePickerPopoverContent({
? option.value === 'custom' && !isLiveLogsEnabled && 'active'
: selectedTime === option.value && !isLiveLogsEnabled && 'active',
)}
variant="ghost"
>
<span className="time-label">{option.label}</span>
@@ -370,12 +370,11 @@ function CustomTimePickerPopoverContent({
<div className="timezone-container__right">
<Button
className="timezone-change-button"
type="text"
size="small"
className="periscope-btn text timezone-change-button"
onClick={handleTimezoneHintClick}
size="sm"
variant="ghost"
prefix={<PenLine size={10} />}
color="none"
icon={<PenLine size={10} />}
>
Change Timezone
</Button>

View File

@@ -1,7 +1,6 @@
import { useCallback, useMemo, useState } from 'react';
import { Popover, Radio, Tooltip } from 'antd';
import { Button, 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';
@@ -105,11 +104,12 @@ 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,18 +137,16 @@ export default function DownloadOptionsMenu({
>
<Tooltip title="Download" placement="top">
<Button
data-testid={`periscope-btn-download-${dataSource}`}
disabled={isDownloading}
variant="outlined"
color="secondary"
size="icon"
prefix={
className="periscope-btn ghost"
icon={
isDownloading ? (
<LoaderCircle size={14} className="animate-spin" />
) : (
<Download size={14} />
)
}
data-testid={`periscope-btn-download-${dataSource}`}
disabled={isDownloading}
/>
</Tooltip>
</Popover>

View File

@@ -1,9 +1,8 @@
import { useState } from 'react';
import { Ellipsis } from '@signozhq/icons';
import { Dropdown, MenuProps } from 'antd';
import { Button, Dropdown, MenuProps } from 'antd';
import './DropDown.styles.scss';
import { Button } from '@signozhq/ui/button';
function DropDown({
element,
@@ -32,12 +31,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

@@ -1,6 +1,6 @@
import React from 'react';
import { Color } from '@signozhq/design-tokens';
import { Modal, Tag } from 'antd';
import { Button, Modal, Tag } from 'antd';
import { CircleAlert, X } from '@signozhq/icons';
import KeyValueLabel from 'periscope/components/KeyValueLabel';
import { useAppContext } from 'providers/App/App';
@@ -9,7 +9,6 @@ import APIError from 'types/api/error';
import ErrorContent from './components/ErrorContent';
import './ErrorModal.styles.scss';
import { Button } from '@signozhq/ui/button';
type Props = {
error: APIError;
@@ -74,11 +73,10 @@ 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,8 +1,16 @@
import { useState } from 'react';
import { useCopyToClipboard } from 'react-use';
import { Col, Dropdown, MenuProps, Popover, Row, Select, Space } from 'antd';
import {
Button,
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';
@@ -151,6 +159,7 @@ function ExplorerCard({
],
};
const saveButtonType = isQueryUpdated ? 'default' : 'primary';
const saveButtonIcon = isQueryUpdated ? null : <Save size="md" />;
const showSaveView = false;
@@ -201,7 +210,7 @@ function ExplorerCard({
</Space>
)}
{isQueryUpdated && (
<Button onClick={onUpdateQueryHandler} prefix={<Save />}>
<Button type="primary" icon={<Save />} onClick={onUpdateQueryHandler}>
Save changes
</Button>
)}
@@ -221,10 +230,9 @@ 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,13 +1,34 @@
import { ReactElement } from 'react';
import {
AuthtypesGettableTransactionDTO,
AuthtypesTransactionDTO,
} from 'api/generated/services/sigNoz.schemas';
import { ENVIRONMENT } from 'constants/env';
import { BrandedPermission } from 'hooks/useAuthZ/types';
import { buildPermission } from 'hooks/useAuthZ/utils';
import { server } from 'mocks-server/server';
import { rest } from 'msw';
import { render, screen, waitFor } from 'tests/test-utils';
import { AUTHZ_CHECK_URL, authzMockResponse } from 'tests/authz-test-utils';
import { GuardAuthZ } from './GuardAuthZ';
const BASE_URL = ENVIRONMENT.baseURL || '';
const AUTHZ_CHECK_URL = `${BASE_URL}/api/v1/authz/check`;
function authzMockResponse(
payload: AuthtypesTransactionDTO[],
authorizedByIndex: boolean[],
): { data: AuthtypesGettableTransactionDTO[]; status: string } {
return {
data: payload.map((txn, i) => ({
relation: txn.relation,
object: txn.object,
authorized: authorizedByIndex[i] ?? false,
})),
status: 'success',
};
}
describe('GuardAuthZ', () => {
const TestChild = (): ReactElement => <div>Protected Content</div>;
const LoadingFallback = (): ReactElement => <div>Loading...</div>;

View File

@@ -1,9 +1,8 @@
import { useCallback, useEffect, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { toast } from '@signozhq/ui/sonner';
import { Input, Radio, RadioChangeEvent } from 'antd';
import { Button, 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';
@@ -126,11 +125,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

@@ -156,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,9 +4,8 @@ import { useSelector } from 'react-redux';
import { matchPath, useLocation } from 'react-router-dom';
import { useCopyToClipboard } from 'react-use';
import { Color } from '@signozhq/design-tokens';
import { Switch } from 'antd';
import { Button, 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';
@@ -156,10 +155,9 @@ function ShareURLModal(): JSX.Element {
</div>
<Button
className="periscope-btn secondary"
onClick={handleCopyURL}
variant="outlined"
color="secondary"
prefix={isURLCopied ? <Check size={14} /> : <Link2 size={14} />}
icon={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,18 +57,17 @@ export default function WaitlistFragment({
</Typography.Text>
<Button
className="join-waitlist-btn"
className="periscope-btn join-waitlist-btn"
type="default"
loading={isSubmitting}
onClick={handleJoinWaitlist}
variant="outlined"
color="secondary"
prefix={
icon={
isSuccess ? (
<CircleCheckBig size={16} color={Color.BG_FOREST_500} />
) : (
<HandPlatter size={16} />
)
}
onClick={handleJoinWaitlist}
>
Get early access
</Button>

View File

@@ -1,7 +1,6 @@
import { useState } from 'react';
import { Input } from 'antd';
import { Button, Input } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { Button } from '@signozhq/ui/button';
import cx from 'classnames';
import { X } from '@signozhq/icons';
@@ -56,12 +55,9 @@ function InputWithLabel({
{labelAfter && <Typography.Text className="label">{label}</Typography.Text>}
{onClose && (
<Button
className="close-btn"
className="periscope-btn ghost close-btn"
icon={closeIcon || <X size={16} />}
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);
> button:hover {
> .ant-btn:hover {
color: var(--bg-amber-400) !important;
border-color: var(--bg-amber-300) !important;
}

View File

@@ -1,9 +1,8 @@
import { useMemo, useState } from 'react';
import { useMutation } from 'react-query';
import { useLocation } from 'react-router-dom';
import { Modal, Tooltip } from 'antd';
import { Button, 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';
@@ -171,9 +170,7 @@ function LaunchChatSupport({
<Button
className={cx('periscope-btn', 'facing-issue-button', className)}
onClick={handleFacingIssuesClick}
variant="outlined"
color="secondary"
prefix={<CircleHelp size={14} />}
icon={<CircleHelp size={14} />}
>
{buttonText || 'Facing issues?'}
</Button>
@@ -192,19 +189,19 @@ function LaunchChatSupport({
key="cancel"
onClick={(): void => setIsAddCreditCardModalOpen(false)}
className="cancel-btn"
variant="outlined"
color="secondary"
prefix={<X size={16} />}
icon={<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 className="learn-more" onClick={handleClick} variant="link">
<Button type="link" className="learn-more" onClick={handleClick}>
<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;
button {
.ant-btn {
display: flex;
align-items: center;
}

View File

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

View File

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

View File

@@ -1,9 +1,8 @@
import { memo, MouseEventHandler } from 'react';
import { Link, TextSelect } from '@signozhq/icons';
import { Tooltip } from 'antd';
import { Button, Tooltip } from 'antd';
import './LogLinesActionButtons.styles.scss';
import { Button } from '@signozhq/ui/button';
export interface LogLinesActionButtonsProps {
handleShowContext: MouseEventHandler<HTMLElement>;
@@ -20,22 +19,18 @@ 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,7 +1,6 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { Input, InputNumber, Popover, Tooltip } from 'antd';
import { Button, 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';
@@ -224,7 +223,7 @@ function OptionsMenu({
<Button
onClick={(): void => setIsFontSizeOptionsOpen(false)}
className="back-btn"
variant="ghost"
type="text"
>
<ChevronLeft size={14} className="icon" />
<Typography.Text className="text">Select font size</Typography.Text>
@@ -236,7 +235,7 @@ function OptionsMenu({
setFontSizeValue(FontSize.SMALL);
}}
className="option-btn"
variant="ghost"
type="text"
>
<Typography.Text className="text">{FontSize.SMALL}</Typography.Text>
{fontSizeValue === FontSize.SMALL && (
@@ -248,7 +247,7 @@ function OptionsMenu({
setFontSizeValue(FontSize.MEDIUM);
}}
className="option-btn"
variant="ghost"
type="text"
>
<Typography.Text className="text">{FontSize.MEDIUM}</Typography.Text>
{fontSizeValue === FontSize.MEDIUM && (
@@ -260,7 +259,7 @@ function OptionsMenu({
setFontSizeValue(FontSize.LARGE);
}}
className="option-btn"
variant="ghost"
type="text"
>
<Typography.Text className="text">{FontSize.LARGE}</Typography.Text>
{fontSizeValue === FontSize.LARGE && (
@@ -339,10 +338,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" />
@@ -473,11 +472,9 @@ 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,4 +1,5 @@
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';
@@ -7,7 +8,6 @@ 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,9 +94,7 @@ function MessagingQueueHealthCheck({
'config-btn',
missingConfiguration ? 'missing-config-btn' : '',
)}
variant="outlined"
color="secondary"
prefix={<Bolt size={12} />}
icon={<Bolt size={12} />}
>
<div className="config-btn-content">
{missingConfiguration

View File

@@ -18,9 +18,8 @@ import {
RefreshCw,
} from '@signozhq/icons';
import { Color } from '@signozhq/design-tokens';
import { Checkbox, Select } from 'antd';
import { Button, 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';
@@ -768,11 +767,11 @@ const CustomMultiSelect: React.FC<CustomMultiSelectProps> = ({
<div className="option-badge">{capitalize(option.type)}</div>
)}
{option.value && ensureValidOption(option.value) && (
<Button className="only-btn" variant="ghost">
<Button type="text" className="only-btn">
{currentToggleTagValue({ option: option.value })}
</Button>
)}
<Button className="toggle-btn" variant="ghost">
<Button type="text" className="toggle-btn">
Toggle
</Button>
</div>

View File

@@ -1,4 +0,0 @@
.callout {
box-sizing: border-box;
width: 100%;
}

View File

@@ -1,22 +0,0 @@
import { render, screen } from 'tests/test-utils';
import PermissionDeniedCallout from './PermissionDeniedCallout';
describe('PermissionDeniedCallout', () => {
it('renders the permission name in the callout message', () => {
render(<PermissionDeniedCallout permissionName="serviceaccount:attach" />);
expect(screen.getByText(/You don't have/)).toBeInTheDocument();
expect(screen.getByText(/serviceaccount:attach/)).toBeInTheDocument();
expect(screen.getByText(/permission/)).toBeInTheDocument();
});
it('accepts an optional className', () => {
const { container } = render(
<PermissionDeniedCallout
permissionName="serviceaccount:read"
className="custom-class"
/>,
);
expect(container.firstChild).toHaveClass('custom-class');
});
});

View File

@@ -1,26 +0,0 @@
import { Callout } from '@signozhq/ui/callout';
import cx from 'classnames';
import styles from './PermissionDeniedCallout.module.scss';
interface PermissionDeniedCalloutProps {
permissionName: string;
className?: string;
}
function PermissionDeniedCallout({
permissionName,
className,
}: PermissionDeniedCalloutProps): JSX.Element {
return (
<Callout
type="error"
showIcon
size="small"
className={cx(styles.callout, className)}
>
{`You don't have ${permissionName} permission`}
</Callout>
);
}
export default PermissionDeniedCallout;

View File

@@ -1,44 +0,0 @@
.container {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
min-height: 50vh;
padding: var(--spacing-10);
}
.content {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: var(--spacing-2);
max-width: 512px;
}
.icon {
margin-bottom: var(--spacing-1);
}
.title {
margin: 0;
font-size: var(--label-base-500-font-size);
font-weight: var(--label-base-500-font-weight);
line-height: var(--line-height-18);
letter-spacing: -0.07px;
color: var(--l1-foreground);
}
.subtitle {
margin: 0;
font-size: var(--label-base-400-font-size);
font-weight: var(--label-base-400-font-weight);
line-height: var(--line-height-18);
letter-spacing: -0.07px;
color: var(--l2-foreground);
}
.permission {
font-family: monospace;
color: var(--l2-foreground);
}

View File

@@ -1,21 +0,0 @@
import { render, screen } from 'tests/test-utils';
import PermissionDeniedFullPage from './PermissionDeniedFullPage';
describe('PermissionDeniedFullPage', () => {
it('renders the title and subtitle with the permissionName interpolated', () => {
render(<PermissionDeniedFullPage permissionName="serviceaccount:list" />);
expect(
screen.getByText("Uh-oh! You don't have permission to view this page."),
).toBeInTheDocument();
expect(screen.getByText(/serviceaccount:list/)).toBeInTheDocument();
expect(
screen.getByText(/Please ask your SigNoz administrator to grant access/),
).toBeInTheDocument();
});
it('renders with a different permissionName', () => {
render(<PermissionDeniedFullPage permissionName="role:read" />);
expect(screen.getByText(/role:read/)).toBeInTheDocument();
});
});

View File

@@ -1,31 +0,0 @@
import { CircleSlash2 } from '@signozhq/icons';
import styles from './PermissionDeniedFullPage.module.scss';
import { Style } from '@signozhq/design-tokens';
interface PermissionDeniedFullPageProps {
permissionName: string;
}
function PermissionDeniedFullPage({
permissionName,
}: PermissionDeniedFullPageProps): JSX.Element {
return (
<div className={styles.container}>
<div className={styles.content}>
<span className={styles.icon}>
<CircleSlash2 color={Style.CALLOUT_WARNING_TITLE} size={14} />
</span>
<p className={styles.title}>
Uh-oh! You don&apos;t have permission to view this page.
</p>
<p className={styles.subtitle}>
You need <code className={styles.permission}>{permissionName}</code> to
view this page. Please ask your SigNoz administrator to grant access.
</p>
</div>
</div>
);
}
export default PermissionDeniedFullPage;

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,12 +368,9 @@ function HavingFilter({
}}
/>
<Button
className="close-btn"
className="close-btn periscope-btn ghost"
icon={<ChevronUp size={16} />}
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 { Radio, RadioChangeEvent, Tooltip } from 'antd';
import { Button, Radio, RadioChangeEvent, Tooltip } from 'antd';
import InputWithLabel from 'components/InputWithLabel/InputWithLabel';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { GroupByFilter } from 'container/QueryBuilder/filters/GroupByFilter/GroupByFilter';
@@ -17,7 +17,6 @@ import HavingFilter from './HavingFilter/HavingFilter';
import { buildDefaultLegendFromGroupBy } from './utils';
import './QueryAddOns.styles.scss';
import { Button } from '@signozhq/ui/button';
interface AddOn {
icon: React.ReactNode;
@@ -371,12 +370,9 @@ function QueryAddOns({
/>
</div>
<Button
className="close-btn"
className="close-btn periscope-btn ghost"
icon={<ChevronUp size={16} />}
onClick={(): void => handleRemoveView('group_by')}
variant="outlined"
color="secondary"
size="icon"
prefix={<ChevronUp size={16} />}
/>
</div>
</div>
@@ -459,12 +455,9 @@ function QueryAddOns({
</div>
{!isListViewPanel && (
<Button
className="close-btn"
className="close-btn periscope-btn ghost"
icon={<ChevronUp size={16} />}
onClick={(): void => handleRemoveView('order_by')}
variant="outlined"
color="secondary"
size="icon"
prefix={<ChevronUp size={16} />}
/>
)}
</div>
@@ -495,12 +488,9 @@ function QueryAddOns({
</div>
<Button
className="close-btn"
className="close-btn periscope-btn ghost"
icon={<ChevronUp size={16} />}
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 { Popover, Tooltip } from 'antd';
import { Button, Popover, Tooltip } from 'antd';
import { getKeySuggestions } from 'api/querySuggestions/getKeySuggestions';
import { QUERY_BUILDER_KEY_TYPES } from 'constants/antlrQueryConstants';
import { QueryBuilderKeys } from 'constants/queryBuilder';
@@ -36,7 +36,6 @@ 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',
@@ -721,10 +720,9 @@ function QueryAggregationSelect({
overlayClassName="query-aggregation-error-popover"
>
<Button
className="query-aggregation-error-btn"
variant="ghost"
size="icon"
prefix={<TriangleAlert size={14} color={Color.BG_CHERRY_500} />}
type="text"
icon={<TriangleAlert size={14} color={Color.BG_CHERRY_500} />}
className="periscope-btn ghost query-aggregation-error-btn"
/>
</Popover>
</div>

View File

@@ -1,7 +1,6 @@
import { useMemo } from 'react';
import { Tooltip } from 'antd';
import { Button, 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';
@@ -57,11 +56,9 @@ function TraceOperatorSection({
}
>
<Button
className="add-trace-operator-button"
className="add-trace-operator-button periscope-btn"
icon={<DraftingCompass size={16} />}
onClick={(): void => addTraceOperator?.()}
variant="outlined"
color="secondary"
prefix={<DraftingCompass size={16} />}
>
<div className="qb-trace-operator-button-container-text">
Add Trace Matching
@@ -95,12 +92,9 @@ 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"
className="add-new-query-button periscope-btn "
icon={<Plus size={16} />}
onClick={addNewBuilderQuery}
variant="outlined"
color="secondary"
size="icon"
prefix={<Plus size={16} />}
/>
</Tooltip>
</div>
@@ -124,11 +118,9 @@ export default function QueryFooter({
}
>
<Button
className="add-formula-button"
className="add-formula-button periscope-btn "
icon={<Sigma size={16} />}
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 { Card, Collapse, Popover, Tag, Tooltip } from 'antd';
import { Button, Card, Collapse, Popover, Tag, Tooltip } from 'antd';
import { getKeySuggestions } from 'api/querySuggestions/getKeySuggestions';
import { getValueSuggestions } from 'api/querySuggestions/getValueSuggestion';
import cx from 'classnames';
@@ -49,7 +49,6 @@ import { queryExamples } from './constants';
import { combineInitialAndUserExpression } from './utils';
import './QuerySearch.styles.scss';
import { Button } from '@signozhq/ui/button';
const { Panel } = Collapse;
@@ -1485,16 +1484,15 @@ function QuerySearch({
>
{validation.isValid ? (
<Button
variant="ghost"
size="icon"
type="text"
icon={<CircleCheck size="md" />}
className="periscope-btn ghost"
prefix={<CircleCheck size={14} />}
/>
) : (
<Button
variant="ghost"
size="icon"
prefix={<TriangleAlert size={14} color={Color.BG_CHERRY_500} />}
type="text"
icon={<TriangleAlert size={14} color={Color.BG_CHERRY_500} />}
className="periscope-btn ghost"
/>
)}
</Popover>

View File

@@ -1,6 +1,5 @@
import { useCallback } from 'react';
import { Tooltip } from 'antd';
import { Button } from '@signozhq/ui/button';
import { Button, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import cx from 'classnames';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
@@ -109,7 +108,7 @@ export default function TraceOperator({
)}
</div>
<Tooltip title="Remove Trace Operator" placement="topLeft">
<Button onClick={removeTraceOperator} variant="outlined" color="secondary">
<Button className="periscope-btn ghost" onClick={removeTraceOperator}>
<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 { Popover } from 'antd';
import { Button, Popover } from 'antd';
import cx from 'classnames';
import {
TRACE_OPERATOR_OPERATORS,
@@ -34,7 +34,6 @@ 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({
@@ -466,15 +465,15 @@ function TraceOperatorEditor({
>
{validation.isValid ? (
<Button
variant="ghost"
size="icon"
prefix={<CircleCheck size={14} />}
type="text"
icon={<CircleCheck size="md" />}
className="periscope-btn ghost"
/>
) : (
<Button
variant="ghost"
size="icon"
prefix={<TriangleAlert size={14} color={Color.BG_CHERRY_500} />}
type="text"
icon={<TriangleAlert size={14} color={Color.BG_CHERRY_500} />}
className="periscope-btn ghost"
/>
)}
</Popover>

View File

@@ -1,7 +1,6 @@
/* eslint-disable sonarjs/no-identical-functions */
import { Fragment, useMemo, useState } from 'react';
import { Checkbox, Input, Skeleton } from 'antd';
import { Button } from '@signozhq/ui/button';
import { Button, Checkbox, Input, Skeleton } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import cx from 'classnames';
import { removeKeysFromExpression } from 'components/QueryBuilderV2/utils';
@@ -661,14 +660,14 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
{String(value)}
</Typography.Text>
)}
<Button className="only-btn" variant="ghost">
<Button type="text" className="only-btn">
{isSomeFilterPresentForCurrentAttribute
? currentFilterState[value] && !isMultipleValuesTrueForTheKey
? 'All'
: 'Only'
: 'Only'}
</Button>
<Button className="toggle-btn" variant="ghost">
<Button type="text" className="toggle-btn">
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 { Collapse } from 'antd';
import { Button, Collapse } from 'antd';
import {
IQuickFiltersConfig,
QuickFiltersSource,
@@ -21,7 +21,6 @@ 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,
@@ -300,9 +299,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,13 +50,11 @@ function SortableFilter({
</div>
{allowRemove && (
<Button
className="remove-filter-btn"
className="remove-filter-btn periscope-btn"
size="small"
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 { Skeleton } from 'antd';
import { Button, 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,7 +12,6 @@ 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 (
@@ -147,11 +146,9 @@ 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"
className="add-filter-btn periscope-btn"
size="small"
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 { Input } from 'antd';
import { Button, Input } from 'antd';
import { Check, TableColumnsSplit, X } from '@signozhq/icons';
import { Filter as FilterType } from 'types/api/quickFilters/getCustomFilters';
@@ -9,7 +9,6 @@ import useQuickFilterSettings from './hooks/useQuickFilterSettings';
import OtherFilters from './OtherFilters';
import './QuickFiltersSettings.styles.scss';
import { Button } from '@signozhq/ui/button';
function QuickFiltersSettings({
signal,
@@ -87,17 +86,17 @@ function QuickFiltersSettings({
{hasUnsavedChanges && (
<div className="qf-footer">
<Button
type="default"
onClick={handleDiscardChanges}
variant="outlined"
color="secondary"
prefix={<X size={16} />}
icon={<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,15 +1,16 @@
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Tooltip } from 'antd';
import { Button, 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']);
@@ -34,12 +35,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>
@@ -48,6 +49,7 @@ 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 { Dropdown, Flex, MenuProps, Switch } from 'antd';
import { Button, Dropdown, Flex, MenuProps, Switch } from 'antd';
import logEvent from 'api/common/logEvent';
import LaunchChatSupport from 'components/LaunchChatSupport/LaunchChatSupport';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
@@ -21,7 +21,6 @@ import {
} from './utils';
import './DynamicColumnTable.syles.scss';
import { Button } from '@signozhq/ui/button';
function DynamicColumnTable({
tablesource,
@@ -134,11 +133,9 @@ 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

@@ -80,7 +80,6 @@ interface BaseProps {
isError?: boolean;
error?: APIError;
onRefetch?: () => void;
disabled?: boolean;
}
interface SingleProps extends BaseProps {
@@ -124,7 +123,6 @@ function RolesSelect(props: RolesSelectProps): JSX.Element {
isError = internalError,
error = convertToApiError(internalErrorObj),
onRefetch = externalRoles === undefined ? internalRefetch : undefined,
disabled,
} = props;
const notFoundContent = isError ? (
@@ -153,7 +151,6 @@ function RolesSelect(props: RolesSelectProps): JSX.Element {
</Checkbox>
)}
getPopupContainer={getPopupContainer}
disabled={disabled}
/>
);
}
@@ -171,7 +168,6 @@ function RolesSelect(props: RolesSelectProps): JSX.Element {
notFoundContent={notFoundContent}
options={options}
getPopupContainer={getPopupContainer}
disabled={disabled}
/>
);
}

View File

@@ -4,11 +4,6 @@ import { Button } from '@signozhq/ui/button';
import { Input } from '@signozhq/ui/input';
import { ToggleGroup, ToggleGroupItem } from '@signozhq/ui/toggle-group';
import { DatePicker } from 'antd';
import AuthZTooltip from 'components/AuthZTooltip/AuthZTooltip';
import {
APIKeyCreatePermission,
buildSAAttachPermission,
} from 'hooks/useAuthZ/permissions/service-account.permissions';
import { popupContainer } from 'utils/selectPopupContainer';
import { disabledDate } from '../utils';
@@ -23,7 +18,6 @@ export interface KeyFormPhaseProps {
isValid: boolean;
onSubmit: () => void;
onClose: () => void;
accountId?: string;
}
function KeyFormPhase({
@@ -34,7 +28,6 @@ function KeyFormPhase({
isValid,
onSubmit,
onClose,
accountId,
}: KeyFormPhaseProps): JSX.Element {
return (
<>
@@ -118,25 +111,17 @@ function KeyFormPhase({
<Button variant="solid" color="secondary" onClick={onClose}>
Cancel
</Button>
<AuthZTooltip
checks={[
APIKeyCreatePermission,
buildSAAttachPermission(accountId ?? ''),
]}
enabled={!!accountId}
<Button
type="submit"
// @ts-expect-error -- form prop not in @signozhq/ui Button type - TODO: Fix this - @SagarRajput
form={FORM_ID}
variant="solid"
color="primary"
loading={isSubmitting}
disabled={!isValid}
>
<Button
type="submit"
// @ts-expect-error -- form prop not in @signozhq/ui Button type - TODO: Fix this - @SagarRajput
form={FORM_ID}
variant="solid"
color="primary"
loading={isSubmitting}
disabled={!isValid}
>
Create Key
</Button>
</AuthZTooltip>
Create Key
</Button>
</div>
</div>
</>

View File

@@ -161,7 +161,6 @@ function AddKeyModal(): JSX.Element {
isValid={isValid}
onSubmit={handleSubmit(handleCreate)}
onClose={handleClose}
accountId={accountId ?? undefined}
/>
)}

View File

@@ -1,8 +1,6 @@
import { useQueryClient } from 'react-query';
import { Trash2, X } from '@signozhq/icons';
import { Button } from '@signozhq/ui/button';
import AuthZTooltip from 'components/AuthZTooltip/AuthZTooltip';
import { buildSADeletePermission } from 'hooks/useAuthZ/permissions/service-account.permissions';
import { DialogWrapper } from '@signozhq/ui/dialog';
import { toast } from '@signozhq/ui/sonner';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
@@ -67,7 +65,7 @@ function DeleteAccountModal(): JSX.Element {
}
function handleCancel(): void {
void setIsDeleteOpen(null);
setIsDeleteOpen(null);
}
const content = (
@@ -84,20 +82,15 @@ function DeleteAccountModal(): JSX.Element {
<X size={12} />
Cancel
</Button>
<AuthZTooltip
checks={[buildSADeletePermission(accountId ?? '')]}
enabled={!!accountId}
<Button
variant="solid"
color="destructive"
loading={isDeleting}
onClick={handleConfirm}
>
<Button
variant="solid"
color="destructive"
loading={isDeleting}
onClick={handleConfirm}
>
<Trash2 size={12} />
Delete
</Button>
</AuthZTooltip>
<Trash2 size={12} />
Delete
</Button>
</div>
);

View File

@@ -7,12 +7,6 @@ import { Input } from '@signozhq/ui/input';
import { ToggleGroup, ToggleGroupItem } from '@signozhq/ui/toggle-group';
import { DatePicker } from 'antd';
import type { ServiceaccounttypesGettableFactorAPIKeyDTO } from 'api/generated/services/sigNoz.schemas';
import AuthZTooltip from 'components/AuthZTooltip/AuthZTooltip';
import {
buildAPIKeyDeletePermission,
buildAPIKeyUpdatePermission,
buildSADetachPermission,
} from 'hooks/useAuthZ/permissions/service-account.permissions';
import { popupContainer } from 'utils/selectPopupContainer';
import { disabledDate, formatLastObservedAt } from '../utils';
@@ -30,8 +24,6 @@ export interface EditKeyFormProps {
onClose: () => void;
onRevokeClick: () => void;
formatTimezoneAdjustedTimestamp: (ts: string, format: string) => string;
canUpdate?: boolean;
accountId?: string;
}
function EditKeyForm({
@@ -45,8 +37,6 @@ function EditKeyForm({
onClose,
onRevokeClick,
formatTimezoneAdjustedTimestamp,
canUpdate = true,
accountId = '',
}: EditKeyFormProps): JSX.Element {
return (
<>
@@ -55,34 +45,12 @@ function EditKeyForm({
<label className="edit-key-modal__label" htmlFor="edit-key-name">
Name
</label>
{!canUpdate ? (
<AuthZTooltip
checks={[buildAPIKeyUpdatePermission(keyItem?.id ?? '')]}
enabled={!!keyItem?.id}
>
<div className="edit-key-modal__key-display">
<span className="edit-key-modal__id-text">{keyItem?.name || '—'}</span>
<LockKeyhole size={12} className="edit-key-modal__lock-icon" />
</div>
</AuthZTooltip>
) : (
<Input
id="edit-key-name"
className="edit-key-modal__input"
placeholder="Enter key name"
{...register('name')}
/>
)}
</div>
<div className="edit-key-modal__field">
<label className="edit-key-modal__label" htmlFor="edit-key-id">
ID
</label>
<div id="edit-key-id" className="edit-key-modal__key-display">
<span className="edit-key-modal__id-text">{keyItem?.id || '—'}</span>
<LockKeyhole size={12} className="edit-key-modal__lock-icon" />
</div>
<Input
id="edit-key-name"
className="edit-key-modal__input"
placeholder="Enter key name"
{...register('name')}
/>
</div>
<div className="edit-key-modal__field">
@@ -105,22 +73,21 @@ function EditKeyForm({
type="single"
value={field.value}
onChange={(val): void => {
if (val && canUpdate) {
if (val) {
field.onChange(val);
}
}}
size="sm"
className="edit-key-modal__expiry-toggle"
>
<ToggleGroupItem
value={ExpiryMode.NONE}
disabled={!canUpdate}
className="edit-key-modal__expiry-toggle-btn"
>
No Expiration
</ToggleGroupItem>
<ToggleGroupItem
value={ExpiryMode.DATE}
disabled={!canUpdate}
className="edit-key-modal__expiry-toggle-btn"
>
Set Expiration Date
@@ -147,7 +114,6 @@ function EditKeyForm({
popupClassName="edit-key-modal-datepicker-popup"
getPopupContainer={popupContainer}
disabledDate={disabledDate}
disabled={!canUpdate}
/>
)}
/>
@@ -167,39 +133,26 @@ function EditKeyForm({
</form>
<div className="edit-key-modal__footer">
<AuthZTooltip
checks={[
buildAPIKeyDeletePermission(keyItem?.id ?? ''),
buildSADetachPermission(accountId ?? ''),
]}
enabled={!!accountId && !!keyItem?.id}
>
<Button variant="link" color="destructive" onClick={onRevokeClick}>
<Trash2 size={12} />
Revoke Key
</Button>
</AuthZTooltip>
<Button variant="link" color="destructive" onClick={onRevokeClick}>
<Trash2 size={12} />
Revoke Key
</Button>
<div className="edit-key-modal__footer-right">
<Button variant="solid" color="secondary" onClick={onClose}>
<X size={12} />
Cancel
</Button>
<AuthZTooltip
checks={[buildAPIKeyUpdatePermission(keyItem?.id ?? '')]}
enabled={!!accountId && !!keyItem?.id}
<Button
type="submit"
// @ts-expect-error -- form prop not in @signozhq/ui Button type - TODO: Fix this - @SagarRajput
form={FORM_ID}
variant="solid"
color="primary"
loading={isSaving}
disabled={!isDirty}
>
<Button
type="submit"
// @ts-expect-error -- form prop not in @signozhq/ui Button type - TODO: Fix this - @SagarRajput
form={FORM_ID}
variant="solid"
color="primary"
loading={isSaving}
disabled={!isDirty}
>
Save Changes
</Button>
</AuthZTooltip>
Save Changes
</Button>
</div>
</div>
</>

View File

@@ -60,16 +60,6 @@
letter-spacing: 2px;
}
&__id-text {
font-size: 13px;
font-family: monospace;
color: var(--foreground);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex: 1;
}
&__lock-icon {
color: var(--foreground);
flex-shrink: 0;

View File

@@ -16,8 +16,6 @@ import type {
import { AxiosError } from 'axios';
import { SA_QUERY_PARAMS } from 'container/ServiceAccountsSettings/constants';
import dayjs from 'dayjs';
import { buildAPIKeyUpdatePermission } from 'hooks/useAuthZ/permissions/service-account.permissions';
import { useAuthZ } from 'hooks/useAuthZ/useAuthZ';
import { parseAsString, useQueryState } from 'nuqs';
import { useErrorModal } from 'providers/ErrorModalProvider';
import { useTimezone } from 'providers/Timezone';
@@ -71,16 +69,6 @@ function EditKeyModal({ keyItem }: EditKeyModalProps): JSX.Element {
const expiryMode = watch('expiryMode');
const { permissions: editPermissions, isLoading: isAuthZLoading } = useAuthZ(
editKeyId ? [buildAPIKeyUpdatePermission(editKeyId)] : [],
{ enabled: !!editKeyId },
);
const canUpdate = isAuthZLoading
? false
: (editPermissions?.[buildAPIKeyUpdatePermission(editKeyId ?? '')]
?.isGranted ?? true);
const { mutate: updateKey, isLoading: isSaving } = useUpdateServiceAccountKey({
mutation: {
onSuccess: async () => {
@@ -127,7 +115,7 @@ function EditKeyModal({ keyItem }: EditKeyModalProps): JSX.Element {
});
function handleClose(): void {
void setEditKeyId(null);
setEditKeyId(null);
setIsRevokeConfirmOpen(false);
}
@@ -181,8 +169,6 @@ function EditKeyModal({ keyItem }: EditKeyModalProps): JSX.Element {
isRevoking={isRevoking}
onCancel={(): void => setIsRevokeConfirmOpen(false)}
onConfirm={handleRevoke}
accountId={selectedAccountId ?? undefined}
keyId={keyItem?.id ?? undefined}
/>
) : undefined
}
@@ -204,8 +190,6 @@ function EditKeyModal({ keyItem }: EditKeyModalProps): JSX.Element {
onClose={handleClose}
onRevokeClick={(): void => setIsRevokeConfirmOpen(true)}
formatTimezoneAdjustedTimestamp={formatTimezoneAdjustedTimestamp}
canUpdate={canUpdate}
accountId={selectedAccountId ?? ''}
/>
)}
</DialogWrapper>

View File

@@ -1,16 +1,9 @@
import React, { useCallback, useMemo } from 'react';
import { useCallback, useMemo } from 'react';
import { KeyRound, X } from '@signozhq/icons';
import { Button } from '@signozhq/ui/button';
import { Skeleton, Table } from 'antd';
import { Skeleton, Table, Tooltip } from 'antd';
import type { ColumnsType } from 'antd/es/table/interface';
import type { ServiceaccounttypesGettableFactorAPIKeyDTO } from 'api/generated/services/sigNoz.schemas';
import AuthZTooltip from 'components/AuthZTooltip/AuthZTooltip';
import {
APIKeyCreatePermission,
buildAPIKeyDeletePermission,
buildSAAttachPermission,
buildSADetachPermission,
} from 'hooks/useAuthZ/permissions/service-account.permissions';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import dayjs from 'dayjs';
import { parseAsBoolean, parseAsString, useQueryState } from 'nuqs';
@@ -24,15 +17,12 @@ interface KeysTabProps {
keys: ServiceaccounttypesGettableFactorAPIKeyDTO[];
isLoading: boolean;
isDisabled?: boolean;
canUpdate?: boolean;
accountId?: string;
currentPage: number;
pageSize: number;
}
interface BuildColumnsParams {
isDisabled: boolean;
accountId: string;
onRevokeClick: (keyId: string) => void;
handleformatLastObservedAt: (
lastObservedAt: Date | null | undefined,
@@ -52,7 +42,6 @@ function formatExpiry(expiresAt: number): JSX.Element {
function buildColumns({
isDisabled,
accountId,
onRevokeClick,
handleformatLastObservedAt,
}: BuildColumnsParams): ColumnsType<ServiceaccounttypesGettableFactorAPIKeyDTO> {
@@ -103,34 +92,22 @@ function buildColumns({
key: 'action',
width: 48,
align: 'right' as const,
onCell: (): {
onClick: (e: React.MouseEvent) => void;
style: React.CSSProperties;
} => ({
onClick: (e): void => e.stopPropagation(),
style: { cursor: 'default' },
}),
render: (_, record): JSX.Element => (
<AuthZTooltip
checks={[
buildAPIKeyDeletePermission(record.id),
buildSADetachPermission(accountId),
]}
enabled={!isDisabled && !!accountId}
>
<Tooltip title={isDisabled ? 'Service account disabled' : 'Revoke Key'}>
<Button
variant="ghost"
size="sm"
color="destructive"
disabled={isDisabled}
onClick={(): void => {
onClick={(e): void => {
e.stopPropagation();
onRevokeClick(record.id);
}}
className="keys-tab__revoke-btn"
>
<X size={12} />
</Button>
</AuthZTooltip>
</Tooltip>
),
},
];
@@ -140,7 +117,6 @@ function KeysTab({
keys,
isLoading,
isDisabled = false,
accountId = '',
currentPage,
pageSize,
}: KeysTabProps): JSX.Element {
@@ -167,20 +143,14 @@ function KeysTab({
const onRevokeClick = useCallback(
(keyId: string): void => {
void setRevokeKeyId(keyId);
setRevokeKeyId(keyId);
},
[setRevokeKeyId],
);
const columns = useMemo(
() =>
buildColumns({
isDisabled,
accountId,
onRevokeClick,
handleformatLastObservedAt,
}),
[isDisabled, accountId, onRevokeClick, handleformatLastObservedAt],
() => buildColumns({ isDisabled, onRevokeClick, handleformatLastObservedAt }),
[isDisabled, onRevokeClick, handleformatLastObservedAt],
);
if (isLoading) {
@@ -206,21 +176,16 @@ function KeysTab({
Learn more
</a>
</p>
<AuthZTooltip
checks={[APIKeyCreatePermission, buildSAAttachPermission(accountId)]}
enabled={!isDisabled && !!accountId}
<Button
variant="link"
color="primary"
onClick={async (): Promise<void> => {
await setIsAddKeyOpen(true);
}}
disabled={isDisabled}
>
<Button
variant="link"
color="primary"
onClick={async (): Promise<void> => {
await setIsAddKeyOpen(true);
}}
disabled={isDisabled}
>
+ Add your first key
</Button>
</AuthZTooltip>
+ Add your first key
</Button>
</div>
);
}

View File

@@ -3,11 +3,9 @@ import { LockKeyhole } from '@signozhq/icons';
import { Badge } from '@signozhq/ui/badge';
import { Input } from '@signozhq/ui/input';
import type { AuthtypesRoleDTO } from 'api/generated/services/sigNoz.schemas';
import AuthZTooltip from 'components/AuthZTooltip/AuthZTooltip';
import RolesSelect from 'components/RolesSelect';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import { ServiceAccountRow } from 'container/ServiceAccountsSettings/utils';
import { buildSAUpdatePermission } from 'hooks/useAuthZ/permissions/service-account.permissions';
import { useTimezone } from 'providers/Timezone';
import APIError from 'types/api/error';
@@ -21,7 +19,6 @@ interface OverviewTabProps {
localRoles: string[];
onRolesChange: (v: string[]) => void;
isDisabled: boolean;
canUpdate?: boolean;
availableRoles: AuthtypesRoleDTO[];
rolesLoading?: boolean;
rolesError?: boolean;
@@ -37,7 +34,6 @@ function OverviewTab({
localRoles,
onRolesChange,
isDisabled,
canUpdate = true,
availableRoles,
rolesLoading,
rolesError,
@@ -67,16 +63,11 @@ function OverviewTab({
<label className="sa-drawer__label" htmlFor="sa-name">
Name
</label>
{isDisabled || !canUpdate ? (
<AuthZTooltip
checks={[buildSAUpdatePermission(account.id)]}
enabled={!isDisabled && !canUpdate}
>
<div className="sa-drawer__input-wrapper sa-drawer__input-wrapper--disabled">
<span className="sa-drawer__input-text">{localName || '—'}</span>
<LockKeyhole size={14} className="sa-drawer__lock-icon" />
</div>
</AuthZTooltip>
{isDisabled ? (
<div className="sa-drawer__input-wrapper sa-drawer__input-wrapper--disabled">
<span className="sa-drawer__input-text">{localName || '—'}</span>
<LockKeyhole size={14} className="sa-drawer__lock-icon" />
</div>
) : (
<Input
id="sa-name"
@@ -87,16 +78,6 @@ function OverviewTab({
)}
</div>
<div className="sa-drawer__field">
<label className="sa-drawer__label" htmlFor="sa-id">
ID
</label>
<div className="sa-drawer__input-wrapper sa-drawer__input-wrapper--disabled">
<span className="sa-drawer__input-text">{account.id || '—'}</span>
<LockKeyhole size={14} className="sa-drawer__lock-icon" />
</div>
</div>
<div className="sa-drawer__field">
<label className="sa-drawer__label" htmlFor="sa-email">
Email Address

View File

@@ -1,11 +1,6 @@
import { useQueryClient } from 'react-query';
import { Trash2, X } from '@signozhq/icons';
import { Button } from '@signozhq/ui/button';
import AuthZTooltip from 'components/AuthZTooltip/AuthZTooltip';
import {
buildAPIKeyDeletePermission,
buildSADetachPermission,
} from 'hooks/useAuthZ/permissions/service-account.permissions';
import { DialogWrapper } from '@signozhq/ui/dialog';
import { toast } from '@signozhq/ui/sonner';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
@@ -28,16 +23,12 @@ export interface RevokeKeyFooterProps {
isRevoking: boolean;
onCancel: () => void;
onConfirm: () => void;
accountId?: string;
keyId?: string;
}
export function RevokeKeyFooter({
isRevoking,
onCancel,
onConfirm,
accountId,
keyId,
}: RevokeKeyFooterProps): JSX.Element {
return (
<>
@@ -45,23 +36,15 @@ export function RevokeKeyFooter({
<X size={12} />
Cancel
</Button>
<AuthZTooltip
checks={[
buildAPIKeyDeletePermission(keyId ?? ''),
buildSADetachPermission(accountId ?? ''),
]}
enabled={!!accountId && !!keyId}
<Button
variant="solid"
color="destructive"
loading={isRevoking}
onClick={onConfirm}
>
<Button
variant="solid"
color="destructive"
loading={isRevoking}
onClick={onConfirm}
>
<Trash2 size={12} />
Revoke Key
</Button>
</AuthZTooltip>
<Trash2 size={12} />
Revoke Key
</Button>
</>
);
}
@@ -132,8 +115,6 @@ function RevokeKeyModal(): JSX.Element {
isRevoking={isRevoking}
onCancel={handleCancel}
onConfirm={handleConfirm}
accountId={accountId ?? undefined}
keyId={revokeKeyId || undefined}
/>
}
>

View File

@@ -16,8 +16,6 @@ import {
import type { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
import { AxiosError } from 'axios';
import ErrorInPlace from 'components/ErrorInPlace/ErrorInPlace';
import { GuardAuthZ } from 'components/GuardAuthZ/GuardAuthZ';
import PermissionDeniedCallout from 'components/PermissionDeniedCallout/PermissionDeniedCallout';
import { useRoles } from 'components/RolesSelect';
import { SA_QUERY_PARAMS } from 'container/ServiceAccountsSettings/constants';
import {
@@ -29,15 +27,6 @@ import {
RoleUpdateFailure,
useServiceAccountRoleManager,
} from 'hooks/serviceAccount/useServiceAccountRoleManager';
import {
APIKeyCreatePermission,
APIKeyListPermission,
buildSAAttachPermission,
buildSADeletePermission,
buildSAReadPermission,
buildSAUpdatePermission,
} from 'hooks/useAuthZ/permissions/service-account.permissions';
import { useAuthZ } from 'hooks/useAuthZ/useAuthZ';
import {
parseAsBoolean,
parseAsInteger,
@@ -48,7 +37,6 @@ import {
import APIError from 'types/api/error';
import { toAPIError } from 'utils/errorUtils';
import AuthZTooltip from 'components/AuthZTooltip/AuthZTooltip';
import AddKeyModal from './AddKeyModal';
import DeleteAccountModal from './DeleteAccountModal';
import KeysTab from './KeysTab';
@@ -108,22 +96,6 @@ function ServiceAccountDrawer({
const queryClient = useQueryClient();
const { permissions: drawerPermissions, isLoading: isAuthZLoading } = useAuthZ(
selectedAccountId
? [
buildSAReadPermission(selectedAccountId),
buildSAUpdatePermission(selectedAccountId),
buildSADeletePermission(selectedAccountId),
APIKeyListPermission,
]
: [],
{ enabled: !!selectedAccountId },
);
const canRead =
drawerPermissions?.[buildSAReadPermission(selectedAccountId ?? '')]
?.isGranted ?? false;
const {
data: accountData,
isLoading: isAccountLoading,
@@ -132,7 +104,7 @@ function ServiceAccountDrawer({
refetch: refetchAccount,
} = useGetServiceAccount(
{ id: selectedAccountId ?? '' },
{ query: { enabled: canRead && !!selectedAccountId } },
{ query: { enabled: !!selectedAccountId } },
);
const account = useMemo(
@@ -145,9 +117,7 @@ function ServiceAccountDrawer({
currentRoles,
isLoading: isRolesLoading,
applyDiff,
} = useServiceAccountRoleManager(selectedAccountId ?? '', {
enabled: canRead && !!selectedAccountId,
});
} = useServiceAccountRoleManager(selectedAccountId ?? '');
const roleSessionRef = useRef<string | null>(null);
@@ -195,16 +165,9 @@ function ServiceAccountDrawer({
refetch: refetchRoles,
} = useRoles();
const canListKeys =
drawerPermissions?.[APIKeyListPermission]?.isGranted ?? false;
const canUpdate =
drawerPermissions?.[buildSAUpdatePermission(selectedAccountId ?? '')]
?.isGranted ?? true;
const { data: keysData, isLoading: keysLoading } = useListServiceAccountKeys(
{ id: selectedAccountId ?? '' },
{ query: { enabled: !!selectedAccountId && canListKeys } },
{ query: { enabled: !!selectedAccountId } },
);
const keys = keysData?.data ?? [];
@@ -429,26 +392,18 @@ function ServiceAccountDrawer({
</ToggleGroupItem>
</ToggleGroup>
{activeTab === ServiceAccountDrawerTab.Keys && (
<AuthZTooltip
checks={[
APIKeyCreatePermission,
buildSAAttachPermission(selectedAccountId ?? ''),
]}
enabled={!isDeleted && !!selectedAccountId}
<Button
variant="outlined"
size="sm"
color="secondary"
disabled={isDeleted}
onClick={(): void => {
void setIsAddKeyOpen(true);
}}
>
<Button
variant="outlined"
size="sm"
color="secondary"
disabled={isDeleted}
onClick={(): void => {
void setIsAddKeyOpen(true);
}}
>
<Plus size={12} />
Add Key
</Button>
</AuthZTooltip>
<Plus size={12} />
Add Key
</Button>
)}
</div>
@@ -457,9 +412,7 @@ function ServiceAccountDrawer({
activeTab === ServiceAccountDrawerTab.Keys ? ' sa-drawer__body--keys' : ''
}`}
>
{(isAuthZLoading || isAccountLoading) && (
<Skeleton active paragraph={{ rows: 6 }} />
)}
{isAccountLoading && <Skeleton active paragraph={{ rows: 6 }} />}
{isAccountError && (
<ErrorInPlace
error={toAPIError(
@@ -468,55 +421,38 @@ function ServiceAccountDrawer({
)}
/>
)}
{!isAuthZLoading &&
!isAccountLoading &&
!isAccountError &&
selectedAccountId && (
<GuardAuthZ
relation="read"
object={`serviceaccount:${selectedAccountId}`}
fallbackOnNoPermissions={(): JSX.Element => (
<PermissionDeniedCallout permissionName="serviceaccount:read" />
)}
>
<>
{activeTab === ServiceAccountDrawerTab.Overview && account && (
<OverviewTab
account={account}
localName={localName}
onNameChange={handleNameChange}
localRoles={localRoles}
onRolesChange={(roles): void => {
setLocalRoles(roles);
clearRoleErrors();
}}
isDisabled={isDeleted}
canUpdate={canUpdate}
availableRoles={availableRoles}
rolesLoading={rolesLoading}
rolesError={rolesError}
rolesErrorObj={rolesErrorObj}
onRefetchRoles={refetchRoles}
saveErrors={saveErrors}
/>
)}
{activeTab === ServiceAccountDrawerTab.Keys &&
(canListKeys ? (
<KeysTab
keys={keys}
isLoading={keysLoading}
isDisabled={isDeleted}
canUpdate={canUpdate}
accountId={selectedAccountId}
currentPage={keysPage}
pageSize={PAGE_SIZE}
/>
) : (
<PermissionDeniedCallout permissionName="factor-api-key:list" />
))}
</>
</GuardAuthZ>
)}
{!isAccountLoading && !isAccountError && (
<>
{activeTab === ServiceAccountDrawerTab.Overview && account && (
<OverviewTab
account={account}
localName={localName}
onNameChange={handleNameChange}
localRoles={localRoles}
onRolesChange={(roles): void => {
setLocalRoles(roles);
clearRoleErrors();
}}
isDisabled={isDeleted}
availableRoles={availableRoles}
rolesLoading={rolesLoading}
rolesError={rolesError}
rolesErrorObj={rolesErrorObj}
onRefetchRoles={refetchRoles}
saveErrors={saveErrors}
/>
)}
{activeTab === ServiceAccountDrawerTab.Keys && (
<KeysTab
keys={keys}
isLoading={keysLoading}
isDisabled={isDeleted}
currentPage={keysPage}
pageSize={PAGE_SIZE}
/>
)}
</>
)}
</div>
</div>
);
@@ -546,21 +482,16 @@ function ServiceAccountDrawer({
) : (
<>
{!isDeleted && (
<AuthZTooltip
checks={[buildSADeletePermission(selectedAccountId ?? '')]}
enabled={!!selectedAccountId}
<Button
variant="link"
color="destructive"
onClick={(): void => {
void setIsDeleteOpen(true);
}}
>
<Button
variant="link"
color="destructive"
onClick={(): void => {
void setIsDeleteOpen(true);
}}
>
<Trash2 size={12} />
Delete Service Account
</Button>
</AuthZTooltip>
<Trash2 size={12} />
Delete Service Account
</Button>
)}
{!isDeleted && (
<div className="sa-drawer__footer-right">

View File

@@ -6,15 +6,6 @@ import { render, screen, userEvent, waitFor } from 'tests/test-utils';
import EditKeyModal from '../EditKeyModal';
jest.mock('components/AuthZTooltip/AuthZTooltip', () => ({
__esModule: true,
default: ({
children,
}: {
children: React.ReactElement;
}): React.ReactElement => children,
}));
jest.mock('@signozhq/ui/sonner', () => ({
...jest.requireActual('@signozhq/ui/sonner'),
toast: { success: jest.fn(), error: jest.fn() },
@@ -28,7 +19,7 @@ const mockKey: ServiceaccounttypesGettableFactorAPIKeyDTO = {
id: 'key-1',
name: 'Original Key Name',
expiresAt: 0,
lastObservedAt: null as unknown as Date,
lastObservedAt: null as any,
serviceAccountId: 'sa-1',
};

View File

@@ -6,15 +6,6 @@ import { render, screen, userEvent, waitFor } from 'tests/test-utils';
import KeysTab from '../KeysTab';
jest.mock('components/AuthZTooltip/AuthZTooltip', () => ({
__esModule: true,
default: ({
children,
}: {
children: React.ReactElement;
}): React.ReactElement => children,
}));
jest.mock('@signozhq/ui/sonner', () => ({
...jest.requireActual('@signozhq/ui/sonner'),
toast: { success: jest.fn(), error: jest.fn() },
@@ -29,7 +20,7 @@ const keys: ServiceaccounttypesGettableFactorAPIKeyDTO[] = [
id: 'key-1',
name: 'Production Key',
expiresAt: 0,
lastObservedAt: null as unknown as Date,
lastObservedAt: null as any,
serviceAccountId: 'sa-1',
},
{

View File

@@ -1,158 +0,0 @@
import type { ReactNode } from 'react';
import { listRolesSuccessResponse } from 'mocks-server/__mockdata__/roles';
import { rest, server } from 'mocks-server/server';
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
import { fireEvent, render, screen, waitFor } from 'tests/test-utils';
import {
setupAuthzAdmin,
setupAuthzDeny,
setupAuthzDenyAll,
} from 'tests/authz-test-utils';
import {
APIKeyListPermission,
buildSADeletePermission,
} from 'hooks/useAuthZ/permissions/service-account.permissions';
import ServiceAccountDrawer from '../ServiceAccountDrawer';
const ROLES_ENDPOINT = '*/api/v1/roles';
const SA_KEYS_ENDPOINT = '*/api/v1/service_accounts/:id/keys';
const SA_ENDPOINT = '*/api/v1/service_accounts/sa-1';
const SA_DELETE_ENDPOINT = '*/api/v1/service_accounts/sa-1';
const SA_ROLES_ENDPOINT = '*/api/v1/service_accounts/:id/roles';
const SA_ROLE_DELETE_ENDPOINT = '*/api/v1/service_accounts/:id/roles/:rid';
const activeAccountResponse = {
id: 'sa-1',
name: 'CI Bot',
email: 'ci-bot@signoz.io',
roles: ['signoz-admin'],
status: 'ACTIVE',
createdAt: '2026-01-01T00:00:00Z',
updatedAt: '2026-01-02T00:00:00Z',
};
jest.mock('@signozhq/ui/drawer', () => ({
...jest.requireActual('@signozhq/ui/drawer'),
DrawerWrapper: ({
children,
footer,
open,
}: {
children?: ReactNode;
footer?: ReactNode;
open: boolean;
}): JSX.Element | null =>
open ? (
<div>
{children}
{footer}
</div>
) : null,
}));
jest.mock('@signozhq/ui/sonner', () => ({
...jest.requireActual('@signozhq/ui/sonner'),
toast: { success: jest.fn(), error: jest.fn() },
}));
function renderDrawer(
searchParams: Record<string, string> = { account: 'sa-1' },
): ReturnType<typeof render> {
return render(
<NuqsTestingAdapter searchParams={searchParams} hasMemory>
<ServiceAccountDrawer onSuccess={jest.fn()} />
</NuqsTestingAdapter>,
);
}
function setupBaseHandlers(): void {
server.use(
rest.get(ROLES_ENDPOINT, (_, res, ctx) =>
res(ctx.status(200), ctx.json(listRolesSuccessResponse)),
),
rest.get(SA_KEYS_ENDPOINT, (_, res, ctx) =>
res(ctx.status(200), ctx.json({ data: [] })),
),
rest.get(SA_ENDPOINT, (_, res, ctx) =>
res(ctx.status(200), ctx.json({ data: activeAccountResponse })),
),
rest.put(SA_ENDPOINT, (_, res, ctx) =>
res(ctx.status(200), ctx.json({ status: 'success', data: {} })),
),
rest.delete(SA_DELETE_ENDPOINT, (_, res, ctx) =>
res(ctx.status(200), ctx.json({ status: 'success', data: {} })),
),
rest.get(SA_ROLES_ENDPOINT, (_, res, ctx) =>
res(
ctx.status(200),
ctx.json({
data: listRolesSuccessResponse.data.filter(
(r) => r.name === 'signoz-admin',
),
}),
),
),
rest.post(SA_ROLES_ENDPOINT, (_, res, ctx) =>
res(ctx.status(200), ctx.json({ status: 'success', data: {} })),
),
rest.delete(SA_ROLE_DELETE_ENDPOINT, (_, res, ctx) =>
res(ctx.status(200), ctx.json({ status: 'success', data: {} })),
),
);
}
describe('ServiceAccountDrawer — permissions', () => {
beforeEach(() => {
jest.clearAllMocks();
setupBaseHandlers();
});
afterEach(() => {
server.resetHandlers();
});
it('shows PermissionDeniedCallout inside drawer when read permission is denied', async () => {
server.use(setupAuthzDenyAll());
renderDrawer();
await waitFor(() => {
expect(screen.getByText(/serviceaccount:read/)).toBeInTheDocument();
});
});
it('shows drawer content when read permission is granted', async () => {
server.use(setupAuthzAdmin());
renderDrawer();
await screen.findByDisplayValue('CI Bot');
expect(screen.queryByText(/serviceaccount:read/)).not.toBeInTheDocument();
});
it('shows PermissionDeniedCallout in Keys tab when list-keys permission is denied', async () => {
server.use(setupAuthzDeny(APIKeyListPermission));
renderDrawer();
await screen.findByDisplayValue('CI Bot');
fireEvent.click(screen.getByRole('radio', { name: /keys/i }));
await waitFor(() => {
expect(screen.getByText(/factor-api-key:list/)).toBeInTheDocument();
});
});
it('disables Delete button when delete permission is denied', async () => {
server.use(setupAuthzDeny(buildSADeletePermission('sa-1')));
renderDrawer();
await screen.findByDisplayValue('CI Bot');
const deleteBtn = screen.getByRole('button', {
name: /Delete Service Account/i,
});
await waitFor(() => expect(deleteBtn).toBeDisabled());
});
});

View File

@@ -3,7 +3,6 @@ import { listRolesSuccessResponse } from 'mocks-server/__mockdata__/roles';
import { rest, server } from 'mocks-server/server';
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
import { setupAuthzAdmin } from 'tests/authz-test-utils';
import ServiceAccountDrawer from '../ServiceAccountDrawer';
@@ -99,7 +98,6 @@ describe('ServiceAccountDrawer', () => {
rest.delete(SA_ROLE_DELETE_ENDPOINT, (_, res, ctx) =>
res(ctx.status(200), ctx.json({ status: 'success', data: {} })),
),
setupAuthzAdmin(),
);
});
@@ -302,6 +300,13 @@ describe('ServiceAccountDrawer', () => {
await screen.findByText(/No keys/i);
});
it('shows skeleton while loading account data', () => {
renderDrawer();
// Skeleton renders while the fetch is in-flight
expect(document.querySelector('.ant-skeleton')).toBeInTheDocument();
});
it('shows error state when account fetch fails', async () => {
server.use(
rest.get(SA_ENDPOINT, (_, res, ctx) =>
@@ -354,7 +359,6 @@ describe('ServiceAccountDrawer save-error UX', () => {
rest.delete(SA_ROLE_DELETE_ENDPOINT, (_, res, ctx) =>
res(ctx.status(200), ctx.json({ status: 'success', data: {} })),
),
setupAuthzAdmin(),
);
});

View File

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

View File

@@ -1,10 +1,9 @@
import React, { Dispatch, SetStateAction, useState } from 'react';
import { Check, Plus, X } from '@signozhq/icons';
import { Flex, Tag } from 'antd';
import { Button, 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>('');
@@ -72,19 +71,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>
@@ -92,14 +91,15 @@ 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>

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