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