Skip to content

Commit

Permalink
cleanup the angular integration
Browse files Browse the repository at this point in the history
in order to integrate properly in angular.js, we need to use a directive.
This patch uses a directive, and remove the class stuff, so that it is more angularish.
  • Loading branch information
tardyp committed Mar 22, 2019
1 parent d290654 commit c8ac982
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 86 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
build/*
*.log
*.lock
node_modules/*
buildbot_react_plugin_boilerplate/*
!/buildbot_react_plugin_boilerplate/__init__.py
Expand Down
26 changes: 13 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,29 @@ iterate:

```webpack --watch```

## Why?
## Why?

The tech used for the Buildbot 9 web UI is already starting to show its age. Currently it is using:

* **gulp** as a build system
* **coffeescript** as an alternative to javascript
* **AngularJS** which has been superceded by Angular 2, which is nearly a full rewrite

The idea behind this repo is to establish a way to use newer web tech for your buildbot plugin, even though buildbot is (for now at least) sticking with these older technologies. It's totally possible to mix the new and the old.
The idea behind this repo is to establish a way to use newer web tech for your buildbot plugin, even though buildbot is (for now at least) sticking with these older technologies. It's totally possible to mix the new and the old.

In my own case, I was driven in this direction by performance issues with Angular that I could not find any good way to get past, besides just not using Angular.
In my own case, I was driven in this direction by performance issues with Angular that I could not find any good way to get past, besides just not using Angular.

This plugin sample is using:

* **Webpack** as the building/bundling solution
* **React** cooperating together with the existing **Angular** setup
* **Typescript** to provide additional compile time type checking and IDE assistance, though you can easily write in plain JS if you like
* **Typescript** to provide additional compile time type checking and IDE assistance, though you can easily write in plain JS if you like

## Developing A Buildbot Plugin Locally

You *could* develop your new plugin directly on your buildbot server, but you may not want to for various reasons. Fortunately there's a good workflow where you can develop locally using data from a remote server.

In my case, I'm developing my plugin on a Windows machine for deployment to a buildbot server that's running Ubuntu, so it's possible to develop in whatever OS environment you are comfortable.
In my case, I'm developing my plugin on a Windows machine for deployment to a buildbot server that's running Ubuntu, so it's possible to develop in whatever OS environment you are comfortable.

These notes are somewhat based on [Pierre Tardy's tutorial](https://medium.com/buildbot/buildbot-ui-plugin-for-python-developer-ef9dcfdedac0), also on [Running buildbot with VirtualEnv](http://trac.buildbot.net/wiki/RunningBuildbotWithVirtualEnv)

Expand Down Expand Up @@ -98,7 +98,7 @@ get this repo

```git clone https://github.com/uglycoyote/buildbot-react-plugin-boilerplate.git```

change everywhere the name of this plugin occurs to whatever you want to call your new plugin. You probably want to do this before you ```pip install``` the thing, at least the parts which are going to influence the python name of the plugin (names on the javascript side can be changed after you have it working). The names relevant to python are
change everywhere the name of this plugin occurs to whatever you want to call your new plugin. You probably want to do this before you ```pip install``` the thing, at least the parts which are going to influence the python name of the plugin (names on the javascript side can be changed after you have it working). The names relevant to python are

* any occurrences in setup.py
* the subdirectory ```buildbot_react_plugin_boilerplate```, containing the ```__init__.py file```
Expand All @@ -110,7 +110,7 @@ then...

```
npm install
webpack
npm run webpack
pip install -e .
```

Expand All @@ -130,14 +130,14 @@ On the javascript side, you'll need to rename the following
* within this class change the name under ```// name of the state```
* change the ```caption```s under "Menu Configuration" and "Configuration"
* The class ending with ```Controller```
* ```// Register new state```
* ```// Register new state```
* change the URL
* change the controller class name to match your new Controller's name
* At the end of the file, there's some lines which register those classes with Angular, which must be changed to reflect the new names of the classes.

## How React and Angular are Fitting Together

Buildbot's UI is based an Angular, but this plugin shows how it is possible have React cooperating with Angular.
Buildbot's UI is based an Angular, but this plugin shows how it is possible have React cooperating with Angular.

The structure of other buildbot plugins is essentially

Expand All @@ -148,17 +148,17 @@ The structure of other buildbot plugins is essentially

In this repo, the setup is similar, except

* rather than using HTML templates, most of the HTML is being produced by a hierarchy of **React Components**
* rather than using HTML templates, most of the HTML is being produced by a hierarchy of **React Components**
* the top level React Component gets its data (its **props**) from the Angular Controller, which calls ```ReactDOM.render``` to invoke React to update


* The Controller is a bit slimmer, since more of the view logic is contained in the React components. The controller's main role now is to get the data from the services and forward it as **props** to React. The controller may want to massage the data a bit get it into a form that's more appropriate for the view. Ideally the **props** should only change when you want the view to change, so if there's extra data returned by the ```dataService``` that your view doesn't need, the controller can play the role of filtering that into a more view-relevant form.

### Taking Advantage of Typescript

I have included a file ```BuildbotJsonInterfaces.ts``` containing a bunch of interface descriptions for the data which is available using the JSON API. These interfaces are automatically generated by requesting URL's such as http://nine.buildbot.net/api/v2/changes?limit=10 and then using [json2ts](https://github.com/GregorBiswanger/json2ts) to convert the json into typescript interfaces.
I have included a file ```BuildbotJsonInterfaces.ts``` containing a bunch of interface descriptions for the data which is available using the JSON API. These interfaces are automatically generated by requesting URL's such as http://nine.buildbot.net/api/v2/changes?limit=10 and then using [json2ts](https://github.com/GregorBiswanger/json2ts) to convert the json into typescript interfaces.

By importing the interfaces using
By importing the interfaces using

```import {Change, Build} from 'BuildbotJsonInterface'```

Expand All @@ -170,4 +170,4 @@ By importing the interfaces using



![](https://github.com/uglycoyote/buildbot-react-plugin-boilerplate/blob/master/documentation/Error.png?raw=true)
![](https://github.com/uglycoyote/buildbot-react-plugin-boilerplate/blob/master/documentation/Error.png?raw=true)
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
"email": "[email protected]",
"url": ""
},
"scripts": {
"watch": "webpack --watch",
"build": "webpack"
},
"keywords": [],
"dependencies": {
"@types/react": "^16.0.5",
Expand Down
120 changes: 47 additions & 73 deletions src/Main.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,20 @@

import { SampleReactComponent } from "./SampleReactComponent"
import {
SampleReactComponent
} from "./SampleReactComponent"
import * as React from 'react';
import * as ReactDOM from 'react-dom';

console.log ( "Hello from the buildbot-react-plugin-boilerplate!" )

// Register new module
class ReactPluginBoilerplate {
constructor() {
return [
'ui.router',
'ui.bootstrap',
'ui.bootstrap.popover',
'ngAnimate',
'guanlecoja.ui',
'bbData'
];
}
}
console.log("Hello from the buildbot-react-plugin-boilerplate!")

class ReactPluginBoilerplateConfig {
constructor($stateProvider, glMenuServiceProvider, bbSettingsServiceProvider, config) {
var module = angular.module('buildbot_react_plugin_boilerplate', ['ui.router',
'ui.bootstrap',
'ui.bootstrap.popover',
'ngAnimate',
'guanlecoja.ui',
'bbData'
])
module.config(['$stateProvider', 'glMenuServiceProvider', 'bbSettingsServiceProvider', 'config',
($stateProvider, glMenuServiceProvider, bbSettingsServiceProvider, config) => {

// Config object coming in from the master.cfg
//console.log( "config", config )
Expand All @@ -44,9 +38,7 @@ class ReactPluginBoilerplateConfig {

// Register new state
const state = {
controller: "reactPluginBoilerplateController",
controllerAs: "c",
template: "<div id='reactContent'></div>",
template: "<my-react-directive></my-react-directive>",
//templateUrl: `react_plugin_boilerplate/views/${name}.html`,
name,
url: "/reactPluginBoilerplate",
Expand All @@ -55,7 +47,7 @@ class ReactPluginBoilerplateConfig {

$stateProvider.state(state);

// bbSettingsServiceProvider.addSettingsGroup({
// bbSettingsServiceProvider.addSettingsGroup({
// name: 'reactPluginBoilerplate',
// caption: 'React Plugin Boilerplate related settings',
// items: [{
Expand All @@ -66,54 +58,36 @@ class ReactPluginBoilerplateConfig {
// }
// ]});
}
}

class ReactPluginBoilerplateController {
constructor($scope, $element, $q, $window, dataService, bbSettingsService, resultsService,
$uibModal, $timeout) {
// Find the div.reactContent in the template (note, this is some kind
// of angular data structure, not an actual dom element.
const reactContentElement = $element.find('#reactContent');
// This is an actual DOM element that React needs
this.reactRawElement = angular.element(reactContentElement).get(0);

this.dataAccessor = dataService.open().closeOnDestroy($scope)

this.changeLimit = 50;

this.changes = this.dataAccessor.getChanges({limit: this.changeLimit, order: '-changeid'})

this.changes.onChange = () => this.update()

this.renderReact();

}

update() {
console.log ("Updating React View");

// your plugin might do some stuff in here to massage the data into a more
// view-amenable form before calling the render function

this.renderReact();
])
module.directive('myReactDirective', ['$q', '$window', 'dataService', 'bbSettingsService', 'resultsService', '$uibModal', '$timeout',
($q, $window, dataService, bbSettingsService, resultsService,
$uibModal, $timeout) => {
function link(scope, element, attrs) {

/* create an instance of the data accessor */
var dataAccessor = dataService.open().closeOnDestroy(scope);

/* get some changes and put the in the react properties */
var changes = dataAccessor.getChanges({
limit: 50,
order: '-changeid'
})
var props = {
changes: changes
}
var react_element = React.createElement(SampleReactComponent, props, null)

function update() {
ReactDOM.render(
react_element,
element.get(0));
}
changes.onChange = () => update()
update()
}

return {
link: link
};
}

renderReact() {
var props = {changes: this.changes}

ReactDOM.render(
React.createElement(SampleReactComponent, props, null),
this.reactRawElement
);
}


}



angular.module('buildbot_react_plugin_boilerplate', new ReactPluginBoilerplate())
.config(['$stateProvider', 'glMenuServiceProvider', 'bbSettingsServiceProvider', 'config', ReactPluginBoilerplateConfig])
.controller('reactPluginBoilerplateController', ['$scope', '$element', '$q', '$window', 'dataService', 'bbSettingsService', 'resultsService', '$uibModal', '$timeout', ReactPluginBoilerplateController]);

])

0 comments on commit c8ac982

Please sign in to comment.