Skip to content
This repository has been archived by the owner on Jul 12, 2023. It is now read-only.

Commit

Permalink
Paginate mobile apps (#955)
Browse files Browse the repository at this point in the history
  • Loading branch information
sethvargo authored Nov 3, 2020
1 parent 0885e22 commit 5d65787
Show file tree
Hide file tree
Showing 11 changed files with 199 additions and 78 deletions.
1 change: 1 addition & 0 deletions cmd/server/assets/apikeys/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

<div class="card mt-4 mb-3">
<div class="card-header">
<span class="oi oi-key mr-2 ml-n1" aria-hidden="true"></span>
API keys
<a href="/apikeys/new" class="float-right mr-n1 text-secondary" data-toggle="tooltip" title="New API key">
<span class="oi oi-plus small" aria-hidden="true"></span>
Expand Down
2 changes: 1 addition & 1 deletion cmd/server/assets/mobileapps/edit.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
{{template "head" .}}
</head>

<body class="tab-content">
<body id="mobileapps-edit" class="tab-content">
{{template "navbar" .}}

<main role="main" class="container">
Expand Down
124 changes: 63 additions & 61 deletions cmd/server/assets/mobileapps/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,74 +8,76 @@
{{template "head" .}}
</head>

<body class="tab-content">
<body id="mobileapps-index" class="tab-content">
{{template "navbar" .}}

<main role="main" class="container">
{{template "flash" .}}

<h1>Mobile apps</h1>
<p>
These are the mobile apps for this realm. You
can also <a href="/mobile-apps/new">create a new mobile app</a>.
</p>
<div class="card mt-4 mb-3">
<div class="card-header">
<span class="oi oi-phone mr-2 ml-n1" aria-hidden="true"></span>
Mobile apps
<a href="/mobile-apps/new" class="float-right mr-n1 text-secondary" data-toggle="tooltip" title="New mobile app">
<span class="oi oi-plus small" aria-hidden="true"></span>
</a>
</div>

{{if .apps}}
<div class="table-responsive">
<table class="table table-bordered table-striped bg-white">
<thead>
<tr>
<th scope="col">App</th>
<th scope="col" width="95">Enabled</th>
<th scope="col" width="40"></th>
</tr>
</thead>
<tbody>
{{range .apps}}
<tr>

<td>
<a href="/mobile-apps/{{.ID}}" class="text-truncate">{{.Name}}</a>
</td>

<td>
{{if .DeletedAt}}
<span class="badge badge-pill badge-danger">Deleted</span>
{{else}}
<span class="badge badge-pill badge-success">Enabled</span>
{{end}}
</td>

<td class="text-center">
{{if .DeletedAt}}
<a href="/mobile-apps/{{.ID}}/enable" class="d-block text-danger"
data-method="patch"
data-confirm="Are you sure you want to restore '{{.Name}}'?"
data-toggle="tooltip"
title="Restore this mobile app">
<span class="oi oi-loop-circular" aria-hidden="true"></span>
</a>
{{else}}
<a href="/mobile-apps/{{.ID}}/disable" class="d-block text-danger"
data-method="patch"
data-confirm="Are you sure you want to disable '{{.Name}}'?"
data-toggle="tooltip"
title="Disable this mobile app">
<span class="oi oi-trash" aria-hidden="true"></span>
</a>
{{end}}
</td>

</tr>
{{end}}
</tbody>
</table>
{{if $apps}}
<table class="table table-bordered table-striped table-fixed table-inner-border-only mb-0">
<thead>
<tr>
<th scope="col" width="40"></th>
<th scope="col">Mobile app</th>
<th scope="col" width="40"></th>
</tr>
</thead>
<tbody>
{{range $apps}}
<tr>
<td class="text-center">
{{if .DeletedAt}}
<span class="oi oi-circle-x text-danger"
data-toggle="tooltip" title="Mobile app is disabled - it will be deleted in a few days"></span>
{{else}}
<span class="oi oi-circle-check text-success"
data-toggle="tooltip" title="Mobile app is enabled"></span>
{{end}}
</td>
<td class="text-truncate">
<a href="/mobile-apps/{{.ID}}">{{.Name}}</a>
</td>
<td class="text-center">
{{if .DeletedAt}}
<a href="/mobile-apps/{{.ID}}/enable" class="d-block text-danger"
data-method="patch"
data-confirm="Are you sure you want to restore '{{.Name}}'?"
data-toggle="tooltip"
title="Restore this mobile app">
<span class="oi oi-loop-circular" aria-hidden="true"></span>
</a>
{{else}}
<a href="/mobile-apps/{{.ID}}/disable" class="d-block text-danger"
data-method="patch"
data-confirm="Are you sure you want to disable '{{.Name}}'?"
data-toggle="tooltip"
title="Disable this mobile app">
<span class="oi oi-trash" aria-hidden="true"></span>
</a>
{{end}}
</td>
</tr>
{{end}}
</tbody>
</table>
{{else}}
<p class="text-center mb-0">
<em>There are no mobile apps.</em>
</p>
{{end}}
</div>
{{else}}
<p class="text-center">
<em>There are no mobile apps.</em>
</p>
{{end}}

{{template "shared/pagination" .}}
</main>
</body>
</html>
Expand Down
4 changes: 2 additions & 2 deletions cmd/server/assets/mobileapps/new.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
{{template "head" .}}
</head>

<body class="tab-content">
<body id="mobileapps-new" class="tab-content">
{{template "navbar" .}}

<main role="main" class="container">
Expand All @@ -27,7 +27,7 @@ <h1>New mobile app</h1>

{{template "mobileapps/_app" .}}

<button type="submit" class="btn btn-primary btn-block">Create mobile app</button>
<button type="submit" id="submit" class="btn btn-primary btn-block">Create mobile app</button>
</form>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion cmd/server/assets/mobileapps/show.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
{{template "head" .}}
</head>

<body class="tab-content">
<body id="mobileapps-show" class="tab-content">
{{template "navbar" .}}

<main role="main" class="container">
Expand Down
1 change: 1 addition & 0 deletions pkg/controller/mobileapps/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ func (c *Controller) HandleCreate() http.Handler {
// renderNew renders the new page.
func (c *Controller) renderNew(ctx context.Context, w http.ResponseWriter, app *database.MobileApp) {
m := templateMap(ctx)
m["title"] = fmt.Sprintf("New mobile app - %s", m["title"])
m["app"] = app
c.h.RenderHTML(w, "mobileapps/new", m)
}
96 changes: 96 additions & 0 deletions pkg/controller/mobileapps/create_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package mobileapps_test

import (
"context"
"strconv"
"testing"
"time"

"github.com/google/exposure-notifications-verification-server/internal/browser"
"github.com/google/exposure-notifications-verification-server/internal/envstest"
"github.com/google/exposure-notifications-verification-server/pkg/controller"
"github.com/google/exposure-notifications-verification-server/pkg/database"

"github.com/chromedp/chromedp"
)

func TestHandleCreate(t *testing.T) {
t.Parallel()

harness := envstest.NewServer(t)

// Get the default realm
realm, err := harness.Database.FindRealm(1)
if err != nil {
t.Fatal(err)
}

// Create a user
admin := &database.User{
Email: "admin@example.com",
Name: "Admin",
Realms: []*database.Realm{realm},
AdminRealms: []*database.Realm{realm},
}
if err := harness.Database.SaveUser(admin, database.System); err != nil {
t.Fatal(err)
}

// Log in the user.
session, err := harness.LoggedInSession(nil, admin.Email)
if err != nil {
t.Fatal(err)
}

// Set the current realm.
controller.StoreSessionRealm(session, realm)

// Mint a cookie for the session.
cookie, err := harness.SessionCookie(session)
if err != nil {
t.Fatal(err)
}
// Create a browser runner.
browserCtx := browser.New(t)
taskCtx, done := context.WithTimeout(browserCtx, 30*time.Second)
defer done()

if err := chromedp.Run(taskCtx,
// Pre-authenticate the user.
browser.SetCookie(cookie),

// Visit /apikeys/new.
chromedp.Navigate(`http://`+harness.Server.Addr()+`/mobile-apps/new`),

// Wait for render.
chromedp.WaitVisible(`body#mobileapps-new`, chromedp.ByQuery),

// Fill out the form.
chromedp.SetValue(`input#name`, "Example mobile app", chromedp.ByQuery),
chromedp.SetValue(`input#url`, "https://example.com", chromedp.ByQuery),
chromedp.SetValue(`select#os`, strconv.Itoa(int(database.OSTypeIOS)), chromedp.ByQuery),
chromedp.SetValue(`input#app-id`, "com.example.app", chromedp.ByQuery),

// Click the submit button.
chromedp.Click(`#submit`, chromedp.ByQuery),

// Wait for the page to reload.
chromedp.WaitVisible(`body#mobileapps-show`, chromedp.ByQuery),
); err != nil {
t.Fatal(err)
}
}
16 changes: 13 additions & 3 deletions pkg/controller/mobileapps/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ package mobileapps

import (
"context"
"fmt"
"net/http"

"github.com/google/exposure-notifications-verification-server/pkg/controller"
"github.com/google/exposure-notifications-verification-server/pkg/database"
"github.com/google/exposure-notifications-verification-server/pkg/pagination"
)

func (c *Controller) HandleIndex() http.Handler {
Expand All @@ -32,20 +34,28 @@ func (c *Controller) HandleIndex() http.Handler {
return
}

pageParams, err := pagination.FromRequest(r)
if err != nil {
controller.InternalError(w, r, c.h, err)
return
}

// Perform the lazy load on authorized apps for the realm.
apps, err := realm.ListMobileApps(c.db)
apps, paginator, err := realm.ListMobileApps(c.db, pageParams)
if err != nil {
controller.InternalError(w, r, c.h, err)
return
}

c.renderIndex(ctx, w, apps)
c.renderIndex(ctx, w, apps, paginator)
})
}

