2.2.3 is now stable stable
authorDavid Douard <david.douard@logilab.fr>
Fri, 23 Mar 2012 12:00:56 +0100
branchstable
changeset 825 9109514e0237
parent 810 d25be2b999f6 (current diff)
parent 824 562c476dddbb (diff)
child 826 5db4dd8bbeee
2.2.3 is now stable
--- a/.hgtags	Tue Jan 31 11:40:32 2012 +0100
+++ b/.hgtags	Fri Mar 23 12:00:56 2012 +0100
@@ -74,3 +74,5 @@
 5b157776204a3d8afcbb3443a0c5925c72b8e854 apycot-debian-version-2.2.0-1
 35bdc8c657d6d85611e934b080a0e69a174851ae apycot-version-2.2.1
 74f6596c6246308afc32f6a08d390fddfb7fb635 apycot-debian-version-2.2.1-1
+1e8dff8bfd1b3ac89b42b4e713cf52889c60374a apycot-version-2.2.2
+372245c5bbb01a8f990c6eb2b9eed545ff6dba2b apycot-debian-version-2.2.2-1
--- a/__pkginfo__.py	Tue Jan 31 11:40:32 2012 +0100
+++ b/__pkginfo__.py	Fri Mar 23 12:00:56 2012 +0100
@@ -4,7 +4,7 @@
 modname = 'apycot'
 distname = 'apycot'
 
-numversion = (2, 2, 1)
+numversion = (2, 2, 3)
 version = '.'.join(str(num) for num in numversion)
 
 license = 'GPL'
@@ -24,6 +24,7 @@
                'cubicweb-vcsfile': '>= 1.6.1',
                'cubicweb-file': None,
                'cubicweb-narval': '>= 3.0.2',
+               'cubicweb-jqplot': '>= 0.1.2',
                }
 __recommends__ = {'cubicweb-tracker': None,
                   'cubicweb-nosylist': '>= 0.5.0'}
--- a/_apycotlib/checkers/python.py	Tue Jan 31 11:40:32 2012 +0100
+++ b/_apycotlib/checkers/python.py	Fri Mar 23 12:00:56 2012 +0100
@@ -56,7 +56,7 @@
         from logilab.devtools.lib.pkginfo import PackageInfo
         pkginfo = PackageInfo(directory=test.project_path())
         modname = getattr(pkginfo, 'modname', None)
-        distname = getattr(pkginfo, 'distname', None)
+        distname = getattr(pkginfo, 'distname', modname)
         package = getattr(pkginfo, 'subpackage_of', None)
         if modname and package:
             modname = '%s.%s' % (package, modname)
--- a/data/cubes.apycot.css	Tue Jan 31 11:40:32 2012 +0100
+++ b/data/cubes.apycot.css	Fri Mar 23 12:00:56 2012 +0100
@@ -214,3 +214,7 @@
     background-color : transparent ;
     border: none;
 }
