Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1683 from yetanotherion/nested
forcedialog: integrate children of nested fields
- Loading branch information
Showing
12 changed files
with
344 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
This plugin permits to create two linked UI inputs that can be integrated | ||
in the force dialog: | ||
- pizza: a text field where the name of the pizza (lower or uppercase) | ||
can be written, | ||
- ingredients: a select input where the ingredients to make the pizza | ||
described in the input above can be selected. This input is automatically | ||
populated via a custom webservice provided by the plugin. | ||
|
||
The force dialog is the UI element that is displayed | ||
when one clicks on the FORCE button associated to a ForceScheduler. | ||
|
||
More precisely the code is composed of two parts: | ||
- python: | ||
* buildbot_nestedexample/__init__.py: definition of NestedExample, | ||
child of buildbot.schedulers.forcesched.NestedParameter. | ||
The two UI elements are embedded in the fields attribute, | ||
and linked to the coffee/jade code by means of its type="nestedexample". | ||
* buildbot_nestedexample/api.py: define the "getIngredients" endpoint | ||
that returns the ingredients necessary to make one pizza. | ||
- coffee: | ||
* plugin boilerplate: | ||
guanlecoja/config.coffee | ||
gulpfile.js | ||
package.json | ||
setup.py | ||
* ui logic: | ||
src/module/nestedexamplefield.directive.coffee | ||
src/module/nestedexamplefield.tpl.jade | ||
|
||
Note that the name of the files match, as they must, the following naming | ||
convention: | ||
<type>field.directive.coffee | ||
<type>field.tpl.jade | ||
|
||
Regarding the coffee code, the only non standard angular code, is the one | ||
that permits: | ||
* ...directive.coffee: to extract the embedded elements from the scope, and | ||
* ...tpl.jade: to communicate to the two basefield-s where the embedded elements are. | ||
This permits to benefit from the error displaying features provided | ||
by basefield. | ||
Please have a look at the commentaries in the code for more details. | ||
|
||
To activate that plugin in one buildbot instance, | ||
one should: | ||
- add that UI element in the ForceScheduler like, | ||
|
||
... | ||
from buildbot_nestedexample import NestedExample | ||
from buildbot.schedulers.forcesched import ForceScheduler | ||
|
||
ForceScheduler(codebases=[CodebaseParameter(codebase="", | ||
branch=FixedParameter(name="branch", default=""), | ||
revision=FixedParameter(name="revision", default=""), | ||
repository=FixedParameter(name="repository", default=""), | ||
project=FixedParameter(name="project", default=""))], | ||
reason=StringParameter(name="reason", | ||
default=""), | ||
properties=[NestedExample(required=True, | ||
default="", | ||
size=80)]) | ||
|
||
- and activate the plugin in the buildbot configuration, | ||
|
||
c['www'] = dict(... | ||
plugins=dict(nestedexample={})) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
from twisted.internet import defer | ||
|
||
from buildbot.www.plugin import Application | ||
from buildbot.schedulers.forcesched import NestedParameter | ||
from buildbot.schedulers.forcesched import StringParameter | ||
from buildbot.schedulers.forcesched import ChoiceStringParameter | ||
from buildbot.schedulers.forcesched import ValidationError | ||
|
||
from .api import Api | ||
|
||
|
||
class NestedExample(NestedParameter): | ||
|
||
"""UI zone""" | ||
type = "nestedexample" | ||
PIZZA = "pizza" | ||
INGREDIENTS = "ingredients" | ||
|
||
def __init__(self, **kw): | ||
pizzaInput = StringParameter(label="type the name of your pizza", | ||
name=self.PIZZA, | ||
required=True) | ||
ingredientsInput = ChoiceStringParameter(name=self.INGREDIENTS, | ||
label="ingredients necessary to make the pizza", | ||
multiple=True, | ||
strict=False, | ||
default="", | ||
choices=[]) | ||
self.params = {self.PIZZA: pizzaInput, | ||
self.INGREDIENTS: ingredientsInput} | ||
self.allIngredients = set(sum([ingr for ingr in Api.pizzaIngredients.values()], | ||
[])) | ||
fields = self.params.values() | ||
super(NestedExample, self).__init__(self.type, label='', fields=fields, **kw) | ||
|
||
def createNestedPropertyName(self, propertyName): | ||
return "{}_{}".format(self.type, propertyName) | ||
|
||
@defer.inlineCallbacks | ||
def validateProperties(self, collector, properties): | ||
# we implement the check between the input and | ||
# the ingredients | ||
if properties[self.INGREDIENTS] not in self.allIngredients or\ | ||
not properties[self.PIZZA]: | ||
# we trigger a specific error message in PIZZA only | ||
def f(): | ||
return defer.fail(ValidationError('Invalid pizza')) | ||
nestedProp = self.createNestedPropertyName(self.PIZZA) | ||
yield collector.collectValidationErrors(nestedProp, f) | ||
|
||
@defer.inlineCallbacks | ||
def updateFromKwargs(self, kwargs, properties, collector, **kw): | ||
yield super(NestedExample, self).updateFromKwargs(kwargs, properties, collector, **kw) | ||
# the properties we have are in the form | ||
# {nestedexample: {input: <url>, | ||
# ingredients: <ingredients>}} | ||
# we just flatten the dict to have | ||
# - input, and | ||
# - ingredients | ||
# in properties | ||
for prop, val in properties.pop(self.type).iteritems(): | ||
properties[prop] = val | ||
yield self.validateProperties(collector, properties) | ||
|
||
|
||
# create the interface for the setuptools entry point | ||
ep = Application(__name__, "Buildbot nested parameter example") | ||
api = Api(ep) | ||
ep.resource.putChild("api", api.app.resource()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import json | ||
|
||
from klein import Klein | ||
from twisted.internet import defer | ||
|
||
|
||
class Api(object): | ||
app = Klein() | ||
pizzaIngredients = {'margherita': ['tomato', 'ham', 'cheese'], | ||
'regina': ['tomato', 'ham', 'cheese', 'mushrooms']} | ||
|
||
def __init__(self, ep): | ||
self.ep = ep | ||
|
||
@app.route("/getIngredients", methods=['GET']) | ||
def getIngredients(self, request): | ||
pizzaArgument = request.args.get('pizza') | ||
if pizzaArgument is None: | ||
return defer.succeed(json.dumps("invalid request")) | ||
pizza = pizzaArgument[0].lower() | ||
res = self.pizzaIngredients.get(pizza, | ||
["only {} are supported " | ||
"for now".format(self.pizzaIngredients.keys())]) | ||
return defer.succeed(json.dumps(res)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
### ############################################################################################### | ||
# | ||
# This module contains all configuration for the build process | ||
# | ||
### ############################################################################################### | ||
ANGULAR_TAG = "~1.3.0" | ||
|
||
config = | ||
|
||
### ########################################################################################### | ||
# Name of the plugin | ||
### ########################################################################################### | ||
name: 'nestedexample' | ||
|
||
|
||
### ########################################################################################### | ||
# Directories | ||
### ########################################################################################### | ||
dir: | ||
# The build folder is where the app resides once it's completely built | ||
build: 'buildbot_nestedexample/static' | ||
|
||
### ########################################################################################### | ||
# Bower dependancies configuration | ||
### ########################################################################################### | ||
bower: | ||
testdeps: | ||
jquery: | ||
version: '2.1.1' | ||
files: 'dist/jquery.js' | ||
angular: | ||
version: ANGULAR_TAG | ||
files: 'angular.js' | ||
lodash: | ||
version: "~2.4.1" | ||
files: 'dist/lodash.js' | ||
"angular-mocks": | ||
version: ANGULAR_TAG | ||
files: "angular-mocks.js" | ||
|
||
buildtasks: ['scripts', 'styles', 'fonts', 'imgs', | ||
'index', 'tests', 'generatedfixtures', 'fixtures'] | ||
|
||
karma: | ||
# we put tests first, so that we have angular, and fake app defined | ||
files: ["tests.js", "scripts.js", 'fixtures.js', "mode-python.js"] | ||
module.exports = config |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
require("guanlecoja")(require("gulp")) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"name": "buildbot-nestedexample", | ||
"engines": { | ||
"node": ">=0.10.0", | ||
"npm": ">=1.4.0" | ||
}, | ||
"devDependencies": { | ||
"guanlecoja": "~0.3.5" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
#!/usr/bin/env python | ||
try: | ||
from buildbot_pkg import setup_www_plugin | ||
except ImportError: | ||
import sys | ||
print >> sys.stderr, "Please install buildbot_pkg module in order to install that package, or use the pre-build .whl modules available on pypi" | ||
sys.exit(1) | ||
|
||
setup_www_plugin( | ||
name='buildbot-nestedexample', | ||
description='"An example of a custom nested parameter"', | ||
author=u'Ion Alberdi', | ||
author_email=u'ialberdi@intel.com', | ||
url='http://buildbot.net/', | ||
license='GNU GPL', | ||
version='0.0.1', | ||
packages=['buildbot_nestedexample'], | ||
install_requires=[ | ||
'klein' | ||
], | ||
package_data={ | ||
'': [ | ||
'VERSION', | ||
'static/*' | ||
] | ||
}, | ||
entry_points=""" | ||
[buildbot.www] | ||
nestedexample = buildbot_nestedexample:ep | ||
""", | ||
) |
70 changes: 70 additions & 0 deletions
70
www/nestedexample/src/module/nestedexamplefield.directive.coffee
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
# Register new module | ||
class Nestedexample extends App | ||
constructor: -> return [ | ||
'common' | ||
] | ||
|
||
class Nestedexamplefield extends Directive | ||
constructor: -> | ||
return { | ||
replace: false | ||
restrict: 'E' | ||
scope: false | ||
templateUrl: "nestedexample/views/nestedexamplefield.html" | ||
controller: '_nestedexamplefieldController' | ||
} | ||
|
||
class _nestedexamplefield extends Controller | ||
constructor: ($scope, $http) -> | ||
# boilerplate to extract our two embedded | ||
# UI elements | ||
|
||
# the name of the embedded UI elements | ||
# are prefixed by the type of the root | ||
# element, "nestedexample" in our case. | ||
# This method permits to compute that | ||
# prefixed name. | ||
createNestedName = (name) -> | ||
return "nestedexample_#{name}" | ||
|
||
# utility method to find the embedded | ||
# field from the scope | ||
findNestedElement = (name) -> | ||
nameInNestedField = createNestedName(name) | ||
res = undefined | ||
$scope.field.fields.forEach (v, i) -> | ||
if v.fullName == nameInNestedField | ||
res = v | ||
return | ||
return res | ||
|
||
# we put our two embedded fields in the scope | ||
$scope.pizza = findNestedElement('pizza') | ||
$scope.ingredients = findNestedElement('ingredients') | ||
|
||
# function that will be called each time a change | ||
# event happens in the pizza input. | ||
ingredientsUrl = (pizza) -> | ||
return "nestedexample/api/getIngredients?pizza=#{pizza}" | ||
|
||
updateValues = (pizza) -> | ||
if pizza == "" | ||
$scope.ingredients.choices = [] | ||
$scope.ingredients.value = "" | ||
return | ||
$http.get(ingredientsUrl(pizza)).then (r) -> | ||
if r.status == 200 | ||
if r.data.error? | ||
$scope.ingredients.choices = [r.data.error] | ||
else | ||
$scope.ingredients.choices = r.data | ||
else | ||
error = "unexpected error got #{r.status}" | ||
$scope.ingredients.choices = [error] | ||
if $scope.ingredients.choices.length > 0 | ||
$scope.ingredients.value = $scope.ingredients.choices[0] | ||
else | ||
$scope.ingredients.value = "" | ||
|
||
$scope.getIngredients = () -> | ||
updateValues($scope.pizza.value) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
div | ||
//- basefield is an angular-js directive provided by buildbot-nine. | ||
//- (ng-init="field=pizza") (ng-init="field=ingredients") | ||
//- are necessary to benefit | ||
//- from the error reporting mechanism provided by basefield | ||
//- in our embedded UI elements. They just say that the field | ||
//- element to process is not the root element (here nestedexample) but | ||
//- each of the embedded elements. | ||
basefield(ng-init="field=pizza") | ||
label.control-label.col-sm-2(for="{{pizza.name}}") | ||
| {{pizza.label}} | ||
.col-sm-10 | ||
input.form-control(type='text', | ||
name="{{pizza.name}}", | ||
ng-model="pizza.value", | ||
ng-change="getIngredients()") | ||
basefield(ng-init="field=ingredients") | ||
label.control-label.col-sm-2(for="{{ingredients.name}}") | ||
| {{ingredients.label}} | ||
.col-sm-10 | ||
select.form-control(name="{{ingredients.name}}", | ||
ng-model="ingredients.value", | ||
ng-options="v for v in ingredients.choices") |
Empty file.