// renderIndex renders the index page.
func (c *Controller) renderIndex(ctx context.Context, w http.ResponseWriter, apps []*database.MobileApp) {
func (c *Controller) renderIndex(ctx context.Context, w http.ResponseWriter, apps []*database.MobileApp, paginator *pagination.Paginator) {
m := templateMap(ctx)
m["title"] = fmt.Sprintf("Mobile apps - %s", m["title"])
m["apps"] = apps
m["paginator"] = paginator
c.h.RenderHTML(w, "mobileapps/index", m)
}
2 changes: 2 additions & 0 deletions pkg/controller/mobileapps/show.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package mobileapps

import (
"context"
"fmt"
"net/http"

"github.com/google/exposure-notifications-verification-server/pkg/controller"
Expand Down Expand Up @@ -61,6 +62,7 @@ func (c *Controller) HandleShow() http.Handler {
// renderShow renders the edit page.
func (c *Controller) renderShow(ctx context.Context, w http.ResponseWriter, app *database.MobileApp) {
m := templateMap(ctx)
m["title"] = fmt.Sprintf("%s - Mobile apps - %s", app.Name, m["title"])
m["app"] = app
c.h.RenderHTML(w, "mobileapps/show", m)
}
2 changes: 2 additions & 0 deletions pkg/controller/mobileapps/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package mobileapps

import (
"context"
"fmt"
"net/http"

"github.com/google/exposure-notifications-verification-server/pkg/controller"
Expand Down Expand Up @@ -110,6 +111,7 @@ func (c *Controller) HandleUpdate() http.Handler {
// renderEdit renders the edit page.
func (c *Controller) renderEdit(ctx context.Context, w http.ResponseWriter, app *database.MobileApp) {
m := templateMap(ctx)
m["title"] = fmt.Sprintf("%s - Edit mobile app - %s", app.Name, m["title"])
m["app"] = app
c.h.RenderHTML(w, "mobileapps/edit", m)
}
Loading

0 comments on commit 5d65787

Please sign in to comment.