backport vcsfile-based branch to default
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Tue, 16 Jun 2009 16:07:29 +0200
changeset 165 11b03137754c
parent 134 bd434757b007 (current diff)
parent 164 fb6d9f7150d8 (diff)
child 167 f9a2bd1e9646
backport vcsfile-based branch to default
--- a/__pkginfo__.py	Sun May 24 17:09:23 2009 +0200
+++ b/__pkginfo__.py	Tue Jun 16 16:07:29 2009 +0200
@@ -4,7 +4,7 @@
 distname = 'cubicweb-apycot'
 modname = distname.split('-', 1)[1]
 
-numversion = (1, 1, 0)
+numversion = (1, 2, 0)
 version = '.'.join(str(num) for num in numversion)
 
 license = 'GPL'
@@ -52,5 +52,5 @@
 
 
 cube_eid = None # <=== FIXME if you need direct bug-subscription
-__use__ = ()
+__use__ = ('vcsfile',)
 __recommend__ = ('jpl',)
--- a/debian/control	Sun May 24 17:09:23 2009 +0200
+++ b/debian/control	Tue Jun 16 16:07:29 2009 +0200
@@ -9,7 +9,7 @@
 
 Package: cubicweb-apycot
 Architecture: all
-Depends: cubicweb-common (>= 3.2.0)
+Depends: cubicweb-common (>= 3.2.0), pyro
 Description: store apycot data and extract reports from them
  CubicWeb is a semantic web application framework.
  .
--- a/entities.py	Sun May 24 17:09:23 2009 +0200
+++ b/entities.py	Tue Jun 16 16:07:29 2009 +0200
@@ -45,8 +45,8 @@
 
 
 
-class ApycotConfigGroup(AnyEntity):
-    id = 'ApycotConfigGroup'
+class TestConfigGroup(AnyEntity):
+    id = 'TestConfigGroup'
 
     fetch_attrs, fetch_order = fetch_config(['name'])
 
@@ -108,31 +108,45 @@
 
     @property
     def my_repository_def(self):
+        if self.vcs_repository:
+            vcsrepo = self.vcs_repository
+            vcsrepotype = self.vcs_repository_type
+        elif self.local_repository:
+            vcsrepo = self.local_repository[0].path
+            if self.local_repository[0].type == 'mercurial':
+                vcsrepotype = 'hg'
+            else:
+                vcsrepotype = 'svn'
+        else:
+            vcsrepo = vcsrepotype = None
         return {
-            'repository_type': self.vcs_repository_type,
-            'repository': self.vcs_repository,
+            'repository_type': vcsrepotype,
+            'repository': vcsrepo,
             'path': self.vcs_path,
-            'tag': self.vcs_tag,
+            'branch': self.vcs_branch,
             }
 
     @property
     def repository_def(self):
         return self._substitute_dict(self._regroup_dict('my_repository_def'))
 
-    @property
-    def my_all_checks(self):
-        if self.checks:
+    def my_all_checks(self, mode=None):
+        if mode is None:
+            mode = 'full'
+        if mode == 'full' and self.checks:
             return get_csv(self.checks)
+        if mode == 'quick' and self.quick_checks:
+            return get_csv(self.quick_checks)
 
-    @property
-    def all_checks(self):
+    def all_checks(self, mode=None):
         for group in self.config_parts():
-            if group.my_all_checks:
-                return group.my_all_checks
+            checks = group.my_all_checks(mode)
+            if checks:
+                return checks
 
 
-class ProjectApycotConfig(ApycotConfigGroup):
-    id = 'ProjectApycotConfig'
+class TestConfig(TestConfigGroup):
+    id = 'TestConfig'
     _testdir = None
 
     def parent(self):
@@ -145,7 +159,7 @@
     # cube specific logic #####################################################
 
     def _substitute(self, value):
-        value = super(ProjectApycotConfig, self)._substitute(value)
+        value = super(TestConfig, self)._substitute(value)
         if value and self._testdir:
             value = value.replace('${TESTDIR}', self._testdir)
         return value
@@ -155,7 +169,7 @@
         from apycotbot.repositories import get_repository
         return get_repository(self.repository_def)
 
-    def dependencies(self, _done=None):
+    def dependencies(self, main=True, _done=None):
         if _done is None:
             _done = set()
         _done.add(self.eid)
@@ -164,7 +178,12 @@
             if pac.eid in _done:
                 continue
             result.append(pac)
-            result += pac.dependencies(_done)
+            result += pac.dependencies(False, _done)
+        if main:
+            for pac in self.test_needs_checkout:
+                if pac.eid in _done:
+                    continue
+                result.append(pac)
         return result
 
     def test_init(self, writer, testdir, additional_options):
@@ -177,7 +196,7 @@
         if 'tag' in additional_options:
             self.repository().tag = additional_options['tag']
         self.checkers = {}
-        for name in self.all_checks:
+        for name in self.all_checks(mode='full'):
             try:
                 options = options_from_dict(config, name)
                 checker = get_registered('checker', name)(writer, options)
@@ -211,7 +230,7 @@
 
     def latest_execution(self):
         rset = self.req.execute('Any X, C ORDERBY X DESC LIMIT 1'
-                                'WHERE X is ApycotExecution, X using_config C, '
+                                'WHERE X is TestExecution, X using_config C, '
                                 'C eid %(c)s', {'c': self.eid}, 'c')
         if rset:
             return rset.get_entity(0, 0)
@@ -219,7 +238,7 @@
     def latest_full_execution(self):
         rset = self.req.execute('Any X, C, COUNT(CR) GROUPBY X, C '
                                 'ORDERBY 3 DESC, X DESC LIMIT 1'
-                                'WHERE X is ApycotExecution, X using_config C, '
+                                'WHERE X is TestExecution, X using_config C, '
                                 'C eid %(c)s, CR during_execution X',
                                 {'c': self.eid}, 'c')
         if rset:
@@ -238,8 +257,8 @@
         return self.parent().project
 
 
-class ApycotExecution(AnyEntity):
-    id = 'ApycotExecution'
+class TestExecution(AnyEntity):
+    id = 'TestExecution'
     __implements__ = AnyEntity.__implements__ + (IPrevNext,)
 
     def dc_title(self):
@@ -255,7 +274,7 @@
 
     def previous_entity(self):
         rql = ('Any X,C ORDERBY X DESC LIMIT 1 '
-               'WHERE X is ApycotExecution, X using_config C, C eid %(c)s, '
+               'WHERE X is TestExecution, X using_config C, C eid %(c)s, '
                'X eid < %(x)s')
         rset = self.req.execute(rql, {'c': self.apycot_config.eid, 'x': self.eid})
         if rset:
@@ -263,7 +282,7 @@
 
     def next_entity(self):
         rql = ('Any X,C ORDERBY X ASC LIMIT 1 '
-               'WHERE X is ApycotExecution, X using_config C, C eid %(c)s, '
+               'WHERE X is TestExecution, X using_config C, C eid %(c)s, '
                'X eid > %(x)s')
         rset = self.req.execute(rql, {'c': self.apycot_config.eid, 'x': self.eid})
         if rset:
@@ -363,3 +382,12 @@
         if attr == 'msg' and format == 'text/html':
             return '<pre class="rawtext">%s</pre>' % value
         return value
+
+
+from logilab.common.pyro_ext import ns_get_proxy
+
+def bot_proxy(config, cache):
+    if not 'botproxy' in cache:
+        cache['botproxy'] = ns_get_proxy(config['bot-pyro-id'], 'apycot',
+                                         nshost=config['bot-pyro-ns'])
+    return cache['botproxy']
--- a/hooks.py	Sun May 24 17:09:23 2009 +0200
+++ b/hooks.py	Tue Jun 16 16:07:29 2009 +0200
@@ -9,13 +9,15 @@
 __docformat__ = "restructuredtext en"
 
 from cubicweb.selectors import implements
