Extending Blue Ocean Keith Zantow
About Keith Senior Software Engineer at CloudBees, Inc. Jenkins & Blue Ocean core contributor 15+ years full-stack development Github: kzantow
Intended Audience Development experience Java JavaScript HTML & CSS Node Existing Jenkins plugin development experience or desire to implement a plugin for Blue Ocean
Example Code github.com/kzantow/blueocean-executor-info-plugin
Introduction
Recap: what is Blue Ocean? Re-thinking of the Jenkins user experience Focused on Pipeline Visualisation Creation Editing Extensible, like Jenkins
Blue Ocean
Pipeline Creation
Pipeline Editor
Pipeline Visualization: Execution
Pipeline Visualization: Post-Execution
Pipeline Visualization: Post-Execution
Pipeline Activity
How is it built?
Technologies Used Java (java.sun.com) Maven (maven.apache.org) Stapler (stapler.kohsuke.org) React (facebook.github.io/react) MobX (mobx.js.org) Node * (nodejs.org) Babel * (babeljs.io) LESS * (lesscss.org) too many more to list here * build-time only
React? Node? Wut? React: a modern, well maintained component framework JSX: familiar syntax that aids React development Node ecosystem has tools to expedite front-end development Babel: transpile JSX, ES7 -> ES5
MobX? Makes state management across the React tree easier to understand Automatically updates components when data is updated
js-modules, js-builder & js-extensions Creates bundles containing CommonJS modules js-modules built to load things from Jenkins js-builder is a gulp buld & plugin for Browserify to build js-modules compatible bundles with easy js-extensions is a plugin for js-builder that processes the jenkins-js-extensions.yaml
Building a plugin The basics
Quick starts Yeoman plugin generator-blueocean-usain Maven Archetype almost Copy/pasta Blue Ocean Executor Info Plugin
Project Structure Based on Maven: / / / /.mvn_exec_node - tells Maven to use maven-frontend-plugin package.json - node build info pom.xml - maven build src/main/js / jenkins-js-extension.yaml - defines extensions / src/main/less / extensions.less - defines css rules
pom.xml <project...> <parent> <groupid>org.jenkins-ci.plugins</groupid> <artifactid>plugin</artifactid> <version>2.30</version> </parent> <name>blue Ocean Executor Info</name> <artifactid>blueocean-executor-info-plugin</artifactid> <packaging>hpi</packaging>
pom.xml <properties> <blueocean.version>1.2.0</blueocean.version> </properties> <dependencies> <dependency> <groupid>io.jenkins.blueocean</groupid> <artifactid>blueocean</artifactid> <version>${blueocean.version}</version> </dependency>...
package.json { "name": "blueocean-pipeline-agent-info", "version": "0.0.0", "scripts": { "bundle:watch": "jjsbuilder --tasks bundle:watch", "mvnbuild": "jjsbuilder --tasks bundle", "mvntest": "jjsbuilder --tasks test" },...
package.json "devdependencies": { "@jenkins-cd/blueocean-core-js": "0.0.150", "@jenkins-cd/design-language": "0.0.145", "@jenkins-cd/js-builder": "0.0.59", "@jenkins-cd/js-extensions": "0.0.36",... }, "dependencies": { "@jenkins-cd/js-modules": "0.0.8", }
mvn hpi:run & npm run bundle:watch mvn install hpi:run - builds and runs the plugin npm run bundle:watch - recompile js & less while editing * Gotcha: extension changes won t update without restart
Debugging the UI Chrome developer tools are your friend! Sources transpiled - find unique strings console.log(<object>) js-logging
Plugin Overview
What do we want to do? Write a REST endpoint to provide some data Add a top-level link to a new page Add a simple page that provides access to the data Add something within an existing page Make the extension point live update Use modern ES6/7
Sample plugin: show executor info Provide API that returns executor status & metadata Add a link & page to display all computers & executors Add active executors to a pipeline list (branches grumble)
Constructing the plugin Back-end REST service
REST endpoint import hudson.extension; import io.jenkins.blueocean.rest.organizationroute; import io.jenkins.blueocean.rest.model.resource; import org.kohsuke.stapler.export.exported; import org.kohsuke.stapler.export.exportedbean; @Extension @ExportedBean public class Computers extends Resource implements OrganizationRoute {
REST endpoint @Override public String geturlname() { return "computers"; } @Override public Link getlink() { return getorganization().getlink().rel(geturlname()); }
REST endpoint @Exported(inline=true) public ComputerInfo[] getcomputers() throws Exception { // some code here }... @Exported public String getdisplayname() { return computer.getdisplayname(); }
Provide configuration to the BO UI import io.jenkins.blueocean.commons.pagestatepreloader; @Extension public class ExecutorinfoPreloader extends PageStatePreloader { static boolean showexecutorinfo = // get sys property public String getstatepropertypath() { return "executor"; } public String getstatejson() { return "{showinfo:" + showexecutorinfo + "}"; } }
Provide configuration to the BO UI // In JenkinsScript: import { blueocean } from '@jenkins-cd/blueocean-core-js/dist/js/scopes'; blueocean.executor.showinfo
Constructing the plugin Front-end extensions
Modern Javascript: Babel Add.babelrc { "presets": ["es2015", "stage-0", "react"], "plugins": [ "transform-decorators-legacy" ] }
Find Extension Points Well maintained extension list: git clone https://github.com/jenkinsci/blueocean-plugin cd blueocean-plugin grep -r "extensionpoint=" $(find. -maxdepth 2 -name src)
jenkins-js-extension.yaml # Extensions in this plugin extensions: - component: Routes extensionpoint: jenkins.main.routes - component: TopLink extensionpoint: jenkins.blueocean.top.links...
Routes.jsx import { Route } from 'react-router'; import { ExecutorInfoPage } from './ExecutorInfoPage'; export default ( <Route path="/organizations/:organization/executor-info" component={executorinfopage} /> );
TopLink.jsx import React from 'react'; // Need in all JSX files import { Link } from 'react-router'; // Links to a Route import { AppConfig } from '@jenkins-cd/blueocean-core-js'; export default () => <Link to={`/organizations/${appconfig.getorganizationname()}/executor-i nfo`} title="executor Information">Executors</Link>;
ExecutorInfoService.js import { observable, action } from 'mobx'; import { Fetch, UrlConfig, AppConfig, sseconnection } from '@jenkins-cd/blueocean-core-js'; export class ExecutorInfoService { @observable // makes this property managed by MobX computers; @action setcomputers(computers) { this.computers = computers;
ExecutorInfoService.js import { Fetch, UrlConfig, AppConfig, sseconnection } from '@jenkins-cd/blueocean-core-js'; constructor() { this.fetchexecutorinfo(); sseconnection.subscribe('pipeline', event => { switch (event.jenkins_event) { case 'pipeline_block_start': case 'pipeline_block_end': { this.fetchexecutorinfo(); }
ExecutorInfoService.js import { Fetch, UrlConfig, AppConfig, sseconnection } from '@jenkins-cd/blueocean-core-js'; fetchexecutorinfo() { Fetch.fetchJSON(`${UrlConfig.getRestBaseURL()}/organizations/${Ap pconfig.getorganizationname()}/computers/`).then(response => { this.setcomputers(response.computers); }); }
ExecutorInfoService.js // Export an instance to be shared so sseconnection.subscribe // not called multiple times export default new ExecutorInfoService();
ExecutorInfoPage.jsx import { observer } from 'mobx-react'; @observer // MobX magic automatically re-render for observable export class ExecutorInfoPage extends React.Component { render() { return ( <JTable columns={columns} classname="executor-info-table"> <TableHeaderRow />...
ExecutorInfoPage.jsx import executorinfoservice from './ExecutorInfoService';... render() {... {executorinfoservice.computers && // important pattern or errors executorinfoservice.computers.map(computer => [ <TableRow>...
Defining Extension Points
Defining Extension Points Extensions object from js-extensions Extensions.Renderer renders all React component extensions extensionpoint property optional filter attribute for various things like ordinals all other properties passed through to the extensions
Defining Extension Points import Extensions from '@jenkins-cd/js-extensions';... render() {... <Extensions.Renderer extensionpoint="jenkins.pipeline.run.list.display" filter={sortbyordinal} run={branch} // passed to the extensions
Demo
Future Improvements * In the pipeline * subject to change
Improvements Finding ExtensionPoints Allow developers to run in a developmentmode UI hints/info where UI extension points are Generate documentation from source
Improvements Defining Extensions Remove jenkins-js-extensions.yaml Provide decorators Allow more strongly typed extension points & inheritence Investigating providing TypeScript interface files
Improved Code Sharing Allow plugins to export arbitrary javascript objects Allow plugins to import from other plugins without npm
Questions?
Resources github.com/jenkinsci/blueocean-plugin github.com/kzantow/blueocean-executor-info-plugin