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

Commitcb7ce18

Browse files
authored
feat: add experimental workspace parameters page for dynamic params (#17841)
![Screenshot 2025-05-20 at 22 2640](https://github.com/user-attachments/assets/639441d7-2349-4c92-a4ee-d8a5a724fe8e)
1 parent3a6d5f5 commitcb7ce18

File tree

7 files changed

+607
-61
lines changed

7 files changed

+607
-61
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
importtype*asTypesGenfrom"api/typesGenerated";
2+
import{useEffect,useRef}from"react";
3+
4+
importtype{PreviewParameter}from"api/typesGenerated";
5+
6+
typeUseSyncFormParametersProps={
7+
parameters:readonlyPreviewParameter[];
8+
formValues:readonlyTypesGen.WorkspaceBuildParameter[];
9+
setFieldValue:(
10+
field:string,
11+
value:TypesGen.WorkspaceBuildParameter[],
12+
)=>void;
13+
};
14+
15+
exportfunctionuseSyncFormParameters({
16+
parameters,
17+
formValues,
18+
setFieldValue,
19+
}:UseSyncFormParametersProps){
20+
// Form values only needs to be updated when parameters change
21+
// Keep track of form values in a ref to avoid unnecessary updates to rich_parameter_values
22+
constformValuesRef=useRef(formValues);
23+
24+
useEffect(()=>{
25+
formValuesRef.current=formValues;
26+
},[formValues]);
27+
28+
useEffect(()=>{
29+
if(!parameters)return;
30+
constcurrentFormValues=formValuesRef.current;
31+
32+
constnewParameterValues=parameters.map((param)=>({
33+
name:param.name,
34+
value:param.value.valid ?param.value.value :"",
35+
}));
36+
37+
constcurrentFormValuesMap=newMap(
38+
currentFormValues.map((value)=>[value.name,value.value]),
39+
);
40+
41+
constisChanged=
42+
currentFormValues.length!==newParameterValues.length||
43+
newParameterValues.some(
44+
(p)=>
45+
!currentFormValuesMap.has(p.name)||
46+
currentFormValuesMap.get(p.name)!==p.value,
47+
);
48+
49+
if(isChanged){
50+
setFieldValue("rich_parameter_values",newParameterValues);
51+
}
52+
},[parameters,setFieldValue]);
53+
}

‎site/src/pages/CreateWorkspacePage/CreateWorkspacePageViewExperimental.tsx

Lines changed: 1 addition & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { Switch } from "components/Switch/Switch";
2020
import{UserAutocomplete}from"components/UserAutocomplete/UserAutocomplete";
2121
import{typeFormikContextType,useFormik}from"formik";
2222
import{ArrowLeft,CircleAlert,TriangleAlert}from"lucide-react";
23+
import{useSyncFormParameters}from"modules/hooks/useSyncFormParameters";
2324
import{
2425
DynamicParameter,
2526
getInitialParameterValues,
@@ -656,52 +657,3 @@ const Diagnostics: FC<DiagnosticsProps> = ({ diagnostics }) => {
656657
</div>
657658
);
658659
};
659-
660-
typeUseSyncFormParametersProps={
661-
parameters:readonlyPreviewParameter[];
662-
formValues:readonlyTypesGen.WorkspaceBuildParameter[];
663-
setFieldValue:(
664-
field:string,
665-
value:TypesGen.WorkspaceBuildParameter[],
666-
)=>void;
667-
};
668-
669-
functionuseSyncFormParameters({
670-
parameters,
671-
formValues,
672-
setFieldValue,
673-
}:UseSyncFormParametersProps){
674-
// Form values only needs to be updated when parameters change
675-
// Keep track of form values in a ref to avoid unnecessary updates to rich_parameter_values
676-
constformValuesRef=useRef(formValues);
677-
678-
useEffect(()=>{
679-
formValuesRef.current=formValues;
680-
},[formValues]);
681-
682-
useEffect(()=>{
683-
if(!parameters)return;
684-
constcurrentFormValues=formValuesRef.current;
685-
686-
constnewParameterValues=parameters.map((param)=>{
687-
return{
688-
name:param.name,
689-
value:param.value.valid ?param.value.value :"",
690-
};
691-
});
692-
693-
constisChanged=
694-
currentFormValues.length!==newParameterValues.length||
695-
newParameterValues.some(
696-
(p)=>
697-
!currentFormValues.find(
698-
(formValue)=>
699-
formValue.name===p.name&&formValue.value===p.value,
700-
),
701-
);
702-
703-
if(isChanged){
704-
setFieldValue("rich_parameter_values",newParameterValues);
705-
}
706-
},[parameters,setFieldValue]);
707-
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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+
consttemplateId=workspace.template_id;
28+
constworkspaceId=workspace.id;
29+
constlocalStorageKey=optOutKey(templateId);
30+
conststoredOptOutString=localStorage.getItem(localStorageKey);
31+
32+
letoptOutResult:boolean;
33+
34+
if(storedOptOutString!==null){
35+
optOutResult=storedOptOutString==="true";
36+
}else{
37+
optOutResult=Boolean(
38+
workspace.template_use_classic_parameter_flow,
39+
);
40+
}
41+
42+
return{
43+
templateId,
44+
workspaceId,
45+
optedOut:optOutResult,
46+
};
47+
},
48+
}
49+
:{enabled:false},
50+
);
51+
52+
if(dynamicParametersEnabled){
53+
if(optOutQuery.isLoading){
54+
return<Loader/>;
55+
}
56+
if(!optOutQuery.data){
57+
return<ErrorAlerterror={optOutQuery.error}/>;
58+
}
59+
60+
consttoggleOptedOut=()=>{
61+
constkey=optOutKey(optOutQuery.data.templateId);
62+
conststoredValue=localStorage.getItem(key);
63+
64+
constcurrent=storedValue
65+
?storedValue==="true"
66+
:Boolean(workspace.template_use_classic_parameter_flow);
67+
68+
localStorage.setItem(key,(!current).toString());
69+
optOutQuery.refetch();
70+
};
71+
72+
return(
73+
<ExperimentalFormContext.Providervalue={{ toggleOptedOut}}>
74+
{optOutQuery.data.optedOut ?(
75+
<WorkspaceParametersPage/>
76+
) :(
77+
<WorkspaceParametersPageExperimental/>
78+
)}
79+
</ExperimentalFormContext.Provider>
80+
);
81+
}
82+
83+
return<WorkspaceParametersPage/>;
84+
};
85+
86+
exportdefaultWorkspaceParametersExperimentRouter;
87+
88+
constoptOutKey=(id:string)=>`parameters.${id}.optOut`;

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

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ 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";
9-
import{PageHeader,PageHeaderTitle}from"components/PageHeader/PageHeader";
1010
import{ExternalLinkIcon}from"lucide-react";
11-
importtype{FC}from"react";
11+
import{typeFC,useContext}from"react";
1212
import{Helmet}from"react-helmet-async";
1313
import{useMutation,useQuery}from"react-query";
1414
import{useNavigate}from"react-router-dom";
@@ -18,6 +18,7 @@ import {
1818
typeWorkspacePermissions,
1919
workspaceChecks,
2020
}from"../../../modules/workspaces/permissions";
21+
import{ExperimentalFormContext}from"../../CreateWorkspacePage/ExperimentalFormContext";
2122
import{useWorkspaceSettings}from"../WorkspaceSettingsLayout";
2223
import{
2324
WorkspaceParametersForm,
@@ -112,15 +113,27 @@ export const WorkspaceParametersPageView: FC<
112113
isSubmitting,
113114
onCancel,
114115
})=>{
116+
constexperimentalFormContext=useContext(ExperimentalFormContext);
115117
return(
116-
<>
117-
<PageHeadercss={{paddingTop:0}}>
118-
<PageHeaderTitle>Workspace parameters</PageHeaderTitle>
119-
</PageHeader>
118+
<divclassName="flex flex-col gap-10">
119+
<headerclassName="flex flex-col items-start gap-2">
120+
<spanclassName="flex flex-row justify-between items-center gap-2">
121+
<h1className="text-3xl m-0">Workspace parameters</h1>
122+
</span>
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+
</header>
120133

121-
{submitError&&!isApiValidationError(submitError)&&(
134+
{submitError&&!isApiValidationError(submitError)?(
122135
<ErrorAlerterror={submitError}css={{marginBottom:48}}/>
123-
)}
136+
) :null}
124137

125138
{data ?(
126139
data.templateVersionRichParameters.length>0 ?(
@@ -161,7 +174,7 @@ export const WorkspaceParametersPageView: FC<
161174
) :(
162175
<Loader/>
163176
)}
164-
</>
177+
</div>
165178
);
166179
};
167180

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp