Compare commits

..

10 Commits

Author SHA1 Message Date
SagarRajput-7
a9dcad863b Merge branch 'main' into base-path-config-setup-1 2026-04-21 11:04:19 +05:30
SagarRajput-7
a9e09ee349 feat: code refactor around feedbacks 2026-04-21 10:25:02 +05:30
SagarRajput-7
0a9bb6ba0b feat: applied suggested patch changes 2026-04-21 10:24:51 +05:30
SagarRajput-7
6664a0fae3 feat: code refactor around feedbacks 2026-04-21 10:24:38 +05:30
SagarRajput-7
040dcb9c9b feat: updated base path utils and fixed navigation and translations 2026-04-21 10:24:23 +05:30
SagarRajput-7
4a39453826 feat: updated the html template 2026-04-21 10:24:12 +05:30
SagarRajput-7
ac4db09ec6 feat: removed plugin and serving the index.html only as the template 2026-04-21 10:24:01 +05:30
SagarRajput-7
a691e6a775 feat: refactor the interceptor and added gotmpl into gitignore 2026-04-21 10:23:51 +05:30
SagarRajput-7
d270a3807b feat: changed output path to dir level 2026-04-21 10:23:42 +05:30
SagarRajput-7
c2b553d26c feat: base path config setup and plugin for gotmpl generation at build time 2026-04-21 10:23:33 +05:30
235 changed files with 2516 additions and 2035 deletions

View File

@@ -66,6 +66,8 @@ module.exports = {
rules: {
// Asset migration — base-path safety
'rulesdir/no-unsupported-asset-pattern': 'error',
// Base-path safety — window.open and origin-concat patterns; upgrade to error coming PR
'rulesdir/no-raw-absolute-path': 'warn',
// Code quality rules
'prefer-const': 'error', // Enforces const for variables never reassigned

View File

@@ -0,0 +1,153 @@
'use strict';
/**
* ESLint rule: no-raw-absolute-path
*
* Catches patterns that break at runtime when the app is served from a
* sub-path (e.g. /signoz/):
*
* 1. window.open(path, '_blank')
* → use openInNewTab(path) which calls withBasePath internally
*
* 2. window.location.origin + path / `${window.location.origin}${path}`
* → use getAbsoluteUrl(path)
*
* 3. frontendBaseUrl: window.location.origin (bare origin usage)
* → use getBaseUrl() to include the base path
*
* 4. window.location.href = path
* → use withBasePath(path) or navigate() for internal navigation
*
* External URLs (first arg starts with "http") are explicitly allowed.
*/
function isOriginAccess(node) {
return (
node.type === 'MemberExpression' &&
!node.computed &&
node.property.name === 'origin' &&
node.object.type === 'MemberExpression' &&
!node.object.computed &&
node.object.property.name === 'location' &&
node.object.object.type === 'Identifier' &&
node.object.object.name === 'window'
);
}
function isHrefAccess(node) {
return (
node.type === 'MemberExpression' &&
!node.computed &&
node.property.name === 'href' &&
node.object.type === 'MemberExpression' &&
!node.object.computed &&
node.object.property.name === 'location' &&
node.object.object.type === 'Identifier' &&
node.object.object.name === 'window'
);
}
function isExternalUrl(node) {
if (node.type === 'Literal' && typeof node.value === 'string') {
return node.value.startsWith('http://') || node.value.startsWith('https://');
}
if (node.type === 'TemplateLiteral' && node.quasis.length > 0) {
const raw = node.quasis[0].value.raw;
return raw.startsWith('http://') || raw.startsWith('https://');
}
return false;
}
// window.open(withBasePath(x)) and window.open(getAbsoluteUrl(x)) are already safe.
function isSafeHelperCall(node) {
return (
node.type === 'CallExpression' &&
node.callee.type === 'Identifier' &&
(node.callee.name === 'withBasePath' || node.callee.name === 'getAbsoluteUrl')
);
}
module.exports = {
meta: {
type: 'suggestion',
docs: {
description:
'Disallow raw window.open and origin-concatenation patterns that miss the runtime base path',
category: 'Base Path Safety',
},
schema: [],
messages: {
windowOpen:
'Use openInNewTab(path) instead of window.open(path, "_blank") — openInNewTab prepends the base path automatically.',
originConcat:
'Use getAbsoluteUrl(path) instead of window.location.origin + path — getAbsoluteUrl prepends the base path automatically.',
originDirect:
'Use getBaseUrl() instead of window.location.origin — getBaseUrl includes the base path.',
hrefAssign:
'Use withBasePath(path) or navigate() instead of window.location.href = path — ensures the base path is included.',
},
},
create(context) {
return {
// window.open(path, ...) — allow only external first-arg URLs
CallExpression(node) {
const { callee, arguments: args } = node;
if (
callee.type !== 'MemberExpression' ||
callee.object.type !== 'Identifier' ||
callee.object.name !== 'window' ||
callee.property.name !== 'open'
)
return;
if (args.length < 1) return;
if (isExternalUrl(args[0])) return;
if (isSafeHelperCall(args[0])) return;
context.report({ node, messageId: 'windowOpen' });
},
// window.location.origin + path
BinaryExpression(node) {
if (node.operator !== '+') return;
if (isOriginAccess(node.left) || isOriginAccess(node.right)) {
context.report({ node, messageId: 'originConcat' });
}
},
// `${window.location.origin}${path}`
TemplateLiteral(node) {
if (node.expressions.some(isOriginAccess)) {
context.report({ node, messageId: 'originConcat' });
}
},
// window.location.origin used directly (not in concatenation)
// Catches: frontendBaseUrl: window.location.origin
MemberExpression(node) {
if (!isOriginAccess(node)) return;
const parent = node.parent;
// Skip if parent is BinaryExpression with + (handled by BinaryExpression visitor)
if (parent.type === 'BinaryExpression' && parent.operator === '+') return;
// Skip if inside TemplateLiteral (handled by TemplateLiteral visitor)
if (parent.type === 'TemplateLiteral') return;
context.report({ node, messageId: 'originDirect' });
},
// window.location.href = path
AssignmentExpression(node) {
if (node.operator !== '=') return;
if (!isHrefAccess(node.left)) return;
// Allow external URLs
if (isExternalUrl(node.right)) return;
// Allow safe helper calls
if (isSafeHelperCall(node.right)) return;
context.report({ node, messageId: 'hrefAssign' });
},
};
},
};

View File

@@ -2,6 +2,7 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<base href="[[.BaseHref]]" />
<meta
http-equiv="Cache-Control"
content="no-cache, no-store, must-revalidate, max-age: 0"
@@ -59,7 +60,7 @@
<meta data-react-helmet="true" name="docusaurus_locale" content="en" />
<meta data-react-helmet="true" name="docusaurus_tag" content="default" />
<meta name="robots" content="noindex" />
<link data-react-helmet="true" rel="shortcut icon" href="/favicon.ico" />
<link data-react-helmet="true" rel="shortcut icon" href="favicon.ico" />
</head>
<body data-theme="default">
<script>
@@ -136,7 +137,7 @@
})(document, 'script');
}
</script>
<link rel="stylesheet" href="/css/uPlot.min.css" />
<link rel="stylesheet" href="css/uPlot.min.css" />
<script type="module" src="./src/index.tsx"></script>
</body>
</html>

View File

@@ -45,8 +45,7 @@ const config: Config.InitialOptions = {
'^.+\\.(js|jsx)$': 'babel-jest',
},
transformIgnorePatterns: [
// @chenglou/pretext is ESM-only; @signozhq/ui pulls it in via text-ellipsis.
'node_modules/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@chenglou/pretext|@signozhq/design-tokens|@signozhq/table|@signozhq/calendar|@signozhq/input|@signozhq/popover|@signozhq/*|date-fns|d3-interpolate|d3-color|api|@codemirror|@lezer|@marijn|@grafana|nuqs)/)',
'node_modules/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@signozhq/design-tokens|@signozhq/table|@signozhq/calendar|@signozhq/input|@signozhq/popover|@signozhq/button|@signozhq/*|date-fns|d3-interpolate|d3-color|api|@codemirror|@lezer|@marijn|@grafana|nuqs)/)',
],
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
testPathIgnorePatterns: ['/node_modules/', '/public/'],

View File

@@ -24,10 +24,6 @@ window.matchMedia =
};
};
if (!HTMLElement.prototype.scrollIntoView) {
HTMLElement.prototype.scrollIntoView = function (): void {};
}
// Patch getComputedStyle to handle CSS parsing errors from @signozhq/* packages.
// These packages inject CSS at import time via style-inject / vite-plugin-css-injected-by-js.
// jsdom's nwsapi cannot parse some of the injected selectors (e.g. Tailwind's :animate-in),

View File

@@ -48,10 +48,24 @@
"@radix-ui/react-tooltip": "1.0.7",
"@sentry/react": "8.41.0",
"@sentry/vite-plugin": "2.22.6",
"@signozhq/button": "0.0.5",
"@signozhq/calendar": "0.1.1",
"@signozhq/callout": "0.0.4",
"@signozhq/checkbox": "0.0.4",
"@signozhq/combobox": "0.0.4",
"@signozhq/command": "0.0.2",
"@signozhq/design-tokens": "2.1.4",
"@signozhq/dialog": "0.0.4",
"@signozhq/drawer": "0.0.6",
"@signozhq/icons": "0.1.0",
"@signozhq/input": "0.0.4",
"@signozhq/popover": "0.1.2",
"@signozhq/radio-group": "0.0.4",
"@signozhq/resizable": "0.0.2",
"@signozhq/ui": "0.0.9",
"@signozhq/tabs": "0.0.11",
"@signozhq/table": "0.3.8",
"@signozhq/toggle-group": "0.0.3",
"@signozhq/ui": "0.0.5",
"@tanstack/react-table": "8.21.3",
"@tanstack/react-virtual": "3.13.22",
"@uiw/codemirror-theme-copilot": "4.23.11",

View File

