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

Commitdd83a00

Browse files
committed
feat: add experimental workspace parameters page for dynamic params
1 parentdc21016 commitdd83a00

File tree

5 files changed

+536
-5
lines changed

5 files changed

+536
-5
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import{ErrorAlert}from"components/Alert/ErrorAlert";
2+
import{Loader}from"components/Loader/Loader";
3+
import{useDashboard}from"modules/dashboard/useDashboard";
4+
importtype{FC}from"react";
5+
import{useQuery}from"react-query";
6+
import{ExperimentalFormContext}from"../../CreateWorkspacePage/ExperimentalFormContext";
7+
import{useWorkspaceSettings}from"../WorkspaceSettingsLayout";
8+
importWorkspaceParametersPagefrom"./WorkspaceParametersPage";
9+
importWorkspaceParametersPageExperimentalfrom"./WorkspaceParametersPageExperimental";
10+
11+
constWorkspaceParametersExperimentRouter:FC=()=>{
12+
const{ experiments}=useDashboard();
13+
constworkspace=useWorkspaceSettings();
14+
constdynamicParametersEnabled=experiments.includes("dynamic-parameters");
15+
16+
constoptOutQuery=useQuery(
17+
dynamicParametersEnabled
18+
?{
19+
queryKey:[
20+
"workspace",
21+
workspace.id,
22+
"template_id",
23+
workspace.template_id,
24+
"optOut",
25+
],
26+
queryFn:()=>({
27+
templateId:workspace.template_id,
28+
workspaceId:workspace.id,
29+
optedOut:
30+
localStorage.getItem(optOutKey(workspace.template_id))==="true",
31+
}),
32+
}
33+
:{enabled:false},
34+
);
35+
36+
if(dynamicParametersEnabled){
37+
if(optOutQuery.isLoading){
38+
return<Loader/>;
39+
}
40+
if(!optOutQuery.data){
41+
return<ErrorAlerterror={optOutQuery.error}/>;
42+
}
43+
44+
consttoggleOptedOut=()=>{
45+
constkey=optOutKey(optOutQuery.data.templateId);
46+
constcurrent=localStorage.getItem(key)==="true";
47+
localStorage.setItem(key,(!current).toString());
48+
optOutQuery.refetch();
49+
};
50+
51+
return(
52+
<ExperimentalFormContext.Providervalue={{ toggleOptedOut}}>
53+
{optOutQuery.data.optedOut ?(
54+
<WorkspaceParametersPage/>
55+
) :(
56+
<WorkspaceParametersPageExperimental/>
57+
)}
58+
</ExperimentalFormContext.Provider>
59+
);
60+
}
61+
62+
return<WorkspaceParametersPage/>;
63+
};
64+
65+
exportdefaultWorkspaceParametersExperimentRouter;
66+
67+
constoptOutKey=(id:string)=>`parameters.${id}.optOut`;

