changeset 760:306419d32527

Expand steps to allow in-progress steps. Some small additions to the BuildStep model, but most changes are in the ui to not assume steps are in their final state when they're shown. No change to actual processing. Refs #455, #573.
author wbell
date Sat, 24 Apr 2010 15:03:48 +0000
parents d11ef8024d7c
children b2272caf5ac4
files bitten/htdocs/bitten.css bitten/model.py bitten/templates/bitten_config.html bitten/web_ui.py
diffstat 4 files changed, 60 insertions(+), 35 deletions(-) [+]
line wrap: on
line diff
--- a/bitten/htdocs/bitten.css
+++ b/bitten/htdocs/bitten.css
@@ -93,9 +93,13 @@
   list-style-type: none; margin: .5em 0 0; padding: 0;
 }
 #content.build #builds ul.steps li.success,
+#content.build #builds ul.steps li.in-progress,
 #content.build #builds ul.steps li.failed {
   border: 1px solid; margin: 1px 0; padding: 0 2px 0 12px;
 }
+#content.build #builds ul.steps li.in-progress {
+  background: #dd9; border-color: #966; color: #993;
+}
 #content.build #builds ul.steps li.success {
   background: #9d9; border-color: #696; color: #393;
 }
--- a/bitten/model.py
+++ b/bitten/model.py
@@ -587,6 +587,7 @@
 
     # Step status codes
     SUCCESS = 'S'
+    IN_PROGRESS = 'I'
     FAILURE = 'F'
 
     def __init__(self, env, build=None, name=None, description=None,
@@ -610,7 +611,8 @@
                       doc='Whether this build step exists in the database')
     successful = property(fget=lambda self: self.status == BuildStep.SUCCESS,
                           doc='Whether the build step was successful')
-
+    completed = property(fget=lambda self: self.status == BuildStep.SUCCESS or self.status == BuildStep.FAILURE,
+                          doc='Whether this build step has completed processing')
     def delete(self, db=None):
         """Remove the build step from the database."""
         if not db:
@@ -645,7 +647,7 @@
             handle_ta = False
 
         assert self.build and self.name
