Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(workspace): add dropdown #123

Merged
merged 5 commits into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ describe("App", () => {
render(<App />);
await waitFor(() =>
expect(
screen.getByRole("link", { name: /codeGate dashboard/i }),
screen.getByRole("link", { name: "CodeGate Dashboard" }),
).toBeVisible(),
);
expect(screen.getByRole("link", { name: "Dashboard" })).toBeVisible();
Expand Down
9 changes: 6 additions & 3 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,31 @@ import { Link } from "react-router-dom";
import { SidebarTrigger } from "./ui/sidebar";
import { HoverPopover } from "./HoverPopover";
import { Separator, ButtonDarkMode } from "@stacklok/ui-kit";
import { WorkspacesSelection } from "@/features/workspace/components/workspaces-selection";

export function Header({ hasError }: { hasError?: boolean }) {
return (
<header
aria-label="App header"
className="shrink-0 h-16 px-3 items-center flex w-full bg-gray-25 border-b-gray-200 border-b"
>
<div className="flex items-center flex-1">
<div className="flex items-center gap-2 flex-1">
{!hasError && (
<>
<SidebarTrigger />
<Separator orientation="vertical" className="h-8 mx-2" />
<Separator orientation="vertical" className="h-8" />
</>
)}

<nav className="mr-1 flex ml-2">
<nav className="flex ml-2">
<Link to="/">
<h1 className="text-2xl text-primary font-title w-max flex font-semibold">
CodeGate Dashboard
</h1>
</Link>
</nav>
<Separator orientation="vertical" className="h-8 ml-4" />
<WorkspacesSelection />
</div>
<div className="flex items-center gap-4 mr-16">
<HoverPopover title="Certificates">
Expand Down
92 changes: 92 additions & 0 deletions src/features/workspace/components/workspaces-selection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { useWorkspacesData } from "@/hooks/useWorkspacesData";
import {
Button,
DialogTrigger,
Input,
ListBox,
ListBoxItem,
Popover,
SearchField,
Separator,
} from "@stacklok/ui-kit";
import { useQueryClient } from "@tanstack/react-query";
import clsx from "clsx";
import { ChevronDown, Search, Settings } from "lucide-react";
import { useState } from "react";
import { Link } from "react-router-dom";

export function WorkspacesSelection() {
const queryClient = useQueryClient();
const { data } = useWorkspacesData();
const [isOpen, setIsOpen] = useState(false);
const [searchWorkspace, setSearchWorkspace] = useState("");
const workspaces = data?.workspaces ?? [];
const filteredWorkspaces = workspaces.filter((workspace) =>
workspace.name.toLowerCase().includes(searchWorkspace.toLowerCase()),
);
const activeWorkspace = workspaces.find((workspace) => workspace.is_active);

const handleWorkspaceClick = () => {
queryClient.invalidateQueries({ refetchType: "all" });
setIsOpen(false);
};

return (
<DialogTrigger isOpen={isOpen} onOpenChange={(test) => setIsOpen(test)}>
<Button variant="tertiary" className="flex cursor-pointer">
Workspace {activeWorkspace?.name ?? "default"}
<ChevronDown />
</Button>

<Popover className="w-1/4 p-4" placement="bottom left">
<div>
<div>
<SearchField
onChange={setSearchWorkspace}
autoFocus
aria-label="search"
>
<Input icon={<Search />} />
</SearchField>
</div>

<ListBox
className="pb-2 pt-3"
aria-label="Workspaces"
items={filteredWorkspaces}
selectedKeys={activeWorkspace?.name ?? []}
renderEmptyState={() => (
<p className="text-center">No workspaces found</p>
)}
>
{(item) => (
<ListBoxItem
id={item.name}
onAction={() => handleWorkspaceClick()}
className={clsx(
"cursor-pointer py-2 m-1 text-base hover:bg-gray-300",
{
"bg-gray-900 text-white hover:text-secondary":
item.is_active,
},
)}
key={item.name}
>
{item.name}
</ListBoxItem>
)}
</ListBox>
<Separator className="" />
<Link
to="/workspaces"
onClick={() => setIsOpen(false)}
className="text-secondary pt-3 px-2 gap-2 flex"
>
<Settings />
Manage Workspaces
</Link>
</div>
</Popover>
</DialogTrigger>
);
}
4 changes: 2 additions & 2 deletions src/hooks/useSse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ export function useSse() {

useEffect(() => {
const eventSource = new EventSource(
`${BASE_URL}/dashboard/alerts_notification`,
`${BASE_URL}/api/v1/dashboard/alerts_notification`,
);

eventSource.onmessage = function (event) {
queryClient.invalidateQueries({ refetchType: "all" });
if (event.data.toLowerCase().includes("new alert detected")) {
queryClient.invalidateQueries({ refetchType: "all" });
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't need to refetch for the all events, but only for the alerts one

sendNotification("CodeGate Dashboard", {
body: "New Alert detected!",
});
Expand Down
10 changes: 10 additions & 0 deletions src/mocks/msw/fixtures/GET_WORKSPACES.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[
{
"name": "workspace-1",
"is_active": true
},
{
"name": "workspace-2",
"is_active": false
}
]
4 changes: 4 additions & 0 deletions src/mocks/msw/handlers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { http, HttpResponse } from "msw";
import mockedPrompts from "@/mocks/msw/fixtures/GET_MESSAGES.json";
import mockedAlerts from "@/mocks/msw/fixtures/GET_ALERTS.json";
import mockedWorkspaces from "@/mocks/msw/fixtures/GET_WORKSPACES.json";

export const handlers = [
http.get("*/health", () =>
Expand All @@ -20,4 +21,7 @@ export const handlers = [
http.get("*/dashboard/alerts", () => {
return HttpResponse.json(mockedAlerts);
}),
http.get("*/workspaces", () => {
return HttpResponse.json(mockedWorkspaces);
}),
];
Loading