@@ -2,6 +2,7 @@ import { initReactI18next } from 'react-i18next';
import i18n from 'i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-http-backend';
import { getBasePath } from 'utils/basePath';
import cacheBursting from '../../i18n-translations-hash.json';
@@ -24,7 +25,7 @@ i18n
const ns = namespace[0];
const pathkey = `/${language}/${ns}`;
const hash = cacheBursting[pathkey as keyof typeof cacheBursting] || '';
return `/locales/${language}/${namespace}.json?h=${hash}`;
return `${getBasePath()}locales/${language}/${namespace}.json?h=${hash}`;
},
},
react: {

View File

@@ -1,5 +1,6 @@
import {
interceptorRejected,
interceptorsRequestBasePath,
interceptorsRequestResponse,
interceptorsResponse,
} from 'api';
@@ -17,6 +18,7 @@ export const GeneratedAPIInstance = <T>(
return generatedAPIAxiosInstance({ ...config }).then(({ data }) => data);
};
generatedAPIAxiosInstance.interceptors.request.use(interceptorsRequestBasePath);
generatedAPIAxiosInstance.interceptors.request.use(interceptorsRequestResponse);
generatedAPIAxiosInstance.interceptors.response.use(
interceptorsResponse,

View File

@@ -11,6 +11,7 @@ import axios, {
import { ENVIRONMENT } from 'constants/env';
import { Events } from 'constants/events';
import { LOCALSTORAGE } from 'constants/localStorage';
import { getBasePath } from 'utils/basePath';
import { eventEmitter } from 'utils/getEventEmitter';
import apiV1, { apiAlertManager, apiV2, apiV3, apiV4, apiV5 } from './apiV1';
@@ -67,6 +68,39 @@ export const interceptorsRequestResponse = (
return value;
};
// Strips the leading '/' from path and joins with base — idempotent if already prefixed.
// e.g. prependBase('/signoz/', '/api/v1/') → '/signoz/api/v1/'
function prependBase(base: string, path: string): string {
return path.startsWith(base) ? path : base + path.slice(1);
}
// Prepends the runtime base path to outgoing requests so API calls work under
// a URL prefix (e.g. /signoz/api/v1/…). No-op for root deployments and dev
// (dev baseURL is a full http:// URL, not an absolute path).
export const interceptorsRequestBasePath = (
value: InternalAxiosRequestConfig,
): InternalAxiosRequestConfig => {
const basePath = getBasePath();
if (basePath === '/') {
return value;
}
if (value.baseURL?.startsWith('/')) {
// Production relative baseURL: '/api/v1/' → '/signoz/api/v1/'
value.baseURL = prependBase(basePath, value.baseURL);
} else if (value.baseURL?.startsWith('http')) {
// Dev absolute baseURL (VITE_FRONTEND_API_ENDPOINT): 'https://host/api/v1/' → 'https://host/signoz/api/v1/'
const url = new URL(value.baseURL);
url.pathname = prependBase(basePath, url.pathname);
value.baseURL = url.toString();
} else if (!value.baseURL && value.url?.startsWith('/')) {
// Orval-generated client (empty baseURL, path in url): '/api/signoz/v1/rules' → '/signoz/api/signoz/v1/rules'
value.url = prependBase(basePath, value.url);
}
return value;
};
export const interceptorRejected = async (
value: AxiosResponse<any>,
): Promise<AxiosResponse<any>> => {
@@ -133,6 +167,7 @@ const instance = axios.create({
});
instance.interceptors.request.use(interceptorsRequestResponse);
instance.interceptors.request.use(interceptorsRequestBasePath);
instance.interceptors.response.use(interceptorsResponse, interceptorRejected);
export const AxiosAlertManagerInstance = axios.create({
@@ -147,6 +182,7 @@ ApiV2Instance.interceptors.response.use(
interceptorRejected,
);
ApiV2Instance.interceptors.request.use(interceptorsRequestResponse);
ApiV2Instance.interceptors.request.use(interceptorsRequestBasePath);
// axios V3
export const ApiV3Instance = axios.create({
@@ -158,6 +194,7 @@ ApiV3Instance.interceptors.response.use(
interceptorRejected,
);
ApiV3Instance.interceptors.request.use(interceptorsRequestResponse);
ApiV3Instance.interceptors.request.use(interceptorsRequestBasePath);
//
// axios V4
@@ -170,6 +207,7 @@ ApiV4Instance.interceptors.response.use(
interceptorRejected,
);
ApiV4Instance.interceptors.request.use(interceptorsRequestResponse);
ApiV4Instance.interceptors.request.use(interceptorsRequestBasePath);
//
// axios V5
@@ -182,6 +220,7 @@ ApiV5Instance.interceptors.response.use(
interceptorRejected,
);
ApiV5Instance.interceptors.request.use(interceptorsRequestResponse);
ApiV5Instance.interceptors.request.use(interceptorsRequestBasePath);
//
// axios Base
@@ -194,6 +233,7 @@ LogEventAxiosInstance.interceptors.response.use(
interceptorRejectedBase,
);
LogEventAxiosInstance.interceptors.request.use(interceptorsRequestResponse);
LogEventAxiosInstance.interceptors.request.use(interceptorsRequestBasePath);
//
AxiosAlertManagerInstance.interceptors.response.use(
@@ -201,6 +241,7 @@ AxiosAlertManagerInstance.interceptors.response.use(
interceptorRejected,
);
AxiosAlertManagerInstance.interceptors.request.use(interceptorsRequestResponse);
AxiosAlertManagerInstance.interceptors.request.use(interceptorsRequestBasePath);
export { apiV1 };
export default instance;

View File

@@ -10,7 +10,21 @@
// PR for reference: https://github.com/SigNoz/signoz/pull/9694
// -------------------------------------------------------------------------
import '@signozhq/button';
import '@signozhq/calendar';
import '@signozhq/callout';
import '@signozhq/checkbox';
import '@signozhq/combobox';
import '@signozhq/command';
import '@signozhq/design-tokens';
import '@signozhq/dialog';
import '@signozhq/drawer';
import '@signozhq/icons';
import '@signozhq/input';
import '@signozhq/popover';
import '@signozhq/radio-group';
import '@signozhq/resizable';
import '@signozhq/tabs';
import '@signozhq/table';
import '@signozhq/toggle-group';
import '@signozhq/ui';

View File

@@ -80,12 +80,12 @@
mask-image: radial-gradient(
circle at 50% 0,
color-mix(in srgb, var(--l1-background) 10%, transparent) 0,
color-mix(in srgb, var(--background) 10%, transparent) 0,
transparent 100%
);
-webkit-mask-image: radial-gradient(
circle at 50% 0,
color-mix(in srgb, var(--l1-background) 10%, transparent) 0,
color-mix(in srgb, var(--background) 10%, transparent) 0,
transparent 100%
);
}

View File

@@ -4,14 +4,13 @@
animation: horizontal-shaking 300ms ease-out;
.error-content {
background: color-mix(in srgb, var(--danger-background) 10%, transparent);
border: 1px solid
color-mix(in srgb, var(--danger-background) 20%, transparent);
background: color-mix(in srgb, var(--bg-cherry-500) 10%, transparent);
border: 1px solid color-mix(in srgb, var(--bg-cherry-500) 20%, transparent);
border-radius: 4px;
&__summary-section {
border-bottom: 1px solid
color-mix(in srgb, var(--danger-background) 20%, transparent);
color-mix(in srgb, var(--bg-cherry-500) 20%, transparent);
}
&__summary {
@@ -59,7 +58,7 @@
&__message-badge-line {
background-image: radial-gradient(
circle,
color-mix(in srgb, var(--danger-background) 30%, transparent) 1px,
color-mix(in srgb, var(--bg-cherry-500) 30%, transparent) 1px,
transparent 2px
);
}
@@ -85,7 +84,7 @@
}
&__scroll-hint {
background: color-mix(in srgb, var(--danger-background) 20%, transparent);
background: color-mix(in srgb, var(--bg-cherry-500) 20%, transparent);
}
&__scroll-hint-text {

View File

@@ -1,5 +1,5 @@
import { useCallback } from 'react';
import { Button } from '@signozhq/ui';
import { Button } from '@signozhq/button';
import { LifeBuoy } from 'lucide-react';
import signozBrandLogoUrl from '@/assets/Logos/signoz-brand-logo.svg';
@@ -23,10 +23,8 @@ function AuthHeader(): JSX.Element {
</div>
<Button
className="auth-header-help-button"
prefix={<LifeBuoy size={12} />}
prefixIcon={<LifeBuoy size={12} />}
onClick={handleGetHelp}
variant="solid"
color="none"
>
Get Help
</Button>

View File

@@ -43,12 +43,12 @@
.masked-dots {
mask-image: radial-gradient(
circle at 50% 0%,
color-mix(in srgb, var(--l1-background) 10%, transparent) 0%,
color-mix(in srgb, var(--background) 10%, transparent) 0%,
transparent 56.77%
);
-webkit-mask-image: radial-gradient(
circle at 50% 0%,
color-mix(in srgb, var(--l1-background) 10%, transparent) 0%,
color-mix(in srgb, var(--background) 10%, transparent) 0%,
transparent 56.77%
);
}

View File

@@ -12,6 +12,7 @@ import { AppState } from 'store/reducers';
import { Query, TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource, MetricAggregateOperator } from 'types/common/queryBuilder';
import { GlobalReducer } from 'types/reducer/globalTime';
import { withBasePath } from 'utils/basePath';
export interface NavigateToExplorerProps {
filters: TagFilterItem[];
@@ -133,7 +134,7 @@ export function useNavigateToExplorer(): (
QueryParams.compositeQuery
}=${JSONCompositeQuery}`;
window.open(newExplorerPath, sameTab ? '_self' : '_blank');
window.open(withBasePath(newExplorerPath), sameTab ? '_self' : '_blank');
},
[
prepareQuery,

View File

@@ -26,14 +26,14 @@
}
}
&--negative {
background: color-mix(in srgb, var(--danger-background) 10%, transparent);
background: color-mix(in srgb, var(--bg-cherry-500) 10%, transparent);
.change-percentage-pill {
&__icon {
color: var(--danger-background);
color: var(--bg-cherry-500);
}
&__label {
color: var(--danger-background);
color: var(--bg-cherry-500);
}
}
}

View File

@@ -1,13 +1,10 @@
import { Controller, useForm } from 'react-hook-form';
import { useQueryClient } from 'react-query';
import { Button } from '@signozhq/button';
import { DialogFooter, DialogWrapper } from '@signozhq/dialog';
import { X } from '@signozhq/icons';
import {
Button,
DialogFooter,
DialogWrapper,
Input,
toast,
} from '@signozhq/ui';
import { Input } from '@signozhq/input';
import { toast } from '@signozhq/ui';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import {
invalidateListServiceAccounts,
@@ -53,7 +50,9 @@ function CreateServiceAccountModal(): JSX.Element {
} = useCreateServiceAccount({
mutation: {
onSuccess: async () => {
toast.success('Service account created successfully');
toast.success('Service account created successfully', {
richColors: true,
});
reset();
await setIsOpen(null);
await invalidateListServiceAccounts(queryClient);
@@ -129,6 +128,7 @@ function CreateServiceAccountModal(): JSX.Element {
type="button"
variant="solid"
color="secondary"
size="sm"
onClick={handleClose}
>
<X size={12} />
@@ -137,10 +137,10 @@ function CreateServiceAccountModal(): JSX.Element {
<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"
size="sm"
loading={isSubmitting}
disabled={!isValid}
>

View File

@@ -1,13 +1,7 @@
import { toast } from '@signozhq/ui';
import { rest, server } from 'mocks-server/server';
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
import {
render,
screen,
userEvent,
waitFor,
waitForElementToBeRemoved,
} from 'tests/test-utils';
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
import CreateServiceAccountModal from '../CreateServiceAccountModal';
@@ -75,6 +69,7 @@ describe('CreateServiceAccountModal', () => {
await waitFor(() => {
expect(mockToast.success).toHaveBeenCalledWith(
'Service account created successfully',
expect.anything(),
);
});
@@ -126,12 +121,12 @@ describe('CreateServiceAccountModal', () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
renderModal();
const dialog = await screen.findByRole('dialog', {
name: /New Service Account/i,
});
await screen.findByRole('dialog', { name: /New Service Account/i });
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 () => {

View File

@@ -1,4 +1,4 @@
import { Calendar } from '@signozhq/ui';
import { Calendar } from '@signozhq/calendar';
import { Button } from 'antd';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import dayjs from 'dayjs';

View File

@@ -7,7 +7,7 @@ import {
useState,
} from 'react';
import { useLocation } from 'react-router-dom';
import { Button } from '@signozhq/ui';
import { Button } from '@signozhq/button';
import { Input, InputRef, Popover, Tooltip } from 'antd';
import cx from 'classnames';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
@@ -661,9 +661,7 @@ function CustomTimePicker({
onClick={handleZoomOut}
disabled={zoomOutDisabled}
data-testid="zoom-out-btn"
prefix={<ZoomOut size={14} />}
variant="solid"
color="none"
prefixIcon={<ZoomOut size={14} />}
/>
</Tooltip>
)}

View File

@@ -1,5 +1,6 @@
import { Button } from '@signozhq/button';
import { DialogFooter, DialogWrapper } from '@signozhq/dialog';
import { Trash2, X } from '@signozhq/icons';
import { Button, DialogWrapper } from '@signozhq/ui';
import { MemberRow } from 'components/MembersTable/MembersTable';
interface DeleteMemberDialogProps {
@@ -35,24 +36,6 @@ function DeleteMemberDialog({
</>
);
const footer = (
<>
<Button variant="solid" color="secondary" onClick={onClose}>
<X size={12} />
Cancel
</Button>
<Button
variant="solid"
color="destructive"
disabled={isDeleting}
onClick={onConfirm}
>
<Trash2 size={12} />
{isDeleting ? 'Processing...' : title}
</Button>
</>
);
return (
<DialogWrapper
open={open}
@@ -66,9 +49,25 @@ function DeleteMemberDialog({
className="alert-dialog delete-dialog"
showCloseButton={false}
disableOutsideClick={false}
footer={footer}
>
{body}
<p className="delete-dialog__body">{body}</p>
<DialogFooter className="delete-dialog__footer">
<Button variant="solid" color="secondary" size="sm" onClick={onClose}>
<X size={12} />
Cancel
</Button>
<Button
variant="solid"
color="destructive"
size="sm"
disabled={isDeleting}
onClick={onConfirm}
>
<Trash2 size={12} />
{isDeleting ? 'Processing...' : title}
</Button>
</DialogFooter>
</DialogWrapper>
);
}

View File

@@ -2,7 +2,7 @@
&__layout {
display: flex;
flex-direction: column;
height: 100%;
height: calc(100vh - 48px);
}
&__body {
@@ -11,6 +11,7 @@
display: flex;
flex-direction: column;
gap: var(--spacing-8);
padding: var(--padding-5) var(--padding-4);
}
&__field {
@@ -49,7 +50,6 @@
border-radius: 2px;
background: var(--l2-background);
border: 1px solid var(--l1-border);
box-sizing: border-box;
&--disabled {
cursor: not-allowed;
@@ -120,11 +120,17 @@
align-items: center;
justify-content: space-between;
width: 100%;
height: 56px;
padding: 0 var(--padding-4);
border-top: 1px solid var(--l1-border);
flex-shrink: 0;
background: var(--card);
}
&__footer-left {
display: flex;
align-items: center;
gap: var(--spacing-8);
}
&__footer-right {
@@ -217,6 +223,10 @@
color: var(--l1-foreground);
}
[data-slot='dialog-description'] {
width: 510px;
}
&__content {
display: flex;
flex-direction: column;

View File

@@ -1,7 +1,10 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { useCopyToClipboard } from 'react-use';
import { Button } from '@signozhq/button';
import { DrawerWrapper } from '@signozhq/drawer';
import { LockKeyhole, RefreshCw, Trash2, X } from '@signozhq/icons';
import { Badge, Button, DrawerWrapper, Input, toast } from '@signozhq/ui';
import { Input } from '@signozhq/input';
import { Badge, toast } from '@signozhq/ui';
import { Skeleton, Tooltip } from 'antd';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import type { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
@@ -204,7 +207,7 @@ function EditMemberDrawer({
onSuccess: (): void => {
toast.success(
isInvited ? 'Invite revoked successfully' : 'Member deleted successfully',
{ position: 'top-right' },
{ richColors: true, position: 'top-right' },
);
setShowDeleteConfirm(false);
onComplete();
@@ -339,7 +342,10 @@ function EditMemberDrawer({
if (errors.length > 0) {
setSaveErrors(errors);
} else {
toast.success('Member details updated successfully');
toast.success('Member details updated successfully', {
richColors: true,
position: 'top-right',
});
onComplete();
}
@@ -397,6 +403,7 @@ function EditMemberDrawer({
onClose();
} else {
toast.error('Failed to generate password reset link', {
richColors: true,
position: 'top-right',
});
}
@@ -420,12 +427,15 @@ function EditMemberDrawer({
linkType === 'invite'
? 'Invite link copied to clipboard'
: 'Reset link copied to clipboard';
toast.success(message);
toast.success(message, { richColors: true, position: 'top-right' });
}, [resetLink, copyToClipboard, linkType]);
useEffect(() => {
if (copyState.error) {
toast.error('Failed to copy link');
toast.error('Failed to copy link', {
richColors: true,
position: 'top-right',
});
}
}, [copyState.error]);
@@ -586,21 +596,16 @@ function EditMemberDrawer({
const drawerContent = (
<div className="edit-member-drawer__layout">
<div className="edit-member-drawer__body">{drawerBody}</div>
</div>
);
const footer = (
<div className="edit-member-drawer__footer">
{!isDeleted && (
<>
<div className="edit-member-drawer__footer">
<div className="edit-member-drawer__footer-left">
<Tooltip title={getDeleteTooltip(isRootUser, isSelf)}>
<span className="edit-member-drawer__tooltip-wrapper">
<Button
className="edit-member-drawer__footer-btn edit-member-drawer__footer-btn--danger"
onClick={(): void => setShowDeleteConfirm(true)}
disabled={isRootUser || isSelf}
variant="link"
color="destructive"
>
<Trash2 size={12} />
{isInvited ? 'Revoke Invite' : 'Delete Member'}
@@ -612,10 +617,9 @@ function EditMemberDrawer({
<Tooltip title={isRootUser ? ROOT_USER_TOOLTIP : undefined}>
<span className="edit-member-drawer__tooltip-wrapper">
<Button
className="edit-member-drawer__footer-btn edit-member-drawer__footer-btn--warning"
onClick={handleGenerateResetLink}
disabled={isGeneratingLink || isRootUser || isLoadingTokenStatus}
variant="link"
color="warning"
>
<RefreshCw size={12} />
{isGeneratingLink
@@ -634,7 +638,7 @@ function EditMemberDrawer({
</div>
<div className="edit-member-drawer__footer-right">
<Button variant="solid" color="secondary" onClick={handleClose}>
<Button variant="solid" color="secondary" size="sm" onClick={handleClose}>
<X size={14} />
Cancel
</Button>
@@ -642,13 +646,14 @@ function EditMemberDrawer({
<Button
variant="solid"
color="primary"
size="sm"
disabled={!isDirty || isSaving || isRootUser}
onClick={handleSave}
>
{isSaving ? 'Saving...' : 'Save Member Details'}
</Button>
</div>
</>
</div>
)}
</div>
);
@@ -663,14 +668,14 @@ function EditMemberDrawer({
}
}}
direction="right"
type="panel"
showCloseButton
showOverlay={false}
title="Member Details"
footer={footer}
width="wide"
>
{drawerContent}
</DrawerWrapper>
allowOutsideClick
header={{ title: 'Member Details' }}
content={drawerContent}
className="edit-member-drawer"
/>
<ResetLinkDialog
open={showResetLinkDialog}

View File

@@ -1,5 +1,6 @@
import { Button } from '@signozhq/button';
import { DialogWrapper } from '@signozhq/dialog';
import { Check, Copy } from '@signozhq/icons';
import { Button, DialogWrapper } from '@signozhq/ui';
interface ResetLinkDialogProps {
open: boolean;
@@ -48,7 +49,7 @@ function ResetLinkDialog({
color="secondary"
size="sm"
onClick={onCopy}
prefix={hasCopied ? <Check size={12} /> : <Copy size={12} />}
prefixIcon={hasCopied ? <Check size={12} /> : <Copy size={12} />}
className="reset-link-dialog__copy-btn"
>
{hasCopied ? 'Copied!' : 'Copy'}

View File

@@ -20,6 +20,36 @@ import { render, screen, userEvent, waitFor } from 'tests/test-utils';
import EditMemberDrawer, { EditMemberDrawerProps } from '../EditMemberDrawer';
jest.mock('@signozhq/drawer', () => ({
DrawerWrapper: ({
content,
open,
}: {
content?: ReactNode;
open: boolean;
}): JSX.Element | null => (open ? <div>{content}</div> : null),
}));
jest.mock('@signozhq/dialog', () => ({
DialogWrapper: ({
children,
open,
title,
}: {
children?: ReactNode;
open: boolean;
title?: string;
}): JSX.Element | null =>
open ? (
<div role="dialog" aria-label={title}>
{children}
</div>
) : null,
DialogFooter: ({ children }: { children?: ReactNode }): JSX.Element => (
<div>{children}</div>
),
}));
jest.mock('api/generated/services/users', () => ({
useDeleteUser: jest.fn(),
useGetUser: jest.fn(),
@@ -36,41 +66,6 @@ jest.mock('api/ErrorResponseHandlerForGeneratedAPIs', () => ({
jest.mock('@signozhq/ui', () => ({
...jest.requireActual('@signozhq/ui'),
DrawerWrapper: ({
children,
footer,
open,
}: {
children?: ReactNode;
footer?: ReactNode;
open: boolean;
}): JSX.Element | null =>
open ? (
<div>
{children}
{footer}
</div>
) : null,
DialogWrapper: ({
children,
footer,
open,
title,
}: {
children?: ReactNode;
footer?: ReactNode;
open: boolean;
title?: string;
}): JSX.Element | null =>
open ? (
<div role="dialog" aria-label={title}>
{children}
{footer}
</div>
) : null,
DialogFooter: ({ children }: { children?: ReactNode }): JSX.Element => (
<div>{children}</div>
),
toast: {
success: jest.fn(),
error: jest.fn(),
@@ -165,8 +160,6 @@ function renderDrawer(
describe('EditMemberDrawer', () => {
beforeEach(() => {
jest.clearAllMocks();
mockCopyState.value = undefined;
mockCopyState.error = undefined;
showErrorModal.mockClear();
server.use(
rest.get(ROLES_ENDPOINT, (_, res, ctx) =>
@@ -733,16 +726,16 @@ describe('EditMemberDrawer', () => {
await user.click(screen.getByRole('button', { name: /^copy$/i }));
await waitFor(() => {
expect(mockCopyToClipboard).toHaveBeenCalledWith(
expect.stringContaining('reset-tok-abc'),
);
expect(
screen.getByRole('button', { name: /copied!/i }),
).toBeInTheDocument();
expect(mockToast.success).toHaveBeenCalledWith(
'Reset link copied to clipboard',
expect.anything(),
);
});
expect(mockCopyToClipboard).toHaveBeenCalledWith(
expect.stringContaining('reset-tok-abc'),
);
expect(screen.getByRole('button', { name: /copied!/i })).toBeInTheDocument();
});
});
});

View File

@@ -5,12 +5,12 @@
align-items: center;
gap: 4px;
border-radius: 20px;
background: color-mix(in srgb, var(--danger-background) 20%, transparent);
background: color-mix(in srgb, var(--bg-cherry-500) 20%, transparent);
padding-left: 3px;
padding-right: 8px;
cursor: pointer;
span {
color: var(--danger-background);
color: var(--bg-cherry-500);
font-size: 10px;
font-weight: 500;
line-height: 20px; /* 200% */
@@ -21,7 +21,7 @@
&__wrap {
background: linear-gradient(
180deg,
color-mix(in srgb, var(--l1-background) 12%, transparent) 0.07%,
color-mix(in srgb, var(--background) 12%, transparent) 0.07%,
color-mix(in srgb, var(--bg-sakura-950) 24%, transparent) 50.04%,
color-mix(in srgb, var(--bg-sakura-800) 36%, transparent) 75.02%,
color-mix(in srgb, var(--bg-sakura-600) 48%, transparent) 87.51%,
@@ -40,17 +40,15 @@
margin: auto;
}
}
&__body {
padding: 0;
background: var(--l2-background);
overflow: hidden;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
&__header {
background: none !important;
.ant-modal-title {
display: flex;
justify-content: space-between;
@@ -82,7 +80,6 @@
pointer-events: none;
}
}
.close-button {
padding: 3px 7px;
background: var(--l2-background);
@@ -93,15 +90,15 @@
box-shadow: none;
}
}
&__footer {
margin: 0 !important;
height: 6px;
background: var(--bg-sakura-500);
}
&__content {
padding: 0 !important;
border-radius: 4px;
overflow: hidden;
background: none !important;
}
}

View File

@@ -5,6 +5,7 @@
&__summary-section {
display: flex;
flex-direction: column;
border-bottom: 1px solid var(--l1-border);
}
&__summary {

View File

@@ -5,6 +5,7 @@
border-radius: 2px 0px 0px 2px;
.label {
color: var(--l2-foreground);
font-size: 12px;
font-style: normal;
font-weight: 500;
@@ -20,9 +21,8 @@
padding: 0px 8px;
border-radius: 2px 0px 0px 2px;
border: 1px solid var(--l2-border);
background: var(--l2-background);
color: var(--l2-foreground);
border: 1px solid var(--l1-border);
background: var(--l3-background);
display: flex;
justify-content: flex-start;
@@ -37,7 +37,6 @@
border-radius: 2px 0px 0px 2px;
border: 1px solid var(--l1-border);
background: var(--l2-background);
border-right: none;
border-left: none;
@@ -47,7 +46,6 @@
border-bottom-left-radius: 0px;
font-size: 12px !important;
line-height: 27px;
&::placeholder {
color: var(--l2-foreground) !important;
font-size: 12px !important;
@@ -63,8 +61,8 @@
.close-btn {
border-radius: 0px 2px 2px 0px;
border: 1px solid var(--l2-border);
background: var(--l2-background);
border: 1px solid var(--l1-border);
background: var(--l3-background);
height: 38px;
width: 38px;
}
@@ -73,7 +71,7 @@
.input {
border-radius: 0px 2px 2px 0px;
border: 1px solid var(--l1-border);
background: var(--l2-background);
background: var(--l3-background);
border-top-right-radius: 0px;
border-bottom-right-radius: 0px;
}

View File

@@ -181,7 +181,7 @@
box-shadow: none;
&:hover {
background: color-mix(in srgb, var(--danger-background) 10%, transparent);
background: color-mix(in srgb, var(--bg-cherry-500) 10%, transparent);
opacity: 0.9;
}
}
@@ -196,16 +196,12 @@
}
.invite-team-members-error-callout {
background: color-mix(in srgb, var(--danger-background) 10%, transparent);
border: 1px solid color-mix(in srgb, var(--danger-background) 20%, transparent);
background: color-mix(in srgb, var(--bg-cherry-500) 10%, transparent);
border: 1px solid color-mix(in srgb, var(--bg-cherry-500) 20%, transparent);
border-radius: 4px;
animation: horizontal-shaking 300ms ease-out;
}
.invite-members-modal__error-callout {
display: flex;
}
@keyframes horizontal-shaking {
0% {
transform: translateX(0);

View File

@@ -1,14 +1,11 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import { Button } from '@signozhq/button';
import { Callout } from '@signozhq/callout';
import { Style } from '@signozhq/design-tokens';
import { DialogFooter, DialogWrapper } from '@signozhq/dialog';
import { ChevronDown, CircleAlert, Plus, Trash2, X } from '@signozhq/icons';
import {
Button,
Callout,
DialogFooter,
DialogWrapper,
Input,
toast,
} from '@signozhq/ui';
import { Input } from '@signozhq/input';
import { toast } from '@signozhq/ui';
import { Select } from 'antd';
import inviteUsers from 'api/v1/invite/bulk/create';
import sendInvite from 'api/v1/invite/create';
@@ -203,7 +200,10 @@ function InviteMembersModal({
})),
});
}
toast.success('Invites sent successfully', { position: 'top-right' });
toast.success('Invites sent successfully', {
richColors: true,
position: 'top-right',
});
resetAndClose();
onComplete?.();
} catch (err) {
@@ -274,6 +274,7 @@ function InviteMembersModal({
<Button
variant="ghost"
color="destructive"
className="remove-team-member-button"
onClick={(): void => removeRow(row.id)}
aria-label="Remove row"
>
@@ -288,16 +289,14 @@ function InviteMembersModal({
</div>
{(hasInvalidEmails || hasInvalidRoles) && (
<div className="invite-members-modal__error-callout">
<Callout
type="error"
size="small"
showIcon
icon={<CircleAlert size={12} />}
>
{getValidationErrorMessage()}
</Callout>
</div>
<Callout
type="error"
size="small"
showIcon
icon={<CircleAlert size={12} />}
className="invite-team-members-error-callout"
description={getValidationErrorMessage()}
/>
)}
</div>
@@ -305,8 +304,9 @@ function InviteMembersModal({
<Button
variant="dashed"
color="secondary"
size="sm"
className="add-another-member-button"
prefix={<Plus size={12} color={Style.L1_FOREGROUND} />}
prefixIcon={<Plus size={12} color={Style.L1_FOREGROUND} />}
onClick={addRow}
>
Add another
@@ -317,6 +317,7 @@ function InviteMembersModal({
type="button"
variant="solid"
color="secondary"
size="sm"
onClick={resetAndClose}
>
<X size={12} />
@@ -326,6 +327,7 @@ function InviteMembersModal({
<Button
variant="solid"
color="primary"
size="sm"
onClick={handleSubmit}
disabled={isSubmitDisabled}
loading={isSubmitting}

View File

@@ -110,7 +110,7 @@
}
&.ERROR {
border-color: var(--danger-background);
border-color: var(--bg-cherry-500);
}
}

View File

@@ -9,7 +9,6 @@ import cx from 'classnames';
import { LogType } from 'components/Logs/LogStateIndicator/LogStateIndicator';
import QuerySearch from 'components/QueryBuilderV2/QueryV2/QuerySearch/QuerySearch';
import { convertExpressionToFilters } from 'components/QueryBuilderV2/utils';
import { FeatureKeys } from 'constants/features';
import { LOCALSTORAGE } from 'constants/localStorage';
import { QueryParams } from 'constants/query';
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
@@ -46,7 +45,6 @@ import {
TextSelect,
X,
} from 'lucide-react';
import { useAppContext } from 'providers/App/App';
import { AppState } from 'store/reducers';
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource, StringOperators } from 'types/common/queryBuilder';
@@ -80,10 +78,6 @@ function LogDetailInner({
const [selectedView, setSelectedView] = useState<VIEWS>(selectedTab);
const [isFilterVisible, setIsFilterVisible] = useState<boolean>(false);
const { featureFlags } = useAppContext();
const isBodyJsonQueryEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.ENABLE_BODY_JSON_QUERY)
?.active || false;
const [filters, setFilters] = useState<TagFilter | null>(null);
const [isEdit, setIsEdit] = useState<boolean>(false);
@@ -213,29 +207,11 @@ function LogDetailInner({
}
};
const logBody = useMemo(() => {
if (!isBodyJsonQueryEnabled) {
return log?.body || '';
}
try {
const json = JSON.parse(log?.body || '');
if (typeof json?.message === 'string' && json.message !== '') {
return json.message;
}
return log?.body || '';
} catch (error) {
return log?.body || '';
}
}, [isBodyJsonQueryEnabled, log?.body]);
const htmlBody = useMemo(
() => ({
__html: getSanitizedLogBody(logBody || '', { shouldEscapeHtml: true }),
__html: getSanitizedLogBody(log?.body || '', { shouldEscapeHtml: true }),
}),
[logBody],
[log?.body],
);
const handleJSONCopy = (): void => {
@@ -435,7 +411,7 @@ function LogDetailInner({
<div className="log-detail-drawer__log">
<Divider type="vertical" className={cx('log-type-indicator', logType)} />
<Tooltip
title={removeEscapeCharacters(logBody)}
title={removeEscapeCharacters(log?.body)}
placement="left"
mouseLeaveDelay={0}
>

View File

@@ -94,7 +94,7 @@
background-color: var(--bg-cherry-600);
}
&.severity-error-1 {
background-color: var(--danger-background);
background-color: var(--bg-cherry-500);
}
&.severity-error-2 {
background-color: var(--bg-cherry-400);

View File

@@ -21,7 +21,7 @@
.ant-table-thead {
> tr > th,
> tr > td {
background: var(--l1-background);
background: var(--background);
font-size: var(--paragraph-small-600-font-size);
font-weight: var(--paragraph-small-600-font-weight);
line-height: var(--paragraph-small-600-line-height);

View File

@@ -1445,22 +1445,11 @@ const CustomMultiSelect: React.FC<CustomMultiSelectProps> = ({
// Custom dropdown render with sections support
const customDropdownRender = useCallback((): React.ReactElement => {
// When ALL is selected and the options contain sections (groups),
// skip prioritization so section headers (e.g. "Related values" /
// "All values") remain visible instead of being collapsed away by
// every option getting hoisted to the top. For flat option lists we
// still prioritize so selected/synthesized values stay rendered.
const hasSections = filteredOptions.some(
(opt) => 'options' in opt && Array.isArray(opt.options),
);
const shouldPrioritize =
selectedValues.length > 0 &&
isEmpty(searchText) &&
!(hasSections && (allOptionShown || isAllSelected));
const processedOptions = shouldPrioritize
? prioritizeOrAddOptionForMultiSelect(filteredOptions, selectedValues)
: filteredOptions;
// Process options based on current search
const processedOptions =
selectedValues.length > 0 && isEmpty(searchText)
? prioritizeOrAddOptionForMultiSelect(filteredOptions, selectedValues)
: filteredOptions;
const { sectionOptions, nonSectionOptions } = splitOptions(processedOptions);
@@ -1758,8 +1747,6 @@ const CustomMultiSelect: React.FC<CustomMultiSelectProps> = ({
}, [
selectedValues,
searchText,
allOptionShown,
isAllSelected,
filteredOptions,
splitOptions,
isLabelPresent,

View File

@@ -6,7 +6,6 @@ import {
screen,
waitFor,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import CustomMultiSelect from '../CustomMultiSelect';
@@ -284,68 +283,4 @@ describe('CustomMultiSelect Component', () => {
// When all options are selected, component shows ALL tag instead
expect(screen.getByText('ALL')).toBeInTheDocument();
});
describe('section visibility when ALL is selected', () => {
it('keeps group headers visible when every grouped value is selected', async () => {
const user = userEvent.setup();
renderWithVirtuoso(
<CustomMultiSelect
options={mockGroupedOptions}
value={['g1-option1', 'g1-option2', 'g2-option1', 'g2-option2']}
/>,
);
await user.click(screen.getByRole('combobox'));
await waitFor(() => {
expect(screen.getByText('Group 1')).toBeInTheDocument();
expect(screen.getByText('Group 2')).toBeInTheDocument();
});
});
it('keeps every grouped option visible within its section when all are selected', async () => {
const user = userEvent.setup();
renderWithVirtuoso(
<CustomMultiSelect
options={mockGroupedOptions}
value={['g1-option1', 'g1-option2', 'g2-option1', 'g2-option2']}
/>,
);
await user.click(screen.getByRole('combobox'));
await waitFor(() => {
const group1Region = screen.getByRole('group', {
name: 'Group 1 options',
});
const group2Region = screen.getByRole('group', {
name: 'Group 2 options',
});
// Each option stays inside its original section rather than being
// hoisted into a flat selected-first list.
expect(group1Region).toHaveTextContent('Group 1 - Option 1');
expect(group1Region).toHaveTextContent('Group 1 - Option 2');
expect(group2Region).toHaveTextContent('Group 2 - Option 1');
expect(group2Region).toHaveTextContent('Group 2 - Option 2');
});
});
it('keeps group headers visible when value is the ALL sentinel', async () => {
const user = userEvent.setup();
renderWithVirtuoso(
<CustomMultiSelect
options={mockGroupedOptions}
value={('__ALL__' as unknown) as string[]}
/>,
);
await user.click(screen.getByRole('combobox'));
await waitFor(() => {
expect(screen.getByText('Group 1')).toBeInTheDocument();
expect(screen.getByText('Group 2')).toBeInTheDocument();
});
});
});
});

View File

@@ -355,7 +355,7 @@ $custom-border-color: #2c3044;
.navigation-error {
.navigation-text,
.navigation-icons {
color: var(--danger-background) !important;
color: var(--bg-cherry-500) !important;
}
display: flex;
align-items: center;

View File

@@ -1,5 +1,3 @@
// TODO: Improve the styling of the query aggregation container and its components. - @YounixM , @H4ad
.query-builder-v2 {
display: flex;
flex-direction: row;
@@ -276,7 +274,7 @@
.ant-input-group-addon {
border-top-left-radius: 0px !important;
border-top-right-radius: 0px !important;
background: var(--l2-background);
background: var(--l3-background);
color: var(--l2-foreground);
font-size: 12px;
font-weight: 300;

View File

@@ -1,5 +1,3 @@
// TODO: Improve the styling of the query aggregation container and its components. - @YounixM , @H4ad
.query-add-ons {
width: 100%;
}
@@ -104,7 +102,7 @@
border-top-right-radius: 0px;
border-bottom-right-radius: 0px;
padding: 0px !important;
background-color: var(--l2-background) !important;
background-color: var(--card) !important;
&:focus-within {
border-color: var(--l1-border);
@@ -213,7 +211,7 @@
.cm-line {
line-height: 36px !important;
font-family: 'Space Mono', monospace !important;
background-color: var(--l2-background) !important;
background-color: var(--card) !important;
::-moz-selection {
background: var(--l3-background) !important;
@@ -251,8 +249,8 @@
.close-btn {
border-radius: 0px 2px 2px 0px;
border: 1px solid var(--l2-border);
background: var(--l2-background);
border: 1px solid var(--l1-border);
background: var(--l3-background);
height: 38px;
width: 38px;
@@ -286,3 +284,108 @@
}
}
}
.lightMode {
.add-ons-list {
.add-ons-tabs {
.add-on-tab-title {
color: var(--l1-foreground) !important;
}
.tab {
border: 1px solid var(--l1-border) !important;
background: var(--l1-background) !important;
&:first-child {
border-left: 1px solid var(--l1-border) !important;
}
}
.tab::before {
background: var(--l3-background) !important;
}
.selected-view {
color: var(--primary-background) !important;
border: 1px solid var(--l1-border) !important;
}
.selected-view::before {
background: var(--l3-background) !important;
}
}
.compass-button {
border: 1px solid var(--l1-border) !important;
background: var(--l1-background) !important;
}
}
.having-filter-container {
.having-filter-select-container {
.having-filter-select-editor {
.cm-editor {
&:focus-within {
border-color: var(--l1-border) !important;
}
.cm-content {
border: 1px solid var(--l1-border) !important;
background: var(--l1-background) !important;
&:focus-within {
border-color: var(--l1-border) !important;
}
}
.cm-tooltip-autocomplete {
background: var(--l1-background) !important;
border: 1px solid var(--l1-border) !important;
color: var(--l1-foreground) !important;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1) !important;
ul {
li {
color: var(--l1-foreground) !important;
&:hover {
background: var(--l3-background) !important;
}
&[aria-selected='true'] {
background: var(--l3-background) !important;
font-weight: 600 !important;
}
}
}
}
.cm-line {
background-color: var(--l1-background) !important;
::-moz-selection {
background: var(--l1-background) !important;
}
::selection {
background: var(--l3-background) !important;
}
.chip-decorator {
background: var(--l3-background) !important;
color: var(--l1-foreground) !important;
}
}
.cm-selectionBackground {
background: var(--l1-background) !important;
}
}
}
.close-btn {
border: 1px solid var(--l1-border) !important;
background: var(--l1-background) !important;
}
}
}
}

View File

@@ -1,5 +1,3 @@
// TODO: Improve the styling of the query aggregation container and its components. - @YounixM , @H4ad
.query-aggregation-container {
display: block;
@@ -28,7 +26,7 @@
&.error {
.cm-editor {
.cm-content {
border-color: var(--danger-background) !important;
border-color: var(--bg-cherry-500) !important;
}
}
}
@@ -142,7 +140,7 @@
.cm-line {
line-height: 36px !important;
font-family: 'Space Mono', monospace !important;
background-color: var(--l2-background) !important;
background-color: var(--l1-background) !important;
::-moz-selection {
background: var(--l3-background) !important;
@@ -186,7 +184,7 @@
max-width: 300px;
.query-aggregation-error-message {
color: var(--danger-background);
color: var(--bg-cherry-500);
font-size: 12px;
line-height: 16px;
}
@@ -198,7 +196,6 @@
min-width: auto;
}
}
.close-btn {
border-radius: 0px 2px 2px 0px;
border: 1px solid var(--l1-border);
@@ -273,7 +270,7 @@
.cm-line {
::-moz-selection {
background: var(--l2-background) !important;
background: var(--l1-background) !important;
opacity: 0.5 !important;
}

View File

@@ -1,5 +1,3 @@
// TODO: Improve the styling of the query aggregation container and its components. - @YounixM , @H4ad
.code-mirror-where-clause {
width: 100%;
display: flex;
@@ -32,7 +30,7 @@
border-left: none !important;
&.hasErrors {
border-color: var(--danger-background);
border-color: var(--bg-cherry-500);
}
}
}
@@ -41,7 +39,7 @@
&.hasErrors {
.cm-editor {
.cm-content {
border-color: var(--danger-background);
border-color: var(--bg-cherry-500);
border-top-right-radius: 0px !important;
border-bottom-right-radius: 0px !important;
}
@@ -158,7 +156,7 @@
.cm-line {
line-height: 34px !important;
font-family: 'Space Mono', monospace !important;
background-color: var(--l2-background) !important;
background-color: var(--l1-background) !important;
::-moz-selection {
background: var(--l3-background) !important;
@@ -213,7 +211,7 @@
.invalid {
background-color: color-mix(in srgb, var(--bg-cherry-400) 10%, transparent);
color: var(--danger-background);
color: var(--bg-cherry-500);
}
.query-validation-status {
@@ -234,7 +232,7 @@
font-size: 12px;
font-family: 'Space Mono', monospace !important;
color: var(--danger-background);
color: var(--bg-cherry-500);
padding: 8px;
}
}
@@ -456,3 +454,30 @@
margin-top: -6px !important;
}
}
.lightMode {
.code-mirror-where-clause {
.cm-editor {
.cm-tooltip-autocomplete {
background: var(--l1-background) !important;
border: 1px solid var(--l1-border);
backdrop-filter: blur(20px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1) !important;
}
.cm-line {
::-moz-selection {
background: var(--bg-robin-200) !important;
}
::selection {
background: var(--bg-robin-200) !important;
}
}
.cm-selectionBackground {
background: var(--bg-robin-200) !important;
}
}
}
}

View File

@@ -177,7 +177,7 @@
width: 2px;
height: 11px;
border-radius: 2px;
background: var(--danger-background);
background: var(--bg-cherry-500);
}
.label-true {

View File

@@ -158,12 +158,12 @@
mask-image: radial-gradient(
circle at 50% 0,
color-mix(in srgb, var(--l1-background) 10%, transparent) 0,
color-mix(in srgb, var(--background) 10%, transparent) 0,
transparent 100%
);
-webkit-mask-image: radial-gradient(
circle at 50% 0,
color-mix(in srgb, var(--l1-background) 10%, transparent) 0,
color-mix(in srgb, var(--background) 10%, transparent) 0,
transparent 100%
);
}

View File

@@ -11,7 +11,7 @@ import {
ComboboxItem,
ComboboxList,
ComboboxTrigger,
} from '@signozhq/ui';
} from '@signozhq/combobox';
import { Skeleton, Switch, Tooltip, Typography } from 'antd';
import getLocalStorageKey from 'api/browser/localstorage/get';
import setLocalStorageKey from 'api/browser/localstorage/set';
@@ -200,6 +200,7 @@ export default function QuickFilters(props: IQuickFiltersProps): JSX.Element {
setOpen(false);
}}
isSelected={validQueryIndex === option.value}
showCheck={false}
>
{option.label}
</ComboboxItem>

View File

@@ -17,7 +17,7 @@
}
&__label {
font-size: var(--font-size-xs);
font-size: var(--font-size-sm);
font-weight: var(--font-weight-normal);
color: var(--foreground);
line-height: var(--line-height-20);
@@ -142,10 +142,6 @@
gap: var(--spacing-2);
}
&__callout-wrapper {
display: flex;
}
&__expiry-label {
font-size: var(--font-size-xs);
font-weight: var(--font-weight-medium);

View File

@@ -1,5 +1,7 @@
import { Button } from '@signozhq/button';
import { Callout } from '@signozhq/callout';
import { Check, Copy } from '@signozhq/icons';
import { Badge, Button, Callout } from '@signozhq/ui';
import { Badge } from '@signozhq/ui';
import type { ServiceaccounttypesGettableFactorAPIKeyWithKeyDTO } from 'api/generated/services/sigNoz.schemas';
export interface KeyCreatedPhaseProps {
@@ -38,13 +40,11 @@ function KeyCreatedPhase({
<Badge color="vanilla">{expiryLabel}</Badge>
</div>
<div className="add-key-modal__callout-wrapper">
<Callout
type="info"
showIcon
title="Store the key securely. This is the only time it will be displayed."
/>
</div>
<Callout
type="info"
showIcon
message="Store the key securely. This is the only time it will be displayed."
/>
</div>
);
}

View File

@@ -1,6 +1,8 @@
import type { Control, UseFormRegister } from 'react-hook-form';
import { Controller } from 'react-hook-form';
import { Button, Input, ToggleGroup, ToggleGroupItem } from '@signozhq/ui';
import { Button } from '@signozhq/button';
import { Input } from '@signozhq/input';
import { ToggleGroup, ToggleGroupItem } from '@signozhq/toggle-group';
import { DatePicker } from 'antd';
import { popupContainer } from 'utils/selectPopupContainer';
@@ -54,12 +56,11 @@ function KeyFormPhase({
<ToggleGroup
type="single"
value={field.value}
onChange={(val): void => {
onValueChange={(val): void => {
if (val) {
field.onChange(val);
}
}}
size="sm"
className="add-key-modal__expiry-toggle"
>
<ToggleGroupItem
@@ -111,7 +112,6 @@ function KeyFormPhase({
</Button>
<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"

View File

@@ -2,7 +2,8 @@ import { useCallback, useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { useQueryClient } from 'react-query';
import { useCopyToClipboard } from 'react-use';
import { DialogWrapper, toast } from '@signozhq/ui';
import { DialogWrapper } from '@signozhq/dialog';
import { toast } from '@signozhq/ui';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import {
invalidateListServiceAccountKeys,
@@ -117,12 +118,12 @@ function AddKeyModal(): JSX.Element {
copyToClipboard(createdKey.key);
setHasCopied(true);
setTimeout(() => setHasCopied(false), 2000);
toast.success('Key copied to clipboard');
toast.success('Key copied to clipboard', { richColors: true });
}, [copyToClipboard, createdKey?.key]);
useEffect(() => {
if (copyState.error) {
toast.error('Failed to copy key');
toast.error('Failed to copy key', { richColors: true });
}
}, [copyState.error]);

View File

@@ -1,6 +1,8 @@
import { useQueryClient } from 'react-query';
import { Button } from '@signozhq/button';
import { DialogFooter, DialogWrapper } from '@signozhq/dialog';
import { Trash2, X } from '@signozhq/icons';
import { Button, DialogWrapper, toast } from '@signozhq/ui';
import { toast } from '@signozhq/ui';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import {
getGetServiceAccountQueryKey,
@@ -40,7 +42,7 @@ function DeleteAccountModal(): JSX.Element {
} = useDeleteServiceAccount({
mutation: {
onSuccess: async () => {
toast.success('Service account deleted');
toast.success('Service account deleted', { richColors: true });
await setIsDeleteOpen(null);
await setAccountId(null);
await invalidateListServiceAccounts(queryClient);
@@ -68,32 +70,6 @@ function DeleteAccountModal(): JSX.Element {
setIsDeleteOpen(null);
}
const content = (
<p className="sa-delete-dialog__body">
Are you sure you want to delete <strong>{accountName}</strong>? This action
cannot be undone. All keys associated with this service account will be
permanently removed.
</p>
);
const footer = (
<div className="sa-delete-dialog__footer">
<Button variant="solid" color="secondary" onClick={handleCancel}>
<X size={12} />
Cancel
</Button>
<Button
variant="solid"
color="destructive"
loading={isDeleting}
onClick={handleConfirm}
>
<Trash2 size={12} />
Delete
</Button>
</div>
);
return (
<DialogWrapper
open={open}
@@ -107,9 +83,28 @@ function DeleteAccountModal(): JSX.Element {
className="alert-dialog sa-delete-dialog"
showCloseButton={false}
disableOutsideClick={isErrorModalVisible}
footer={footer}
>
{content}
<p className="sa-delete-dialog__body">
Are you sure you want to delete <strong>{accountName}</strong>? This action
cannot be undone. All keys associated with this service account will be
permanently removed.
</p>
<DialogFooter className="sa-delete-dialog__footer">
<Button variant="solid" color="secondary" size="sm" onClick={handleCancel}>
<X size={12} />
Cancel
</Button>
<Button
variant="solid"
color="destructive"
size="sm"
loading={isDeleting}
onClick={handleConfirm}
>
<Trash2 size={12} />
Delete
</Button>
</DialogFooter>
</DialogWrapper>
);
}

View File

@@ -1,13 +1,10 @@
import type { Control, UseFormRegister } from 'react-hook-form';
import { Controller } from 'react-hook-form';
import { Button } from '@signozhq/button';
import { LockKeyhole, Trash2, X } from '@signozhq/icons';
import {
Badge,
Button,
Input,
ToggleGroup,
ToggleGroupItem,
} from '@signozhq/ui';
import { Input } from '@signozhq/input';
import { ToggleGroup, ToggleGroupItem } from '@signozhq/toggle-group';
import { Badge } from '@signozhq/ui';
import { DatePicker } from 'antd';
import type { ServiceaccounttypesGettableFactorAPIKeyDTO } from 'api/generated/services/sigNoz.schemas';
import { popupContainer } from 'utils/selectPopupContainer';
@@ -75,12 +72,11 @@ function EditKeyForm({
<ToggleGroup
type="single"
value={field.value}
onChange={(val): void => {
onValueChange={(val): void => {
if (val) {
field.onChange(val);
}
}}
size="sm"
className="edit-key-modal__expiry-toggle"
>
<ToggleGroupItem
@@ -136,21 +132,25 @@ function EditKeyForm({
</form>
<div className="edit-key-modal__footer">
<Button variant="ghost" color="destructive" onClick={onRevokeClick}>
<Button
type="button"
className="edit-key-modal__footer-danger"
onClick={onRevokeClick}
>
<Trash2 size={12} />
Revoke Key
</Button>
<div className="edit-key-modal__footer-right">
<Button variant="solid" color="secondary" onClick={onClose}>
<Button variant="solid" color="secondary" size="sm" onClick={onClose}>
<X size={12} />
Cancel
</Button>
<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"
size="sm"
loading={isSaving}
disabled={!isDirty}
>

View File

@@ -138,7 +138,7 @@
&__meta {
display: flex;
flex-direction: column;
gap: var(--spacing-4);
gap: var(--spacing-2);
}
&__meta-label {

View File

@@ -1,7 +1,8 @@
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { useQueryClient } from 'react-query';
import { DialogWrapper, toast } from '@signozhq/ui';
import { DialogWrapper } from '@signozhq/dialog';
import { toast } from '@signozhq/ui';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import {
invalidateListServiceAccountKeys,
@@ -71,7 +72,7 @@ function EditKeyModal({ keyItem }: EditKeyModalProps): JSX.Element {
const { mutate: updateKey, isLoading: isSaving } = useUpdateServiceAccountKey({
mutation: {
onSuccess: async () => {
toast.success('Key updated successfully');
toast.success('Key updated successfully', { richColors: true });
await setEditKeyId(null);
if (selectedAccountId) {
await invalidateListServiceAccountKeys(queryClient, {
@@ -95,7 +96,7 @@ function EditKeyModal({ keyItem }: EditKeyModalProps): JSX.Element {
} = useRevokeServiceAccountKey({
mutation: {
onSuccess: async () => {
toast.success('Key revoked successfully');
toast.success('Key revoked successfully', { richColors: true });
setIsRevokeConfirmOpen(false);
await setEditKeyId(null);
if (selectedAccountId) {

View File

@@ -1,6 +1,6 @@
import { useCallback, useMemo } from 'react';
import { Button } from '@signozhq/button';
import { KeyRound, X } from '@signozhq/icons';
import { Button } from '@signozhq/ui';
import { Skeleton, Table, Tooltip } from 'antd';
import type { ColumnsType } from 'antd/es/table/interface';
import type { ServiceaccounttypesGettableFactorAPIKeyDTO } from 'api/generated/services/sigNoz.schemas';
@@ -96,7 +96,7 @@ function buildColumns({
<Tooltip title={isDisabled ? 'Service account disabled' : 'Revoke Key'}>
<Button
variant="ghost"
size="sm"
size="xs"
color="destructive"
disabled={isDisabled}
onClick={(e): void => {
@@ -177,8 +177,8 @@ function KeysTab({
</a>
</p>
<Button
variant="link"
color="primary"
type="button"
className="keys-tab__learn-more"
onClick={async (): Promise<void> => {
await setIsAddKeyOpen(true);
}}

View File

@@ -1,6 +1,7 @@
import { useCallback } from 'react';
import { LockKeyhole } from '@signozhq/icons';
import { Badge, Input } from '@signozhq/ui';
import { Input } from '@signozhq/input';
import { Badge } from '@signozhq/ui';
import type { AuthtypesRoleDTO } from 'api/generated/services/sigNoz.schemas';
import RolesSelect from 'components/RolesSelect';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';

View File

@@ -1,6 +1,8 @@
import { useQueryClient } from 'react-query';
import { Button } from '@signozhq/button';
import { DialogFooter, DialogWrapper } from '@signozhq/dialog';
import { Trash2, X } from '@signozhq/icons';
import { Button, DialogWrapper, toast } from '@signozhq/ui';
import { toast } from '@signozhq/ui';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import {
getListServiceAccountKeysQueryKey,
@@ -34,7 +36,7 @@ export function RevokeKeyContent({
Revoking this key will permanently invalidate it. Any systems using this key
will lose access immediately.
</p>
<div className="delete-dialog__footer">
<DialogFooter className="delete-dialog__footer">
<Button variant="solid" color="secondary" size="sm" onClick={onCancel}>
<X size={12} />
Cancel
@@ -49,7 +51,7 @@ export function RevokeKeyContent({
<Trash2 size={12} />
Revoke Key
</Button>
</div>
</DialogFooter>
</>
);
}
@@ -77,7 +79,7 @@ function RevokeKeyModal(): JSX.Element {
} = useRevokeServiceAccountKey({
mutation: {
onSuccess: async () => {
toast.success('Key revoked successfully');
toast.success('Key revoked successfully', { richColors: true });
await setRevokeKeyId(null);
if (accountId) {
await invalidateListServiceAccountKeys(queryClient, { id: accountId });

View File

@@ -1,7 +1,7 @@
import { useState } from 'react';
import { Button } from '@signozhq/button';
import { Color } from '@signozhq/design-tokens';
import { ChevronDown, ChevronUp, CircleAlert, RotateCw } from '@signozhq/icons';
import { Button } from '@signozhq/ui';
import ErrorContent from 'components/ErrorModal/components/ErrorContent';
import APIError from 'types/api/error';
@@ -40,9 +40,9 @@ function SaveErrorItem({
</span>
{onRetry && !isRetrying && (
<Button
variant="link"
color="none"
type="button"
aria-label="Retry"
size="xs"
onClick={async (e): Promise<void> => {
e.stopPropagation();
setIsRetrying(true);

View File

@@ -5,21 +5,31 @@
margin-left: var(--margin-2);
}
&__layout {
display: flex;
flex-direction: column;
height: calc(100vh - 48px);
}
&__tabs {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--padding-3) var(--padding-4) var(--padding-2) var(--padding-4);
flex-shrink: 0;
}
&__tab-group {
[data-slot='toggle-group'] {
height: 32px;
border-radius: 2px;
border: 1px solid var(--l1-border);
background: var(--l2-background);
gap: 0;
}
[data-slot='toggle-group-item'] {
height: 32px;
border-radius: 0;
border-left: 1px solid var(--l1-border);
background: transparent;
@@ -30,7 +40,6 @@
padding: 0 var(--padding-7);
gap: var(--spacing-3);
box-shadow: none;
border: none;
&:first-child {
border-left: none;
@@ -79,7 +88,7 @@
&__body {
flex: 1;
overflow-y: auto;
padding-top: var(--padding-5);
padding: var(--padding-5) var(--padding-4);
display: flex;
flex-direction: column;
gap: var(--spacing-8);
@@ -103,11 +112,14 @@
}
&__footer {
height: 56px;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
padding: 0 var(--padding-4);
border-top: 1px solid var(--secondary);
background: var(--card);
}
&__keys-pagination {
@@ -290,7 +302,7 @@
&__icon {
flex-shrink: 0;
color: var(--danger-background);
color: var(--bg-cherry-500);
}
&__title {
@@ -298,7 +310,7 @@
min-width: 0;
font-size: var(--font-size-xs);
font-weight: var(--font-weight-medium);
color: var(--danger-background);
color: var(--bg-cherry-500);
line-height: var(--line-height-18);
letter-spacing: -0.06px;
text-align: left;
@@ -575,5 +587,6 @@
display: flex;
justify-content: flex-end;
gap: var(--spacing-4);
margin-top: var(--margin-6);
}
}

View File

@@ -1,13 +1,10 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useQueryClient } from 'react-query';
import { Button } from '@signozhq/button';
import { DrawerWrapper } from '@signozhq/drawer';
import { Key, LayoutGrid, Plus, Trash2, X } from '@signozhq/icons';
import {
Button,
DrawerWrapper,
toast,
ToggleGroup,
ToggleGroupItem,
} from '@signozhq/ui';
import { ToggleGroup, ToggleGroupItem } from '@signozhq/toggle-group';
import { toast } from '@signozhq/ui';
import { Pagination, Skeleton } from 'antd';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import {
@@ -334,6 +331,7 @@ function ServiceAccountDrawer({
setSaveErrors(errors);
} else {
toast.success('Service account updated successfully', {
richColors: true,
position: 'top-right',
});
onSuccess({ closeDrawer: false });
@@ -381,7 +379,7 @@ function ServiceAccountDrawer({
<ToggleGroup
type="single"
value={activeTab}
onChange={(val): void => {
onValueChange={(val): void => {
if (val) {
setActiveTab(val as ServiceAccountDrawerTab);
if (val !== ServiceAccountDrawerTab.Keys) {
@@ -473,64 +471,69 @@ function ServiceAccountDrawer({
</>
)}
</div>
</div>
);
const footer = (
<div className="sa-drawer__footer">
{activeTab === ServiceAccountDrawerTab.Keys ? (
<Pagination
current={keysPage}
pageSize={PAGE_SIZE}
total={keys.length}
showTotal={(total: number, range: number[]): JSX.Element => (
<>
<span className="sa-drawer__pagination-range">
{range[0]} &#8212; {range[1]}
</span>
<span className="sa-drawer__pagination-total"> of {total}</span>
</>
)}
showSizeChanger={false}
hideOnSinglePage
onChange={(page): void => {
void setKeysPage(page);
}}
className="sa-drawer__keys-pagination"
/>
) : (
<>
{!isDeleted && (
<Button
variant="link"
color="destructive"
onClick={(): void => {
setIsDeleteOpen(true);
}}
>
<Trash2 size={12} />
Delete Service Account
</Button>
)}
{!isDeleted && (
<div className="sa-drawer__footer-right">
<Button variant="solid" color="secondary" onClick={handleClose}>
<X size={14} />
Cancel
</Button>
<div className="sa-drawer__footer">
{activeTab === ServiceAccountDrawerTab.Keys ? (
<Pagination
current={keysPage}
pageSize={PAGE_SIZE}
total={keys.length}
showTotal={(total: number, range: number[]): JSX.Element => (
<>
<span className="sa-drawer__pagination-range">
{range[0]} &#8212; {range[1]}
</span>
<span className="sa-drawer__pagination-total"> of {total}</span>
</>
)}
showSizeChanger={false}
hideOnSinglePage
onChange={(page): void => {
void setKeysPage(page);
}}
className="sa-drawer__keys-pagination"
/>
) : (
<>
{!isDeleted && (
<Button
variant="solid"
color="primary"
loading={isSaving}
disabled={!isDirty}
onClick={handleSave}
variant="ghost"
color="destructive"
className="sa-drawer__footer-btn"
onClick={(): void => {
setIsDeleteOpen(true);
}}
>
Save Changes
<Trash2 size={12} />
Delete Service Account
</Button>
</div>
)}
</>
)}
)}
{!isDeleted && (
<div className="sa-drawer__footer-right">
<Button
variant="solid"
color="secondary"
size="sm"
onClick={handleClose}
>
<X size={14} />
Cancel
</Button>
<Button
variant="solid"
color="primary"
size="sm"
loading={isSaving}
disabled={!isDirty}
onClick={handleSave}
>
Save Changes
</Button>
</div>
)}
</>
)}
</div>
</div>
);
@@ -544,15 +547,14 @@ function ServiceAccountDrawer({
}
}}
direction="right"
type="panel"
showCloseButton
showOverlay={false}
title="Service Account Details"
allowOutsideClick
header={{ title: 'Service Account Details' }}
content={drawerContent}
className="sa-drawer"
width="wide"
footer={footer}
>
{drawerContent}
</DrawerWrapper>
/>
<DeleteAccountModal />

View File

@@ -1,13 +1,7 @@
import { toast } from '@signozhq/ui';
import { rest, server } from 'mocks-server/server';
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
import {
render,
screen,
userEvent,
waitFor,
waitForElementToBeRemoved,
} from 'tests/test-utils';
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
import AddKeyModal from '../AddKeyModal';
@@ -123,7 +117,10 @@ describe('AddKeyModal', () => {
await waitFor(() => {
expect(mockCopyToClipboard).toHaveBeenCalledWith('snz_abc123xyz456secret');
expect(mockToast.success).toHaveBeenCalledWith('Key copied to clipboard');
expect(mockToast.success).toHaveBeenCalledWith(
'Key copied to clipboard',
expect.anything(),
);
});
});
@@ -131,9 +128,11 @@ describe('AddKeyModal', () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
renderModal();
const dialog = await screen.findByRole('dialog', { name: /Add a New Key/i });
await screen.findByRole('dialog', { name: /Add a New Key/i });
await user.click(screen.getByRole('button', { name: /Cancel/i }));
await waitForElementToBeRemoved(dialog);
expect(
screen.queryByRole('dialog', { name: /Add a New Key/i }),
).not.toBeInTheDocument();
});
});

View File

@@ -29,14 +29,9 @@ function renderModal(
account: 'sa-1',
'edit-key': 'key-1',
},
onUrlUpdate?: jest.Mock,
): ReturnType<typeof render> {
return render(
<NuqsTestingAdapter
searchParams={searchParams}
hasMemory
onUrlUpdate={onUrlUpdate}
>
<NuqsTestingAdapter searchParams={searchParams} hasMemory>
<EditKeyModal keyItem={keyItem} />
</NuqsTestingAdapter>,
);
@@ -87,7 +82,10 @@ describe('EditKeyModal (URL-controlled)', () => {
await user.click(screen.getByRole('button', { name: /Save Changes/i }));
await waitFor(() => {
expect(mockToast.success).toHaveBeenCalledWith('Key updated successfully');
expect(mockToast.success).toHaveBeenCalledWith(
'Key updated successfully',
expect.anything(),
);
});
await waitFor(() => {
@@ -99,31 +97,14 @@ describe('EditKeyModal (URL-controlled)', () => {
it('cancel clears edit-key param and closes modal', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
const onUrlUpdate = jest.fn();
renderModal(mockKey, undefined, onUrlUpdate);
renderModal();
await screen.findByDisplayValue('Original Key Name');
await user.click(screen.getByRole('button', { name: /Cancel/i }));
await waitFor(() => {
expect(onUrlUpdate).toHaveBeenCalled();
});
const latestUrlUpdate =
onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1]?.[0];
expect(latestUrlUpdate).toEqual(
expect.objectContaining({
queryString: expect.any(String),
}),
);
expect(latestUrlUpdate.queryString).toContain('account=sa-1');
expect(latestUrlUpdate.queryString).not.toContain('edit-key=');
await waitFor(() => {
expect(
screen.queryByRole('dialog', { name: /Edit Key Details/i }),
).not.toBeInTheDocument();
});
expect(
screen.queryByRole('dialog', { name: /Edit Key Details/i }),
).not.toBeInTheDocument();
});
it('revoke flow: clicking Revoke Key shows confirmation inside same dialog', async () => {
@@ -155,7 +136,10 @@ describe('EditKeyModal (URL-controlled)', () => {
await user.click(confirmBtn);
await waitFor(() => {
expect(mockToast.success).toHaveBeenCalledWith('Key revoked successfully');
expect(mockToast.success).toHaveBeenCalledWith(
'Key revoked successfully',
expect.anything(),
);
});
await waitFor(() => {

View File

@@ -164,7 +164,10 @@ describe('KeysTab', () => {
await user.click(confirmBtn);
await waitFor(() => {
expect(mockToast.success).toHaveBeenCalledWith('Key revoked successfully');
expect(mockToast.success).toHaveBeenCalledWith(
'Key revoked successfully',
expect.anything(),
);
});
});

View File

@@ -6,23 +6,18 @@ import { render, screen, userEvent, waitFor } from 'tests/test-utils';
import ServiceAccountDrawer from '../ServiceAccountDrawer';
jest.mock('@signozhq/ui', () => ({
...jest.requireActual('@signozhq/ui'),
jest.mock('@signozhq/drawer', () => ({
DrawerWrapper: ({
children,
footer,
content,
open,
}: {
children?: ReactNode;
footer?: ReactNode;
content?: ReactNode;
open: boolean;
}): JSX.Element | null =>
open ? (
<div>
{children}
{footer}
</div>
) : null,
}): JSX.Element | null => (open ? <div>{content}</div> : null),
}));
jest.mock('@signozhq/ui', () => ({
...jest.requireActual('@signozhq/ui'),
toast: { success: jest.fn(), error: jest.fn() },
}));

View File

@@ -21,7 +21,7 @@
.ant-table-thead {
> tr > th,
> tr > td {
background: var(--l1-background);
background: var(--background);
font-size: var(--paragraph-small-600-font-size);
font-weight: var(--paragraph-small-600-font-weight);
line-height: var(--paragraph-small-600-line-height);

View File

@@ -11,7 +11,7 @@
gap: 20px;
padding: 8px 12px;
background: var(--l1-background);
background: var(--background);
color: var(--l2-foreground);
border-radius: 8px;

View File

@@ -50,7 +50,7 @@
&__error {
font-size: 0.75rem;
color: var(--danger-background);
color: var(--bg-cherry-500);
font-weight: 500;
}
@@ -94,7 +94,7 @@
height: 20px;
flex-shrink: 0;
border-radius: 2px;
background: var(--danger-background);
background: var(--bg-cherry-500);
}
}

View File

@@ -1,11 +1,11 @@
.warning-content {
display: flex;
flex-direction: column;
// === SECTION: Summary (Top)
&__summary-section {
display: flex;
flex-direction: column;
border-bottom: 1px solid var(--l1-border);
}
&__summary {

View File

@@ -161,11 +161,10 @@ describe('CmdKPalette', () => {
});
test('clicking a navigation item calls history.push with correct route', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
render(<CmdKPalette userRole="ADMIN" />);
const homeItem = screen.getByText(HOME_LABEL);
await user.click(homeItem);
await userEvent.click(homeItem);
expect(history.push).toHaveBeenCalledWith(ROUTES.HOME);
});
@@ -195,11 +194,10 @@ describe('CmdKPalette', () => {
});
test('closing the palette via handleInvoke sets open to false', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
render(<CmdKPalette userRole="ADMIN" />);
const dashItem = screen.getByText('Go to Dashboards');
await user.click(dashItem);
await userEvent.click(dashItem);
// last call from handleInvoke should set open to false
expect(mockSetOpen).toHaveBeenCalledWith(false);

View File

@@ -41,7 +41,7 @@
.cmdk-item-light:hover {
cursor: pointer;
background-color: var(--l1-background) !important;
background-color: var(--background) !important;
}
.cmdk-item-light[data-selected='true'] {

View File

@@ -7,7 +7,7 @@ import {
CommandItem,
CommandList,
CommandShortcut,
} from '@signozhq/ui';
} from '@signozhq/command';
import logEvent from 'api/common/logEvent';
import { useThemeMode } from 'hooks/useDarkMode';
import history from 'lib/history';

View File

@@ -9,5 +9,4 @@ export enum FeatureKeys {
ANOMALY_DETECTION = 'anomaly_detection',
ONBOARDING_V3 = 'onboarding_v3',
DOT_METRICS_ENABLED = 'dot_metrics_enabled',
ENABLE_BODY_JSON_QUERY = 'enable_body_json_query',
}

View File

@@ -50,7 +50,6 @@
.app-content {
width: calc(100% - 54px); // width of the sidebar
z-index: 0;
background: var(--l1-background);
margin: 0 auto;
@@ -140,6 +139,22 @@
}
}
.isDarkMode {
.app-layout {
.app-content {
background: var(--background);
}
}
}
.isLightMode {
.app-layout {
.app-content {
background: var(--card);
}
}
}
.trial-expiry-banner,
.slow-api-warning-banner,
.workspace-restricted-banner {

View File

@@ -82,7 +82,7 @@
.sentence-text {
color: var(--l2-foreground);
font-size: 13px;
font-size: 14px;
line-height: 1.5;
display: flex;
align-items: center;

View File

@@ -1,5 +1,5 @@
import { useCallback, useMemo } from 'react';
import { Button, Input } from '@signozhq/ui';
import { Button } from 'antd';
import logEvent from 'api/common/logEvent';
import classNames from 'classnames';
import { QueryParams } from 'constants/query';
@@ -59,18 +59,15 @@ function CreateAlertHeader(): JSX.Element {
<div className="alert-header__tab-bar">
<div className="alert-header__tab">New Alert Rule</div>
<Button
prefix={<RotateCcw size={12} />}
icon={<RotateCcw size={16} />}
onClick={handleSwitchToClassicExperience}
variant="solid"
color="secondary"
size="sm"
>
Switch to Classic Experience
</Button>
</div>
)}
<div className="alert-header__content">
<Input
<input
type="text"
value={alertState.name}
onChange={(e): void =>

View File

@@ -2,7 +2,6 @@
background-color: var(--l1-background);
font-family: inherit;
color: var(--l1-foreground);
padding: 12px 16px;
&__tab-bar {
display: flex;
@@ -15,20 +14,23 @@
display: flex;
align-items: center;
background-color: var(--l1-background);
padding: 0 12px;
height: 32px;
font-size: 13px;
color: var(--l1-foreground);
margin-left: 12px;
margin-top: 12px;
}
&__tab::before {
content: '';
margin-right: 6px;
font-size: 13px;
font-size: 14px;
color: var(--l3-foreground);
}
&__content {
padding: 8px 0;
padding: 16px;
background: var(--l1-background);
display: flex;
flex-direction: column;
@@ -38,14 +40,22 @@
}
&__input.title {
font-size: 18px;
font-weight: 500;
background-color: transparent;
color: var(--l1-foreground);
width: 100%;
min-width: 300px;
}
&__input:focus,
&__input:active {
border: none;
outline: none;
}
&__input.description {
font-size: 13px;
font-size: 14px;
background-color: transparent;
color: var(--l2-foreground);
}

View File

@@ -13,7 +13,7 @@
.advanced-option-item-title {
color: var(--l2-foreground);
font-family: Inter;
font-size: 13px;
font-size: 14px;
font-weight: 500;
display: flex;
align-items: center;
@@ -23,7 +23,7 @@
.advanced-option-item-description {
color: var(--muted-foreground);
font-family: Inter;
font-size: 13px;
font-size: 14px;
font-weight: 400;
}

View File

@@ -22,7 +22,7 @@
color: var(--l2-foreground);
font-weight: 500;
margin: 0 4px;
font-size: 13px;
font-size: 14px;
}
}
@@ -30,7 +30,7 @@
.ant-btn {
border: 1px solid var(--l1-border);
color: var(--l2-foreground);
font-size: 13px;
font-size: 14px;
display: flex;
align-items: center;
gap: 8px;
@@ -48,7 +48,7 @@
.evaluation-cadence-details-title {
color: var(--l1-foreground);
font-size: 13px;
font-size: 14px;
font-weight: 500;
padding-left: 16px;
padding-top: 16px;
@@ -138,7 +138,7 @@
border-radius: 4px;
color: var(--l2-foreground) !important;
font-family: 'Space Mono';
font-size: 13px;
font-size: 14px;
&::placeholder {
font-family: 'Space Mono';
@@ -202,7 +202,7 @@
gap: 8px;
height: 100%;
color: var(--l1-foreground);
font-size: 13px;
font-size: 14px;
}
.schedule-preview {
@@ -225,7 +225,7 @@
.schedule-preview-title {
color: var(--l2-foreground);
font-size: 13px;
font-size: 14px;
font-weight: 500;
}
}
@@ -281,7 +281,7 @@
.schedule-preview-date {
color: var(--l2-foreground);
font-size: 13px;
font-size: 14px;
font-weight: 400;
white-space: nowrap;
}
@@ -396,7 +396,7 @@
.schedule-preview-title {
color: var(--l1-foreground);
font-size: 13px;
font-size: 14px;
font-weight: 500;
}
}
@@ -422,7 +422,7 @@
.schedule-preview-content {
.schedule-preview-date {
color: var(--l1-foreground);
font-size: 13px;
font-size: 14px;
font-weight: 500;
}

View File

@@ -10,7 +10,7 @@
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
font-family: 'Space Mono', monospace;
font-size: 13px;
font-size: 14px;
font-weight: 600;
text-align: center;
border-radius: 4px;
@@ -43,7 +43,7 @@
.time-input-separator {
color: var(--l2-foreground);
font-size: 13px;
font-size: 14px;
font-weight: 600;
margin: 0 4px;
user-select: none;

View File

@@ -13,7 +13,7 @@
.ant-typography {
color: var(--l2-foreground);
font-size: 13px;
font-size: 14px;
}
.evaluate-alert-conditions-separator {
@@ -175,7 +175,7 @@
background-color: var(--l3-background);
border: 1px solid var(--l1-border);
color: var(--l2-foreground);
font-size: 13px;
font-size: 14px;
}
}
}

View File

@@ -1,6 +1,6 @@
import { useCallback, useMemo } from 'react';
import { Button, toast } from '@signozhq/ui';
import { Tooltip } from 'antd';
import { toast } from '@signozhq/ui';
import { Button, Tooltip, Typography } from 'antd';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import { Check, Loader, Send, X } from 'lucide-react';
@@ -147,8 +147,7 @@ function Footer(): JSX.Element {
const saveAlertButton = useMemo(() => {
let button = (
<Button
variant="solid"
color="primary"
type="primary"
onClick={handleSaveAlert}
disabled={disableButtons || Boolean(alertValidationMessage)}
>
@@ -157,7 +156,7 @@ function Footer(): JSX.Element {
) : (
<Check size={14} />
)}
Save Alert Rule
<Typography.Text>Save Alert Rule</Typography.Text>
</Button>
);
if (alertValidationMessage) {
@@ -175,13 +174,12 @@ function Footer(): JSX.Element {
const testAlertButton = useMemo(() => {
let button = (
<Button
variant="solid"
color="secondary"
type="default"
onClick={handleTestNotification}
disabled={disableButtons || Boolean(alertValidationMessage)}
>
{isTestingAlertRule ? <Loader size={14} /> : <Send size={14} />}
Test Notification
<Typography.Text>Test Notification</Typography.Text>
</Button>
);
if (alertValidationMessage) {
@@ -197,12 +195,7 @@ function Footer(): JSX.Element {
return (
<div className="create-alert-v2-footer">
<Button
variant="solid"
color="secondary"
onClick={handleDiscard}
disabled={disableButtons}
>
<Button type="default" onClick={handleDiscard} disabled={disableButtons}>
<X size={14} /> Discard
</Button>
<div className="button-group">

View File

@@ -4,8 +4,9 @@
left: 63px;
right: 0;
background-color: var(--l1-background);
height: 70px;
border-top: 1px solid var(--l1-border);
padding: 12px;
padding: 16px 24px;
z-index: 1000;
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1);
display: flex;

View File

@@ -29,14 +29,14 @@
align-items: center;
color: var(--l1-foreground);
font-family: Inter;
font-size: 13px;
font-size: 14px;
font-weight: 500;
}
.notification-message-header-description {
color: var(--l2-foreground);
font-family: Inter;
font-size: 13px;
font-size: 14px;
font-weight: 400;
}
}
@@ -59,7 +59,7 @@
border-radius: 4px;
color: var(--l2-foreground) !important;
font-family: Inter;
font-size: 13px;
font-size: 14px;
}
}
@@ -130,7 +130,7 @@
.multiple-notifications-header-title {
color: var(--l1-foreground);
font-family: Inter;
font-size: 13px;
font-size: 14px;
font-weight: 500;
display: flex;
align-items: center;
@@ -140,7 +140,7 @@
.multiple-notifications-header-description {
color: var(--l2-foreground);
font-family: Inter;
font-size: 13px;
font-size: 14px;
font-weight: 400;
}
}
@@ -179,14 +179,14 @@
.advanced-option-item-title {
color: var(--l2-foreground);
font-family: Inter;
font-size: 13px;
font-size: 14px;
font-weight: 500;
}
.advanced-option-item-description {
color: var(--muted-foreground);
font-family: Inter;
font-size: 13px;
font-size: 14px;
font-weight: 400;
}
}
@@ -206,7 +206,7 @@
flex-wrap: nowrap;
.ant-typography {
font-size: 13px;
font-size: 14px;
font-weight: 400;
color: var(--l2-foreground);
white-space: nowrap;

View File

@@ -16,8 +16,8 @@
margin-bottom: -1px;
.prom-ql-icon {
height: 13px;
width: 13px;
height: 14px;
width: 14px;
}
.explorer-view-option {

View File

@@ -17,7 +17,7 @@
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 13px;
font-size: 14px;
flex-shrink: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}

View File

@@ -1,7 +1,9 @@
import { useEffect, useState } from 'react';
import { Button } from '@signozhq/button';
import { Color } from '@signozhq/design-tokens';
import { DialogWrapper } from '@signozhq/dialog';
import { CircleAlert, CircleCheck, LoaderCircle } from '@signozhq/icons';
import { Button, DialogWrapper, Input } from '@signozhq/ui';
import { Input } from '@signozhq/input';
import { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
import { AxiosError } from 'axios';
import LaunchChatSupport from 'components/LaunchChatSupport/LaunchChatSupport';

View File

@@ -78,6 +78,7 @@
display: flex;
align-items: center;
gap: var(--spacing-10);
padding-left: 26px;
}
.custom-domain-card-meta-row.workspace-name-hidden {
@@ -271,6 +272,7 @@
display: flex;
align-items: center;
gap: var(--spacing-3);
height: 44px;
padding: 6px var(--padding-3);
background: var(--l1-background);
border: 1px solid var(--l1-border);

View File

@@ -1,4 +1,6 @@
import { useEffect, useMemo, useState } from 'react';
import { Button } from '@signozhq/button';
import { Callout } from '@signozhq/callout';
import {
Check,
ChevronDown,
@@ -9,7 +11,7 @@ import {
SolidAlertCircle,
X,
} from '@signozhq/icons';
import { Button, Callout, toast } from '@signozhq/ui';
import { toast } from '@signozhq/ui';
import { Dropdown, Skeleton } from 'antd';
import {
RenderErrorResponseDTO,
@@ -42,9 +44,9 @@ function DomainUpdateToast({
<div className="custom-domain-toast-actions">
<Button
variant="ghost"
size="sm"
size="xs"
className="custom-domain-toast-visit-btn"
suffix={<ExternalLink size={12} />}
suffixIcon={<ExternalLink size={12} />}
onClick={(): void => {
window.open(url, '_blank', 'noopener,noreferrer');
}}
@@ -59,7 +61,7 @@ function DomainUpdateToast({
toast.dismiss(toastId);
}}
aria-label="Dismiss"
prefix={<X size={14} />}
prefixIcon={<X size={14} />}
/>
</div>
</div>
@@ -243,10 +245,10 @@ export default function CustomDomainSettings(): JSX.Element {
)}
>
<Button
type="button"
size="xs"
className="workspace-url-trigger"
disabled={isFetchingHosts}
variant="link"
color="none"
>
<Link2 size={12} />
<span>{stripProtocol(activeHost?.url ?? '')}</span>
@@ -262,8 +264,9 @@ export default function CustomDomainSettings(): JSX.Element {
<Button
variant="solid"
color="secondary"
prefix={<FilePenLine size={12} />}
size="sm"
className="custom-domain-edit-button"
prefixIcon={<FilePenLine size={12} />}
disabled={isFetchingHosts || isPollingEnabled}
onClick={(): void => setIsEditModalOpen(true)}
>
@@ -278,7 +281,7 @@ export default function CustomDomainSettings(): JSX.Element {
className="custom-domain-callout"
size="small"
icon={<SolidAlertCircle size={13} color="primary" />}
title={`Updating your URL to ⎯ ${customDomainSubdomain}.${dnsSuffix}. This may take a few mins.`}
message={`Updating your URL to ⎯ ${customDomainSubdomain}.${dnsSuffix}. This may take a few mins.`}
/>
)}

View File

@@ -69,7 +69,7 @@
width: 26px;
height: 22px;
border-radius: 2px;
background: color-mix(in srgb, var(--danger-background) 10%, transparent);
background: color-mix(in srgb, var(--bg-cherry-500) 10%, transparent);
display: flex;
padding: 4px 6px;
align-items: center;

View File

@@ -1,7 +1,8 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useMutation } from 'react-query';
import { useCopyToClipboard } from 'react-use';
import { Checkbox, toast } from '@signozhq/ui';
import { Checkbox } from '@signozhq/checkbox';
import { toast } from '@signozhq/ui';
import { Button, Select, Typography } from 'antd';
import createPublicDashboardAPI from 'api/dashboard/public/createPublicDashboard';
import revokePublicDashboardAccessAPI from 'api/dashboard/public/revokePublicDashboardAccess';
@@ -246,11 +247,10 @@ function PublicDashboardSetting(): JSX.Element {
<div className="timerange-enabled-checkbox">
<Checkbox
id="enable-time-range"
value={timeRangeEnabled}
onChange={handleTimeRangeEnabled}
>
Enable time range
</Checkbox>
checked={timeRangeEnabled}
onCheckedChange={handleTimeRangeEnabled}
labelName="Enable time range"
/>
</div>
<div className="default-time-range-select">

View File

@@ -1,4 +1,4 @@
import { useCallback, useMemo, useRef } from 'react';
import { useCallback, useRef } from 'react';
import ChartLayout from 'container/DashboardContainer/visualization/layout/ChartLayout/ChartLayout';
import Legend from 'lib/uPlotV2/components/Legend/Legend';
import {
@@ -30,7 +30,6 @@ export default function ChartWrapper({
onDestroy = noop,
children,
layoutChildren,
yAxisUnit,
customTooltip,
pinnedTooltipElement,
'data-testid': testId,
@@ -63,13 +62,6 @@ export default function ChartWrapper({
[customTooltip],
);
const syncMetadata = useMemo(
() => ({
yAxisUnit,
}),
[yAxisUnit],
);
return (
<PlotContextProvider>
<ChartLayout
@@ -107,7 +99,6 @@ export default function ChartWrapper({
averageLegendWidth + TOOLTIP_WIDTH_PADDING,
)}
syncKey={syncKey}
syncMetadata={syncMetadata}
render={renderTooltipCallback}
pinnedTooltipElement={pinnedTooltipElement}
/>

View File

@@ -24,12 +24,13 @@ export default function Histogram(props: HistogramChartProps): JSX.Element {
}
const tooltipProps: HistogramTooltipProps = {
...props,
timezone: rest.timezone,
yAxisUnit: rest.yAxisUnit,
decimalPrecision: rest.decimalPrecision,
};
return <HistogramTooltip {...tooltipProps} />;
},
[customTooltip, rest.yAxisUnit, rest.decimalPrecision],
[customTooltip, rest.timezone, rest.yAxisUnit, rest.decimalPrecision],
);
return (

View File

@@ -12,7 +12,10 @@ interface BaseChartProps {
height: number;
showTooltip?: boolean;
showLegend?: boolean;
timezone?: Timezone;
canPinTooltip?: boolean;
yAxisUnit?: string;
decimalPrecision?: PrecisionOption;
pinnedTooltipElement?: (clickData: TooltipClickData) => React.ReactNode;
customTooltip?: (props: TooltipRenderArgs) => React.ReactNode;
'data-testid'?: string;
@@ -29,31 +32,18 @@ interface UPlotBasedChartProps {
layoutChildren?: React.ReactNode;
}
interface UPlotChartDataProps {
yAxisUnit?: string;
decimalPrecision?: PrecisionOption;
}
export interface TimeSeriesChartProps
extends BaseChartProps,
UPlotBasedChartProps,
UPlotChartDataProps {
timezone?: Timezone;
}
UPlotBasedChartProps {}
export interface HistogramChartProps
extends BaseChartProps,
UPlotBasedChartProps,
UPlotChartDataProps {
UPlotBasedChartProps {
isQueriesMerged?: boolean;
}
export interface BarChartProps
extends BaseChartProps,
UPlotBasedChartProps,
UPlotChartDataProps {
export interface BarChartProps extends BaseChartProps, UPlotBasedChartProps {
isStackedBarChart?: boolean;
timezone?: Timezone;
}
export type ChartProps =

View File

@@ -123,13 +123,13 @@ function BarPanel(props: PanelWrapperProps): JSX.Element {
}}
plotRef={onPlotRef}
onDestroy={onPlotDestroy}
yAxisUnit={widget.yAxisUnit}
decimalPrecision={widget.decimalPrecision}
data={chartData as uPlot.AlignedData}
width={containerDimensions.width}
height={containerDimensions.height}
layoutChildren={layoutChildren}
isStackedBarChart={widget.stackedBarChart ?? false}
yAxisUnit={widget.yAxisUnit}
decimalPrecision={widget.decimalPrecision}
timezone={timezone}
>
<ContextMenu

View File

@@ -3,6 +3,8 @@ import { PanelWrapperProps } from 'container/PanelWrapper/panelWrapper.types';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useResizeObserver } from 'hooks/useDimensions';
import { LegendPosition } from 'lib/uPlotV2/components/types';
import { DashboardCursorSync } from 'lib/uPlotV2/plugins/TooltipPlugin/types';
import { useTimezone } from 'providers/Timezone';
import uPlot from 'uplot';
import Histogram from '../../charts/Histogram/Histogram';
@@ -27,6 +29,7 @@ function HistogramPanel(props: PanelWrapperProps): JSX.Element {
const containerDimensions = useResizeObserver(graphRef);
const isDarkMode = useIsDarkMode();
const { timezone } = useTimezone();
const config = useMemo(() => {
return prepareHistogramPanelConfig({
@@ -89,9 +92,11 @@ function HistogramPanel(props: PanelWrapperProps): JSX.Element {
onDestroy={(): void => {
uPlotRef.current = null;
}}
isQueriesMerged={widget.mergeAllActiveQueries}
yAxisUnit={widget.yAxisUnit}
decimalPrecision={widget.decimalPrecision}
isQueriesMerged={widget.mergeAllActiveQueries}
syncMode={DashboardCursorSync.Crosshair}
timezone={timezone}
data={chartData as uPlot.AlignedData}
width={containerDimensions.width}
height={containerDimensions.height}

View File

@@ -48,8 +48,8 @@ jest.mock(
{JSON.stringify({
legendPosition: props.legendConfig?.position,
isQueriesMerged: props.isQueriesMerged,
yAxisUnit: props?.yAxisUnit,
decimalPrecision: props?.decimalPrecision,
yAxisUnit: props.yAxisUnit,
decimalPrecision: props.decimalPrecision,
})}
</div>
{props.layoutChildren}

View File

@@ -112,9 +112,9 @@ function TimeSeriesPanel(props: PanelWrapperProps): JSX.Element {
legendConfig={{
position: widget?.legendPosition ?? LegendPosition.BOTTOM,
}}
timezone={timezone}
yAxisUnit={widget.yAxisUnit}
decimalPrecision={widget.decimalPrecision}
timezone={timezone}
data={chartData as uPlot.AlignedData}
width={containerDimensions.width}
height={containerDimensions.height}

View File

@@ -1,5 +1,5 @@
import { Button } from '@signozhq/button';
import { ArrowLeft, Mail } from '@signozhq/icons';
import { Button } from '@signozhq/ui';
interface SuccessScreenProps {
onBackToLogin: () => void;
@@ -28,7 +28,7 @@ function SuccessScreen({ onBackToLogin }: SuccessScreenProps): JSX.Element {
data-testid="back-to-login"
className="login-submit-btn"
onClick={onBackToLogin}
prefix={<ArrowLeft size={12} />}
prefixIcon={<ArrowLeft size={12} />}
>
Back to login
</Button>

View File

@@ -1,6 +1,7 @@
import { useCallback, useEffect, useMemo } from 'react';
import { Button } from '@signozhq/button';
import { ArrowLeft, ArrowRight } from '@signozhq/icons';
import { Button, Input } from '@signozhq/ui';
import { Input } from '@signozhq/input';
import { Form, Select } from 'antd';
import { ErrorResponseHandlerForGeneratedAPIs } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import { useForgotPassword } from 'api/generated/services/users';
@@ -190,7 +191,7 @@ function ForgotPassword({
data-testid="forgot-password-back"
className="forgot-password-back-button"
onClick={handleBackToLogin}
prefix={<ArrowLeft size={12} />}
prefixIcon={<ArrowLeft size={12} />}
>
Back to login
</Button>
@@ -203,7 +204,7 @@ function ForgotPassword({
type="submit"
data-testid="forgot-password-submit"
className="login-submit-btn"
suffix={<ArrowRight size={12} />}
suffixIcon={<ArrowRight size={12} />}
>
{isLoading ? 'Sending...' : 'Send reset link'}
</Button>

View File

@@ -3,8 +3,8 @@ import { useTranslation } from 'react-i18next';
import { UseQueryResult } from 'react-query';
import { useInterval } from 'react-use';
import { LoadingOutlined } from '@ant-design/icons';
import { Button } from '@signozhq/button';
import { Compass, ScrollText } from '@signozhq/icons';
import { Button } from '@signozhq/ui';
import { Modal, Spin } from 'antd';
import setRetentionApi from 'api/settings/setRetention';
import setRetentionApiV2 from 'api/settings/setRetentionV2';

View File

@@ -1,6 +1,7 @@
import { useCopyToClipboard } from 'react-use';
import { Button } from '@signozhq/button';
import { Copy, KeyRound } from '@signozhq/icons';
import { Button, toast } from '@signozhq/ui';
import { toast } from '@signozhq/ui';
import { useAppContext } from 'providers/App/App';
import { getMaskedKey } from 'utils/maskedKey';
@@ -16,7 +17,7 @@ function LicenseKeyRow(): JSX.Element | null {
const handleCopyLicenseKey = (text: string): void => {
copyToClipboard(text);
toast.success('License key copied to clipboard.');
toast.success('License key copied to clipboard.', { richColors: true });
};
return (
@@ -31,7 +32,7 @@ function LicenseKeyRow(): JSX.Element | null {
</code>
<Button
type="button"
size="sm"
size="xs"
aria-label="Copy license key"
data-testid="license-key-row-copy-btn"
className="license-key-row__copy-btn"

View File

@@ -1,6 +1,6 @@
import { useState } from 'react';
import { Link } from 'react-router-dom';
import { Callout } from '@signozhq/ui';
import { Callout } from '@signozhq/callout';
import getLocalStorageApi from 'api/browser/localstorage/get';
import setLocalStorageApi from 'api/browser/localstorage/set';
import { FeatureKeys } from 'constants/features';
@@ -44,38 +44,39 @@ function LicenseRowDismissibleCallout(): JSX.Element | null {
type="info"
size="small"
showIcon
action="dismissible"
onClick={handleDismissCallout}
dismissable
onClose={handleDismissCallout}
className="license-key-callout"
>
<div className="license-key-callout__description">
This is <strong>NOT</strong> your ingestion or Service account key.
{(hasServiceAccountsAccess || hasIngestionAccess) && (
<>
{' '}
Find your{' '}
{hasServiceAccountsAccess && (
<Link
to={ROUTES.SERVICE_ACCOUNTS_SETTINGS}
className="license-key-callout__link"
>
Service account here
</Link>
)}
{hasServiceAccountsAccess && hasIngestionAccess && ' and '}
{hasIngestionAccess && (
<Link
to={ROUTES.INGESTION_SETTINGS}
className="license-key-callout__link"
>
Ingestion key here
</Link>
)}
.
</>
)}
</div>
</Callout>
description={
<div className="license-key-callout__description">
This is <strong>NOT</strong> your ingestion or Service account key.
{(hasServiceAccountsAccess || hasIngestionAccess) && (
<>
{' '}
Find your{' '}
{hasServiceAccountsAccess && (
<Link
to={ROUTES.SERVICE_ACCOUNTS_SETTINGS}
className="license-key-callout__link"
>
Service account here
</Link>
)}
{hasServiceAccountsAccess && hasIngestionAccess && ' and '}
{hasIngestionAccess && (
<Link
to={ROUTES.INGESTION_SETTINGS}
className="license-key-callout__link"
>
Ingestion key here
</Link>
)}
.
</>
)}
</div>
}
/>
) : null;
}

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