-
Notifications
You must be signed in to change notification settings - Fork 198
feat: add build timeout handling to prevent stuck build UI #2520
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
5889e8c
4ec0298
4f3b9d3
ddf5996
f0cae0b
0ceaed9
032f094
fc83486
6a4c4ed
37c38c7
add4296
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| import type { Models } from '@appwrite.io/console'; | ||
|
|
||
| /** | ||
| * Checks if a build has exceeded the maximum build timeout duration | ||
| */ | ||
| function isBuildTimedOut(createdAt: string, status: string, timeoutSeconds: number): boolean { | ||
| if (!['waiting', 'processing', 'building'].includes(status)) { | ||
| return false; | ||
| } | ||
|
|
||
| if (!timeoutSeconds || timeoutSeconds <= 0) { | ||
| return false; | ||
| } | ||
|
|
||
| const created = new Date(createdAt); | ||
| const elapsedSeconds = Math.floor((Date.now() - created.getTime()) / 1000); | ||
|
|
||
| return elapsedSeconds > timeoutSeconds; | ||
| } | ||
|
|
||
| /** | ||
| * Gets the effective status for a build, considering timeout | ||
| */ | ||
| export function getEffectiveBuildStatus( | ||
| originalStatus: string, | ||
| createdAt: string, | ||
| consoleVariables: Models.ConsoleVariables | undefined | ||
| ): string { | ||
| const timeoutSeconds = getBuildTimeoutSeconds(consoleVariables); | ||
| if (isBuildTimedOut(createdAt, originalStatus, timeoutSeconds)) { | ||
| return 'failed'; | ||
| } | ||
| return originalStatus; | ||
| } | ||
|
|
||
| /** | ||
| * Helper to get timeout value from console variables | ||
| */ | ||
| function getBuildTimeoutSeconds(consoleVariables: Models.ConsoleVariables | undefined): number { | ||
| if (!consoleVariables?._APP_COMPUTE_BUILD_TIMEOUT) { | ||
| return 0; | ||
| } | ||
| const timeout = parseInt(String(consoleVariables._APP_COMPUTE_BUILD_TIMEOUT), 10); | ||
| return isNaN(timeout) ? 0 : timeout; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,8 @@ | |
| <script lang="ts"> | ||
| import { capitalize } from '$lib/helpers/string'; | ||
| import { app } from '$lib/stores/app'; | ||
| import { getEffectiveBuildStatus } from '$lib/helpers/buildTimeout'; | ||
| import { regionalConsoleVariables } from '$routes/(console)/project-[region]-[project]/store'; | ||
| import type { Models } from '@appwrite.io/console'; | ||
| import { Badge, Card, Layout, Logs, Spinner, Typography } from '@appwrite.io/pink-svelte'; | ||
| import LogsTimer from './logsTimer.svelte'; | ||
|
|
@@ -38,15 +40,19 @@ | |
| emptyCopy?: string; | ||
| } = $props(); | ||
| let effectiveStatus = $derived( | ||
| getEffectiveBuildStatus(deployment.status, deployment.$createdAt, $regionalConsoleVariables) | ||
| ); | ||
|
Comment on lines
+43
to
+45
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Logs badge stays “building” forever once mounted
🤖 Prompt for AI Agents |
||
| function setCopy() { | ||
| if (deployment.status === 'failed') { | ||
| if (effectiveStatus === 'failed') { | ||
| return 'Your deployment has failed.'; | ||
| } else if (deployment.status === 'building') { | ||
| } else if (effectiveStatus === 'building') { | ||
| //Do not remove empty space before the string it's an invisible character | ||
| return '[37mPreparing for build ... [0m\n'; | ||
| } else if (deployment.status === 'waiting') { | ||
| } else if (effectiveStatus === 'waiting') { | ||
| return '[37mPreparing for build ... [0m\n'; | ||
| } else if (deployment.status === 'processing') { | ||
| } else if (effectiveStatus === 'processing') { | ||
| return '[37mPreparing for build ... [0m\n'; | ||
| } else { | ||
| return emptyCopy; | ||
|
|
@@ -62,16 +68,16 @@ | |
| Deployment logs | ||
| </Typography.Text> | ||
| <Badge | ||
| content={capitalize(deployment.status)} | ||
| content={capitalize(effectiveStatus)} | ||
| size="xs" | ||
| variant="secondary" | ||
| type={badgeTypeDeployment(deployment.status)} /> | ||
| type={badgeTypeDeployment(effectiveStatus)} /> | ||
| </Layout.Stack> | ||
| <LogsTimer status={deployment.status} {deployment} /> | ||
| <LogsTimer status={effectiveStatus} {deployment} /> | ||
| </Layout.Stack> | ||
| {/if} | ||
|
|
||
| {#if ['waiting', 'processing'].includes(deployment.status) || (deployment.status === 'building' && !deployment?.buildLogs?.length)} | ||
| {#if ['waiting', 'processing'].includes(effectiveStatus) || (effectiveStatus === 'building' && !deployment?.buildLogs?.length)} | ||
| <Card.Base variant="secondary"> | ||
| <Layout.Stack direction="row" justifyContent="center" gap="s"> | ||
| <Spinner /> Waiting for build to start... | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Menu never updates when the timeout is crossed
effectiveStatusis calculated once when this template renders. Because no reactive clock or prop changes fire afterward, a deployment that should time out continues to look “building,” so “Cancel” stays visible and “Delete” stays hidden indefinitely. Please drive this through a time-reactive status (e.g., pass one down from the table/card that ticks) or introduce a local ticking signal sogetEffectiveBuildStatusactually re-runs as time advances.