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

Commitd9b00e4

Browse files
feat: add inline actions into workspaces table (#17636)
Related to#17311This PR adds inline actions in the workspaces page. It is a bitdifferent of the [originaldesign](https://www.figma.com/design/OR75XeUI0Z3ksqt1mHsNQw/Workspace-views?node-id=656-3979&m=dev)because I'm splitting the work into three phases that I will explain inmore details in the demo.https://github.com/user-attachments/assets/6383375e-ed10-45d1-b5d5-b4421e86d158
1 parent5f516ed commitd9b00e4

19 files changed

+495
-127
lines changed

‎site/src/api/queries/workspaces.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -139,13 +139,9 @@ function workspacesKey(config: WorkspacesRequest = {}) {
139139
}
140140

141141
exportfunctionworkspaces(config:WorkspacesRequest={}){
142-
// Duplicates some of the work from workspacesKey, but that felt better than
143-
// letting invisible properties sneak into the query logic
144-
const{ q, limit}=config;
145-
146142
return{
147143
queryKey:workspacesKey(config),
148-
queryFn:()=>API.getWorkspaces({ q, limit}),
144+
queryFn:()=>API.getWorkspaces(config),
149145
}asconstsatisfiesQueryOptions<WorkspacesResponse>;
150146
}
151147

@@ -281,7 +277,10 @@ const updateWorkspaceBuild = async (
281277
build.workspace_owner_name,
282278
build.workspace_name,
283279
);
284-
constpreviousData=queryClient.getQueryData(workspaceKey)asWorkspace;
280+
constpreviousData=queryClient.getQueryData<Workspace>(workspaceKey);
281+
if(!previousData){
282+
return;
283+
}
285284

286285
// Check if the build returned is newer than the previous build that could be
287286
// updated from web socket

‎site/src/components/Dialogs/Dialog.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,14 @@ export const DialogActionButtons: FC<DialogActionButtonsProps> = ({
3535
return(
3636
<>
3737
{onCancel&&(
38-
<Buttondisabled={confirmLoading}onClick={onCancel}variant="outline">
38+
<Button
39+
disabled={confirmLoading}
40+
onClick={(e)=>{
41+
e.stopPropagation();
42+
onCancel();
43+
}}
44+
variant="outline"
45+
>
3946
{cancelText}
4047
</Button>
4148
)}

‎site/src/hooks/usePagination.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export const usePagination = ({
99
const[searchParams,setSearchParams]=searchParamsResult;
1010
constpage=searchParams.get("page") ?Number(searchParams.get("page")) :1;
1111
constlimit=DEFAULT_RECORDS_PER_PAGE;
12-
constoffset=page<=0 ?0 :(page-1)*limit;
12+
constoffset=calcOffset(page,limit);
1313

1414
constgoToPage=(page:number)=>{
1515
searchParams.set("page",page.toString());
@@ -23,3 +23,7 @@ export const usePagination = ({
2323
offset,
2424
};
2525
};
26+
27+
exportconstcalcOffset=(page:number,limit:number)=>{
28+
returnpage<=0 ?0 :(page-1)*limit;
29+
};
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import{MissingBuildParameters}from"api/api";
2+
import{updateWorkspace}from"api/queries/workspaces";
3+
importtype{
4+
TemplateVersion,
5+
Workspace,
6+
WorkspaceBuild,
7+
WorkspaceBuildParameter,
8+
}from"api/typesGenerated";
9+
import{ConfirmDialog}from"components/Dialogs/ConfirmDialog/ConfirmDialog";
10+
import{MemoizedInlineMarkdown}from"components/Markdown/Markdown";
11+
import{UpdateBuildParametersDialog}from"pages/WorkspacePage/UpdateBuildParametersDialog";
12+
import{typeFC,useState}from"react";
13+
import{useMutation,useQueryClient}from"react-query";
14+
15+
typeUseWorkspaceUpdateOptions={
16+
workspace:Workspace;
17+
latestVersion:TemplateVersion|undefined;
18+
onSuccess?:(build:WorkspaceBuild)=>void;
19+
onError?:(error:unknown)=>void;
20+
};
21+
22+
typeUseWorkspaceUpdateResult={
23+
update:()=>void;
24+
isUpdating:boolean;
25+
dialogs:{
26+
updateConfirmation:UpdateConfirmationDialogProps;
27+
missingBuildParameters:MissingBuildParametersDialogProps;
28+
};
29+
};
30+
31+
exportconstuseWorkspaceUpdate=({
32+
workspace,
33+
latestVersion,
34+
onSuccess,
35+
onError,
36+
}:UseWorkspaceUpdateOptions):UseWorkspaceUpdateResult=>{
37+
constqueryClient=useQueryClient();
38+
const[isConfirmingUpdate,setIsConfirmingUpdate]=useState(false);
39+
40+
constupdateWorkspaceOptions=updateWorkspace(workspace,queryClient);
41+
constupdateWorkspaceMutation=useMutation({
42+
...updateWorkspaceOptions,
43+
onSuccess:(build:WorkspaceBuild)=>{
44+
updateWorkspaceOptions.onSuccess(build);
45+
onSuccess?.(build);
46+
},
47+
onError,
48+
});
49+
50+
constupdate=()=>{
51+
setIsConfirmingUpdate(true);
52+
};
53+
54+
constconfirmUpdate=(buildParameters:WorkspaceBuildParameter[]=[])=>{
55+
updateWorkspaceMutation.mutate(buildParameters);
56+
setIsConfirmingUpdate(false);
57+
};
58+
59+
return{
60+
update,
61+
isUpdating:updateWorkspaceMutation.isLoading,
62+
dialogs:{
63+
updateConfirmation:{
64+
open:isConfirmingUpdate,
65+
onClose:()=>setIsConfirmingUpdate(false),
66+
onConfirm:()=>confirmUpdate(),
67+
latestVersion,
68+
},
69+
missingBuildParameters:{
70+
error:updateWorkspaceMutation.error,
71+
onClose:()=>{
72+
updateWorkspaceMutation.reset();
73+
},
74+
onUpdate:(buildParameters:WorkspaceBuildParameter[])=>{
75+
if(updateWorkspaceMutation.errorinstanceofMissingBuildParameters){
76+
confirmUpdate(buildParameters);
77+
}
78+
},
79+
},
80+
},
81+
};
82+
};
83+
84+
typeWorkspaceUpdateDialogsProps={
85+
updateConfirmation:UpdateConfirmationDialogProps;
86+
missingBuildParameters:MissingBuildParametersDialogProps;
87+
};
88+
89+
exportconstWorkspaceUpdateDialogs:FC<WorkspaceUpdateDialogsProps>=({
90+
updateConfirmation,
91+
missingBuildParameters,
92+
})=>{
93+
return(
94+
<>
95+
<UpdateConfirmationDialog{...updateConfirmation}/>
96+
<MissingBuildParametersDialog{...missingBuildParameters}/>
97+
</>
98+
);
99+
};
100+
101+
typeUpdateConfirmationDialogProps={
102+
open:boolean;
103+
onClose:()=>void;
104+
onConfirm:()=>void;
105+
latestVersion?:TemplateVersion;
106+
};
107+
108+
constUpdateConfirmationDialog:FC<UpdateConfirmationDialogProps>=({
109+
latestVersion,
110+
...dialogProps
111+
})=>{
112+
return(
113+
<ConfirmDialog
114+
{...dialogProps}
115+
hideCancel={false}
116+
title="Update workspace?"
117+
confirmText="Update"
118+
description={
119+
<divclassName="flex flex-col gap-2">
120+
<p>
121+
Updating your workspace will start the workspace on the latest
122+
template version. This can{" "}
123+
<strong>delete non-persistent data</strong>.
124+
</p>
125+
{latestVersion?.message&&(
126+
<MemoizedInlineMarkdownallowedElements={["ol","ul","li"]}>
127+
{latestVersion.message}
128+
</MemoizedInlineMarkdown>
129+
)}
130+
</div>
131+
}
132+
/>
133+
);
134+
};
135+
136+
typeMissingBuildParametersDialogProps={
137+
error:unknown;
138+
onClose:()=>void;
139+
onUpdate:(buildParameters:WorkspaceBuildParameter[])=>void;
140+
};
141+
142+
constMissingBuildParametersDialog:FC<MissingBuildParametersDialogProps>=({
143+
error,
144+
...dialogProps
145+
})=>{
146+
return(
147+
<UpdateBuildParametersDialog
148+
missedParameters={
149+
errorinstanceofMissingBuildParameters ?error.parameters :[]
150+
}
151+
open={errorinstanceofMissingBuildParameters}
152+
{...dialogProps}
153+
/>
154+
);
155+
};

‎site/src/pages/WorkspacePage/WorkspaceActions/constants.tsrenamed to‎site/src/modules/workspaces/actions.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ const actionTypes = [
3434

3535
exporttypeActionType=(typeofactionTypes)[number];
3636

37+
typeActionPermissions={
38+
canDebug:boolean;
39+
isOwner:boolean;
40+
};
41+
3742
typeWorkspaceAbilities={
3843
actions:readonlyActionType[];
3944
canCancel:boolean;
@@ -42,8 +47,11 @@ type WorkspaceAbilities = {
4247

4348
exportconstabilitiesByWorkspaceStatus=(
4449
workspace:Workspace,
45-
canDebug:boolean,
50+
permissions:ActionPermissions,
4651
):WorkspaceAbilities=>{
52+
consthasPermissionToCancel=
53+
workspace.template_allow_user_cancel_workspace_jobs||permissions.isOwner;
54+
4755
if(workspace.dormant_at){
4856
return{
4957
actions:["activate"],
@@ -58,7 +66,7 @@ export const abilitiesByWorkspaceStatus = (
5866
case"starting":{
5967
return{
6068
actions:["starting"],
61-
canCancel:true,
69+
canCancel:hasPermissionToCancel,
6270
canAcceptJobs:false,
6371
};
6472
}
@@ -83,7 +91,7 @@ export const abilitiesByWorkspaceStatus = (
8391
case"stopping":{
8492
return{
8593
actions:["stopping"],
86-
canCancel:true,
94+
canCancel:hasPermissionToCancel,
8795
canAcceptJobs:false,
8896
};
8997
}
@@ -115,7 +123,7 @@ export const abilitiesByWorkspaceStatus = (
115123
case"failed":{
116124
constactions:ActionType[]=["retry"];
117125

118-
if(canDebug){
126+
if(permissions.canDebug){
119127
actions.push("debug");
120128
}
121129

‎site/src/pages/TemplateSettingsPage/TemplateSchedulePage/useWorkspacesToBeDeleted.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
importtype{Template,Workspace}from"api/typesGenerated";
22
import{compareAsc}from"date-fns";
3+
import{calcOffset}from"hooks/usePagination";
34
import{useWorkspacesData}from"pages/WorkspacesPage/data";
45
importtype{TemplateScheduleFormValues}from"./formHelpers";
56

@@ -9,9 +10,9 @@ export const useWorkspacesToGoDormant = (
910
fromDate:Date,
1011
)=>{
1112
const{ data}=useWorkspacesData({
12-
page:0,
13+
offset:calcOffset(0,0),
1314
limit:0,
14-
query:`template:${template.name}`,
15+
q:`template:${template.name}`,
1516
});
1617

1718
returndata?.workspaces?.filter((workspace:Workspace)=>{
@@ -40,9 +41,9 @@ export const useWorkspacesToBeDeleted = (
4041
fromDate:Date,
4142
)=>{
4243
const{ data}=useWorkspacesData({
43-
page:0,
44+
offset:calcOffset(0,0),
4445
limit:0,
45-
query:`template:${template.name} dormant:true`,
46+
q:`template:${template.name} dormant:true`,
4647
});
4748
returndata?.workspaces?.filter((workspace:Workspace)=>{
4849
if(!workspace.dormant_at||!formValues.time_til_dormant_autodelete_ms){

‎site/src/pages/WorkspacePage/Workspace.stories.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { Meta, StoryObj } from "@storybook/react";
33
importtype{ProvisionerJobLog}from"api/typesGenerated";
44
import{ProxyContext,getPreferredProxy}from"contexts/ProxyContext";
55
import*asMocksfrom"testHelpers/entities";
6-
import{withDashboardProvider}from"testHelpers/storybook";
6+
import{withAuthProvider,withDashboardProvider}from"testHelpers/storybook";
77
import{Workspace}from"./Workspace";
88
importtype{WorkspacePermissions}from"./permissions";
99

@@ -40,8 +40,10 @@ const meta: Meta<typeof Workspace> = {
4040
data:Mocks.MockListeningPortsResponse,
4141
},
4242
],
43+
user:Mocks.MockUser,
4344
},
4445
decorators:[
46+
withAuthProvider,
4547
withDashboardProvider,
4648
(Story)=>(
4749
<ProxyContext.Provider

‎site/src/pages/WorkspacePage/Workspace.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ export interface WorkspaceProps {
5353
buildLogs?:TypesGen.ProvisionerJobLog[];
5454
latestVersion?:TypesGen.TemplateVersion;
5555
permissions:WorkspacePermissions;
56-
isOwner:boolean;
5756
timings?:TypesGen.WorkspaceBuildTimings;
5857
}
5958

@@ -86,7 +85,6 @@ export const Workspace: FC<WorkspaceProps> = ({
8685
buildLogs,
8786
latestVersion,
8887
permissions,
89-
isOwner,
9088
timings,
9189
})=>{
9290
constnavigate=useNavigate();
@@ -161,7 +159,6 @@ export const Workspace: FC<WorkspaceProps> = ({
161159
isUpdating={isUpdating}
162160
isRestarting={isRestarting}
163161
canUpdateWorkspace={permissions.updateWorkspace}
164-
isOwner={isOwner}
165162
template={template}
166163
permissions={permissions}
167164
latestVersion={latestVersion}

‎site/src/pages/WorkspacePage/WorkspaceActions/WorkspaceActions.stories.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { expect, userEvent, within } from "@storybook/test";
33
import{agentLogsKey,buildLogsKey}from"api/queries/workspaces";
44
import*asMocksfrom"testHelpers/entities";
55
import{
6+
withAuthProvider,
67
withDashboardProvider,
78
withDesktopViewport,
89
}from"testHelpers/storybook";
@@ -14,7 +15,10 @@ const meta: Meta<typeof WorkspaceActions> = {
1415
args:{
1516
isUpdating:false,
1617
},
17-
decorators:[withDashboardProvider,withDesktopViewport],
18+
decorators:[withDashboardProvider,withDesktopViewport,withAuthProvider],
19+
parameters:{
20+
user:Mocks.MockUser,
21+
},
1822
};
1923

2024
exportdefaultmeta;
@@ -163,14 +167,15 @@ export const CancelShownForOwner: Story = {
163167
...Mocks.MockStartingWorkspace,
164168
template_allow_user_cancel_workspace_jobs:false,
165169
},
166-
isOwner:true,
167170
},
168171
};
169172

170173
exportconstCancelShownForUser:Story={
171174
args:{
172175
workspace:Mocks.MockStartingWorkspace,
173-
isOwner:false,
176+
},
177+
parameters:{
178+
user:Mocks.MockUser2,
174179
},
175180
};
176181

@@ -180,7 +185,9 @@ export const CancelHiddenForUser: Story = {
180185
...Mocks.MockStartingWorkspace,
181186
template_allow_user_cancel_workspace_jobs:false,
182187
},
183-
isOwner:false,
188+
},
189+
parameters:{
190+
user:Mocks.MockUser2,
184191
},
185192
};
186193

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp