[gnome-ostree] build: Improve web frontend with diff, list view



commit eb7eb54866ab333ad7178d0657d7235de47f1a78
Author: Colin Walters <walters verbum org>
Date:   Sun Oct 21 20:09:37 2012 -0400

    build: Improve web frontend with diff, list view

 Makefile-ostbuild.am                           |    1 +
 qa/repoweb/index.html                          |    7 +-
 qa/repoweb/repoweb.js                          |  129 +++++++++++++++++-------
 src/ostbuild/pyostbuild/builtin_autobuilder.py |   28 ++++-
 src/ostbuild/pyostbuild/builtin_build.py       |   10 ++
 src/ostbuild/pyostbuild/jsondb.py              |   16 +++
 src/ostbuild/pyostbuild/snapshot.py            |   50 +++++++++
 7 files changed, 196 insertions(+), 45 deletions(-)
---
diff --git a/Makefile-ostbuild.am b/Makefile-ostbuild.am
index 2b3b6aa..d93a760 100644
--- a/Makefile-ostbuild.am
+++ b/Makefile-ostbuild.am
@@ -63,6 +63,7 @@ pyostbuild_PYTHON =					\
 	src/ostbuild/pyostbuild/ostbuildrc.py		\
 	src/ostbuild/pyostbuild/privileged_subproc.py	\
 	src/ostbuild/pyostbuild/warningfilter.py	\
+	src/ostbuild/pyostbuild/snapshot.py		\
 	src/ostbuild/pyostbuild/subprocess_helpers.py	\
 	src/ostbuild/pyostbuild/vcs.py			\
 	$(NULL)
diff --git a/qa/repoweb/index.html b/qa/repoweb/index.html
index 424d272..cb2e3fa 100644
--- a/qa/repoweb/index.html
+++ b/qa/repoweb/index.html
@@ -24,11 +24,10 @@
             the <a href="http://git.gnome.org/browse/gnome-ostree";>gnome-ostree</a>
             git module.</p>
         
-          <h3>Resolve</h3>
-          <div id="resolve-summary">Loading...</div>
+          <h3>Builds</h3>
+          <ul id="build-summary" data-role="listview" data-theme="d" data-divider-theme="d">
+          </ul>
 
-          <h3>Build</h3>
-          <div id="build-summary">Loading...</div>
         </div>
         <div class="content-secondary">
 	  <ul data-role="listview" data-theme="c" data-dividertheme="d">
diff --git a/qa/repoweb/repoweb.js b/qa/repoweb/repoweb.js
index 1ef195d..5476a8c 100644
--- a/qa/repoweb/repoweb.js
+++ b/qa/repoweb/repoweb.js
@@ -61,57 +61,114 @@ function timeago(d, now) {
     }
 }
 