-        assert self.status in (self.SUCCESS, self.FAILURE)
+        assert self.status in (self.SUCCESS, self.IN_PROGRESS, self.FAILURE)
 
         cursor = db.cursor()
         cursor.execute("INSERT INTO bitten_step (build,name,description,status,"
@@ -694,7 +696,7 @@
         if not db:
             db = env.get_db_cnx()
 
-        assert status in (None, BuildStep.SUCCESS, BuildStep.FAILURE)
+        assert status in (None, BuildStep.SUCCESS, BuildStep.IN_PROGRESS, BuildStep.FAILURE)
 
         where_clauses = []
         if build is not None:
@@ -709,7 +711,7 @@
             where = ""
 
         cursor = db.cursor()
-        cursor.execute("SELECT build,name FROM bitten_step %s ORDER BY stopped"
+        cursor.execute("SELECT build,name FROM bitten_step %s ORDER BY started"
                        % where, [wc[1] for wc in where_clauses])
         for build, name in cursor:
             yield BuildStep.fetch(env, build, name, db=db)
--- a/bitten/templates/bitten_config.html
+++ b/bitten/templates/bitten_config.html
@@ -18,6 +18,17 @@
       </py:choose>
     </strong>
     
+    <div py:def="build_time(build)" class="system">
+      <py:choose>
+	<span py:when="build.stopped">
+	  <div>Duration: ${build.duration}</div>
+	</span>
+	<span py:otherwise="">
+	  <div>${build.started} (${build.started_delta} ago)</div>
+	</span>
+      </py:choose>
+    </div>
+
     <div py:def="slave_info(slave)" class="system">
       <strong>$slave.name</strong> ($slave.ipnr)<br />
       $slave.os_name $slave.os_version
@@ -27,12 +38,12 @@
     
     <ul py:def="build_steps(steps)" py:if="steps" class="steps">
       <li py:for="step in steps"
-          class="${step.failed and 'failed' or 'success'}">
+          class="${step.cls}">
         <span class="duration">$step.duration</span> 
         <a href="$step.href" title="${step.description or None}">
           $step.name
         </a>
-        <ul py:if="step.failed and step.errors">
+        <ul py:if="step.status is 'failed' and step.errors">
           <li py:for="error in step.errors">$error</li>
         </ul>
       </li>
@@ -65,8 +76,7 @@
         </py:if></py:for>)</i>
        </div></py:if><py:if test="config.builds_inprogress">
        <div>$config.builds_inprogress in-progress
-         build<py:if test="config.builds_inprogress > 1">s</py:if>&nbsp;<i>(<py:for each="platform in config.platforms">
-           <py:if test="platform.builds_inprogress">
+         build<py:if test="config.builds_inprogress > 1">s</py:if>&nbsp;<i>(<py:for each="platform in config.platforms"><py:if test="platform.builds_inprogress">
             $platform.name: $platform.builds_inprogress
           </py:if></py:for>)</i>
       </div></py:if>
@@ -137,9 +147,8 @@
           $platform.name: $platform.builds_pending
         </py:if></py:for>)</i>
        </div></py:if><py:if test="config.builds_inprogress">
-        <div>$config.builds_inprogress in-progress build<py:if test="config.builds_inprogress > 1">s</py:if>&nbsp;<i>(<py:for each="platform in config.platforms">
-          <py:if test="platform.builds_inprogress">
-            $platform.name: $platform.builds_inprogress
+	<div>$config.builds_inprogress in-progress build<py:if test="config.builds_inprogress > 1">s</py:if>&nbsp;<i>(<py:for each="platform in config.platforms"><py:if test="platform.builds_inprogress">
+	$platform.name: $platform.builds_inprogress
         </py:if></py:for>)</i>
       </div></py:if>
       <div id="charts"><py:for each="chart in config.charts">
@@ -173,6 +182,7 @@
                   ${build_status(build.status)}
                 </a>
                 ${slave_info(build.slave)}
+		${build_time(build)}
               </div>
               ${build_steps(build.steps)}
             </td>
@@ -201,6 +211,7 @@
                 $build.id: <strong class="status">$build.platform</strong>
               </a>
               ${slave_info(build.slave)}
+              ${build_time(build)}
             </div>
             ${build_steps(build.steps)}
           </td>
--- a/bitten/web_ui.py
+++ b/bitten/web_ui.py
@@ -12,6 +12,7 @@
 
 import posixpath
 import re
+import time
 from StringIO import StringIO
 from datetime import datetime
 
@@ -45,6 +46,9 @@
                  Build.IN_PROGRESS: 'In Progress',
                  Build.SUCCESS: 'Success',
                  Build.FAILURE: 'Failure'}
+_step_status_label = {BuildStep.SUCCESS: 'success',
+                      BuildStep.FAILURE: 'failed',
+                      BuildStep.IN_PROGRESS: 'in progress'}
 
 def _get_build_data(env, req, build):
     data = {'id': build.id, 'name': build.slave, 'rev': build.rev,
@@ -89,7 +93,7 @@
             status = ''
             if BuildMaster(self.env).quick_status:
                 repos = self.env.get_repository(req.authname)
-                for config in BuildConfig.select(self.env, 
+                for config in BuildConfig.select(self.env,
                                                  include_inactive=False):
                     prev_rev = None
                     for platform, rev, build in collect_changes(repos, config):
@@ -106,11 +110,11 @@
                                 status='bitteninprogress'
                             elif not status:
                                 if (build_data['status'] == 'completed'):
-                                    status='bittencompleted'  
+                                    status='bittencompleted'
                 if not status:
                     status='bittenpending'
             yield ('mainnav', 'build',
-                   tag.a('Build Status', href=req.href.build(), accesskey=5, 
+                   tag.a('Build Status', href=req.href.build(), accesskey=5,
                          class_=status))
 
     # ITemplatesProvider methods
@@ -164,16 +168,16 @@
         return 'bitten_config.html', data, None
 
     # IRequestHandler methods
-    
+
     def pre_process_request(self, req, handler):
         return handler
 
     def post_process_request(self, req, template, data, content_type):
         if template:
             add_stylesheet(req, 'bitten/bitten.css')
-        
+
         return template, data, content_type
-        
+
     # Internal methods
 
     def _render_overview(self, req):
@@ -301,9 +305,10 @@
                     build_data['steps'].append({
                         'name': step.name,
                         'description': step.description,
-                        'duration': to_datetime(step.stopped, utc) - \
+                        'duration': to_datetime(step.stopped or int(time.time()), utc) - \
                                     to_datetime(step.started, utc),
-                        'failed': not step.successful,
+                        'status': _step_status_label[step.status],
+                        'cls': _step_status_label[step.status].replace(' ', '-'),
                         'errors': step.errors,
                         'href': build_data['href'] + '#step_' + step.name
                     })
@@ -311,7 +316,7 @@
                 builds.append(build_data)
                 current_builds += 1
 
-            if current_builds == 0: 
+            if current_builds == 0:
                 continue
 
             description = config.description
@@ -325,7 +330,7 @@
                 'builds': builds
             })
 
-        data['configs'] = configs
+        data['configs'] = sorted(configs, key=lambda x:x['label'].lower())
         return data
 
     def _render_config(self, req, config_name):
@@ -372,7 +377,7 @@
                                                db=db))
         data['config']['platforms'] = [
             { 'name': platform.name,
-              'id': platform.id, 
+              'id': platform.id,
               'builds_pending': len(list(Build.select(self.env,
                                                     config=config.name,
                                                     status=Build.PENDING,
@@ -393,13 +398,13 @@
         if has_reports:
             chart_generators = []
             report_categories = list(self._report_categories_for_config(config))
-            for generator in ReportChartController(self.env).generators: 
-                for category in generator.get_supported_categories(): 
+            for generator in ReportChartController(self.env).generators:
+                for category in generator.get_supported_categories():
                     if category in report_categories:
                         chart_generators.append({
-                            'href': req.href.build(config.name, 'chart/' + category) 
+                            'href': req.href.build(config.name, 'chart/' + category)
                         })
-            data['config']['charts'] = chart_generators 
+            data['config']['charts'] = chart_generators
             charts_license = self.config.get('bitten', 'charts_license')
             if charts_license:
                 data['config']['charts_license'] = charts_license
@@ -428,9 +433,11 @@
                         build_data['steps'].append({
                             'name': step.name,
                             'description': step.description,
-                            'duration': to_datetime(step.stopped, utc) - \
+                            'duration': to_datetime(step.stopped or int(time.time()), utc) - \
                                         to_datetime(step.started, utc),
-                            'failed': not step.successful,
+                            'status': _step_status_label[step.status],
+                            'cls': _step_status_label[step.status].replace(' ', '-'),
+
                             'errors': step.errors,
                             'href': build_data['href'] + '#step_' + step.name
                         })
@@ -457,16 +464,16 @@
         """Yields the categories of reports that exist for active builds
         of this configuration.
         """
-           
+
         db = self.env.get_db_cnx()
         repos = self.env.get_repository()
         cursor = db.cursor()
-        
+
         cursor.execute("""SELECT DISTINCT report.category as category
-FROM bitten_build AS build 
+FROM bitten_build AS build
 JOIN bitten_report AS report ON (report.build=build.id)
-WHERE build.config=%s AND build.rev_time >= %s AND build.rev_time <= %s""", 
-                       (config.name, 
+WHERE build.config=%s AND build.rev_time >= %s AND build.rev_time <= %s""",
+                       (config.name,
                         config.min_rev_time(self.env),
                         config.max_rev_time(self.env)))
 
@@ -545,8 +552,9 @@
         for step in BuildStep.select(self.env, build=build.id, db=db):
             steps.append({
                 'name': step.name, 'description': step.description,
-                'duration': pretty_timedelta(step.started, step.stopped),
-                'failed': step.status == BuildStep.FAILURE,
+                'duration': pretty_timedelta(step.started, step.stopped or int(time.time())),
+                'status': _step_status_label[step.status],
+                'cls': _step_status_label[step.status].replace(' ', '-'),
                 'errors': step.errors,
                 'log': self._render_log(req, build, formatters, step),
                 'reports': self._render_reports(req, config, build, summarizers,
Copyright (C) 2012-2017 Edgewall Software