+
+table.plotlegend td{
+    padding : 0 5px 0 10px
+}
\ No newline at end of file
--- a/debian/changelog	Tue Jan 31 11:40:32 2012 +0100
+++ b/debian/changelog	Fri Mar 23 12:00:56 2012 +0100
@@ -1,3 +1,21 @@
+apycot (2.2.3-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- David Douard <david.douard@logilab.fr>  Fri, 23 Mar 2012 10:18:46 +0100
+
+apycot (2.2.2-2) unstable; urgency=low
+
+  * add cubicweb-jqplot as a recommends
+
+ -- David Douard <david.douard@logilab.fr>  Fri, 09 Mar 2012 10:14:47 +0100
+
+apycot (2.2.2-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- David Douard <david.douard@logilab.fr>  Thu, 08 Mar 2012 10:10:19 +0100
+
 apycot (2.2.1-1) unstable; urgency=low
 
   * new upstream release
--- a/debian/control	Tue Jan 31 11:40:32 2012 +0100
+++ b/debian/control	Fri Mar 23 12:00:56 2012 +0100
@@ -14,6 +14,7 @@
 Architecture: all
 XB-Python-Version: ${python:Versions}
 Depends: ${misc:Depends}, ${python:Depends}, cubicweb-common (>= 3.14.0), cubicweb-vcsfile (>= 1.6.1), cubicweb-file (>= 1.8.2), cubicweb-narval (>= 3.0.2), pyro
+Recommends: cubicweb-jqplot (>= 0.1.2)
 Suggests: cubicweb-tracker, cubicweb-nosylist (>= 0.5.0)
 Description: apycot component for the CubicWeb framework
  This CubicWeb component store data from the Apycot testing framework
--- a/entities.py	Tue Jan 31 11:40:32 2012 +0100
+++ b/entities.py	Fri Mar 23 12:00:56 2012 +0100
@@ -16,11 +16,18 @@
 from logilab.mtconverter import xml_escape
 
 from cubicweb import ValidationError
+from cubicweb.uilib import domid
 from cubicweb.entity import _marker
 from cubicweb.entities import AnyEntity, fetch_config
 
 from cubes.narval.entities import Plan
 
+def _anchor_name(data):
+    """escapes XML/HTML forbidden characters in attributes and PCDATA"""
+    return (data.replace('&', '').replace('<', '').replace('>','')
+            .replace('"', '').replace("'", ''))
+
+
 class ExecutionRSSMixin(object): # XXX move to an ui adapter
 
     RSS_LIMIT = 20
@@ -431,13 +438,17 @@
                                              'name', 'status'])
 
     def absolute_url(self, *args, **kwargs):
-        kwargs.setdefault('tab', self.name)
+        kwargs.setdefault('tab', domid(self.anchored_name))
         return self.execution.absolute_url(*args, **kwargs)
 
     @property
     def execution(self):
         return self.during_execution[0]
 
+    @property
+    def anchored_name(self):
+        return _anchor_name(self.name)
+
 
 class CheckResultInfo(AnyEntity):
     __regid__ = 'CheckResultInfo'
--- a/i18n/en.po	Tue Jan 31 11:40:32 2012 +0100
+++ b/i18n/en.po	Fri Mar 23 12:00:56 2012 +0100
@@ -873,6 +873,9 @@
 msgid "when this test config should be started"
 msgstr ""
 
+msgid "Time taken by Test Executions"
+msgstr ""
+
 #~ msgid "boxes_apycot.te.download_box"
 #~ msgstr "download box"
 
--- a/i18n/fr.po	Tue Jan 31 11:40:32 2012 +0100
+++ b/i18n/fr.po	Fri Mar 23 12:00:56 2012 +0100
@@ -894,4 +894,8 @@
 msgstr "chaque semaine"
 
 msgid "when this test config should be started"
-msgstr "quand cette configuration doit-ellle être lancée"
+msgstr "quand cette configuration doit-elle être lancée"
+
+msgid "Time taken by Test Executions"
+msgstr "Temps d'exécution des tests"
+
--- a/views/__init__.py	Tue Jan 31 11:40:32 2012 +0100
+++ b/views/__init__.py	Fri Mar 23 12:00:56 2012 +0100
@@ -11,11 +11,6 @@
 
 from cubes.narval.proxy import bot_proxy
 