+function buildDiffComponentAppend(container, description, a) {
+    var additional = 0;
+    if (a.length > 10) {
+        a = a.slice(0, 10); 
+        additional = a.length - 10;
+    }
+    var p = document.createElement('p');
+    container.appendChild(p);
+    p.appendChild(document.createTextNode(description + ": " + a.join(", ")));
+    if (additional > 0) {
+        var b = document.createElement('b');
+        p.appendChild(b);
+        b.appendChild.document.createTextNode(" and " + additional + " more");
+    }
+}
+
+function buildDiffAppend(container, buildDiff) {
+    if (!buildDiff)
+        return document.createTextNode("No changes or new build");
+    var added = buildDiff[0];
+    var modified = buildDiff[1];
+    var removed = buildDiff[2];
+
+    if (added.length > 0)
+        buildDiffComponentAppend(container, 'Added', added);
+    if (modified.length > 0)
+        buildDiffComponentAppend(container, 'Updated', modified);
+    if (removed.length > 0)
+        buildDiffComponentAppend(container, 'removed', removed);
+}
+
 function renderBuild(container, build) {
     var now = new Date();
 
-    var div = document.createElement('div');
-    container.appendChild(div);
     var version = build['meta']['version'];
-    var endTimestamp = null;
-    if (build['timestamp'])
-        endTimestamp = new Date(build['timestamp'] * 1000);
+
+    var divider = document.createElement('li');
+    container.appendChild(divider);
+    divider.setAttribute('data-role', 'list-divider');
+    divider.appendChild(document.createTextNode(version));
+
+    var li = document.createElement('li');
+    container.appendChild(li);
     var a = document.createElement('a');
-    div.appendChild(a);
+    li.appendChild(a);
     a.setAttribute('href', 'work/tasks/' + prefix + '-build/' + build['v'] + '/log');
     a.setAttribute('rel', 'external');
-    a.appendChild(document.createTextNode("Build " + version));
-    div.appendChild(document.createTextNode(": "));
-    var stateSpan = document.createElement('span');
-    div.appendChild(stateSpan);
-    var state = document.createTextNode(build['state']);
-    stateSpan.appendChild(state);
-    if (build['state'] == 'success')
-        $(stateSpan).addClass("repoweb-build-success");
-    else
-        $(stateSpan).addClass("repoweb-build-failed");
-    var status = build['build-status'];
-    if (status)
-        div.appendChild(document.createTextNode(" " + status['description']));
-    else if (endTimestamp)
-        div.appendChild(document.createTextNode(" " + timeago(endTimestamp, now)));
+
+    var state = build['state'];
+    
+    if (state == 'running') {
+        var p = document.createElement('p');
+        a.appendChild(p);
+        var text = "Build " + version + " running";
+        var status = build['build-status'];
+        if (status)
+            text += ": " + status['description'];
+        p.appendChild(document.createTextNode(text));
+        p = document.createElement('p');
+        p.appendChild(document.createTextNode(text));
+    } else {
+        var p = document.createElement('p');
+        a.appendChild(p);
+        p.appendChild(document.createTextNode("Build " + version + ": "));
+        var stateSpan = document.createElement('span');
+        p.appendChild(stateSpan);
+        stateSpan.appendChild(document.createTextNode(build['state']));
+        if (state == 'success')
+            $(stateSpan).addClass("repoweb-build-success");
+        else if (state == 'failed')
+            $(stateSpan).addClass("repoweb-build-failed");
+    }
+
+    buildDiffAppend(a, build['diff']);
+
+    if (build['timestamp']) {
+        var endTimestamp = new Date(build['timestamp'] * 1000);
+        var p = document.createElement('p');
+        a.appendChild(p);
+        $(p).addClass("ui-li-aside");
+        p.appendChild(document.createTextNode(timeago(endTimestamp, now)));
+    }
+}
+
+function updateResolve() {
+    $("#resolve-summary").empty();
+    var summary = $("#resolve-summary").get(0);
+
+    var div = document.createElement('div');
+    summary.appendChild(div);
+    div.appendChild(document.createTextNode("Current version: "));
+    var a = document.createElement('a');
+    div.appendChild(a);
+    a.setAttribute('href', 'work/snapshots/' + repoData['version-path']);
+    a.setAttribute('rel', 'external');
+    a.appendChild(document.createTextNode(repoData['version']));
 }
 
 function repoweb_index_init() {
     repoweb_init();
     $(repoDataSignal).on("loaded", function () {
-	$("#resolve-summary").empty();
-	var summary = $("#resolve-summary").get(0);
-
-        var div = document.createElement('div');
-        summary.appendChild(div);
-        div.appendChild(document.createTextNode("Current version: "));
-        var a = document.createElement('a');
-        div.appendChild(a);
-        a.setAttribute('href', 'work/snapshots/' + repoData['version-path']);
-        a.setAttribute('rel', 'external');
-        a.appendChild(document.createTextNode(repoData['version']));
-
-	$("#build-summary").empty();
-	summary = $("#build-summary").get(0);
+
+	var buildSummary = $("#build-summary").get(0);
         var buildData = repoData.build;
         for (var i = buildData.length - 1; i >= 0; i--) {
             var build = buildData[i];
-            renderBuild(summary, build);
+            renderBuild(buildSummary, build);
         }
+	$(buildSummary).listview('refresh');
     });
 }
diff --git a/src/ostbuild/pyostbuild/builtin_autobuilder.py b/src/ostbuild/pyostbuild/builtin_autobuilder.py
index 0d71675..d84a1a4 100755
--- a/src/ostbuild/pyostbuild/builtin_autobuilder.py
+++ b/src/ostbuild/pyostbuild/builtin_autobuilder.py
@@ -35,6 +35,7 @@ from . import kvfile
 from . import filemonitor
 from . import jsondb
 from . import odict
+from . import snapshot
 from . import vcs
 from . import task
 
@@ -51,10 +52,27 @@ class OstbuildAutobuilder(builtins.Builtin):
         self.source_snapshot_path = None
         self.build_needed = True
         self.last_build_succeeded = True
+        self._build_diff_cache = {}
 
     def _status_is_success(self, estatus):
         return os.WIFEXITED(estatus) and os.WEXITSTATUS(estatus) == 0
 
+    def _get_build_diff_for_task(self, task):
+        if hasattr(task, 'build_diff'):
+            return task.build_diff
+        db = self.get_src_snapshot_db()
+        meta_path = os.path.join(task.path, 'meta.json')
+        f = open(meta_path)
+        meta = json.load(f)
+        f.close()
+        snapshot_path = meta['version-path']
+        prev_snapshot_path = db.get_previous_path(snapshot_path)
+        if prev_snapshot_path is None:
+            task.build_diff = None
+        else:
+            task.build_diff = snapshot.snapshot_diff(db.load_from_path(snapshot_path),
+                                                     db.load_from_path(prev_snapshot_path))
+
     def _on_resolve_exited(self, pid, status):
         self.resolve_proc = None
         success = self._status_is_success(status)
