chore: add count based limits for metrics (#6738)

This commit is contained in:
Srikanth Chekuri 2025-01-16 18:29:53 +05:30 committed by GitHub
parent bab8c8274c
commit 92299e1b08
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 439 additions and 220 deletions

View File

@ -58,7 +58,11 @@ import { useTranslation } from 'react-i18next';
import { useMutation } from 'react-query'; import { useMutation } from 'react-query';
import { useCopyToClipboard } from 'react-use'; import { useCopyToClipboard } from 'react-use';
import { ErrorResponse } from 'types/api'; import { ErrorResponse } from 'types/api';
import { LimitProps } from 'types/api/ingestionKeys/limits/types'; import {
AddLimitProps,
LimitProps,
UpdateLimitProps,
} from 'types/api/ingestionKeys/limits/types';
import { import {
IngestionKeyProps, IngestionKeyProps,
PaginationProps, PaginationProps,
@ -69,6 +73,18 @@ const { Option } = Select;
const BYTES = 1073741824; const BYTES = 1073741824;
const COUNT_MULTIPLIER = {
thousand: 1000,
million: 1000000,
billion: 1000000000,
};
const SIGNALS_CONFIG = [
{ name: 'logs', usesSize: true, usesCount: false },
{ name: 'traces', usesSize: true, usesCount: false },
{ name: 'metrics', usesSize: false, usesCount: true },
];
// Using any type here because antd's DatePicker expects its own internal Dayjs type // Using any type here because antd's DatePicker expects its own internal Dayjs type
// which conflicts with our project's Dayjs type that has additional plugins (tz, utc etc). // which conflicts with our project's Dayjs type that has additional plugins (tz, utc etc).
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
@ -76,8 +92,6 @@ export const disabledDate = (current: any): boolean =>
// Disable all dates before today // Disable all dates before today
current && current < dayjs().endOf('day'); current && current < dayjs().endOf('day');
const SIGNALS = ['logs', 'traces', 'metrics'];
export const showErrorNotification = ( export const showErrorNotification = (
notifications: NotificationInstance, notifications: NotificationInstance,
err: Error, err: Error,
@ -101,6 +115,31 @@ export const API_KEY_EXPIRY_OPTIONS: ExpiryOption[] = [
{ value: '0', label: 'No Expiry' }, { value: '0', label: 'No Expiry' },
]; ];
const countToUnit = (count: number): { value: number; unit: string } => {
if (
count >= COUNT_MULTIPLIER.billion ||
count / COUNT_MULTIPLIER.million >= 1000
) {
return { value: count / COUNT_MULTIPLIER.billion, unit: 'billion' };
}
if (
count >= COUNT_MULTIPLIER.million ||
count / COUNT_MULTIPLIER.thousand >= 1000
) {
return { value: count / COUNT_MULTIPLIER.million, unit: 'million' };
}
if (count >= COUNT_MULTIPLIER.thousand) {
return { value: count / COUNT_MULTIPLIER.thousand, unit: 'thousand' };
}
// Default to million for small numbers
return { value: count / COUNT_MULTIPLIER.million, unit: 'million' };
};
const countFromUnit = (value: number, unit: string): number =>
value *
(COUNT_MULTIPLIER[unit as keyof typeof COUNT_MULTIPLIER] ||
COUNT_MULTIPLIER.million);
function MultiIngestionSettings(): JSX.Element { function MultiIngestionSettings(): JSX.Element {
const { user } = useAppContext(); const { user } = useAppContext();
const { notifications } = useNotifications(); const { notifications } = useNotifications();
@ -181,7 +220,6 @@ function MultiIngestionSettings(): JSX.Element {
const showEditModal = (apiKey: IngestionKeyProps): void => { const showEditModal = (apiKey: IngestionKeyProps): void => {
setActiveAPIKey(apiKey); setActiveAPIKey(apiKey);
handleFormReset(); handleFormReset();
setUpdatedTags(apiKey.tags || []); setUpdatedTags(apiKey.tags || []);
@ -424,44 +462,90 @@ function MultiIngestionSettings(): JSX.Element {
addEditLimitForm.resetFields(); addEditLimitForm.resetFields();
}; };
/* eslint-disable sonarjs/cognitive-complexity */
const handleAddLimit = ( const handleAddLimit = (
APIKey: IngestionKeyProps, APIKey: IngestionKeyProps,
signalName: string, signalName: string,
): void => { ): void => {
const { dailyLimit, secondsLimit } = addEditLimitForm.getFieldsValue(); const {
dailyLimit,
secondsLimit,
dailyCount,
dailyCountUnit,
secondsCount,
secondsCountUnit,
} = addEditLimitForm.getFieldsValue();
const payload = { const payload: AddLimitProps = {
keyID: APIKey.id, keyID: APIKey.id,
signal: signalName, signal: signalName,
config: {}, config: {},
}; };
if (!isUndefined(dailyLimit)) { const signalCfg = SIGNALS_CONFIG.find((cfg) => cfg.name === signalName);
payload.config = { if (!signalCfg) return;
day: {
// Only set size if usesSize is true
if (signalCfg.usesSize) {
if (!isUndefined(dailyLimit)) {
payload.config.day = {
...payload.config.day,
size: gbToBytes(dailyLimit), size: gbToBytes(dailyLimit),
}, };
}; }
} if (!isUndefined(secondsLimit)) {
payload.config.second = {
if (!isUndefined(secondsLimit)) { ...payload.config.second,
payload.config = {
...payload.config,
second: {
size: gbToBytes(secondsLimit), size: gbToBytes(secondsLimit),
}, };
}; }
} }
if (isUndefined(dailyLimit) && isUndefined(secondsLimit)) { // Only set count if usesCount is true
// No need to save as no limit is provided, close the edit view and reset active signal and api key if (signalCfg.usesCount) {
if (!isUndefined(dailyCount)) {
payload.config.day = {
...payload.config.day,
count: countFromUnit(dailyCount, dailyCountUnit || 'million'),
};
}
if (!isUndefined(secondsCount)) {
payload.config.second = {
...payload.config.second,
count: countFromUnit(secondsCount, secondsCountUnit || 'million'),
};
}
}
// If neither size nor count was given, skip
const noSizeProvided =
isUndefined(dailyLimit) && isUndefined(secondsLimit) && signalCfg.usesSize;
const noCountProvided =
isUndefined(dailyCount) && isUndefined(secondsCount) && signalCfg.usesCount;
if (
signalCfg.usesSize &&
signalCfg.usesCount &&
noSizeProvided &&
noCountProvided
) {
// Both size and count are effectively empty
setActiveSignal(null); setActiveSignal(null);
setActiveAPIKey(null); setActiveAPIKey(null);
setIsEditAddLimitOpen(false); setIsEditAddLimitOpen(false);
setUpdatedTags([]); setUpdatedTags([]);
hideAddViewModal(); hideAddViewModal();
setHasCreateLimitForIngestionKeyError(false); setHasCreateLimitForIngestionKeyError(false);
return;
}
if (!signalCfg.usesSize && !signalCfg.usesCount) {
// Edge case: If there's no count or size usage at all
setActiveSignal(null);
setActiveAPIKey(null);
setIsEditAddLimitOpen(false);
setUpdatedTags([]);
hideAddViewModal();
return; return;
} }
@ -472,44 +556,73 @@ function MultiIngestionSettings(): JSX.Element {
APIKey: IngestionKeyProps, APIKey: IngestionKeyProps,
signal: LimitProps, signal: LimitProps,
): void => { ): void => {
const { dailyLimit, secondsLimit } = addEditLimitForm.getFieldsValue(); const {
const payload = { dailyLimit,
secondsLimit,
dailyCount,
dailyCountUnit,
secondsCount,
secondsCountUnit,
} = addEditLimitForm.getFieldsValue();
const payload: UpdateLimitProps = {
limitID: signal.id, limitID: signal.id,
signal: signal.signal, signal: signal.signal,
config: {}, config: {},
}; };
if (isUndefined(dailyLimit) && isUndefined(secondsLimit)) { const signalCfg = SIGNALS_CONFIG.find((cfg) => cfg.name === signal.signal);
showDeleteLimitModal(APIKey, signal); if (!signalCfg) return;
const noSizeProvided =
isUndefined(dailyLimit) && isUndefined(secondsLimit) && signalCfg.usesSize;
const noCountProvided =
isUndefined(dailyCount) && isUndefined(secondsCount) && signalCfg.usesCount;
// If the user cleared out all fields, remove the limit
if (noSizeProvided && noCountProvided) {
showDeleteLimitModal(APIKey, signal);
return; return;
} }
if (!isUndefined(dailyLimit)) { if (signalCfg.usesSize) {
payload.config = { if (!isUndefined(dailyLimit)) {
day: { payload.config.day = {
...payload.config.day,
size: gbToBytes(dailyLimit), size: gbToBytes(dailyLimit),
}, };
}; }
if (!isUndefined(secondsLimit)) {
payload.config.second = {
...payload.config.second,
size: gbToBytes(secondsLimit),
};
}
} }
if (!isUndefined(secondsLimit)) { if (signalCfg.usesCount) {
payload.config = { if (!isUndefined(dailyCount)) {
...payload.config, payload.config.day = {
second: { ...payload.config.day,
size: gbToBytes(secondsLimit), count: countFromUnit(dailyCount, dailyCountUnit || 'million'),
}, };
}; }
if (!isUndefined(secondsCount)) {
payload.config.second = {
...payload.config.second,
count: countFromUnit(secondsCount, secondsCountUnit || 'million'),
};
}
} }
updateLimitForIngestionKey(payload); updateLimitForIngestionKey(payload);
}; };
/* eslint-enable sonarjs/cognitive-complexity */
const bytesToGb = (size: number | undefined): number => { const bytesToGb = (size: number | undefined): number => {
if (!size) { if (!size) {
return 0; return 0;
} }
return size / BYTES; return size / BYTES;
}; };
@ -517,6 +630,12 @@ function MultiIngestionSettings(): JSX.Element {
APIKey: IngestionKeyProps, APIKey: IngestionKeyProps,
signal: LimitProps, signal: LimitProps,
): void => { ): void => {
const dayCount = signal?.config?.day?.count;
const secondCount = signal?.config?.second?.count;
const dayCountConverted = countToUnit(dayCount || 0);
const secondCountConverted = countToUnit(secondCount || 0);
setActiveAPIKey(APIKey); setActiveAPIKey(APIKey);
setActiveSignal({ setActiveSignal({
...signal, ...signal,
@ -524,11 +643,14 @@ function MultiIngestionSettings(): JSX.Element {
...signal.config, ...signal.config,
day: { day: {
...signal.config?.day, ...signal.config?.day,
enabled: !isNil(signal?.config?.day?.size), enabled:
!isNil(signal?.config?.day?.size) || !isNil(signal?.config?.day?.count),
}, },
second: { second: {
...signal.config?.second, ...signal.config?.second,
enabled: !isNil(signal?.config?.second?.size), enabled:
!isNil(signal?.config?.second?.size) ||
!isNil(signal?.config?.second?.count),
}, },
}, },
}); });
@ -536,15 +658,22 @@ function MultiIngestionSettings(): JSX.Element {
addEditLimitForm.setFieldsValue({ addEditLimitForm.setFieldsValue({
dailyLimit: bytesToGb(signal?.config?.day?.size || 0), dailyLimit: bytesToGb(signal?.config?.day?.size || 0),
secondsLimit: bytesToGb(signal?.config?.second?.size || 0), secondsLimit: bytesToGb(signal?.config?.second?.size || 0),
enableDailyLimit: !isNil(signal?.config?.day?.size), enableDailyLimit:
enableSecondLimit: !isNil(signal?.config?.second?.size), !isNil(signal?.config?.day?.size) || !isNil(signal?.config?.day?.count),
enableSecondLimit:
!isNil(signal?.config?.second?.size) ||
!isNil(signal?.config?.second?.count),
dailyCount: dayCountConverted.value,
dailyCountUnit: dayCountConverted.unit,
secondsCount: secondCountConverted.value,
secondsCountUnit: secondCountConverted.unit,
}); });
setIsEditAddLimitOpen(true); setIsEditAddLimitOpen(true);
}; };
const onDeleteLimitHandler = (): void => { const onDeleteLimitHandler = (): void => {
if (activeSignal && activeSignal?.id) { if (activeSignal && activeSignal.id) {
deleteLimitForKey(activeSignal.id); deleteLimitForKey(activeSignal.id);
} }
}; };
@ -572,13 +701,13 @@ function MultiIngestionSettings(): JSX.Element {
formatTimezoneAdjustedTimestamp, formatTimezoneAdjustedTimestamp,
); );
const limits: { [key: string]: LimitProps } = {}; // Convert array of limits to a dictionary for quick access
const limitsDict: Record<string, LimitProps> = {};
APIKey.limits?.forEach((limit: LimitProps) => { APIKey.limits?.forEach((limitItem: LimitProps) => {
limits[limit.signal] = limit; limitsDict[limitItem.signal] = limitItem;
}); });
const hasLimits = (signal: string): boolean => !!limits[signal]; const hasLimits = (signalName: string): boolean => !!limitsDict[signalName];
const items: CollapseProps['items'] = [ const items: CollapseProps['items'] = [
{ {
@ -614,11 +743,9 @@ function MultiIngestionSettings(): JSX.Element {
onClick={(e): void => { onClick={(e): void => {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
showEditModal(APIKey); showEditModal(APIKey);
}} }}
/> />
<Button <Button
className="periscope-btn ghost" className="periscope-btn ghost"
icon={<Trash2 color={Color.BG_CHERRY_500} size={14} />} icon={<Trash2 color={Color.BG_CHERRY_500} size={14} />}
@ -670,18 +797,23 @@ function MultiIngestionSettings(): JSX.Element {
<div className="limits-data"> <div className="limits-data">
<div className="signals"> <div className="signals">
{SIGNALS.map((signal) => { {SIGNALS_CONFIG.map((signalCfg) => {
const hasValidDayLimit = !isNil(limits[signal]?.config?.day?.size); const signalName = signalCfg.name;
const hasValidSecondLimit = !isNil( const limit = limitsDict[signalName];
limits[signal]?.config?.second?.size,
); const hasValidDayLimit =
limit?.config?.day?.size !== undefined ||
limit?.config?.day?.count !== undefined;
const hasValidSecondLimit =
limit?.config?.second?.size !== undefined ||
limit?.config?.second?.count !== undefined;
return ( return (
<div className="signal" key={signal}> <div className="signal" key={signalName}>
<div className="header"> <div className="header">
<div className="signal-name">{signal}</div> <div className="signal-name">{signalName}</div>
<div className="actions"> <div className="actions">
{hasLimits(signal) ? ( {hasLimits(signalName) ? (
<> <>
<Button <Button
className="periscope-btn ghost" className="periscope-btn ghost"
@ -690,10 +822,9 @@ function MultiIngestionSettings(): JSX.Element {
onClick={(e): void => { onClick={(e): void => {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
enableEditLimitMode(APIKey, limits[signal]); enableEditLimitMode(APIKey, limit);
}} }}
/> />
<Button <Button
className="periscope-btn ghost" className="periscope-btn ghost"
icon={<Trash2 color={Color.BG_CHERRY_500} size={14} />} icon={<Trash2 color={Color.BG_CHERRY_500} size={14} />}
@ -701,7 +832,7 @@ function MultiIngestionSettings(): JSX.Element {
onClick={(e): void => { onClick={(e): void => {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
showDeleteLimitModal(APIKey, limits[signal]); showDeleteLimitModal(APIKey, limit);
}} }}
/> />
</> </>
@ -712,14 +843,12 @@ function MultiIngestionSettings(): JSX.Element {
shape="round" shape="round"
icon={<PlusIcon size={14} />} icon={<PlusIcon size={14} />}
disabled={!!(activeAPIKey?.id === APIKey.id && activeSignal)} disabled={!!(activeAPIKey?.id === APIKey.id && activeSignal)}
// eslint-disable-next-line sonarjs/no-identical-functions
onClick={(e): void => { onClick={(e): void => {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
enableEditLimitMode(APIKey, { enableEditLimitMode(APIKey, {
id: signal, id: signalName,
signal, signal: signalName,
config: {}, config: {},
}); });
}} }}
@ -732,7 +861,7 @@ function MultiIngestionSettings(): JSX.Element {
<div className="signal-limit-values"> <div className="signal-limit-values">
{activeAPIKey?.id === APIKey.id && {activeAPIKey?.id === APIKey.id &&
activeSignal?.signal === signal && activeSignal?.signal === signalName &&
isEditAddLimitOpen ? ( isEditAddLimitOpen ? (
<Form <Form
name="edit-ingestion-key-limit-form" name="edit-ingestion-key-limit-form"
@ -740,8 +869,8 @@ function MultiIngestionSettings(): JSX.Element {
form={addEditLimitForm} form={addEditLimitForm}
autoComplete="off" autoComplete="off"
initialValues={{ initialValues={{
dailyLimit: bytesToGb(limits[signal]?.config?.day?.size), dailyLimit: bytesToGb(limit?.config?.day?.size || 0),
secondsLimit: bytesToGb(limits[signal]?.config?.second?.size), secondsLimit: bytesToGb(limit?.config?.second?.size || 0),
}} }}
className="edit-ingestion-key-limit-form" className="edit-ingestion-key-limit-form"
> >
@ -756,16 +885,20 @@ function MultiIngestionSettings(): JSX.Element {
size="small" size="small"
checked={activeSignal?.config?.day?.enabled} checked={activeSignal?.config?.day?.enabled}
onChange={(value): void => { onChange={(value): void => {
setActiveSignal({ setActiveSignal((prev) =>
...activeSignal, prev
config: { ? {
...activeSignal.config, ...prev,
day: { config: {
...activeSignal.config?.day, ...prev.config,
enabled: value, day: {
}, ...prev.config?.day,
}, enabled: value,
}); },
},
}
: null,
);
}} }}
/> />
</Form.Item> </Form.Item>
@ -775,50 +908,87 @@ function MultiIngestionSettings(): JSX.Element {
Add a limit for data ingested daily Add a limit for data ingested daily
</div> </div>
</div> </div>
<div className="size"> {signalCfg.usesSize && (
{activeSignal?.config?.day?.enabled ? ( <div className="size">
<Form.Item name="dailyLimit" key="dailyLimit"> {activeSignal?.config?.day?.enabled ? (
<InputNumber <Form.Item name="dailyLimit" key="dailyLimit">
disabled={!activeSignal?.config?.day?.enabled} <InputNumber
key="dailyLimit" disabled={!activeSignal?.config?.day?.enabled}
addonAfter={ addonAfter={
<Select defaultValue="GiB" disabled> <Select defaultValue="GiB" disabled>
<Option value="TiB"> TiB</Option> <Option value="TiB">TiB</Option>
<Option value="GiB"> GiB</Option> <Option value="GiB">GiB</Option>
<Option value="MiB"> MiB </Option> <Option value="MiB">MiB</Option>
<Option value="KiB"> KiB </Option> <Option value="KiB">KiB</Option>
</Select> </Select>
} }
/> />
</Form.Item> </Form.Item>
) : ( ) : (
<div className="no-limit"> <div className="no-limit">
<Infinity size={16} /> NO LIMIT <Infinity size={16} /> NO LIMIT
</div> </div>
)} )}
</div> </div>
)}
{signalCfg.usesCount && (
<div className="count">
{activeSignal?.config?.day?.enabled ? (
<Form.Item name="dailyCount" key="dailyCount">
<InputNumber
placeholder="Enter max # of samples/day"
addonAfter={
<Form.Item
name="dailyCountUnit"
noStyle
initialValue="million"
>
<Select
style={{
width: 90,
}}
>
<Option value="thousand">Thousand</Option>
<Option value="million">Million</Option>
<Option value="billion">Billion</Option>
</Select>
</Form.Item>
}
/>
</Form.Item>
) : (
<div className="no-limit">
<Infinity size={16} /> NO LIMIT
</div>
)}
</div>
)}
</div> </div>
<div className="second-limit"> <div className="second-limit">
<div className="heading"> <div className="heading">
<div className="title"> <div className="title">
Per Second limit{' '} Per Second limit
<div className="limit-enable-disable-toggle"> <div className="limit-enable-disable-toggle">
<Form.Item name="enableSecondLimit"> <Form.Item name="enableSecondLimit">
<Switch <Switch
size="small" size="small"
checked={activeSignal?.config?.second?.enabled} checked={activeSignal?.config?.second?.enabled}
onChange={(value): void => { onChange={(value): void => {
setActiveSignal({ setActiveSignal((prev) =>
...activeSignal, prev
config: { ? {
...activeSignal.config, ...prev,
second: { config: {
...activeSignal.config?.second, ...prev.config,
enabled: value, second: {
}, ...prev.config?.second,
}, enabled: value,
}); },
},
}
: null,
);
}} }}
/> />
</Form.Item> </Form.Item>
@ -828,37 +998,68 @@ function MultiIngestionSettings(): JSX.Element {
Add a limit for data ingested every second Add a limit for data ingested every second
</div> </div>
</div> </div>
{signalCfg.usesSize && (
<div className="size"> <div className="size">
{activeSignal?.config?.second?.enabled ? ( {activeSignal?.config?.second?.enabled ? (
<Form.Item name="secondsLimit" key="secondsLimit"> <Form.Item name="secondsLimit" key="secondsLimit">
<InputNumber <InputNumber
key="secondsLimit" disabled={!activeSignal?.config?.second?.enabled}
disabled={!activeSignal?.config?.second?.enabled} addonAfter={
addonAfter={ <Select defaultValue="GiB" disabled>
<Select defaultValue="GiB" disabled> <Option value="TiB">TiB</Option>
<Option value="TiB"> TiB</Option> <Option value="GiB">GiB</Option>
<Option value="GiB"> GiB</Option> <Option value="MiB">MiB</Option>
<Option value="MiB"> MiB </Option> <Option value="KiB">KiB</Option>
<Option value="KiB"> KiB </Option> </Select>
</Select> }
} />
/> </Form.Item>
</Form.Item> ) : (
) : ( <div className="no-limit">
<div className="no-limit"> <Infinity size={16} /> NO LIMIT
<Infinity size={16} /> NO LIMIT </div>
</div> )}
)} </div>
</div> )}
{signalCfg.usesCount && (
<div className="count">
{activeSignal?.config?.second?.enabled ? (
<Form.Item name="secondsCount" key="secondsCount">
<InputNumber
placeholder="Enter max # of samples/s"
addonAfter={
<Form.Item
name="secondsCountUnit"
noStyle
initialValue="million"
>
<Select
style={{
width: 90,
}}
>
<Option value="thousand">Thousand</Option>
<Option value="million">Million</Option>
<Option value="billion">Billion</Option>
</Select>
</Form.Item>
}
/>
</Form.Item>
) : (
<div className="no-limit">
<Infinity size={16} /> NO LIMIT
</div>
)}
</div>
)}
</div> </div>
</div> </div>
{activeAPIKey?.id === APIKey.id && {activeAPIKey?.id === APIKey.id &&
activeSignal.signal === signal && activeSignal.signal === signalName &&
!isLoadingLimitForKey && !isLoadingLimitForKey &&
hasCreateLimitForIngestionKeyError && hasCreateLimitForIngestionKeyError &&
createLimitForIngestionKeyError &&
createLimitForIngestionKeyError?.error && ( createLimitForIngestionKeyError?.error && (
<div className="error"> <div className="error">
{createLimitForIngestionKeyError?.error} {createLimitForIngestionKeyError?.error}
@ -866,17 +1067,17 @@ function MultiIngestionSettings(): JSX.Element {
)} )}
{activeAPIKey?.id === APIKey.id && {activeAPIKey?.id === APIKey.id &&
activeSignal.signal === signal && activeSignal.signal === signalName &&
!isLoadingLimitForKey && !isLoadingLimitForKey &&
hasUpdateLimitForIngestionKeyError && hasUpdateLimitForIngestionKeyError &&
updateLimitForIngestionKeyError && ( updateLimitForIngestionKeyError?.error && (
<div className="error"> <div className="error">
{updateLimitForIngestionKeyError?.error} {updateLimitForIngestionKeyError?.error}
</div> </div>
)} )}
{activeAPIKey?.id === APIKey.id && {activeAPIKey?.id === APIKey.id &&
activeSignal.signal === signal && activeSignal.signal === signalName &&
isEditAddLimitOpen && ( isEditAddLimitOpen && (
<div className="signal-limit-save-discard"> <div className="signal-limit-save-discard">
<Button <Button
@ -890,10 +1091,10 @@ function MultiIngestionSettings(): JSX.Element {
isLoadingLimitForKey || isLoadingUpdatedLimitForKey isLoadingLimitForKey || isLoadingUpdatedLimitForKey
} }
onClick={(): void => { onClick={(): void => {
if (!hasLimits(signal)) { if (!hasLimits(signalName)) {
handleAddLimit(APIKey, signal); handleAddLimit(APIKey, signalName);
} else { } else {
handleUpdateLimit(APIKey, limits[signal]); handleUpdateLimit(APIKey, limitsDict[signalName]);
} }
}} }}
> >
@ -915,55 +1116,99 @@ function MultiIngestionSettings(): JSX.Element {
</Form> </Form>
) : ( ) : (
<div className="signal-limit-view-mode"> <div className="signal-limit-view-mode">
{/* DAILY limit usage/limit */}
<div className="signal-limit-value"> <div className="signal-limit-value">
<div className="limit-type"> <div className="limit-type">
Daily <Minus size={16} />{' '} Daily <Minus size={16} />
</div> </div>
<div className="limit-value"> <div className="limit-value">
{hasValidDayLimit ? ( {/* Size (if usesSize) */}
<> {signalCfg.usesSize &&
{getYAxisFormattedValue( (hasValidDayLimit &&
(limits[signal]?.metric?.day?.size || 0).toString(), limit?.config?.day?.size !== undefined ? (
'bytes', <>
)}{' '} {getYAxisFormattedValue(
/{' '} (limit?.metric?.day?.size || 0).toString(),
{getYAxisFormattedValue( 'bytes',
(limits[signal]?.config?.day?.size || 0).toString(), )}{' '}
'bytes', /{' '}
)} {getYAxisFormattedValue(
</> (limit?.config?.day?.size || 0).toString(),
) : ( 'bytes',
<> )}
<Infinity size={16} /> NO LIMIT </>
</> ) : (
)} <>
<Infinity size={16} /> NO LIMIT
</>
))}
{/* Count (if usesCount) */}
{signalCfg.usesCount &&
(limit?.config?.day?.count !== undefined ? (
<div style={{ marginTop: 4 }}>
{countToUnit(
limit?.metric?.day?.count || 0,
).value.toFixed(2)}{' '}
{countToUnit(limit?.metric?.day?.count || 0).unit} /{' '}
{countToUnit(
limit?.config?.day?.count || 0,
).value.toFixed(2)}{' '}
{countToUnit(limit?.config?.day?.count || 0).unit}
</div>
) : (
<>
<Infinity size={16} /> NO LIMIT
</>
))}
</div> </div>
</div> </div>
{/* SECOND limit usage/limit */}
<div className="signal-limit-value"> <div className="signal-limit-value">
<div className="limit-type"> <div className="limit-type">
Seconds <Minus size={16} /> Seconds <Minus size={16} />
</div> </div>
<div className="limit-value"> <div className="limit-value">
{hasValidSecondLimit ? ( {/* Size (if usesSize) */}
<> {signalCfg.usesSize &&
{getYAxisFormattedValue( (hasValidSecondLimit &&
(limits[signal]?.metric?.second?.size || 0).toString(), limit?.config?.second?.size !== undefined ? (
'bytes', <>
)}{' '} {getYAxisFormattedValue(
/{' '} (limit?.metric?.second?.size || 0).toString(),
{getYAxisFormattedValue( 'bytes',
(limits[signal]?.config?.second?.size || 0).toString(), )}{' '}
'bytes', /{' '}
)} {getYAxisFormattedValue(
</> (limit?.config?.second?.size || 0).toString(),
) : ( 'bytes',
<> )}
<Infinity size={16} /> NO LIMIT </>
</> ) : (
)} <>
<Infinity size={16} /> NO LIMIT
</>
))}
{/* Count (if usesCount) */}
{signalCfg.usesCount &&
(limit?.config?.second?.count !== undefined ? (
<div style={{ marginTop: 4 }}>
{countToUnit(
limit?.metric?.second?.count || 0,
).value.toFixed(2)}{' '}
{countToUnit(limit?.metric?.second?.count || 0).unit} /{' '}
{countToUnit(
limit?.config?.second?.count || 0,
).value.toFixed(2)}{' '}
{countToUnit(limit?.config?.second?.count || 0).unit}
</div>
) : (
<>
<Infinity size={16} /> NO LIMIT
</>
))}
</div> </div>
</div> </div>
</div> </div>
@ -1033,7 +1278,6 @@ function MultiIngestionSettings(): JSX.Element {
className="learn-more" className="learn-more"
rel="noreferrer" rel="noreferrer"
> >
{' '}
Learn more <ArrowUpRight size={14} /> Learn more <ArrowUpRight size={14} />
</a> </a>
</Typography.Text> </Typography.Text>

View File

@ -1,3 +1,14 @@
export interface LimitConfig {
size?: number;
count?: number; // mainly used for metrics
enabled?: boolean;
}
export interface LimitSettings {
day?: LimitConfig;
second?: LimitConfig;
}
export interface LimitProps { export interface LimitProps {
id: string; id: string;
signal: string; signal: string;
@ -5,56 +16,20 @@ export interface LimitProps {
key_id?: string; key_id?: string;
created_at?: string; created_at?: string;
updated_at?: string; updated_at?: string;
config?: { config?: LimitSettings;
day?: { metric?: LimitSettings;
size?: number;
enabled?: boolean;
};
second?: {
size?: number;
enabled?: boolean;
};
};
metric?: {
day?: {
size?: number;
enabled?: boolean;
};
second?: {
size?: number;
enabled?: boolean;
};
};
} }
export interface AddLimitProps { export interface AddLimitProps {
keyID: string; keyID: string;
signal: string; signal: string;
config: { config: LimitSettings;
day?: {
size?: number;
enabled?: boolean;
};
second?: {
size?: number;
enabled?: boolean;
};
};
} }
export interface UpdateLimitProps { export interface UpdateLimitProps {
limitID: string; limitID: string;
signal: string; signal: string;
config: { config: LimitSettings;
day?: {
size?: number;
enabled?: boolean;
};
second?: {
size?: number;
enabled?: boolean;
};
};
} }
export interface LimitSuccessProps { export interface LimitSuccessProps {