diff --git a/frontend/src/component/common/ApiTokenTable/CreateApiTokenButton/CreateApiTokenButton.test.tsx b/frontend/src/component/common/ApiTokenTable/CreateApiTokenButton/CreateApiTokenButton.test.tsx
new file mode 100644
index 000000000000..efee510fe430
--- /dev/null
+++ b/frontend/src/component/common/ApiTokenTable/CreateApiTokenButton/CreateApiTokenButton.test.tsx
@@ -0,0 +1,67 @@
+import { screen, waitFor } from '@testing-library/react';
+import { render } from 'utils/testRenderer';
+import { testServerRoute, testServerSetup } from 'utils/testServer';
+import { CreateApiTokenButton } from './CreateApiTokenButton';
+import { CREATE_PROJECT_API_TOKEN } from 'component/providers/AccessProvider/permissions';
+
+const server = testServerSetup();
+
+const setupApi = ({
+ apiTokenCount,
+ apiTokenLimit,
+}: { apiTokenCount: number; apiTokenLimit: number }) => {
+ testServerRoute(server, '/api/admin/ui-config', {
+ flags: {
+ resourceLimits: true,
+ },
+ resourceLimits: {
+ apiTokens: apiTokenLimit,
+ },
+ });
+
+ testServerRoute(server, '/api/admin/api-tokens', {
+ tokens: Array.from({ length: apiTokenCount }).map((_, i) => ({
+ secret: 'super-secret',
+ tokenName: `token—name-${i}`,
+ type: 'client',
+ })),
+ });
+};
+
+test('should allow you to create API tokens when there are fewer apiTokens than the limit', async () => {
+ setupApi({ apiTokenLimit: 3, apiTokenCount: 2 });
+
+ render(
+ ,
+ {
+ permissions: [{ permission: CREATE_PROJECT_API_TOKEN }],
+ },
+ );
+
+ await waitFor(async () => {
+ const button = await screen.findByRole('button');
+ expect(button).not.toBeDisabled();
+ });
+});
+
+test('should not allow you to create API tokens when you have reached the limit', async () => {
+ setupApi({ apiTokenLimit: 3, apiTokenCount: 3 });
+
+ render(
+ ,
+ {
+ permissions: [{ permission: CREATE_PROJECT_API_TOKEN }],
+ },
+ );
+
+ await waitFor(async () => {
+ const button = await screen.findByRole('button');
+ expect(button).toBeDisabled();
+ });
+});
diff --git a/frontend/src/component/common/ApiTokenTable/CreateApiTokenButton/CreateApiTokenButton.tsx b/frontend/src/component/common/ApiTokenTable/CreateApiTokenButton/CreateApiTokenButton.tsx
index dd9e40dd8154..f0a5fdd76e1f 100644
--- a/frontend/src/component/common/ApiTokenTable/CreateApiTokenButton/CreateApiTokenButton.tsx
+++ b/frontend/src/component/common/ApiTokenTable/CreateApiTokenButton/CreateApiTokenButton.tsx
@@ -2,18 +2,41 @@ import ResponsiveButton from 'component/common/ResponsiveButton/ResponsiveButton
import { CREATE_API_TOKEN_BUTTON } from 'utils/testIds';
import { useNavigate } from 'react-router-dom';
import Add from '@mui/icons-material/Add';
+import { useApiTokens } from 'hooks/api/getters/useApiTokens/useApiTokens';
+import { useUiFlag } from 'hooks/useUiFlag';
+import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
interface ICreateApiTokenButton {
path: string;
permission: string | string[];
project?: string;
}
+const useApiTokenLimit = (apiTokenLimit: number, apiTokenCount: number) => {
+ const resourceLimitsEnabled = useUiFlag('resourceLimits');
+ const limitReached =
+ resourceLimitsEnabled && apiTokenCount >= apiTokenLimit;
+
+ return {
+ limitReached,
+ limitMessage: limitReached
+ ? `You have reached the limit of ${apiTokenLimit} API tokens`
+ : undefined,
+ };
+};
+
export const CreateApiTokenButton = ({
path,
permission,
project,
}: ICreateApiTokenButton) => {
const navigate = useNavigate();
+ const { tokens, loading } = useApiTokens();
+ const { uiConfig } = useUiConfig();
+
+ const { limitReached, limitMessage } = useApiTokenLimit(
+ uiConfig.resourceLimits.apiTokens,
+ tokens.length,
+ );
return (
New API token
diff --git a/frontend/src/hooks/api/getters/useUiConfig/defaultValue.tsx b/frontend/src/hooks/api/getters/useUiConfig/defaultValue.tsx
index b81dcbd045e8..903af08f0a2d 100644
--- a/frontend/src/hooks/api/getters/useUiConfig/defaultValue.tsx
+++ b/frontend/src/hooks/api/getters/useUiConfig/defaultValue.tsx
@@ -42,5 +42,6 @@ export const defaultValue: IUiConfig = {
constraintValues: 250,
projects: 500,
segments: 300,
+ apiTokens: 2000,
},
};
diff --git a/frontend/src/openapi/models/resourceLimitsSchema.ts b/frontend/src/openapi/models/resourceLimitsSchema.ts
index 5a5770852def..a7fb36e34c1b 100644
--- a/frontend/src/openapi/models/resourceLimitsSchema.ts
+++ b/frontend/src/openapi/models/resourceLimitsSchema.ts
@@ -32,6 +32,13 @@ export interface ResourceLimitsSchema {
constraintValues: number;
/** The maximum number of projects allowed. */
projects: number;
- /** The maximum number of segment allowed. */
+ /** The maximum number of segments allowed. */
segments: number;
+ /** The maximum number of SDK and admin API tokens you can have at
+ * the same time. This limit applies only to server-side and
+ * client-side SDK tokens and to admin tokens. Personal access
+ * tokens are not subject to this limit. The limit applies to the
+ * total number of tokens across all projects in your
+ * organization. */
+ apiTokens: number;
}