diff --git a/site/e2e/api.ts b/site/e2e/api.ts index bca000e5c29c6..f4a95c981a0dc 100644 --- a/site/e2e/api.ts +++ b/site/e2e/api.ts @@ -28,7 +28,7 @@ export const getCurrentOrgId = async (): Promise => { return currentOrgId; }; -export const createUser = async (orgId: string) => { +export const createUser = async (...orgIds: string[]) => { const name = randomName(); const user = await API.createUser({ email: `${name}@coder.com`, @@ -36,7 +36,7 @@ export const createUser = async (orgId: string) => { name: name, password: "s3cure&password!", login_type: "password", - organization_ids: [orgId], + organization_ids: orgIds, user_status: null, }); return user; diff --git a/site/e2e/helpers.ts b/site/e2e/helpers.ts index 34fa03837057f..100e0a3d0bf95 100644 --- a/site/e2e/helpers.ts +++ b/site/e2e/helpers.ts @@ -1037,3 +1037,25 @@ export async function createUser( await page.goto(returnTo, { waitUntil: "domcontentloaded" }); return { name, username, email, password }; } + +export async function createOrganization(page: Page): Promise<{ + name: string; + displayName: string; + description: string; +}> { + // Create a new organization to test + await page.goto("/organizations/new", { waitUntil: "domcontentloaded" }); + const name = randomName(); + await page.getByLabel("Slug").fill(name); + const displayName = `Org ${name}`; + await page.getByLabel("Display name").fill(displayName); + const description = `Org description ${name}`; + await page.getByLabel("Description").fill(description); + await page.getByLabel("Icon", { exact: true }).fill("/emojis/1f957.png"); + await page.getByRole("button", { name: "Submit" }).click(); + + await expectUrl(page).toHavePathName(`/organizations/${name}`); + await expect(page.getByText("Organization created.")).toBeVisible(); + + return { name, displayName, description }; +} diff --git a/site/e2e/tests/organizationGroups.spec.ts b/site/e2e/tests/organizationGroups.spec.ts new file mode 100644 index 0000000000000..0a049191b97df --- /dev/null +++ b/site/e2e/tests/organizationGroups.spec.ts @@ -0,0 +1,103 @@ +import { expect, test } from "@playwright/test"; +import { + createGroup, + createOrganization, + createUser, + setupApiCalls, +} from "../api"; +import { expectUrl } from "../expectUrl"; +import { randomName, requiresLicense } from "../helpers"; +import { beforeCoderTest } from "../hooks"; + +test.beforeEach(async ({ page }) => { + await beforeCoderTest(page); + await setupApiCalls(page); +}); + +test("create group", async ({ page }) => { + requiresLicense(); + + // Create a new organization + const org = await createOrganization(); + await page.goto(`/organizations/${org.name}`); + + // Navigate to groups page + await page.getByText("Groups").click(); + await expect(page).toHaveTitle(`Groups - Org ${org.name} - Coder`); + + // Create a new group + await page.getByText("Create group").click(); + await expect(page).toHaveTitle("Create Group - Coder"); + const name = randomName(); + await page.getByLabel("Name", { exact: true }).fill(name); + const displayName = `Group ${name}`; + await page.getByLabel("Display Name").fill(displayName); + await page.getByLabel("Avatar URL").fill("/emojis/1f60d.png"); + await page.getByRole("button", { name: "Submit" }).click(); + + await expectUrl(page).toHavePathName( + `/organizations/${org.name}/groups/${name}`, + ); + await expect(page).toHaveTitle(`${displayName} - Coder`); + await expect(page.getByText("No members yet")).toBeVisible(); + await expect(page.getByText(displayName)).toBeVisible(); + + // Add a user to the group + const personToAdd = await createUser(org.id); + await page.getByPlaceholder("User email or username").fill(personToAdd.email); + await page.getByRole("option", { name: personToAdd.email }).click(); + await page.getByRole("button", { name: "Add user" }).click(); + const addedRow = page.locator("tr", { hasText: personToAdd.email }); + await expect(addedRow).toBeVisible(); + + // Ensure we can't add a user who isn't in the org + const otherOrg = await createOrganization(); + const personToReject = await createUser(otherOrg.id); + await page + .getByPlaceholder("User email or username") + .fill(personToReject.email); + await expect(page.getByText("No users found")).toBeVisible(); + + // Remove someone from the group + await addedRow.getByLabel("More options").click(); + await page.getByText("Remove").click(); + await expect(addedRow).not.toBeVisible(); + + // Delete the group + await page.getByRole("button", { name: "Delete" }).click(); + const dialog = page.getByTestId("dialog"); + await dialog.getByLabel("Name of the group to delete").fill(name); + await dialog.getByRole("button", { name: "Delete" }).click(); + await expect(page.getByText("Group deleted successfully.")).toBeVisible(); + + await expectUrl(page).toHavePathName(`/organizations/${org.name}/groups`); + await expect(page).toHaveTitle(`Groups - Org ${org.name} - Coder`); +}); + +test("change quota settings", async ({ page }) => { + requiresLicense(); + + // Create a new organization and group + const org = await createOrganization(); + const group = await createGroup(org.id); + + // Go to settings + await page.goto(`/organizations/${org.name}/groups/${group.name}`); + await page.getByRole("button", { name: "Settings" }).click(); + expectUrl(page).toHavePathName( + `/organizations/${org.name}/groups/${group.name}/settings`, + ); + + // Update Quota + await page.getByLabel("Quota Allowance").fill("100"); + await page.getByRole("button", { name: "Submit" }).click(); + + // We should get sent back to the group page afterwards + expectUrl(page).toHavePathName( + `/organizations/${org.name}/groups/${group.name}`, + ); + + // ...and that setting should persist if we go back + await page.getByRole("button", { name: "Settings" }).click(); + await expect(page.getByLabel("Quota Allowance")).toHaveValue("100"); +}); diff --git a/site/e2e/tests/organizationMembers.spec.ts b/site/e2e/tests/organizationMembers.spec.ts index 56fb2dbc4acde..a761f7c723132 100644 --- a/site/e2e/tests/organizationMembers.spec.ts +++ b/site/e2e/tests/organizationMembers.spec.ts @@ -1,7 +1,6 @@ import { expect, test } from "@playwright/test"; import { setupApiCalls } from "../api"; -import { expectUrl } from "../expectUrl"; -import { createUser, randomName, requiresLicense } from "../helpers"; +import { createOrganization, createUser, requiresLicense } from "../helpers"; import { beforeCoderTest } from "../hooks"; test.beforeEach(async ({ page }) => { @@ -12,19 +11,12 @@ test.beforeEach(async ({ page }) => { test("add and remove organization member", async ({ page }) => { requiresLicense(); - // Create a new organization to test - await page.goto("/organizations/new", { waitUntil: "domcontentloaded" }); - const name = randomName(); - await page.getByLabel("Slug").fill(name); - await page.getByLabel("Display name").fill(`Org ${name}`); - await page.getByLabel("Description").fill(`Org description ${name}`); - await page.getByLabel("Icon", { exact: true }).fill("/emojis/1f957.png"); - await page.getByRole("button", { name: "Submit" }).click(); + // Create a new organization + const { displayName } = await createOrganization(page); // Navigate to members page - await expectUrl(page).toHavePathName(`/organizations/${name}`); - await expect(page.getByText("Organization created.")).toBeVisible(); await page.getByText("Members").click(); + await expect(page).toHaveTitle(`Members - ${displayName} - Coder`); // Add a user to the org const personToAdd = await createUser(page); diff --git a/site/src/pages/ManagementSettingsPage/GroupsPage/GroupPage.tsx b/site/src/pages/ManagementSettingsPage/GroupsPage/GroupPage.tsx index 22841f5d2fbc0..42251f5d5965f 100644 --- a/site/src/pages/ManagementSettingsPage/GroupsPage/GroupPage.tsx +++ b/site/src/pages/ManagementSettingsPage/GroupsPage/GroupPage.tsx @@ -111,9 +111,10 @@ export const GroupPage: FC = () => { {canUpdateGroup && ( diff --git a/site/src/pages/ManagementSettingsPage/GroupsPage/GroupsPage.tsx b/site/src/pages/ManagementSettingsPage/GroupsPage/GroupsPage.tsx index 774360dc6a6d1..5f9df03f825e0 100644 --- a/site/src/pages/ManagementSettingsPage/GroupsPage/GroupsPage.tsx +++ b/site/src/pages/ManagementSettingsPage/GroupsPage/GroupsPage.tsx @@ -69,7 +69,9 @@ export const GroupsPage: FC = () => { return ( <> - {pageTitle("Groups")} + + {pageTitle("Groups", organization.display_name || organization.name)} + { @@ -50,8 +52,7 @@ const OrganizationMembersPage: FC = () => { updateOrganizationMemberRoles(queryClient, organizationName), ); - const { organizations } = useManagementSettings(); - const organization = organizations?.find((o) => o.name === organizationName); + const { organization } = useManagementSettings(); const permissionsQuery = useQuery(organizationPermissions(organization?.id)); const [memberToDelete, setMemberToDelete] = @@ -62,8 +63,17 @@ const OrganizationMembersPage: FC = () => { return ; } + const helmet = organization && ( + + + {pageTitle("Members", organization.display_name || organization.name)} + + + ); + return ( <> + {helmet} { @@ -25,12 +26,19 @@ const OrganizationProvisionersPage: FC = () => { } return ( - + <> + + + {pageTitle("Members", organization.display_name || organization.name)} + + + + ); };