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
@@ -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();
@@ -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>
@@ -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 },
},
@@ -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) => {