yocto_console_view: Add initial version of our customisation buildbot plugin

This commit is contained in:
Richard Purdie 2018-06-10 17:12:37 +01:00
parent f08c7d3044
commit 02bcaeb027
26 changed files with 84343 additions and 0 deletions

View File

@ -0,0 +1,17 @@
This custom buildbot plugin does three things:
* Replaces the "Console View" with our own "Yocto Console View"
* Adds a yoctochangedetails element to customise the information we display about a build
(link to the code repository, link to error reporting for the build)
* Add a custom field element, ReleaseSelector to the force build scheduler allowing
us to customise the form input fields to allow auto population of fields for
specific release branch combinations
The plugin ships in compiled form along with its source code. The generated files are:
yocto_console_view/static/*
yocto_console_view/VERSION
In order to build this plugin you need a buildbot development environment setup along
with its dependencies. FIXME, add more info on building.

View File

@ -0,0 +1,28 @@
### ###############################################################################################
#
# 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

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

View File

@ -0,0 +1,11 @@
{
"name": "yocto-console-view",
"engines": {
"node": ">=0.10.0",
"npm": ">=1.4.0"
},
"dependencies": {
"guanlecoja": "~0.8.3",
"gulp": "3.9.0"
}
}

View File

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

View File

@ -0,0 +1,47 @@
#!/usr/bin/env python
#
# 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 __future__ import absolute_import
from __future__ import division
from __future__ import print_function
try:
from buildbot_pkg import setup_www_plugin
except ImportError:
import sys
print("Please install buildbot_pkg module in order to install that package, or use the pre-build .whl modules available on pypi", file=sys.stderr)
sys.exit(1)
setup_www_plugin(
name='yocto-console-view',
description='Yocto Project Console View 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={
'': [
'VERSION',
'static/*'
]
},
entry_points="""
[buildbot.www]
console_view = yocto_console_view:ep
""",
)

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,33 @@
.console.no-select
.load-indicator(ng-hide='c.builds.$resolved && c.changes.$resolved && c.buildrequests.$resolved && c.buildsets.$resolved')
.spinner
i.fa.fa-circle-o-notch.fa-spin.fa-2x
p loading
div(ng-show="c.changes.$resolved && c.filtered_changes.length==0")
p No changes. Console view needs changesource to be setup, and
a(href="#changes") changes
| to be in the system.
table.table.table-striped.table-bordered(ng-hide="c.filtered_changes.length==0" ng-class="{'table-fixedwidth': c.isBigTable()}")
tr.first-row
th.row-header(ng-style="{'width': c.getRowHeaderWidth()}")
i.fa.fa-plus-circle.pull-left(ng-click='c.openAll()' uib-tooltip='Open information for all changes' uib-tooltip-placement='right')
i.fa.fa-minus-circle.pull-left(ng-click='c.closeAll()' uib-tooltip='Close information for all changes' uib-tooltip-placement='right')
th.column(ng-repeat="builder in c.builders")
span.builder(ng-style="{'margin-top': c.getColHeaderHeight()}")
a(ng-href='#/builders/{{ builder.builderid }}'
ng-bind='builder.name')
tr.tag_row(ng-repeat="tag_line in c.tag_lines")
td.row-header
td(ng-repeat="tag in tag_line" colspan="{{tag.colspan}}")
span(uib-tooltip='{{ tag.tag }}' ng-style='{width: tag.colspan*50}') {{tag.tag}}
tr(ng-repeat="change in c.filtered_changes | orderBy: ['-when_timestamp'] track by change.changeid")
td
yoctochangedetails(change="change")
td.column(ng-repeat="builder in change.builders"
title="{{builder.name}}")
a(ng-repeat="build in builder.builds | orderBy: ['number']")
span.badge-status(ng-if='build.buildid'
ng-class="c.results2class(build, 'pulse')"
ng-click='c.selectBuild(build)')
| {{ build.number }}

View File

@ -0,0 +1,375 @@
# 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.builds = @builds = @dataAccessor.getBuilds
property: ["yp_build_revision"]
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
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?
rev = build.properties.yp_build_revision[0]
change = @changesByRevision[rev]
if not change?
change = @changesBySSID[rev]
if not change?
change = @makeFakeChange(rev, build.started_at, rev)
change.caption = "Commit"
change.revlink = "http://git.yoctoproject.org/cgit.cgi/poky/commit/?id=" + rev
change.errorlink = "http://errors.yoctoproject.org/Errors/Latest/Autobuilder/?filter=" + rev + "&type=commit"
else
rev = "Unresolved"
if not change?
change = @makeFakeChange("Unresolved #{build.builderid}-#{build.buildid}", build.started_at, "Unresolved #{build.builderid}-#{build.buildid}")
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,177 @@
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,43 @@
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,6 @@
basefield
label.control-label.col-sm-2(for="{{field.name}}")
| {{field.label}}
.col-sm-10
select.form-control(ng-model="field.value", ng-options="v for v in field.choices")
input.select-editable.form-control(type='text', ng-model="field.value")

View File

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

View File

@ -0,0 +1,9 @@
.modal-big {
.modal-dialog {
width: 80%;
}
.fa {
cursor: pointer;
}
}

View File

@ -0,0 +1,6 @@
// Show build summary for the selected build in a modal window
.modal-header
i.fa.fa-times.pull-right(ng-click='modal.close()')
h4.modal-title Build summary
.modal-body
buildsummary(ng-if='modal.selectedBuild' buildid='modal.selectedBuild.buildid')

View File

@ -0,0 +1,10 @@
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,37 @@
div.yoctochangedetails(style="width:100%;")
div(style="width:100%;", ng-click="change.show_details = !change.show_details")
a(ng-if="change.revlink", ng-href="{{change.revlink}}", uib-tooltip="{{change.comments}}")
| {{ change.caption }} &nbsp;
a(ng-if="change.errorlink", ng-href="{{change.errorlink}}")
| {{ "Errors" }} &nbsp;
span(ng-if="!change.revlink", uib-tooltip="{{change.comments}}")
| {{ change.caption }} &nbsp;
span(ng-if="!compact" uib-tooltip="{{change.when_timestamp | dateformat:'LLL'}}")
| ({{ change.when_timestamp | timeago }}) &nbsp;
i.fa.fa-chevron-circle-right.rotate.clickable(ng-class="{'fa-rotate-90':change.show_details}")
div.anim-changedetails(ng-show="change.show_details")
table.table.table-striped.table-condensed(ng-show="change.show_details")
tr(ng-show="change.author")
td Author
td {{ change.author }}
tr
td Date
td {{ change.when_timestamp | dateformat:'LLL'}} ({{ change.when_timestamp | timeago }})
tr(ng-show="change.repository")
td Repository
td {{ change.repository }}
tr(ng-show="change.branch")
td Branch
td {{ change.branch }}
tr
td Revision
td
a(ng-if="change.revlink", ng-href="{{change.revlink}}")
| {{ change.revision }}
h5 Comment
pre {{ change.comments }}
h5 Changed files
ul
li(ng-repeat='file in change.files') {{file}}
p(ng-hide="change.files.length") No files

View File

@ -0,0 +1,83 @@
@column-width: 40px;
.console {
.table-fixedwidth {
width: initial;
}
.load-indicator {
width: 100%;
height: 100%;
z-index: 900;
background-color: #ffffff;
display: table;
.spinner {
display: table-cell;
vertical-align: middle;
text-align: center;
p {
font-weight: 300;
margin-top: 10px;
}
}
}
.column {
min-width: @column-width;
max-width: @column-width;
width: @column-width;
}
table {
border: none;
}
.tag_row{
td {
margin:0px;
padding:0px;
}
span {
position: relative;
float: left;
font-size: 10px;
overflow: hidden;
text-decoration: none;
white-space: nowrap;
}
}
tr.first-row {
background-color: #fff!important;
th {
border: none;
background-color: #fff !important;
}
.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;
}

View File

@ -0,0 +1,2 @@
# app module is necessary for plugins, but only in the test environment
angular.module("app", []).constant("config", {"url": "foourl"})

3087
yocto_console_view/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
1.1.3.dev66

View File

@ -0,0 +1,11 @@
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")
class ReleaseSelector(ChoiceStringParameter):
spec_attributes = ["selectors"]
type = "releaseselector"
selectors = None

View File

@ -0,0 +1,347 @@
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

View File

@ -0,0 +1 @@
.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}

File diff suppressed because it is too large Load Diff