← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~jtv/launchpad/536819-display into lp:launchpad

 

Jeroen T. Vermeulen has proposed merging lp:~jtv/launchpad/536819-display into lp:launchpad with lp:~jtv/launchpad/buildfarmjob-getspecificjob-translationtemplatesbuild as a prerequisite.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers): code
Related bugs:
  #536819 Translation template jobs should keep history
  https://bugs.launchpad.net/bugs/536819


= Bug 536819: Display =

I hope this will be the final part in our efforts to move the Translations part of the build-farm work over to the latest data model.  It produces a simple UI page for TranslationTemplatesBuild.

This is still a work in progress.  More details as they develop.


Jeroen
-- 
https://code.launchpad.net/~jtv/launchpad/536819-display/+merge/35085
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~jtv/launchpad/536819-display into lp:launchpad.
=== modified file 'lib/lp/code/browser/branch.py'
--- lib/lp/code/browser/branch.py	2010-08-24 10:45:57 +0000
+++ lib/lp/code/browser/branch.py	2010-09-10 11:41:18 +0000
@@ -104,6 +104,7 @@
 from canonical.widgets.branch import TargetBranchWidget
 from canonical.widgets.itemswidgets import LaunchpadRadioWidgetWithDescription
 from canonical.widgets.lazrjs import vocabulary_to_choice_edit_items
+from lp.app.errors import NotFoundError
 from lp.blueprints.interfaces.specificationbranch import ISpecificationBranch
 from lp.bugs.interfaces.bug import IBugSet
 from lp.bugs.interfaces.bugbranch import IBugBranch
@@ -145,6 +146,9 @@
 from lp.registry.interfaces.productseries import IProductSeries
 from lp.registry.vocabularies import UserTeamsParticipationPlusSelfVocabulary
 from lp.services.propertycache import cachedproperty
+from lp.translations.interfaces.translationtemplatesbuild import (
+    ITranslationTemplatesBuildSource,
+    )
 
 
 def quote(text):
@@ -237,6 +241,16 @@
         """Traverses to the `ICodeImport` for the branch."""
         return self.context.code_import
 
+    @stepthrough("+translation-templates-build")
+    def traverse_translation_templates_build(self, id_string):
+        """Traverses to a `TranslationTemplatesBuild`."""
+        try:
+            buildfarmjob_id = int(id_string)
+        except ValueError:
+            raise NotFoundError(id_string)
+        source = getUtility(ITranslationTemplatesBuildSource)
+        return source.getByBuildFarmJob(buildfarmjob_id)
+
 
 class BranchEditMenu(NavigationMenu):
     """Edit menu for IBranch."""

=== modified file 'lib/lp/translations/browser/configure.zcml'
--- lib/lp/translations/browser/configure.zcml	2010-07-23 09:41:07 +0000
+++ lib/lp/translations/browser/configure.zcml	2010-09-10 11:41:18 +0000
@@ -1,4 +1,4 @@
-<!-- Copyright 2009 Canonical Ltd.  This software is licensed under the
+<!-- Copyright 2009-2010 Canonical Ltd.  This software is licensed under the
      GNU Affero General Public License version 3 (see the file LICENSE).
 -->
 
@@ -1024,4 +1024,21 @@
         permission="launchpad.Admin"/>
 
     </facet>
+
+<!-- TranslationTemplateBuild -->
+    <browser:defaultView
+        for="lp.translations.interfaces.translationtemplatesbuild.ITranslationTemplatesBuild"
+        name="+index"/>
+    <browser:url
+        for="lp.translations.interfaces.translationtemplatesbuild.ITranslationTemplatesBuild"
+        path_expression="string:+translation-templates-build/${build_farm_job/id}"
+        attribute_to_parent="branch"/>
+    <browser:page
+        for="lp.translations.interfaces.translationtemplatesbuild.ITranslationTemplatesBuild"
+        class="lp.translations.browser.translationtemplatesbuild.TranslationTemplatesBuildView"
+        permission="launchpad.View"
+        name="+index"
+        facet="overview"
+        template="../templates/translationtemplatesbuild-index.pt"/>
+
 </configure>

=== added file 'lib/lp/translations/browser/translationtemplatesbuild.py'
--- lib/lp/translations/browser/translationtemplatesbuild.py	1970-01-01 00:00:00 +0000
+++ lib/lp/translations/browser/translationtemplatesbuild.py	2010-09-10 11:41:18 +0000
@@ -0,0 +1,72 @@
+# Copyright 2010 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Display `TranslationTemplateBuild`s."""
+
+__metaclass__ = type
+__all__ = [
+    'TranslationTemplatesBuildNavigation',
+    'TranslationTemplatesBuildUrl',
+    'TranslationTemplatesBuildView',
+    ]
+
+from zope.component import getUtility
+
+from canonical.launchpad.webapp.publisher import LaunchpadView
+from canonical.launchpad.webapp.tales import DateTimeFormatterAPI
+from lp.buildmaster.enums import BuildStatus
+from lp.registry.interfaces.productseries import IProductSeriesSet
+from lp.services.propertycache import cachedproperty
+
+
+class TranslationTemplatesBuildView(LaunchpadView):
+
+    def getTargets(self):
+        utility = getUtility(IProductSeriesSet)
+        return utility.findByTranslationsImportBranch(self.context.branch)
+
+    @property
+    def status(self):
+        return self.context.status
+
+    def isBuilding(self):
+        return self.status == BuildStatus.BUILDING
+
+    @cachedproperty
+    def current_builder(self):
+        if self.isBuilding():
+            return self.context.buildqueue.builder
+        else:
+            return self.context.builder
+
+    def getDispatchTime(self):
+        if self.context.was_built:
+            return self.context.buildqueue_record.job.date_started
+        elif self.context.date_started is not None:
+            return self.context.date_started
+        elif self.context.buildqueue_record is not None:
+            return self.context.buildqueue_record.getEstimatedJobStartTime()
+        else:
+            return None
+
+    def _composeTimeText(self, time, preamble=''):
+        if time is None:
+            return None
+        formatter = DateTimeFormatterAPI(time)
+        return '%s <span title="%s">%s</span>' % (
+            preamble, formatter.datetime(), formatter.approximatedate())
+
+    def composeDispatchTimeText(self):
+        if self.context.date_started is None:
+            preamble = "Start"
+        else:
+            preamble = "Started"
+
+        return self._composeTimeText(self.getDispatchTime(), preamble)
+
+    def composeFinishTimeText(self):
+        return self._composeTimeText(self.context.date_finished, "Finished")
+
+    @cachedproperty
+    def last_score(self):
+        return self.context.buildqueue.lastscore

=== modified file 'lib/lp/translations/interfaces/translationtemplatesbuild.py'
--- lib/lp/translations/interfaces/translationtemplatesbuild.py	2010-09-10 11:41:16 +0000
+++ lib/lp/translations/interfaces/translationtemplatesbuild.py	2010-09-10 11:41:18 +0000
@@ -35,5 +35,15 @@
     def create(build_farm_job, branch):
         """Create a new `ITranslationTemplatesBuild`."""
 
-    def findByBranch(branch):
+    def findByBranch(branch, store=None):
         """Find `ITranslationTemplatesBuild`s for `branch`."""
+
+    def get(build_id, store=None):
+        """Find `ITranslationTemplatesBuild`s by id.
+
+        :param build_id: Numerical id to look for.
+        :param store: Optional database store to look in.
+        """
+
+    def getByBuildFarmJob(buildfarmjob_id, store=None):
+        """Find `ITranslationTemplatesBuild`s by `BuildFarmJob` id."""

=== modified file 'lib/lp/translations/model/translationtemplatesbuild.py'
--- lib/lp/translations/model/translationtemplatesbuild.py	2010-09-10 11:41:16 +0000
+++ lib/lp/translations/model/translationtemplatesbuild.py	2010-09-10 11:41:18 +0000
@@ -20,7 +20,7 @@
     )
 from zope.security.proxy import ProxyFactory
 
-from canonical.launchpad.interfaces.lpstorm import IMasterStore
+from canonical.launchpad.interfaces.lpstorm import IStore
 from lp.buildmaster.model.buildfarmjob import BuildFarmJobDerived
 from lp.code.model.branchjob import (
     BranchJob,
@@ -43,7 +43,7 @@
 
     __storm_table__ = 'TranslationTemplatesBuild'
 
-    id = Int(primary=True)
+    id = Int(name='id', primary=True)
     build_farm_job_id = Int(name='build_farm_job', allow_none=False)
     build_farm_job = Reference(build_farm_job_id, 'BuildFarmJob.id')
     branch_id = Int(name='branch', allow_none=False)
@@ -54,16 +54,9 @@
         self.build_farm_job = build_farm_job
         self.branch = branch
 
-    @classmethod
-    def create(self, build_farm_job, branch):
-        """See `ITranslationTemplatesBuildSource`."""
-        build = TranslationTemplatesBuild(build_farm_job, branch)
-        IMasterStore(TranslationTemplatesBuild).add(build)
-        return build
-
     def makeJob(self):
         """See `IBuildFarmJobOld`."""
-        store = IMasterStore(BranchJob)
+        store = IStore(BranchJob)
 
         # Pass public HTTP URL for the branch.
         metadata = {'branch_url': self.branch.composePublicURL()}
@@ -72,6 +65,49 @@
         store.add(branch_job)
         return TranslationTemplatesBuildJob(branch_job)
 
+    @classmethod
+    def _getStore(cls, store=None):
+        """Return `store` if given, or the default."""
+        if store is None:
+            return IStore(cls)
+        else:
+            return store
+
+    @classmethod
+    def create(cls, build_farm_job, branch):
+        """See `ITranslationTemplatesBuildSource`."""
+        build = TranslationTemplatesBuild(build_farm_job, branch)
+        store = cls._getStore()
+        store.add(build)
+        store.flush()
+        return build
+
+    @classmethod
+    def get(cls, build_id, store=None):
+        """See `ITranslationTemplatesBuildSource`."""
+        store = cls._getStore(store)
+        match = store.find(
+            TranslationTemplatesBuild,
+            TranslationTemplatesBuild.id == build_id)
+        return match.one()
+
+    @classmethod
+    def getByBuildFarmJob(cls, buildfarmjob_id, store=None):
+        """See `ITranslationTemplatesBuildSource`."""
+        store = cls._getStore(store)
+        match = store.find(
+            TranslationTemplatesBuild,
+            TranslationTemplatesBuild.build_farm_job == buildfarmjob_id)
+        return match.one()
+
+    @classmethod
+    def findByBranch(cls, branch, store=None):
+        """See `ITranslationTemplatesBuildSource`."""
+        store = cls._getStore(store)
+        return store.find(
+            TranslationTemplatesBuild,
+            TranslationTemplatesBuild.branch == branch)
+
 
 def get_translation_templates_build_for_build_farm_job(build_farm_job):
     """Return a `TranslationTemplatesBuild` from its `BuildFarmJob`."""

=== modified file 'lib/lp/translations/stories/buildfarm/xx-build-summary.txt'
--- lib/lp/translations/stories/buildfarm/xx-build-summary.txt	2010-08-31 21:30:43 +0000
+++ lib/lp/translations/stories/buildfarm/xx-build-summary.txt	2010-09-10 11:41:18 +0000
@@ -1,13 +1,18 @@
+<<<<<<< TREE
 TranslationTemplatesBuildJob Build Summary
 ==========================================
+=======
+TranslationTemplatesBuild Build Summary
+=======================================
+>>>>>>> MERGE-SOURCE
 
-The builders UI can show TranslationTemplateBuildJobs, although they
+The builders UI can show TranslationTemplateBuild records, although they
 look a little different from Soyuz-style jobs.
 
 Setup
 -----
 
-Create a builder working on a TranslationTemplatesBuildJob for a branch.
+Create a builder working on a TranslationTemplatesBuild for a branch.
 
     >>> from zope.component import getUtility
     >>> from canonical.launchpad.interfaces.launchpad import (
@@ -76,3 +81,18 @@
     Working on TranslationTemplatesBuildJob for branch ...
 
     >>> user_browser.getLink(branch_url).click()
+
+
+Show build record
+-----------------
+
+    >>> login(ANONYMOUS)
+    >>> from lp.translations.interfaces.translationtemplatesbuild import (
+    ...     ITranslationTemplatesBuildSource,
+    ...     )
+    >>> build = getUtility(ITranslationTemplatesBuildSource).findByBranch(
+    ...     branch).one()
+    >>> build_url = canonical_url(build)
+    >>> logout()
+
+    >>> user_browser.open(build_url)

=== added file 'lib/lp/translations/templates/translationtemplatesbuild-index.pt'
--- lib/lp/translations/templates/translationtemplatesbuild-index.pt	1970-01-01 00:00:00 +0000
+++ lib/lp/translations/templates/translationtemplatesbuild-index.pt	2010-09-10 11:41:18 +0000
@@ -0,0 +1,133 @@
+<html
+  xmlns="http://www.w3.org/1999/xhtml";
+  xmlns:tal="http://xml.zope.org/namespaces/tal";
+  xmlns:metal="http://xml.zope.org/namespaces/metal";
+  xmlns:i18n="http://xml.zope.org/namespaces/i18n";
+  metal:use-macro="view/macro:page/main_only"
+  i18n:domain="launchpad"
+>
+
+  <body>
+
+    <tal:registering metal:fill-slot="registering">
+        created
+        <span tal:content="context/date_created/fmt:displaydate"
+              tal:attributes="title context/date_created/fmt:datetime"
+          >on 2005-01-01</span>
+    </tal:registering>
+
+    <div metal:fill-slot="main">
+
+      <div class="yui-g">
+
+        <div id="status" class="yui-u first">
+	  <div class="portlet">
+            <div metal:use-macro="template/macros/status" />
+          </div>
+        </div>
+
+        <div id="details" class="yui-u">
+	  <div class="portlet">
+            <div metal:use-macro="template/macros/details" />
+          </div>
+        </div>
+
+      </div> <!-- yui-g  -->
+
+      <div id="buildlog" class="portlet"
+           tal:condition="context/status/enumvalue:BUILDING">
+        <div metal:use-macro="template/macros/buildlog" />
+      </div>
+
+   </div> <!-- main -->
+
+
+<metal:macros fill-slot="bogus">
+
+  <metal:macro define-macro="details">
+    <tal:comment replace="nothing">
+      Details section.
+    </tal:comment>
+    <h2>Build details</h2>
+    <p>Branch:
+	<tal:branch replace="structure context/branch/fmt:link">
+	  lp:foo/trunk
+	</tal:branch>
+    </p>
+    <tal:targets tal:define="targets view/getTargets">
+      <div tal:condition="targets">
+        For import into:
+        <ul>
+	  <li tal:repeat="target targets">
+	    <a tal:replace="target/fmt:link">gawk trunk series</a>
+	  </li>
+        </ul>
+      </div>
+      <div tal:condition="not:targets">
+        <em>Not imported anywhere.</em>
+      </div>
+    </tal:targets>
+  </metal:macro>
+
+  <metal:macro define-macro="status">
+    <tal:comment replace="nothing">
+      Status section.
+    </tal:comment>
+    <h2>Build status</h2>
+    <p>
+      <span tal:replace="structure context/image:icon" />
+      <span tal:attributes="
+            class string:buildstatus${context/status/name};"
+            tal:content="view/status/title">Fully built</span>
+      <tal:building define="builder view/current_builder"
+                    condition="builder">
+        on <a tal:content="builder/title"
+              tal:attributes="href builder/fmt:url"/>
+      </tal:building>
+    </p>
+
+    <ul>
+      <li tal:define="time view/composeDispatchTimeText"
+          tal:condition="time"
+          tal:content="structure time">
+          Started 5 minutes ago
+      </li>
+      <li tal:define="time view/composeFinishTimeText"
+          tal:condition="time"
+          tal:content="structure time">
+        Finished 30 seconds ago
+        <tal:duration define="duration context/duration" condition="duration">
+          (took <span tal:replace="duration/fmt:exactduration" />)
+        </tal:duration>
+      </li>
+      
+      <li tal:define="file context/log"
+          tal:condition="file">
+        <a class="sprite download"
+           tal:attributes="href context/log_url"
+           tal:content="string: buildlog">BUILDLOG</a>
+        (<span tal:replace="file/content/filesize/fmt:bytes" />)
+      </li>
+    </ul>
+  </metal:macro>
+
+  <metal:macro define-macro="buildlog">
+    <tal:comment replace="nothing">
+      Buildlog section.
+    </tal:comment>
+    <h2>Buildlog</h2>
+    <div id="buildlog-tail" class="logtail"
+         tal:define="logtail context/buildqueue_record/logtail"
+         tal:content="structure logtail/fmt:text-to-html">
+      <p>Things are crashing and burning all over the place.</p>
+    </div>
+    <p class="discreet" tal:condition="view/user">
+      Updated on <span tal:replace="structure view/user/fmt:local-time"/>
+    </p>
+  </metal:macro>
+
+</metal:macros>
+
+
+  </body>
+</html>

=== modified file 'lib/lp/translations/tests/test_translationtemplatesbuild.py'
--- lib/lp/translations/tests/test_translationtemplatesbuild.py	2010-09-10 11:41:16 +0000
+++ lib/lp/translations/tests/test_translationtemplatesbuild.py	2010-09-10 11:41:18 +0000
@@ -5,7 +5,6 @@
 
 __metaclass__ = type
 
-from storm.store import Store
 from zope.component import getUtility
 from zope.interface.verify import verifyObject
 
@@ -14,7 +13,6 @@
 from lp.buildmaster.interfaces.buildfarmjob import (
     IBuildFarmJob,
     IBuildFarmJobSource,
-    ISpecificBuildFarmJob,
     )
 from lp.testing import TestCaseWithFactory
 from lp.translations.interfaces.translationtemplatesbuild import (
@@ -33,12 +31,6 @@
 
     layer = DatabaseFunctionalLayer
 
-    def _findBuildForBranch(self, branch):
-        """Find the `TranslationTemplatesBuild` for `branch`, if any."""
-        return Store.of(branch).find(
-            TranslationTemplatesBuild,
-            TranslationTemplatesBuild.branch == branch).one()
-
     def _makeBuildFarmJob(self):
         """Create a `BuildFarmJob` for testing."""
         source = getUtility(IBuildFarmJobSource)
@@ -53,7 +45,6 @@
 
         self.assertTrue(verifyObject(ITranslationTemplatesBuild, build))
         self.assertTrue(verifyObject(IBuildFarmJob, build))
-        self.assertTrue(verifyObject(ISpecificBuildFarmJob, build))
         self.assertEqual(build_farm_job, build.build_farm_job)
         self.assertEqual(branch, build.branch)
 
@@ -62,10 +53,14 @@
         # TranslationTemplatesBuild.  This utility will become obsolete
         # later.
         jobset = getUtility(ITranslationTemplatesBuildJobSource)
+        source = getUtility(ITranslationTemplatesBuildSource)
         branch = self.factory.makeBranch()
 
         translationtemplatesbuildjob = jobset.create(branch)
-        self.assertNotEqual(None, self._findBuildForBranch(branch))
+
+        builds = list(source.findByBranch(branch))
+        self.assertEqual(1, len(builds))
+        self.assertIsInstance(builds[0], TranslationTemplatesBuild)
 
     def test_getSpecificJob(self):
         source = getUtility(ITranslationTemplatesBuildSource)
@@ -74,3 +69,49 @@
         build = source.create(build_farm_job, branch)
 
         self.assertEqual(build, build_farm_job.getSpecificJob())
+
+    def test_findByBranch(self):
+        source = getUtility(ITranslationTemplatesBuildSource)
+        build_farm_job = self._makeBuildFarmJob()
+        branch = self.factory.makeBranch()
+
+        self.assertContentEqual([], source.findByBranch(branch))
+
+        build = source.create(build_farm_job, branch)
+
+        by_branch = list(source.findByBranch(branch))
+        self.assertEqual([build], by_branch)
+
+    def test_get(self):
+        source = getUtility(ITranslationTemplatesBuildSource)
+        build_farm_job = self._makeBuildFarmJob()
+        branch = self.factory.makeBranch()
+        build = source.create(build_farm_job, branch)
+
+        self.assertEqual(build, source.get(build.id))
+
+    def test_get_returns_none_if_not_found(self):
+        source = getUtility(ITranslationTemplatesBuildSource)
+        build_farm_job = self._makeBuildFarmJob()
+        branch = self.factory.makeBranch()
+        build = source.create(build_farm_job, branch)
+
+        self.assertIs(None, source.get(build.id + 999))
+
+    def test_getByBuildFarmJob(self):
+        source = getUtility(ITranslationTemplatesBuildSource)
+        build_farm_job = self._makeBuildFarmJob()
+        branch = self.factory.makeBranch()
+        build = source.create(build_farm_job, branch)
+
+        self.assertEqual(build, source.getByBuildFarmJob(build_farm_job.id))
+
+    def test_getByBuildFarmJob_returns_none_if_not_found(self):
+        source = getUtility(ITranslationTemplatesBuildSource)
+        build_farm_job = self._makeBuildFarmJob()
+        branch = self.factory.makeBranch()
+        build = source.create(build_farm_job, branch)
+
+        self.assertIs(
+            None,
+            source.getByBuildFarmJob(build_farm_job.id + 999))


Follow ups