Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitcde5b62

Browse files
feat: display the number of idle tasks in the navbar (#19471)
Depends on:#19377Closes#19323**Screenshot:**<img width="1511" height="777" alt="Screenshot 2025-08-21 at 11 52 21"src="https://github.com/user-attachments/assets/be04e507-bf04-47d0-8748-2f71b93b5685"/>**Screen recording:**https://github.com/user-attachments/assets/f70b34fe-952b-427b-9bc9-71961ca23201<!-- This is an auto-generated comment: release notes by coderabbit.ai-->## Summary by CodeRabbit- New Features- Added a Tasks navigation item showing a badge with the number of idletasks and a tooltip: “You have X tasks waiting for input.”- Improvements - Fetches per-user tasks with periodic refresh for up-to-date counts.- Updated active styling for the Tasks link for clearer navigationstate. - User menu now always appears on medium+ screens.- Tests- Expanded Storybook with preloaded, user-filtered task scenarios toshowcase idle/task states.<!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent6fbe777 commitcde5b62

File tree

2 files changed

+139
-28
lines changed

2 files changed

+139
-28
lines changed

‎site/src/modules/dashboard/Navbar/NavbarView.stories.tsx‎

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,31 @@
11
import{chromaticWithTablet}from"testHelpers/chromatic";
2-
import{MockUserMember,MockUserOwner}from"testHelpers/entities";
2+
import{
3+
MockUserMember,
4+
MockUserOwner,
5+
MockWorkspace,
6+
MockWorkspaceAppStatus,
7+
}from"testHelpers/entities";
38
import{withDashboardProvider}from"testHelpers/storybook";
49
importtype{Meta,StoryObj}from"@storybook/react-vite";
510
import{userEvent,within}from"storybook/test";
611
import{NavbarView}from"./NavbarView";
712

13+
consttasksFilter={
14+
username:MockUserOwner.username,
15+
};
16+
817
constmeta:Meta<typeofNavbarView>={
918
title:"modules/dashboard/NavbarView",
10-
parameters:{chromatic:chromaticWithTablet,layout:"fullscreen"},
19+
parameters:{
20+
chromatic:chromaticWithTablet,
21+
layout:"fullscreen",
22+
queries:[
23+
{
24+
key:["tasks",tasksFilter],
25+
data:[],
26+
},
27+
],
28+
},
1129
component:NavbarView,
1230
args:{
1331
user:MockUserOwner,
@@ -78,3 +96,36 @@ export const CustomLogo: Story = {
7896
logo_url:"/icon/github.svg",
7997
},
8098
};
99+
100+
exportconstIdleTasks:Story={
101+
parameters:{
102+
queries:[
103+
{
104+
key:["tasks",tasksFilter],
105+
data:[
106+
{
107+
prompt:"Task 1",
108+
workspace:{
109+
...MockWorkspace,
110+
latest_app_status:{
111+
...MockWorkspaceAppStatus,
112+
state:"idle",
113+
},
114+
},
115+
},
116+
{
117+
prompt:"Task 2",
118+
workspace:MockWorkspace,
119+
},
120+
{
121+
prompt:"Task 3",
122+
workspace:{
123+
...MockWorkspace,
124+
latest_app_status:MockWorkspaceAppStatus,
125+
},
126+
},
127+
],
128+
},
129+
],
130+
},
131+
};

‎site/src/modules/dashboard/Navbar/NavbarView.tsx‎

Lines changed: 86 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
11
import{API}from"api/api";
22
importtype*asTypesGenfrom"api/typesGenerated";
3+
import{Badge}from"components/Badge/Badge";
34
import{Button}from"components/Button/Button";
45
import{ExternalImage}from"components/ExternalImage/ExternalImage";
56
import{CoderIcon}from"components/Icons/CoderIcon";
7+
import{
8+
Tooltip,
9+
TooltipContent,
10+
TooltipProvider,
11+
TooltipTrigger,
12+
}from"components/Tooltip/Tooltip";
613
importtype{ProxyContextValue}from"contexts/ProxyContext";
714
import{useWebpushNotifications}from"contexts/useWebpushNotifications";
815
import{useEmbeddedMetadata}from"hooks/useEmbeddedMetadata";
916
import{NotificationsInbox}from"modules/notifications/NotificationsInbox/NotificationsInbox";
1017
importtype{FC}from"react";
18+
import{useQuery}from"react-query";
1119
import{NavLink,useLocation}from"react-router";
1220
import{cn}from"utils/cn";
1321
import{DeploymentDropdown}from"./DeploymentDropdown";
@@ -17,7 +25,7 @@ import { UserDropdown } from "./UserDropdown/UserDropdown";
1725

1826
interfaceNavbarViewProps{
1927
logo_url?:string;
20-
user?:TypesGen.User;
28+
user:TypesGen.User;
2129
buildInfo?:TypesGen.BuildInfoResponse;
2230
supportLinks?:readonlyTypesGen.LinkConfig[];
2331
onSignOut:()=>void;
@@ -60,7 +68,7 @@ export const NavbarView: FC<NavbarViewProps> = ({
6068
)}
6169
</NavLink>
6270

63-
<NavItemsclassName="ml-4"/>
71+
<NavItemsclassName="ml-4"user={user}/>
6472

6573
<divclassName="flex items-center gap-3 ml-auto">
6674
{proxyContextValue&&(
@@ -109,16 +117,14 @@ export const NavbarView: FC<NavbarViewProps> = ({
109117
}
110118
/>
111119

112-
{user&&(
113-
<divclassName="hidden md:block">
114-
<UserDropdown
115-
user={user}
116-
buildInfo={buildInfo}
117-
supportLinks={supportLinks}
118-
onSignOut={onSignOut}
119-
/>
120-
</div>
121-
)}
120+
<divclassName="hidden md:block">
121+
<UserDropdown
122+
user={user}
123+
buildInfo={buildInfo}
124+
supportLinks={supportLinks}
125+
onSignOut={onSignOut}
126+
/>
127+
</div>
122128

123129
<divclassName="md:hidden">
124130
<MobileMenu
@@ -140,11 +146,11 @@ export const NavbarView: FC<NavbarViewProps> = ({
140146

141147
interfaceNavItemsProps{
142148
className?:string;
149+
user:TypesGen.User;
143150
}
144151

145-
constNavItems:FC<NavItemsProps>=({ className})=>{
152+
constNavItems:FC<NavItemsProps>=({ className, user})=>{
146153
constlocation=useLocation();
147-
const{ metadata}=useEmbeddedMetadata();
148154

149155
return(
150156
<navclassName={cn("flex items-center gap-4 h-full",className)}>
@@ -153,30 +159,84 @@ const NavItems: FC<NavItemsProps> = ({ className }) => {
153159
if(location.pathname.startsWith("/@")){
154160
isActive=true;
155161
}
156-
returncn(linkStyles.default,isActive ?linkStyles.active :"");
162+
returncn(linkStyles.default,{[linkStyles.active]:isActive});
157163
}}
158164
to="/workspaces"
159165
>
160166
Workspaces
161167
</NavLink>
162168
<NavLink
163169
className={({ isActive})=>{
164-
returncn(linkStyles.default,isActive ?linkStyles.active :"");
170+
returncn(linkStyles.default,{[linkStyles.active]:isActive});
165171
}}
166172
to="/templates"
167173
>
168174
Templates
169175
</NavLink>
170-
{metadata["tasks-tab-visible"].value&&(
171-
<NavLink
172-
className={({ isActive})=>{
173-
returncn(linkStyles.default,isActive ?linkStyles.active :"");
174-
}}
175-
to="/tasks"
176-
>
177-
Tasks
178-
</NavLink>
179-
)}
176+
<TasksNavItemuser={user}/>
180177
</nav>
181178
);
182179
};
180+
181+
typeTasksNavItemProps={
182+
user:TypesGen.User;
183+
};
184+
185+
constTasksNavItem:FC<TasksNavItemProps>=({ user})=>{
186+
const{ metadata}=useEmbeddedMetadata();
187+
constcanSeeTasks=Boolean(
188+
metadata["tasks-tab-visible"].value||
189+
process.env.NODE_ENV==="development"||
190+
process.env.STORYBOOK,
191+
);
192+
constfilter={
193+
username:user.username,
194+
};
195+
const{data:idleCount}=useQuery({
196+
queryKey:["tasks",filter],
197+
queryFn:()=>API.experimental.getTasks(filter),
198+
refetchInterval:1_000*60,
199+
enabled:canSeeTasks,
200+
refetchOnWindowFocus:true,
201+
initialData:[],
202+
select:(data)=>
203+
data.filter((task)=>task.workspace.latest_app_status?.state==="idle")
204+
.length,
205+
});
206+
207+
if(!canSeeTasks){
208+
returnnull;
209+
}
210+
211+
return(
212+
<NavLink
213+
to="/tasks"
214+
className={({ isActive})=>{
215+
returncn(linkStyles.default,{[linkStyles.active]:isActive});
216+
}}
217+
>
218+
Tasks
219+
{idleCount>0&&(
220+
<TooltipProvider>
221+
<Tooltip>
222+
<TooltipTriggerasChild>
223+
<Badge
224+
variant="info"
225+
size="xs"
226+
className="ml-2"
227+
aria-label={idleTasksLabel(idleCount)}
228+
>
229+
{idleCount}
230+
</Badge>
231+
</TooltipTrigger>
232+
<TooltipContent>{idleTasksLabel(idleCount)}</TooltipContent>
233+
</Tooltip>
234+
</TooltipProvider>
235+
)}
236+
</NavLink>
237+
);
238+
};
239+
240+
functionidleTasksLabel(count:number){
241+
return`You have${count}${count===1 ?"task" :"tasks"} waiting for input`;
242+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp