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,  <[email protected]> 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,





Reply via email to