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 }}
-
- - {{ .Stage.Name }} -
{{ 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 }}
-
- - {{ .Stage.Name }} -
{{ 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 {