chore: make env permissions optional in api key (#5309)

Co-authored-by: Victor Santos <victor@formbricks.com>
This commit is contained in:
Dhruwang Jariwala
2025-04-15 17:12:48 +05:30
committed by GitHub
parent a171f9cb00
commit b685032b34
11 changed files with 49 additions and 37 deletions

View File

@@ -1,4 +1,3 @@
import { ApiKeyPermission } from "@prisma/client";
import "@testing-library/jest-dom/vitest";
import { cleanup, render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
@@ -123,6 +122,9 @@ describe("AddApiKeyModal", () => {
it("handles permission changes", async () => {
render(<AddApiKeyModal {...defaultProps} />);
const addButton = screen.getByRole("button", { name: /add_permission/i });
await userEvent.click(addButton);
// Open project dropdown for the first permission row
const projectDropdowns = screen.getAllByRole("button", { name: /Project 1/i });
await userEvent.click(projectDropdowns[0]);
@@ -143,6 +145,8 @@ describe("AddApiKeyModal", () => {
const addButton = screen.getByRole("button", { name: /add_permission/i });
await userEvent.click(addButton);
await userEvent.click(addButton);
// Verify new permission row is added
const deleteButtons = screen.getAllByRole("button", { name: "" }); // Trash icons
expect(deleteButtons).toHaveLength(2);
@@ -161,6 +165,9 @@ describe("AddApiKeyModal", () => {
const labelInput = screen.getByPlaceholderText("e.g. GitHub, PostHog, Slack") as HTMLInputElement;
await userEvent.type(labelInput, "Test API Key");
const addButton = screen.getByRole("button", { name: /add_permission/i });
await userEvent.click(addButton);
// Click submit
const submitButton = screen.getByRole("button", {
name: "environments.project.api_keys.add_api_key",
@@ -172,7 +179,7 @@ describe("AddApiKeyModal", () => {
environmentPermissions: [
{
environmentId: "env1",
permission: ApiKeyPermission.read,
permission: "read",
},
],
organizationAccess: {
@@ -203,12 +210,7 @@ describe("AddApiKeyModal", () => {
expect(mockOnSubmit).toHaveBeenCalledWith({
label: "Test API Key",
environmentPermissions: [
{
environmentId: "env1",
permission: ApiKeyPermission.read,
},
],
environmentPermissions: [],
organizationAccess: {
accessControl: {
read: true,
@@ -218,7 +220,7 @@ describe("AddApiKeyModal", () => {
});
});
it("disables submit button when label is empty", async () => {
it("disables submit button when label is empty and there are not environment permissions", async () => {
render(<AddApiKeyModal {...defaultProps} />);
const submitButton = screen.getByRole("button", {
name: "environments.project.api_keys.add_api_key",
@@ -228,6 +230,9 @@ describe("AddApiKeyModal", () => {
// Initially disabled
expect(submitButton).toBeDisabled();
const addButton = screen.getByRole("button", { name: /add_permission/i });
await userEvent.click(addButton);
// After typing, it should be enabled
await userEvent.type(labelInput, "Test");
expect(submitButton).not.toBeDisabled();

View File

@@ -89,9 +89,7 @@ export const AddApiKeyModal = ({
};
// Initialize with one permission by default
const [selectedPermissions, setSelectedPermissions] = useState<Record<string, PermissionRecord>>(() =>
getInitialPermissions()
);
const [selectedPermissions, setSelectedPermissions] = useState<Record<string, PermissionRecord>>({});
const projectOptions: ProjectOption[] = projects.map((project) => ({
id: project.id,
@@ -106,14 +104,12 @@ export const AddApiKeyModal = ({
const addPermission = () => {
const newIndex = Object.keys(selectedPermissions).length;
if (projects.length > 0 && projects[0].environments.length > 0) {
const initialPermission = getInitialPermissions()["permission-0"];
if (initialPermission) {
setSelectedPermissions({
...selectedPermissions,
[`permission-${newIndex}`]: initialPermission,
});
}
const initialPermission = getInitialPermissions()["permission-0"];
if (initialPermission) {
setSelectedPermissions({
...selectedPermissions,
[`permission-${newIndex}`]: initialPermission,
});
}
};
@@ -176,7 +172,7 @@ export const AddApiKeyModal = ({
});
reset();
setSelectedPermissions(getInitialPermissions());
setSelectedPermissions({});
setSelectedOrganizationAccess(defaultOrganizationAccess);
};
@@ -191,11 +187,16 @@ export const AddApiKeyModal = ({
if (!apiKeyLabel?.trim()) {
return true;
}
// Check if there are any valid permissions
if (Object.keys(selectedPermissions).length === 0) {
return true;
}
return false;
// Check if at least one project permission is set or one organization access toggle is ON
const hasProjectAccess = Object.keys(selectedPermissions).length > 0;
const hasOrganizationAccess = Object.values(selectedOrganizationAccess).some((accessGroup) =>
Object.values(accessGroup).some((value) => value === true)
);
// Disable submit if no access rights are granted
return !(hasProjectAccess || hasOrganizationAccess);
};
const setSelectedOrganizationAccessValue = (key: string, accessType: string, value: boolean) => {
@@ -335,15 +336,8 @@ export const AddApiKeyModal = ({
<button
type="button"
className="p-2"
onClick={() => removePermission(permissionIndex)}
disabled={Object.keys(selectedPermissions).length <= 1}>
<Trash2Icon
className={`h-5 w-5 ${
Object.keys(selectedPermissions).length <= 1
? "text-slate-300"
: "text-slate-500 hover:text-red-500"
}`}
/>
onClick={() => removePermission(permissionIndex)}>
<Trash2Icon className={"h-5 w-5 text-slate-500 hover:text-red-500"} />
</button>
</div>
);
@@ -403,7 +397,7 @@ export const AddApiKeyModal = ({
onClick={() => {
setOpen(false);
reset();
setSelectedPermissions(getInitialPermissions());
setSelectedPermissions({});
}}>
{t("common.cancel")}
</Button>

View File

@@ -265,7 +265,7 @@ describe("EditAPIKeys", () => {
organizationId: "org1",
apiKeyData: {
label: "New Key",
environmentPermissions: [{ environmentId: "env1", permission: ApiKeyPermission.read }],
environmentPermissions: [],
organizationAccess: {
accessControl: { read: true, write: false },
},

View File

@@ -98,9 +98,15 @@ export const ViewPermissionModal = ({
data-testid="api-key-label"
{...register("label", { required: true, validate: (value) => value.trim() !== "" })}
/>
{/* Permission rows */}
</div>
<div className="space-y-2">
<Label>{t("environments.project.api_keys.permissions")}</Label>
{apiKey.apiKeyEnvironments?.length === 0 && (
<div className="text-center text-sm">
{t("environments.project.api_keys.no_env_permissions_found")}
</div>
)}
<div className="space-y-2">
{/* Permission rows */}
{apiKey.apiKeyEnvironments?.map((permission) => {

View File

@@ -18,6 +18,7 @@ export async function loginAndGetApiKey(page: Page, users: UsersFixture) {
await page.getByRole("button", { name: "Add API Key" }).isVisible();
await page.getByRole("button", { name: "Add API Key" }).click();
await page.getByPlaceholder("e.g. GitHub, PostHog, Slack").fill("E2E Test API Key");
await page.getByRole("button", { name: "+ Add permission" }).click();
await page.getByRole("button", { name: "development" }).click();
await page.getByRole("menuitem", { name: "production" }).click();
await page.getByRole("button", { name: "read" }).click();

View File

@@ -788,6 +788,7 @@
"api_key_updated": "API-Schlüssel aktualisiert",
"duplicate_access": "Doppelter Projektzugriff nicht erlaubt",
"no_api_keys_yet": "Du hast noch keine API-Schlüssel",
"no_env_permissions_found": "Keine Umgebungsberechtigungen gefunden",
"organization_access": "Organisationszugang",
"permissions": "Berechtigungen",
"project_access": "Projektzugriff",

View File

@@ -788,6 +788,7 @@
"api_key_updated": "API Key updated",
"duplicate_access": "Duplicate project access not allowed",
"no_api_keys_yet": "You don't have any API keys yet",
"no_env_permissions_found": "No environment permissions found",
"organization_access": "Organization Access",
"permissions": "Permissions",
"project_access": "Project Access",

View File

@@ -788,6 +788,7 @@
"api_key_updated": "Clé API mise à jour",
"duplicate_access": "L'accès en double au projet n'est pas autorisé",
"no_api_keys_yet": "Vous n'avez pas encore de clés API.",
"no_env_permissions_found": "Aucune autorisation d'environnement trouvée",
"organization_access": "Accès à l'organisation",
"permissions": "Permissions",
"project_access": "Accès au projet",

View File

@@ -788,6 +788,7 @@
"api_key_updated": "Chave de API atualizada",
"duplicate_access": "Acesso duplicado ao projeto não permitido",
"no_api_keys_yet": "Você ainda não tem nenhuma chave de API",
"no_env_permissions_found": "Nenhuma permissão de ambiente encontrada",
"organization_access": "Acesso à Organização",
"permissions": "Permissões",
"project_access": "Acesso ao Projeto",

View File

@@ -788,6 +788,7 @@
"api_key_updated": "Chave API atualizada",
"duplicate_access": "Acesso duplicado ao projeto não permitido",
"no_api_keys_yet": "Ainda não tem nenhuma chave API",
"no_env_permissions_found": "Nenhuma permissão de ambiente encontrada",
"organization_access": "Acesso à Organização",
"permissions": "Permissões",
"project_access": "Acesso ao Projeto",

View File

@@ -788,6 +788,7 @@
"api_key_updated": "API 金鑰已更新",
"duplicate_access": "不允許重複的 project 存取",
"no_api_keys_yet": "您還沒有任何 API 金鑰",
"no_env_permissions_found": "找不到環境權限",
"organization_access": "組織 Access",
"permissions": "權限",
"project_access": "專案存取",