@@ -131,7 +149,8 @@ class OstbuildAutobuilder(builtins.Builtin):
         self.loop.watch_pid(self.build_proc.pid, self._on_build_exited)
         self._write_status()
 
-    def _taskhistory_to_json(self, history):
+    def _buildhistory_to_json(self):
+        history = self._build_taskset.get_history()
         MAXITEMS = 5
         entries = []
         for item in history[-MAXITEMS:]:
@@ -144,6 +163,7 @@ class OstbuildAutobuilder(builtins.Builtin):
                 f = open(meta_path)
                 data['meta'] = json.load(f)
                 f.close()
+            data['diff'] = self._get_build_diff_for_task(item)
         return entries
 
     def _write_status(self):
@@ -156,12 +176,10 @@ class OstbuildAutobuilder(builtins.Builtin):
         else:
             status['version'] = ''
         
-        status['resolve'] = self._taskhistory_to_json(self._resolve_taskset.get_history())
-        build_history = self._build_taskset.get_history()
-        status['build'] = self._taskhistory_to_json(build_history)
+        status['build'] = self._buildhistory_to_json()
         
         if self.build_proc is not None:
-            active_build = build_history[-1]
+            active_build = self._build_taskset.get_history()[-1]
             active_build_json = status['build'][-1]
             status_path = os.path.join(active_build.path, 'status.json')
             if os.path.isfile(status_path):
diff --git a/src/ostbuild/pyostbuild/builtin_build.py b/src/ostbuild/pyostbuild/builtin_build.py
index b2c8077..234841f 100755
--- a/src/ostbuild/pyostbuild/builtin_build.py
+++ b/src/ostbuild/pyostbuild/builtin_build.py
@@ -32,6 +32,7 @@ from . import buildutil
 from . import task
 from . import fileutil
 from . import kvfile
+from . import snapshot
 from . import odict
 from . import vcs
 
@@ -509,6 +510,15 @@ and the manifest input."""
 
         log("Using source snapshot: %s" % (os.path.basename(self.snapshot_path), ))
 
+        db = self.get_src_snapshot_db()
+        prev_snapshot = db.get_previous_path(self.snapshot_path)
+        if prev_snapshot is not None:
+            (removed, modified, added) = snapshot.snapshot_diff(db.load_from_path(self.snapshot_path),
+                                                                db.load_from_path(prev_snapshot))
+            log("Removed components: %r" % (removed, ))
+            log("Modified components: %r" % (modified, ))
+            log("Added components: %r" % (added, ))
+
         self._write_status('Starting')
 
         self.buildopts = BuildOptions()
diff --git a/src/ostbuild/pyostbuild/jsondb.py b/src/ostbuild/pyostbuild/jsondb.py
index 5a1500d..3ff2930 100644
--- a/src/ostbuild/pyostbuild/jsondb.py
+++ b/src/ostbuild/pyostbuild/jsondb.py
@@ -64,12 +64,28 @@ class JsonDB(object):
             return None
         return json.load(open(path))
 
+    def load_from_path(self, path):
+        return json.load(open(os.path.join(self._dirpath, os.path.basename(path))))
+
     def get_latest_path(self):
         files = self._get_all()
         if len(files) == 0:
             return None
         return os.path.join(self._dirpath, files[0][3])
 
+    def get_previous_path(self, path):
+        name = os.path.basename(path)
+        match = self._version_csum_re.search(name)
+        assert match is not None
+        (target_major, target_minor) = (int(match.group(1)), int(match.group(2)))
+        files = self._get_all()
+        prev = None
+        for (major, minor, csum, fname) in reversed(files):
+            if target_major == major and target_minor == minor:
+                break
+            prev = fname
+        return prev
+
     def parse_version(self, name):
         match = self._version_csum_re.search(name)
         assert match is not None
diff --git a/src/ostbuild/pyostbuild/snapshot.py b/src/ostbuild/pyostbuild/snapshot.py
new file mode 100644
index 0000000..b706ebf
--- /dev/null
+++ b/src/ostbuild/pyostbuild/snapshot.py
@@ -0,0 +1,50 @@
+#
+# Copyright (C) 2012 Colin Walters <walters verbum org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+import os,sys
+
+def _component_dict(snapshot):
+    r = {}
+    for component in snapshot['components']:
+        r[component['name']] = component
+    patches = snapshot['patches']
+    r[patches['name']] = patches
+    base = snapshot['base']
+    r[base['name']] = base
+    return r
+
+def snapshot_diff(a, b):
+    a_components = _component_dict(a)
+    b_components = _component_dict(b)
+
+    added = []
+    modified = []
+    removed = []
+
+    for name in a_components:
+        c_a = a_components[name]
+        c_b = b_components.get(name)
+        if c_b is None:
+            removed.append(name)
+        elif c_a['revision'] != c_b['revision']:
+            modified.append(name)
+    for name in b_components:
+        if name not in a_components:
+            added.append(name)
+    return (added, modified, removed)
+        



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]