-def anchor_name(data):
-    """escapes XML/HTML forbidden characters in attributes and PCDATA"""
-    return (data.replace('&', '').replace('<', '').replace('>','')
-            .replace('"', '').replace("'", ''))
-
 _afs = uicfg.autoform_section
 _affk = uicfg.autoform_field_kwargs
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/plots.py	Fri Mar 23 12:00:56 2012 +0100
@@ -0,0 +1,75 @@
+"""this module contains some plot report views for test execution results
+
+:organization: Logilab
+:copyright: 2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: General Public License version 2 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+_ = unicode
+
+
+ERROR_CODES = {
+    'error': "#FF5555",
+    'failure': "#FF8844",
+    'partial': "#FFFF55",
+    'success': "#33FF33",
+    'other': "#AAAAAA",
+    }
+
+from cubicweb.selectors import multi_columns_rset
+
+try:
+    from cubes.jqplot.views import JQPlotSimpleView
+except ImportError:
+    pass
+else:
+    class JQPlotTestExecutionView(JQPlotSimpleView):
+        __regid__ = 'jqplot.testexecution'
+        __select__ = multi_columns_rset(3)
+        default_renderer = 'bar'
+
+        onload = '''%(id)s = $.jqplot("%(id)s", %(data)s, %(options)s);
+
+        $(document).ready(function(){
+            $("#%(id)s").bind('jqplotDataClick',
+                function (ev, seriesIndex, pointIndex, data) {
+                    /* To open in a NEW window use: */
+                    if ( ev.which == 2 ) {
+                       window.open(data[2]);
+                    } else  {
+                       /* To open in the same window use: */
+                       window.location.href=data[2];
+                    };
+                }
+            );
+        });
+        '''
+        default_options = {
+            'varyBarColor': True,
+            'barMargin':0,
+            }
+        default_legend = {
+            'show': False,
+            }
+
+        def get_data(self):
+            return [[i+1,x[1],self._cw.build_url(x[0])] for i,x in enumerate(self.cw_rset.rows)]
+
+        def set_custom_options(self, options):
+            other = ERROR_CODES.get('other')
+            options['seriesColors'] = [ERROR_CODES.get(x[2], other) for x in self.cw_rset.rows]
+            #FIXME hack to approximate width since it doesn't work in js
+            options['series'][0]['rendererOptions']['barWidth'] = 350/len(self.cw_rset.rows)
+
+        def div_holder(self, divid, width, height):
+            super(JQPlotTestExecutionView, self).div_holder(divid, width, height)
+            self.w(u'''<table class="plotlegend"><tr>%s</tr></table>
+            ''' % ''.join(['<td>%s</td><td style="background:%s">&nbsp;&nbsp;&nbsp;</td>' % (x,y) for x,y in  ERROR_CODES.items()]))
+
+
+def registration_callback(vreg):
+    if not 'jqplot' in vreg.config.cubes():
+        return # don't register anything from this module
+    vreg.register_all(globals().values(), __name__)
+
--- a/views/primary.py	Tue Jan 31 11:40:32 2012 +0100
+++ b/views/primary.py	Fri Mar 23 12:00:56 2012 +0100
@@ -21,7 +21,6 @@
 from cubicweb.web.views import ibreadcrumbs
 
 from cubes.narval.views import no_robot_index, startplan
-from cubes.apycot.views import anchor_name
 
 
 _pvs = uicfg.primaryview_section
@@ -136,6 +135,9 @@
     html_headers = no_robot_index
 
     def entity_call(self, entity):
+        project_rset = self._cw.execute('Any P WHERE P has_apycot_environment TC, TC eid %(e)s',
+                                        {'e': entity.eid})
+        self.wview('projectgraphtestresults', project_rset, 'null')
         rset = self._cw.execute(
             'Any T,TC,T,TB,TST,TET,TF, TS ORDERBY TST DESC WHERE '
             'T status TS, T using_config TC, T branch TB, '
--- a/views/reports.py	Tue Jan 31 11:40:32 2012 +0100
+++ b/views/reports.py	Fri Mar 23 12:00:56 2012 +0100
@@ -14,11 +14,11 @@
 
 from StringIO import StringIO
 
+from cubicweb.uilib import domid
 from cubicweb.view import EntityView
 from cubicweb.selectors import is_instance
 
 from cubes.narval.views import no_robot_index
-from cubes.apycot.views import anchor_name
 
 
 @cached
@@ -91,16 +91,16 @@
         self.w(u'<table class="summary">')
         self.w(u'<tr><th>%s</th><th>%s</th></tr>'
                % (self._cw._('name'), self._cw._('status')))
-        url = entity.absolute_url()
         for checkname in entity.configuration.all_checks():
             check = entity.check_result_by_name(checkname)
             if check is None:
                 status = 'nc'
+                url = entity.absolute_url()
             else:
                 status = entity.check_result_by_name(checkname).status
-            self.w(u'<tr><td><a href="%s?tab=%s">%s</a></td><td class="status_%s">%s</td></tr>'
-                   % (url, anchor_name(checkname), xml_escape(checkname),
-                      status, status))
+                url = check.absolute_url()
+            self.w(u'<tr><td><a href="%s">%s</a></td><td class="status_%s">%s</td></tr>'
+                   % (url, xml_escape(checkname), status, status))
         self.w(u'</table>')
 
 
--- a/views/testexecution.py	Tue Jan 31 11:40:32 2012 +0100
+++ b/views/testexecution.py	Fri Mar 23 12:00:56 2012 +0100
@@ -20,7 +20,6 @@
 from cubicweb.web.views import ibreadcrumbs, idownloadable, navigation
 
 from cubes.narval.views import no_robot_index
-from cubes.apycot.views import anchor_name
 
 from cubes.narval.views.plan import PlanOptionsCell
 
@@ -180,7 +179,7 @@
         for check in entity.reverse_during_execution:#configuration.all_checks:
             label = u'%s [<b class="status_%s">%s</b>]' % (
                 xml_escape(check.name), check.status, self._cw._(check.status))
-            tabs.append((anchor_name(check.name),
+            tabs.append((check.anchored_name,
                          {'vid': 'apycot.te.checkresult', 'label': label,
                           'rset': check.as_rset()}))
         return tabs
@@ -253,7 +252,7 @@
         checks = []
         for check in entity.reverse_during_execution:
             content = u'%s (%s)' % (self._cw._(check.name), check.status)
-            url = entity.absolute_url(tab=anchor_name(check.name))
+            url = check.absolute_url()
             title = self._cw._('see execution report for %s') % check.name
             checks.append(tags.a(content, href=url, title=title))
         if checks:
@@ -397,7 +396,7 @@
     def render_entity_title(self, entity):
         self._cw.add_css('cubes.apycot.css')
         self.w('<h4 id="%s" >%s [<span class="status_%s">%s</span>]</h4>'
-               % (anchor_name(entity.name),
+               % (entity.anchored_name,
                   xml_escape(entity.name), entity.status, entity.status))
 
 
--- a/views/tracker.py	Tue Jan 31 11:40:32 2012 +0100
+++ b/views/tracker.py	Fri Mar 23 12:00:56 2012 +0100
@@ -1,7 +1,7 @@
 """this module contains some stuff to integrate the apycot cube into jpl
 
 :organization: Logilab
-:copyright: 2009-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2009-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 :license: General Public License version 2 - http://www.gnu.org/licenses
 """
@@ -11,16 +11,95 @@
 from cubicweb.selectors import is_instance
 from cubicweb.view import EntityView
 from cubicweb.web import uicfg
+from cubicweb.web.views import forms
+from cubicweb.web.formfields import StringField, DateField
+from cubicweb.web.views.formrenderers import EntityFormRenderer
+from cubicweb.web import formwidgets as fwdgs, httpcache
 
 _pvs = uicfg.primaryview_section
 _pvs.tag_subject_of(('Project', 'has_apycot_environment', '*'), 'attributes')
 
+# in graphs ignore branches that have less MIN_NB_RUNS_IN_GRAPH
+MIN_NB_RUNS_IN_GRAPH = 3
+
+def available_graphs(form, **attrs):
+    entity = form.cw_rset.get_entity(0,0)
+    testconfig_rset = form._cw.execute('Any TC,NAME WHERE P has_apycot_environment TENV, '
+                                       'TC use_environment TENV, P eid %(p)s, TC name NAME',  {'p': entity.eid})
+    branches_rset = form._cw.execute('Any B GROUPBY B WHERE TE branch B, '
+                                     'TE is TestExecution, TE using_environment TENV, '
+                                     'P has_apycot_environment TENV, P eid %(p)s HAVING COUNT(TE) > %(limit)s',
+                                     {'p':entity.eid,
+                                      'limit':MIN_NB_RUNS_IN_GRAPH})
+    labels_and_values = []
+    for branch in branches_rset:
+        var_dict = {'p': entity.eid,
+                    'branch':branch[0]}
+        for testconfig in testconfig_rset:
+            label = '%s : %s - %s' % (_(u'Test run time'), testconfig[1], branch[0])
+            rql = 'Any TE,  ET - ST, S ORDERBY ST LIMIT 50 WHERE ' \
+            'TE is TestExecution, TE using_environment TENV, ' \
+            'P has_apycot_environment TENV, TE starttime ST, ' \
+            'TE endtime ET, TE eid E, TE status S, P eid %(p)s, ' \
+            'TE using_config TC, TC eid %(tc)s, TE branch "%(branch)s"'
+            var_dict['tc'] = testconfig[0]
+            rset = form._cw.execute(rql % var_dict)
+            if rset and len(rset) > MIN_NB_RUNS_IN_GRAPH:
+                js_call = form._cw.ajax_replace_url('graph-container',
+                                                    rql= rql % var_dict,
+                                                    vid='jqplot.testexecution')
+                labels_and_values.append((label, js_call.replace('javascript: ', '')))
+
+        for cri_label, label in (('pylint.evaluation', 'Pylint score'),
+                                 ('cover-line-rate', 'Cover line rate')):
+            label = '%s : %s' % (_(label), branch[0])
+            rql = 'Any V ORDERBY D LIMIT 50 WHERE X is CheckResultInfo, X label "%(cri_label)s", ' \
+                  'X value V, X for_check CR, CR during_execution TE, TE using_environment TENV, '\
+                  'P has_apycot_environment TENV, P eid %(p)s, TE starttime D, TE branch "%(branch)s" '
+            var_dict['cri_label'] = cri_label
+            rset = form._cw.execute(rql % var_dict)
+            if rset and len(rset) > MIN_NB_RUNS_IN_GRAPH:
+                js_call = form._cw.ajax_replace_url('graph-container',
+                                                    rql= rql % var_dict,
+                                                    vid='jqplot.nonperiodic')
+                labels_and_values.append((label, js_call.replace('javascript: ', '')))
+    labels_and_values.reverse()
+    return labels_and_values
+
+class GraphRefreshForm(forms.FieldsForm):
+    """Form to select what graph is being displayed"""
+    __regid__ = 'select-graph'
+    graphs = StringField(widget=fwdgs.Select(attrs={'onchange':'eval($("select#graphs").val())',
+                                                    'onkeyup':'this.blur();this.focus();'}),
+                         label=_('Graph:'),
+                         choices=available_graphs)
+    form_buttons = []
+    form_renderer_id = 'nomaininfo'
+
+class NoBoxEntityFormRenderer(EntityFormRenderer):
+    __regid__ = 'nomaininfo'
+    main_form_title = ''
+
+class ProjectGraphTestResults(EntityView):
+    """display project's graph from various execution results"""
+    __regid__ = 'projectgraphtestresults'
+    __select__ = is_instance('Project')
+
+    def entity_call(self, entity):
+        self.w(u'<h3>%s</h3>' % _('Time taken by Test Executions'))
+        form = self._cw.vreg['forms'].select('select-graph', self._cw, rset=self.cw_rset)
+        form.render(w=self.w)
+        self._cw.add_onload('eval($("select#graphs").val())')
+        self.w(u'<div id="graph-container"></div>')
+
+
 class ProjectTestResultsTab(EntityView):
-    """display project's documentation"""
+    """display project's test execution results"""
     __regid__ = title = _('apycottestresults_tab')
     __select__ = is_instance('Project')
 
     def entity_call(self, entity):
+        self.wview('projectgraphtestresults', self.cw_rset, 'null')
         rset = self._cw.execute(
             'Any T,TC,T,TB,TST,TET,TF, TS ORDERBY TST DESC WHERE '
             'T status TS, T using_config TC, T branch TB, '
@@ -49,10 +128,15 @@
 #         self.wview('summary', configsrset, title=self._cw._(self.title))
 
 
-try:
-    from cubes.tracker.views.project import ProjectPrimaryView
-except ImportError:
-    pass
-else:
-    if 'apycottestresults_tab' not in ProjectPrimaryView.tabs:
-        ProjectPrimaryView.tabs.append('apycottestresults_tab')
+def registration_callback(vreg):
+    try:
+        from cubes.tracker.views.project import ProjectPrimaryView
+    except ImportError:
+        pass
+    else:
+        has_jqplot = 'jqplot' in vreg.config.cubes()
+        no_jqplot = [GraphRefreshForm, ProjectGraphTestResults]
+        to_register = [value for value in globals().values() if has_jqplot or value not in no_jqplot]
+        vreg.register_all(to_register, __name__)
+        if 'apycottestresults_tab' not in ProjectPrimaryView.tabs:
+            ProjectPrimaryView.tabs.append('apycottestresults_tab')