yocto_console_view: Convert to new plugin standards to match upstream buildbot

Our plugin is based on upstream buildbot's console plugin. Upstream moved
away from coffescript and made a number of other fixes and improvements.

Run decaffeinate on the coffeescript to translate to angularjs and then
convert to the new build environment and standards.

Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
This commit is contained in:
Richard Purdie 2020-11-28 18:08:45 +00:00
parent 8245de5e6e
commit 36ae2de715
23 changed files with 1076 additions and 81070 deletions

View File

@ -1,28 +0,0 @@
### ###############################################################################################
#
# This module contains all configuration for the build process
#
### ###############################################################################################
ANGULAR_TAG = "~1.5.3"
module.exports =
### ###########################################################################################
# Name of the plugin
### ###########################################################################################
name: 'yocto_console_view'
dir: build: 'yocto_console_view/static'
bower:
testdeps:
"guanlecoja-ui":
version: '~1.6.0'
files: ['vendors.js', 'scripts.js']
"angular-mocks":
version: ANGULAR_TAG
files: "angular-mocks.js"
'buildbot-data':
version: '~2.1.0'
files: 'dist/buildbot-data.js'
karma:
# we put tests first, so that we have angular, and fake app defined
files: ["tests.js", "scripts.js", 'fixtures.js']

View File

@ -1 +0,0 @@
require("guanlecoja")(require("gulp"))

View File