-from cubicweb.server.hooksmanager import Hook
-from cubicweb.sobjects.notification import NotificationView, RenderAndSendNotificationView
+from cubicweb.server import hooksmanager, pool
+from cubicweb.sobjects import notification
 
+from cubes.apycot.entities import bot_proxy
 
-class ExecStatusChangeView(NotificationView):
+class ExecStatusChangeView(notification.NotificationView):
     id = 'exstchange'
-    __select__ = implements('ApycotExecution')
+    __select__ = implements('TestExecution')
+
     content = '''The following changes occured between executions started at %(ex1time)s and %(ex2time)s:
 
 %(changelist)s
@@ -25,8 +27,23 @@
 
     def subject(self):
         entity = self.entity(0, 0)
-        subject = self.req._('status changes during checks execution for %s')
-        return '[APyCoT] %s' % (subject % entity.apycot_config.name)
+        changes = entity.status_changes()
+        testconfig = entity.apycot_config.name
+        if len(changes) == 1:
+            subject = self.req._('%s: %s status changed to %s')
+            name, (fromstate, tostate) = changes.items()[0]
+            subject %= (testconfig, name, self.req._(tostate))
+        else:
+            count = {}
+            for fromstate, tostate in entity.status_changes().values():
+                try:
+                    count[tostate] += 1
+                except KeyError:
+                    count[tostate] = 1
+            resume = ', '.join('%s %s' % (num, self.req._(state))
+                               for state, num in count.items())
+            subject = self.req._('%s now has %s') % (testconfig, resume)
+        return '[%s] %s' % (self.config.appid, subject)
 
     def context(self):
         entity = self.entity(0, 0)
@@ -44,13 +61,64 @@
         return ctx
 
 
-class ExecStatusChangeHook(Hook):
+class ExecStatusChangeHook(hooksmanager.Hook):
     events = ('after_update_entity',)
-    accepts = ('ApycotExecution',)
+    accepts = ('TestExecution',)
 
     def call(self, session, entity):
         # end of test execution : set endtime
         if 'endtime' in entity and entity.status_changes():
-            view = session.vreg.select_view('exstchange', session, entity.rset, row=0)
-            RenderAndSendNotificationView(session, view=view)
+            view = session.vreg.select_view('exstchange', session,
+                                            rset=entity.rset, row=0)
+            notification.RenderAndSendNotificationView(session, view=view)
+
+
+class StartTestAfterAddVersionContent(hooksmanager.Hook):
+    events = ('after_add_entity',)
+    accepts = ('VersionContent', 'DeletedVersionContent')
+
+    def call(self, session, entity):
+        if session.repo.config['auto-test-mode'] == 'no-test':
+            return
+        rql = ('Any TCN WHERE TC is TestConfig, TC name TCN, TC local_repository R, R eid %(r)s, '
+               '(TC vcs_path "" OR TC vcs_path NULL OR EXISTS(TC vcs_path P, VC content_for VF, VF directory ~= P + "%")), '
+               'VC eid %(vc)s, TC vcs_branch %(branch)s')
+        branch = entity.rev.branch
+        if branch and branch == entity.repository.default_branch():
+            # XXX this could be avoided by ensuring vcs_
+            rql += ' OR TC vcs_branch "" OR TC vcs_branch NULL'
+        matching_config = session.execute(rql,
+            {'r': entity.repository.eid, 'vc': entity.eid,
+             'branch': entity.rev.branch}, ('r', 'vc'))
+        if matching_config:
+            StartTestsOp(session, set(name for name, in matching_config))
+
 
+class StartTestsOp(pool.SingleLastOperation):
+    def __init__(self, session, tests):
+        self.tests = tests
+        super(StartTestsOp, self).__init__(session)
+
+    def register(self, session):
+        previous = super(StartTestsOp, self).register(session)
+        if previous:
+            self.tests |= previous.tests
+
+    def commit_event(self):
+        self.repo.threaded_task(self.start_tests)
+
+    def start_tests(self):
+        try:
+            bot = bot_proxy(self.config, self.session.transaction_data)
+        except Exception, ex:
+            self.error('cant contact apycot bot: %s', ex)
+            # XXX create a TestConfig to report the attempt to launch test
+            return
+        mode = self.session.repo.config['auto-test-mode']
+        for tcname in self.tests:
+            try:
+                bot.queue_task(tcname, mode=mode)
+            except Exception, ex:
+                self.error('cant start test %s: %s', tcname, ex)
+                # XXX create a TestConfig to report the attempt to launch test
+                return
--- a/i18n/en.po	Sun May 24 17:09:23 2009 +0200
+++ b/i18n/en.po	Tue Jun 16 16:07:29 2009 +0200
@@ -12,19 +12,19 @@
 # schema pot file, generated on 2009-01-14 12:58:20
 #
 # singular and plural forms for each entity type
-msgid "ApycotConfigGroup"
+msgid "TestConfigGroup"
 msgstr "Apycot configuration group"
 
-msgid "ApycotConfigGroup_plural"
+msgid "TestConfigGroup_plural"
 msgstr "Apycot configuration groups"
 
 # schema pot file, generated on 2008-12-23 10:18:45
 #
 # singular and plural forms for each entity type
-msgid "ApycotExecution"
+msgid "TestExecution"
 msgstr "Apycot execution"
 
-msgid "ApycotExecution_plural"
+msgid "TestExecution_plural"
 msgstr "Apycot executions"
 
 msgid "CheckResult"
@@ -68,10 +68,10 @@
 msgid "Latest test results"
 msgstr ""
 
-msgid "New ApycotConfigGroup"
+msgid "New TestConfigGroup"
 msgstr "New apycot config group"
 
-msgid "New ApycotExecution"
+msgid "New TestExecution"
 msgstr "xxx"
 
 msgid "New CheckResult"
@@ -83,22 +83,22 @@
 msgid "New CheckResultLog"
 msgstr "xxx"
 
-msgid "New ProjectApycotConfig"
+msgid "New TestConfig"
 msgstr "New apycot project configuration"
 
-msgid "ProjectApycotConfig"
+msgid "TestConfig"
 msgstr "Apycot configuration"
 
-msgid "ProjectApycotConfig_plural"
+msgid "TestConfig_plural"
 msgstr "Apycot configurations"
 
 msgid "Test executions summary"
 msgstr ""
 
-msgid "This ApycotConfigGroup"
+msgid "This TestConfigGroup"
 msgstr "This apycot configuration group"
 
-msgid "This ApycotExecution"
+msgid "This TestExecution"
 msgstr "This apycot execution"
 
 msgid "This CheckResult"
@@ -110,7 +110,7 @@
 msgid "This CheckResultLog"
 msgstr "This check result log"
 
-msgid "This ProjectApycotConfig"
+msgid "This TestConfig"
 msgstr "This apycot project configuration"
 
 msgid "WARNING"
@@ -128,35 +128,35 @@
 msgid "activated"
 msgstr ""
 
-msgid "add ApycotExecution using_config ProjectApycotConfig object"
+msgid "add TestExecution using_config TestConfig object"
 msgstr "xxx"
 
-msgid "add CheckResult during_execution ApycotExecution object"
+msgid "add CheckResult during_execution TestExecution object"
 msgstr "xxx"
 
-msgid "add CheckResultInfo for_check ApycotExecution object"
+msgid "add CheckResultInfo for_check TestExecution object"
 msgstr "xxx"
 
 msgid "add CheckResultInfo for_check CheckResult object"
 msgstr "xxx"
 
 # add related box generated message
-msgid "add CheckResultLog for_check ApycotExecution object"
+msgid "add CheckResultLog for_check TestExecution object"
 msgstr "xxx"
 
 msgid "add CheckResultLog for_check CheckResult object"
 msgstr "xxx"
 
-msgid "add Project has_apycot_config ProjectApycotConfig subject"
+msgid "add Project has_apycot_config TestConfig subject"
 msgstr "apycot configuration"
 
-msgid "add Version has_apycot_config ProjectApycotConfig subject"
+msgid "add Version has_apycot_config TestConfig subject"
 msgstr "apycot configuration"
 
-msgid "add a ApycotConfigGroup"
+msgid "add a TestConfigGroup"
 msgstr "add a apycot configuration group"
 
-msgid "add a ApycotExecution"
+msgid "add a TestExecution"
 msgstr "xxx"
 
 msgid "add a CheckResult"
@@ -168,7 +168,7 @@
 msgid "add a CheckResultLog"
 msgstr "xxx"
 
-msgid "add a ProjectApycotConfig"
+msgid "add a TestConfig"
 msgstr "add a apycot project configuration"
 
 msgid "apycot"
@@ -213,17 +213,17 @@
 msgstr "section displaying latest apycot execution results"
 
 msgid ""
-"creating ApycotExecution (ApycotExecution using_config ProjectApycotConfig %"
+"creating TestExecution (TestExecution using_config TestConfig %"
 "(linkto)s)"
 msgstr "xxx"
 
 msgid ""
-"creating CheckResult (CheckResult during_execution ApycotExecution %(linkto)"
+"creating CheckResult (CheckResult during_execution TestExecution %(linkto)"
 "s)"
 msgstr "xxx"
 
 msgid ""
-"creating CheckResultInfo (CheckResultInfo for_check ApycotExecution %(linkto)"
+"creating CheckResultInfo (CheckResultInfo for_check TestExecution %(linkto)"
 "s)"
 msgstr "xxx"
 
@@ -232,7 +232,7 @@
 msgstr "xxx"
 
 msgid ""
-"creating CheckResultLog (CheckResultLog for_check ApycotExecution %(linkto)s)"
+"creating CheckResultLog (CheckResultLog for_check TestExecution %(linkto)s)"
 msgstr "xxx"
 
 msgid ""
@@ -240,13 +240,13 @@
 msgstr "xxx"
 
 msgid ""
-"creating ProjectApycotConfig (Project %(linkto)s has_apycot_config "
-"ProjectApycotConfig)"
+"creating TestConfig (Project %(linkto)s has_apycot_config "
+"TestConfig)"
 msgstr "creating configuration for project %(linkto)s"
 
 msgid ""
-"creating ProjectApycotConfig (Version %(linkto)s has_apycot_config "
-"ProjectApycotConfig)"
+"creating TestConfig (Version %(linkto)s has_apycot_config "
+"TestConfig)"
 msgstr "creating configuration for version %(linkto)s"
 
 msgid "deactivate"
@@ -373,10 +373,10 @@
 msgid "relative path to the project into the repository"
 msgstr ""
 
-msgid "remove this ApycotConfigGroup"
+msgid "remove this TestConfigGroup"
 msgstr "remove this apycot configuration group"
 
-msgid "remove this ApycotExecution"
+msgid "remove this TestExecution"
 msgstr "remove this execution"
 
 msgid "remove this CheckResult"
@@ -388,7 +388,7 @@
 msgid "remove this CheckResultLog"
 msgstr "remove this log"
 
-msgid "remove this ProjectApycotConfig"
+msgid "remove this TestConfig"
 msgstr "remove this apycot project configuration"
 
 msgid "severity"
@@ -434,5 +434,5 @@
 msgid "vcs_repository_type"
 msgstr "vcs type"
 
-msgid "vcs_tag"
-msgstr "vcs tag"
+msgid "vcs_branch"
+msgstr "branch"
--- a/i18n/fr.po	Sun May 24 17:09:23 2009 +0200
+++ b/i18n/fr.po	Tue Jun 16 16:07:29 2009 +0200
@@ -18,19 +18,19 @@
 # schema pot file, generated on 2009-01-14 12:58:20
 #
 # singular and plural forms for each entity type
-msgid "ApycotConfigGroup"
+msgid "TestConfigGroup"
 msgstr "Groupe de configuration Apycot"
 
-msgid "ApycotConfigGroup_plural"
+msgid "TestConfigGroup_plural"
 msgstr "Groupes de configuration Apycot"
 
 # schema pot file, generated on 2008-12-23 10:18:45
 #
 # singular and plural forms for each entity type
-msgid "ApycotExecution"
+msgid "TestExecution"
 msgstr "Éxécution apycot"
 
-msgid "ApycotExecution_plural"
+msgid "TestExecution_plural"
 msgstr "Éxécutions apycot"
 
 msgid "CheckResult"
@@ -74,10 +74,10 @@
 msgid "Latest test results"
 msgstr "Derniers résultats d'éxécution des tests"
 
-msgid "New ApycotConfigGroup"
+msgid "New TestConfigGroup"
 msgstr "Nouveau groupe de configuration apycot"
 
-msgid "New ApycotExecution"
+msgid "New TestExecution"
 msgstr "Nouvelle éxécution"
 
 msgid "New CheckResult"
@@ -89,22 +89,22 @@
 msgid "New CheckResultLog"
 msgstr "Nouveau message de vérification"
 
-msgid "New ProjectApycotConfig"
+msgid "New TestConfig"
 msgstr "Nouvelle configuration apycot"
 
-msgid "ProjectApycotConfig"
+msgid "TestConfig"
 msgstr "Configuration apycot"
 
-msgid "ProjectApycotConfig_plural"
+msgid "TestConfig_plural"
 msgstr "Configurations apycot"
 
 msgid "Test executions summary"
 msgstr "Rapport d'éxécution des tests"
 
-msgid "This ApycotConfigGroup"
+msgid "This TestConfigGroup"
 msgstr "Ce groupe de configuration"
 
-msgid "This ApycotExecution"
+msgid "This TestExecution"
 msgstr "Cette éxecution"
 
 msgid "This CheckResult"
@@ -116,7 +116,7 @@
 msgid "This CheckResultLog"
 msgstr "Ce message"
 
-msgid "This ProjectApycotConfig"
+msgid "This TestConfig"
 msgstr "Cette configuration de projet"
 
 msgid "WARNING"
@@ -134,35 +134,35 @@
 msgid "activated"
 msgstr "activé"
 
-msgid "add ApycotExecution using_config ProjectApycotConfig object"
+msgid "add TestExecution using_config TestConfig object"
 msgstr "xxx"
 
-msgid "add CheckResult during_execution ApycotExecution object"
+msgid "add CheckResult during_execution TestExecution object"
 msgstr "xxx"
 
-msgid "add CheckResultInfo for_check ApycotExecution object"
+msgid "add CheckResultInfo for_check TestExecution object"
 msgstr "xxx"
 
 msgid "add CheckResultInfo for_check CheckResult object"
 msgstr "xxx"
 
 # add related box generated message
-msgid "add CheckResultLog for_check ApycotExecution object"
+msgid "add CheckResultLog for_check TestExecution object"
 msgstr "xxx"
 
 msgid "add CheckResultLog for_check CheckResult object"
 msgstr "xxx"
 
-msgid "add Project has_apycot_config ProjectApycotConfig subject"
+msgid "add Project has_apycot_config TestConfig subject"
 msgstr "configuration apycot"
 
-msgid "add Version has_apycot_config ProjectApycotConfig subject"
+msgid "add Version has_apycot_config TestConfig subject"
 msgstr "configuration apycot"
 
-msgid "add a ApycotConfigGroup"
+msgid "add a TestConfigGroup"
 msgstr "ajouter un groupe de configuration apycot"
 
-msgid "add a ApycotExecution"
+msgid "add a TestExecution"
 msgstr "xxx"
 
 msgid "add a CheckResult"
@@ -174,7 +174,7 @@
 msgid "add a CheckResultLog"
 msgstr "xxx"
 
-msgid "add a ProjectApycotConfig"
+msgid "add a TestConfig"
 msgstr "ajouter une configuration de projet apycot"
 
 msgid "apycot"
@@ -221,17 +221,17 @@
 msgstr "section affichant le dernier rapport d'éxécution des tests"
 
 msgid ""
-"creating ApycotExecution (ApycotExecution using_config ProjectApycotConfig %"
+"creating TestExecution (TestExecution using_config TestConfig %"
 "(linkto)s)"
 msgstr "xxx"
 
 msgid ""
-"creating CheckResult (CheckResult during_execution ApycotExecution %(linkto)"
+"creating CheckResult (CheckResult during_execution TestExecution %(linkto)"
 "s)"
 msgstr "xxx"
 
 msgid ""
-"creating CheckResultInfo (CheckResultInfo for_check ApycotExecution %(linkto)"
+"creating CheckResultInfo (CheckResultInfo for_check TestExecution %(linkto)"
 "s)"
 msgstr "xxx"
 
@@ -240,7 +240,7 @@
 msgstr "xxx"
 
 msgid ""
-"creating CheckResultLog (CheckResultLog for_check ApycotExecution %(linkto)s)"
+"creating CheckResultLog (CheckResultLog for_check TestExecution %(linkto)s)"
 msgstr "xxx"
 
 msgid ""
@@ -248,13 +248,13 @@
 msgstr "xxx"
 
 msgid ""
-"creating ProjectApycotConfig (Project %(linkto)s has_apycot_config "
-"ProjectApycotConfig)"
+"creating TestConfig (Project %(linkto)s has_apycot_config "
+"TestConfig)"
 msgstr "configuration apycot pour le projet %(linkto)s"
 
 msgid ""
-"creating ProjectApycotConfig (Version %(linkto)s has_apycot_config "
-"ProjectApycotConfig)"
+"creating TestConfig (Version %(linkto)s has_apycot_config "
+"TestConfig)"
 msgstr "configuration apycot pour la version %(linkto)s"
 
 msgid "deactivate"
@@ -389,10 +389,10 @@
 msgid "relative path to the project into the repository"
 msgstr "chemin relatif vers le projet dans l'entrepot"
 
-msgid "remove this ApycotConfigGroup"
+msgid "remove this TestConfigGroup"
 msgstr "supprimer ce groupe"
 
-msgid "remove this ApycotExecution"
+msgid "remove this TestExecution"
 msgstr "xxx"
 
 msgid "remove this CheckResult"
@@ -404,7 +404,7 @@
 msgid "remove this CheckResultLog"
 msgstr "xxx"
 
-msgid "remove this ProjectApycotConfig"
+msgid "remove this TestConfig"
 msgstr "supprimer cette  configuration"
 
 msgid "severity"
@@ -438,9 +438,9 @@
 msgid "using_config_object"
 msgstr "éxécutions"
 
-msgid "vcs tag or branch to use for test's checkout"
+msgid "vcs branch to use for test's checkout"
 msgstr ""
-"étiquette ou branche à utiliser lors de l'extraction du source pour les tests"
+"branche à utiliser lors de l'extraction du source pour les tests"
 
 msgid "vcs_path"
 msgstr "chemin relatif dans le système de gestion de source"
@@ -451,8 +451,8 @@
 msgid "vcs_repository_type"
 msgstr "type système de gestion de source"
 
-msgid "vcs_tag"
-msgstr "étiquette"
+msgid "vcs_branch"
+msgstr "branche"
 
 #~ msgid "label"
 #~ msgstr "libellé"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/migration/1.2.0_Any.py	Tue Jun 16 16:07:29 2009 +0200
@@ -0,0 +1,50 @@
+# schema changes ###############################################################
+
+rename_entity_type('ApycotConfigGroup', 'TestConfigGroup')
+rename_entity_type('ProjectApycotConfig', 'TestConfig')
+rename_entity_type('ApycotExecution', 'TestExecution')
+rename_attribute('TestConfig', 'vcs_tag', 'vcs_branch')
+rename_attribute('TestConfigGroup', 'vcs_tag', 'vcs_branch')
+add_relation_type('local_repository')
+add_relation_type('using_revision')
+add_relation_type('test_needs_checkout')
+add_relation_type('quick_checks')
+
+add_cube('vcsfile')
+
+# renamed preprocessors and checkers ###########################################
+
+CHK_PP_MAP = {'debian_lint': 'lintian',
+              'debian_deb_lint': 'lintian_deb',
+              'debian_piuparts': 'piuparts',
+              'debian_dpkgdeb': 'dpkg-deb',
+              'python_unittest':  'pyunit',
+              'python_pytest': 'py.test',
+              'python_lint':  'pylint',
+              'python_test_coverage': 'pycoverage',
+              'python_check': 'pychecker',
+              'set_python_env': 'python_setenv',
+              'setup_install': 'python_setup',
+              }
+def update_field(string):
+    if string is None:
+        return string
+    for old, new in CHK_PP_MAP.items():
+        string = string.replace(old, new)
+    return string
+
+for testconfig in rql('Any X,XC,XCP WHERE X checks XC, X check_preprocessors XCP').entities():
+    checks = testconfig.checks
+    newchecks = update_field(checks)
+    cpp = testconfig.check_preprocessors
+    newcpp = update_field(cpp)
+    if newchecks != checks or newcpp != cpp:
+        testconfig.set_attributes(checks=newchecks, check_preprocessors=newcpp)
+checkpoint()
+
+# new expectation for vcs_branch / vcs_path ####################################
+
+rql('SET X vcs_branch NULL WHERE X vcs_repository_type "svn", X vcs_branch "HEAD"')
+rql('SET X vcs_path NULL WHERE X vcs_path P, X vcs_repository ~= "%/"+P')
+rql('SET X quick_checks "pyunit" WHERE X checks ~= "%pyunit%"')
+checkpoint()
--- a/migration/postcreate.py	Sun May 24 17:09:23 2009 +0200
+++ b/migration/postcreate.py	Tue Jun 16 16:07:29 2009 +0200
@@ -1,10 +1,20 @@
 # postcreate script. You could setup a workflow here for example
 
-activatedeid = add_state(_('activated'), 'ProjectApycotConfig', initial=True)
-deactivatedeid = add_state(_('deactivated'), 'ProjectApycotConfig')
-add_transition(_('deactivate'), 'ProjectApycotConfig',
+activatedeid = add_state(_('activated'), 'TestConfig', initial=True)
+deactivatedeid = add_state(_('deactivated'), 'TestConfig')
+add_transition(_('deactivate'), 'TestConfig',
                (activatedeid,), deactivatedeid,
                requiredgroups=('managers',))
-add_transition(_('activate'), 'ProjectApycotConfig',
+add_transition(_('activate'), 'TestConfig',
                (deactivatedeid,), activatedeid,
                requiredgroups=('managers',))
+
+
+add_entity('Bookmark', title=_('quick tests summary'),
+           path=u'view?rql=Any+X%2CXN+ORDERBY+XN+WHERE+X+is+TestConfig%2C+X+name+XN%2C+X+in_state+S%2C+S+name+%22activated%22&vid=summary')
+
+
+if not config['pyro-server']:
+    config.global_set_option('pyro-server', True)
+    config.save()
+
--- a/schema/apycot.py	Sun May 24 17:09:23 2009 +0200
+++ b/schema/apycot.py	Tue Jun 16 16:07:29 2009 +0200
@@ -1,7 +1,7 @@
 """apycot cube's specific schema
 
 :organization: Logilab
-:copyright: 2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2008-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
@@ -12,11 +12,13 @@
     class has_apycot_config(RelationDefinition):
         permissions = {
             'read':   ('managers', 'users', 'guests'),
-            'add':    ('managers', 'staff', RRQLExpression('U has_update_permission S', 'S'),),
-            'delete': ('managers', 'staff', RRQLExpression('U has_update_permission S', 'S'),),
+            'add':    ('managers', 'staff',
+                       RRQLExpression('U has_update_permission S', 'S'),),
+            'delete': ('managers', 'staff',
+                       RRQLExpression('U has_update_permission S', 'S'),),
             }
         subject = ('Project', 'Version')
-        object = 'ProjectApycotConfig'
+        object = 'TestConfig'
         cardinality = '*?'
         composite = 'subject'
 
@@ -26,8 +28,11 @@
 
     CONF_WRITE_GROUPS = ('managers', )
 
+Repository = import_erschema('Repository')
+Repository.permissions['read'] += ('apycot',)
 
-class ApycotConfigGroup(EntityType):
+
+class TestConfigGroup(EntityType):
     """regroup some common configuration used by multiple projects"""
     permissions = {
         'read':   ('managers', 'users', 'guests', 'apycot'),
@@ -36,27 +41,64 @@
         'delete': CONF_WRITE_GROUPS,
         }
 
-    name = String(required=True, unique=True, maxsize=128,
-                   description=_('name for this configuration'))
-    vcs_repository_type = String(vocabulary=(u'hg', u'svn', u'cvs', u'fs', u'null'),
-                                 description=_('kind of version control system (vcs): mercurial, subversion, cvs, file system (eg no version control)'))
-    vcs_repository = String(description=_('path or url to the vcs repository containing the project'))
-    vcs_path = String(description=_('relative path to the project into the repository'))
-    vcs_tag  = String(description=_('vcs tag or branch to use for test\'s checkout'), maxsize=200)
+    name = String(
+        required=True, unique=True, maxsize=128,
+        description=_('name for this configuration')
+        )
+    vcs_repository_type = String(
+        vocabulary=(u'hg', u'svn', u'cvs', u'fs', u'null'),
+        description=_('kind of version control system (vcs): hg (mercurial), '
+                      'svn (subversion), cvs (CVS), fs (file system, eg no '
+                      'version control), null (virtual dependency)')
+        )
+    vcs_repository = String(
+        description=_('path or url to the vcs repository containing the project')
+        )
+    vcs_path = String(
+        description=_('relative path to the project into the repository')
+        )
+    vcs_branch  = String(
+        description=_('branch to use for test\'s checkout. In case of '
+                      'subversion repository, this should be the relative path '
+                      'of the branch into the repository (vcs_path won\'t be '
+                      'considered then).'),
+        maxsize=256
+        )
+    checks = String(
+        description=_('comma separated list of checks to execute full tests '
+                      'for this project'),
+        fulltextindexed=True
+        )
+    quick_checks = String(
+        description=_('comma separated list of checks to execute quick tests '
+                      'for this project'),
+        fulltextindexed=True
+        )
+    check_preprocessors = String(
+        description=_('preprocessors to use for this project for install, '
+                      'debian, build_doc... (one per line)'),
+        fulltextindexed=True
+        )
+    check_environment = String(
+        description=_('environment variables to be set in the check process '
+                      'environment (one per line)'),
+        fulltextindexed=True
+        )
+    check_config = String(
+        description=_('preprocessor/checker options (one per line)'),
+        fulltextindexed=True
+        )
 
-    checks = String(description=_('comma separated list of checks to execute by default for this project'),
-                                 fulltextindexed=True)
-    check_preprocessors = String(description=_('preprocessors to use for this project for install, debian, build_doc... (one per line)'),
-                                 fulltextindexed=True)
-    check_environment = String(description=_('environment variables to be set in the check process environment (one per line)'),
-                                 fulltextindexed=True)
-    check_config = String(description=_('preprocessor/checker options (one per line)'),
-                                 fulltextindexed=True)
-
-    use_group = SubjectRelation('ApycotConfigGroup')#, constraints=[RQLConstraint('NOT S identity O')])
+    local_repository = SubjectRelation(
+        'Repository',
+        description=_('link to a vcsfile repository, may be used to replace '
+                      'vcs_repository_type / vcs_repository to have deeper '
+                      'integration.')
+        )
+    use_group = SubjectRelation('TestConfigGroup')#, constraints=[RQLConstraint('NOT S identity O')])
 
 
-class ProjectApycotConfig(ApycotConfigGroup):
+class TestConfig(TestConfigGroup):
     """apycot configuration to register a project branch to test"""
     permissions = {
         'read':   ('managers', 'users', 'guests', 'apycot'),
@@ -64,10 +106,17 @@
         'update': CONF_WRITE_GROUPS,
         'delete': CONF_WRITE_GROUPS,
         }
+    needs_checkout = SubjectRelation(
+        'TestConfig',
+        description=_('project\'s dependencies that should be installed from '
+                      'their repository during checks requiring installation'),
+        )#constraints=[RQLConstraint('NOT S identity O')])
+    test_needs_checkout = SubjectRelation(
+        'TestConfig',
+        description=_('project\'s dependencies that should be installed from '
+                      'their repository to execute test on this configuration'),
+        )#constraints=[RQLConstraint('NOT S identity O')])
 
-    needs_checkout = SubjectRelation('ProjectApycotConfig',
-                                     description=_('project\'s dependencies that should be installed from their repository during checks requiring installation'),
-                                     )#constraints=[RQLConstraint('NOT S identity O')])
     in_state = SubjectRelation('State', cardinality='1*',
                                constraints=[RQLConstraint('O state_of ET, S is ET')],
                                description=_('automatic test status'))
@@ -81,12 +130,19 @@
         'delete': ('managers',),
         }
 
-class ApycotExecution(EntityType):
+
+class TestExecution(EntityType):
     permissions = BOT_ENTITY_PERMS
 
     starttime = Datetime(required=True)
     endtime   = Datetime()
-    using_config = SubjectRelation('ProjectApycotConfig', cardinality='1*', composite='object')
+    using_config = SubjectRelation('TestConfig', cardinality='1*', composite='object')
+    using_revision = SubjectRelation(
+        'Revision',
+        description=_('link to revision which has been used in the test '
+                      'environment for configurations which are linked to a '
+                      'repository,'))
+
 
 class CheckResult(EntityType):
     """group results of execution of a specific test on a project"""
@@ -99,7 +155,7 @@
                                    _('processing')))
     starttime = Datetime()
     endtime   = Datetime()
-    during_execution = SubjectRelation('ApycotExecution', cardinality='1*', composite='object')
+    during_execution = SubjectRelation('TestExecution', cardinality='1*', composite='object')
 
 
 class CheckResultLog(EntityType):
@@ -113,7 +169,8 @@
                                   _('ERROR'), _('FATAL')))
     msg      = String()
 
-    for_check = SubjectRelation(('CheckResult', 'ApycotExecution'), cardinality='1*', composite='object')
+    for_check = SubjectRelation(('CheckResult', 'TestExecution'), cardinality='1*', composite='object')
+
 
 class CheckResultInfo(EntityType):
     """arbitrary information about execution of a specific test on a project"""
@@ -123,7 +180,7 @@
     label = String(required=True, internationalizable=True)
     value = String(required=True, internationalizable=True)
 
-    for_check = SubjectRelation(('CheckResult', 'ApycotExecution'), cardinality='1*', composite='object')
+    for_check = SubjectRelation(('CheckResult', 'TestExecution'), cardinality='1*', composite='object')
 
 
 class use_group(RelationType):
@@ -145,3 +202,6 @@
     permissions = BOT_ENTITY_PERMS
     inlined = True
 
+class using_revision(RelationType):
+    permissions = BOT_ENTITY_PERMS
+
--- a/site_cubicweb.py	Sun May 24 17:09:23 2009 +0200
+++ b/site_cubicweb.py	Tue Jun 16 16:07:29 2009 +0200
@@ -10,7 +10,7 @@
     register_function(severity_sort_value)
 except AssertionError:
     pass
-    
+
 
 
 try:
@@ -24,6 +24,32 @@
             return {"DEBUG": 0, "INFO": 10, "WARNING": 20,
                     "ERROR": 30, "FATAL": 40}[text]
         cnx.create_function("SEVERITY_SORT_VALUE", 1, severity_sort_value)
-        
+
     sqlite_hooks = SQL_CONNECT_HOOKS.setdefault('sqlite', [])
     sqlite_hooks.append(init_sqlite_connexion)
+
+
+options = (
+    ('bot-pyro-id',
+     {'type' : 'string',
+      'default' : ':apycot.apycotbot',
+      'help': _('Identifier of the apycot bot in the pyro name-server.'),
+      'group': 'apycot', 'inputlevel': 1,
+      }),
+    ('bot-pyro-ns',
+     {'type' : 'string',
+      'default' : None,
+      'help': _('Pyro name server\'s host where the bot is registered. If not '
+                'set, will be detected by a broadcast query. You can also '
+                'specify a port using <host>:<port> notation.'),
+      'group': 'apycot', 'inputlevel': 1,
+      }),
+    ('auto-test-mode',
+     {'type' : 'choice', 'choices' : ('full', 'quick', 'no-test'),
+      'default' : 'quick',
+      'help': _('Mode to be used when new revision are detected in a repository'
+                ' linked to a test configuration. if no-test, nothing will be '
+                'done, else full or quick tests will be launched.'),
+      'group': 'apycot', 'inputlevel': 1,
+      }),
+    )
--- a/test/test_apycot.py	Sun May 24 17:09:23 2009 +0200
+++ b/test/test_apycot.py	Tue Jun 16 16:07:29 2009 +0200
@@ -4,23 +4,25 @@
 
 from cubicweb.devtools.testlib import AutomaticWebTest
 
-class ApycotAutomaticWebTest(AutomaticWebTest):
-    
+class AutomaticWebTest(AutomaticWebTest):
+    no_auto_populate = ('Repository', 'Revision', 'VersionedFile',
+                        'VersionContent', 'DeletedVersionContent',)
+    ignored_relations = ('at_revision', 'parent_revision',
+                         'from_repository', 'from_revision', 'content_for',)
+
     def setUp(self):
-        super(ApycotAutomaticWebTest, self).setUp()
-        for etype in ('ApycotExecution', 'CheckResult',
+        super(AutomaticWebTest, self).setUp()
+        for etype in ('TestExecution', 'CheckResult',
                       'CheckResultLog', 'CheckResultInfo',
                       'using_config', 'during_execution', 'for_check'):
             self.schema[etype].set_groups('add', ('managers',))
-            
+
     def to_test_etypes(self):
-        return set(('ProjectApycotConfig', 'ApycotConfigGroup',
-                    'ApycotExecution', 'CheckResult', 'CheckResultLog', 'CheckResultInfo'))
-    
+        return set(('TestConfig', 'TestConfigGroup',
+                    'TestExecution', 'CheckResult', 'CheckResultLog', 'CheckResultInfo'))
+
     def list_startup_views(self):
         return ()
-    
-del AutomaticWebTest
 
 if __name__ == '__main__':
     unittest_main()
--- a/test/unittest_entities.py	Sun May 24 17:09:23 2009 +0200
+++ b/test/unittest_entities.py	Tue Jun 16 16:07:29 2009 +0200
@@ -6,21 +6,21 @@
 
     def setup_database(self):
         ApycotBaseTC.setup_database(self)
-        self.add_entity('ProjectApycotConfig', name=u'lgd')
-        self.execute('SET X use_group Y WHERE X name "lgd", Y is ApycotConfigGroup')
-                        
+        self.add_entity('TestConfig', name=u'lgd')
+        self.execute('SET X use_group Y WHERE X name "lgd", Y is TestConfigGroup')
+
     def test_use_group_base(self):
-        lgd = self.execute('ProjectApycotConfig X WHERE X name "lgd"').get_entity(0, 0)
-        self.assertEquals(lgd.all_checks,
+        lgd = self.execute('TestConfig X WHERE X name "lgd"').get_entity(0, 0)
+        self.assertEquals(lgd.all_checks(),
                           'python_pkg,pkg_doc,python_syntax,python_lint,python_unittest,python_test_coverage'.split(','))
         self.assertEquals(lgd.raw_preprocessors, {'install': 'setup_install'})
         self.assertEquals(lgd.testconfig, {'python_lint_treshold': '7',
                                            'python_lint_ignore': 'thirdparty',
                                            'python_test_coverage_treshold': '70'})
-        
+
     def test_use_group_override(self):
-        lgc = self.execute('ProjectApycotConfig X WHERE X name "lgc"').get_entity(0, 0)
-        self.assertEquals(lgc.all_checks,
+        lgc = self.execute('TestConfig X WHERE X name "lgc"').get_entity(0, 0)
+        self.assertEquals(lgc.all_checks(),
                           'python_lint,python_unittest,python_test_coverage'.split(','))
         self.assertEquals(lgc.raw_preprocessors, {'install': 'setup_install', 'build_doc': 'make'})
         self.assertEquals(lgc.testconfig, {'python_lint_treshold': '8',
@@ -47,6 +47,6 @@
         self.restore_connection()
         self.assertEquals([cr.eid for cr in self.lgc.all_check_results()],
                           [covcr, ucr])
-    
+
 if __name__ == '__main__':
     unittest_main()
--- a/test/unittest_hooks.py	Sun May 24 17:09:23 2009 +0200
+++ b/test/unittest_hooks.py	Tue Jun 16 16:07:29 2009 +0200
@@ -8,7 +8,7 @@
     return re.sub('[0-9]', 'X', string.strip())
 
 class NotificationTC(ApycotBaseTC):
-                        
+
     def test_exec_status_change(self):
         self.login('apycotbot', 'apycot')
         self.add_execution('lgc', [('unittest', 'success'), ('coverage', 'success')])
@@ -24,16 +24,33 @@
         self.commit()
         self.assertEquals(len(MAILBOX), 1)
         self.assertEquals(MAILBOX[0].message.get('Subject'),
-                          '[APyCoT] status changes during checks execution for lgc')
+                          '[data] lgc now has 2 failure')
         self.assertTextEquals(clean_str(MAILBOX[0].message.get_payload(decode=True)),
                               '''The following changes occured between executions started at XXXX/XX/XX XX:XX and XXXX/XX/XX XX:XX:
 
 * coverage status changed from success to failure
 * unittest status changed from success to failure
 
