diff --git a/alembic/versions/1f24c9c8efa5_store_the_user_who_closed_an_issue_in_.py b/alembic/versions/1f24c9c8efa5_store_the_user_who_closed_an_issue_in_.py
new file mode 100644
index 0000000..29c963b
--- /dev/null
+++ b/alembic/versions/1f24c9c8efa5_store_the_user_who_closed_an_issue_in_.py
@@ -0,0 +1,31 @@
+"""Store the user who closed an issue in the db
+
+Revision ID: 1f24c9c8efa5
+Revises: 6a3ed02ee160
+Create Date: 2018-12-04 13:02:57.101095
+
+"""
+
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = "1f24c9c8efa5"
+down_revision = "6a3ed02ee160"
+
+
+def upgrade():
+
+ op.add_column(
+ "issues",
+ sa.Column(
+ "closed_by_id",
+ sa.Integer,
+ sa.ForeignKey("users.id", onupdate="CASCADE"),
+ ),
+ )
+
+
+def downgrade():
+ op.drop_column("issues", "closed_by_id")
diff --git a/pagure/api/issue.py b/pagure/api/issue.py
index 4c529b1..68c7e1c 100644
--- a/pagure/api/issue.py
+++ b/pagure/api/issue.py
@@ -249,6 +249,7 @@ def api_new_issue(repo, username=None, namespace=None):
"blocks": [],
"close_status": null,
"closed_at": null,
+ "closed_by": null,
"comments": [],
"content": "This issue needs attention",
"custom_fields": [],
@@ -465,6 +466,7 @@ def api_view_issues(repo, username=None, namespace=None):
"blocks": ["1"],
"close_status": null,
"closed_at": null,
+ "closed_by": null,
"comments": [],
"content": "asd",
"custom_fields": [],
diff --git a/pagure/api/user.py b/pagure/api/user.py
index c8c1f4b..15ddbc8 100644
--- a/pagure/api/user.py
+++ b/pagure/api/user.py
@@ -286,6 +286,7 @@ def api_view_user_issues(username):
"blocks": [],
"close_status": null,
"closed_at": null,
+ "closed_by": null,
"comments": [],
"content": "Test Issue",
"custom_fields": [],
@@ -314,6 +315,7 @@ def api_view_user_issues(username):
"blocks": [],
"close_status": null,
"closed_at": null,
+ "closed_by": null,
"comments": [],
"content": "Test Issue",
"custom_fields": [],
diff --git a/pagure/lib/model.py b/pagure/lib/model.py
index 1e6d9f4..6f4252b 100644
--- a/pagure/lib/model.py
+++ b/pagure/lib/model.py
@@ -1244,6 +1244,11 @@ class Issue(BASE):
sa.DateTime, nullable=False, default=datetime.datetime.utcnow
)
closed_at = sa.Column(sa.DateTime, nullable=True)
+ closed_by_id = sa.Column(
+ sa.Integer,
+ sa.ForeignKey("users.id", onupdate="CASCADE"),
+ nullable=True,
+ )
project = relation(
"Project",
@@ -1279,6 +1284,13 @@ class Issue(BASE):
viewonly=True,
)
+ closed_by = relation(
+ "User",
+ foreign_keys=[closed_by_id],
+ remote_side=[User.id],
+ backref="closed_issues",
+ )
+
def __repr__(self):
return "Issue(%s, project:%s, user:%s, title:%s)" % (
self.id,
@@ -1436,6 +1448,9 @@ class Issue(BASE):
"priority": self.priority,
"milestone": self.milestone,
"custom_fields": custom_fields,
+ "closed_by": self.closed_by.to_json(public=public)
+ if self.closed_by
+ else None,
}
comments = []
diff --git a/pagure/lib/query.py b/pagure/lib/query.py
index ecf7434..529e5ef 100644
--- a/pagure/lib/query.py
+++ b/pagure/lib/query.py
@@ -2026,6 +2026,7 @@ def edit_issue(
issue.status = status
if status.lower() != "open":
issue.closed_at = datetime.datetime.utcnow()
+ issue.closed_by_id = user_obj.id
elif issue.close_status:
issue.close_status = None
close_status = Unspecified
diff --git a/pagure/templates/issue.html b/pagure/templates/issue.html
index 117c2c5..e3a01f8 100644
--- a/pagure/templates/issue.html
+++ b/pagure/templates/issue.html
@@ -90,8 +90,10 @@
{% endif %}
{{ issue.closed_at |humanize }}
+ {% if issue.closed_by %}
by
- {{ issue.user.user }}.
+ {{ issue.closed_by.user }}.
+ {% endif %}
Opened {{ issue.date_created |humanize }}
diff --git a/tests/test_pagure_flask_api_issue.py b/tests/test_pagure_flask_api_issue.py
index 8feb4cf..39290ea 100644
--- a/tests/test_pagure_flask_api_issue.py
+++ b/tests/test_pagure_flask_api_issue.py
@@ -40,6 +40,7 @@ FULL_ISSUE_LIST = [
"blocks": [],
"close_status": None,
"closed_at": None,
+ "closed_by": None,
"comments": [],
"content": "We should work on this",
"custom_fields": [],
@@ -63,6 +64,7 @@ FULL_ISSUE_LIST = [
"blocks": [],
"close_status": None,
"closed_at": None,
+ "closed_by": None,
"comments": [],
"content": "This issue needs attention",
"custom_fields": [],
@@ -86,6 +88,7 @@ FULL_ISSUE_LIST = [
"blocks": [],
"close_status": None,
"closed_at": None,
+ "closed_by": None,
"comments": [],
"content": "This issue needs attention",
"custom_fields": [],
@@ -109,6 +112,7 @@ FULL_ISSUE_LIST = [
"blocks": [],
"close_status": None,
"closed_at": None,
+ "closed_by": None,
"comments": [],
"content": "This issue needs attention",
"custom_fields": [],
@@ -132,6 +136,7 @@ FULL_ISSUE_LIST = [
"blocks": [],
"close_status": None,
"closed_at": None,
+ "closed_by": None,
"comments": [],
"content": "This issue needs attention",
"custom_fields": [],
@@ -155,6 +160,7 @@ FULL_ISSUE_LIST = [
"blocks": [],
"close_status": None,
"closed_at": None,
+ "closed_by": None,
"comments": [],
"content": "This issue needs attention",
"custom_fields": [],
@@ -178,6 +184,7 @@ FULL_ISSUE_LIST = [
"blocks": [],
"close_status": None,
"closed_at": None,
+ "closed_by": None,
"comments": [],
"content": "This issue needs attention",
"custom_fields": [],
@@ -201,6 +208,7 @@ FULL_ISSUE_LIST = [
"blocks": [],
"close_status": None,
"closed_at": None,
+ "closed_by": None,
"comments": [],
"content": "This issue needs attention",
"custom_fields": [],
@@ -224,6 +232,7 @@ FULL_ISSUE_LIST = [
"blocks": [],
"close_status": None,
"closed_at": None,
+ "closed_by": None,
"comments": [],
"content": "This issue needs attention",
"custom_fields": [],
@@ -251,6 +260,7 @@ LCL_ISSUES = [
'blocks': [],
'close_status': None,
'closed_at': None,
+ "closed_by": None,
'comments': [],
'content': 'Description',
'custom_fields': [],
@@ -271,6 +281,7 @@ LCL_ISSUES = [
'blocks': [],
'close_status': None,
'closed_at': None,
+ "closed_by": None,
'comments': [],
'content': 'Description',
'custom_fields': [],
@@ -2337,6 +2348,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
'blocks': [],
'close_status': None,
'closed_at': None,
+ 'closed_by': None,
'comments': [],
'content': 'Description',
'custom_fields': [],
@@ -2410,6 +2422,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
"date_created": "1431414800",
"close_status": None,
"closed_at": None,
+ "closed_by": None,
"depends": [],
"id": 1,
"last_updated": "1431414800",
@@ -2508,6 +2521,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
"date_created": "1431414800",
"close_status": None,
"closed_at": None,
+ "closed_by": None,
"depends": [],
"id": 2,
"last_updated": "1431414800",
@@ -2541,6 +2555,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
"date_created": "1431414800",
"close_status": None,
"closed_at": None,
+ "closed_by": None,
"depends": [],
"id": 2,
"last_updated": "1431414800",
diff --git a/tests/test_pagure_flask_api_issue_create.py b/tests/test_pagure_flask_api_issue_create.py
index b7722eb..8247033 100644
--- a/tests/test_pagure_flask_api_issue_create.py
+++ b/tests/test_pagure_flask_api_issue_create.py
@@ -152,6 +152,7 @@ class PagureFlaskApiIssueCreatetests(tests.Modeltests):
"blocks": [],
"close_status": None,
"closed_at": None,
+ "closed_by": None,
"comments": [],
"content": "This issue needs attention",
"custom_fields": [],
@@ -206,6 +207,7 @@ class PagureFlaskApiIssueCreatetests(tests.Modeltests):
"blocks": [],
"close_status": None,
"closed_at": None,
+ "closed_by": None,
"comments": [],
"content": "This issue needs attention",
"custom_fields": [],
@@ -260,6 +262,7 @@ class PagureFlaskApiIssueCreatetests(tests.Modeltests):
"blocks": [],
"close_status": None,
"closed_at": None,
+ "closed_by": None,
"comments": [],
"content": "This issue needs attention",
"custom_fields": [],
diff --git a/tests/test_pagure_flask_api_ui_private_repo.py b/tests/test_pagure_flask_api_ui_private_repo.py
index 9ff217d..3e7c3cf 100644
--- a/tests/test_pagure_flask_api_ui_private_repo.py
+++ b/tests/test_pagure_flask_api_ui_private_repo.py
@@ -29,6 +29,7 @@ FULL_ISSUE_LIST = [
"blocks": [],
"close_status": None,
"closed_at": None,
+ "closed_by": None,
"comments": [],
"content": "We should work on this",
"custom_fields": [],
@@ -52,6 +53,7 @@ FULL_ISSUE_LIST = [
"blocks": [],
"close_status": None,
"closed_at": None,
+ "closed_by": None,
"comments": [],
"content": "This issue needs attention",
"custom_fields": [],
@@ -75,6 +77,7 @@ FULL_ISSUE_LIST = [
"blocks": [],
"close_status": None,
"closed_at": None,
+ "closed_by": None,
"comments": [],
"content": "This issue needs attention",
"custom_fields": [],
@@ -98,6 +101,7 @@ FULL_ISSUE_LIST = [
"blocks": [],
"close_status": None,
"closed_at": None,
+ "closed_by": None,
"comments": [],
"content": "This issue needs attention",
"custom_fields": [],
@@ -121,6 +125,7 @@ FULL_ISSUE_LIST = [
"blocks": [],
"close_status": None,
"closed_at": None,
+ "closed_by": None,
"comments": [],
"content": "This issue needs attention",
"custom_fields": [],
@@ -144,6 +149,7 @@ FULL_ISSUE_LIST = [
"blocks": [],
"close_status": None,
"closed_at": None,
+ "closed_by": None,
"comments": [],
"content": "This issue needs attention",
"custom_fields": [],
@@ -167,6 +173,7 @@ FULL_ISSUE_LIST = [
"blocks": [],
"close_status": None,
"closed_at": None,
+ "closed_by": None,
"comments": [],
"content": "This issue needs attention",
"custom_fields": [],
@@ -190,6 +197,7 @@ FULL_ISSUE_LIST = [
"blocks": [],
"close_status": None,
"closed_at": None,
+ "closed_by": None,
"comments": [],
"content": "This issue needs attention",
"custom_fields": [],
@@ -2498,6 +2506,7 @@ class PagurePrivateRepotest(tests.Modeltests):
"blocks": [],
"close_status": None,
"closed_at": None,
+ "closed_by": None,
"comments": [],
"content": "This issue needs attention",
"custom_fields": [],
@@ -2577,6 +2586,7 @@ class PagurePrivateRepotest(tests.Modeltests):
"blocks": [],
"close_status": None,
"closed_at": None,
+ "closed_by": None,
"comments": [],
"content": "We should work on this",
"custom_fields": [],
@@ -2600,6 +2610,7 @@ class PagurePrivateRepotest(tests.Modeltests):
"blocks": [],
"close_status": None,
"closed_at": None,
+ "closed_by": None,
"comments": [],
"content": "This issue needs attention",
"custom_fields": [],
@@ -2672,6 +2683,7 @@ class PagurePrivateRepotest(tests.Modeltests):
"blocks": [],
"close_status": None,
"closed_at": None,
+ "closed_by": None,
"comments": [],
"content": "We should work on this",
"custom_fields": [],
@@ -2695,6 +2707,7 @@ class PagurePrivateRepotest(tests.Modeltests):
"blocks": [],
"close_status": None,
"closed_at": None,
+ "closed_by": None,
"comments": [],
"content": "This issue needs attention",
"custom_fields": [],
@@ -2835,6 +2848,7 @@ class PagurePrivateRepotest(tests.Modeltests):
"blocks": [],
"close_status": None,
"closed_at": None,
+ "closed_by": None,
"comments": [],
"content": "We should work on this",
"custom_fields": [],
@@ -2858,6 +2872,7 @@ class PagurePrivateRepotest(tests.Modeltests):
"blocks": [],
"close_status": None,
"closed_at": None,
+ "closed_by": None,
"comments": [],
"content": "This issue needs attention",
"custom_fields": [],
@@ -2948,6 +2963,7 @@ class PagurePrivateRepotest(tests.Modeltests):
"blocks": [],
"close_status": None,
"closed_at": None,
+ "closed_by": None,
"comments": [],
"content": "This issue needs attention",
"custom_fields": [],
@@ -2996,6 +3012,7 @@ class PagurePrivateRepotest(tests.Modeltests):
"blocks": [],
"close_status": None,
"closed_at": None,
+ "closed_by": None,
"comments": [],
"content": "This issue needs attention",
"custom_fields": [],
diff --git a/tests/test_pagure_flask_api_user.py b/tests/test_pagure_flask_api_user.py
index 86a75e1..8a898a8 100644
--- a/tests/test_pagure_flask_api_user.py
+++ b/tests/test_pagure_flask_api_user.py
@@ -1225,6 +1225,7 @@ class PagureFlaskApiUsertestissues(tests.Modeltests):
"blocks": [],
"close_status": None,
"closed_at": None,
+ "closed_by": None,
"comments": [],
"content": "We should work on this",
"custom_fields": [],
diff --git a/tests/test_pagure_flask_ui_issues.py b/tests/test_pagure_flask_ui_issues.py
index ae2fa04..8a60fcf 100644
--- a/tests/test_pagure_flask_ui_issues.py
+++ b/tests/test_pagure_flask_ui_issues.py
@@ -3960,7 +3960,9 @@ class PagureFlaskIssuestests(tests.Modeltests):
self.assertEqual(msg.title, 'Test issue')
user = tests.FakeUser()
- user.username = 'pingou'
+ user.username = 'foo'
+ msg = pagure.lib.query.add_user_to_project(self.session, repo, "foo", "pingou")
+ self.session.commit()
with tests.user_set(self.app.application, user):
output = self.app.get('/test/issue/1')
self.assertEqual(output.status_code, 200)
@@ -4002,6 +4004,13 @@ class PagureFlaskIssuestests(tests.Modeltests):
self.assertTrue(
''
in output_text)
+ self.assertIn(
+ ' Closed: Fixed\n'
+ ' just now\n'
+ ' \n'
+ ' by\n'
+ ' foo.\n',
+ output_text)
def _set_up_for_reaction_test(self, private=False):
tests.create_projects(self.session)
diff --git a/tests/test_pagure_lib_git.py b/tests/test_pagure_lib_git.py
index 69fe990..3299ee0 100644
--- a/tests/test_pagure_lib_git.py
+++ b/tests/test_pagure_lib_git.py
@@ -1437,7 +1437,7 @@ new file mode 100644
index 0000000..60f7480
--- /dev/null
+++ b/456
-@@ -0,0 +1,28 @@
+@@ -0,0 +1,29 @@
+{
+ "assignee": null,
+ "blocks": [],
@@ -1493,6 +1493,8 @@ index 0000000..60f7480
elif 'closed_at' in row:
t = row.split(': ')[0]
row = '%s: null,' % t
+ elif 'closed_by' in row:
+ continue
elif row.startswith('index 00'):
row = 'index 0000000..60f7480'
elif row.startswith('+++ b/'):
@@ -1535,8 +1537,7 @@ diff --git a/123 b/456
index 458821a..77674a8
--- a/123
+++ b/456
-@@ -3,13 +3,32 @@
- "blocks": [],
+@@ -4,13 +4,32 @@
"close_status": null,
"closed_at": null,
- "comments": [],
@@ -1593,6 +1594,8 @@ index 458821a..77674a8
elif 'closed_at' in row:
t = row.split(': ')[0]
row = '%s: null,' % t
+ elif 'closed_by' in row:
+ continue
elif row.startswith('index'):
row = 'index 458821a..77674a8'
elif row.startswith('--- a/'):