Compare commits

...

1 Commits

Author SHA1 Message Date
Karan Balani
842a53b6d8 feat: use gateway v2 apis in frontend to power ingestion keys page 2026-01-21 14:24:31 +05:30
14 changed files with 152 additions and 114 deletions

View File

@@ -1,17 +1,17 @@
import { GatewayApiV1Instance } from 'api';
import { ApiV2Instance } from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import {
CreatedIngestionKey,
CreateIngestionKeyProps,
IngestionKeyProps,
} from 'types/api/ingestionKeys/types';
const createIngestionKey = async (
props: CreateIngestionKeyProps,
): Promise<SuccessResponse<IngestionKeyProps> | ErrorResponse> => {
): Promise<SuccessResponse<CreatedIngestionKey> | ErrorResponse> => {
try {
const response = await GatewayApiV1Instance.post('/workspaces/me/keys', {
const response = await ApiV2Instance.post('/gateway/ingestion_keys', {
...props,
});

View File

@@ -1,4 +1,4 @@
import { GatewayApiV1Instance } from 'api';
import { ApiV2Instance } from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
@@ -8,9 +8,7 @@ const deleteIngestionKey = async (
id: string,
): Promise<SuccessResponse<AllIngestionKeyProps> | ErrorResponse> => {
try {
const response = await GatewayApiV1Instance.delete(
`/workspaces/me/keys/${id}`,
);
const response = await ApiV2Instance.delete(`/gateway/ingestion_keys/${id}`);
return {
statusCode: 200,

View File

@@ -1,4 +1,4 @@
import { GatewayApiV1Instance } from 'api';
import { ApiV2Instance } from 'api';
import { AxiosResponse } from 'axios';
import {
AllIngestionKeyProps,
@@ -11,11 +11,11 @@ export const getAllIngestionKeys = (
// eslint-disable-next-line @typescript-eslint/naming-convention
const { search, per_page, page } = props;
const BASE_URL = '/workspaces/me/keys';
const BASE_URL = '/gateway/ingestion_keys';
const URL_QUERY_PARAMS =
search && search.length > 0
? `/search?name=${search}&page=1&per_page=100`
: `?page=${page}&per_page=${per_page}`;
return GatewayApiV1Instance.get(`${BASE_URL}${URL_QUERY_PARAMS}`);
return ApiV2Instance.get(`${BASE_URL}${URL_QUERY_PARAMS}`);
};

View File

@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-throw-literal */
import { GatewayApiV1Instance } from 'api';
import { ApiV2Instance } from 'api';
import axios from 'axios';
import {
AddLimitProps,
@@ -24,8 +24,8 @@ const createLimitForIngestionKey = async (
props: AddLimitProps,
): Promise<SuccessResponse<LimitSuccessProps> | ErrorResponse> => {
try {
const response = await GatewayApiV1Instance.post(
`/workspaces/me/keys/${props.keyID}/limits`,
const response = await ApiV2Instance.post(
`/gateway/ingestion_keys/${props.keyID}/limits`,
{
...props,
},

View File

@@ -1,4 +1,4 @@
import { GatewayApiV1Instance } from 'api';
import { ApiV2Instance } from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
@@ -8,8 +8,8 @@ const deleteLimitsForIngestionKey = async (
id: string,
): Promise<SuccessResponse<AllIngestionKeyProps> | ErrorResponse> => {
try {
const response = await GatewayApiV1Instance.delete(
`/workspaces/me/limits/${id}`,
const response = await ApiV2Instance.delete(
`/gateway/ingestion_keys/limits/${id}`,
);
return {

View File

@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-throw-literal */
import { GatewayApiV1Instance } from 'api';
import { ApiV2Instance } from 'api';
import axios from 'axios';
import {
LimitSuccessProps,
@@ -24,8 +24,8 @@ const updateLimitForIngestionKey = async (
props: UpdateLimitProps,
): Promise<SuccessResponse<LimitSuccessProps> | ErrorResponse> => {
try {
const response = await GatewayApiV1Instance.patch(
`/workspaces/me/limits/${props.limitID}`,
const response = await ApiV2Instance.patch(
`/gateway/ingestion_keys/limits/${props.limitID}`,
{
config: props.config,
},

View File

@@ -1,4 +1,4 @@
import { GatewayApiV1Instance } from 'api';
import { ApiV2Instance } from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
@@ -11,8 +11,8 @@ const updateIngestionKey = async (
props: UpdateIngestionKeyProps,
): Promise<SuccessResponse<IngestionKeysPayloadProps> | ErrorResponse> => {
try {
const response = await GatewayApiV1Instance.patch(
`/workspaces/me/keys/${props.id}`,
const response = await ApiV2Instance.patch(
`/gateway/ingestion_keys/${props.id}`,
{
...props.data,
},

View File

@@ -2,19 +2,27 @@ import './IngestionSettings.styles.scss';
import { Skeleton, Table, Typography } from 'antd';
import type { ColumnsType } from 'antd/es/table';
import getIngestionData from 'api/settings/getIngestionData';
import { useAppContext } from 'providers/App/App';
import { useQuery } from 'react-query';
import { useGetGlobalConfig } from 'hooks/globalConfig/useGetGlobalConfig';
import { useGetAllIngestionsKeys } from 'hooks/IngestionKeys/useGetAllIngestionKeys';
import { IngestionDataType } from 'types/api/settings/ingestion';
export default function IngestionSettings(): JSX.Element {
const { user } = useAppContext();
const {
data: globalConfig,
isFetching: isFetchingGlobalConfig,
} = useGetGlobalConfig();
const { data: ingestionData, isFetching } = useQuery({
queryFn: getIngestionData,
queryKey: ['getIngestionData', user?.id],
const {
data: ingestionKeys,
isFetching: isFetchingIngestionKeys,
} = useGetAllIngestionsKeys({
search: '',
page: 1,
per_page: 1,
});
const isFetching = isFetchingGlobalConfig || isFetchingIngestionKeys;
const columns: ColumnsType<IngestionDataType> = [
{
title: 'Name',
@@ -40,27 +48,19 @@ export default function IngestionSettings(): JSX.Element {
},
];
const injectionDataPayload =
ingestionData &&
ingestionData.payload &&
Array.isArray(ingestionData.payload) &&
ingestionData?.payload[0];
const ingestionKey = ingestionKeys?.data?.data?.keys?.[0]?.value || '';
const ingestionURL = globalConfig?.data?.ingestion_url || '';
const data: IngestionDataType[] = [
{
key: '1',
name: 'Ingestion URL',
value: injectionDataPayload?.ingestionURL,
value: ingestionURL,
},
{
key: '2',
name: 'Ingestion Key',
value: injectionDataPayload?.ingestionKey,
},
{
key: '3',
name: 'Ingestion Region',
value: injectionDataPayload?.dataRegion,
value: ingestionKey,
},
];

View File

@@ -114,6 +114,13 @@ export const showErrorNotification = (
});
};
export const getErrorMessage = (err: any): string => {
if (err?.error?.message) return err.error.message;
if (typeof err?.error === 'string') return err.error;
if (err?.message) return err.message;
return 'Something went wrong';
};
type ExpiryOption = {
value: string;
label: string;
@@ -271,14 +278,14 @@ function MultiIngestionSettings(): JSX.Element {
});
useEffect(() => {
setActiveAPIKey(IngestionKeys?.data.data[0]);
setActiveAPIKey(IngestionKeys?.data.data.keys[0]);
}, [IngestionKeys]);
useEffect(() => {
setDataSource(IngestionKeys?.data.data || []);
setTotalIngestionKeys(IngestionKeys?.data?._pagination?.total || 0);
setDataSource(IngestionKeys?.data.data.keys || []);
setTotalIngestionKeys(IngestionKeys?.data?.data._pagination?.total || 0);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [IngestionKeys?.data?.data]);
}, [IngestionKeys?.data?.data?.keys]);
useEffect(() => {
if (isError) {
@@ -311,7 +318,7 @@ function MultiIngestionSettings(): JSX.Element {
isLoading: isLoadingCreateAPIKey,
} = useMutation(createIngestionKeyApi, {
onSuccess: (data) => {
setActiveAPIKey(data.payload);
// setActiveAPIKey(data.payload); // New API returns partial data (created key id/value), and we close modal anyway.
setUpdatedTags([]);
hideAddViewModal();
refetchAPIKeys();
@@ -1007,6 +1014,7 @@ function MultiIngestionSettings(): JSX.Element {
{activeSignal?.config?.day?.enabled ? (
<Form.Item name="dailyLimit" key="dailyLimit">
<InputNumber
min={0}
disabled={!activeSignal?.config?.day?.enabled}
addonAfter={
<Select defaultValue="GiB" disabled>
@@ -1030,6 +1038,7 @@ function MultiIngestionSettings(): JSX.Element {
{activeSignal?.config?.day?.enabled ? (
<Form.Item name="dailyCount" key="dailyCount">
<InputNumber
min={0}
placeholder="Enter max # of samples/day"
addonAfter={
<Form.Item
@@ -1097,6 +1106,7 @@ function MultiIngestionSettings(): JSX.Element {
{activeSignal?.config?.second?.enabled ? (
<Form.Item name="secondsLimit" key="secondsLimit">
<InputNumber
min={0}
disabled={!activeSignal?.config?.second?.enabled}
addonAfter={
<Select defaultValue="GiB" disabled>
@@ -1120,6 +1130,7 @@ function MultiIngestionSettings(): JSX.Element {
{activeSignal?.config?.second?.enabled ? (
<Form.Item name="secondsCount" key="secondsCount">
<InputNumber
min={0}
placeholder="Enter max # of samples/s"
addonAfter={
<Form.Item
@@ -1153,20 +1164,18 @@ function MultiIngestionSettings(): JSX.Element {
{activeAPIKey?.id === APIKey.id &&
activeSignal.signal === signalName &&
!isLoadingLimitForKey &&
hasCreateLimitForIngestionKeyError &&
createLimitForIngestionKeyError?.error && (
hasCreateLimitForIngestionKeyError && (
<div className="error">
{createLimitForIngestionKeyError?.error}
{getErrorMessage(createLimitForIngestionKeyError)}
</div>
)}
{activeAPIKey?.id === APIKey.id &&
activeSignal.signal === signalName &&
!isLoadingLimitForKey &&
hasUpdateLimitForIngestionKeyError &&
updateLimitForIngestionKeyError?.error && (
hasUpdateLimitForIngestionKeyError && (
<div className="error">
{updateLimitForIngestionKeyError?.error}
{getErrorMessage(updateLimitForIngestionKeyError)}
</div>
)}

View File

@@ -5,6 +5,7 @@ import { LimitProps } from 'types/api/ingestionKeys/limits/types';
import {
AllIngestionKeyProps,
IngestionKeyProps,
PaginationProps,
} from 'types/api/ingestionKeys/types';
import MultiIngestionSettings from '../MultiIngestionSettings';
@@ -15,7 +16,10 @@ interface TestIngestionKeyProps extends Omit<IngestionKeyProps, 'limits'> {
}
interface TestAllIngestionKeyProps extends Omit<AllIngestionKeyProps, 'data'> {
data: TestIngestionKeyProps[];
data: {
keys: TestIngestionKeyProps[];
_pagination: PaginationProps;
};
}
// Mock useHistory.push to capture navigation URL used by MultiIngestionSettings
@@ -88,26 +92,28 @@ describe('MultiIngestionSettings Page', () => {
// Arrange API response with a metrics daily count limit so the alert button is visible
const response: TestAllIngestionKeyProps = {
status: 'success',
data: [
{
name: 'Key One',
expires_at: TEST_EXPIRES_AT,
value: 'secret',
workspace_id: TEST_WORKSPACE_ID,
id: 'k1',
created_at: TEST_CREATED_UPDATED,
updated_at: TEST_CREATED_UPDATED,
tags: [],
limits: [
{
id: 'l1',
signal: 'metrics',
config: { day: { count: 1000 } },
},
],
},
],
_pagination: { page: 1, per_page: 10, pages: 1, total: 1 },
data: {
keys: [
{
name: 'Key One',
expires_at: TEST_EXPIRES_AT,
value: 'secret',
workspace_id: TEST_WORKSPACE_ID,
id: 'k1',
created_at: TEST_CREATED_UPDATED,
updated_at: TEST_CREATED_UPDATED,
tags: [],
limits: [
{
id: 'l1',
signal: 'metrics',
config: { day: { count: 1000 } },
},
],
},
],
_pagination: { page: 1, per_page: 10, pages: 1, total: 1 },
},
};
server.use(
@@ -177,26 +183,28 @@ describe('MultiIngestionSettings Page', () => {
// Arrange API response with a logs daily size limit so the alert button is visible
const response: TestAllIngestionKeyProps = {
status: 'success',
data: [
{
name: 'Key Two',
expires_at: TEST_EXPIRES_AT,
value: 'secret',
workspace_id: TEST_WORKSPACE_ID,
id: 'k2',
created_at: TEST_CREATED_UPDATED,
updated_at: TEST_CREATED_UPDATED,
tags: [],
limits: [
{
id: 'l2',
signal: 'logs',
config: { day: { size: 2048 } },
},
],
},
],
_pagination: { page: 1, per_page: 10, pages: 1, total: 1 },
data: {
keys: [
{
name: 'Key Two',
expires_at: TEST_EXPIRES_AT,
value: 'secret',
workspace_id: TEST_WORKSPACE_ID,
id: 'k2',
created_at: TEST_CREATED_UPDATED,
updated_at: TEST_CREATED_UPDATED,
tags: [],
limits: [
{
id: 'l2',
signal: 'logs',
config: { day: { size: 2048 } },
},
],
},
],
_pagination: { page: 1, per_page: 10, pages: 1, total: 1 },
},
};
server.use(

View File

@@ -5,13 +5,14 @@ import './Onboarding.styles.scss';
import { ArrowRightOutlined } from '@ant-design/icons';
import { Button, Card, Form, Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import getIngestionData from 'api/settings/getIngestionData';
import cx from 'classnames';
import { FeatureKeys } from 'constants/features';
import ROUTES from 'constants/routes';
import FullScreenHeader from 'container/FullScreenHeader/FullScreenHeader';
import InviteUserModal from 'container/OrganizationSettings/InviteUserModal/InviteUserModal';
import { InviteMemberFormValues } from 'container/OrganizationSettings/PendingInvitesContainer';
import { useGetGlobalConfig } from 'hooks/globalConfig/useGetGlobalConfig';
import { useGetAllIngestionsKeys } from 'hooks/IngestionKeys/useGetAllIngestionKeys';
import history from 'lib/history';
import { UserPlus } from 'lucide-react';
import { useAppContext } from 'providers/App/App';
@@ -128,29 +129,41 @@ export default function Onboarding(): JSX.Element {
logEvent('Onboarding V2 Started', {});
});
const { status, data: ingestionData } = useQuery({
queryFn: () => getIngestionData(),
const {
status: globalConfigStatus,
data: globalConfig,
} = useGetGlobalConfig();
const {
status: ingestionKeysStatus,
data: ingestionKeys,
} = useGetAllIngestionsKeys({
search: '',
page: 1,
per_page: 1,
});
useEffect(() => {
if (
status === 'success' &&
ingestionData &&
ingestionData &&
Array.isArray(ingestionData.payload)
globalConfigStatus === 'success' &&
ingestionKeysStatus === 'success' &&
ingestionKeys?.data.data.keys
) {
const payload = ingestionData.payload[0] || {
ingestionKey: '',
dataRegion: '',
};
const ingestionKey = ingestionKeys.data.data.keys[0]?.value || '';
const ingestionURL = globalConfig?.data?.ingestion_url || '';
const region = ''; // Region not available in GlobalConfig
updateIngestionData({
SIGNOZ_INGESTION_KEY: payload?.ingestionKey,
REGION: payload?.dataRegion,
SIGNOZ_INGESTION_KEY: ingestionKey,
REGION: region,
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [status, ingestionData?.payload]);
}, [
globalConfigStatus,
ingestionKeysStatus,
ingestionKeys?.data.data.keys,
globalConfig?.data,
]);
const setModuleStepsBasedOnSelectedDataSource = (
selectedDataSource: DataSourceType | null,

View File

@@ -69,8 +69,11 @@ export default function OnboardingIngestionDetails(): JSX.Element {
};
useEffect(() => {
if (ingestionKeys?.data.data && ingestionKeys?.data.data.length > 0) {
setFirstIngestionKey(ingestionKeys?.data.data[0]);
if (
ingestionKeys?.data.data.keys &&
ingestionKeys?.data.data.keys.length > 0
) {
setFirstIngestionKey(ingestionKeys?.data.data.keys[0]);
}
}, [ingestionKeys]);

View File

@@ -54,8 +54,10 @@ export interface PaginationProps {
export interface AllIngestionKeyProps {
status: string;
data: IngestionKeyProps[];
_pagination: PaginationProps;
data: {
keys: IngestionKeyProps[];
_pagination: PaginationProps;
};
}
export interface CreateIngestionKeyProp {
@@ -83,3 +85,8 @@ export type IngestionKeysPayloadProps = {
export type GetIngestionKeyPayloadProps = {
id: string;
};
export interface CreatedIngestionKey {
id: string;
value: string;
}

View File

@@ -33,8 +33,8 @@ type LimitConfig struct {
}
type LimitValue struct {
Size int64 `json:"size,omitempty"`
Count int64 `json:"count,omitempty"`
Size int64 `json:"size"`
Count int64 `json:"count"`
}
type LimitMetric struct {