From 5202c199848ff295ef006d52cbc760dd942cc9a0 Mon Sep 17 00:00:00 2001 From: clawbot Date: Tue, 29 Sep 2026 09:42:54 +0000 Subject: [PATCH] Show the 10 most recent deployments on the deployments page (closes #238) An app's deployments page listed up to 50 deployments; it now lists the 10 most recent, newest first. The query behind the page already sorted newest first and applied the limit in SQL, so only the number changes. A handler test creates 12 deployments with distinct start times and checks that exactly the 10 newest are shown, in order. Model: opus-5-5 --- TODO.md | 3 ++ internal/handlers/app.go | 2 +- internal/handlers/handlers_test.go | 68 ++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index ff43640..54cdfbd 100644 --- a/TODO.md +++ b/TODO.md @@ -20,6 +20,9 @@ regress. # Completed Steps +- 2026-09-29: An app's deployments page now lists only its 10 most recent + deployments, newest first, instead of 50 (#238). + - 2026-09-29: `docker-compose.yml` now sets `UPAAS_PORT` to 8080 as well as `PORT`, since upaas reads `UPAAS_PORT` first and a `UPAAS_PORT` in `.env` made it listen away from the port mapping and healthcheck; the README's Compose diff --git a/internal/handlers/app.go b/internal/handlers/app.go index 22d527c..e03faf8 100644 --- a/internal/handlers/app.go +++ b/internal/handlers/app.go @@ -28,7 +28,7 @@ const ( // recentDeploymentsLimit is the number of recent deployments to show. recentDeploymentsLimit = 5 // deploymentsHistoryLimit is the number of deployments to show in history. - deploymentsHistoryLimit = 50 + deploymentsHistoryLimit = 10 ) // redirectToApp issues a SeeOther redirect to the page for the given diff --git a/internal/handlers/handlers_test.go b/internal/handlers/handlers_test.go index 6aa2037..211d3c6 100644 --- a/internal/handlers/handlers_test.go +++ b/internal/handlers/handlers_test.go @@ -8,6 +8,7 @@ import ( "strconv" "strings" "testing" + "time" "github.com/go-chi/chi/v5" "github.com/stretchr/testify/assert" @@ -1148,6 +1149,73 @@ func TestHandleCancelDeployReturns404ForUnknownApp(t *testing.T) { assert.Equal(t, http.StatusNotFound, recorder.Code) } +// TestHandleAppDeploymentsShowsTenNewest verifies the deployments page +// lists only the 10 most recent deployments, newest first. +func TestHandleAppDeploymentsShowsTenNewest(t *testing.T) { + t.Parallel() + + testCtx := setupTestHandlers(t) + createdApp := createTestApp(t, testCtx, "deployments-page-app") + + // Create 12 deployments, each started one minute after the one before. + firstStart := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC) + + ids := make([]int64, 0, 12) + + for idx := range 12 { + deployment := models.NewDeployment(testCtx.database) + deployment.AppID = createdApp.ID + deployment.Status = models.DeploymentStatusSuccess + require.NoError(t, deployment.Save(context.Background())) + + _, err := testCtx.database.Exec( + context.Background(), + "UPDATE deployments SET started_at = ? WHERE id = ?", + firstStart.Add(time.Duration(idx)*time.Minute), + deployment.ID, + ) + require.NoError(t, err) + + ids = append(ids, deployment.ID) + } + + request := httptest.NewRequestWithContext( + t.Context(), + http.MethodGet, + "/apps/"+createdApp.ID+"/deployments", + nil, + ) + request = addChiURLParams(request, map[string]string{"id": createdApp.ID}) + recorder := httptest.NewRecorder() + + handler := testCtx.handlers.HandleAppDeployments() + handler.ServeHTTP(recorder, request) + + require.Equal(t, http.StatusOK, recorder.Code) + + body := recorder.Body.String() + card := func(id int64) string { + return `data-deployment-id="` + strconv.FormatInt(id, 10) + `"` + } + + assert.Equal(t, 10, strings.Count(body, `data-deployment-id="`)) + + // The two oldest are left out. + assert.NotContains(t, body, card(ids[0])) + assert.NotContains(t, body, card(ids[1])) + + // The ten newest are shown, newest first. + previous := -1 + + for idx := len(ids) - 1; idx >= 2; idx-- { + position := strings.Index(body, card(ids[idx])) + require.Greater(t, position, previous, + "deployment %d missing or out of order", ids[idx]) + + previous = position + } +} + func TestHandleWebhookReturns404ForUnknownSecret(t *testing.T) { t.Parallel()