mirror of
https://github.com/coder/coder.git
synced 2026-09-22 05:05:20 +08:00
fix: sort orgs alphabetically in dropdown (#16583)
resolves https://github.com/coder/internal/issues/352 <img width="312" alt="Screenshot 2025-02-18 at 12 16 09 PM" src="https://github.com/user-attachments/assets/c7863bfd-c7cc-4fa6-be88-68a03cd19c1f" /> --------- Co-authored-by: Jaayden Halko <jaayden.halko@gmail.com>
This commit is contained in:
co-authored by
Jaayden Halko
parent
53f0007acb
commit
2c6df5a9ae
@@ -1,5 +1,6 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { expect, userEvent, waitFor, within } from "@storybook/test";
|
||||
import type { AuthorizationResponse } from "api/typesGenerated";
|
||||
import {
|
||||
MockNoPermissions,
|
||||
MockOrganization,
|
||||
@@ -7,7 +8,10 @@ import {
|
||||
MockPermissions,
|
||||
} from "testHelpers/entities";
|
||||
import { withDashboardProvider } from "testHelpers/storybook";
|
||||
import { OrganizationSidebarView } from "./OrganizationSidebarView";
|
||||
import {
|
||||
OrganizationSidebarView,
|
||||
type OrganizationWithPermissions,
|
||||
} from "./OrganizationSidebarView";
|
||||
|
||||
const meta: Meta<typeof OrganizationSidebarView> = {
|
||||
title: "modules/management/OrganizationSidebarView",
|
||||
@@ -286,3 +290,114 @@ export const OrgsDisabled: Story = {
|
||||
showOrganizations: false,
|
||||
},
|
||||
};
|
||||
|
||||
const commonPerms: AuthorizationResponse = {
|
||||
editOrganization: true,
|
||||
editMembers: true,
|
||||
editGroups: true,
|
||||
auditOrganization: true,
|
||||
};
|
||||
|
||||
const activeOrganization: OrganizationWithPermissions = {
|
||||
...MockOrganization,
|
||||
display_name: "Omega org",
|
||||
name: "omega",
|
||||
id: "1",
|
||||
permissions: {
|
||||
...commonPerms,
|
||||
},
|
||||
};
|
||||
|
||||
export const OrgsSortedAlphabetically: Story = {
|
||||
args: {
|
||||
activeOrganization,
|
||||
permissions: {
|
||||
...MockPermissions,
|
||||
createOrganization: true,
|
||||
},
|
||||
organizations: [
|
||||
{
|
||||
...MockOrganization,
|
||||
display_name: "Zeta Org",
|
||||
id: "2",
|
||||
name: "zeta",
|
||||
permissions: commonPerms,
|
||||
},
|
||||
{
|
||||
...MockOrganization,
|
||||
display_name: "alpha Org",
|
||||
id: "3",
|
||||
name: "alpha",
|
||||
permissions: commonPerms,
|
||||
},
|
||||
activeOrganization,
|
||||
],
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await userEvent.click(canvas.getByRole("button", { name: /Omega org/i }));
|
||||
|
||||
// dropdown is not in #storybook-root so must query full document
|
||||
const globalScreen = within(document.body);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(globalScreen.queryByText("alpha Org")).toBeInTheDocument();
|
||||
expect(globalScreen.queryByText("Zeta Org")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const orgElements = globalScreen.getAllByRole("option");
|
||||
// filter out Create btn
|
||||
const filteredElems = orgElements.slice(0, 3);
|
||||
|
||||
const orgNames = filteredElems.map(
|
||||
// handling fuzzy matching
|
||||
(el) => el.textContent?.replace(/^[A-Z]/, "").trim() || "",
|
||||
);
|
||||
|
||||
// active name first
|
||||
expect(orgNames).toEqual(["Omega org", "alpha Org", "Zeta Org"]);
|
||||
},
|
||||
};
|
||||
|
||||
export const SearchForOrg: Story = {
|
||||
args: {
|
||||
activeOrganization,
|
||||
permissions: MockPermissions,
|
||||
organizations: [
|
||||
{
|
||||
...MockOrganization,
|
||||
display_name: "Zeta Org",
|
||||
id: "2",
|
||||
name: "zeta",
|
||||
permissions: commonPerms,
|
||||
},
|
||||
{
|
||||
...MockOrganization,
|
||||
display_name: "alpha Org",
|
||||
id: "3",
|
||||
name: "fish",
|
||||
permissions: commonPerms,
|
||||
},
|
||||
activeOrganization,
|
||||
],
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await userEvent.click(canvas.getByRole("button", { name: /Omega org/i }));
|
||||
|
||||
// dropdown is not in #storybook-root so must query full document
|
||||
const globalScreen = within(document.body);
|
||||
const searchInput =
|
||||
await globalScreen.getByPlaceholderText("Find organization");
|
||||
|
||||
await userEvent.type(searchInput, "ALPHA");
|
||||
|
||||
const filteredResult = await globalScreen.findByText("alpha Org");
|
||||
expect(filteredResult).toBeInTheDocument();
|
||||
|
||||
// Omega org remains visible as the default org
|
||||
await waitFor(() => {
|
||||
expect(globalScreen.queryByText("Zeta Org")).not.toBeInTheDocument();
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@@ -3,9 +3,12 @@ import { Avatar } from "components/Avatar/Avatar";
|
||||
import { Button } from "components/Button/Button";
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
CommandSeparator,
|
||||
} from "components/Command/Command";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import {
|
||||
@@ -88,11 +91,15 @@ const OrganizationsSettingsNavigation: FC<
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
// Sort organizations to put active organization first
|
||||
const sortedOrganizations = [
|
||||
activeOrganization,
|
||||
...organizations.filter((org) => org.id !== activeOrganization.id),
|
||||
];
|
||||
const sortedOrganizations = [...organizations].sort((a, b) => {
|
||||
// active org first
|
||||
if (a.id === activeOrganization.id) return -1;
|
||||
if (b.id === activeOrganization.id) return 1;
|
||||
|
||||
return a.display_name
|
||||
.toLowerCase()
|
||||
.localeCompare(b.display_name.toLowerCase());
|
||||
});
|
||||
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
@@ -123,14 +130,16 @@ const OrganizationsSettingsNavigation: FC<
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="start" className="w-60">
|
||||
<Command loop>
|
||||
<CommandInput placeholder="Find organization" />
|
||||
<CommandList>
|
||||
<CommandEmpty>No organization found.</CommandEmpty>
|
||||
<CommandGroup className="pb-2">
|
||||
{sortedOrganizations.length > 1 && (
|
||||
<div className="flex flex-col max-h-[260px] overflow-y-auto">
|
||||
{sortedOrganizations.map((organization) => (
|
||||
<CommandItem
|
||||
key={organization.id}
|
||||
value={organization.name}
|
||||
value={`${organization.display_name} ${organization.name}`}
|
||||
onSelect={() => {
|
||||
setIsPopoverOpen(false);
|
||||
navigate(urlForSubpage(organization.name));
|
||||
@@ -158,11 +167,11 @@ const OrganizationsSettingsNavigation: FC<
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{permissions.createOrganization && (
|
||||
<>
|
||||
{organizations.length > 1 && (
|
||||
<hr className="h-px my-2 border-none bg-border -mx-2" />
|
||||
)}
|
||||
</CommandGroup>
|
||||
{permissions.createOrganization && (
|
||||
<>
|
||||
{organizations.length > 1 && <CommandSeparator />}
|
||||
<CommandGroup>
|
||||
<CommandItem
|
||||
className="flex justify-center data-[selected=true]:bg-transparent"
|
||||
onSelect={() => {
|
||||
@@ -174,9 +183,9 @@ const OrganizationsSettingsNavigation: FC<
|
||||
>
|
||||
<Plus /> Create Organization
|
||||
</CommandItem>
|
||||
</>
|
||||
)}
|
||||
</CommandGroup>
|
||||
</CommandGroup>
|
||||
</>
|
||||
)}
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
|
||||
Reference in New Issue
Block a user