mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: route groups by name instead of id (#13692)
* feat: route groups by name instead of id * fix: update group navigation when name changes * fix: update isLoading and error checking * fix: fix format * fix: update isLoading and error * fix: cleanup
This commit is contained in:
@@ -20,7 +20,7 @@ test("add members", async ({ page, baseURL }) => {
|
||||
Array.from({ length: numberOfMembers }, () => createUser(orgId)),
|
||||
);
|
||||
|
||||
await page.goto(`${baseURL}/groups/${group.id}`, {
|
||||
await page.goto(`${baseURL}/groups/${group.name}`, {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
await expect(page).toHaveTitle(`${group.display_name} - Coder`);
|
||||
|
||||
@@ -11,7 +11,7 @@ test("remove group", async ({ page, baseURL }) => {
|
||||
const orgId = await getCurrentOrgId();
|
||||
const group = await createGroup(orgId);
|
||||
|
||||
await page.goto(`${baseURL}/groups/${group.id}`, {
|
||||
await page.goto(`${baseURL}/groups/${group.name}`, {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
await expect(page).toHaveTitle(`${group.display_name} - Coder`);
|
||||
|
||||
@@ -21,7 +21,7 @@ test("remove member", async ({ page, baseURL }) => {
|
||||
]);
|
||||
await API.addMember(group.id, member.id);
|
||||
|
||||
await page.goto(`${baseURL}/groups/${group.id}`, {
|
||||
await page.goto(`${baseURL}/groups/${group.name}`, {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
await expect(page).toHaveTitle(`${group.display_name} - Coder`);
|
||||
|
||||
+4
-2
@@ -1455,8 +1455,10 @@ class ApiMethods {
|
||||
return response.data;
|
||||
};
|
||||
|
||||
getGroup = async (groupId: string): Promise<TypesGen.Group> => {
|
||||
const response = await this.axios.get(`/api/v2/groups/${groupId}`);
|
||||
getGroup = async (groupName: string): Promise<TypesGen.Group> => {
|
||||
const response = await this.axios.get(
|
||||
`/api/v2/organizations/default/groups/${groupName}`,
|
||||
);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import type {
|
||||
const GROUPS_QUERY_KEY = ["groups"];
|
||||
type GroupSortOrder = "asc" | "desc";
|
||||
|
||||
const getGroupQueryKey = (groupId: string) => ["group", groupId];
|
||||
const getGroupQueryKey = (groupName: string) => ["group", groupName];
|
||||
|
||||
export const groups = (organizationId: string) => {
|
||||
return {
|
||||
@@ -18,10 +18,10 @@ export const groups = (organizationId: string) => {
|
||||
} satisfies UseQueryOptions<Group[]>;
|
||||
};
|
||||
|
||||
export const group = (groupId: string) => {
|
||||
export const group = (groupName: string) => {
|
||||
return {
|
||||
queryKey: getGroupQueryKey(groupId),
|
||||
queryFn: () => API.getGroup(groupId),
|
||||
queryKey: getGroupQueryKey(groupName),
|
||||
queryFn: () => API.getGroup(groupName),
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ export const CreateGroupPage: FC = () => {
|
||||
organizationId,
|
||||
...data,
|
||||
});
|
||||
navigate(`/groups/${newGroup.id}`);
|
||||
navigate(`/groups/${newGroup.name}`);
|
||||
}}
|
||||
error={createGroupMutation.error}
|
||||
isLoading={createGroupMutation.isLoading}
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
removeMember,
|
||||
} from "api/queries/groups";
|
||||
import type { Group, ReducedUser, User } from "api/typesGenerated";
|
||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
import { AvatarData } from "components/AvatarData/AvatarData";
|
||||
import { DeleteDialog } from "components/Dialogs/DeleteDialog/DeleteDialog";
|
||||
import { EmptyState } from "components/EmptyState/EmptyState";
|
||||
@@ -53,16 +54,20 @@ import { isEveryoneGroup } from "utils/groups";
|
||||
import { pageTitle } from "utils/page";
|
||||
|
||||
export const GroupPage: FC = () => {
|
||||
const { groupId } = useParams() as { groupId: string };
|
||||
const { groupName } = useParams() as { groupName: string };
|
||||
const queryClient = useQueryClient();
|
||||
const navigate = useNavigate();
|
||||
const groupQuery = useQuery(group(groupId));
|
||||
const groupQuery = useQuery(group(groupName));
|
||||
const groupData = groupQuery.data;
|
||||
const { data: permissions } = useQuery(groupPermissions(groupId));
|
||||
const { data: permissions } = useQuery(
|
||||
groupData !== undefined
|
||||
? groupPermissions(groupData.id)
|
||||
: { enabled: false },
|
||||
);
|
||||
const addMemberMutation = useMutation(addMember(queryClient));
|
||||
const deleteGroupMutation = useMutation(deleteGroup(queryClient));
|
||||
const [isDeletingGroup, setIsDeletingGroup] = useState(false);
|
||||
const isLoading = !groupData || !permissions;
|
||||
const isLoading = groupQuery.isLoading || !groupData || !permissions;
|
||||
const canUpdateGroup = permissions ? permissions.canUpdateGroup : false;
|
||||
|
||||
const helmet = (
|
||||
@@ -75,6 +80,10 @@ export const GroupPage: FC = () => {
|
||||
</Helmet>
|
||||
);
|
||||
|
||||
if (groupQuery.error) {
|
||||
return <ErrorAlert error={groupQuery.error} />;
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<>
|
||||
@@ -83,6 +92,7 @@ export const GroupPage: FC = () => {
|
||||
</>
|
||||
);
|
||||
}
|
||||
const groupId = groupData.id;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -137,6 +147,7 @@ export const GroupPage: FC = () => {
|
||||
userId: user.id,
|
||||
});
|
||||
reset();
|
||||
await groupQuery.refetch();
|
||||
} catch (error) {
|
||||
displayError(getErrorMessage(error, "Failed to add member."));
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ export const GroupsPageView: FC<GroupsPageViewProps> = ({
|
||||
|
||||
<Cond>
|
||||
{groups?.map((group) => {
|
||||
const groupPageLink = `/groups/${group.id}`;
|
||||
const groupPageLink = `/groups/${group.name}`;
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
|
||||
@@ -4,26 +4,47 @@ import { useMutation, useQuery, useQueryClient } from "react-query";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { getErrorMessage } from "api/errors";
|
||||
import { group, patchGroup } from "api/queries/groups";
|
||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
import { displayError } from "components/GlobalSnackbar/utils";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import { pageTitle } from "utils/page";
|
||||
import SettingsGroupPageView from "./SettingsGroupPageView";
|
||||
|
||||
export const SettingsGroupPage: FC = () => {
|
||||
const { groupId } = useParams() as { groupId: string };
|
||||
const { groupName } = useParams() as { groupName: string };
|
||||
const queryClient = useQueryClient();
|
||||
const groupQuery = useQuery(group(groupId));
|
||||
const groupQuery = useQuery(group(groupName));
|
||||
const { data: groupData, isLoading, error } = useQuery(group(groupName));
|
||||
const patchGroupMutation = useMutation(patchGroup(queryClient));
|
||||
const navigate = useNavigate();
|
||||
|
||||
const navigateToGroup = () => {
|
||||
navigate(`/groups/${groupId}`);
|
||||
navigate(`/groups/${groupName}`);
|
||||
};
|
||||
|
||||
const helmet = (
|
||||
<Helmet>
|
||||
<title>{pageTitle("Settings Group")}</title>
|
||||
</Helmet>
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return <ErrorAlert error={error} />;
|
||||
}
|
||||
|
||||
if (isLoading || !groupData) {
|
||||
return (
|
||||
<>
|
||||
{helmet}
|
||||
<Loader />
|
||||
</>
|
||||
);
|
||||
}
|
||||
const groupId = groupData.id;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>{pageTitle("Settings Group")}</title>
|
||||
</Helmet>
|
||||
{helmet}
|
||||
|
||||
<SettingsGroupPageView
|
||||
onCancel={navigateToGroup}
|
||||
@@ -35,7 +56,7 @@ export const SettingsGroupPage: FC = () => {
|
||||
add_users: [],
|
||||
remove_users: [],
|
||||
});
|
||||
navigateToGroup();
|
||||
navigate(`/groups/${data.name}`, { replace: true });
|
||||
} catch (error) {
|
||||
displayError(getErrorMessage(error, "Failed to update group"));
|
||||
}
|
||||
|
||||
+2
-2
@@ -330,8 +330,8 @@ export const router = createBrowserRouter(
|
||||
</Route>
|
||||
|
||||
<Route path="create" element={<CreateGroupPage />} />
|
||||
<Route path=":groupId" element={<GroupPage />} />
|
||||
<Route path=":groupId/settings" element={<SettingsGroupPage />} />
|
||||
<Route path=":groupName" element={<GroupPage />} />
|
||||
<Route path=":groupName/settings" element={<SettingsGroupPage />} />
|
||||
</Route>
|
||||
|
||||
<Route path="/audit" element={<AuditPage />} />
|
||||
|
||||
Reference in New Issue
Block a user