-URL: http://testing.fr/cubicweb/apycotexecution/XXX''')
-        
+URL: http://testing.fr/cubicweb/testexecution/XXX''')
+
 
-    
+    def test_exec_one_status_change(self):
+        self.login('apycotbot', 'apycot')
+        self.add_execution('lgc', [('unittest', 'success'), ('coverage', 'success')])
+        self.commit()
+        self.add_execution('lgc', [('unittest', 'failure')])
+        self.commit()
+        self.assertEquals(len(MAILBOX), 1)
+        self.assertEquals(MAILBOX[0].message.get('Subject'),
+                          '[data] lgc: unittest status changed to failure')
+        self.assertTextEquals(clean_str(MAILBOX[0].message.get_payload(decode=True)),
+                              '''The following changes occured between executions started at XXXX/XX/XX XX:XX and XXXX/XX/XX XX:XX:
+
+* unittest status changed from success to failure
+
+URL: http://testing.fr/cubicweb/testexecution/XXX''')
+
+
+
 if __name__ == '__main__':
     unittest_main()
--- a/test/utils.py	Sun May 24 17:09:23 2009 +0200
+++ b/test/utils.py	Tue Jun 16 16:07:29 2009 +0200
@@ -3,28 +3,28 @@
 
 class ApycotBaseTC(EnvBasedTC):
     def setup_database(self):
-        self.add_entity('ApycotConfigGroup', name=u'PYTHONPACKAGE',
+        self.add_entity('TestConfigGroup', name=u'PYTHONPACKAGE',
                         checks=u'python_pkg,pkg_doc,python_syntax,python_lint,python_unittest,python_test_coverage',
                         check_preprocessors=u'install=setup_install',
                         check_config=u'''python_lint_treshold=7
 python_lint_ignore=thirdparty
 python_test_coverage_treshold=70
 ''')
-        self.lgc = self.add_entity('ProjectApycotConfig', name=u'lgc',
+        self.lgc = self.add_entity('TestConfig', name=u'lgc',
                                    vcs_repository_type=u'hg',
                                    checks=u'python_lint,python_unittest,python_test_coverage',
                                    check_preprocessors=u'build_doc=make',
                                    check_config=u'''python_lint_treshold=8
 pouet=5''')
-        self.execute('SET X use_group Y WHERE X is ProjectApycotConfig, Y is ApycotConfigGroup')
+        self.execute('SET X use_group Y WHERE X is TestConfig, Y is TestConfigGroup')
 
     def add_execution(self, confname, check_defs, setend=True):
-        ex = self.add_entity('ApycotExecution', starttime=datetime.now())
+        ex = self.add_entity('TestExecution', starttime=datetime.now())
         self.execute('SET X using_config Y WHERE X eid %(x)s, Y name %(confname)s',
                      {'x': ex.eid, 'confname': confname}, 'x')
         for name, status in check_defs:
             cr = self.add_entity('CheckResult', name=unicode(name), status=unicode(status))
-            self.execute('SET X during_execution Y WHERE X eid %(x)s, Y is ApycotExecution',
+            self.execute('SET X during_execution Y WHERE X eid %(x)s, Y is TestExecution',
                          {'x': cr.eid}, 'x')
         if setend:
             self.execute('SET X endtime %(et)s WHERE X eid %(x)s',
--- a/views/__init__.py	Sun May 24 17:09:23 2009 +0200
+++ b/views/__init__.py	Tue Jun 16 16:07:29 2009 +0200
@@ -1,10 +1,85 @@
 '''apycot reports'''
 
-from cubicweb.web import uicfg
+from cubicweb.web import uicfg, formwidgets as wdgs
+from cubicweb.web.views import urlrewrite
 
-uicfg.autoform_section.tag_subject_of(('*', 'use_group', '*'), 'primary')
+from cubes.apycot.entities import bot_proxy
+
 
 def anchor_name(data):
     """escapes XML/HTML forbidden characters in attributes and PCDATA"""
     return (data.replace('&', '').replace('<', '').replace('>','')
             .replace('"', '').replace("'", ''))
+
+# ui configuration #############################################################
+
+_pvs = uicfg.primaryview_section
+_pvs.tag_subject_of(('Project', 'has_apycot_config', '*'), 'hidden')
+_pvs.tag_subject_of(('*', 'use_group', '*'), 'attributes')
+_pvs.tag_object_of(('*', 'has_apycot_config', '*'), 'sideboxes')
+_pvs.tag_subject_of(('*', 'needs_checkout', '*'), 'sideboxes')
+_pvs.tag_subject_of(('*', 'test_needs_checkout', '*'), 'sideboxes')
+_pvs.tag_object_of(('*', 'needs_checkout', '*'), 'sideboxes')
+_pvs.tag_object_of(('*', 'test_needs_checkout', '*'), 'sideboxes')
+_pvs.tag_object_of(('*', 'using_config', '*'), 'relations')
+_pvs.tag_attribute(('CheckResult', 'name'), 'hidden')
+_pvs.tag_attribute(('CheckResult', 'status'), 'hidden')
+_pvs.tag_object_of(('*', 'for_check', '*'), 'hidden')
+_pvs.tag_object_of(('*', 'during_execution', '*'), 'hidden')
+
+
+_afs = uicfg.autoform_section
+_afs.tag_subject_of(('*', 'use_group', '*'), 'primary')
+
+
+# register generated message id
+_('Available checkers:')
+_('Available preprocessors:')
+_('Available options:')
+
+def build_help_func(attr, apycot_type):
+    def help_func(form, attr=attr, apycot_type=apycot_type):
+        req = form.req
+        help = form.schema.eschema('TestConfigGroup').rproperty(attr, 'description')
+        help = '<div>%s.</div>' % req._(help)
+        try:
+            bot = bot_proxy(form.config, req.data)
+        except Exception, ex:
+            form.warning('cant contact apycot bot: %s', ex)
+            return help
+        method = getattr(bot, 'available_%s' % apycot_type)
+        available = ', '.join(defdict.get('id', defdict.get('name'))
+                              for defdict in method())
+        help += '<div>%s %s (<a href="%s">%s</a>)</div>' % (
+            req.__('Available %s:' % apycot_type), available,
+            req.build_url('apycotdoc#%s' % apycot_type),
+            req._('more information')
+            )
+        return help
+    return help_func
+
+_affk = uicfg.autoform_field_kwargs
+for attr, apycot_type in (('check_preprocessors', 'preprocessors'),
+                          ('check_config', 'options')):
+    helpfunc = build_help_func(attr, apycot_type)
+    _affk.tag_attribute(('*', attr), {'help': helpfunc})
+for attr, apycot_type in (('quick_checks', 'checkers'),
+                          ('checks', 'checkers')):
+    helpfunc = build_help_func(attr, apycot_type)
+    _affk.tag_attribute(('*', attr), {'help': helpfunc,
+                                      'widget': wdgs.TextInput({'size': 100})})
+
+_affk.tag_attribute(('*', 'vcs_repository'), {'widget': wdgs.TextInput})
+_affk.tag_attribute(('*', 'vcs_path'), {'widget': wdgs.TextInput})
+
+
+_abba = uicfg.actionbox_appearsin_addmenu
+_abba.tag_subject_of(('*', 'has_apycot_config', 'TestConfig'), True)
+
+
+# urls configuration ###########################################################
+
+class SimpleReqRewriter(urlrewrite.SimpleReqRewriter):
+    rules = [
+        (urlrewrite.rgx('/apycotdoc'), dict(vid='apycotdoc')),
+        ]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/actions.py	Tue Jun 16 16:07:29 2009 +0200
@@ -0,0 +1,43 @@
+from cubicweb.selectors import implements, match_user_groups
+from cubicweb.view import EntityView
+from cubicweb.web import Redirect, action
+
+from cubes.apycot.entities import bot_proxy
+
+
+class StartQuickTest(action.Action):
+    mode = 'quick'
+    id = 'starttest' + mode
+    __select__ = match_user_groups('managers', 'staff') & implements('TestConfig')
+    title = id + '_action'
+    order = 10
+    def url(self):
+        entity = self.entity(self.row or 0, self.col or 0)
+        return entity.absolute_url(vid='starttest', mode=self.mode)
+
+
+class StartFullTest(StartQuickTest):
+    mode = 'full'
+    id = 'starttest' + mode
+    title = id + '_action'
+
+
+class StartTestView(EntityView):
+    id = 'starttest'
+    __select__ = match_user_groups('managers', 'staff') & implements('TestConfig')
+
+    def call(self):
+        for i in xrange(self.rset.rowcount):
+            self.cell_call(i, 0)
+        msg = self.req._('test(s) queued')
+        try:
+            url = self.build_url(self.req.form['__redirectpath'],
+                                 __message=msg)
+        except KeyError:
+            url = self.entity(i, 0).absolute_url(__message=msg)
+        raise Redirect(url)
+
+    def cell_call(self, row, col):
+        mode = self.req.form.get('mode', 'full')
+        bot = bot_proxy(self.config, self.req.data)
+        bot.queue_task(self.entity(row, col).name, mode=mode)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/help.py	Tue Jun 16 16:07:29 2009 +0200
@@ -0,0 +1,36 @@
+from logilab.mtconverter import html_escape
+from cubicweb.view import StartupView
+from cubes.apycot.entities import bot_proxy
+
+class ApycotHelpView(StartupView):
+    id = 'apycotdoc'
+
+    def page_title(self):
+        return self.req._('apycot documentation')
+
+    def call(self):
+        self.w(u'<h1>%s</h1>' % self.page_title())
+        bot = bot_proxy(self.config, self.req.data)
+        self.w(u'<p>%s</p>' % self.req._(
+            'First notice that you may miss some information if you\'re using '
+            'some plugin not loaded by the apycot bot.'))
+        self.section('checkers')
+        headers = [_('checker'), _('need preprocessor'), _('description')]
+        data = [(defdict['id'], defdict['preprocessor'], html_escape(defdict['help']))
+                 for defdict in bot.available_checkers()]
+        self.wview('pyvaltable', pyvalue=data, headers=headers)
+        self.section('preprocessors')
+        headers = [_('preprocessor'), _('description')]
+        data = [(defdict['id'], html_escape(defdict['help']))
+                 for defdict in bot.available_preprocessors()]
+        self.wview('pyvaltable', pyvalue=data, headers=headers)
+        self.section('options')
+        headers = [_('option'), _('required'), _('type'), _('description')]
+        data = [(defdict.get('id', defdict['name']),
+                 defdict.get('required') and _('yes') or _('no'),
+                 defdict['type'], html_escape(defdict['help']))
+                 for defdict in bot.available_options()]
+        self.wview('pyvaltable', pyvalue=data, headers=headers)
+
+    def section(self, name):
+        self.w(u'<h2>%s</h2><a name="%s"/>' % (self.req._(name), name))
--- a/views/jpl.py	Sun May 24 17:09:23 2009 +0200
+++ b/views/jpl.py	Tue Jun 16 16:07:29 2009 +0200
@@ -29,17 +29,27 @@
             return
         self.wview('summary', configsrset, title=self.req._(self.title))
 
+
 class ProjectAddApycotConfig(LinkToEntityAction):
     id = 'addapycotconf'
     __select__ = (LinkToEntityAction.__select__ & implements('Project')
                   & rql_condition('X in_state S, NOT S name "moved"'))
 
-    etype = 'ProjectApycotConfig'
+    etype = 'TestConfig'
     rtype = 'has_apycot_config'
     target = 'object'
-    title = _('add Project has_apycot_config ProjectApycotConfig subject')
+    title = _('add Project has_apycot_config TestConfig subject')
     order = 130
 
+
 class VersionAddApycotConfig(ProjectAddApycotConfig):
     __select__ = implements('Version')
-    title = _('add Version has_apycot_config ProjectApycotConfig subject')
+    title = _('add Version has_apycot_config TestConfig subject')
+
+
+try:
+    from cubes.jpl.views.sections import ProjectMainTab
+except ImportError:
+    pass
+else:
+    ProjectMainTab.attribute_relations.append(('has_apycot_config', 'subject'))
--- a/views/primary.py	Sun May 24 17:09:23 2009 +0200
+++ b/views/primary.py	Tue Jun 16 16:07:29 2009 +0200
@@ -9,25 +9,11 @@
 
 from logilab.mtconverter import html_escape
 
-
 from cubicweb.selectors import implements
-from cubicweb.web import uicfg, formwidgets, action
-from cubicweb.web.views import primary, baseviews
+from cubicweb.web.views import primary
 
 from cubes.apycot.views import anchor_name
 
-uicfg.primaryview_section.tag_attribute(('CheckResult', 'name'), 'hidden')
-uicfg.primaryview_section.tag_attribute(('CheckResult', 'status'), 'hidden')
-
-uicfg.autoform_field_kwargs.tag_attribute(('*', 'vcs_repository'),
-                                          {'widget': formwidgets.TextInput})
-uicfg.autoform_field_kwargs.tag_attribute(('*', 'vcs_path'),
-                                          {'widget': formwidgets.TextInput})
-uicfg.autoform_field_kwargs.tag_attribute(('*', 'checks'),
-                                          {'widget': formwidgets.TextInput})
-
-uicfg.actionbox_appearsin_addmenu.tag_subject_of(('*', 'has_apycot_config', 'ProjectApycotConfig'), True)
-
 
 class InfoLogMixin(object):
 
@@ -51,16 +37,26 @@
                    divid='logs%s'%entity.eid)
 
 
-class ApycotExecutionPrimaryView(InfoLogMixin, primary.PrimaryView):
-    __select__ = implements('ApycotExecution')
+class TestExecutionPrimaryView(InfoLogMixin, primary.PrimaryView):
+    __select__ = implements('TestExecution')
 
     def render_entity_title(self, entity):
         title = self.req._('Execution for config %(config)s') % {
             'config': entity.apycot_config.view('incontext')}
         self.w('<h1>%s</h1>' % title)
 
+    def display_version_configuration(self, entity):
+        title = self.req._('version configuration')
+        rset = self.req.execute(
+            'Any TC, REV, B ORDERBY TN '
+            'WHERE TC name TN, TC eid %(tc)s, '
+            'TE using_revision REV, TE eid %(te)s, REV branch B',
+            {'tc': entity.apycot_config.eid, 'te': entity.eid}, ('tc', 'te'))
+        self.wview('table', rset, 'null', title=title, divid='vc%s'%entity.eid)
+
     def render_entity_relations(self, entity):
         self.req.add_css('cubes.apycot.css')
+        self.display_version_configuration(entity)
         self.display_info_section(entity)
         self.display_log_section(entity)
         if entity.reverse_during_execution:
--- a/views/reports.py	Sun May 24 17:09:23 2009 +0200
+++ b/views/reports.py	Tue Jun 16 16:07:29 2009 +0200
@@ -18,8 +18,8 @@
 from cubes.apycot.views import anchor_name
 
 
-class ApycotExecutionSummary(EntityView):
-    __select__ = implements('ApycotExecution',)
+class TestExecutionSummary(EntityView):
+    __select__ = implements('TestExecution',)
     id = 'summary'
 
     def cell_call(self, row, col):
@@ -35,8 +35,8 @@
         self.w(u'</table>')
 
 
-class ProjectApycotConfigLatestExecutionSummary(EntityView):
-    __select__ = implements('ProjectApycotConfig',)
+class TestConfigLatestExecutionSummary(EntityView):
+    __select__ = implements('TestConfig',)
     id = 'summary'
     title = _('Test executions summary')