bloodhound-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Gary Martin <gary.mar...@wandisco.com>
Subject Re: svn commit: r1614483 - in /bloodhound/branches/bep_0011_batch_create_tickets/bloodhound_theme/bhtheme: htdocs/js/batchcreate.js theme.py
Date Fri, 01 Aug 2014 17:45:33 GMT
OK, that looks useful but I have some observations/requests

  * If as a logging in user I create a table with N tickets and add 
information to M of the rows (where N > M) and then hit the save button, 
I get N - M tickets created. It would seem better to ignore the creation 
of the 'empty' rows. If we define the empty rows as those that do not 
have a summary, that might be enough.

  * With the ability to remove rows you also need to be careful with any 
rows that have data that you are effectively removing in case it was not 
intended. Perhaps it would be better to give controls that allow you 
remove specific rows instead?

  * Given the ability to add rows, it might also be sensible provide a 
means to clone details from the preceding row as an alternative row 
addition. Here I am not thinking of the summary and description which 
should probably be expected to be more variable than other fields but if 
there were milestone and component fields, these are things that might 
stay the same along with the product. The priority might not be so 
important to clone but I would say it wouldn't hurt too much. There are 
of course other possible ways to go that would help reduce a user's need 
to edit.

  * The status of tickets is somewhat tied to workflow and so really the 
state should only be new. There may be a case for allowing it to be 
different but workflow is complicated so I would probably leave that 
until later. There is certainly a temptation to be able to provide an 
initial assignment of the ticket to a user which might suggest that 
assigned is also a valid starting state but workflow really is a bit of 
a minefield where you may not be able to make any certain predictions of 
what is definitely required at the moment.

  * If you decide to drop status you could consider replacing it with 
component and/or milestone. In the long run it might well be useful to 
have a way of setting the fields that can be set but I would suspect 
there is no time for that.

Anyway, pick things that you think you can improve on, add testing if 
you can and tidy up as best you can. The firm pencils down deadline 
looks like it is 18th August and I would hope that you really are only 
writing tests and improving documentation in the week before that.

Cheers,
     Gary