‎site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.tsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ import { isApiValidationError } from "api/errors";
44
import{checkAuthorization}from"api/queries/authCheck";
55
importtype{Workspace,WorkspaceBuildParameter}from"api/typesGenerated";
66
import{ErrorAlert}from"components/Alert/ErrorAlert";
7+
import{ButtonasShadcnButton}from"components/Button/Button";
78
import{EmptyState}from"components/EmptyState/EmptyState";
89
import{Loader}from"components/Loader/Loader";
910
import{PageHeader,PageHeaderTitle}from"components/PageHeader/PageHeader";
1011
import{ExternalLinkIcon}from"lucide-react";
11-
importtype{FC}from"react";
12+
import{typeFC,useContext}from"react";
1213
import{Helmet}from"react-helmet-async";
1314
import{useMutation,useQuery}from"react-query";
1415
import{useNavigate}from"react-router-dom";
@@ -18,6 +19,7 @@ import {
1819
typeWorkspacePermissions,
1920
workspaceChecks,
2021
}from"../../../modules/workspaces/permissions";
22+
import{ExperimentalFormContext}from"../../CreateWorkspacePage/ExperimentalFormContext";
2123
import{useWorkspaceSettings}from"../WorkspaceSettingsLayout";
2224
import{
2325
WorkspaceParametersForm,
@@ -112,9 +114,23 @@ export const WorkspaceParametersPageView: FC<
112114
isSubmitting,
113115
onCancel,
114116
})=>{
117+
constexperimentalFormContext=useContext(ExperimentalFormContext);
115118
return(
116119
<>
117-
<PageHeadercss={{paddingTop:0}}>
120+
<PageHeader
121+
css={{paddingTop:0}}
122+
actions={
123+
experimentalFormContext&&(
124+
<ShadcnButton
125+
size="sm"
126+
variant="outline"
127+
onClick={experimentalFormContext.toggleOptedOut}
128+
>
129+
Try out the new workspace parameters ✨
130+
</ShadcnButton>
131+
)
132+
}
133+
>
118134
<PageHeaderTitle>Workspace parameters</PageHeaderTitle>
119135
</PageHeader>
120136

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
import{API}from"api/api";
2+
import{DetailedError}from"api/errors";
3+
import{checkAuthorization}from"api/queries/authCheck";
4+
importtype{
5+
DynamicParametersRequest,
6+
DynamicParametersResponse,
7+
WorkspaceBuildParameter,
8+
}from"api/typesGenerated";
9+
import{ErrorAlert}from"components/Alert/ErrorAlert";
10+
import{Button}from"components/Button/Button";
11+
import{EmptyState}from"components/EmptyState/EmptyState";
12+
import{FeatureStageBadge}from"components/FeatureStageBadge/FeatureStageBadge";
13+
import{Link}from"components/Link/Link";
14+
import{Loader}from"components/Loader/Loader";
15+
importtype{FC}from"react";
16+
import{
17+
useCallback,
18+
useContext,
19+
useEffect,
20+
useMemo,
21+
useRef,
22+
useState,
23+
}from"react";
24+
import{Helmet}from"react-helmet-async";
25+
import{useMutation,useQuery}from"react-query";
26+
import{useNavigate}from"react-router-dom";
27+
import{docs}from"utils/docs";
28+
import{pageTitle}from"utils/page";
29+
import{
30+
typeWorkspacePermissions,
31+
workspaceChecks,
32+
}from"../../../modules/workspaces/permissions";
33+
import{ExperimentalFormContext}from"../../CreateWorkspacePage/ExperimentalFormContext";
34+
import{useWorkspaceSettings}from"../WorkspaceSettingsLayout";
35+
import{WorkspaceParametersPageViewExperimental}from"./WorkspaceParametersPageViewExperimental";
36+
37+
constWorkspaceParametersPageExperimental:FC=()=>{
38+
constworkspace=useWorkspaceSettings();
39+
constnavigate=useNavigate();
40+
constexperimentalFormContext=useContext(ExperimentalFormContext);
41+
42+
const[currentResponse,setCurrentResponse]=
43+
useState<DynamicParametersResponse|null>(null);
44+
const[wsResponseId,setWSResponseId]=useState<number>(-1);
45+
constws=useRef<WebSocket|null>(null);
46+
const[wsError,setWsError]=useState<Error|null>(null);
47+
48+
constonMessage=useCallback((response:DynamicParametersResponse)=>{
49+
setCurrentResponse((prev)=>{
50+
if(prev?.id===response.id){
51+
returnprev;
52+
}
53+
returnresponse;
54+
});
55+
},[]);
56+
57+
useEffect(()=>{
58+
if(!workspace.latest_build.template_version_id)return;
59+
60+
constsocket=API.templateVersionDynamicParameters(
61+
workspace.owner_id,
62+
workspace.latest_build.template_version_id,
63+
{
64+
onMessage,
65+
onError:(error)=>{
66+
setWsError(error);
67+
},
68+
onClose:()=>{
69+
if(ws.current===socket){
70+
setWsError(
71+
newDetailedError(
72+
"Websocket connection for dynamic parameters unexpectedly closed.",
73+
"Refresh the page to reset the form.",
74+
),
75+
);
76+
}
77+
},
78+
},
79+
);
80+
81+
ws.current=socket;
82+
83+
return()=>{
84+
socket.close();
85+
};
86+
},[
87+
workspace.owner_id,
88+
workspace.latest_build.template_version_id,
89+
onMessage,
90+
]);
91+
92+
constsendMessage=useCallback((formValues:Record<string,string>)=>{
93+
setWSResponseId((prevId)=>{
94+
constrequest:DynamicParametersRequest={
95+
id:prevId+1,
96+
inputs:formValues,
97+
};
98+
if(ws.current&&ws.current.readyState===WebSocket.OPEN){
99+
ws.current.send(JSON.stringify(request));
100+
returnprevId+1;
101+
}
102+
returnprevId;
103+
});
104+
},[]);
105+
106+
constupdateParameters=useMutation({
107+
mutationFn:(buildParameters:WorkspaceBuildParameter[])=>
108+
API.postWorkspaceBuild(workspace.id,{
109+
transition:"start",
110+
rich_parameter_values:buildParameters,
111+
}),
112+
onSuccess:()=>{
113+
navigate(`/@${workspace.owner_name}/${workspace.name}`);
114+
},
115+
});
116+
117+
constchecks=workspace ?workspaceChecks(workspace) :{};
118+
constpermissionsQuery=useQuery({
119+
...checkAuthorization({ checks}),
120+
enabled:workspace!==undefined,
121+
});
122+
constpermissions=permissionsQuery.dataasWorkspacePermissions|undefined;
123+
constcanChangeVersions=Boolean(permissions?.updateWorkspaceVersion);
124+
125+
consthandleSubmit=(values:{
126+
rich_parameter_values:WorkspaceBuildParameter[];
127+
})=>{
128+
if(!currentResponse||!currentResponse.parameters){
129+
return;
130+
}
131+
132+
// Only submit mutable parameters
133+
constonlyMutableValues=currentResponse.parameters
134+
.filter((p)=>p.mutable)
135+
.map((p)=>{
136+
constvalue=values.rich_parameter_values.find(
137+
(v)=>v.name===p.name,
138+
);
139+
if(!value){
140+
thrownewError(`Missing value for parameter${p.name}`);
141+
}
142+
returnvalue;
143+
});
144+
145+
updateParameters.mutate(onlyMutableValues);
146+
};
147+
148+
constsortedParams=useMemo(()=>{
149+
if(!currentResponse?.parameters){
150+
return[];
151+
}
152+
return[...currentResponse.parameters].sort((a,b)=>a.order-b.order);
153+
},[currentResponse?.parameters]);
154+
155+
consterror=wsError||updateParameters.error;
156+
157+
if(
158+
!currentResponse||
159+
(ws.current&&ws.current.readyState===WebSocket.CONNECTING)
160+
){
161+
return<Loader/>;
162+
}
163+
164+
return(
165+
<divclassName="flex flex-col gap-6 max-w-screen-md mx-auto">
166+
<Helmet>
167+
<title>{pageTitle(workspace.name,"Parameters")}</title>
168+
</Helmet>
169+
170+
<headerclassName="flex flex-col items-start gap-2">
171+
<spanclassName="flex flex-row items-center gap-2">
172+
<h1className="text-3xl m-0">Workspace parameters</h1>
173+
<FeatureStageBadgecontentType={"beta"}/>
174+
</span>
175+
{experimentalFormContext&&(
176+
<Button
177+
size="sm"
178+
variant="outline"
179+
onClick={experimentalFormContext.toggleOptedOut}
180+
>
181+
Go back to the classic workspace parameters view
182+
</Button>
183+
)}
184+
</header>
185+
186+
{Boolean(error)&&<ErrorAlerterror={error}/>}
187+
188+
{sortedParams.length>0 ?(
189+
<WorkspaceParametersPageViewExperimental
190+
workspace={workspace}
191+
canChangeVersions={canChangeVersions}
192+
parameters={sortedParams}
193+
diagnostics={currentResponse.diagnostics}
194+
isSubmitting={updateParameters.isLoading}
195+
onSubmit={handleSubmit}
196+
onCancel={()=>
197+
navigate(`/@${workspace.owner_name}/${workspace.name}`)
198+
}
199+
sendMessage={sendMessage}
200+
/>
201+
) :(
202+
<EmptyState
203+
className="border border-border border-solid rounded-md"
204+
message="This workspace has no parameters"
205+
cta={
206+
<Link
207+
href={docs("/admin/templates/extending-templates/parameters")}
208+
>
209+
Learn more about parameters
210+
</Link>
211+
}
212+
/>
213+
)}
214+
</div>
215+
);
216+
};
217+
218+
exportdefaultWorkspaceParametersPageExperimental;

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp