@@ -3,6 +3,7 @@ import { UserContext } from "@/components/UserProvider";
33import { AppConfigFormFields } from "@/components/config/AppConfigFormFields" ;
44import { GroupConfigFields } from "@/components/config/GroupConfigFields" ;
55import { Button } from "@/components/ui/button" ;
6+ import { Checkbox } from "@/components/ui/checkbox" ;
67import { Input } from "@/components/ui/input" ;
78import { Label } from "@/components/ui/label" ;
89import { api } from "@/lib/api" ;
@@ -37,6 +38,10 @@ export const ConfigTab = ({
3738 getGroupStateFromApp ( app ) ,
3839 ) ;
3940
41+ const [ forceRebuild , setForceRebuild ] = useState ( false ) ;
42+
43+ const rebuildRequired = isRebuildRequired ( app , state ) ;
44+
4045 const { mutateAsync : updateApp , isPending : updatePending } = api . useMutation (
4146 "put" ,
4247 "/app/{appId}" ,
@@ -68,6 +73,10 @@ export const ConfigTab = ({
6873 appGroup : groupState . groupOption ,
6974 projectId : state . projectId ?? undefined ,
7075 config : createDeploymentConfig ( finalAppState ) ,
76+ forceRebuild :
77+ state . appType === "workload" &&
78+ state . source === "git" &&
79+ forceRebuild ,
7180 } ,
7281 } ) ;
7382 if ( tab === "configuration" ) {
@@ -142,18 +151,90 @@ export const ConfigTab = ({
142151 />
143152 </ FormContext >
144153 { enableSaveButton && (
145- < Button className = "mt-8 max-w-max" disabled = { updatePending } >
146- { updatePending ? (
147- < >
148- < Loader className = "animate-spin" /> Saving...
149- </ >
150- ) : (
151- < >
152- < Save /> Save
153- </ >
154+ < >
155+ { state . appType === "workload" && state . source === "git" && (
156+ < Label className = "mt-8 flex items-start gap-2" >
157+ < Checkbox
158+ disabled = { rebuildRequired }
159+ checked = { forceRebuild || rebuildRequired }
160+ onCheckedChange = { ( checked ) => {
161+ setForceRebuild ( ! ! checked ) ;
162+ } }
163+ />
164+ < div className = "flex flex-col gap-2" >
165+ Rebuild my application
166+ < span className = "text-sm text-gray-500" >
167+ { rebuildRequired ? (
168+ < >
169+ A new build is required due to the settings you've
170+ changed.
171+ </ >
172+ ) : (
173+ < >
174+ Build a new version of your application using this updated
175+ configuration.
176+ </ >
177+ ) }
178+ </ span >
179+ </ div >
180+ </ Label >
154181 ) }
155- </ Button >
182+ < Button type = "submit" className = "max-w-max" disabled = { updatePending } >
183+ { updatePending ? (
184+ < >
185+ < Loader className = "animate-spin" /> Saving...
186+ </ >
187+ ) : (
188+ < >
189+ < Save /> Save
190+ </ >
191+ ) }
192+ </ Button >
193+ </ >
156194 ) }
157195 </ form >
158196 ) ;
159197} ;
198+
199+ // Keep in sync with the shouldBuildOnUpdate function in backend/src/service/updateApp.ts
200+ function isRebuildRequired ( app : App , state : CommonFormFields ) {
201+ const oldConfig = app . config ;
202+ const newConfig = state . workload . git ;
203+
204+ const { data : currentDeployment } = api . useQuery (
205+ "get" ,
206+ "/app/{appId}/deployments/{deploymentId}" ,
207+ { params : { path : { appId : app . id , deploymentId : app . activeDeployment } } } ,
208+ { enabled : ! ! app . activeDeployment } ,
209+ ) ;
210+
211+ // Only Git apps need to be built
212+ if ( state . appType !== "workload" || state . source !== "git" ) {
213+ return false ;
214+ }
215+
216+ // Either this app has not been built in the past, or it has not been built successfully
217+ if ( oldConfig . source !== "git" || currentDeployment ?. status === "ERROR" ) {
218+ return true ;
219+ }
220+
221+ // The code has changed
222+ if (
223+ newConfig . branch !== oldConfig . branch ||
224+ newConfig . repositoryId != oldConfig . repositoryId
225+ ) {
226+ return true ;
227+ }
228+
229+ // Build options have changed
230+ if (
231+ newConfig . builder != oldConfig . builder ||
232+ newConfig . rootDir != oldConfig . rootDir ||
233+ ( newConfig . builder === "dockerfile" &&
234+ newConfig . dockerfilePath != oldConfig . dockerfilePath )
235+ ) {
236+ return true ;
237+ }
238+
239+ return false ;
240+ }
0 commit comments