Write only one comment per action

* For every 'select' or 'unselect' a single comment is written in
   every affected project
 * Previous automatic comments are deleted (as a first approach)
 * For more info, see https://progress.opensuse.org/issues/2564
This commit is contained in:
Ancor Gonzalez Sosa 2014-06-04 16:54:21 +02:00
parent 690c76d6fb
commit 507e50d633
6 changed files with 127 additions and 44 deletions

View File

@ -2,10 +2,8 @@ from collections import defaultdict
from xml.etree import cElementTree as ET
from osc import oscerr
from osc.core import get_request
from osc.core import http_GET
from osclib.comments import CommentAPI
from osclib.request_finder import RequestFinder
# from osclib.freeze_command import FreezeCommand
@ -19,8 +17,7 @@ class SelectCommand(object):
def __init__(self, api):
self.api = api
self.comment = CommentAPI(self.api.apiurl)
self.pending_comments = defaultdict(lambda: defaultdict(list))
self.affected_projects = set()
def _package(self, request):
"""
@ -67,11 +64,6 @@ class SelectCommand(object):
# Normal 'select' command
print('Adding request "{}" to project "{}"'.format(request, self.target_project))
# Add a new pending comment.
user = get_request(self.api.apiurl, str(request)).get_creator()
package = self._package(request)
self.pending_comments[user][SELECT].append((package, request))
return self.api.rq_to_prj(request, self.target_project)
elif 'staging' in request_project and (move or supersede):
# 'select' command becomes a 'move'
@ -91,10 +83,8 @@ class SelectCommand(object):
print('Moving "{}" from "{}" to "{}"'.format(request, fprj, self.target_project))
# Add a new pending comment.
user = get_request(self.api.apiurl, str(request)).get_creator()
package = self._package(request)
self.pending_comments[user][MOVE].append((fprj, package, request))
# Store the source project, we also need to write a comment there
self.affected_projects.add(fprj)
return self.api.move_between_project(fprj, request, self.target_project)
elif 'staging' in request_project and not move:
@ -128,26 +118,10 @@ class SelectCommand(object):
if not self.select_request(request, request_project, move, from_):
return False
# Publish pending comments grouped by user and operation.
for user in self.pending_comments:
lines = []
if SELECT in self.pending_comments[user]:
lines.append('Packages tracked now in %s:\n' % self.target_project)
for package, request in self.pending_comments[user][SELECT]:
lines.append('* %s [%s](%s)' % (package, request, '/request/show/' + str(request)))
if MOVE in self.pending_comments[user]:
if lines:
lines.append('\n')
lines.append('Packages moved to %s:\n' % self.target_project)
for from_project, package, request in self.pending_comments[user][MOVE]:
lines.append('* %s [%s](%s) from %s' % (package, request, '/request/show/' + str(request), from_project))
lines.append('\nCC [at]%s' % user)
msg = '\n'.join(lines)
self.comment.add_comment(project_name=self.target_project, comment=msg)
# Notify everybody about the changes
self.api.update_status_comments(target_project, 'select')
for fprj in self.affected_projects:
self.api.update_status_comments(fprj, 'select')
# now make sure we enable the prj if the prj contains any ringed package
self.api.build_switch_staging_project(target_project)

View File

@ -24,6 +24,7 @@ from osc.core import http_GET
from osc.core import http_POST
from osc.core import http_PUT
from osclib.comments import CommentAPI
class StagingAPI(object):
"""
@ -387,13 +388,15 @@ class StagingAPI(object):
"""
data = self.get_prj_pseudometa(project)
author = get_request(self.apiurl, str(request_id)).get_creator()
append = True
for request in data['requests']:
if request['package'] == package:
request['author'] = author
request['id'] = request_id
append = False
if append:
data['requests'].append({'id': request_id, 'package': package})
data['requests'].append({'id': request_id, 'package': package, 'author': author })
self.set_prj_pseudometa(project, data)
def get_request_id_for_package(self, project, package):
@ -1054,3 +1057,45 @@ class StagingAPI(object):
except urllib2.HTTPError:
return False
return True
def update_status_comments(self, project, command):
"""
Refresh the status comments, used for notification purposes, based on
the current list of requests. To ensure that all involved users
(and nobody else) get notified, old status comments are deleted and
a new one is created.
:param project: project name
:param command: name of the command to include in the message
"""
# TODO: we need to discuss the best way to keep track of status
# comments. Right now they are marked with '[osc staging' at the
# beginning. Maybe a cleaner approach would be to store something
# like 'last_status_comment_id' in the pseudometa. But the current
# OBS API for adding comments doesn't return the id of the created
# comment.
comment_api = CommentAPI(self.apiurl)
comments = comment_api.get_comments(project_name=project)
for comment in comments.values():
# TODO: update the comment removing the user mentions instead of
# deleting the whole comment. But there is currently not call in
# OBS API to update a comment
if comment['comment'].startswith('[osc staging'):
comment_api.delete(comment['id'])
meta = self.get_prj_pseudometa(project)
lines = ['[osc staging %s]\n' % command]
lines.append('The list of requests tracked in %s has changed:\n' % project)
for req in meta['requests']:
author = ''
if 'author' in req.keys():
# Proper metadata
author = req['author']
else:
# Old style metadata
author = get_request(self.apiurl, str(req['id'])).get_creator()
lines.append('Request req#%s for package %s submitted by [AT]%s' % (req['id'], req['package'], author))
msg = '\n'.join(lines)
comment_api.add_comment(project_name=project, comment=msg)

View File

@ -2,7 +2,6 @@ from osc import oscerr
from osc.core import get_request
from osc.core import http_GET
from osclib.comments import CommentAPI
from osclib.request_finder import RequestFinder
@ -10,7 +9,6 @@ class UnselectCommand(object):
def __init__(self, api):
self.api = api
self.comment = CommentAPI(self.api.apiurl)
def perform(self, packages):
"""
@ -18,15 +16,16 @@ class UnselectCommand(object):
:param packages: packages/requests to delete from staging projects
"""
affected_projects = set()
for request, request_project in RequestFinder.find_staged_sr(packages,
self.api).items():
staging_project = request_project['staging']
affected_projects.add(staging_project)
msg = 'Unselecting "{}" from "{}"'.format(request, staging_project)
print(msg)
self.api.rm_from_prj(staging_project, request_id=request)
self.api.add_review(request, by_group='factory-staging', msg='Please recheck')
# Write a comment in the project.
# user = get_request(self.api.apiurl, str(request)).get_creator()
# self.comment.add_comment(project_name=staging_project,
# comment='[at]%s: %s' % (user, msg))
# Notify everybody about the changes
for prj in affected_projects:
self.api.update_status_comments(prj, 'unselect')

View File

@ -227,7 +227,7 @@ class TestApiCalls(unittest.TestCase):
self.assertEqual('new', self.obs.requests[rq]['review'])
self.assertEqual('review', self.obs.requests[rq]['request'])
self.assertEqual(self.api.get_prj_pseudometa('openSUSE:Factory:Staging:A'),
{'requests': [{'id': 123, 'package': 'gcc'}]})
{'requests': [{'id': 123, 'package': 'gcc', 'author': 'Admin'}]})
def test_generate_build_status_details(self):
"""Check whether generate_build_status_details works."""

View File

@ -210,6 +210,16 @@ class OBS(object):
},
}
self.comments = {
'openSUSE:Factory:Staging:A': [
{'who': 'Admin', 'when': '2014-06-01 17:56:28 UTC', 'id': '1',
'body': 'Just a comment'
}
],
'openSUSE:Factory:Staging:U': [],
'openSUSE:Factory:Staging:B': []
}
#
# /request/
#
@ -552,12 +562,40 @@ class OBS(object):
# /comments/
#
@GET(re.compile(r'/comments/project/.*'))
def get_comment(self, request, uri, headers):
"""Get comments for a project."""
prj = re.search(r'comments/project/([^/]*)', uri).group(1)
comments = self.comments[prj]
if not comments:
return (200, headers, '<comments project="%s"/>' % prj)
else:
ret_str = '<comments project="%s">' % prj
for c in comments:
ret_str += '<comment who="%s" when="%s" id="%s">' % (c['who'], c['when'], c['id'])
ret_str += c['body']
ret_str += '</comment>'
ret_str += '</comments>'
return (200, headers, ret_str)
@POST(re.compile(r'/comments/project/.*'))
def comment(self, request, uri, headers):
"""Manage comments into projects."""
def post_comment(self, request, uri, headers):
"""Add comment to a project."""
prj = re.search(r'comments/project/([^/]*)', uri).group(1)
comment = {'who': 'Admin', 'when': time.strftime('%Y-%m-%d %H:%M:%S UTC', time.gmtime()),
'id': str(sum(len(c) for c in self.comments.values()) + 1), 'body': request.body }
self.comments[prj].append(comment)
response = (200, headers, '<result>Ok</result>')
return response
@DELETE(re.compile(r'/comment/\d+'))
def delete_comment(self, request, uri, headers):
"""Delete a comments."""
comment_id = re.search(r'comment/(\d+)', uri).group(1)
for prj in self.comments:
self.comments[prj] = [c for c in self.comments[prj] if c['id'] != comment_id]
return (200, headers, '<result>Ok</result>')
#
# Static fixtures
#

View File

@ -14,7 +14,7 @@ from obs import OBS
from osc import oscerr
from osclib.select_command import SelectCommand
from oscs import StagingAPI
from osclib.comments import CommentAPI
class TestSelect(unittest.TestCase):
@ -30,6 +30,33 @@ class TestSelect(unittest.TestCase):
self.assertEqual(True, SelectCommand(self.api).perform('openSUSE:Factory:Staging:A', ['gcc']))
self.assertEqual(self.api.prj_frozen_enough('openSUSE:Factory:Staging:A'), True)
def test_select_comments(self):
c_api = CommentAPI(self.api.apiurl)
staging_a = 'openSUSE:Factory:Staging:A'
comments = c_api.get_comments(project_name=staging_a)
# First select
self.assertEqual(True, SelectCommand(self.api).perform(staging_a, ['gcc', 'wine']))
first_select_comments = c_api.get_comments(project_name=staging_a)
last_id = sorted(first_select_comments.keys())[-1]
first_select_comment = first_select_comments[last_id]
# Only one comment is added
self.assertEqual(len(first_select_comments), len(comments) + 1)
# With the right content
self.assertTrue('Request req#123 for package gcc submitted by [AT]Admin' in first_select_comment['comment'])
# Second select
self.assertEqual(True, SelectCommand(self.api).perform(staging_a, ['puppet']))
second_select_comments = c_api.get_comments(project_name=staging_a)
last_id = sorted(second_select_comments.keys())[-1]
second_select_comment = second_select_comments[last_id]
# The number of comments remains, but they are different
self.assertEqual(len(second_select_comments), len(first_select_comments))
self.assertNotEqual(second_select_comment['comment'], first_select_comment['comment'])
# The new comments contents both old and new information
self.assertTrue('Request req#123 for package gcc submitted by [AT]Admin' in second_select_comment['comment'])
self.assertTrue('Request req#321 for package puppet submitted by [AT]Admin' in second_select_comment['comment'])
def test_no_matches(self):
# search for requests
with self.assertRaises(oscerr.WrongArgs) as cm: