ExtensionPoint async loading
This commit is contained in:
parent
411a844149
commit
31ecd20548
|
@ -4,8 +4,20 @@ var store = require('./store.js');
|
|||
|
||||
var ExtensionPoint = React.createClass({
|
||||
|
||||
getInitialState: function () {
|
||||
// Initial state is the set of extensions.
|
||||
return {
|
||||
extensions: []
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this._renderAllExtensions();
|
||||
var thisEp = this;
|
||||
store.loadExtensions(this.props.name, function(extensions) {
|
||||
thisEp.setState({
|
||||
extensions: extensions
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
componentDidUpdate: function() {
|
||||
|
@ -16,9 +28,12 @@ var ExtensionPoint = React.createClass({
|
|||
this._unmountAllExtensions();
|
||||
},
|
||||
|
||||
// TODO: resolve possible differences/inconsistencies between render, _renderAllExtensions and _renderExtension
|
||||
// Something looks wrong with all of that... nested render, render, render
|
||||
|
||||
render: function() {
|
||||
var extensionDivs = [];
|
||||
var extensions = store.getExtensions(this.props.name);
|
||||
var extensions = this.state.extensions;
|
||||
|
||||
for (var i = 0; i < extensions.length; i++) {
|
||||
extensionDivs.push(<div key={i}/>);
|
||||
|
@ -67,4 +82,4 @@ ExtensionPoint.propTypes = {
|
|||
wrappingElement: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.element])
|
||||
};
|
||||
|
||||
module.exports = ExtensionPoint;
|
||||
module.exports = ExtensionPoint;
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
//
|
||||
// See https://github.com/jenkinsci/js-builder
|
||||
//
|
||||
require('jenkins-js-builder');
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@jenkins-cd/js-extensions",
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.3",
|
||||
"description": "Jenkins Extension Store",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
@ -15,6 +15,12 @@
|
|||
"react-dom": "^0.14.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"gulp": "^3.9.1",
|
||||
"jenkins-js-builder": "0.0.51",
|
||||
"jenkins-js-test": "^1.0.1",
|
||||
"react-tools": "^0.13.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"jenkins-js-modules": "^1.5.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
[
|
||||
{
|
||||
"extensions": [
|
||||
{
|
||||
"component": "component-1.1",
|
||||
"extensionPoint": "ep-1"
|
||||
},
|
||||
{
|
||||
"component": "component-1.2",
|
||||
"extensionPoint": "ep-1"
|
||||
},
|
||||
{
|
||||
"component": "component-1.3",
|
||||
"extensionPoint": "ep-2"
|
||||
}
|
||||
],
|
||||
"hpiPluginId": "plugin-1"
|
||||
},
|
||||
{
|
||||
"extensions": [
|
||||
{
|
||||
"component": "component-2.1",
|
||||
"extensionPoint": "ep-1"
|
||||
},
|
||||
{
|
||||
"component": "component-2.2",
|
||||
"extensionPoint": "ep-2"
|
||||
},
|
||||
{
|
||||
"component": "component-1.3",
|
||||
"extensionPoint": "ep-2"
|
||||
}
|
||||
],
|
||||
"hpiPluginId": "plugin-2"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,56 @@
|
|||
describe("store.js", function () {
|
||||
|
||||
it("- test ", function (done) {
|
||||
var javaScriptExtensionInfo = require('./javaScriptExtensionInfo-01.json');
|
||||
var store = require('../store');
|
||||
var jsModules = require('jenkins-js-modules');
|
||||
var pluginsLoaded = {};
|
||||
var loaded = 0;
|
||||
|
||||
// Mock the calls to addScript
|
||||
jsModules.addScript = function(scriptUrl, options) {
|
||||
expect(scriptUrl).toBe('io/jenkins/' + options.hpiPluginId + '/jenkins-js-extension.js');
|
||||
// mimic registering of those extensions
|
||||
for(var i1 = 0; i1 < javaScriptExtensionInfo.length; i1++) {
|
||||
var pluginMetadata = javaScriptExtensionInfo[i1];
|
||||
var extensions = pluginMetadata.extensions;
|
||||
for(var i2 = 0; i2 < extensions.length; i2++) {
|
||||
store.addExtension(extensions[i2].component, extensions[i2].extensionPoint);
|
||||
}
|
||||
}
|
||||
if (pluginsLoaded[options.hpiPluginId] === undefined) {
|
||||
pluginsLoaded[options.hpiPluginId] = true;
|
||||
loaded++;
|
||||
}
|
||||
options.success();
|
||||
};
|
||||
|
||||
// Initialise the store with some extension point info. At runtime,
|
||||
// this info will be loaded from <jenkins>/blue/javaScriptExtensionInfo
|
||||
store.setExtensionPointMetadata(javaScriptExtensionInfo);
|
||||
|
||||
// Call load for ExtensionPoint impls 'ep-1'. This should mimic
|
||||
// the store checking all plugins and loading the bundles for any
|
||||
// plugins that define an impl of 'ep-1' (if not already loaded).
|
||||
store.loadExtensions('ep-1', function() {
|
||||
if (loaded === 2) {
|
||||
expect(pluginsLoaded['plugin-1']).toBeDefined();
|
||||
expect(pluginsLoaded['plugin-2']).toBeDefined();
|
||||
|
||||
// if we call load again, nothing should happen as
|
||||
// all plugin bundles have been loaded i.e. loaded
|
||||
// should still be 2 (i.e. unchanged).
|
||||
store.loadExtensions('ep-1', function() {
|
||||
expect(loaded, 2);
|
||||
|
||||
// Calling it yet again for different extension point, but
|
||||
// where the bundles for that extension point have already.
|
||||
store.loadExtensions('ep-2', function() {
|
||||
expect(loaded, 2);
|
||||
done();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,4 +1,22 @@
|
|||
/**
|
||||
* The registered ExtensionPoint instances.
|
||||
*/
|
||||
var points = {};
|
||||
/**
|
||||
* The ExtensionPoint metadata.
|
||||
*/
|
||||
var extensionPointMetadata = [];
|
||||
|
||||
// TODO: Probably need some sort of mechanism to support refreshing of the ExtensionPoint info based on plugin install/uninstall.
|
||||
// We need an eventbus of some sort.
|
||||
|
||||
exports.setExtensionPointMetadata = function(data) {
|
||||
// This data should come from <jenkins>/blue/javaScriptExtensionInfo
|
||||
if (data) {
|
||||
// We clone the data because we add to it.
|
||||
extensionPointMetadata = JSON.parse(JSON.stringify(data));
|
||||
}
|
||||
};
|
||||
|
||||
exports.addExtensionPoint = function(key) {
|
||||
points[key] = points[key] || [];
|
||||
|
@ -9,6 +27,100 @@ exports.addExtension = function (key, extension) {
|
|||
points[key].push(extension);
|
||||
};
|
||||
|
||||
exports.loadExtensions = function(key, onload) {
|
||||
loadBundles(key, function() {
|
||||
var point = points[key];
|
||||
onload(point);
|
||||
});
|
||||
};
|
||||
|
||||
exports.getExtensions = function(key) {
|
||||
return points[key] || [];
|
||||
};
|
||||
};
|
||||
|
||||
function LoadCountMonitor() {
|
||||
this.counter = 0;
|
||||
this.callback = undefined;
|
||||
}
|
||||
LoadCountMonitor.prototype.inc = function() {
|
||||
this.counter++;
|
||||
if (this.callback) {
|
||||
this.callback();
|
||||
}
|
||||
};
|
||||
LoadCountMonitor.prototype.dec = function() {
|
||||
this.counter--;
|
||||
if (this.callback) {
|
||||
this.callback();
|
||||
}
|
||||
};
|
||||
LoadCountMonitor.prototype.onchange = function(callback) {
|
||||
this.callback = callback;
|
||||
};
|
||||
|
||||
function loadBundles(extensionPointId, onBundlesLoaded) {
|
||||
var jsModules = require('jenkins-js-modules');
|
||||
var loadCountMonitor = new LoadCountMonitor();
|
||||
|
||||
function loadPluginBundle(pluginMetadata) {
|
||||
var scriptUrl = 'io/jenkins/' + pluginMetadata.hpiPluginId + '/jenkins-js-extension.js';
|
||||
loadCountMonitor.inc();
|
||||
|
||||
// The plugin bundle for this plugin may already be in the process of loading (async extension
|
||||
// point rendering). If it's not, pluginMetadata.loadCountMonitors will not be undefined,
|
||||
// which means we can go ahead with the async loading. If it is, pluginMetadata.loadCountMonitors
|
||||
// is defined, we just add "this" loadCountMonitor to pluginMetadata.loadCountMonitors.
|
||||
// It will get called as soon as the script loading is complete.
|
||||
if (!pluginMetadata.loadCountMonitors) {
|
||||
pluginMetadata.loadCountMonitors = [];
|
||||
pluginMetadata.loadCountMonitors.push(loadCountMonitor);
|
||||
jsModules.addScript(scriptUrl, {
|
||||
scriptSrcBase: '@adjunct',
|
||||
hpiPluginId: pluginMetadata.hpiPluginId, // Used for testing
|
||||
success: function () {
|
||||
pluginMetadata.bundleLoaded = true;
|
||||
for (var i = 0; i < pluginMetadata.loadCountMonitors.length; i++) {
|
||||
pluginMetadata.loadCountMonitors[i].dec();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
pluginMetadata.loadCountMonitors.push(loadCountMonitor);
|
||||
}
|
||||
}
|
||||
|
||||
function checkLoading() {
|
||||
if (loadCountMonitor.counter === 0) {
|
||||
onBundlesLoaded();
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate over each plugin in extensionPointMetadata, async loading
|
||||
// the extension point .js bundle (if not already loaded) for each of the
|
||||
// plugins that implement the specified extensionPointId.
|
||||
for(var i1 = 0; i1 < extensionPointMetadata.length; i1++) {
|
||||
var pluginMetadata = extensionPointMetadata[i1];
|
||||
var extensions = pluginMetadata.extensions;
|
||||
|
||||
for(var i2 = 0; i2 < extensions.length; i2++) {
|
||||
if (extensions[i2].extensionPoint === extensionPointId) {
|
||||
// This plugin implements the ExtensionPoint.
|
||||
// If we haven't already loaded the extension point
|
||||
// bundle for this plugin, lets load it now.
|
||||
if (!pluginMetadata.bundleLoaded) {
|
||||
loadPluginBundle(pluginMetadata);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Listen to the inc/dec calls now that we've iterated
|
||||
// over all of the plugins.
|
||||
loadCountMonitor.onchange(function() {
|
||||
checkLoading();
|
||||
});
|
||||
|
||||
// Call checkLoading immediately in case all plugin
|
||||
// bundles have been loaded already.
|
||||
checkLoading();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue