Author: astaric
Date: Thu May 30 07:52:22 2013
New Revision: 1487776
URL: http://svn.apache.org/r1487776
Log:
Refs #537: Better handling of errors in bhrelations web ui.
Added:
bloodhound/trunk/bloodhound_relations/bhrelations/tests/web_ui.py
Modified:
bloodhound/trunk/bloodhound_relations/bhrelations/api.py
bloodhound/trunk/bloodhound_relations/bhrelations/templates/manage.html
bloodhound/trunk/bloodhound_relations/bhrelations/tests/__init__.py
bloodhound/trunk/bloodhound_relations/bhrelations/tests/api.py
bloodhound/trunk/bloodhound_relations/bhrelations/web_ui.py
Modified: bloodhound/trunk/bloodhound_relations/bhrelations/api.py
URL:
http://svn.apache.org/viewvc/bloodhound/trunk/bloodhound_relations/bhrelations/api.py?rev=1487776&r1=1487775&r2=1487776&view=diff
==============================================================================
--- bloodhound/trunk/bloodhound_relations/bhrelations/api.py (original)
+++ bloodhound/trunk/bloodhound_relations/bhrelations/api.py Thu May 30
07:52:22 2013
@@ -197,6 +197,8 @@ class RelationsSystem(Component):
self.env, source_resource_instance)
destination = ResourceIdSerializer.get_resource_id_from_instance(
self.env, destination_resource_instance)
+ if relation_type not in self.link_ends_map:
+ raise UnknownRelationType(relation_type)
if when is None:
when = datetime.now(utc)
relation = Relation(self.env)
@@ -579,3 +581,6 @@ def unique(seq):
seen = set()
return (x for x in seq if x not in seen and not seen.add(x))
+
+class UnknownRelationType(ValueError):
+ pass
Modified:
bloodhound/trunk/bloodhound_relations/bhrelations/templates/manage.html
URL:
http://svn.apache.org/viewvc/bloodhound/trunk/bloodhound_relations/bhrelations/templates/manage.html?rev=1487776&r1=1487775&r2=1487776&view=diff
==============================================================================
--- bloodhound/trunk/bloodhound_relations/bhrelations/templates/manage.html
(original)
+++ bloodhound/trunk/bloodhound_relations/bhrelations/templates/manage.html Thu
May 30 07:52:22 2013
@@ -37,6 +37,14 @@
<div class="row">
<div class="span8">
+ <py:if test='error'>
+ <div class="alert alert-error">
+ <span class="label label-important">Oops !</span>
+ Could not create relation.
+ $error
+ </div>
+ </py:if>
+
<form id="addrelation" class="well form-horizontal" method="post"
action="">
<fieldset>
<legend>Add relation:</legend>
@@ -45,7 +53,7 @@
<div class="control-group">
<label class="control-label" for="dest_tid">Related ticket:</label>
<div class="controls">
- <input type="text" id="dest_tid" class="span4" name="dest_tid" />
+ <input type="text" id="dest_tid" class="span4" name="dest_tid"
value="$relation.destination" />
</div>
</div>
@@ -53,7 +61,7 @@
<label class="control-label" for="reltype">Relation type:</label>
<div class="controls">
<select class="span4" id="reltype" name="reltype">
- <option py:for="reltype,label in reltypes.iteritems()"
value="$reltype">$label</option>
+ <option py:for="reltype,label in reltypes.iteritems()"
value="$reltype" selected="${True if reltype == relation.type else
None}">$label</option>
</select>
</div>
</div>
@@ -61,7 +69,7 @@
<div class="control-group">
<label class="control-label" for="comment">Comment:</label>
<div class="controls">
- <textarea name="comment" rows="3" class="span4" />
+ <textarea name="comment" rows="3"
class="span4">${relation.comment}</textarea>
</div>
</div>
Modified: bloodhound/trunk/bloodhound_relations/bhrelations/tests/__init__.py
URL:
http://svn.apache.org/viewvc/bloodhound/trunk/bloodhound_relations/bhrelations/tests/__init__.py?rev=1487776&r1=1487775&r2=1487776&view=diff
==============================================================================
--- bloodhound/trunk/bloodhound_relations/bhrelations/tests/__init__.py
(original)
+++ bloodhound/trunk/bloodhound_relations/bhrelations/tests/__init__.py Thu May
30 07:52:22 2013
@@ -25,7 +25,8 @@
# import unittest
import unittest
-from bhrelations.tests import api, notification, search, validation
+from bhrelations.tests import api, notification, search, validation, web_ui
+
def suite():
test_suite = unittest.TestSuite()
@@ -33,7 +34,7 @@ def suite():
test_suite.addTest(notification.suite())
test_suite.addTest(search.suite())
test_suite.addTest(validation.suite())
-
+ test_suite.addTest(web_ui.suite())
return test_suite
if __name__ == '__main__':
Modified: bloodhound/trunk/bloodhound_relations/bhrelations/tests/api.py
URL:
http://svn.apache.org/viewvc/bloodhound/trunk/bloodhound_relations/bhrelations/tests/api.py?rev=1487776&r1=1487775&r2=1487776&view=diff
==============================================================================
--- bloodhound/trunk/bloodhound_relations/bhrelations/tests/api.py (original)
+++ bloodhound/trunk/bloodhound_relations/bhrelations/tests/api.py Thu May 30
07:52:22 2013
@@ -27,7 +27,7 @@ from bhrelations.validation import Valid
from multiproduct.env import ProductEnvironment
from tests.env import MultiproductTestCase
from trac.ticket.model import Ticket
-from trac.test import EnvironmentStub, Mock
+from trac.test import EnvironmentStub, Mock, MockPerm
from trac.core import TracError
from trac.util.datefmt import utc
@@ -79,6 +79,7 @@ class BaseApiApiTestCase(MultiproductTes
self.req = Mock(href=self.env.href, authname='anonymous', tz=utc,
args=dict(action='dummy'),
locale=locale_en, lc_time=locale_en)
+ self.req.perm = MockPerm()
self.relations_system = RelationsSystem(self.env)
self._upgrade_env()
Added: bloodhound/trunk/bloodhound_relations/bhrelations/tests/web_ui.py
URL:
http://svn.apache.org/viewvc/bloodhound/trunk/bloodhound_relations/bhrelations/tests/web_ui.py?rev=1487776&view=auto
==============================================================================
--- bloodhound/trunk/bloodhound_relations/bhrelations/tests/web_ui.py (added)
+++ bloodhound/trunk/bloodhound_relations/bhrelations/tests/web_ui.py Thu May
30 07:52:22 2013
@@ -0,0 +1,101 @@
+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+import unittest
+
+from bhrelations.web_ui import RelationManagementModule
+from bhrelations.tests.api import BaseApiApiTestCase
+
+
+class RelationManagementModuleTestCase(BaseApiApiTestCase):
+ def setUp(self):
+ BaseApiApiTestCase.setUp(self)
+ ticket_id = self._insert_ticket(self.env, "Foo")
+ args=dict(action='add', id=ticket_id, dest_tid='', reltype='',
comment='')
+ self.req.method = 'GET',
+ self.req.args['id'] = ticket_id
+
+ def test_can_process_empty_request(self):
+ data = self.process_request()
+
+ self.assertSequenceEqual(data['relations'], [])
+ self.assertEqual(len(data['reltypes']), 11)
+
+ def test_handles_missing_ticket_id(self):
+ self.req.method = "POST"
+ self.req.args['add'] = 'add'
+
+ data = self.process_request()
+
+ self.assertIn("Invalid ticket", data["error"])
+
+ def test_handles_invalid_ticket_id(self):
+ self.req.method = "POST"
+ self.req.args['add'] = 'add'
+ self.req.args['dest_tid'] = 'no such ticket'
+
+ data = self.process_request()
+
+ self.assertIn("Invalid ticket", data["error"])
+
+ def test_handles_missing_relation_type(self):
+ t2 = self._insert_ticket(self.env, "Bar")
+ self.req.method = "POST"
+ self.req.args['add'] = 'add'
+ self.req.args['dest_tid'] = str(t2)
+
+ data = self.process_request()
+
+ self.assertIn("Unknown relation type", data["error"])
+
+ def test_handles_invalid_relation_type(self):
+ t2 = self._insert_ticket(self.env, "Bar")
+ self.req.method = "POST"
+ self.req.args['add'] = 'add'
+ self.req.args['dest_tid'] = str(t2)
+ self.req.args['reltype'] = 'no such relation'
+
+ data = self.process_request()
+
+ self.assertIn("Unknown relation type", data["error"])
+
+ def test_shows_relation_that_was_just_added(self):
+ t2 = self._insert_ticket(self.env, "Bar")
+ self.req.method = "POST"
+ self.req.args['add'] = 'add'
+ self.req.args['dest_tid'] = str(t2)
+ self.req.args['reltype'] = 'dependson'
+
+ data = self.process_request()
+
+ self.assertEqual(len(data["relations"]), 1)
+
+ def process_request(self):
+ url, data, x = RelationManagementModule(self.env).process_request(
+ self.req)
+ return data
+
+
+def suite():
+ test_suite = unittest.TestSuite()
+ test_suite.addTest(unittest.makeSuite(RelationManagementModuleTestCase,
'test'))
+ return test_suite
+
+if __name__ == '__main__':
+ unittest.main()
Modified: bloodhound/trunk/bloodhound_relations/bhrelations/web_ui.py
URL:
http://svn.apache.org/viewvc/bloodhound/trunk/bloodhound_relations/bhrelations/web_ui.py?rev=1487776&r1=1487775&r2=1487776&view=diff
==============================================================================
--- bloodhound/trunk/bloodhound_relations/bhrelations/web_ui.py (original)
+++ bloodhound/trunk/bloodhound_relations/bhrelations/web_ui.py Thu May 30
07:52:22 2013
@@ -35,8 +35,9 @@ from trac.web import IRequestHandler
from trac.web.chrome import ITemplateProvider, add_warning
from bhrelations.api import RelationsSystem, ResourceIdSerializer, \
- TicketRelationsSpecifics
+ TicketRelationsSpecifics, UnknownRelationType
from bhrelations.model import Relation
+from bhrelations.validation import ValidationError
from multiproduct.model import Product
from multiproduct.env import ProductEnvironment
@@ -66,38 +67,51 @@ class RelationManagementModule(Component
req.perm.require('TICKET_VIEW')
relsys = RelationsSystem(self.env)
+ data = {
+ 'relation': {},
+ }
if req.method == 'POST':
# for modifying the relations TICKET_MODIFY is required for
# both the source and the destination tickets
req.perm.require('TICKET_MODIFY')
- if req.args.has_key('remove'):
+ if 'remove' in req.args:
rellist = req.args.get('sel')
if rellist:
if isinstance(rellist, basestring):
rellist = [rellist, ]
self.remove_relations(req, rellist)
- elif req.args.has_key('add'):
- dest_tid = req.args.get('dest_tid')
- reltype = req.args.get('reltype')
- comment = req.args.get('comment')
- if dest_tid and reltype:
- try:
- dest_ticket = self.find_ticket(dest_tid)
- except ValueError:
- raise TracError(_('Invalid ticket id.'))
-
- req.perm.require('TICKET_MODIFY', Resource(dest_ticket.id))
- relsys.add(ticket, dest_ticket, reltype, comment,
- req.authname)
+ elif 'add' in req.args:
+ relation = dict(
+ destination=req.args.get('dest_tid', ''),
+ type=req.args.get('reltype', ''),
+ comment=req.args.get('comment', ''),
+ )
+ try:
+ dest_ticket = self.find_ticket(relation['destination'])
+ req.perm.require('TICKET_MODIFY',
+ Resource(dest_ticket.id))
+ relsys.add(ticket, dest_ticket,
+ relation['type'],
+ relation['comment'],
+ req.authname)
+ except NoSuchTicketError:
+ data['error'] = _('Invalid ticket id.')
+ except UnknownRelationType:
+ data['error'] = _('Unknown relation type.')
+ except ValidationError as ex:
+ data['error'] = ex.message
+ if 'error' in data:
+ data['relation'] = relation
+
else:
raise TracError(_('Invalid operation.'))
- data = {
+ data.update({
'ticket': ticket,
'reltypes': relsys.get_relation_types(),
'relations': self.get_ticket_relations(ticket),
- }
+ })
return 'manage.html', data, None
# ITemplateProvider methods
@@ -149,7 +163,7 @@ class RelationManagementModule(Component
resource = ResourceIdSerializer.get_resource_by_id(tid)
ticket = trs._create_ticket_by_full_id(resource)
except:
- raise ValueError
+ raise NoSuchTicketError
return ticket
def remove_relations(self, req, rellist):
@@ -164,3 +178,6 @@ class RelationManagementModule(Component
add_warning(req,
_('Not enough permissions to remove relation "%s"' %
relid))
+
+class NoSuchTicketError(ValueError):
+ pass