Ext.define('RallyBuildLight', {
    extend: 'Rally.app.App',
    requires: [
        'Ext.data.JsonP',
        'Ext.util.TaskManager',
        'Rally.ui.NumberField'
    ],
    componentCls: 'app',

    /*
     * @private
     * @property
     * Classname to indicate the build light should blink.
     */
    blinkCls: 'blinkme',

    /*
     * @private
     * @property
     * Status name constants.
     */
    statuses: {
        INCOMPLETE: 'INCOMPLETE',
        SUCCESS: 'SUCCESS'
    },

    layout: 'fit',

    items: [{
        itemId: 'statusIndicator',
        cls: 'status-indicator',
        border: false,
        bodyBorder: false
    }],

    initComponent: function() {
        this.callParent(arguments);

        this.on('afterrender', function() {
            this.mon(this.down('#statusIndicator').getEl(), 'click', this._onStatusIndicatorClick, this);
        }, this, {single: true});
    },

    launch: function() {
        this.setLoading(true);
        this._startPoll();
    },

    getSettingsFields: function() {
        return [{
            xtype: 'rallynumberfield',
            name: 'interval',
            label: 'Refresh Interval (seconds)',
            value: 15
        }, {
            xtype: 'rallytextfield',
            name: 'jobUrl',
            label: 'Jenkins Job URL',
            value: 'http://jenkins.server/job/jobname',
            width: 400
        }];
    },

    _startPoll: function() {
        if (this.pollTask) {
            this.pollTask.destroy();
        }

        this.pollTask = Ext.util.TaskManager.start({
            run: this._poll,
            scope: this,
            interval: (parseInt(this.getSettings().interval, 10) || 15) * 1000
        }) ;
    },

    _poll: function() {
        var jobStatuses = this._createJobStatuses(),
            me = this;

        _.each(jobStatuses, function(jobStatus) {
            Ext.data.JsonP.request({
                url: jobStatus.apiUrl,
                callbackKey: 'jsonp',
                success: function(response) {
                    jobStatus.data = response;
                    me._checkJobStatus(jobStatuses);
                },
                failure: function() {
                    if (!me.notifiedError) {
                        Rally.ui.notify.Notifier.showError({message: 'Unable to contact Jenkins: ' + jobStatus.apiUrl});
                        me.notifiedError = true; // avoid spewing notifications on every poll.
                    }
                }
            });
        });
    },

    _checkJobStatus: function(jobStatuses) {
        var status = this._getJobStatus(jobStatuses),
            statusIndicator;

        if (status !== this.statuses.INCOMPLETE) {
            statusIndicator = this.down('#statusIndicator');
            if (this.currentStatus) {
                statusIndicator.removeCls(this.currentStatus);
            }
            statusIndicator.addCls(status);
            this.currentStatus = status;
            this.jobStatuses = jobStatuses;

            if (this._isSuccessOrClaimed()) {
                statusIndicator.removeCls(this.blinkCls);
            } else {
                statusIndicator.addCls(this.blinkCls);
            }

            this.setLoading(false);
        }
    },

    _isSuccessOrClaimed: function() {
        if (this.currentStatus !== this.statuses.SUCCESS) {
            return _.some(this.jobStatuses, function(jobStatus) {
                if (!jobStatus.data) {
                    return false;
                }

                return jobStatus.data.result === this.statuses.SUCCESS || !jobStatus.data.description.match(/claim this build/);
            }, this);
        }

        return true;
    },

    _getJobStatus: function(jobStatuses) {
        var i, jobStatus, result;

        for (i = 0; i < jobStatuses.length; i++) {
            jobStatus = jobStatuses[i];

            if (!jobStatus.data || !jobStatus.data.result) {
                return this.statuses.INCOMPLETE;
            }

            if (jobStatus.data.result !== this.statuses.SUCCESS) {
                return jobStatus.data.result;
            }
        }

        return this.statuses.SUCCESS;
    },

    _getApiUrl: function(jobUrl) {
        return jobUrl.replace(/^\s+|\s+$/g, '') + '/lastCompletedBuild/api/json';
    },

    _createJobStatuses: function(jobUrls) {
        var jobUrls = (this.getSettings().jobUrl || '').split(',');

        return _.map(jobUrls, function(jobUrl) {
            return {
                url: jobUrl,
                apiUrl: this._getApiUrl(jobUrl)
            };
        }, this);
    },

    _onStatusIndicatorClick: function() {
        _.every(this.jobStatuses, function(jobStatus) {
            if (jobStatus.data && jobStatus.data.result === this.currentStatus) {
                window.open(jobStatus.url + '/lastCompletedBuild', '_blank');
            }
        }, this);
    }
});

@@ -484,24 +484,165 @@
 SS'%0A    %7D,%0A%0A
+    config: %7B%0A        defaultSettings: %7B%0A            interval: 15,%0A            jobUrl: 'http://jenkins.server/job/jobname'%0A        %7D%0A    %7D,%0A%0A
     layout: 
@@ -1317,30 +1317,8 @@
 )',%0A
-            value: 15%0A
     
@@ -1350,22 +1350,16 @@
 e: '
-rallytextfield
+textarea
 ',%0A 
@@ -1427,64 +1427,8 @@
 L',%0A
-            value: 'http://jenkins.server/job/jobname',%0A
     

Improve settings.