From 58893384e848b54687c21d6d2ca38b70b3036ae2 Mon Sep 17 00:00:00 2001
From: Lauris BH <lauris@nix.lv>
Date: Mon, 19 Feb 2018 04:39:26 +0200
Subject: [PATCH] Add issue closed time column to fix activity closed issues
 list (#3537)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Lauris Bukšis-Haberkorns <lauris@nix.lv>
---
 models/issue.go                 |  8 +++++++-
 models/issue_milestone_test.go  |  6 ++++--
 models/migrations/migrations.go |  2 ++
 models/migrations/v57.go        | 30 ++++++++++++++++++++++++++++++
 models/repo_activity.go         |  8 ++++++--
 templates/repo/activity.tmpl    |  2 +-
 6 files changed, 50 insertions(+), 6 deletions(-)
 create mode 100644 models/migrations/v57.go

diff --git a/models/issue.go b/models/issue.go
index d10c521db..9106db281 100644
--- a/models/issue.go
+++ b/models/issue.go
@@ -49,6 +49,7 @@ type Issue struct {
 	DeadlineUnix util.TimeStamp `xorm:"INDEX"`
 	CreatedUnix  util.TimeStamp `xorm:"INDEX created"`
 	UpdatedUnix  util.TimeStamp `xorm:"INDEX updated"`
+	ClosedUnix   util.TimeStamp `xorm:"INDEX"`
 
 	Attachments []*Attachment `xorm:"-"`
 	Comments    []*Comment    `xorm:"-"`
@@ -612,8 +613,13 @@ func (issue *Issue) changeStatus(e *xorm.Session, doer *User, repo *Repository,
 		return nil
 	}
 	issue.IsClosed = isClosed
+	if isClosed {
+		issue.ClosedUnix = util.TimeStampNow()
+	} else {
+		issue.ClosedUnix = 0
+	}
 
-	if err = updateIssueCols(e, issue, "is_closed"); err != nil {
+	if err = updateIssueCols(e, issue, "is_closed", "closed_unix"); err != nil {
 		return err
 	}
 
diff --git a/models/issue_milestone_test.go b/models/issue_milestone_test.go
index f7987d45a..c57f92439 100644
--- a/models/issue_milestone_test.go
+++ b/models/issue_milestone_test.go
@@ -214,13 +214,15 @@ func TestChangeMilestoneIssueStats(t *testing.T) {
 		"is_closed=0").(*Issue)
 
 	issue.IsClosed = true
-	_, err := x.Cols("is_closed").Update(issue)
+	issue.ClosedUnix = util.TimeStampNow()
+	_, err := x.Cols("is_closed", "closed_unix").Update(issue)
 	assert.NoError(t, err)
 	assert.NoError(t, changeMilestoneIssueStats(x.NewSession(), issue))
 	CheckConsistencyFor(t, &Milestone{})
 
 	issue.IsClosed = false
-	_, err = x.Cols("is_closed").Update(issue)
+	issue.ClosedUnix = 0
+	_, err = x.Cols("is_closed", "closed_unix").Update(issue)
 	assert.NoError(t, err)
 	assert.NoError(t, changeMilestoneIssueStats(x.NewSession(), issue))
 	CheckConsistencyFor(t, &Milestone{})
diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index d79d40371..dfaef2c78 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -166,6 +166,8 @@ var migrations = []Migration{
 	NewMigration("add writable deploy keys", addModeToDeploKeys),
 	// v56 -> v57
 	NewMigration("remove is_owner, num_teams columns from org_user", removeIsOwnerColumnFromOrgUser),
+	// v57 -> v58
+	NewMigration("add closed_unix column for issues", addIssueClosedTime),
 }
 
 // Migrate database to current version
diff --git a/models/migrations/v57.go b/models/migrations/v57.go
new file mode 100644
index 000000000..3a79a5cca
--- /dev/null
+++ b/models/migrations/v57.go
@@ -0,0 +1,30 @@
+// Copyright 2017 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package migrations
+
+import (
+	"fmt"
+
+	"code.gitea.io/gitea/modules/util"
+
+	"github.com/go-xorm/xorm"
+)
+
+func addIssueClosedTime(x *xorm.Engine) error {
+	// Issue see models/issue.go
+	type Issue struct {
+		ClosedUnix util.TimeStamp `xorm:"INDEX"`
+	}
+
+	if err := x.Sync2(new(Issue)); err != nil {
+		return fmt.Errorf("Sync2: %v", err)
+	}
+
+	if _, err := x.Exec("UPDATE `issue` SET `closed_unix` = `updated_unix` WHERE `is_closed` = ?", true); err != nil {
+		return err
+	}
+
+	return nil
+}
diff --git a/models/repo_activity.go b/models/repo_activity.go
index 839a74a7e..c3017e8e3 100644
--- a/models/repo_activity.go
+++ b/models/repo_activity.go
@@ -176,7 +176,7 @@ func (stats *ActivityStats) FillIssues(repoID int64, fromTime time.Time) error {
 
 	// Closed issues
 	sess := issuesForActivityStatement(repoID, fromTime, true, false)
-	sess.OrderBy("issue.updated_unix DESC")
+	sess.OrderBy("issue.closed_unix DESC")
 	stats.ClosedIssues = make(IssueList, 0)
 	if err = sess.Find(&stats.ClosedIssues); err != nil {
 		return err
@@ -228,7 +228,11 @@ func issuesForActivityStatement(repoID int64, fromTime time.Time, closed, unreso
 
 	if !unresolved {
 		sess.And("issue.is_pull = ?", false)
-		sess.And("issue.created_unix >= ?", fromTime.Unix())
+		if closed {
+			sess.And("issue.closed_unix >= ?", fromTime.Unix())
+		} else {
+			sess.And("issue.created_unix >= ?", fromTime.Unix())
+		}
 	} else {
 		sess.And("issue.created_unix < ?", fromTime.Unix())
 		sess.And("issue.updated_unix >= ?", fromTime.Unix())
diff --git a/templates/repo/activity.tmpl b/templates/repo/activity.tmpl
index cd528582f..f5454afb9 100644
--- a/templates/repo/activity.tmpl
+++ b/templates/repo/activity.tmpl
@@ -134,7 +134,7 @@
 					<p class="desc">
 						<div class="ui red label">{{$.i18n.Tr "repo.activity.closed_issue_label"}}</div>
 						#{{.Index}} <a class="title has-emoji" href="{{$.Repository.HTMLURL}}/issues/{{.Index}}">{{.Title}}</a>
-						{{TimeSinceUnix .UpdatedUnix $.Lang}}
+						{{TimeSinceUnix .ClosedUnix $.Lang}}
 					</p>
 				{{end}}
 			</div>