On 29/07/14 21:51, Dammina Sahabandu wrote:
> Earlier the batch create functionality allowed the users to batch
> create certain number of tickets. But they needed to decide the number
> that they are going to create initially. After that they can't change
> the number. If they need they have to do the process from the
> beginning. The new functionality provides the users the freedom to
> change the number of tickets at any moment.
>
> On Wed, Jul 30, 2014 at 2:19 AM,  <dammina@apache.org> wrote:
>> Author: dammina
>> Date: Tue Jul 29 20:49:38 2014
>> New Revision: 1614483
>>
>> URL: http://svn.apache.org/r1614483
>> Log:
>> The new functionality provides the users the freedom to change the number of tickets
at any moment.
>>
>> Modified:
>>      bloodhound/branches/bep_0011_batch_create_tickets/bloodhound_theme/bhtheme/htdocs/js/batchcreate.js
>>      bloodhound/branches/bep_0011_batch_create_tickets/bloodhound_theme/bhtheme/theme.py
>>
>> Modified: bloodhound/branches/bep_0011_batch_create_tickets/bloodhound_theme/bhtheme/htdocs/js/batchcreate.js
>> URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0011_batch_create_tickets/bloodhound_theme/bhtheme/htdocs/js/batchcreate.js?rev=1614483&r1=1614482&r2=1614483&view=diff
>> ==============================================================================
>> --- bloodhound/branches/bep_0011_batch_create_tickets/bloodhound_theme/bhtheme/htdocs/js/batchcreate.js
(original)
>> +++ bloodhound/branches/bep_0011_batch_create_tickets/bloodhound_theme/bhtheme/htdocs/js/batchcreate.js
Tue Jul 29 20:49:38 2014
>> @@ -175,7 +175,26 @@ function emptyTable(products,href,token)
>>          }
>>          table.appendChild(tbody);
>>          form.appendChild(table);
>> +
>> +       remove_row_button = document.createElement("button");
>> +       remove_row_button.setAttribute("class","btn pull-right");
>> +       remove_row_button.setAttribute("type","button");
>> +       remove_row_button.setAttribute("onclick","remove_row_btn_action()");
>> +       remove_row_button.setAttribute("id","bct-rmv-empty-row");
>> +       remove_row_button.appendChild(document.createTextNode("-"));
>> +       form.appendChild(remove_row_button);
>>
>> +       add_row_button = document.createElement("button");
>> +       add_row_button.setAttribute("class","btn pull-right");
>> +       add_row_button.setAttribute("type","button");
>> +       add_row_button.addEventListener("click", function(event) {
>> +               add_row_btn_action(products);
>> +               event.preventDefault();
>> +       });
>> +       add_row_button.setAttribute("id","bct-add-empty-row");
>> +       add_row_button.appendChild(document.createTextNode("+"));
>> +       form.appendChild(add_row_button);
>> +
>>       submit_button = document.createElement("button");
>>          submit_button.setAttribute("class","btn pull-right");
>>          submit_button.setAttribute("type","button");
>> @@ -282,3 +301,144 @@ function submit_btn_action() {
>>                          contentDiv.appendChild(div);
>>           });
>>   }
>> +
>> +function add_row_btn_action(products){
>> +       // alert("1");
>> +
>> +       var headers = {"summary":"Summary","description":"Description","product":"Product","status":"Status","priority":"Priority"}
>> +       var statuses = ["accepted", "assigned", "closed", "new", "reopened"];
>> +       var priorities = ["blocker", "critical", "major", "minor", "trivial"];
>> +       var types = ["defect", "enhancement", "task"];
>> +
>> +    tr_rows = document.createElement("tr");
>> +
>> +    for (header in headers){
>> +               if (header == "summary"){
>> +                               td_row = document.createElement("td");
>> +                               input_summary = document.createElement("input");
>> +                               input_summary.setAttribute("type","text");
>> +                               input_summary.setAttribute("id","field-summary"+i);
>> +                               input_summary.setAttribute("class","input-block-level");
>> +                               input_summary.setAttribute("name","field_summary"+i);
>> +                               td_row.appendChild(input_summary);
>> +                               tr_rows.appendChild(td_row);
>> +                       }
>> +                       else if (header == "description") {
>> +                               td_row = document.createElement("td");
>> +                               input_description = document.createElement("textarea");
>> +                               input_description.setAttribute("id","field-description"+i);
>> +                               input_description.setAttribute("class","input-block-level");
>> +                               input_description.setAttribute("name","field_description"+i);
>> +                               input_description.setAttribute("rows","2");
>> +                               input_description.setAttribute("cols","28");
>> +                               td_row.appendChild(input_description);
>> +                               tr_rows.appendChild(td_row);
>> +                       }
>> +                       else if (header == "status") {
>> +                               td_row = document.createElement("td");
>> +                               input_status = document.createElement("select");
>> +                               input_status.setAttribute("id","field-status"+i);
>> +                               input_status.setAttribute("class","input-block-level");
>> +                               input_status.setAttribute("name","field_status"+i);
>> +                               for (status in statuses){
>> +                                       option = document.createElement("option");
>> +                                       option.setAttribute("value",statuses[status]);
>> +                                       option.appendChild(document.createTextNode(statuses[status]));
>> +                                       input_status.appendChild(option);
>> +                               }
>> +                               td_row.appendChild(input_status);
>> +                               tr_rows.appendChild(td_row);
>> +                       }
>> +                       else if (header == "priority") {
>> +                               td_row = document.createElement("td");
>> +                               input_priority = document.createElement("select");
>> +                               input_priority.setAttribute("id","field-priority"+i);
>> +                               input_priority.setAttribute("class","input-block-level");
>> +                               input_priority.setAttribute("name","field_priority"+i);
>> +                               for (priority in priorities){
>> +                                       option = document.createElement("option");
>> +                                       option.setAttribute("value",priorities[priority]);
>> +                                       option.appendChild(document.createTextNode(priorities[priority]));
>> +                                       input_priority.appendChild(option);
>> +                               }
>> +                               td_row.appendChild(input_priority);
>> +                               tr_rows.appendChild(td_row);
>> +                       }
>> +                       /*else if (header == "type") {
>> +                               td_row = document.createElement("td");
>> +                               input_type = document.createElement("select");
>> +                               input_type.setAttribute("id","field-type"+i);
>> +                               input_type.setAttribute("class","input-block-level");
>> +                               input_type.setAttribute("name","field_type"+i);
>> +                               for (type in types){
>> +                                       option = document.createElement("option");
>> +                                       option.setAttribute("value",types[type]);
>> +                                       option.appendChild(document.createTextNode(types[type]));
>> +                                       input_type.appendChild(option);
>> +                               }
>> +                               td_row.appendChild(input_type);
>> +                               tr_rows.appendChild(td_row);
>> +                       }*/
>> +                       else if (header == "product") {
>> +                               td_row = document.createElement("td");
>> +                               field_product = document.createElement("select");
>> +                               field_product.setAttribute("id","field-product"+i);
>> +                               field_product.setAttribute("class","input-block-level");
>> +                               field_product.setAttribute("name","field_product"+i);
>> +                               for (product in products){
>> +                                       option = document.createElement("option");
>> +                                       option.setAttribute("value",(products[product])[0]);
>> +                                       option.appendChild(document.createTextNode((products[product])[1]));
>> +                                       field_product.appendChild(option);
>> +                               }
>> +                               td_row.appendChild(field_product);
>> +                               tr_rows.appendChild(td_row);
>> +                       }
>> +                       /*else if (header == "owner"){
>> +                               td_row = document.createElement("td");
>> +                               input_owner = document.createElement("input");
>> +                               input_owner.setAttribute("type","text");
>> +                               input_owner.setAttribute("id","field-owner"+i);
>> +                               input_owner.setAttribute("class","input-block-level");
>> +                               input_owner.setAttribute("name","field_owner"+i);
>> +                               td_row.appendChild(input_owner);
>> +                               tr_rows.appendChild(td_row);
>> +                       }*/
>> +                       /*else if (header == "cc"){
>> +                               td_row = document.createElement("td");
>> +                               input_cc = document.createElement("input");
>> +                               input_cc.setAttribute("type","text");
>> +                               input_cc.setAttribute("id","field-cc"+i);
>> +                               input_cc.setAttribute("class","input-block-level");
>> +                               input_cc.setAttribute("name","field_cc"+i);
>> +                               td_row.appendChild(input_cc);
>> +                               tr_rows.appendChild(td_row);
>> +                       }*/
>> +                       /*else if (header == "milestone"){
>> +                               td_row = document.createElement("td");
>> +                               input_milestone = document.createElement("input");
>> +                               input_milestone.setAttribute("type","text");
>> +                               input_milestone.setAttribute("id","field-milestone"+i);
>> +                               input_milestone.setAttribute("class","input-block-level");
>> +                               input_milestone.setAttribute("name","field_milestone"+i);
>> +                               td_row.appendChild(input_milestone);
>> +                               tr_rows.appendChild(td_row);
>> +                       }*/
>> +                       /*else if (header == "keywords"){
>> +                               td_row = document.createElement("td");
>> +                               input_keywords = document.createElement("input");
>> +                               input_keywords.setAttribute("type","text");
>> +                               input_keywords.setAttribute("id","field-keywords"+i);
>> +                               input_keywords.setAttribute("class","input-block-level");
>> +                               input_keywords.setAttribute("name","field_keywords"+i);
>> +                               td_row.appendChild(input_keywords);
>> +                               tr_rows.appendChild(td_row);
>> +                       }*/
>> +
>> +       }
>> +       document.getElementById("empty-table").childNodes[1].childNodes[1].childNodes[1].appendChild(tr_rows);
>> +}
>> +
>> +function remove_row_btn_action(){
>> +       document.getElementById("empty-table").childNodes[1].childNodes[1].childNodes[1].lastChild.remove();
>> +}
>>
>> Modified: bloodhound/branches/bep_0011_batch_create_tickets/bloodhound_theme/bhtheme/theme.py
>> URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0011_batch_create_tickets/bloodhound_theme/bhtheme/theme.py?rev=1614483&r1=1614482&r2=1614483&view=diff
>> ==============================================================================
>> --- bloodhound/branches/bep_0011_batch_create_tickets/bloodhound_theme/bhtheme/theme.py
(original)
>> +++ bloodhound/branches/bep_0011_batch_create_tickets/bloodhound_theme/bhtheme/theme.py
Tue Jul 29 20:49:38 2014
>> @@ -36,14 +36,8 @@ from trac.util.compat import set
>>   from trac.util.presentation import to_json
>>   from trac.versioncontrol.web_ui.browser import BrowserModule
>>   from trac.web.api import IRequestFilter, ITemplateStreamFilter
>> -from trac.web.chrome import (
>> -    add_stylesheet,
>> -    add_warning,
>> -    INavigationContributor,
>> -    ITemplateProvider,
>> -    prevnext_nav,
>> -    Chrome,
>> -    add_script)
>> +from trac.web.chrome import (add_stylesheet, add_warning, INavigationContributor,
>> +                             ITemplateProvider, prevnext_nav, Chrome, add_script)
>>   from trac.web.main import IRequestHandler
>>   from trac.wiki.admin import WikiAdmin
>>   from trac.wiki.formatter import format_to_html
>> @@ -63,9 +57,7 @@ try:
>>   except ImportError:
>>       ProductTicketModule = None
>>
>> -
>>   class BloodhoundTheme(ThemeBase):
>> -
>>       """Look and feel of Bloodhound issue tracker.
>>       """
>>       template = htdocs = css = screenshot = disable_trac_css = True
>> @@ -168,34 +160,24 @@ class BloodhoundTheme(ThemeBase):
>>       )
>>
>>       labels_application_short = Option('labels', 'application_short',
>> -                                      'Bloodhound', """A short version of application
name most commonly
>> +        'Bloodhound', """A short version of application name most commonly
>>           displayed in text, titles and labels""", doc_domain='bhtheme')
>>
>>       labels_application_full = Option('labels', 'application_full',
>> -                                     'Apache Bloodhound', """This is full name with
trade mark and
>> +        'Apache Bloodhound', """This is full name with trade mark and
>>           everything, it is currently used in footers and about page only""",
>>                                        doc_domain='bhtheme')
>>
>> -    labels_footer_left_prefix = Option(
>> -        'labels',
>> -        'footer_left_prefix',
>> -        '',
>> +    labels_footer_left_prefix = Option('labels', 'footer_left_prefix', '',
>>           """Text to display before full application name in footers""",
>> -        doc_domain='bhtheme')
>> +                                       doc_domain='bhtheme')
>>
>> -    labels_footer_left_postfix = Option(
>> -        'labels',
>> -        'footer_left_postfix',
>> -        '',
>> +    labels_footer_left_postfix = Option('labels', 'footer_left_postfix', '',
>>           """Text to display after full application name in footers""",
>> -        doc_domain='bhtheme')
>> +                                        doc_domain='bhtheme')
>>
>> -    labels_footer_right = Option(
>> -        'labels',
>> -        'footer_right',
>> -        '',
>> -        """Text to use as the right aligned footer""",
>> -        doc_domain='bhtheme')
>> +    labels_footer_right = Option('labels', 'footer_right', '',
>> +        """Text to use as the right aligned footer""", doc_domain='bhtheme')
>>
>>       _wiki_pages = None
>>       Chrome.default_html_doctype = DocType.HTML5
>> @@ -365,13 +347,7 @@ class BloodhoundTheme(ThemeBase):
>>
>>       # Request modifiers
>>
>> -    def _modify_search_data(
>> -            self,
>> -            req,
>> -            template,
>> -            data,
>> -            content_type,
>> -            is_active):
>> +    def _modify_search_data(self, req, template, data, content_type, is_active):
>>           """Insert breadcumbs and context navigation items in search web UI
>>           """
>>           if is_active:
>> @@ -405,7 +381,7 @@ class BloodhoundTheme(ThemeBase):
>>           self._modify_resource_breadcrumb(req, template, data, content_type,
>>                                            is_active)
>>
>> -        # add a creation event to the changelog if the ticket exists
>> +        #add a creation event to the changelog if the ticket exists
>>           ticket = data['ticket']
>>           if ticket.exists:
>>               data['changes'] = [{'comment': '',
>> @@ -417,7 +393,7 @@ class BloodhoundTheme(ThemeBase):
>>                                   'date': ticket['time'],
>>                                   },
>>                                  ] + data['changes']
>> -        # and set default order
>> +        #and set default order
>>           if not req.session.get('ticket_comments_order'):
>>               req.session['ticket_comments_order'] = 'newest'
>>
>> @@ -440,13 +416,7 @@ class BloodhoundTheme(ThemeBase):
>>               if mname:
>>                   data['milestone'] = Milestone(self.env, mname)
>>
>> -    def _modify_admin_breadcrumb(
>> -            self,
>> -            req,
>> -            template,
>> -            data,
>> -            content_type,
>> -            is_active):
>> +    def _modify_admin_breadcrumb(self, req, template, data, content_type, is_active):
>>           # override 'normal' product list with the admin one
>>
>>           def admin_url(prefix):
>> @@ -495,7 +465,7 @@ class BloodhoundTheme(ThemeBase):
>>                   SELECT product, value FROM bloodhound_productconfig
>>                   WHERE product IN (%s) AND section='project' AND
>>                   option='icon'""" % ', '.join(["%s"] * len(products)),
>> -                               tuple(p.prefix for p in products))
>> +                tuple(p.prefix for p in products))
>>           icons = dict(icons)
>>           data['thumbsize'] = 64
>>           # FIXME: Gray icon for missing products
>> @@ -510,29 +480,29 @@ class BloodhoundTheme(ThemeBase):
>>                                                      product_ctx(product),
>>                                                      product.description),
>>                           links={'extras': (([{'href': req.href.products(
>> -                            product.prefix, action='edit'),
>> -                            'title': _('Edit product %(prefix)s',
>> -                                       prefix=product.prefix),
>> -                            'icon': tag.i(class_='icon-edit'),
>> -                            'label': _('Edit')}, ]
>> -                            if 'PRODUCT_MODIFY' in req.perm
>> -                            else []) +
>> -                            [{'href': product.href(),
>> -                              'title': _('Home page'),
>> -                              'icon': tag.i(class_='icon-home'),
>> -                              'label': _('Home')},
>> -                             {'href': product.href.dashboard(),
>> -                              'title': _('Tickets dashboard'),
>> -                              'icon': tag.i(class_='icon-tasks'),
>> -                              'label': _('Tickets')},
>> -                             {'href': product.href.wiki(),
>> -                              'title': _('Wiki'),
>> -                              'icon': tag.i(class_='icon-book'),
>> -                              'label': _('Wiki')}]),
>> -                'main': {'href': product.href(),
>> -                         'title': None,
>> -                         'icon': tag.i(class_='icon-chevron-right'),
>> -                         'label': _('Browse')}})
>> +                                                product.prefix, action='edit'),
>> +                                             'title': _('Edit product %(prefix)s',
>> +                                                        prefix=product.prefix),
>> +                                             'icon': tag.i(class_='icon-edit'),
>> +                                             'label': _('Edit')},]
>> +                                           if 'PRODUCT_MODIFY' in req.perm
>> +                                           else []) +
>> +                                          [{'href': product.href(),
>> +                                            'title': _('Home page'),
>> +                                            'icon': tag.i(class_='icon-home'),
>> +                                            'label': _('Home')},
>> +                                           {'href': product.href.dashboard(),
>> +                                            'title': _('Tickets dashboard'),
>> +                                            'icon': tag.i(class_='icon-tasks'),
>> +                                            'label': _('Tickets')},
>> +                                           {'href': product.href.wiki(),
>> +                                            'title': _('Wiki'),
>> +                                            'icon': tag.i(class_='icon-book'),
>> +                                            'label': _('Wiki')}]),
>> +                               'main': {'href': product.href(),
>> +                                        'title': None,
>> +                                        'icon': tag.i(class_='icon-chevron-right'),
>> +                                        'label': _('Browse')}})
>>
>>           data['products'] = [product_media_data(icons, product)
>>                               for product in products]
>> @@ -550,16 +520,29 @@ class BloodhoundTheme(ThemeBase):
>>                          tag.a(_('Source'),
>>                                href=req.href.wiki('TracRepositoryAdmin')))
>>
>> +class QCTSelectFieldUpdate(Component):
>> +    implements(IRequestHandler)
>> +
>> +    def match_request(self, req):
>> +        return req.path_info == '/update-menus'
>> +
>> +    def process_request(self, req):
>> +        product = req.args.get('product')
>> +        fields_to_update = req.args.get('fields_to_update[]');
>> +        env = ProductEnvironment(self.env.parent, req.args.get('product'))
>> +        ticket_fields = TicketSystem(env).get_ticket_fields()
>> +        data = dict([f['name'], f['options']]  for f in ticket_fields
>> +            if f['type'] == 'select' and f['name'] in fields_to_update)
>> +        req.send(to_json(data), 'application/json')
>> +
>>
>>   class QuickCreateTicketDialog(Component):
>>       implements(IRequestFilter, IRequestHandler)
>>
>> -    qct_fields = ListOption(
>> -        'ticket',
>> -        'quick_create_fields',
>> -        'product, version, type',
>> +    qct_fields = ListOption('ticket', 'quick_create_fields',
>> +                            'product, version, type',
>>           doc="""Multiple selection fields displayed in create ticket menu""",
>> -        doc_domain='bhtheme')
>> +                            doc_domain='bhtheme')
>>
>>       def __init__(self, *args, **kwargs):
>>           import pkg_resources
>> @@ -610,8 +593,8 @@ class QuickCreateTicketDialog(Component)
>>                            new_ticket_url=dum_req.href.products(p, 'newticket'),
>>                            description=ProductEnvironment.lookup_env(self.env, p)
>>                                                          .product.name
>> -                         )
>> -                    for p in product_field['options']
>> +                    )
>> +                for p in product_field['options']
>>                       if req.perm.has_permission('TICKET_CREATE',
>>                                                  Neighborhood('product', p)
>>                                                  .child(None, None))]
>> @@ -628,7 +611,7 @@ class QuickCreateTicketDialog(Component)
>>                   'fields': [all_fields[k] for k in self.qct_fields
>>                              if k in all_fields],
>>                   'hidden_fields': [all_fields[k] for k in all_fields.keys()
>> -                                  if k not in self.qct_fields]}
>> +                                  if k not in self.qct_fields] }
>>           return template, data, content_type
>>
>>       # IRequestHandler methods
>> @@ -652,7 +635,7 @@ class QuickCreateTicketDialog(Component)
>>               attrs = dict([k[6:], v] for k, v in req.args.iteritems()
>>                            if k.startswith('field_'))
>>               product, tid = self.create(req, summary, desc, attrs, True)
>> -        except Exception as exc:
>> +        except Exception, exc:
>>               self.log.exception("BH: Quick create ticket failed %s" % (exc,))
>>               req.send(str(exc), 'plain/text', 500)
>>           else:
>> @@ -700,7 +683,7 @@ class QuickCreateTicketDialog(Component)
>>               try:
>>                   tn = TicketNotifyEmail(env)
>>                   tn.notify(t, newticket=True)
>> -            except Exception as e:
>> +            except Exception, e:
>>                   self.log.exception("Failure sending notification on creation "
>>                                      "of ticket #%s: %s" % (t.id, e))
>>           return t['product'], t.id
>> @@ -708,7 +691,6 @@ class QuickCreateTicketDialog(Component)
>>   from pkg_resources import get_distribution
>>   application_version = get_distribution('BloodhoundTheme').version
>>
>> -
>>   class BatchCreateTicketDialog(Component):
>>       implements(
>>           IRequestFilter,
>>
>>
>
>


Mime
View raw message