From a880e67bfe97ed19e28f6a761960ae50ef58e639 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Thu, 21 Aug 2025 14:39:18 +0000 Subject: [PATCH 1/6] feat: add badge in the navbar --- site/src/components/Badge/Badge.tsx | 1 + .../modules/dashboard/Navbar/NavbarView.tsx | 63 ++++++++++++++++--- 2 files changed, 54 insertions(+), 10 deletions(-) diff --git a/site/src/components/Badge/Badge.tsx b/site/src/components/Badge/Badge.tsx index a042b5cf7203c..c3d0b27475bf2 100644 --- a/site/src/components/Badge/Badge.tsx +++ b/site/src/components/Badge/Badge.tsx @@ -24,6 +24,7 @@ const badgeVariants = cva( "border border-solid border-border-destructive bg-surface-red text-highlight-red shadow", green: "border border-solid border-surface-green bg-surface-green text-highlight-green shadow", + info: "border border-solid border-surface-sky bg-surface-sky text-highlight-sky shadow", }, size: { xs: "text-2xs font-regular h-5 [&_svg]:hidden rounded px-1.5", diff --git a/site/src/modules/dashboard/Navbar/NavbarView.tsx b/site/src/modules/dashboard/Navbar/NavbarView.tsx index 4a2b3027a47dd..fc776844a223c 100644 --- a/site/src/modules/dashboard/Navbar/NavbarView.tsx +++ b/site/src/modules/dashboard/Navbar/NavbarView.tsx @@ -1,13 +1,17 @@ import { API } from "api/api"; import type * as TypesGen from "api/typesGenerated"; +import { Badge } from "components/Badge/Badge"; import { Button } from "components/Button/Button"; import { ExternalImage } from "components/ExternalImage/ExternalImage"; import { CoderIcon } from "components/Icons/CoderIcon"; import type { ProxyContextValue } from "contexts/ProxyContext"; import { useWebpushNotifications } from "contexts/useWebpushNotifications"; +import { useAuthenticated } from "hooks"; import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata"; import { NotificationsInbox } from "modules/notifications/NotificationsInbox/NotificationsInbox"; +import { data } from "pages/TasksPage/TasksPage"; import type { FC } from "react"; +import { useQuery } from "react-query"; import { NavLink, useLocation } from "react-router"; import { cn } from "utils/cn"; import { DeploymentDropdown } from "./DeploymentDropdown"; @@ -144,7 +148,6 @@ interface NavItemsProps { const NavItems: FC = ({ className }) => { const location = useLocation(); - const { metadata } = useEmbeddedMetadata(); return ( + ); +}; + +const TasksNavItem: FC = () => { + const { metadata } = useEmbeddedMetadata(); + const canSeeTasks = + !!metadata["tasks-tab-visible"].value || + process.env.NODE_ENV === "development"; + const { user } = useAuthenticated(); + const filter = { + user: { + label: user.username, + value: user.username, + avatarUrl: user.avatar_url, + }, + }; + const { data: idleTasks } = useQuery({ + queryKey: ["tasks", filter], + queryFn: () => data.fetchTasks(filter), + refetchInterval: 10_000, + enabled: canSeeTasks, + refetchOnWindowFocus: true, + select: (data) => + data.filter((task) => task.workspace.latest_app_status?.state === "idle"), + }); + + if (!canSeeTasks) { + return null; + } + + return ( + { + return cn(linkStyles.default, isActive ? linkStyles.active : ""); + }} + to="/tasks" + > + Tasks + {idleTasks && idleTasks.length > 0 && ( + - Tasks - + {idleTasks.length} + )} - + ); }; From f4375561bd672f0a46a4f9d201335be2d848884e Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Thu, 21 Aug 2025 15:02:00 +0000 Subject: [PATCH 2/6] Add stories --- .../dashboard/Navbar/NavbarView.stories.tsx | 48 ++++++++++- .../modules/dashboard/Navbar/NavbarView.tsx | 80 +++++++++++++------ 2 files changed, 100 insertions(+), 28 deletions(-) diff --git a/site/src/modules/dashboard/Navbar/NavbarView.stories.tsx b/site/src/modules/dashboard/Navbar/NavbarView.stories.tsx index 786f595d32932..02c84a123bb24 100644 --- a/site/src/modules/dashboard/Navbar/NavbarView.stories.tsx +++ b/site/src/modules/dashboard/Navbar/NavbarView.stories.tsx @@ -1,13 +1,35 @@ import { chromaticWithTablet } from "testHelpers/chromatic"; -import { MockUserMember, MockUserOwner } from "testHelpers/entities"; +import { + MockUserMember, + MockUserOwner, + MockWorkspace, + MockWorkspaceAppStatus, +} from "testHelpers/entities"; import { withDashboardProvider } from "testHelpers/storybook"; import type { Meta, StoryObj } from "@storybook/react-vite"; import { userEvent, within } from "storybook/test"; import { NavbarView } from "./NavbarView"; +const filter = { + user: { + label: MockUserOwner.username, + value: MockUserOwner.username, + avatarUrl: MockUserOwner.avatar_url, + }, +}; + const meta: Meta = { title: "modules/dashboard/NavbarView", - parameters: { chromatic: chromaticWithTablet, layout: "fullscreen" }, + parameters: { + chromatic: chromaticWithTablet, + layout: "fullscreen", + queries: [ + { + key: ["tasks", filter], + data: [], + }, + ], + }, component: NavbarView, args: { user: MockUserOwner, @@ -78,3 +100,25 @@ export const CustomLogo: Story = { logo_url: "/icon/github.svg", }, }; + +export const IdleTasks: Story = { + parameters: { + queries: [ + { + key: ["tasks", filter], + data: [ + { + prompt: "Task 1", + workspace: { + ...MockWorkspace, + latest_app_status: { + ...MockWorkspaceAppStatus, + state: "idle", + }, + }, + }, + ], + }, + ], + }, +}; diff --git a/site/src/modules/dashboard/Navbar/NavbarView.tsx b/site/src/modules/dashboard/Navbar/NavbarView.tsx index fc776844a223c..d9cc64b36a9b0 100644 --- a/site/src/modules/dashboard/Navbar/NavbarView.tsx +++ b/site/src/modules/dashboard/Navbar/NavbarView.tsx @@ -4,11 +4,17 @@ import { Badge } from "components/Badge/Badge"; import { Button } from "components/Button/Button"; import { ExternalImage } from "components/ExternalImage/ExternalImage"; import { CoderIcon } from "components/Icons/CoderIcon"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "components/Tooltip/Tooltip"; import type { ProxyContextValue } from "contexts/ProxyContext"; import { useWebpushNotifications } from "contexts/useWebpushNotifications"; -import { useAuthenticated } from "hooks"; import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata"; import { NotificationsInbox } from "modules/notifications/NotificationsInbox/NotificationsInbox"; +import type { Task } from "modules/tasks/tasks"; import { data } from "pages/TasksPage/TasksPage"; import type { FC } from "react"; import { useQuery } from "react-query"; @@ -21,7 +27,7 @@ import { UserDropdown } from "./UserDropdown/UserDropdown"; interface NavbarViewProps { logo_url?: string; - user?: TypesGen.User; + user: TypesGen.User; buildInfo?: TypesGen.BuildInfoResponse; supportLinks?: readonly TypesGen.LinkConfig[]; onSignOut: () => void; @@ -64,7 +70,7 @@ export const NavbarView: FC = ({ )} - +
{proxyContextValue && ( @@ -113,16 +119,14 @@ export const NavbarView: FC = ({ } /> - {user && ( -
- -
- )} +
+ +
= ({ interface NavItemsProps { className?: string; + user: TypesGen.User; } -const NavItems: FC = ({ className }) => { +const NavItems: FC = ({ className, user }) => { const location = useLocation(); return ( @@ -170,17 +175,20 @@ const NavItems: FC = ({ className }) => { > Templates - + ); }; -const TasksNavItem: FC = () => { +type TasksNavItemProps = { + user: TypesGen.User; +}; + +const TasksNavItem: FC = ({ user }) => { const { metadata } = useEmbeddedMetadata(); const canSeeTasks = !!metadata["tasks-tab-visible"].value || process.env.NODE_ENV === "development"; - const { user } = useAuthenticated(); const filter = { user: { label: user.username, @@ -202,24 +210,44 @@ const TasksNavItem: FC = () => { return null; } + const search = + idleTasks && idleTasks.length > 0 + ? new URLSearchParams({ + username: user.username, + tab: "waiting-for-input", + }).toString() + : undefined; + return ( { return cn(linkStyles.default, isActive ? linkStyles.active : ""); }} - to="/tasks" > Tasks {idleTasks && idleTasks.length > 0 && ( - - {idleTasks.length} - + + + + + {idleTasks.length} + + + {idleTasksLabel(idleTasks)} + + )} ); }; + +function idleTasksLabel(idleTasks: Task[]) { + const count = idleTasks.length; + return `You have ${count} ${count === 1 ? "task" : "tasks"} waiting for input`; +} From 1fa4f23726f18d04a250964880cc85134d22a77d Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Thu, 21 Aug 2025 16:41:50 +0000 Subject: [PATCH 3/6] show tasks nav item in chromatic --- .../modules/dashboard/Navbar/NavbarView.stories.tsx | 11 +++++++++++ site/src/modules/dashboard/Navbar/NavbarView.tsx | 10 ++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/site/src/modules/dashboard/Navbar/NavbarView.stories.tsx b/site/src/modules/dashboard/Navbar/NavbarView.stories.tsx index 02c84a123bb24..40c278187aba3 100644 --- a/site/src/modules/dashboard/Navbar/NavbarView.stories.tsx +++ b/site/src/modules/dashboard/Navbar/NavbarView.stories.tsx @@ -117,6 +117,17 @@ export const IdleTasks: Story = { }, }, }, + { + prompt: "Task 2", + workspace: MockWorkspace, + }, + { + prompt: "Task 3", + workspace: { + ...MockWorkspace, + latest_app_status: MockWorkspaceAppStatus, + }, + }, ], }, ], diff --git a/site/src/modules/dashboard/Navbar/NavbarView.tsx b/site/src/modules/dashboard/Navbar/NavbarView.tsx index d9cc64b36a9b0..f0f16c8962c10 100644 --- a/site/src/modules/dashboard/Navbar/NavbarView.tsx +++ b/site/src/modules/dashboard/Navbar/NavbarView.tsx @@ -186,9 +186,11 @@ type TasksNavItemProps = { const TasksNavItem: FC = ({ user }) => { const { metadata } = useEmbeddedMetadata(); - const canSeeTasks = - !!metadata["tasks-tab-visible"].value || - process.env.NODE_ENV === "development"; + const canSeeTasks = Boolean( + metadata["tasks-tab-visible"].value || + process.env.NODE_ENV === "development" || + process.env.STORYBOOK, + ); const filter = { user: { label: user.username, @@ -199,7 +201,7 @@ const TasksNavItem: FC = ({ user }) => { const { data: idleTasks } = useQuery({ queryKey: ["tasks", filter], queryFn: () => data.fetchTasks(filter), - refetchInterval: 10_000, + refetchInterval: 1_000 * 60, enabled: canSeeTasks, refetchOnWindowFocus: true, select: (data) => From bddab452734e9dd9fd78c07ebffdec296d04899e Mon Sep 17 00:00:00 2001 From: Bruno Quaresma Date: Fri, 22 Aug 2025 13:48:57 +0000 Subject: [PATCH 4/6] Apply PR review --- .../modules/dashboard/Navbar/NavbarView.tsx | 34 +++++++------------ 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/site/src/modules/dashboard/Navbar/NavbarView.tsx b/site/src/modules/dashboard/Navbar/NavbarView.tsx index f0f16c8962c10..8b8c97bcea262 100644 --- a/site/src/modules/dashboard/Navbar/NavbarView.tsx +++ b/site/src/modules/dashboard/Navbar/NavbarView.tsx @@ -14,7 +14,6 @@ import type { ProxyContextValue } from "contexts/ProxyContext"; import { useWebpushNotifications } from "contexts/useWebpushNotifications"; import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata"; import { NotificationsInbox } from "modules/notifications/NotificationsInbox/NotificationsInbox"; -import type { Task } from "modules/tasks/tasks"; import { data } from "pages/TasksPage/TasksPage"; import type { FC } from "react"; import { useQuery } from "react-query"; @@ -161,7 +160,7 @@ const NavItems: FC = ({ className, user }) => { if (location.pathname.startsWith("/@")) { isActive = true; } - return cn(linkStyles.default, isActive ? linkStyles.active : ""); + return cn(linkStyles.default, { [linkStyles.active]: isActive }); }} to="/workspaces" > @@ -169,7 +168,7 @@ const NavItems: FC = ({ className, user }) => { { - return cn(linkStyles.default, isActive ? linkStyles.active : ""); + return cn(linkStyles.default, { [linkStyles.active]: isActive }); }} to="/templates" > @@ -198,37 +197,31 @@ const TasksNavItem: FC = ({ user }) => { avatarUrl: user.avatar_url, }, }; - const { data: idleTasks } = useQuery({ + const { data: idleCount } = useQuery({ queryKey: ["tasks", filter], queryFn: () => data.fetchTasks(filter), refetchInterval: 1_000 * 60, enabled: canSeeTasks, refetchOnWindowFocus: true, + initialData: [], select: (data) => - data.filter((task) => task.workspace.latest_app_status?.state === "idle"), + data.filter((task) => task.workspace.latest_app_status?.state === "idle") + .length, }); if (!canSeeTasks) { return null; } - const search = - idleTasks && idleTasks.length > 0 - ? new URLSearchParams({ - username: user.username, - tab: "waiting-for-input", - }).toString() - : undefined; - return ( { - return cn(linkStyles.default, isActive ? linkStyles.active : ""); + return cn(linkStyles.default, { [linkStyles.active]: isActive }); }} > Tasks - {idleTasks && idleTasks.length > 0 && ( + {idleCount > 0 && ( @@ -236,12 +229,12 @@ const TasksNavItem: FC = ({ user }) => { variant="info" size="xs" className="ml-2" - aria-label={idleTasksLabel(idleTasks)} + aria-label={idleTasksLabel(idleCount)} > - {idleTasks.length} + {idleCount} - {idleTasksLabel(idleTasks)} + {idleTasksLabel(idleCount)} )} @@ -249,7 +242,6 @@ const TasksNavItem: FC = ({ user }) => { ); }; -function idleTasksLabel(idleTasks: Task[]) { - const count = idleTasks.length; +function idleTasksLabel(count: number) { return `You have ${count} ${count === 1 ? "task" : "tasks"} waiting for input`; } From a0995bf04162252447defb2bdf027b1c0d8d94b4 Mon Sep 17 00:00:00 2001 From: Bruno Quaresma Date: Fri, 22 Aug 2025 14:21:34 +0000 Subject: [PATCH 5/6] Fix merge issues --- site/src/modules/dashboard/Navbar/NavbarView.tsx | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/site/src/modules/dashboard/Navbar/NavbarView.tsx b/site/src/modules/dashboard/Navbar/NavbarView.tsx index 8b8c97bcea262..0cafaa8fdd46f 100644 --- a/site/src/modules/dashboard/Navbar/NavbarView.tsx +++ b/site/src/modules/dashboard/Navbar/NavbarView.tsx @@ -14,7 +14,6 @@ import type { ProxyContextValue } from "contexts/ProxyContext"; import { useWebpushNotifications } from "contexts/useWebpushNotifications"; import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata"; import { NotificationsInbox } from "modules/notifications/NotificationsInbox/NotificationsInbox"; -import { data } from "pages/TasksPage/TasksPage"; import type { FC } from "react"; import { useQuery } from "react-query"; import { NavLink, useLocation } from "react-router"; @@ -191,15 +190,11 @@ const TasksNavItem: FC = ({ user }) => { process.env.STORYBOOK, ); const filter = { - user: { - label: user.username, - value: user.username, - avatarUrl: user.avatar_url, - }, + username: user.username, }; const { data: idleCount } = useQuery({ queryKey: ["tasks", filter], - queryFn: () => data.fetchTasks(filter), + queryFn: () => API.experimental.getTasks(filter), refetchInterval: 1_000 * 60, enabled: canSeeTasks, refetchOnWindowFocus: true, From cbef90935d3cc5f3865acb9aba05c8e148e9430b Mon Sep 17 00:00:00 2001 From: Bruno Quaresma Date: Fri, 22 Aug 2025 14:28:30 +0000 Subject: [PATCH 6/6] Fix storybook --- .../modules/dashboard/Navbar/NavbarView.stories.tsx | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/site/src/modules/dashboard/Navbar/NavbarView.stories.tsx b/site/src/modules/dashboard/Navbar/NavbarView.stories.tsx index 40c278187aba3..6b44ab0911024 100644 --- a/site/src/modules/dashboard/Navbar/NavbarView.stories.tsx +++ b/site/src/modules/dashboard/Navbar/NavbarView.stories.tsx @@ -10,12 +10,8 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { userEvent, within } from "storybook/test"; import { NavbarView } from "./NavbarView"; -const filter = { - user: { - label: MockUserOwner.username, - value: MockUserOwner.username, - avatarUrl: MockUserOwner.avatar_url, - }, +const tasksFilter = { + username: MockUserOwner.username, }; const meta: Meta = { @@ -25,7 +21,7 @@ const meta: Meta = { layout: "fullscreen", queries: [ { - key: ["tasks", filter], + key: ["tasks", tasksFilter], data: [], }, ], @@ -105,7 +101,7 @@ export const IdleTasks: Story = { parameters: { queries: [ { - key: ["tasks", filter], + key: ["tasks", tasksFilter], data: [ { prompt: "Task 1", pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy