Show the deploy branch in the app page title and early in the app list (closes #240)
Check / check (pull_request) Skipped

The app page shows the configured branch as a neutral label next to the
status badge; the line under the title now shows only the repository.
The app list moves the Branch column to right after Name and lets the
table scroll sideways inside its card, so narrow screens no longer cut
columns off. A new test renders both pages for an app on a non-main
branch.

Model: opus-5-5
This commit is contained in:
2026-09-29 09:47:13 +00:00
parent 0a94484795
commit f68e755bd2
4 changed files with 88 additions and 4 deletions
+5
View File
@@ -20,6 +20,11 @@ regress.
# Completed Steps # Completed Steps
- 2026-09-29: The app page shows the app's branch as a label in its title, next
to the status badge, instead of after the repository under it; the app list
shows the Branch column right after Name and scrolls sideways on narrow
screens instead of cutting columns off (#240).
- 2026-09-29: `docker-compose.yml` now sets `UPAAS_PORT` to 8080 as well as - 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 `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 it listen away from the port mapping and healthcheck; the README's Compose
+76
View File
@@ -0,0 +1,76 @@
package handlers_test
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sneak.berlin/go/upaas/internal/service/app"
)
// TestAppBranchIsShown checks that an app's branch can be read from the app
// page title and from the app list without opening the edit page.
func TestAppBranchIsShown(t *testing.T) {
t.Parallel()
testCtx := setupTestHandlers(t)
createdApp, err := testCtx.appSvc.CreateApp(t.Context(), app.CreateAppInput{
Name: "branch-shown-app",
RepoURL: "git@example.com:user/branch-shown-app.git",
Branch: "staging",
})
require.NoError(t, err)
t.Run("in the app page title next to the status badge", func(t *testing.T) {
t.Parallel()
request := httptest.NewRequestWithContext(
t.Context(), http.MethodGet, "/apps/"+createdApp.ID, nil,
)
request = addChiURLParams(request, map[string]string{"id": createdApp.ID})
recorder := httptest.NewRecorder()
testCtx.handlers.HandleAppDetail().ServeHTTP(recorder, request)
require.Equal(t, http.StatusOK, recorder.Code)
// The title row runs from the app name heading to the end of its div.
_, afterHeading, found := strings.Cut(recorder.Body.String(), "<h1")
require.True(t, found, "app page has no heading")
titleRow, _, _ := strings.Cut(afterHeading, "</div>")
assert.Contains(t, titleRow, `x-text="statusLabel"`)
assert.Contains(t, titleRow, ">staging</span>")
})
t.Run("in the app list before the repository", func(t *testing.T) {
t.Parallel()
request := httptest.NewRequestWithContext(
t.Context(), http.MethodGet, "/", nil,
)
recorder := httptest.NewRecorder()
testCtx.handlers.HandleDashboard().ServeHTTP(recorder, request)
require.Equal(t, http.StatusOK, recorder.Code)
_, afterRowStart, found := strings.Cut(
recorder.Body.String(), `<tr class="table-row-hover">`,
)
require.True(t, found, "app list has no row for the app")
row, _, _ := strings.Cut(afterRowStart, "</tr>")
branchAt := strings.Index(row, ">staging</td>")
repoAt := strings.Index(row, createdApp.RepoURL)
require.NotEqual(t, -1, branchAt, "branch missing from the app's row")
require.NotEqual(t, -1, repoAt, "repository missing from the app's row")
assert.Less(t, branchAt, repoAt, "branch should come before repository")
})
}
+3 -2
View File
@@ -26,11 +26,12 @@
<!-- Header --> <!-- Header -->
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-8"> <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-8">
<div> <div>
<div class="flex items-center gap-3"> <div class="flex flex-wrap items-center gap-3">
<h1 class="text-2xl font-medium text-gray-900">{{.App.Name}}</h1> <h1 class="text-2xl font-medium text-gray-900">{{.App.Name}}</h1>
<span x-bind:class="statusBadgeClass" x-text="statusLabel"></span> <span x-bind:class="statusBadgeClass" x-text="statusLabel"></span>
<span class="badge-neutral font-mono break-all" title="Branch">{{.App.Branch}}</span>
</div> </div>
<p class="text-gray-500 font-mono text-sm mt-1">{{.App.RepoURL}}@{{.App.Branch}}</p> <p class="text-gray-500 font-mono text-sm mt-1">{{.App.RepoURL}}</p>
</div> </div>
<div class="flex gap-3"> <div class="flex gap-3">
<a href="/apps/{{.App.ID}}/edit" class="btn-secondary">Edit</a> <a href="/apps/{{.App.ID}}/edit" class="btn-secondary">Edit</a>
+4 -2
View File
@@ -21,12 +21,13 @@
{{if .AppStats}} {{if .AppStats}}
<div class="card overflow-hidden"> <div class="card overflow-hidden">
<div class="overflow-x-auto">
<table class="table"> <table class="table">
<thead class="table-header"> <thead class="table-header">
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Repository</th>
<th>Branch</th> <th>Branch</th>
<th>Repository</th>
<th>Status</th> <th>Status</th>
<th>Last Deploy</th> <th>Last Deploy</th>
<th>Deploys</th> <th>Deploys</th>
@@ -41,8 +42,8 @@
{{.App.Name}} {{.App.Name}}
</a> </a>
</td> </td>
<td class="text-gray-500 font-mono text-xs">{{.App.RepoURL}}</td>
<td class="text-gray-500">{{.App.Branch}}</td> <td class="text-gray-500">{{.App.Branch}}</td>
<td class="text-gray-500 font-mono text-xs">{{.App.RepoURL}}</td>
<td> <td>
{{if eq .App.Status "running"}} {{if eq .App.Status "running"}}
<span class="badge-success">Running</span> <span class="badge-success">Running</span>
@@ -79,6 +80,7 @@
</tbody> </tbody>
</table> </table>
</div> </div>
</div>
{{else}} {{else}}
<div class="card"> <div class="card">
<div class="empty-state"> <div class="empty-state">