mirror of
git://git.yoctoproject.org/yocto-autobuilder2.git
synced 2025-07-19 20:59:02 +02:00
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:
parent
8245de5e6e
commit
36ae2de715
|
@ -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']
|
|
@ -1 +0,0 @@
|
|||
require("guanlecoja")(require("gulp"))
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
[bdist_wheel]
|
||||
universal=1
|
|
@ -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)'
|
||||
],
|
||||
)
|
||||
|
|
|
@ -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
|
593
yocto_console_view/src/module/main.module.js
Normal file
593
yocto_console_view/src/module/main.module.js
Normal 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');
|
|
@ -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)
|
228
yocto_console_view/src/module/main.module.spec.js
Normal file
228
yocto_console_view/src/module/main.module.spec.js
Normal 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);
|
||||
});
|
||||
});
|
|
@ -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
|
|
@ -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])
|
|
@ -1,7 +0,0 @@
|
|||
class ConsoleModal extends Controller
|
||||
constructor: ($scope, @$uibModalInstance, @selectedBuild) ->
|
||||
$scope.$on '$stateChangeStart', =>
|
||||
@close()
|
||||
|
||||
close: ->
|
||||
@$uibModalInstance.close()
|
22
yocto_console_view/src/module/view/modal/modal.controller.js
Normal file
22
yocto_console_view/src/module/view/modal/modal.controller.js
Normal 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]);
|
|
@ -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'
|
||||
}
|
|
@ -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])
|
26
yocto_console_view/webpack.config.js
Normal file
26
yocto_console_view/webpack.config.js
Normal 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,
|
||||
});
|
||||
}();
|
|
@ -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):
|
||||
|
||||
|
|
|
@ -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
|
@ -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*/
|
|
@ -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
Loading…
Reference in New Issue
Block a user