mirror of
https://github.com/SigNoz/signoz.git
synced 2026-05-22 18:00:25 +01:00
Compare commits
2 Commits
chore/migr
...
chore/migr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0004c4b402 | ||
|
|
94621e41d3 |
@@ -15,6 +15,7 @@
|
||||
|
||||
const BANNED_COMPONENTS = {
|
||||
Typography: 'Use @signozhq/ui Typography instead of antd Typography.',
|
||||
InputNumber: 'Use components/InputNumber instead of antd InputNumber.',
|
||||
};
|
||||
|
||||
export default {
|
||||
@@ -49,7 +50,7 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
const pathComponent = match[1].toLowerCase();
|
||||
const pathComponent = match[1].toLowerCase().replace(/-/g, '');
|
||||
|
||||
for (const [componentName, message] of Object.entries(BANNED_COMPONENTS)) {
|
||||
if (pathComponent === componentName.toLowerCase()) {
|
||||
|
||||
209
frontend/src/components/InputNumber/InputNumber.tsx
Normal file
209
frontend/src/components/InputNumber/InputNumber.tsx
Normal file
@@ -0,0 +1,209 @@
|
||||
import {
|
||||
ChangeEvent,
|
||||
CSSProperties,
|
||||
FocusEvent,
|
||||
FocusEventHandler,
|
||||
forwardRef,
|
||||
KeyboardEventHandler,
|
||||
ReactNode,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import cx from 'classnames';
|
||||
|
||||
export type InputNumberProps = {
|
||||
value?: number | null;
|
||||
defaultValue?: number | null;
|
||||
onChange?: (value: number | null) => void;
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
/**
|
||||
* Number of decimal places to display and round to on blur. Mirrors antd
|
||||
* InputNumber's `precision`: while focused the user can type freely, and on
|
||||
* blur the value is rounded and rendered with trailing zeros (e.g.
|
||||
* precision=2 → "1.50").
|
||||
*/
|
||||
precision?: number;
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
prefix?: ReactNode;
|
||||
suffix?: ReactNode;
|
||||
className?: string;
|
||||
rootClassName?: string;
|
||||
style?: CSSProperties;
|
||||
id?: string;
|
||||
name?: string;
|
||||
testId?: string;
|
||||
autoFocus?: boolean;
|
||||
onKeyDown?: KeyboardEventHandler<HTMLInputElement>;
|
||||
onBlur?: FocusEventHandler<HTMLInputElement>;
|
||||
onFocus?: FocusEventHandler<HTMLInputElement>;
|
||||
'aria-label'?: string;
|
||||
'data-testid'?: string;
|
||||
};
|
||||
|
||||
// Permits the in-progress shapes a user types while building a number:
|
||||
// "", "-", "1", "1.", "1.5", ".5", "-1.5"
|
||||
const NUMERIC_TOKEN_REGEX = /^-?(\d+\.?\d*|\.\d*)?$/;
|
||||
|
||||
const formatForDisplay = (
|
||||
value: number | null | undefined,
|
||||
precision?: number,
|
||||
): string => {
|
||||
if (value === null || value === undefined || Number.isNaN(value)) {
|
||||
return '';
|
||||
}
|
||||
if (precision === undefined) {
|
||||
return String(value);
|
||||
}
|
||||
return value.toFixed(precision);
|
||||
};
|
||||
|
||||
const parseRaw = (raw: string): number | null => {
|
||||
if (raw === '' || raw === '-' || raw === '.' || raw === '-.') {
|
||||
return null;
|
||||
}
|
||||
const parsed = Number(raw);
|
||||
return Number.isNaN(parsed) ? null : parsed;
|
||||
};
|
||||
|
||||
const clamp = (value: number, min?: number, max?: number): number => {
|
||||
let next = value;
|
||||
if (min !== undefined && next < min) {
|
||||
next = min;
|
||||
}
|
||||
if (max !== undefined && next > max) {
|
||||
next = max;
|
||||
}
|
||||
return next;
|
||||
};
|
||||
|
||||
const InputNumber = forwardRef<HTMLInputElement, InputNumberProps>(
|
||||
(
|
||||
{
|
||||
value,
|
||||
defaultValue,
|
||||
onChange,
|
||||
min,
|
||||
max,
|
||||
step,
|
||||
precision,
|
||||
placeholder,
|
||||
disabled,
|
||||
prefix,
|
||||
suffix,
|
||||
className,
|
||||
rootClassName,
|
||||
style,
|
||||
id,
|
||||
name,
|
||||
testId,
|
||||
autoFocus,
|
||||
onKeyDown,
|
||||
onBlur,
|
||||
onFocus,
|
||||
'aria-label': ariaLabel,
|
||||
'data-testid': dataTestId,
|
||||
},
|
||||
ref,
|
||||
): JSX.Element => {
|
||||
const isControlled = value !== undefined;
|
||||
const isFocusedRef = useRef(false);
|
||||
const [displayValue, setDisplayValue] = useState<string>(() =>
|
||||
formatForDisplay(isControlled ? value : defaultValue, precision),
|
||||
);
|
||||
|
||||
// Sync display from the controlled value when the user isn't actively
|
||||
// typing, so external state changes (and precision changes) propagate.
|
||||
useEffect(() => {
|
||||
if (!isControlled || isFocusedRef.current) {
|
||||
return;
|
||||
}
|
||||
setDisplayValue(formatForDisplay(value, precision));
|
||||
}, [isControlled, value, precision]);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(event: ChangeEvent<HTMLInputElement>): void => {
|
||||
const raw = event.target.value;
|
||||
if (raw !== '' && !NUMERIC_TOKEN_REGEX.test(raw)) {
|
||||
return;
|
||||
}
|
||||
setDisplayValue(raw);
|
||||
onChange?.(parseRaw(raw));
|
||||
},
|
||||
[onChange],
|
||||
);
|
||||
|
||||
const handleFocus = useCallback(
|
||||
(event: FocusEvent<HTMLInputElement>): void => {
|
||||
isFocusedRef.current = true;
|
||||
onFocus?.(event);
|
||||
},
|
||||
[onFocus],
|
||||
);
|
||||
|
||||
const handleBlur = useCallback(
|
||||
(event: FocusEvent<HTMLInputElement>): void => {
|
||||
isFocusedRef.current = false;
|
||||
const parsed = parseRaw(displayValue);
|
||||
if (parsed === null) {
|
||||
if (displayValue !== '') {
|
||||
setDisplayValue('');
|
||||
onChange?.(null);
|
||||
}
|
||||
} else {
|
||||
const clamped = clamp(parsed, min, max);
|
||||
const finalValue =
|
||||
precision === undefined
|
||||
? clamped
|
||||
: Math.round(clamped * 10 ** precision) / 10 ** precision;
|
||||
const nextDisplay = formatForDisplay(finalValue, precision);
|
||||
if (nextDisplay !== displayValue) {
|
||||
setDisplayValue(nextDisplay);
|
||||
}
|
||||
if (finalValue !== parsed) {
|
||||
onChange?.(finalValue);
|
||||
}
|
||||
}
|
||||
onBlur?.(event);
|
||||
},
|
||||
[displayValue, min, max, precision, onChange, onBlur],
|
||||
);
|
||||
|
||||
return (
|
||||
<Input
|
||||
ref={ref}
|
||||
type="text"
|
||||
inputMode="decimal"
|
||||
value={displayValue}
|
||||
onChange={handleChange}
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
min={min}
|
||||
max={max}
|
||||
step={step}
|
||||
placeholder={placeholder}
|
||||
disabled={disabled}
|
||||
prefix={prefix}
|
||||
suffix={suffix}
|
||||
className={cx('signoz-input-number', className)}
|
||||
containerClassName={cx('signoz-input-number-container', rootClassName)}
|
||||
style={style}
|
||||
id={id}
|
||||
name={name}
|
||||
testId={testId ?? dataTestId}
|
||||
autoFocus={autoFocus}
|
||||
onKeyDown={onKeyDown}
|
||||
aria-label={ariaLabel}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
InputNumber.displayName = 'InputNumber';
|
||||
|
||||
export default InputNumber;
|
||||
2
frontend/src/components/InputNumber/index.ts
Normal file
2
frontend/src/components/InputNumber/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default } from './InputNumber';
|
||||
export type { InputNumberProps } from './InputNumber';
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { Button, Input, InputNumber, Popover, Tooltip } from 'antd';
|
||||
import { Button, Input, Popover, Tooltip } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import type { DefaultOptionType } from 'antd/es/select';
|
||||
import cx from 'classnames';
|
||||
import InputNumber from 'components/InputNumber';
|
||||
import { LogViewMode } from 'container/LogsTable';
|
||||
import { FontSize, OptionsMenuConfig } from 'container/OptionsMenu/types';
|
||||
import useDebouncedFn from 'hooks/useDebouncedFunction';
|
||||
|
||||
@@ -3,14 +3,13 @@ import {
|
||||
Checkbox,
|
||||
Collapse,
|
||||
Form,
|
||||
InputNumber,
|
||||
InputNumberProps,
|
||||
Select,
|
||||
SelectProps,
|
||||
Space,
|
||||
} from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import type { DefaultOptionType } from 'antd/es/select';
|
||||
import InputNumber from 'components/InputNumber';
|
||||
import {
|
||||
getCategoryByOptionId,
|
||||
getCategorySelectOptionByName,
|
||||
@@ -289,7 +288,7 @@ function RuleOptions({
|
||||
</Form.Item>
|
||||
);
|
||||
|
||||
const onChange: InputNumberProps['onChange'] = (value): void => {
|
||||
const onChange = (value: number | null): void => {
|
||||
setAlertDef({
|
||||
...alertDef,
|
||||
condition: {
|
||||
@@ -391,11 +390,9 @@ function RuleOptions({
|
||||
<Space direction="horizontal" align="center">
|
||||
<Form.Item noStyle>
|
||||
<InputNumber
|
||||
addonBefore={t('field_threshold')}
|
||||
prefix={t('field_threshold')}
|
||||
value={alertDef?.condition?.target}
|
||||
onChange={onChange}
|
||||
type="number"
|
||||
onWheel={(e): void => e.currentTarget.blur()}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
@@ -455,8 +452,6 @@ function RuleOptions({
|
||||
},
|
||||
});
|
||||
}}
|
||||
type="number"
|
||||
onWheel={(e): void => e.currentTarget.blur()}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Typography.Text>{t('text_for')}</Typography.Text>
|
||||
@@ -494,8 +489,6 @@ function RuleOptions({
|
||||
},
|
||||
});
|
||||
}}
|
||||
type="number"
|
||||
onWheel={(e): void => e.currentTarget.blur()}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Typography.Text>{t('text_num_points')}</Typography.Text>
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
DatePicker,
|
||||
Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
Modal,
|
||||
Row,
|
||||
Select,
|
||||
@@ -23,6 +22,7 @@ import {
|
||||
Tooltip,
|
||||
} from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import InputNumber from 'components/InputNumber';
|
||||
import type { NotificationInstance } from 'antd/es/notification/interface';
|
||||
import type { CollapseProps } from 'antd/lib';
|
||||
import {
|
||||
@@ -1212,7 +1212,7 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
<Form.Item name="dailyLimit" key="dailyLimit">
|
||||
<InputNumber
|
||||
disabled={!activeSignal?.config?.day?.enabled}
|
||||
addonAfter={
|
||||
suffix={
|
||||
<Select defaultValue="GiB" disabled>
|
||||
<Option value="TiB">TiB</Option>
|
||||
<Option value="GiB">GiB</Option>
|
||||
@@ -1235,7 +1235,7 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
<Form.Item name="dailyCount" key="dailyCount">
|
||||
<InputNumber
|
||||
placeholder="Enter max # of samples/day"
|
||||
addonAfter={
|
||||
suffix={
|
||||
<Form.Item
|
||||
name="dailyCountUnit"
|
||||
noStyle
|
||||
@@ -1302,7 +1302,7 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
<Form.Item name="secondsLimit" key="secondsLimit">
|
||||
<InputNumber
|
||||
disabled={!activeSignal?.config?.second?.enabled}
|
||||
addonAfter={
|
||||
suffix={
|
||||
<Select defaultValue="GiB" disabled>
|
||||
<Option value="TiB">TiB</Option>
|
||||
<Option value="GiB">GiB</Option>
|
||||
@@ -1325,7 +1325,7 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
<Form.Item name="secondsCount" key="secondsCount">
|
||||
<InputNumber
|
||||
placeholder="Enter max # of samples/s"
|
||||
addonAfter={
|
||||
suffix={
|
||||
<Form.Item
|
||||
name="secondsCountUnit"
|
||||
noStyle
|
||||
|
||||
@@ -1,17 +1,5 @@
|
||||
.new-explorer-cta {
|
||||
display: flex;
|
||||
.new-explorer-cta-with-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: var(--muted-foreground);
|
||||
|
||||
/* Bifrost (Ancient)/Content/sm */
|
||||
font-family: Inter;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 20px; /* 142.857% */
|
||||
letter-spacing: -0.07px;
|
||||
|
||||
.ant-btn-icon {
|
||||
margin-inline-end: 0px;
|
||||
}
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import ROUTES from 'constants/routes';
|
||||
|
||||
export const RIBBON_STYLES = {
|
||||
top: '-0.75rem',
|
||||
};
|
||||
|
||||
export const buttonText: Record<string, string> = {
|
||||
[ROUTES.LOGS_EXPLORER]: 'Old Explorer',
|
||||
[ROUTES.TRACE]: 'New Explorer',
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { Badge, Button } from 'antd';
|
||||
import { Button } from 'antd';
|
||||
import { Badge } from '@signozhq/ui/badge';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||
import { Undo } from '@signozhq/icons';
|
||||
import { isModifierKeyPressed } from 'utils/app';
|
||||
|
||||
import { buttonText, RIBBON_STYLES } from './config';
|
||||
import { buttonText } from './config';
|
||||
|
||||
import './NewExplorerCTA.styles.scss';
|
||||
|
||||
@@ -70,9 +71,12 @@ function NewExplorerCTA(): JSX.Element | null {
|
||||
}
|
||||
|
||||
return (
|
||||
<Badge.Ribbon style={RIBBON_STYLES} text="New">
|
||||
<span className="new-explorer-cta-with-badge">
|
||||
{button}
|
||||
</Badge.Ribbon>
|
||||
<Badge color="robin" variant="default">
|
||||
New
|
||||
</Badge>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { InputNumber, Select } from 'antd';
|
||||
import { Select } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { Axis3D, ChartLine, Spline } from '@signozhq/icons';
|
||||
import InputNumber from 'components/InputNumber';
|
||||
|
||||
import SettingsSection from '../../components/SettingsSection/SettingsSection';
|
||||
|
||||
@@ -48,7 +49,6 @@ export default function AxesSection({
|
||||
<section className="container">
|
||||
<Typography.Text className="text">Soft Min</Typography.Text>
|
||||
<InputNumber
|
||||
type="number"
|
||||
value={softMin}
|
||||
onChange={softMinHandler}
|
||||
rootClassName="input"
|
||||
@@ -58,7 +58,6 @@ export default function AxesSection({
|
||||
<Typography.Text className="text">Soft Max</Typography.Text>
|
||||
<InputNumber
|
||||
value={softMax}
|
||||
type="number"
|
||||
rootClassName="input"
|
||||
onChange={softMaxHandler}
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { InputNumber, Switch } from 'antd';
|
||||
import { Switch } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import InputNumber from 'components/InputNumber';
|
||||
|
||||
import SettingsSection from '../../components/SettingsSection/SettingsSection';
|
||||
|
||||
@@ -31,7 +32,6 @@ export default function HistogramBucketsSection({
|
||||
</Typography.Text>
|
||||
<InputNumber
|
||||
value={bucketCount || null}
|
||||
type="number"
|
||||
min={0}
|
||||
rootClassName="bucket-input"
|
||||
placeholder="Default: 30"
|
||||
@@ -44,7 +44,6 @@ export default function HistogramBucketsSection({
|
||||
</Typography.Text>
|
||||
<InputNumber
|
||||
value={bucketWidth || null}
|
||||
type="number"
|
||||
precision={2}
|
||||
placeholder="Default: Auto"
|
||||
step={0.1}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/* eslint-disable sonarjs/cognitive-complexity */
|
||||
import { useMemo, useRef, useState } from 'react';
|
||||
import { useDrag, useDrop, XYCoord } from 'react-dnd';
|
||||
import { Button, Input, InputNumber, Select, Space } from 'antd';
|
||||
import { Button, Input, Select, Space } from 'antd';
|
||||
import InputNumber from 'components/InputNumber';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import YAxisUnitSelector from 'components/YAxisUnitSelector';
|
||||
import { Y_AXIS_UNIT_NAMES } from 'components/YAxisUnitSelector/constants';
|
||||
|
||||
@@ -14,12 +14,7 @@ function MaxLinesField({ config }: MaxLinesFieldProps): JSX.Element | null {
|
||||
return (
|
||||
<MaxLinesFieldWrapper>
|
||||
<FieldTitle>{t('options_menu.maxLines')}</FieldTitle>
|
||||
<MaxLinesInput
|
||||
controls
|
||||
size="small"
|
||||
value={config.value}
|
||||
onChange={config.onChange}
|
||||
/>
|
||||
<MaxLinesInput value={config.value} onChange={config.onChange} />
|
||||
</MaxLinesFieldWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { InputNumber } from 'antd';
|
||||
import InputNumber from 'components/InputNumber';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const MaxLinesFieldWrapper = styled.div`
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { InputNumberProps, RadioProps, SelectProps } from 'antd';
|
||||
import { RadioProps, SelectProps } from 'antd';
|
||||
import { TelemetryFieldKey } from 'api/v5/v5';
|
||||
import type { InputNumberProps } from 'components/InputNumber';
|
||||
import { LogViewMode } from 'container/LogsTable';
|
||||
|
||||
export enum FontSize {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useMemo } from 'react';
|
||||
import { InputNumber, InputNumberProps } from 'antd';
|
||||
import InputNumber from 'components/InputNumber';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
@@ -15,9 +15,9 @@ function AggregateEveryFilter({
|
||||
[query.dataSource],
|
||||
);
|
||||
|
||||
const onChangeHandler: InputNumberProps<number>['onChange'] = (event) => {
|
||||
if (event && event >= 0) {
|
||||
onChange(event);
|
||||
const onChangeHandler = (value: number | null): void => {
|
||||
if (value !== null && value >= 0) {
|
||||
onChange(value);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { InputNumber } from 'antd';
|
||||
import InputNumber from 'components/InputNumber';
|
||||
|
||||
import { selectStyle } from '../../QueryBuilderSearch/config';
|
||||
import { handleKeyDownLimitFilter } from '../../utils';
|
||||
@@ -8,7 +8,6 @@ function LimitFilter({ onChange, formula }: LimitFilterProps): JSX.Element {
|
||||
return (
|
||||
<InputNumber
|
||||
min={1}
|
||||
type="number"
|
||||
value={formula.limit}
|
||||
style={selectStyle}
|
||||
onChange={onChange}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { InputNumber } from 'antd';
|
||||
import InputNumber from 'components/InputNumber';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
@@ -13,7 +13,6 @@ function LimitFilter({ onChange, query }: LimitFilterProps): JSX.Element {
|
||||
return (
|
||||
<InputNumber
|
||||
min={1}
|
||||
type="number"
|
||||
value={query.limit}
|
||||
style={selectStyle}
|
||||
disabled={isDisabled}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { InputNumber, Row, Space } from 'antd';
|
||||
import { Row, Space } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import InputNumber from 'components/InputNumber';
|
||||
|
||||
interface PopoverContentProps {
|
||||
linesPerRow: number;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState } from 'react';
|
||||
import { X } from '@signozhq/icons';
|
||||
import { Card, InputNumber } from 'antd';
|
||||
import { Card } from 'antd';
|
||||
import InputNumber from 'components/InputNumber';
|
||||
import Spinner from 'components/Spinner';
|
||||
import TextToolTip from 'components/TextToolTip';
|
||||
import {
|
||||
|
||||
Reference in New Issue
Block a user