Skip to content

Commit 7949404

Browse files
lafriksappleboy
authored andcommitted
Show commit status icon in commits table (#1688)
* Show commit status icon in commits table * Add comments * Fix icons * Few more places where commit table is displayed * Change integration test to use goquery for parsing html * Add integration tests for commit table and status icons * Fix status to return lates status correctly on all databases * Rewrote lates commit status selects
1 parent c864ccf commit 7949404

24 files changed

+4185
-87
lines changed

integrations/html_helper.go

Lines changed: 7 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -7,104 +7,28 @@ package integrations
77
import (
88
"bytes"
99

10-
"golang.org/x/net/html"
10+
"github.com/PuerkitoBio/goquery"
1111
)
1212

1313
type HtmlDoc struct {
14-
doc *html.Node
15-
body *html.Node
14+
doc *goquery.Document
1615
}
1716

1817
func NewHtmlParser(content []byte) (*HtmlDoc, error) {
19-
doc, err := html.Parse(bytes.NewReader(content))
18+
doc, err := goquery.NewDocumentFromReader(bytes.NewReader(content))
2019
if err != nil {
2120
return nil, err
2221
}
2322

2423
return &HtmlDoc{doc: doc}, nil
2524
}
2625

27-
func (doc *HtmlDoc) GetBody() *html.Node {
28-
if doc.body == nil {
29-
var b *html.Node
30-
var f func(*html.Node)
31-
f = func(n *html.Node) {
32-
if n.Type == html.ElementNode && n.Data == "body" {
33-
b = n
34-
return
35-
}
36-
for c := n.FirstChild; c != nil; c = c.NextSibling {
37-
f(c)
38-
}
39-
}
40-
f(doc.doc)
41-
if b != nil {
42-
doc.body = b
43-
} else {
44-
doc.body = doc.doc
45-
}
46-
}
47-
return doc.body
48-
}
49-
50-
func (doc *HtmlDoc) GetAttribute(n *html.Node, key string) (string, bool) {
51-
for _, attr := range n.Attr {
52-
if attr.Key == key {
53-
return attr.Val, true
54-
}
55-
}
56-
return "", false
57-
}
58-
59-
func (doc *HtmlDoc) checkAttr(n *html.Node, attr, val string) bool {
60-
if n.Type == html.ElementNode {
61-
s, ok := doc.GetAttribute(n, attr)
62-
if ok && s == val {
63-
return true
64-
}
65-
}
66-
return false
67-
}
68-
69-
func (doc *HtmlDoc) traverse(n *html.Node, attr, val string) *html.Node {
70-
if doc.checkAttr(n, attr, val) {
71-
return n
72-
}
73-
74-
for c := n.FirstChild; c != nil; c = c.NextSibling {
75-
result := doc.traverse(c, attr, val)
76-
if result != nil {
77-
return result
78-
}
79-
}
80-
81-
return nil
82-
}
83-
84-
func (doc *HtmlDoc) GetElementById(id string) *html.Node {
85-
return doc.traverse(doc.GetBody(), "id", id)
86-
}
87-
8826
func (doc *HtmlDoc) GetInputValueById(id string) string {
89-
inp := doc.GetElementById(id)
90-
if inp == nil {
91-
return ""
92-
}
93-
94-
val, _ := doc.GetAttribute(inp, "value")
95-
return val
96-
}
97-
98-
func (doc *HtmlDoc) GetElementByName(name string) *html.Node {
99-
return doc.traverse(doc.GetBody(), "name", name)
27+
text, _ := doc.doc.Find("#" + id).Attr("value")
28+
return text
10029
}
10130

10231
func (doc *HtmlDoc) GetInputValueByName(name string) string {
103-
inp := doc.GetElementByName(name)
104-
if inp == nil {
105-
return ""
106-
}
107-
108-
val, _ := doc.GetAttribute(inp, "value")
109-
return val
32+
text, _ := doc.doc.Find("input[name=\"" + name + "\"]").Attr("value")
33+
return text
11034
}

integrations/repo_commits_test.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Copyright 2017 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package integrations
6+
7+
import (
8+
"bytes"
9+
"net/http"
10+
"path"
11+
"testing"
12+
13+
"github.com/stretchr/testify/assert"
14+
)
15+
16+
func TestRepoCommits(t *testing.T) {
17+
prepareTestEnv(t)
18+
19+
session := loginUser(t, "user2", "password")
20+
21+
// Request repository commits page
22+
req, err := http.NewRequest("GET", "/user2/repo1/commits/master", nil)
23+
assert.NoError(t, err)
24+
resp := session.MakeRequest(t, req)
25+
assert.EqualValues(t, http.StatusOK, resp.HeaderCode)
26+
27+
doc, err := NewHtmlParser(resp.Body)
28+
assert.NoError(t, err)
29+
commitURL, exists := doc.doc.Find("#commits-table tbody tr td.sha a").Attr("href")
30+
assert.True(t, exists)
31+
assert.NotEmpty(t, commitURL)
32+
}
33+
34+
func doTestRepoCommitWithStatus(t *testing.T, state string, classes ...string) {
35+
prepareTestEnv(t)
36+
37+
session := loginUser(t, "user2", "password")
38+
39+
// Request repository commits page
40+
req, err := http.NewRequest("GET", "/user2/repo1/commits/master", nil)
41+
assert.NoError(t, err)
42+
resp := session.MakeRequest(t, req)
43+
assert.EqualValues(t, http.StatusOK, resp.HeaderCode)
44+
45+
doc, err := NewHtmlParser(resp.Body)
46+
assert.NoError(t, err)
47+
// Get first commit URL
48+
commitURL, exists := doc.doc.Find("#commits-table tbody tr td.sha a").Attr("href")
49+
assert.True(t, exists)
50+
assert.NotEmpty(t, commitURL)
51+
52+
// Call API to add status for commit
53+
req, err = http.NewRequest("POST", "/api/v1/repos/user2/repo1/statuses/"+path.Base(commitURL),
54+
bytes.NewBufferString("{\"state\":\""+state+"\", \"target_url\": \"http://test.ci/\", \"description\": \"\", \"context\": \"testci\"}"))
55+
56+
assert.NoError(t, err)
57+
req.Header.Add("Content-Type", "application/json")
58+
resp = session.MakeRequest(t, req)
59+
assert.EqualValues(t, http.StatusCreated, resp.HeaderCode)
60+
61+
req, err = http.NewRequest("GET", "/user2/repo1/commits/master", nil)
62+
assert.NoError(t, err)
63+
resp = session.MakeRequest(t, req)
64+
assert.EqualValues(t, http.StatusOK, resp.HeaderCode)
65+
66+
doc, err = NewHtmlParser(resp.Body)
67+
assert.NoError(t, err)
68+
// Check if commit status is displayed in message column
69+
sel := doc.doc.Find("#commits-table tbody tr td.message i.commit-status")
70+
assert.Equal(t, sel.Length(), 1)
71+
for _, class := range classes {
72+
assert.True(t, sel.HasClass(class))
73+
}
74+
}
75+
76+
func TestRepoCommitsWithStatusPending(t *testing.T) {
77+
doTestRepoCommitWithStatus(t, "pending", "circle", "yellow")
78+
}
79+
80+
func TestRepoCommitsWithStatusSuccess(t *testing.T) {
81+
doTestRepoCommitWithStatus(t, "success", "check", "green")
82+
}
83+
84+
func TestRepoCommitsWithStatusError(t *testing.T) {
85+
doTestRepoCommitWithStatus(t, "error", "warning", "red")
86+
}
87+
88+
func TestRepoCommitsWithStatusFailure(t *testing.T) {
89+
doTestRepoCommitWithStatus(t, "failure", "remove", "red")
90+
}
91+
92+
func TestRepoCommitsWithStatusWarning(t *testing.T) {
93+
doTestRepoCommitWithStatus(t, "warning", "warning", "sign", "yellow")
94+
}

models/status.go

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package models
66

77
import (
8+
"container/list"
89
"fmt"
910
"strings"
1011
"time"
@@ -144,10 +145,20 @@ func GetCommitStatuses(repo *Repository, sha string, page int) ([]*CommitStatus,
144145

145146
// GetLatestCommitStatus returns all statuses with a unique context for a given commit.
146147
func GetLatestCommitStatus(repo *Repository, sha string, page int) ([]*CommitStatus, error) {
147-
statuses := make([]*CommitStatus, 0, 10)
148-
return statuses, x.Limit(10, page*10).
149-
Where("repo_id = ?", repo.ID).And("sha = ?", sha).Select("*").
150-
GroupBy("context").Desc("created_unix").Find(&statuses)
148+
ids := make([]int64, 0, 10)
149+
err := x.Limit(10, page*10).
150+
Table(&CommitStatus{}).
151+
Where("repo_id = ?", repo.ID).And("sha = ?", sha).
152+
Select("max( id ) as id").
153+
GroupBy("context").OrderBy("max( id ) desc").Find(&ids)
154+
if err != nil {
155+
return nil, err
156+
}
157+
statuses := make([]*CommitStatus, 0, len(ids))
158+
if len(ids) == 0 {
159+
return statuses, nil
160+
}
161+
return statuses, x.In("id", ids).Find(&statuses)
151162
}
152163

153164
// GetCommitStatus populates a given status for a given commit.
@@ -252,3 +263,42 @@ func NewCommitStatus(repo *Repository, creator *User, sha string, status *Commit
252263

253264
return sess.Commit()
254265
}
266+
267+
// SignCommitWithStatuses represents a commit with validation of signature and status state.
268+
type SignCommitWithStatuses struct {
269+
Statuses []*CommitStatus
270+
State CommitStatusState
271+
*SignCommit
272+
}
273+
274+
// ParseCommitsWithStatus checks commits latest statuses and calculates its worst status state
275+
func ParseCommitsWithStatus(oldCommits *list.List, repo *Repository) *list.List {
276+
var (
277+
newCommits = list.New()
278+
e = oldCommits.Front()
279+
err error
280+
)
281+
282+
for e != nil {
283+
c := e.Value.(SignCommit)
284+
commit := SignCommitWithStatuses{
285+
SignCommit: &c,
286+
State: "",
287+
Statuses: make([]*CommitStatus, 0),
288+
}
289+
commit.Statuses, err = GetLatestCommitStatus(repo, commit.ID.String(), 0)
290+
if err != nil {
291+
log.Error(3, "GetLatestCommitStatus: %v", err)
292+
} else {
293+
for _, status := range commit.Statuses {
294+
if status.State.IsWorseThan(commit.State) {
295+
commit.State = status.State
296+
}
297+
}
298+
}
299+
300+
newCommits.PushBack(commit)
301+
e = e.Next()
302+
}
303+
return newCommits
304+
}

routers/repo/commit.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ func Commits(ctx *context.Context) {
6969
commits = renderIssueLinks(commits, ctx.Repo.RepoLink)
7070
commits = models.ValidateCommitsWithEmails(commits)
7171
commits = models.ParseCommitsWithSignature(commits)
72+
commits = models.ParseCommitsWithStatus(commits, ctx.Repo.Repository)
7273
ctx.Data["Commits"] = commits
7374

7475
ctx.Data["Username"] = ctx.Repo.Owner.Name
@@ -123,6 +124,7 @@ func SearchCommits(ctx *context.Context) {
123124
commits = renderIssueLinks(commits, ctx.Repo.RepoLink)
124125
commits = models.ValidateCommitsWithEmails(commits)
125126
commits = models.ParseCommitsWithSignature(commits)
127+
commits = models.ParseCommitsWithStatus(commits, ctx.Repo.Repository)
126128
ctx.Data["Commits"] = commits
127129

128130
ctx.Data["Keyword"] = keyword
@@ -170,6 +172,7 @@ func FileHistory(ctx *context.Context) {
170172
commits = renderIssueLinks(commits, ctx.Repo.RepoLink)
171173
commits = models.ValidateCommitsWithEmails(commits)
172174
commits = models.ParseCommitsWithSignature(commits)
175+
commits = models.ParseCommitsWithStatus(commits, ctx.Repo.Repository)
173176
ctx.Data["Commits"] = commits
174177

175178
ctx.Data["Username"] = ctx.Repo.Owner.Name
@@ -281,6 +284,7 @@ func CompareDiff(ctx *context.Context) {
281284
}
282285
commits = models.ValidateCommitsWithEmails(commits)
283286
commits = models.ParseCommitsWithSignature(commits)
287+
commits = models.ParseCommitsWithStatus(commits, ctx.Repo.Repository)
284288

285289
ctx.Data["CommitRepoLink"] = ctx.Repo.RepoLink
286290
ctx.Data["Commits"] = commits

routers/repo/pull.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,8 @@ func ViewPullCommits(ctx *context.Context) {
293293
}
294294

295295
commits = models.ValidateCommitsWithEmails(commits)
296+
commits = models.ParseCommitsWithSignature(commits)
297+
commits = models.ParseCommitsWithStatus(commits, ctx.Repo.Repository)
296298
ctx.Data["Commits"] = commits
297299
ctx.Data["CommitCount"] = commits.Len()
298300

@@ -576,6 +578,8 @@ func PrepareCompareDiff(
576578
}
577579

578580
prInfo.Commits = models.ValidateCommitsWithEmails(prInfo.Commits)
581+
prInfo.Commits = models.ParseCommitsWithSignature(prInfo.Commits)
582+
prInfo.Commits = models.ParseCommitsWithStatus(prInfo.Commits, headRepo)
579583
ctx.Data["Commits"] = prInfo.Commits
580584
ctx.Data["CommitCount"] = prInfo.Commits.Len()
581585
ctx.Data["Username"] = headUser.Name

templates/repo/commits_table.tmpl

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,21 @@
6161
</td>
6262
<td class="message collapsing">
6363
<span class="has-emoji{{if gt .ParentCount 1}} grey text{{end}}">{{RenderCommitMessage false .Summary $.RepoLink $.Repository.ComposeMetas}}</span>
64+
{{if eq .State "pending"}}
65+
<i class="commit-status circle icon yellow"></i>
66+
{{end}}
67+
{{if eq .State "success"}}
68+
<i class="commit-status check icon green"></i>
69+
{{end}}
70+
{{if eq .State "error"}}
71+
<i class="commit-status warning icon red"></i>
72+
{{end}}
73+
{{if eq .State "failure"}}
74+
<i class="commit-status remove icon red"></i>
75+
{{end}}
76+
{{if eq .State "warning"}}
77+
<i class="commit-status warning sign icon yellow"></i>
78+
{{end}}
6479
</td>
6580
<td class="grey text right aligned">{{TimeSince .Author.When $.Lang}}</td>
6681
</tr>

vendor/github.com/PuerkitoBio/goquery/LICENSE

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)