@ -1,11 +1,25 @@
{
"name": "yocto-console-view",
"engines": {
"node": ">=0.10.0",
"npm": ">=1.4.0"
"plugin_name": "console_view",
"private": true,
"main": "yocto_console_view/static/scripts.js",
"style": "yocto_console_view/static/styles.js",
"scripts": {
"build": "rimraf yocto_console_view/static && webpack --bail --progress --profile --env prod",
"build-dev": "rimraf yocto_console_view/static && webpack --bail --progress --profile --env dev",
"dev": "webpack --bail --progress --profile --watch --env dev"
},
"devDependencies": {
"angular-mocks": "^1.7.9",
"buildbot-build-common": ">0.1",
"lodash": "^4.17.11",
"rimraf": "^2.6.3"
},
"dependencies": {
"guanlecoja": "~0.8.3",
"gulp": "3.9.0"
"@uirouter/angularjs": "^1.0.15",
"angular": "^1.7.9",
"angular-animate": "^1.7.9",
"buildbot-data-js": ">0.1",
"guanlecoja-ui": ">0.1"
}
}

View File

@ -1,2 +0,0 @@
[bdist_wheel]
universal=1

View File

@ -15,10 +15,6 @@
#
# Copyright Buildbot Team Members
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
try:
from buildbot_pkg import setup_www_plugin
except ImportError:
@ -32,7 +28,6 @@ setup_www_plugin(
author=u'Richard Purdie',
author_email=u'richard.purdie@linuxfoundation.org',
url='http://autobuilder.yoctoproject.org/',
license='MIT',
packages=['yocto_console_view'],
package_data={
'': [
@ -44,4 +39,7 @@ setup_www_plugin(
[buildbot.www]
console_view = yocto_console_view:ep
""",
classifiers=[
'License :: OSI Approved :: GNU General Public License v2 (GPLv2)'
],
)

View File

@ -1,441 +0,0 @@
# Register new module
class App extends App
constructor: ->
return [
'ui.router'
'ui.bootstrap'
'ngAnimate'
'guanlecoja.ui'
'bbData'
]
class State extends Config
constructor: ($stateProvider, glMenuServiceProvider, bbSettingsServiceProvider) ->
# Name of the state
name = 'console'
# Menu configuration
glMenuServiceProvider.addGroup
name: name
caption: 'Yocto Console View'
icon: 'exclamation-circle'
order: 5
# Configuration
cfg =
group: name
caption: 'Yocto Console View'
# Register new state
state =
controller: "#{name}Controller"
controllerAs: "c"
templateUrl: "yocto_console_view/views/#{name}.html"
name: name
url: "/#{name}"
data: cfg
$stateProvider.state(state)
bbSettingsServiceProvider.addSettingsGroup
name: 'Console'
caption: 'Console related settings'
items: [
type: 'integer'
name: 'buildLimit'
caption: 'Number of builds to fetch'
default_value: 200
,
type: 'integer'
name: 'changeLimit'
caption: 'Number of changes to fetch'
default_value: 30
]
class Console extends Controller
constructor: (@$scope, $q, @$window, dataService, bbSettingsService, resultsService,
@$uibModal, @$timeout) ->
angular.extend this, resultsService
settings = bbSettingsService.getSettingsGroup('Console')
@buildLimit = settings.buildLimit.value
@changeLimit = settings.changeLimit.value
@dataAccessor = dataService.open().closeOnDestroy(@$scope)
@_infoIsExpanded = {}
@$scope.all_builders = @all_builders = @dataAccessor.getBuilders()
@$scope.builders = @builders = []
if Intl?
collator = new Intl.Collator(undefined, {numeric: true, sensitivity: 'base'})
@strcompare = collator.compare
else
@strcompare = (a, b) ->
if a < b
return -1
if a == b
return 0
return 1
@$scope.revmapping = @revmapping = {}
@$scope.branchmapping = @branchmapping = {}
@$scope.builds = @builds = @dataAccessor.getBuilds
property: ["yp_build_revision", "yp_build_branch", "reason"]
limit: @buildLimit
order: '-started_at'
@changes = @dataAccessor.getChanges({limit: @changeLimit, order: '-changeid'})
@buildrequests = @dataAccessor.getBuildrequests({limit: @buildLimit, order: '-submitted_at'})
@buildsets = @dataAccessor.getBuildsets({limit: @buildLimit, order: '-submitted_at'})
@builds.onChange = @changes.onChange = @buildrequests.onChange = @buildsets.onChange = @onChange
@builds.onNew = (build) =>
change = false
buildid = build.buildid
if build.properties?.yp_build_revision?
@revmapping[build.buildid] = build.properties.yp_build_revision[0]
change = true
if build.properties?.yp_build_branch?
@branchmapping[build.buildid] = build.properties.yp_build_branch[0]
change = true
if (! @revmapping[buildid] || ! @branchmapping[buildid]) && ! build.complete_at
build.getProperties().onChange = (properties) =>
change = false
buildid = properties.endpoint.split('/')[1]
if ! @revmapping[buildid]
rev = @getBuildProperty(properties[0], 'yp_build_revision')
if rev?
@revmapping[buildid] = rev
change = true
if ! @branchmapping[buildid]
branch = @getBuildProperty(properties[0], 'yp_build_branch')
if branch?
@branchmapping[buildid] = branch
change = true
if change and not @onchange_debounce?
@onchange_debounce = @$timeout(@_onChange, 100)
if change and not @onchange_debounce?
@onchange_debounce = @$timeout(@_onChange, 100)
getBuildProperty: (properties, property) ->
hasProperty = properties && properties.hasOwnProperty(property)
return if hasProperty then properties[property][0] else null
onChange: (s) =>
# if there is no data, no need to try and build something.
if @builds.length == 0 or @all_builders.length == 0 or not @changes.$resolved or
@buildsets.length == 0 or @buildrequests == 0
return
if not @onchange_debounce?
@onchange_debounce = @$timeout(@_onChange, 100)
_onChange: =>
@onchange_debounce = undefined
# we only display builders who actually have builds
for build in @builds
@all_builders.get(build.builderid).hasBuild = true
@sortBuildersByTags(@all_builders)
@changesBySSID = {}
@changesByRevision = {}
for change in @changes
@changesBySSID[change.sourcestamp.ssid] = change
@changesByRevision[change.revision] = change
@populateChange(change)
for build in @builds
@matchBuildWithChange(build)
@filtered_changes = []
for ssid, change of @changesBySSID
if change.comments
change.subject = change.comments.split("\n")[0]
for builder in change.builders
if builder.builds.length > 0
@filtered_changes.push(change)
break
###
# Sort builders by tags
# Buildbot eight has the category option, but it was only limited to one category per builder,
# which make it easy to sort by category
# Here, we have multiple tags per builder, we need to try to group builders with same tags together
# The algorithm is rather twisted. It is a first try at the concept of grouping builders by tags..
###
sortBuildersByTags: (all_builders) ->
# first we only want builders with builds
builders_with_builds = []
builderids_with_builds = ""
for builder in all_builders
if builder.hasBuild
builders_with_builds.push(builder)
builderids_with_builds += "." + builder.builderid
if builderids_with_builds == @last_builderids_with_builds
# don't recalculate if it hasn't changed!
return
# we call recursive function, which finds non-overlapping groups
tag_line = @_sortBuildersByTags(builders_with_builds)
# we get a tree of builders grouped by tags
# we now need to flatten the tree, in order to build several lines of tags
# (each line is representing a depth in the tag tree)
# we walk the tree left to right and build the list of builders in the tree order, and the tag_lines
# in the tree, there are groups of remaining builders, which could not be grouped together,
# those have the empty tag ''
tag_lines = []
sorted_builders = []
set_tag_line = (depth, tag, colspan) ->
# we build the tag lines by using a sparse array
_tag_line = tag_lines[depth]
if not _tag_line?
# initialize the sparse array
_tag_line = tag_lines[depth] = []
else
# if we were already initialized, look at the last tag if this is the same
# we merge the two entries
last_tag = _tag_line[_tag_line.length - 1]
if last_tag.tag == tag
last_tag.colspan += colspan
return
_tag_line.push(tag: tag, colspan: colspan)
self = @
# recursive tree walking
walk_tree = (tag, depth) ->
set_tag_line(depth, tag.tag, tag.builders.length)
if not tag.tag_line? or tag.tag_line.length == 0
# this is the leaf of the tree, sort by buildername, and add them to the
# list of sorted builders
tag.builders.sort (a, b) -> self.strcompare(a.name, b.name)
sorted_builders = sorted_builders.concat(tag.builders)
for i in [1..100] # set the remaining depth of the tree to the same colspan
# (we hardcode the maximum depth for now :/ )
set_tag_line(depth + i, '', tag.builders.length)
return
for _tag in tag.tag_line
walk_tree(_tag, depth + 1)
for tag in tag_line
walk_tree(tag, 0)
@builders = sorted_builders
@tag_lines = []
# make a new array to avoid it to be sparse, and to remove lines filled with null tags
for tag_line in tag_lines
if not (tag_line.length == 1 and tag_line[0].tag == "")
@tag_lines.push(tag_line)
@last_builderids_with_builds = builderids_with_builds
###
# recursive function which sorts the builders by tags
# call recursively with groups of builders smaller and smaller
###
_sortBuildersByTags: (all_builders) ->
# first find out how many builders there is by tags in that group
builders_by_tags = {}
for builder in all_builders
if builder.tags?
for tag in builder.tags
if not builders_by_tags[tag]?
builders_by_tags[tag] = []
builders_by_tags[tag].push(builder)
tags = []
for tag, builders of builders_by_tags
# we don't want the tags that are on all the builders
if builders.length < all_builders.length
tags.push(tag: tag, builders: builders)
# sort the tags to first look at tags with the larger number of builders
# @FIXME maybe this is not the best method to find the best groups
tags.sort (a, b) -> b.builders.length - a.builders.length
tag_line = []
chosen_builderids = {}
# pick the tags one by one, by making sure we make non-overalaping groups
for tag in tags
excluded = false
for builder in tag.builders
if chosen_builderids.hasOwnProperty(builder.builderid)
excluded = true
break
if not excluded
for builder in tag.builders
chosen_builderids[builder.builderid] = tag.tag
tag_line.push(tag)
# some builders do not have tags, we put them in another group
remaining_builders = []
for builder in all_builders
if not chosen_builderids.hasOwnProperty(builder.builderid)
remaining_builders.push(builder)
if remaining_builders.length
tag_line.push(tag: "", builders: remaining_builders)
# if there is more than one tag in this line, we need to recurse
if tag_line.length > 1
for tag in tag_line
tag.tag_line = @_sortBuildersByTags(tag.builders)
return tag_line
###
# fill a change with a list of builders
###
populateChange: (change) ->
change.builders = []
change.buildersById = {}
for builder in @builders
builder = builderid: builder.builderid, name: builder.name, builds: []
change.builders.push(builder)
change.buildersById[builder.builderid] = builder
###
# Match builds with a change
###
matchBuildWithChange: (build) =>
buildrequest = @buildrequests.get(build.buildrequestid)
if not buildrequest?
return
buildset = @buildsets.get(buildrequest.buildsetid)
if not buildset?
return
if buildset? and buildset.sourcestamps?
for sourcestamp in buildset.sourcestamps
change = @changesBySSID[sourcestamp.ssid]
if build.properties?.yp_build_revision? or @revmapping[build.buildid]
if build.properties?.yp_build_revision?
rev = build.properties.yp_build_revision[0]
else
rev = @revmapping[build.buildid]
change = @changesByRevision[rev]
if not change?
change = @changesBySSID[rev]
if not change?
change = @makeFakeChange(rev, build.started_at, rev)
if buildset? and buildset.parent_buildid?
oldrev = "Unresolved #{buildset.parent_buildid}"
delete @changesBySSID[oldrev]
oldrev = "Unresolved #{build.builderid}-#{build.buildid}"
delete @changesBySSID[oldrev]
change.caption = "Commit"
if build.properties?.yp_build_branch?
change.caption = build.properties.yp_build_branch[0]
if @branchmapping[build.buildid]
change.caption = @branchmapping[build.buildid]
change.revlink = "http://git.yoctoproject.org/cgit.cgi/poky/commit/?id=" + rev
change.errorlink = "http://errors.yoctoproject.org/Errors/Latest/?filter=" + rev + "&type=commit&limit=150"
bid = build.buildid
if buildset? and buildset.parent_buildid?
bid = buildset.parent_buildid
if (change.bid? and bid > change.bid) or !change.bid?
change.bid = bid
change.loglink = "https://wiki.yoctoproject.org/wiki/BuildLog#" + bid
if build.properties?.reason?
change.reason = build.properties.reason[0]
else
if buildset? and buildset.parent_buildid?
rev = "Unresolved #{buildset.parent_buildid}"
if not change?
change = @changesBySSID[rev]
if not change?
oldrev = "Unresolved #{build.builderid}-#{build.buildid}"
delete @changesBySSID[oldrev]
change = @makeFakeChange(rev, build.started_at, rev)
if not change?
rev = "Unresolved #{build.builderid}-#{build.buildid}"
if not change?
change = @changesBySSID[rev]
if not change?
change = @makeFakeChange(rev, build.started_at, rev)
change.caption = rev
change.buildersById[build.builderid].builds.push(build)
makeFakeChange: (revision, when_timestamp, comments) =>
change =
revision: revision
changeid: revision
when_timestamp: when_timestamp
comments: comments
@changesBySSID[revision] = change
@populateChange(change)
return change
###
# Open all change row information
###
openAll: ->
for change in @filtered_changes
change.show_details = true
###
# Close all change row information
###
closeAll: ->
for change in @filtered_changes
change.show_details = false
###
# Calculate row header (aka first column) width
# depending if we display commit comment, we reserve more space
###
getRowHeaderWidth: ->
if @hasExpanded()
return 400 # magic value enough to hold 78 characters lines
else
return 200
###
# Calculate col header (aka first row) height
# It depends on the length of the longest builder
###
getColHeaderHeight: ->
max_buildername = 0
for builder in @builders
max_buildername = Math.max(builder.name.length, max_buildername)
return Math.max(100, max_buildername * 3)
###
#
# Determine if we use a 100% width table or if we allow horizontal scrollbar
# depending on number of builders, and size of window, we need a fixed column size or a 100% width table
#
###
isBigTable: ->
padding = @getRowHeaderWidth()
if ((@$window.innerWidth - padding) / @builders.length) < 40
return true
return false
###
#
# do we have at least one change expanded?
#
###
hasExpanded: ->
for change in @changes
if @infoIsExpanded(change)
return true
return false
###
#
# display build details
#
###
selectBuild: (build) ->
modal = @$uibModal.open
templateUrl: 'yocto_console_view/views/modal.html'
controller: 'consoleModalController as modal'
windowClass: 'modal-big'
resolve:
selectedBuild: -> build
###
#
# toggle display of additional info for that change
#
###
toggleInfo: (change) ->
change.show_details = !change.show_details
infoIsExpanded: (change) ->
return change.show_details

View File

@ -0,0 +1,593 @@
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS205: Consider reworking code to avoid use of IIFEs
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
import 'angular-animate';
import '@uirouter/angularjs';
import 'guanlecoja-ui';
import 'buildbot-data-js';
class ConsoleState {
constructor($stateProvider, glMenuServiceProvider, bbSettingsServiceProvider) {
// Name of the state
const name = 'console';
// Menu configuration
glMenuServiceProvider.addGroup({
name,
caption: 'Yocto Console View',
icon: 'exclamation-circle',
order: 5
});
// Configuration
const cfg = {
group: name,
caption: 'Yocto Console View'
};
// Register new state
const state = {
controller: `${name}Controller`,
controllerAs: "c",
template: require('./console.tpl.jade'),
name,
url: `/${name}`,
data: cfg
};
$stateProvider.state(state);
bbSettingsServiceProvider.addSettingsGroup({
name: 'Console',
caption: 'Console related settings',
items: [{
type: 'integer',
name: 'buildLimit',
caption: 'Number of builds to fetch',
default_value: 200
}
, {
type: 'integer',
name: 'changeLimit',
caption: 'Number of changes to fetch',
default_value: 30
}
]});
}
}
class Console {
constructor($scope, $q, $window, dataService, bbSettingsService, resultsService,
$uibModal, $timeout) {
this.onChange = this.onChange.bind(this);
this._onChange = this._onChange.bind(this);
this.matchBuildWithChange = this.matchBuildWithChange.bind(this);
this.makeFakeChange = this.makeFakeChange.bind(this);
this.$scope = $scope;
this.$window = $window;
this.$uibModal = $uibModal;
this.$timeout = $timeout;
angular.extend(this, resultsService);
const settings = bbSettingsService.getSettingsGroup('Console');
this.buildLimit = settings.buildLimit.value;
this.changeLimit = settings.changeLimit.value;
this.dataAccessor = dataService.open().closeOnDestroy(this.$scope);
this._infoIsExpanded = {};
this.$scope.all_builders = (this.all_builders = this.dataAccessor.getBuilders());
this.$scope.builders = (this.builders = []);
if (typeof Intl !== 'undefined' && Intl !== null) {
const collator = new Intl.Collator(undefined, {numeric: true, sensitivity: 'base'});
this.strcompare = collator.compare;
} else {
this.strcompare = function(a, b) {
if (a < b) {
return -1;
}
if (a === b) {
return 0;
}
return 1;
};
}
this.$scope.revmapping = (this.revmapping = {});
this.$scope.branchmapping = (this.branchmapping = {});
this.$scope.builds = (this.builds = this.dataAccessor.getBuilds({
property: ["yp_build_revision", "yp_build_branch", "reason"],
limit: this.buildLimit,
order: '-started_at'
}));
this.changes = this.dataAccessor.getChanges({limit: this.changeLimit, order: '-changeid'});
this.buildrequests = this.dataAccessor.getBuildrequests({limit: this.buildLimit, order: '-submitted_at'});
this.buildsets = this.dataAccessor.getBuildsets({limit: this.buildLimit, order: '-submitted_at'});
this.builds.onChange = (this.changes.onChange = (this.buildrequests.onChange = (this.buildsets.onChange = this.onChange)));
this.builds.onNew = build => {
let change = false;
let {
buildid
} = build;
if ((build.properties != null ? build.properties.yp_build_revision : undefined) != null) {
this.revmapping[build.buildid] = build.properties.yp_build_revision[0];
change = true;
}
if ((build.properties != null ? build.properties.yp_build_branch : undefined) != null) {
this.branchmapping[build.buildid] = build.properties.yp_build_branch[0];
change = true;
}
if ((!this.revmapping[buildid] || !this.branchmapping[buildid]) && !build.complete_at) {
build.getProperties().onChange = properties => {
change = false;
buildid = properties.endpoint.split('/')[1];
if (!this.revmapping[buildid]) {
const rev = this.getBuildProperty(properties[0], 'yp_build_revision');
if (rev != null) {
this.revmapping[buildid] = rev;
change = true;
}
}
if (!this.branchmapping[buildid]) {
const branch = this.getBuildProperty(properties[0], 'yp_build_branch');
if (branch != null) {
this.branchmapping[buildid] = branch;
}
return change = true;
}
};
if (change && (this.onchange_debounce == null)) {
this.onchange_debounce = this.$timeout(this._onChange, 100);
}
}
if (change && (this.onchange_debounce == null)) {
return this.onchange_debounce = this.$timeout(this._onChange, 100);
}
};
}
getBuildProperty(properties, property) {
const hasProperty = properties && properties.hasOwnProperty(property);
if (hasProperty) { return properties[property][0]; } else { return null; }
}
onChange(s) {
// if there is no data, no need to try and build something.
if ((this.builds.length === 0) || (this.all_builders.length === 0) || !this.changes.$resolved ||
(this.buildsets.length === 0) || (this.buildrequests === 0)) {
return;
}
if ((this.onchange_debounce == null)) {
return this.onchange_debounce = this.$timeout(this._onChange, 100);
}
}
_onChange() {
let build, change;
this.onchange_debounce = undefined;
// we only display builders who actually have builds
for (build of Array.from(this.builds)) {
this.all_builders.get(build.builderid).hasBuild = true;
}
this.sortBuildersByTags(this.all_builders);
if (this.changesBySSID == null) { this.changesBySSID = {}; }
if (this.changesByRevision == null) { this.changesByRevision = {}; }
for (change of Array.from(this.changes)) {
this.changesBySSID[change.sourcestamp.ssid] = change;
this.changesByRevision[change.revision] = change;
this.populateChange(change);
}
for (build of Array.from(this.builds)) {
this.matchBuildWithChange(build);
}
this.filtered_changes = [];
return (() => {
const result = [];
for (let ssid in this.changesBySSID) {
change = this.changesBySSID[ssid];
if (change.comments) {
change.subject = change.comments.split("\n")[0];
}
result.push((() => {
const result1 = [];
for (let builder of Array.from(change.builders)) {
if (builder.builds.length > 0) {
this.filtered_changes.push(change);
break;
} else {
result1.push(undefined);
}
}
return result1;
})());
}
return result;
})();
}
/*
* Sort builders by tags
* Buildbot eight has the category option, but it was only limited to one category per builder,
* which make it easy to sort by category
* Here, we have multiple tags per builder, we need to try to group builders with same tags together
* The algorithm is rather twisted. It is a first try at the concept of grouping builders by tags..
*/
sortBuildersByTags(all_builders) {
// first we only want builders with builds
let tag;
const builders_with_builds = [];
let builderids_with_builds = "";
for (let builder of Array.from(all_builders)) {
if (builder.hasBuild) {
builders_with_builds.push(builder);
builderids_with_builds += "." + builder.builderid;
}
}
if (builderids_with_builds === this.last_builderids_with_builds) {
// don't recalculate if it hasn't changed!
return;
}
// we call recursive function, which finds non-overlapping groups
let tag_line = this._sortBuildersByTags(builders_with_builds);
// we get a tree of builders grouped by tags
// we now need to flatten the tree, in order to build several lines of tags
// (each line is representing a depth in the tag tree)
// we walk the tree left to right and build the list of builders in the tree order, and the tag_lines
// in the tree, there are groups of remaining builders, which could not be grouped together,
// those have the empty tag ''
const tag_lines = [];
let sorted_builders = [];
const set_tag_line = function(depth, tag, colspan) {
// we build the tag lines by using a sparse array
let _tag_line = tag_lines[depth];
if ((_tag_line == null)) {
// initialize the sparse array
_tag_line = (tag_lines[depth] = []);
} else {
// if we were already initialized, look at the last tag if this is the same
// we merge the two entries
const last_tag = _tag_line[_tag_line.length - 1];
if (last_tag.tag === tag) {
last_tag.colspan += colspan;
return;
}
}
return _tag_line.push({tag, colspan});
};
const self = this;
// recursive tree walking
var walk_tree = function(tag, depth) {
set_tag_line(depth, tag.tag, tag.builders.length);
if ((tag.tag_line == null) || (tag.tag_line.length === 0)) {
// this is the leaf of the tree, sort by buildername, and add them to the
// list of sorted builders
tag.builders.sort((a, b) => self.strcompare(a.name, b.name));
sorted_builders = sorted_builders.concat(tag.builders);
for (let i = 1; i <= 100; i++) { // set the remaining depth of the tree to the same colspan
// (we hardcode the maximum depth for now :/ )
set_tag_line(depth + i, '', tag.builders.length);
}
return;
}
return Array.from(tag.tag_line).map((_tag) =>
walk_tree(_tag, depth + 1));
};
for (tag of Array.from(tag_line)) {
walk_tree(tag, 0);
}
this.builders = sorted_builders;
this.tag_lines = [];
// make a new array to avoid it to be sparse, and to remove lines filled with null tags
for (tag_line of Array.from(tag_lines)) {
if (!((tag_line.length === 1) && (tag_line[0].tag === ""))) {
this.tag_lines.push(tag_line);
}
}
return this.last_builderids_with_builds = builderids_with_builds;
}
/*
* recursive function which sorts the builders by tags
* call recursively with groups of builders smaller and smaller
*/
_sortBuildersByTags(all_builders) {
// first find out how many builders there is by tags in that group
let builder, builders, tag;
const builders_by_tags = {};
for (builder of Array.from(all_builders)) {
if (builder.tags != null) {
for (tag of Array.from(builder.tags)) {
if ((builders_by_tags[tag] == null)) {
builders_by_tags[tag] = [];
}
builders_by_tags[tag].push(builder);
}
}
}
const tags = [];
for (tag in builders_by_tags) {
// we don't want the tags that are on all the builders
builders = builders_by_tags[tag];
if (builders.length < all_builders.length) {
tags.push({tag, builders});
}
}
// sort the tags to first look at tags with the larger number of builders
// @FIXME maybe this is not the best method to find the best groups
tags.sort((a, b) => b.builders.length - a.builders.length);
const tag_line = [];
const chosen_builderids = {};
// pick the tags one by one, by making sure we make non-overalaping groups
for (tag of Array.from(tags)) {
let excluded = false;
for (builder of Array.from(tag.builders)) {
if (chosen_builderids.hasOwnProperty(builder.builderid)) {
excluded = true;
break;
}
}
if (!excluded) {
for (builder of Array.from(tag.builders)) {
chosen_builderids[builder.builderid] = tag.tag;
}
tag_line.push(tag);
}
}
// some builders do not have tags, we put them in another group
const remaining_builders = [];
for (builder of Array.from(all_builders)) {
if (!chosen_builderids.hasOwnProperty(builder.builderid)) {
remaining_builders.push(builder);
}
}
if (remaining_builders.length) {
tag_line.push({tag: "", builders: remaining_builders});
}
// if there is more than one tag in this line, we need to recurse
if (tag_line.length > 1) {
for (tag of Array.from(tag_line)) {
tag.tag_line = this._sortBuildersByTags(tag.builders);
}
}
return tag_line;
}
/*
* fill a change with a list of builders
*/
populateChange(change) {
change.builders = [];
change.buildersById = {};
return (() => {
const result = [];
for (let builder of Array.from(this.builders)) {
builder = {builderid: builder.builderid, name: builder.name, builds: []};
change.builders.push(builder);
result.push(change.buildersById[builder.builderid] = builder);
}
return result;
})();
}
/*
* Match builds with a change
*/
matchBuildWithChange(build) {
let change, oldrev, rev;
const buildrequest = this.buildrequests.get(build.buildrequestid);
if ((buildrequest == null)) {
return;
}
const buildset = this.buildsets.get(buildrequest.buildsetid);
if ((buildset == null)) {
return;
}
if ((buildset != null) && (buildset.sourcestamps != null)) {
for (let sourcestamp of Array.from(buildset.sourcestamps)) {
change = this.changesBySSID[sourcestamp.ssid];
}
}
if (((build.properties != null ? build.properties.yp_build_revision : undefined) != null) || this.revmapping[build.buildid]) {
if ((build.properties != null ? build.properties.yp_build_revision : undefined) != null) {
rev = build.properties.yp_build_revision[0];
} else {
rev = this.revmapping[build.buildid];
}
change = this.changesByRevision[rev];
if ((change == null)) {
change = this.changesBySSID[rev];
}
if ((change == null)) {
change = this.makeFakeChange(rev, build.started_at, rev);
}
if ((buildset != null) && (buildset.parent_buildid != null)) {
oldrev = `Unresolved ${buildset.parent_buildid}`;
delete this.changesBySSID[oldrev];
}
oldrev = `Unresolved ${build.builderid}-${build.buildid}`;
delete this.changesBySSID[oldrev];
change.caption = "Commit";
if ((build.properties != null ? build.properties.yp_build_branch : undefined) != null) {
change.caption = build.properties.yp_build_branch[0];
}
if (this.branchmapping[build.buildid]) {
change.caption = this.branchmapping[build.buildid];
}
change.revlink = "http://git.yoctoproject.org/cgit.cgi/poky/commit/?id=" + rev;
change.errorlink = "http://errors.yoctoproject.org/Errors/Latest/?filter=" + rev + "&type=commit&limit=150";
let bid = build.buildid;
if ((buildset != null) && (buildset.parent_buildid != null)) {
bid = buildset.parent_buildid;
}
if (((change.bid != null) && (bid > change.bid)) || (change.bid == null)) {
change.bid = bid;
change.loglink = "https://wiki.yoctoproject.org/wiki/BuildLog#" + bid;
}
if ((build.properties != null ? build.properties.reason : undefined) != null) {
change.reason = build.properties.reason[0];
}
} else {
if ((buildset != null) && (buildset.parent_buildid != null)) {
rev = `Unresolved ${buildset.parent_buildid}`;
if ((change == null)) {
change = this.changesBySSID[rev];
}
if ((change == null)) {
oldrev = `Unresolved ${build.builderid}-${build.buildid}`;
delete this.changesBySSID[oldrev];
change = this.makeFakeChange(rev, build.started_at, rev);
}
}
if ((change == null)) {
rev = `Unresolved ${build.builderid}-${build.buildid}`;
if ((change == null)) {
change = this.changesBySSID[rev];
}
if ((change == null)) {
change = this.makeFakeChange(rev, build.started_at, rev);
}
}
change.caption = rev;
}
return change.buildersById[build.builderid].builds.push(build);
}
makeFakeChange(revision, when_timestamp, comments) {
const change = {
revision,
changeid: revision,
when_timestamp,
comments
};
this.changesBySSID[revision] = change;
this.populateChange(change);
return change;
}
/*
* Open all change row information
*/
openAll() {
return Array.from(this.filtered_changes).map((change) =>
(change.show_details = true));
}
/*
* Close all change row information
*/
closeAll() {
return Array.from(this.filtered_changes).map((change) =>
(change.show_details = false));
}
/*
* Calculate row header (aka first column) width
* depending if we display commit comment, we reserve more space
*/
getRowHeaderWidth() {
if (this.hasExpanded()) {
return 400; // magic value enough to hold 78 characters lines
} else {
return 200;
}
}
/*
* Calculate col header (aka first row) height
* It depends on the length of the longest builder
*/
getColHeaderHeight() {
let max_buildername = 0;
for (let builder of Array.from(this.builders)) {
max_buildername = Math.max(builder.name.length, max_buildername);
}
return Math.max(100, max_buildername * 3);
}
/*
*
* Determine if we use a 100% width table or if we allow horizontal scrollbar
* depending on number of builders, and size of window, we need a fixed column size or a 100% width table
*
*/
isBigTable() {
const padding = this.getRowHeaderWidth();
if (((this.$window.innerWidth - padding) / this.builders.length) < 40) {
return true;
}
return false;
}
/*
*
* do we have at least one change expanded?
*
*/
hasExpanded() {
for (let change of Array.from(this.changes)) {
if (this.infoIsExpanded(change)) {
return true;
}
}
return false;
}
/*
*
* display build details
*
*/
selectBuild(build) {
let modal;
return modal = this.$uibModal.open({
template: require('./view/modal/modal.tpl.jade'),
controller: 'consoleModalController as modal',
windowClass: 'modal-big',
resolve: {
selectedBuild() { return build; }
}
});
}
/*
*
* toggle display of additional info for that change
*
*/
toggleInfo(change) {
return change.show_details = !change.show_details;
}
infoIsExpanded(change) {
return change.show_details;
}
}
angular.module('yocto_console_view', [
'ui.router', 'ui.bootstrap', 'ngAnimate', 'guanlecoja.ui', 'bbData'])
.config(['$stateProvider', 'glMenuServiceProvider', 'bbSettingsServiceProvider', ConsoleState])
.controller('consoleController', ['$scope', '$q', '$window', 'dataService', 'bbSettingsService', 'resultsService', '$uibModal', '$timeout', Console]);
require('./view/modal/modal.controller.js');
require('./releaseselectorfield.directive.js');
require('./yoctochangedetails.directive.js');

View File

@ -1,177 +0,0 @@
beforeEach ->
module ($provide) ->
$provide.service '$uibModal', -> open: ->
null
module ($provide) ->
$provide.service 'resultsService', -> results2class: ->
null
# Mock bbSettingsProvider
module ($provide) ->
$provide.provider 'bbSettingsService', class
group = {}
addSettingsGroup: (g) -> g.items.map (i) ->
if i.name is 'lazy_limit_waterfall'
i.default_value = 2
group[i.name] = value: i.default_value
$get: ->
getSettingsGroup: ->
return group
save: ->
null
module 'yocto_console_view'
describe 'Console view', ->
$state = null
beforeEach inject ($injector) ->
$state = $injector.get('$state')
it 'should register a new state with the correct configuration', ->
name = 'console'
state = $state.get().pop()
data = state.data
expect(state.controller).toBe("#{name}Controller")
expect(state.controllerAs).toBe('c')
expect(state.templateUrl).toBe("yocto_console_view/views/#{name}.html")
expect(state.url).toBe("/#{name}")
describe 'Console view controller', ->
# Test data
builders = [
builderid: 1
masterids: [1]
,
builderid: 2
masterids: [1]
,
builderid: 3
masterids: [1]
,
builderid: 4
masterids: [1]
]
builds1 = [
buildid: 1
builderid: 1
buildrequestid: 1
,
buildid: 2
builderid: 2
buildrequestid: 1
,
buildid: 3
builderid: 4
buildrequestid: 2
,
buildid: 4
builderid: 3
buildrequestid: 2
]
builds2 = [
buildid: 5
builderid: 2
buildrequestid: 3
]
builds = builds1.concat(builds2)
buildrequests = [
builderid: 1
buildrequestid: 1
buildsetid: 1
,
builderid: 1
buildrequestid: 2
buildsetid: 1
,
builderid: 1
buildrequestid: 3
buildsetid: 2
]
buildsets = [
bsid: 1
sourcestamps: [
ssid: 1
]
,
bsid: 2
sourcestamps: [
ssid: 2
]
]
changes = [
changeid: 1
sourcestamp:
ssid: 1
]
createController = scope = $rootScope = dataService = $window = $timeout = null
injected = ($injector) ->
$q = $injector.get('$q')
$rootScope = $injector.get('$rootScope')
$window = $injector.get('$window')
$timeout = $injector.get('$timeout')
dataService = $injector.get('dataService')
scope = $rootScope.$new()
dataService.when('builds', builds)
dataService.when('builders', builders)
dataService.when('changes', changes)
dataService.when('buildrequests', buildrequests)
dataService.when('buildsets', buildsets)
# Create new controller using controller as syntax
$controller = $injector.get('$controller')
createController = ->
return $controller 'consoleController as c',
# Inject controller dependencies
$q: $q
$window: $window
$scope: scope
beforeEach(inject(injected))
it 'should be defined', ->
createController()
expect(scope.c).toBeDefined()
it 'should bind the builds, builders, changes, buildrequests and buildsets to scope', ->
createController()
$rootScope.$digest()
$timeout.flush()
expect(scope.c.builds).toBeDefined()
expect(scope.c.builds.length).toBe(builds.length)
expect(scope.c.all_builders).toBeDefined()
expect(scope.c.all_builders.length).toBe(builders.length)
expect(scope.c.changes).toBeDefined()
expect(scope.c.changes.length).toBe(changes.length)
expect(scope.c.buildrequests).toBeDefined()
expect(scope.c.buildrequests.length).toBe(buildrequests.length)
expect(scope.c.buildsets).toBeDefined()
expect(scope.c.buildsets.length).toBe(buildsets.length)
it 'should match the builds with the change', ->
createController()
$timeout.flush()
$rootScope.$digest()
$timeout.flush()
expect(scope.c.changes[0]).toBeDefined()
expect(scope.c.changes[0].builders).toBeDefined()
builders = scope.c.changes[0].builders
expect(builders[0].builds[0].buildid).toBe(1)
expect(builders[1].builds[0].buildid).toBe(2)
expect(builders[2].builds[0].buildid).toBe(4)
expect(builders[3].builds[0].buildid).toBe(3)
xit 'should match sort the builders by tag groups', ->
createController()
_builders = FIXTURES['builders.fixture.json'].builders
for builder in _builders
builder.hasBuild = true
scope.c.sortBuildersByTags(_builders)
expect(_builders.length).toBe(scope.c.builders.length)
expect(scope.c.tag_lines.length).toEqual(5)

View File

@ -0,0 +1,228 @@
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS206: Consider reworking classes to avoid initClass
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
beforeEach(function() {
angular.mock.module(function($provide) {
$provide.service('$uibModal', function() { return {open() {}}; });
});
angular.mock.module(function($provide) {
$provide.service('resultsService', function() { return {results2class() {}}; });
});
// Mock bbSettingsProvider
angular.mock.module(function($provide) {
$provide.provider('bbSettingsService', (function() {
let group = undefined;
const Cls = class {
static initClass() {
group = {};
}
addSettingsGroup(g) { return g.items.map(function(i) {
if (i.name === 'lazy_limit_waterfall') {
i.default_value = 2;
}
return group[i.name] = {value: i.default_value};
}); }
$get() {
return {
getSettingsGroup() {
return group;
},
save() {}
};
}
};
Cls.initClass();
return Cls;
})()
);
});
angular.mock.module('yocto_console_view');
});
describe('Console view', function() {
let $state = null;
beforeEach(inject($injector => $state = $injector.get('$state'))
);
it('should register a new state with the correct configuration', function() {
const name = 'console';
const state = $state.get().pop();
const { data } = state;
expect(state.controller).toBe(`${name}Controller`);
expect(state.controllerAs).toBe('c');
expect(state.url).toBe(`/${name}`);
});
});
describe('Console view controller', function() {
// Test data
let $rootScope, $timeout, $window, dataService, scope;
let builders = [{
builderid: 1,
masterids: [1]
}
, {
builderid: 2,
masterids: [1]
}
, {
builderid: 3,
masterids: [1]
}
, {
builderid: 4,
masterids: [1]
}
];
const builds1 = [{
buildid: 1,
builderid: 1,
buildrequestid: 1
}
, {
buildid: 2,
builderid: 2,
buildrequestid: 1
}
, {
buildid: 3,
builderid: 4,
buildrequestid: 2
}
, {
buildid: 4,
builderid: 3,
buildrequestid: 2
}
];
const builds2 = [{
buildid: 5,
builderid: 2,
buildrequestid: 3
}
];
const builds = builds1.concat(builds2);
const buildrequests = [{
builderid: 1,
buildrequestid: 1,
buildsetid: 1
}
, {
builderid: 1,
buildrequestid: 2,
buildsetid: 1
}
, {
builderid: 1,
buildrequestid: 3,
buildsetid: 2
}
];
const buildsets = [{
bsid: 1,
sourcestamps: [
{ssid: 1}
]
}
, {
bsid: 2,
sourcestamps: [
{ssid: 2}
]
}
];
const changes = [{
changeid: 1,
sourcestamp: {
ssid: 1
}
}
];
let createController = (scope = ($rootScope = (dataService = ($window = ($timeout = null)))));
const injected = function($injector) {
const $q = $injector.get('$q');
$rootScope = $injector.get('$rootScope');
$window = $injector.get('$window');
$timeout = $injector.get('$timeout');
dataService = $injector.get('dataService');
scope = $rootScope.$new();
dataService.when('builds', builds);
dataService.when('builders', builders);
dataService.when('changes', changes);
dataService.when('buildrequests', buildrequests);
dataService.when('buildsets', buildsets);
// Create new controller using controller as syntax
const $controller = $injector.get('$controller');
createController = () =>
$controller('consoleController as c', {
// Inject controller dependencies
$q,
$window,
$scope: scope
}
)
;
};
beforeEach(inject(injected));
it('should be defined', function() {
createController();
expect(scope.c).toBeDefined();
});
it('should bind the builds, builders, changes, buildrequests and buildsets to scope', function() {
createController();
$rootScope.$digest();
$timeout.flush();
expect(scope.c.builds).toBeDefined();
expect(scope.c.builds.length).toBe(builds.length);
expect(scope.c.all_builders).toBeDefined();
expect(scope.c.all_builders.length).toBe(builders.length);
expect(scope.c.changes).toBeDefined();
expect(scope.c.changes.length).toBe(changes.length);
expect(scope.c.buildrequests).toBeDefined();
expect(scope.c.buildrequests.length).toBe(buildrequests.length);
expect(scope.c.buildsets).toBeDefined();
expect(scope.c.buildsets.length).toBe(buildsets.length);
});
it('should match the builds with the change', function() {
createController();
$timeout.flush();
$rootScope.$digest();
$timeout.flush();
expect(scope.c.changes[0]).toBeDefined();
expect(scope.c.changes[0].builders).toBeDefined();
({ builders } = scope.c.changes[0]);
expect(builders[0].builds[0].buildid).toBe(1);
expect(builders[1].builds[0].buildid).toBe(2);
expect(builders[2].builds[0].buildid).toBe(4);
expect(builders[3].builds[0].buildid).toBe(3);
});
xit('should match sort the builders by tag groups', function() {
createController();
const _builders = FIXTURES['builders.fixture.json'].builders;
for (let builder of Array.from(_builders)) {
builder.hasBuild = true;
}
scope.c.sortBuildersByTags(_builders);
expect(_builders.length).toBe(scope.c.builders.length);
expect(scope.c.tag_lines.length).toEqual(5);
});
});

View File

@ -1,43 +0,0 @@
class Releaseselectorfield extends Directive
constructor: ->
return {
replace: false
restrict: 'E'
scope: false
templateUrl: "yocto_console_view/views/releaseselectorfield.html"
controller: '_ReleaseselectorfieldController'
}
class _Releaseselectorfield extends Controller
constructor: ($scope, $http) ->
# HACK: we find the rootfield by doing $scope.$parent.$parent
rootfield = $scope
while rootfield? and not rootfield.rootfield?
rootfield = rootfield.$parent
if not rootfield?
console.log "rootfield not found!?!?"
return
# copy paste of code in forcedialog, which flatten the fields to be able to find easily
fields_ref = {}
gatherFields = (fields) ->
for field in fields
if field.fields?
gatherFields(field.fields)
else
fields_ref[field.fullName] = field
gatherFields(rootfield.rootfield.fields)
console.log fields_ref
# when our field change, we update the fields that we are suppose to
$scope.$watch "field.value", (n, o) ->
selector = $scope.field.selectors[n]
if selector?
for k, v of selector
console.log k
fields_ref[k].value = v

View File

@ -0,0 +1,69 @@
/*
* decaffeinate suggestions:
* DS002: Fix invalid constructor
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS205: Consider reworking code to avoid use of IIFEs
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
class Releaseselectorfield {
constructor() {
return {
replace: false,
restrict: 'E',
scope: false,
template: require('./releaseselectorfield.tpl.jade'),
controller: '_ReleaseselectorfieldController'
};
}
}
class _releaseselectorfield {
constructor($scope, $http) {
// HACK: we find the rootfield by doing $scope.$parent.$parent
let rootfield = $scope;
while ((rootfield != null) && (rootfield.rootfield == null)) {
rootfield = rootfield.$parent;
}
if ((rootfield == null)) {
console.log("rootfield not found!?!?");
return;
}
// copy paste of code in forcedialog, which flatten the fields to be able to find easily
const fields_ref = {};
var gatherFields = fields => Array.from(fields).map((field) =>
(field.fields != null) ?
gatherFields(field.fields)
:
(fields_ref[field.fullName] = field));
gatherFields(rootfield.rootfield.fields);
console.log(fields_ref);
// when our field change, we update the fields that we are suppose to
$scope.$watch("field.value", function(n, o) {
const selector = $scope.field.selectors[n];
if (selector != null) {
return (() => {
const result = [];
for (let k in selector) {
const v = selector[k];
console.log(k);
result.push(fields_ref[k].value = v);
}
return result;
})();
}
});
}
}
angular.module('yocto_console_view')
.directive('releaseselectorfield', [Releaseselectorfield])
.controller('_ReleaseselectorfieldController', ['$scope', '$http', _releaseselectorfield])

View File

@ -1,7 +0,0 @@
class ConsoleModal extends Controller
constructor: ($scope, @$uibModalInstance, @selectedBuild) ->
$scope.$on '$stateChangeStart', =>
@close()
close: ->
@$uibModalInstance.close()

View File

@ -0,0 +1,22 @@
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
class ConsoleModal {
constructor($scope, $uibModalInstance, selectedBuild) {
this.$uibModalInstance = $uibModalInstance;
this.selectedBuild = selectedBuild;
$scope.$on('$stateChangeStart', () => {
return this.close();
});
}
close() {
return this.$uibModalInstance.close();
}
}
angular.module('yocto_console_view')
.controller('consoleModalController', ['$scope', '$uibModalInstance', 'selectedBuild', ConsoleModal]);

View File

@ -1,10 +0,0 @@
class Yoctochangedetails extends Directive('common')
constructor: ->
return {
replace: true
restrict: 'E'
scope:
change: '='
compact: '=?'
templateUrl: 'yocto_console_view/views/yoctochangedetails.html'
}

View File

@ -0,0 +1,21 @@
/*
* decaffeinate suggestions:
* DS002: Fix invalid constructor
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
class Yoctochangedetails {
constructor() {
return {
replace: true,
restrict: 'E',
scope: {
change: '=',
compact: '=?'
},
template: require('./yoctochangedetails.tpl.jade')
};
}
}
angular.module('yocto_console_view')
.directive('yoctochangedetails', [Yoctochangedetails])

View File

@ -0,0 +1,26 @@
'use strict';
const common = require('buildbot-build-common');
const env = require('yargs').argv.env;
const pkg = require('./package.json');
var event = process.env.npm_lifecycle_event;
var isTest = event === 'test' || event === 'test-watch';
var isProd = env === 'prod';
module.exports = function() {
return common.createTemplateWebpackConfig({
entry: {
scripts: './src/module/main.module.js',
styles: './src/styles/styles.less',
},
libraryName: pkg.name,
pluginName: pkg.plugin_name,
dirname: __dirname,
isTest: isTest,
isProd: isProd,
outputPath: __dirname + '/yocto_console_view/static',
extractStyles: true,
});
}();

View File

@ -1,8 +1,23 @@
# This file is part of Buildbot. Buildbot is free software: you can
# redistribute it and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation, version 2.
#
# This program 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 General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc., 51
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Copyright Buildbot Team Members
from buildbot.www.plugin import Application
from buildbot.schedulers.forcesched import ChoiceStringParameter
# create the interface for the setuptools entry point
ep = Application(__name__, "Yocto Console View UI")
ep = Application(__name__, "Buildbot Console View UI")
class ReleaseSelector(ChoiceStringParameter):

View File

@ -1,347 +0,0 @@
window.FIXTURES = {
"builders.fixture.json": {
"builders": [
{
"builderid": 1,
"description": null,
"masterids": [
1
],
"name": "buildbot-job",
"tags": [
"job",
"buildbot"
]
},
{
"builderid": 2,
"description": null,
"masterids": [
1
],
"name": "buildbot",
"tags": [
"buildbot",
"trunk"
]
},
{
"builderid": 3,
"description": null,
"masterids": [],
"name": "buildbot SQLALCHEMY:latest TESTS:pylint TWISTED:latest python:2.7",
"tags": [
"buildbot",
"SQLALCHEMY:latest",
"TESTS:pylint",
"TWISTED:latest",
"python:2.7"
]
},
{
"builderid": 4,
"description": null,
"masterids": [],
"name": "buildbot SQLALCHEMY:latest TESTS:flake8 TWISTED:latest python:2.7",
"tags": [
"buildbot",
"SQLALCHEMY:latest",
"TWISTED:latest",
"python:2.7",
"TESTS:flake8"
]
},
{
"builderid": 5,
"description": null,
"masterids": [],
"name": "buildbot SQLALCHEMY:latest TESTS:isort TWISTED:latest python:2.7",
"tags": [
"buildbot",
"SQLALCHEMY:latest",
"TWISTED:latest",
"python:2.7",
"TESTS:isort"
]
},
{
"builderid": 6,
"description": null,
"masterids": [],
"name": "buildbot SQLALCHEMY:latest TESTS:docs TWISTED:latest python:2.7",
"tags": [
"buildbot",
"SQLALCHEMY:latest",
"TWISTED:latest",
"python:2.7",
"TESTS:docs"
]
},
{
"builderid": 7,
"description": null,
"masterids": [],
"name": "buildbot SQLALCHEMY:latest TESTS:coverage TWISTED:latest python:2.7",
"tags": [
"buildbot",
"SQLALCHEMY:latest",
"TWISTED:latest",
"python:2.7",
"TESTS:coverage"
]
},
{
"builderid": 8,
"description": null,
"masterids": [],
"name": "buildbot SQLALCHEMY:latest TESTS:js TWISTED:latest python:2.7",
"tags": [
"buildbot",
"SQLALCHEMY:latest",
"TWISTED:latest",
"python:2.7",
"TESTS:js"
]
},
{
"builderid": 9,
"description": null,
"masterids": [],
"name": "buildbot SQLALCHEMY:latest TESTS:smokes TWISTED:latest python:2.7",
"tags": [
"buildbot",
"SQLALCHEMY:latest",
"TWISTED:latest",
"python:2.7",
"TESTS:smokes"
]
},
{
"builderid": 10,
"description": null,
"masterids": [],
"name": "buildbot SQLALCHEMY:latest TESTS:trial TWISTED:14.0.2 python:2.7",
"tags": [
"buildbot",
"SQLALCHEMY:latest",
"python:2.7",
"TESTS:trial",
"TWISTED:14.0.2"
]
},
{
"builderid": 11,
"description": null,
"masterids": [],
"name": "buildbot SQLALCHEMY:latest TESTS:trial TWISTED:15.4.0 python:2.7",
"tags": [
"buildbot",
"SQLALCHEMY:latest",
"python:2.7",
"TESTS:trial",
"TWISTED:15.4.0"
]
},
{
"builderid": 12,
"description": null,
"masterids": [],
"name": "buildbot SQLALCHEMY:latest TESTS:trial TWISTED:latest python:2.7",
"tags": [
"buildbot",
"SQLALCHEMY:latest",
"TWISTED:latest",
"python:2.7",
"TESTS:trial"
]
},
{
"builderid": 13,
"description": null,
"masterids": [],
"name": "buildbot DB_TYPE:sqlite SQLALCHEMY:latest TESTS:trial TWISTED:latest python:2.7",
"tags": [
"buildbot",
"SQLALCHEMY:latest",
"TWISTED:latest",
"python:2.7",
"TESTS:trial",
"DB_TYPE:sqlite"
]
},
{
"builderid": 14,
"description": null,
"masterids": [],
"name": "buildbot DB_TYPE:mysql SQLALCHEMY:latest TESTS:trial TWISTED:latest python:2.7",
"tags": [
"buildbot",
"SQLALCHEMY:latest",
"TWISTED:latest",
"python:2.7",
"TESTS:trial",
"DB_TYPE:mysql"
]
},
{
"builderid": 15,
"description": null,
"masterids": [],
"name": "buildbot SQLALCHEMY:0.8.0 TESTS:trial TWISTED:15.5.0 python:2.7",
"tags": [
"buildbot",
"python:2.7",
"TESTS:trial",
"SQLALCHEMY:0.8.0",
"TWISTED:15.5.0"
]
},
{
"builderid": 16,
"description": null,
"masterids": [],
"name": "buildbot SQLALCHEMY:latest TESTS:trial TWISTED:15.5.0 python:2.7",
"tags": [
"buildbot",
"SQLALCHEMY:latest",
"python:2.7",
"TESTS:trial",
"TWISTED:15.5.0"
]
},
{
"builderid": 17,
"description": null,
"masterids": [],
"name": "buildbot SQLALCHEMY:latest TESTS:trial_worker TWISTED:10.2.0 python:2.7",
"tags": [
"buildbot",
"SQLALCHEMY:latest",
"python:2.7",
"TESTS:trial_worker",
"TWISTED:10.2.0"
]
},
{
"builderid": 18,
"description": null,
"masterids": [],
"name": "buildbot SQLALCHEMY:latest TESTS:trial_worker TWISTED:11.1.0 python:2.7",
"tags": [
"buildbot",
"SQLALCHEMY:latest",
"python:2.7",
"TESTS:trial_worker",
"TWISTED:11.1.0"
]
},
{
"builderid": 19,
"description": null,
"masterids": [],
"name": "buildbot SQLALCHEMY:latest TESTS:trial_worker TWISTED:12.2.0 python:2.7",
"tags": [
"buildbot",
"SQLALCHEMY:latest",
"python:2.7",
"TESTS:trial_worker",
"TWISTED:12.2.0"
]
},
{
"builderid": 20,
"description": null,
"masterids": [],
"name": "buildbot SQLALCHEMY:latest TESTS:trial_worker TWISTED:13.2.0 python:2.7",
"tags": [
"buildbot",
"SQLALCHEMY:latest",
"python:2.7",
"TESTS:trial_worker",
"TWISTED:13.2.0"
]
},
{
"builderid": 21,
"description": null,
"masterids": [],
"name": "buildbot SQLALCHEMY:latest TESTS:trial_worker TWISTED:14.0.2 python:2.6",
"tags": [
"buildbot",
"SQLALCHEMY:latest",
"TWISTED:14.0.2",
"TESTS:trial_worker",
"python:2.6"
]
},
{
"builderid": 22,
"description": null,
"masterids": [],
"name": "buildbot SQLALCHEMY:latest TESTS:trial_worker TWISTED:15.4.0 python:2.6",
"tags": [
"buildbot",
"SQLALCHEMY:latest",
"TWISTED:15.4.0",
"TESTS:trial_worker",
"python:2.6"
]
},
{
"builderid": 23,
"description": null,
"masterids": [],
"name": "buildbot SQLALCHEMY:latest TESTS:coverage TWISTED:trunk python:3.5",
"tags": [
"buildbot",
"SQLALCHEMY:latest",
"TESTS:coverage",
"TWISTED:trunk",
"python:3.5"
]
},
{
"builderid": 24,
"description": null,
"masterids": [],
"name": "buildbot SQLALCHEMY:latest TESTS:flake8 TWISTED:trunk python:3.5",
"tags": [
"buildbot",
"SQLALCHEMY:latest",
"TESTS:flake8",
"TWISTED:trunk",
"python:3.5"
]
},
{
"builderid": 25,
"description": null,
"masterids": [],
"name": "buildbot SQLALCHEMY:latest TESTS:smokes TWISTED:latest python:3.5",
"tags": [
"buildbot",
"SQLALCHEMY:latest",
"TWISTED:latest",
"TESTS:smokes",
"python:3.5"
]
},
{
"builderid": 26,
"description": null,
"masterids": [],
"name": "buildbot SQLALCHEMY:latest TESTS:trial TWISTED:trunk python:3.6",
"tags": [
"buildbot",
"SQLALCHEMY:latest",
"TESTS:trial",
"TWISTED:trunk",
"python:3.6"
]
}
],
"meta": {
"total": 26
}
}
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1,74 @@
.console .table-fixedwidth{width:initial}.console .load-indicator{width:100%;height:100%;z-index:900;background-color:#fff;display:table}.console .load-indicator .spinner{display:table-cell;vertical-align:middle;text-align:center}.console .load-indicator .spinner p{font-weight:300;margin-top:10px}.console .column{min-width:40px;max-width:40px;width:40px}.console table{border:none}.console .tag_row td{margin:0;padding:0}.console .tag_row span{position:relative;float:left;font-size:10px;overflow:hidden;text-decoration:none;white-space:nowrap}.console tr.first-row{background-color:#fff!important}.console tr.first-row th{border:none;background-color:#fff!important}.console tr.first-row .builder{position:relative;float:left;font-size:12px;text-align:center;transform:rotate(-25deg);transform-origin:0 100%;text-decoration:none;white-space:nowrap}.yoctochangedetails>.no-select>*{margin-left:.3em;margin-right:.3em}.select-editable{position:absolute;top:0;border:none;margin:2px;width:90%;height:29px}.select-editable input:focus{outline:0}.modal-big .modal-dialog{width:80%}.modal-big .fa{cursor:pointer}
.console .table-fixedwidth {
width: initial;
}
.console .load-indicator {
width: 100%;
height: 100%;
z-index: 900;
background-color: #ffffff;
display: table;
}
.console .load-indicator .spinner {
display: table-cell;
vertical-align: middle;
text-align: center;
}
.console .load-indicator .spinner p {
font-weight: 300;
margin-top: 10px;
}
.console .column {
min-width: 40px;
max-width: 40px;
width: 40px;
}
.console table {
border: none;
}
.console .tag_row td {
margin: 0px;
padding: 0px;
}
.console .tag_row span {
position: relative;
float: left;
font-size: 10px;
overflow: hidden;
text-decoration: none;
white-space: nowrap;
}
.console tr.first-row {
background-color: #fff !important;
}
.console tr.first-row th {
border: none;
background-color: #fff !important;
}
.console tr.first-row .builder {
position: relative;
float: left;
font-size: 12px;
text-align: center;
transform: rotate(-25deg);
transform-origin: 0% 100%;
text-decoration: none;
white-space: nowrap;
}
.yoctochangedetails > .no-select > * {
margin-left: 0.3em;
margin-right: 0.3em;
}
.select-editable {
position: absolute;
top: 0;
border: none;
margin: 2px;
width: 90%;
height: 29px;
}
.select-editable input:focus {
outline: none;
}
/*# sourceMappingURL=styles.css.map*/

View File

@ -0,0 +1 @@
{"version":3,"sources":["webpack://yocto-console-view/./src/styles/styles.less"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"styles.css","sourcesContent":[".console .table-fixedwidth {\n width: initial;\n}\n.console .load-indicator {\n width: 100%;\n height: 100%;\n z-index: 900;\n background-color: #ffffff;\n display: table;\n}\n.console .load-indicator .spinner {\n display: table-cell;\n vertical-align: middle;\n text-align: center;\n}\n.console .load-indicator .spinner p {\n font-weight: 300;\n margin-top: 10px;\n}\n.console .column {\n min-width: 40px;\n max-width: 40px;\n width: 40px;\n}\n.console table {\n border: none;\n}\n.console .tag_row td {\n margin: 0px;\n padding: 0px;\n}\n.console .tag_row span {\n position: relative;\n float: left;\n font-size: 10px;\n overflow: hidden;\n text-decoration: none;\n white-space: nowrap;\n}\n.console tr.first-row {\n background-color: #fff !important;\n}\n.console tr.first-row th {\n border: none;\n background-color: #fff !important;\n}\n.console tr.first-row .builder {\n position: relative;\n float: left;\n font-size: 12px;\n text-align: center;\n transform: rotate(-25deg);\n transform-origin: 0% 100%;\n text-decoration: none;\n white-space: nowrap;\n}\n.yoctochangedetails > .no-select > * {\n margin-left: 0.3em;\n margin-right: 0.3em;\n}\n.select-editable {\n position: absolute;\n top: 0;\n border: none;\n margin: 2px;\n width: 90%;\n height: 29px;\n}\n.select-editable input:focus {\n outline: none;\n}\n"],"sourceRoot":""}

File diff suppressed because it is too large Load Diff