diff --git a/handler/handler.go b/handler/handler.go
index 9000704..9c76fca 100644
--- a/handler/handler.go
+++ b/handler/handler.go
@@ -51,18 +51,30 @@ func HandleIndex(t *history.History) http.HandlerFunc {
// HandleStage returns a http.HandlerFunc that displays the
// stage details.
-func HandleStage(t *history.History) http.HandlerFunc {
+func HandleStage(hist *history.History, logger *hook.Hook) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
id, _ := strconv.ParseInt(r.FormValue("id"), 10, 64)
- for _, e := range t.Entries() {
- if e.Stage.ID == id {
- nocache(w)
- render(w, "stage.tmpl", e)
- return
- }
+
+ // filter logs by stage id.
+ logs := logger.Filter(func(entry *hook.Entry) bool {
+ return entry.Data["stage.id"] == id
+ })
+
+ // find pipeline by stage id
+ entry := hist.Entry(id)
+ if entry == nil {
+ w.WriteHeader(404)
+ return
}
- // TODO(bradrydzewski) we need an error template.
- w.WriteHeader(404)
+
+ nocache(w)
+ render(w, "stage.tmpl", struct {
+ *history.Entry
+ Logs []*hook.Entry
+ }{
+ Entry: entry,
+ Logs: logs,
+ })
}
}
diff --git a/handler/router/router.go b/handler/router/router.go
index e4c0427..61813e7 100644
--- a/handler/router/router.go
+++ b/handler/router/router.go
@@ -43,7 +43,7 @@ func New(tracer *history.History, history *hook.Hook, config Config) http.Handle
// dashboard handles.
mux.Handle("/static/", http.StripPrefix("/static/", fs))
mux.Handle("/logs", auth(handler.HandleLogHistory(history)))
- mux.Handle("/view", auth(handler.HandleStage(tracer)))
+ mux.Handle("/view", auth(handler.HandleStage(tracer, history)))
mux.Handle("/", auth(handler.HandleIndex(tracer)))
return mux
}
diff --git a/handler/static/files/style.css b/handler/static/files/style.css
index be76071..32bc3db 100644
--- a/handler/static/files/style.css
+++ b/handler/static/files/style.css
@@ -173,7 +173,7 @@ a.card {
.steps .step {
display: grid;
grid-gap: 10px 0px;
- grid-template-columns: 30px auto;
+ grid-template-columns: 30px auto 100px;
padding: 10px 15px;
}
@@ -218,6 +218,16 @@ a.card {
display: none;
}
+.steps .status-name {
+ align-items: center;
+ color: rgba(30,55,90,.6);
+ display: flex;
+ font-size: 14px;
+ font-style: italic;
+ justify-content: flex-end;
+ text-transform: capitalize;
+}
+
/**
* breadcrumb component
*/
diff --git a/handler/static/static_gen.go b/handler/static/static_gen.go
index 1841d6f..88efec5 100644
--- a/handler/static/static_gen.go
+++ b/handler/static/static_gen.go
@@ -228,8 +228,8 @@ var files = map[string]file{
data: file11,
FileInfo: &fileInfo{
name: "style.css",
- size: 8275,
- modTime: time.Unix(1563076316, 0),
+ size: 8488,
+ modTime: time.Unix(1563131883, 0),
},
},
"/timeago.js": {
@@ -1337,7 +1337,7 @@ a.card {
.steps .step {
display: grid;
grid-gap: 10px 0px;
- grid-template-columns: 30px auto;
+ grid-template-columns: 30px auto 100px;
padding: 10px 15px;
}
@@ -1382,6 +1382,16 @@ a.card {
display: none;
}
+.steps .status-name {
+ align-items: center;
+ color: rgba(30,55,90,.6);
+ display: flex;
+ font-size: 14px;
+ font-style: italic;
+ justify-content: flex-end;
+ text-transform: capitalize;
+}
+
/**
* breadcrumb component
*/
diff --git a/handler/template/files/stage.tmpl b/handler/template/files/stage.tmpl
index 17b2f46..484980c 100644
--- a/handler/template/files/stage.tmpl
+++ b/handler/template/files/stage.tmpl
@@ -2,7 +2,9 @@
-
+{{- if not (done .Stage.Status) }}
+
+{{- end }}
Dashboard
@@ -61,20 +63,34 @@
{{ if .Stage.Steps }}
-
{{ range .Stage.Steps }}
- {{ .Name }}
+ {{ .Name }}
+ {{ .Status }}
{{ end }}
{{ end }}
+
+ {{ if .Logs }}
+
+ {{ range .Logs }}
+
+ {{ .Level }}
+ {{ .Message }}
+
+ {{ range $key, $val := .Data }}
+ {{ $key }}{{ $val }}
+ {{ end }}
+
+
+
+ {{ end }}
+
+ {{ end }}
diff --git a/handler/template/server.go b/handler/template/server.go
index 92d30c1..27c2a6b 100644
--- a/handler/template/server.go
+++ b/handler/template/server.go
@@ -103,4 +103,7 @@ var funcMap = map[string]interface{}{
"tag": func(s string) string {
return strings.TrimPrefix(s, "refs/tags/")
},
+ "done": func(s string) bool {
+ return s != "pending" && s != "running"
+ },
}
diff --git a/handler/template/template.go b/handler/template/template.go
index 549a846..4cd599b 100644
--- a/handler/template/template.go
+++ b/handler/template/template.go
@@ -33,4 +33,7 @@ var funcMap = map[string]interface{}{
"tag": func(s string) string {
return strings.TrimPrefix(s, "refs/tags/")
},
+ "done": func(s string) bool {
+ return s != "pending" && s != "running"
+ },
}
diff --git a/handler/template/template_gen.go b/handler/template/template_gen.go
index 77b990a..3fb9a42 100644
--- a/handler/template/template_gen.go
+++ b/handler/template/template_gen.go
@@ -164,7 +164,9 @@ var stage = `
-
+{{- if not (done .Stage.Status) }}
+
+{{- end }}
Dashboard
@@ -223,20 +225,34 @@ var stage = `
{{ if .Stage.Steps }}
-
{{ range .Stage.Steps }}
- {{ .Name }}
+ {{ .Name }}
+ {{ .Status }}
{{ end }}
{{ end }}
+
+ {{ if .Logs }}
+
+ {{ range .Logs }}
+
+ {{ .Level }}
+ {{ .Message }}
+
+ {{ range $key, $val := .Data }}
+ {{ $key }}{{ $val }}
+ {{ end }}
+
+
+
+ {{ end }}
+
+ {{ end }}
diff --git a/handler/template/testdata/stage.json b/handler/template/testdata/stage.json
index 24f2f6b..c5ce459 100644
--- a/handler/template/testdata/stage.json
+++ b/handler/template/testdata/stage.json
@@ -15,7 +15,7 @@
},
"Stage": {
"Name": "test",
- "Status": "success",
+ "Status": "running",
"Started": 1563059000,
"Created": 1563059000,
"Steps": [
@@ -24,5 +24,71 @@
{ "Name": "test", "Status": "success" },
{ "Name": "deploy", "Status": "success" }
]
- }
+ },
+ "Logs": [
+ {
+ "Level": "debug",
+ "Message": "updated stage to running",
+ "Data": {
+ "build.id": 110,
+ "build.number": 110,
+ "repo.id": 48,
+ "repo.name": "hello-world",
+ "repo.namespace": "octocat",
+ "stage.id": 110,
+ "stage.name": "test",
+ "stage.number": 1,
+ "thread": 1
+ },
+ "Unix": 1563058875
+ },
+ {
+ "Level": "debug",
+ "Message": "process started",
+ "Data": {
+ "build.id": 110,
+ "build.number": 110,
+ "repo.id": 48,
+ "repo.name": "hello-world",
+ "repo.namespace": "octocat",
+ "stage.id": 110,
+ "stage.name": "test",
+ "stage.number": 1,
+ "thread": 1
+ },
+ "Unix": 1563058875
+ },
+ {
+ "Level": "debug",
+ "Message": "process finished",
+ "Data": {
+ "build.id": 110,
+ "build.number": 110,
+ "repo.id": 48,
+ "repo.name": "hello-world",
+ "repo.namespace": "octocat",
+ "stage.id": 110,
+ "stage.name": "test",
+ "stage.number": 1,
+ "thread": 1
+ },
+ "Unix": 1563058975
+ },
+ {
+ "Level": "debug",
+ "Message": "updated stage to complete",
+ "Data": {
+ "build.id": 110,
+ "build.number": 110,
+ "repo.id": 48,
+ "repo.name": "hello-world",
+ "repo.namespace": "octocat",
+ "stage.id": 110,
+ "stage.name": "test",
+ "stage.number": 1,
+ "thread": 1
+ },
+ "Unix": 1563058977
+ }
+ ]
}
diff --git a/logger/history/history.go b/logger/history/history.go
index 6cd02a3..a195416 100644
--- a/logger/history/history.go
+++ b/logger/history/history.go
@@ -85,6 +85,20 @@ func (h *Hook) Entries() []*Entry {
return entries
}
+// Filter returns a list of all entries for which the filter
+// function returns true.
+func (h *Hook) Filter(filter func(*Entry) bool) []*Entry {
+ h.RLock()
+ defer h.RUnlock()
+ var entries []*Entry
+ for _, entry := range h.entries {
+ if filter(entry) {
+ entries = append(entries, copyEntry(entry))
+ }
+ }
+ return entries
+}
+
// helper funtion copies an entry for threadsafe access.
func copyEntry(src *Entry) *Entry {
dst := new(Entry)
diff --git a/logger/history/history_test.go b/logger/history/history_test.go
index ab4bc47..1079572 100644
--- a/logger/history/history_test.go
+++ b/logger/history/history_test.go
@@ -107,3 +107,38 @@ func TestHistory(t *testing.T) {
t.Log(diff)
}
}
+
+func TestFilter(t *testing.T) {
+ hook := New()
+
+ now := time.Now()
+ hook.Fire(&logrus.Entry{
+ Level: logrus.DebugLevel,
+ Message: "foo",
+ Data: logrus.Fields{"foo": "bar"},
+ Time: now,
+ })
+
+ hook.Fire(&logrus.Entry{
+ Level: logrus.InfoLevel,
+ Message: "bar",
+ Data: logrus.Fields{"baz": "qux"},
+ Time: now,
+ })
+
+ expect := []*Entry{
+ {
+ Level: LevelDebug,
+ Message: "foo",
+ Data: logrus.Fields{"foo": "bar"},
+ Unix: now.Unix(),
+ },
+ }
+ entries := hook.Filter(func(entry *Entry) bool {
+ return entry.Data["foo"] == "bar"
+ })
+ if diff := cmp.Diff(entries, expect); diff != "" {
+ t.Errorf("Entries should return an exact copy of all entries")
+ t.Log(diff)
+ }
+}
diff --git a/pipeline/history/history.go b/pipeline/history/history.go
index cee3bcb..b4140a4 100644
--- a/pipeline/history/history.go
+++ b/pipeline/history/history.go
@@ -64,6 +64,20 @@ func (h *History) Entries() []*Entry {
return entries
}
+// Entry returns the entry by id.
+func (h *History) Entry(id int64) *Entry {
+ h.Lock()
+ defer h.Unlock()
+ for _, src := range h.items {
+ if src.Stage.ID == id {
+ dst := new(Entry)
+ *dst = *src
+ return dst
+ }
+ }
+ return nil
+}
+
// Limit returns the history limit.
func (h *History) Limit() int {
if h.limit == 0 {
diff --git a/pipeline/history/history_test.go b/pipeline/history/history_test.go
index f33635f..6968c35 100644
--- a/pipeline/history/history_test.go
+++ b/pipeline/history/history_test.go
@@ -116,6 +116,21 @@ func TestEntries(t *testing.T) {
}
}
+func TestEntry(t *testing.T) {
+ s1 := &drone.Stage{ID: 1}
+ s2 := &drone.Stage{ID: 2}
+ v := History{}
+ v.items = append(v.items, &Entry{Stage: s1}, &Entry{Stage: s2})
+
+ if got := v.Entry(99); got != nil {
+ t.Errorf("Want nil when stage not found")
+ }
+ if got := v.Entry(s1.ID); got == nil {
+ t.Errorf("Want entry by stage ID, got nil")
+ return
+ }
+}
+
func TestLimit(t *testing.T) {
v := History{}
if got, want := v.Limit(), defaultLimit; got != want {