You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by ma...@apache.org on 2017/03/24 15:10:50 UTC

[03/17] nifi git commit: NIFI-3380 Bumping NAR plugin to 1.2.0-SNAPSHOT development to leverage changes from master, adding buildnumber-maven-plugin to nifi-nar-bundles to properly set build info in MANIFEST of NARs - Refactoring NarDetails to include al

http://git-wip-us.apache.org/repos/asf/nifi/blob/d90cf846/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-component-version.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-component-version.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-component-version.js
new file mode 100644
index 0000000..2100649
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-component-version.js
@@ -0,0 +1,341 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* global nf */
+
+/**
+ * Views state for a given component.
+ */
+(function (root, factory) {
+    if (typeof define === 'function' && define.amd) {
+        define(['jquery',
+                'nf.ErrorHandler',
+                'nf.Common',
+                'nf.Client',
+                'nf.CanvasUtils',
+                'nf.ProcessGroupConfiguration',
+                'nf.ng.Bridge'],
+            function ($, Slick, nfErrorHandler, nfCommon, nfClient, nfCanvasUtils, nfProcessGroupConfiguration, nfNgBridge) {
+                return (nf.ComponentState = factory($, nfErrorHandler, nfCommon, nfClient, nfCanvasUtils, nfProcessGroupConfiguration, nfNgBridge));
+            });
+    } else if (typeof exports === 'object' && typeof module === 'object') {
+        module.exports = (nf.ComponentState =
+            factory(require('jquery'),
+                require('nf.ErrorHandler'),
+                require('nf.Common',
+                require('nf.Client'),
+                require('nf.CanvasUtils'),
+                require('nf.ProcessGroupConfiguration'),
+                require('nf.ng.Bridge'))));
+    } else {
+        nf.ComponentVersion = factory(root.$,
+            root.nf.ErrorHandler,
+            root.nf.Common,
+            root.nf.Client,
+            root.nf.CanvasUtils,
+            root.nf.ProcessGroupConfiguration,
+            root.nf.ng.Bridge);
+    }
+}(this, function ($, nfErrorHandler, nfCommon, nfClient, nfCanvasUtils, nfProcessGroupConfiguration, nfNgBridge) {
+    'use strict';
+
+    var versionMap;
+    var nfSettings;
+
+    /**
+     * Gets the URI for retrieving available types/bundles
+     *
+     * @param {object} componentEntity
+     * @returns {string} uri
+     */
+    var getTypeUri = function (componentEntity) {
+        if (componentEntity.type === 'ReportingTask') {
+            return '../nifi-api/flow/reporting-task-types';
+        } else if (componentEntity.type === 'ControllerService') {
+            return '../nifi-api/flow/controller-service-types';
+        } else {
+            return '../nifi-api/flow/processor-types';
+        }
+    };
+
+    /**
+     * Gets the field to use to access the returned types/bundles.
+     *
+     * @param {object} componentEntity
+     * @returns {string} field
+     */
+    var getTypeField = function (componentEntity) {
+        if (componentEntity.type === 'ReportingTask') {
+            return 'reportingTaskTypes';
+        } else if (componentEntity.type === 'ControllerService') {
+            return 'controllerServiceTypes';
+        } else {
+            return 'processorTypes';
+        }
+    };
+
+    /**
+     * Reset the dialog.
+     */
+    var resetDialog = function () {
+        // clear the versions
+        var versions = versionMap.keys();
+        $.each(versions, function (_, version) {
+            versionMap.remove(version);
+        });
+
+        // clear the service apis
+        $('#component-version-controller-service-apis').empty();
+        $('#component-version-controller-service-apis-container').hide();
+
+        // clear the fields
+        $('#component-version-name').text('');
+        $('#component-version-bundle').text('');
+        $('#component-version-tags').text('');
+        $('#component-version-restriction').removeClass('unset').text('');
+        $('#component-version-description').text('');
+
+        // destroy the version combo
+        $('#component-version-selector').combo('destroy');
+
+        // removed the stored data
+        $('#component-version-dialog').removeData('component');
+    };
+
+    /**
+     * Sets the specified option.
+     *
+     * @param {object} selectedOption
+     */
+    var select = function (selectedOption) {
+        var documentedType = versionMap.get(selectedOption.value);
+
+        // set any restriction
+        if (nfCommon.isDefinedAndNotNull(documentedType.usageRestriction)) {
+            $('#component-version-restriction').text(documentedType.usageRestriction);
+        } else {
+            $('#component-version-restriction').addClass('unset').text('No restriction');
+        }
+
+        // update the service apis if necessary
+        if (!nfCommon.isEmpty(documentedType.controllerServiceApis)) {
+            var formattedControllerServiceApis = nfCommon.getFormattedServiceApis(documentedType.controllerServiceApis);
+            var serviceTips = nfCommon.formatUnorderedList(formattedControllerServiceApis);
+            $('#component-version-controller-service-apis').empty().append(serviceTips);
+            $('#component-version-controller-service-apis-container').show();
+        }
+
+        // update the tags and description
+        $('#component-version-tags').text(documentedType.tags.join(', '));
+        $('#component-version-description').text(documentedType.description);
+    };
+
+    return {
+        init: function (settings) {
+            versionMap = d3.map();
+            nfSettings = settings;
+
+            // initialize the component version dialog
+            $('#component-version-dialog').modal({
+                scrollableContentStyle: 'scrollable',
+                headerText: 'Component Version',
+                buttons: [{
+                    buttonText: 'Apply',
+                    color: {
+                        base: '#728E9B',
+                        hover: '#004849',
+                        text: '#ffffff'
+                    },
+                    handler: {
+                        click: function () {
+                            // get the selected version
+                            var selectedOption = $('#component-version-selector').combo('getSelectedOption');
+                            var documentedType = versionMap.get(selectedOption.value);
+
+                            // get the current component
+                            var componentEntity = $('#component-version-dialog').data('component');
+
+                            // build the request entity
+                            var requestEntity = {
+                                'revision': nfClient.getRevision(componentEntity),
+                                'component': {
+                                    'id': componentEntity.id,
+                                    'bundle': {
+                                        'group': documentedType.bundle.group,
+                                        'artifact': documentedType.bundle.artifact,
+                                        'version': documentedType.bundle.version
+                                    }
+                                }
+                            };
+
+                            // save the bundle
+                            $.ajax({
+                                type: 'PUT',
+                                url: componentEntity.uri,
+                                data: JSON.stringify(requestEntity),
+                                dataType: 'json',
+                                contentType: 'application/json'
+                            }).done(function (response) {
+                                // set the response
+                                if (componentEntity.type === 'Processor') {
+                                    // update the processor
+                                    nfCanvasUtils.getComponentByType(componentEntity.type).set(response);
+
+                                    // inform Angular app values have changed
+                                    nfNgBridge.digest();
+                                } else if (componentEntity.type === 'ControllerService') {
+                                    var parentGroupId = componentEntity.component.parentGroupId;
+
+                                    $.Deferred(function (deferred) {
+                                        if (nfCommon.isDefinedAndNotNull(parentGroupId)) {
+                                            if ($('#process-group-configuration').is(':visible')) {
+                                                nfProcessGroupConfiguration.loadConfiguration(parentGroupId).done(function () {
+                                                    deferred.resolve();
+                                                });
+                                            } else {
+                                                nfProcessGroupConfiguration.showConfiguration(parentGroupId).done(function () {
+                                                    deferred.resolve();
+                                                });
+                                            }
+                                        } else {
+                                            if ($('#settings').is(':visible')) {
+                                                // reload the settings
+                                                nfSettings.loadSettings().done(function () {
+                                                    deferred.resolve();
+                                                });
+                                            } else {
+                                                // reload the settings and show
+                                                nfSettings.showSettings().done(function () {
+                                                    deferred.resolve();
+                                                });
+                                            }
+                                        }
+                                    }).done(function () {
+                                        if (nfCommon.isDefinedAndNotNull(parentGroupId)) {
+                                            nfProcessGroupConfiguration.selectControllerService(componentEntity.id);
+                                        } else {
+                                            nfSettings.selectControllerService(componentEntity.id);
+                                        }
+                                    });
+                                } else if (componentEntity.type === 'ReportingTask') {
+                                    $.Deferred(function (deferred) {
+                                        if ($('#settings').is(':visible')) {
+                                            // reload the settings
+                                            nfSettings.loadSettings().done(function () {
+                                                deferred.resolve();
+                                            });
+                                        } else {
+                                            // reload the settings and show
+                                            nfSettings.showSettings().done(function () {
+                                                deferred.resolve();
+                                            });
+                                        }
+                                    }).done(function () {
+                                        nfSettings.selectReportingTask(componentEntity.id);
+                                    });
+                                }
+                            }).fail(nfErrorHandler.handleAjaxError);
+
+                            // reset and hide the dialog
+                            this.modal('hide');
+                        }
+                    }
+                },
+                    {
+                        buttonText: 'Cancel',
+                        color: {
+                            base: '#E3E8EB',
+                            hover: '#C7D2D7',
+                            text: '#004849'
+                        },
+                        handler: {
+                            click: function () {
+                                this.modal('hide');
+                            }
+                        }
+                    }],
+                handler: {
+                    close: function () {
+                        resetDialog();
+                    }
+                }
+            });
+        },
+
+        /**
+         * Prompts to change the version of a component.
+         *
+         * @param {object} componentEntity
+         */
+        promptForVersionChange: function (componentEntity) {
+            return $.ajax({
+                type: 'GET',
+                url: getTypeUri(componentEntity) + '?' + $.param({
+                    'bundleGroupFilter': componentEntity.component.bundle.group,
+                    'bundleArtifactFilter': componentEntity.component.bundle.artifact,
+                    'typeFilter': componentEntity.component.type
+                }),
+                dataType: 'json'
+            }).done(function (response) {
+                var options = [];
+                var selectedOption;
+
+                // go through each type
+                $.each(response[getTypeField(componentEntity)], function (i, documentedType) {
+                    var type = documentedType.type;
+
+                    // store the documented type
+                    versionMap.set(documentedType.bundle.version, documentedType);
+
+                    // create the option
+                    var option = {
+                        text: documentedType.bundle.version,
+                        value: documentedType.bundle.version,
+                        description: nfCommon.escapeHtml(documentedType.description)
+                    };
+
+                    // record the currently selected option
+                    if (documentedType.bundle.version === componentEntity.component.bundle.version) {
+                        selectedOption = option;
+                    }
+
+                    // store this option
+                    options.push(option);
+                });
+
+                // sort the text version visible to the user
+                options.sort(function (a, b) {
+                    return -nfCommon.sortVersion(a.text, b.text);
+                });
+
+                // populate the name/description
+                $('#component-version-name').text(componentEntity.component.name);
+                $('#component-version-bundle').text(nfCommon.formatBundle(componentEntity.component.bundle));
+
+                // build the combo
+                $('#component-version-selector').combo({
+                    options: options,
+                    selectedOption: selectedOption,
+                    select: select
+                });
+
+                // show the dialog
+                $('#component-version-dialog').data('component', componentEntity).modal('show');
+            }).fail(nfErrorHandler.handleAjaxError);
+        }
+    };
+}));
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/d90cf846/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-context-menu.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-context-menu.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-context-menu.js
index 4013167..1c16626 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-context-menu.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-context-menu.js
@@ -325,6 +325,28 @@
     };
 
     /**
+     * Determines whether the current selection is a processor with more than one version.
+     *
+     * @param {selection} selection
+     */
+    var canChangeProcessorVersion = function (selection) {
+        // ensure the correct number of components are selected
+        if (selection.size() !== 1) {
+            return false;
+        }
+        if (nfCanvasUtils.canRead(selection) === false || nfCanvasUtils.canModify(selection) === false) {
+            return false;
+        }
+
+        if (nfCanvasUtils.isProcessor(selection)) {
+            var processorData = selection.datum();
+            return processorData.component.multipleVersionsAvailable === true;
+        } else {
+            return false;
+        }
+    };
+
+    /**
      * Determines whether the current selection is a process group.
      *
      * @param {selection} selection
@@ -513,6 +535,7 @@
         {condition: supportsStats, menuItem: {clazz: 'fa fa-area-chart', text: 'Status History', action: 'showStats'}},
         {condition: canAccessProvenance, menuItem: {clazz: 'icon icon-provenance', imgStyle: 'context-menu-provenance', text: 'Data provenance', action: 'openProvenance'}},
         {condition: isStatefulProcessor, menuItem: {clazz: 'fa fa-tasks', text: 'View state', action: 'viewState'}},
+        {condition: canChangeProcessorVersion, menuItem: {clazz: 'fa fa-exchange', text: 'Change version', action: 'changeVersion'}},
         {condition: canMoveToFront, menuItem: {clazz: 'fa fa-clone', text: 'Bring to front', action: 'toFront'}},
         {condition: isConnection, menuItem: {clazz: 'fa fa-long-arrow-left', text: 'Go to source', action: 'showSource'}},
         {condition: isConnection, menuItem: {clazz: 'fa fa-long-arrow-right', text: 'Go to destination', action: 'showDestination'}},
@@ -530,8 +553,8 @@
         {condition: canEmptyQueue, menuItem: {clazz: 'fa fa-minus-circle', text: 'Empty queue', action: 'emptyQueue'}},
         {condition: isDeletable, menuItem: {clazz: 'fa fa-trash', text: 'Delete', action: 'delete'}},
         {condition: canManagePolicies, menuItem: {clazz: 'fa fa-key', text: 'Access policies', action: 'managePolicies'}},
-        {condition: canAlign, menuItem: {clazz: 'fa fa-align-center', text: 'Align vertical', action: 'alignVertical'}},
-        {condition: canAlign, menuItem: { clazz: 'fa fa-align-center fa-rotate-90', text: 'Align horizontal', action: 'alignHorizontal'}},
+        {condition: canAlign, menuItem: {clazz: 'fa fa-align-center', text: 'Align vertically', action: 'alignVertical'}},
+        {condition: canAlign, menuItem: { clazz: 'fa fa-align-center fa-rotate-90', text: 'Align horizontally', action: 'alignHorizontal'}},
         {condition: canUploadTemplate, menuItem: {clazz: 'icon icon-template-import', text: 'Upload template', action: 'uploadTemplate'}},
         {condition: canCreateTemplate, menuItem: {clazz: 'icon icon-template-save', text: 'Create template', action: 'template'}}
     ];

http://git-wip-us.apache.org/repos/asf/nifi/blob/d90cf846/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-service.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-service.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-service.js
index 2bbc8c2..5acd3ca 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-service.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-service.js
@@ -204,6 +204,7 @@
         var controllerServiceData = controllerServiceGrid.getData();
         var currentControllerServiceEntity = controllerServiceData.getItemById(controllerServiceEntity.id);
         controllerServiceData.updateItem(controllerServiceEntity.id, $.extend({
+            type: 'ControllerService',
             bulletins: currentControllerServiceEntity.bulletins
         }, controllerServiceEntity));
     };
@@ -1679,6 +1680,9 @@
                         // clear the comments
                         nfCommon.clearField('read-only-controller-service-comments');
 
+                        // clear the compatible apis
+                        $('#controller-service-compatible-apis').empty();
+
                         // removed the cached controller service details
                         $('#controller-service-configuration').removeData('controllerServiceDetails');
                     },
@@ -1844,10 +1848,20 @@
 
                 // populate the controller service settings
                 nfCommon.populateField('controller-service-id', controllerService['id']);
-                nfCommon.populateField('controller-service-type', nfCommon.substringAfterLast(controllerService['type'], '.'));
+                nfCommon.populateField('controller-service-type', nfCommon.formatType(controllerService));
+                nfCommon.populateField('controller-service-bundle', nfCommon.formatBundle(controllerService['bundle']));
                 $('#controller-service-name').val(controllerService['name']);
                 $('#controller-service-comments').val(controllerService['comments']);
 
+                // set the implemented apis
+                if (!nfCommon.isEmpty(controllerService['controllerServiceApis'])) {
+                    var formattedControllerServiceApis = nfCommon.getFormattedServiceApis(controllerService['controllerServiceApis']);
+                    var serviceTips = nfCommon.formatUnorderedList(formattedControllerServiceApis);
+                    $('#controller-service-compatible-apis').append(serviceTips);
+                } else {
+                    $('#controller-service-compatible-apis').append('<span class="unset">None</span>');
+                }
+
                 // get the reference container
                 var referenceContainer = $('#controller-service-referencing-components');
 
@@ -2013,10 +2027,20 @@
 
                 // populate the controller service settings
                 nfCommon.populateField('controller-service-id', controllerService['id']);
-                nfCommon.populateField('controller-service-type', nfCommon.substringAfterLast(controllerService['type'], '.'));
+                nfCommon.populateField('controller-service-type', nfCommon.formatType(controllerService));
+                nfCommon.populateField('controller-service-bundle', nfCommon.formatBundle(controllerService['bundle']));
                 nfCommon.populateField('read-only-controller-service-name', controllerService['name']);
                 nfCommon.populateField('read-only-controller-service-comments', controllerService['comments']);
 
+                // set the implemented apis
+                if (!nfCommon.isEmpty(controllerService['controllerServiceApis'])) {
+                    var formattedControllerServiceApis = nfCommon.getFormattedServiceApis(controllerService['controllerServiceApis']);
+                    var serviceTips = nfCommon.formatUnorderedList(formattedControllerServiceApis);
+                    $('#controller-service-compatible-apis').append(serviceTips);
+                } else {
+                    $('#controller-service-compatible-apis').append('<span class="unset">None</span>');
+                }
+
                 // get the reference container
                 var referenceContainer = $('#controller-service-referencing-components');
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/d90cf846/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-services.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-services.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-services.js
index 9f1a26d..427ef35 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-services.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-services.js
@@ -33,9 +33,10 @@
                 'nf.ProcessGroup',
                 'nf.PolicyManagement',
                 'nf.ComponentState',
+                'nf.ComponentVersion',
                 'nf.ng.Bridge'],
-            function ($, d3, Slick, nfClient, nfShell, nfProcessGroupConfiguration, nfCanvasUtils, nfErrorHandler, nfDialog, nfCommon, nfControllerService, nfProcessGroup, nfPolicyManagement, nfComponentState, nfNgBridge) {
-                return (nf.ControllerServices = factory($, d3, Slick, nfClient, nfShell, nfProcessGroupConfiguration, nfCanvasUtils, nfErrorHandler, nfDialog, nfCommon, nfControllerService, nfProcessGroup, nfPolicyManagement, nfComponentState, nfNgBridge));
+            function ($, d3, Slick, nfClient, nfShell, nfProcessGroupConfiguration, nfCanvasUtils, nfErrorHandler, nfDialog, nfCommon, nfControllerService, nfProcessGroup, nfPolicyManagement, nfComponentState, nfComponentVersion, nfNgBridge) {
+                return (nf.ControllerServices = factory($, d3, Slick, nfClient, nfShell, nfProcessGroupConfiguration, nfCanvasUtils, nfErrorHandler, nfDialog, nfCommon, nfControllerService, nfProcessGroup, nfPolicyManagement, nfComponentState, nfComponentVersion, nfNgBridge));
             });
     } else if (typeof exports === 'object' && typeof module === 'object') {
         module.exports = (nf.ControllerServices =
@@ -53,6 +54,7 @@
                 require('nf.ProcessGroup'),
                 require('nf.PolicyManagement'),
                 require('nf.ComponentState'),
+                require('nf.ComponentVersion'),
                 require('nf.ng.Bridge')));
     } else {
         nf.ControllerServices = factory(root.$,
@@ -69,9 +71,10 @@
             root.nf.ProcessGroup,
             root.nf.PolicyManagement,
             root.nf.ComponentState,
+            root.nf.ComponentVersion,
             root.nf.ng.Bridge);
     }
-}(this, function ($, d3, Slick, nfClient, nfShell, nfProcessGroupConfiguration, nfCanvasUtils, nfErrorHandler, nfDialog, nfCommon, nfControllerService, nfProcessGroup, nfPolicyManagement, nfComponentState, nfNgBridge) {
+}(this, function ($, d3, Slick, nfClient, nfShell, nfProcessGroupConfiguration, nfCanvasUtils, nfErrorHandler, nfDialog, nfCommon, nfControllerService, nfProcessGroup, nfPolicyManagement, nfComponentState, nfComponentVersion, nfNgBridge) {
     'use strict';
 
     var dblClick = null;
@@ -143,10 +146,11 @@
      * Hides the selected controller service.
      */
     var clearSelectedControllerService = function () {
-        $('#controller-service-type-description').text('');
-        $('#controller-service-type-name').text('');
+        $('#controller-service-type-description').attr('title', '').text('');
+        $('#controller-service-type-name').attr('title', '').text('');
+        $('#controller-service-type-bundle').attr('title', '').text('');
         $('#selected-controller-service-name').text('');
-        $('#selected-controller-service-type').text('');
+        $('#selected-controller-service-type').text('').removeData('bundle');
         $('#controller-service-description-container').hide();
     };
 
@@ -183,8 +187,17 @@
             }
         }
 
+        // determine if the row matches the selected source group
+        var matchesGroup = true;
+        if (matchesFilter && matchesTags) {
+            var bundleGroup = $('#controller-service-bundle-group-combo').combo('getSelectedOption');
+            if (nfCommon.isDefinedAndNotNull(bundleGroup) && bundleGroup.value !== '') {
+                matchesGroup = (item.bundle.group === bundleGroup.value);
+            }
+        }
+
         // determine if this row should be visible
-        var matches = matchesFilter && matchesTags;
+        var matches = matchesFilter && matchesTags && matchesGroup;
 
         // if this row is currently selected and its being filtered
         if (matches === false && $('#selected-controller-service-type').text() === item['type']) {
@@ -254,6 +267,7 @@
      */
     var addSelectedControllerService = function (controllerServicesUri, serviceTable) {
         var selectedServiceType = $('#selected-controller-service-type').text();
+        var selectedServiceBundle = $('#selected-controller-service-type').data('bundle');
 
         // ensure something was selected
         if (selectedServiceType === '') {
@@ -262,7 +276,7 @@
                 dialogContent: 'The type of controller service to create must be selected.'
             });
         } else {
-            addControllerService(controllerServicesUri, serviceTable, selectedServiceType);
+            addControllerService(controllerServicesUri, serviceTable, selectedServiceType, selectedServiceBundle);
         }
     };
 
@@ -272,8 +286,9 @@
      * @param {string} controllerServicesUri
      * @param {jQuery} serviceTable
      * @param {string} controllerServiceType
+     * @param {object} controllerServiceBundle
      */
-    var addControllerService = function (controllerServicesUri, serviceTable, controllerServiceType) {
+    var addControllerService = function (controllerServicesUri, serviceTable, controllerServiceType, controllerServiceBundle) {
         // build the controller service entity
         var controllerServiceEntity = {
             'revision': nfClient.getRevision({
@@ -282,7 +297,8 @@
                 }
             }),
             'component': {
-                'type': controllerServiceType
+                'type': controllerServiceType,
+                'bundle': controllerServiceBundle
             }
         };
 
@@ -297,7 +313,10 @@
             // add the item
             var controllerServicesGrid = serviceTable.data('gridInstance');
             var controllerServicesData = controllerServicesGrid.getData();
-            controllerServicesData.addItem(controllerServiceEntity);
+            controllerServicesData.addItem($.extend({
+                type: 'ControllerService',
+                bulletins: []
+            }, controllerServiceEntity));
 
             // resort
             controllerServicesData.reSort();
@@ -319,21 +338,29 @@
      * Initializes the new controller service dialog.
      */
     var initNewControllerServiceDialog = function () {
-        // initialize the processor type table
+        // initialize the controller service type table
         var controllerServiceTypesColumns = [
             {
                 id: 'type',
                 name: 'Type',
                 field: 'label',
                 formatter: nfCommon.typeFormatter,
-                sortable: false,
+                sortable: true,
+                resizable: true
+            },
+            {
+                id: 'version',
+                name: 'Version',
+                field: 'version',
+                formatter: nfCommon.typeVersionFormatter,
+                sortable: true,
                 resizable: true
             },
             {
                 id: 'tags',
                 name: 'Tags',
                 field: 'tags',
-                sortable: false,
+                sortable: true,
                 resizable: true
             }
         ];
@@ -348,11 +375,23 @@
         });
         controllerServiceTypesData.setFilter(filterControllerServiceTypes);
 
+        // initialize the sort
+        nfCommon.sortType({
+            columnId: 'type',
+            sortAsc: true
+        }, controllerServiceTypesData);
+
         // initialize the grid
         var controllerServiceTypesGrid = new Slick.Grid('#controller-service-types-table', controllerServiceTypesData, controllerServiceTypesColumns, gridOptions);
         controllerServiceTypesGrid.setSelectionModel(new Slick.RowSelectionModel());
         controllerServiceTypesGrid.registerPlugin(new Slick.AutoTooltips());
         controllerServiceTypesGrid.setSortColumn('type', true);
+        controllerServiceTypesGrid.onSort.subscribe(function (e, args) {
+            nfCommon.sortType({
+                columnId: args.sortCol.field,
+                sortAsc: args.sortAsc
+            }, controllerServiceTypesData);
+        });
         controllerServiceTypesGrid.onSelectedRowsChanged.subscribe(function (e, args) {
             if ($.isArray(args.rows) && args.rows.length === 1) {
                 var controllerServiceTypeIndex = args.rows[0];
@@ -374,10 +413,14 @@
                             .ellipsis();
                     }
 
+                    var bundle = nfCommon.formatBundle(controllerServiceType.bundle);
+                    var type = nfCommon.formatType(controllerServiceType);
+
                     // populate the dom
-                    $('#controller-service-type-name').text(controllerServiceType.label).ellipsis();
+                    $('#controller-service-type-name').text(type).attr('title', type);
+                    $('#controller-service-type-bundle').text(bundle).attr('title', bundle);
                     $('#selected-controller-service-name').text(controllerServiceType.label);
-                    $('#selected-controller-service-type').text(controllerServiceType.type);
+                    $('#selected-controller-service-type').text(controllerServiceType.type).data('bundle', controllerServiceType.bundle);
 
                     // refresh the buttons based on the current selection
                     $('#new-controller-service-dialog').modal('refreshButtons');
@@ -386,6 +429,7 @@
         });
         controllerServiceTypesGrid.onViewportChanged.subscribe(function (e, args) {
             nfCommon.cleanUpTooltips($('#controller-service-types-table'), 'div.view-usage-restriction');
+            nfCommon.cleanUpTooltips($('#controller-service-types-table'), 'div.controller-service-apis');
         });
 
         // wire up the dataview to the grid
@@ -427,6 +471,35 @@
                     }));
                 }
             }
+
+            var serviceApis = $(this).find('div.controller-service-apis');
+            if (serviceApis.length && !serviceApis.data('qtip')) {
+                var rowId = $(this).find('span.row-id').text();
+
+                // get the status item
+                var item = controllerServiceTypesData.getItemById(rowId);
+
+                // show the tooltip
+                if (!nfCommon.isEmpty(item.controllerServiceApis)) {
+                    var formattedControllerServiceApis = nfCommon.getFormattedServiceApis(item.controllerServiceApis);
+                    var serviceTips = nfCommon.formatUnorderedList(formattedControllerServiceApis);
+
+                    var tipContent = $('<div style="padding: 4px;"><p>Supports Controller Services</p><br/></div>').append(serviceTips);
+
+                    serviceApis.qtip($.extend({}, nfCommon.config.tooltipConfig, {
+                        content: tipContent,
+                        position: {
+                            container: $('#summary'),
+                            at: 'bottom right',
+                            my: 'top left',
+                            adjust: {
+                                x: 4,
+                                y: 4
+                            }
+                        }
+                    }));
+                }
+            }
         });
 
         // load the available controller services
@@ -437,17 +510,23 @@
         }).done(function (response) {
             var id = 0;
             var tags = [];
+            var groups = d3.set();
 
             // begin the update
             controllerServiceTypesData.beginUpdate();
 
             // go through each controller service type
             $.each(response.controllerServiceTypes, function (i, documentedType) {
+                // record the group
+                groups.add(documentedType.bundle.group);
+
                 // add the documented type
                 controllerServiceTypesData.addItem({
                     id: id++,
                     label: nfCommon.substringAfterLast(documentedType.type, '.'),
                     type: documentedType.type,
+                    bundle: documentedType.bundle,
+                    controllerServiceApis: documentedType.controllerServiceApis,
                     description: nfCommon.escapeHtml(documentedType.description),
                     usageRestriction: nfCommon.escapeHtml(documentedType.usageRestriction),
                     tags: documentedType.tags.join(', ')
@@ -459,9 +538,13 @@
                 });
             });
 
-            // end the udpate
+            // end the update
             controllerServiceTypesData.endUpdate();
 
+            // resort
+            controllerServiceTypesData.reSort();
+            controllerServiceTypesGrid.invalidate();
+
             // set the total number of processors
             $('#total-controller-service-types, #displayed-controller-service-types').text(response.controllerServiceTypes.length);
 
@@ -471,6 +554,24 @@
                 select: applyControllerServiceTypeFilter,
                 remove: applyControllerServiceTypeFilter
             });
+
+            // build the combo options
+            var options = [{
+                text: 'all groups',
+                value: ''
+            }];
+            groups.forEach(function (group) {
+                options.push({
+                    text: group,
+                    value: group
+                });
+            });
+
+            // initialize the bundle group combo
+            $('#controller-service-bundle-group-combo').combo({
+                options: options,
+                select: applyControllerServiceTypeFilter
+            });
         }).fail(nfErrorHandler.handleAjaxError);
 
         // initialize the controller service dialog
@@ -488,6 +589,11 @@
                     // clear the tagcloud
                     $('#controller-service-tag-cloud').tagcloud('clearSelectedTags');
 
+                    // reset the group combo
+                    $('#controller-service-bundle-group-combo').combo('setSelectedOption', {
+                        value: ''
+                    });
+
                     // reset the filter
                     applyControllerServiceTypeFilter();
 
@@ -525,24 +631,6 @@
     };
 
     /**
-     * Formatter for the type column.
-     *
-     * @param {type} row
-     * @param {type} cell
-     * @param {type} value
-     * @param {type} columnDef
-     * @param {type} dataContext
-     * @returns {String}
-     */
-    var typeFormatter = function (row, cell, value, columnDef, dataContext) {
-        if (!dataContext.permissions.canRead) {
-            return '';
-        }
-        
-        return nfCommon.substringAfterLast(dataContext.component.type, '.');
-    };
-
-    /**
      * Formatter for the name column.
      *
      * @param {type} row
@@ -774,15 +862,19 @@
                         if (nfCommon.isEmpty(dataContext.component.validationErrors)) {
                             markup += '<div class="pointer enable-controller-service fa fa-flash" title="Enable" style="margin-top: 2px; margin-right: 3px;"></div>';
                         }
+
+                        if (dataContext.component.multipleVersionsAvailable === true) {
+                            markup += '<div title="Change Version" class="pointer change-version-controller-service fa fa-exchange" style="margin-top: 2px; margin-right: 3px;" ></div>';
+                        }
+
+                        if (canWriteControllerServiceParent(dataContext)) {
+                            markup += '<div class="pointer delete-controller-service fa fa-trash" title="Remove" style="margin-top: 2px; margin-right: 3px;" ></div>';
+                        }
                     }
 
                     if (dataContext.component.persistsState === true) {
                         markup += '<div title="View State" class="pointer view-state-controller-service fa fa-tasks" style="margin-top: 2px; margin-right: 3px;" ></div>';
                     }
-
-                    if (canWriteControllerServiceParent(dataContext)) {
-                        markup += '<div class="pointer delete-controller-service fa fa-trash" title="Remove" style="margin-top: 2px; margin-right: 3px;" ></div>';
-                    }
                 } else {
                     markup += '<div class="pointer go-to-controller-service fa fa-long-arrow-right" title="Go To" style="margin-top: 2px; margin-right: 3px;" ></div>';
                 }
@@ -818,7 +910,14 @@
             {
                 id: 'type',
                 name: 'Type',
-                formatter: typeFormatter,
+                formatter: nfCommon.instanceTypeFormatter,
+                sortable: true,
+                resizable: true
+            },
+            {
+                id: 'bundle',
+                name: 'Bundle',
+                formatter: nfCommon.instanceBundleFormatter,
                 sortable: true,
                 resizable: true
             },
@@ -884,6 +983,8 @@
                     nfControllerService.promptToDeleteController(serviceTable, controllerServiceEntity);
                 } else if (target.hasClass('view-state-controller-service')) {
                     nfComponentState.showState(controllerServiceEntity, controllerServiceEntity.state === 'DISABLED');
+                } else if (target.hasClass('change-version-controller-service')) {
+                    nfComponentVersion.promptForVersionChange(controllerServiceEntity);
                 } else if (target.hasClass('edit-access-policies')) {
                     // show the policies for this service
                     nfPolicyManagement.showControllerServicePolicy(controllerServiceEntity);
@@ -1028,6 +1129,7 @@
             var services = [];
             $.each(response.controllerServices, function (_, service) {
                 services.push($.extend({
+                    type: 'ControllerService',
                     bulletins: []
                 }, service));
             });
@@ -1075,6 +1177,7 @@
         promptNewControllerService: function (controllerServicesUri, serviceTable) {
             // get the grid reference
             var grid = $('#controller-service-types-table').data('gridInstance');
+            var dataview = grid.getData();
 
             // update the keyhandler
             $('#controller-service-type-filter').off('keyup').on('keyup', function (e) {
@@ -1110,7 +1213,7 @@
                         var item = grid.getDataItem(selected[0]);
                         return isSelectable(item) === false;
                     } else {
-                        return grid.getData().getLength() === 0;
+                        return dataview.getLength() === 0;
                     }
                 },
                 handler: {
@@ -1132,27 +1235,27 @@
                 }
             }]).modal('show');
 
-            var controllerServiceTypesGrid = $('#controller-service-types-table').data('gridInstance');
-
             // remove previous dbl click handler
             if (dblClick !== null) {
-                controllerServiceTypesGrid.onDblClick.unsubscribe(dblClick);
+                grid.onDblClick.unsubscribe(dblClick);
             }
 
             // update the dbl click handler and subsrcibe
             dblClick = function(e, args) {
-                var controllerServiceType = controllerServiceTypesGrid.getDataItem(args.row);
+                var controllerServiceType = grid.getDataItem(args.row);
 
                 if (isSelectable(controllerServiceType)) {
-                    addControllerService(controllerServicesUri, serviceTable, controllerServiceType.type);
+                    addControllerService(controllerServicesUri, serviceTable, controllerServiceType.type, controllerServiceType.bundle);
                 }
             };
-            controllerServiceTypesGrid.onDblClick.subscribe(dblClick);
+            grid.onDblClick.subscribe(dblClick);
 
             // reset the canvas size after the dialog is shown
-            if (nfCommon.isDefinedAndNotNull(controllerServiceTypesGrid)) {
-                controllerServiceTypesGrid.setSelectedRows([0]);
-                controllerServiceTypesGrid.resizeCanvas();
+            grid.resizeCanvas();
+
+            // auto select the first row if possible
+            if (dataview.getLength() > 0) {
+                grid.setSelectedRows([0]);
             }
 
             // set the initial focus

http://git-wip-us.apache.org/repos/asf/nifi/blob/d90cf846/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-processor-configuration.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-processor-configuration.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-processor-configuration.js
index c26969e..1a26f5a 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-processor-configuration.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-processor-configuration.js
@@ -672,7 +672,8 @@
 
                     // populate the processor settings
                     $('#processor-id').text(processor['id']);
-                    $('#processor-type').text(nfCommon.substringAfterLast(processor['type'], '.'));
+                    $('#processor-type').text(nfCommon.formatType(processor));
+                    $('#processor-bundle').text(nfCommon.formatBundle(processor['bundle']));
                     $('#processor-name').val(processor['name']);
                     $('#processor-enabled').removeClass('checkbox-unchecked checkbox-checked').addClass(processorEnableStyle);
                     $('#penalty-duration').val(processor.config['penaltyDuration']);

http://git-wip-us.apache.org/repos/asf/nifi/blob/d90cf846/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-processor.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-processor.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-processor.js
index 4f25699..bc09526 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-processor.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-processor.js
@@ -136,8 +136,8 @@
         // processor name
         processor.append('text')
             .attr({
-                'x': 72,
-                'y': 23,
+                'x': 75,
+                'y': 18,
                 'width': 210,
                 'height': 14,
                 'class': 'processor-name'
@@ -198,6 +198,9 @@
         updated.select('rect.border')
             .classed('unauthorized', function (d) {
                 return d.permissions.canRead === false;
+            })
+            .classed('ghost', function (d) {
+                return d.permissions.canRead === true && d.component.extensionMissing === true;
             });
 
         // processor body authorization
@@ -230,9 +233,19 @@
                     details.append('text')
                         .attr({
                             'class': 'processor-type',
-                            'x': 72,
-                            'y': 37,
-                            'width': 236,
+                            'x': 75,
+                            'y': 32,
+                            'width': 230,
+                            'height': 12
+                        });
+
+                    // processor type
+                    details.append('text')
+                        .attr({
+                            'class': 'processor-bundle',
+                            'x': 75,
+                            'y': 45,
+                            'width': 230,
                             'height': 12
                         });
 
@@ -490,7 +503,7 @@
                     details.append('text')
                         .attr({
                             'class': 'active-thread-count-icon',
-                            'y': 42
+                            'y': 45
                         })
                         .text('\ue83f');
 
@@ -498,7 +511,7 @@
                     details.append('text')
                         .attr({
                             'class': 'active-thread-count',
-                            'y': 42
+                            'y': 45
                         });
 
                     // ---------
@@ -540,8 +553,8 @@
                             // apply ellipsis to the processor name as necessary
                             nfCanvasUtils.ellipsis(processorName, d.component.name);
                         }).append('title').text(function (d) {
-                        return d.component.name;
-                    });
+                            return d.component.name;
+                        });
 
                     // update the processor type
                     processor.select('text.processor-type')
@@ -552,16 +565,33 @@
                             processorType.text(null).selectAll('title').remove();
 
                             // apply ellipsis to the processor type as necessary
-                            nfCanvasUtils.ellipsis(processorType, nfCommon.substringAfterLast(d.component.type, '.'));
+                            nfCanvasUtils.ellipsis(processorType, nfCommon.formatType(d.component));
                         }).append('title').text(function (d) {
-                        return nfCommon.substringAfterLast(d.component.type, '.');
-                    });
+                            return nfCommon.formatType(d.component);
+                        });
+
+                    // update the processor bundle
+                    processor.select('text.processor-bundle')
+                        .each(function (d) {
+                            var processorBundle = d3.select(this);
+
+                            // reset the processor type to handle any previous state
+                            processorBundle.text(null).selectAll('title').remove();
+
+                            // apply ellipsis to the processor type as necessary
+                            nfCanvasUtils.ellipsis(processorBundle, nfCommon.formatBundle(d.component.bundle));
+                        }).append('title').text(function (d) {
+                            return nfCommon.formatBundle(d.component.bundle);
+                        });
                 } else {
                     // clear the processor name
                     processor.select('text.processor-name').text(null);
 
                     // clear the processor type
                     processor.select('text.processor-type').text(null);
+
+                    // clear the processor bundle
+                    processor.select('text.processor-bundle').text(null);
                 }
 
                 // populate the stats

http://git-wip-us.apache.org/repos/asf/nifi/blob/d90cf846/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-reporting-task.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-reporting-task.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-reporting-task.js
index bb1b07c..5525874 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-reporting-task.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-reporting-task.js
@@ -230,6 +230,7 @@
         var reportingTaskData = reportingTaskGrid.getData();
         var currentReportingTask = reportingTaskData.getItemById(reportingTaskEntity.id);
         reportingTaskData.updateItem(reportingTaskEntity.id, $.extend({
+            type: 'ReportingTask',
             bulletins: currentReportingTask.bulletins
         }, reportingTaskEntity));
     };
@@ -478,7 +479,8 @@
 
                 // populate the reporting task settings
                 nfCommon.populateField('reporting-task-id', reportingTask['id']);
-                nfCommon.populateField('reporting-task-type', nfCommon.substringAfterLast(reportingTask['type'], '.'));
+                nfCommon.populateField('reporting-task-type', nfCommon.formatType(reportingTask));
+                nfCommon.populateField('reporting-task-bundle', nfCommon.formatBundle(reportingTask['bundle']));
                 $('#reporting-task-name').val(reportingTask['name']);
                 $('#reporting-task-enabled').removeClass('checkbox-unchecked checkbox-checked').addClass(reportingTaskEnableStyle);
                 $('#reporting-task-comments').val(reportingTask['comments']);
@@ -677,6 +679,7 @@
                 // populate the reporting task settings
                 nfCommon.populateField('reporting-task-id', reportingTask['id']);
                 nfCommon.populateField('reporting-task-type', nfCommon.substringAfterLast(reportingTask['type'], '.'));
+                nfCommon.populateField('reporting-task-bundle', nfCommon.formatBundle(reportingTask['bundle']));
                 nfCommon.populateField('read-only-reporting-task-name', reportingTask['name']);
                 nfCommon.populateField('read-only-reporting-task-comments', reportingTask['comments']);
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/d90cf846/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js
index fa7afbd..667a675 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js
@@ -31,9 +31,10 @@
                 'nf.ReportingTask',
                 'nf.Shell',
                 'nf.ComponentState',
+                'nf.ComponentVersion',
                 'nf.PolicyManagement'],
-            function ($, Slick, d3, nfClient, nfDialog, nfCommon, nfCanvasUtils, nfControllerServices, nfErrorHandler, nfReportingTask, nfShell, nfComponentState, nfPolicyManagement) {
-                return (nf.Settings = factory($, Slick, d3, nfClient, nfDialog, nfCommon, nfCanvasUtils, nfControllerServices, nfErrorHandler, nfReportingTask, nfShell, nfComponentState, nfPolicyManagement));
+            function ($, Slick, d3, nfClient, nfDialog, nfCommon, nfCanvasUtils, nfControllerServices, nfErrorHandler, nfReportingTask, nfShell, nfComponentState, nfComponentVersion, nfPolicyManagement) {
+                return (nf.Settings = factory($, Slick, d3, nfClient, nfDialog, nfCommon, nfCanvasUtils, nfControllerServices, nfErrorHandler, nfReportingTask, nfShell, nfComponentState, nfComponentVersion, nfPolicyManagement));
             });
     } else if (typeof exports === 'object' && typeof module === 'object') {
         module.exports = (nf.Settings =
@@ -49,6 +50,7 @@
                 require('nf.ReportingTask'),
                 require('nf.Shell'),
                 require('nf.ComponentState'),
+                require('nf.ComponentVersion'),
                 require('nf.PolicyManagement')));
     } else {
         nf.Settings = factory(root.$,
@@ -63,9 +65,10 @@
             root.nf.ReportingTask,
             root.nf.Shell,
             root.nf.ComponentState,
+            root.nf.ComponentVersion,
             root.nf.PolicyManagement);
     }
-}(this, function ($, Slick, d3, nfClient, nfDialog, nfCommon, nfCanvasUtils, nfControllerServices, nfErrorHandler, nfReportingTask, nfShell, nfComponentState, nfPolicyManagement) {
+}(this, function ($, Slick, d3, nfClient, nfDialog, nfCommon, nfCanvasUtils, nfControllerServices, nfErrorHandler, nfReportingTask, nfShell, nfComponentState, nfComponentVersion, nfPolicyManagement) {
     'use strict';
 
 
@@ -233,24 +236,6 @@
     };
 
     /**
-     * Formatter for the type column.
-     *
-     * @param {type} row
-     * @param {type} cell
-     * @param {type} value
-     * @param {type} columnDef
-     * @param {type} dataContext
-     * @returns {String}
-     */
-    var typeFormatter = function (row, cell, value, columnDef, dataContext) {
-        if (!dataContext.permissions.canRead) {
-            return '';
-        }
-
-        return nfCommon.substringAfterLast(dataContext.component.type, '.');
-    };
-
-    /**
      * Sorts the specified data using the specified sort details.
      *
      * @param {object} sortDetails
@@ -345,10 +330,11 @@
      * Hides the selected reporting task.
      */
     var clearSelectedReportingTask = function () {
-        $('#reporting-task-type-description').text('');
-        $('#reporting-task-type-name').text('');
+        $('#reporting-task-type-description').attr('title', '').text('');
+        $('#reporting-task-type-name').attr('title', '').text('');
+        $('#reporting-task-type-bundle').attr('title', '').text('');
         $('#selected-reporting-task-name').text('');
-        $('#selected-reporting-task-type').text('');
+        $('#selected-reporting-task-type').text('').removeData('bundle');
         $('#reporting-task-description-container').hide();
     };
 
@@ -385,8 +371,17 @@
             }
         }
 
+        // determine if the row matches the selected source group
+        var matchesGroup = true;
+        if (matchesFilter && matchesTags) {
+            var bundleGroup = $('#reporting-task-bundle-group-combo').combo('getSelectedOption');
+            if (nfCommon.isDefinedAndNotNull(bundleGroup) && bundleGroup.value !== '') {
+                matchesGroup = (item.bundle.group === bundleGroup.value);
+            }
+        }
+
         // determine if this row should be visible
-        var matches = matchesFilter && matchesTags;
+        var matches = matchesFilter && matchesTags && matchesGroup;
 
         // if this row is currently selected and its being filtered
         if (matches === false && $('#selected-reporting-task-type').text() === item['type']) {
@@ -401,6 +396,7 @@
      */
     var addSelectedReportingTask = function () {
         var selectedTaskType = $('#selected-reporting-task-type').text();
+        var selectedTaskBundle = $('#selected-reporting-task-type').data('bundle');
 
         // ensure something was selected
         if (selectedTaskType === '') {
@@ -409,7 +405,7 @@
                 dialogContent: 'The type of reporting task to create must be selected.'
             });
         } else {
-            addReportingTask(selectedTaskType);
+            addReportingTask(selectedTaskType, selectedTaskBundle);
         }
     };
 
@@ -417,8 +413,9 @@
      * Adds a new reporting task of the specified type.
      *
      * @param {string} reportingTaskType
+     * @param {object} reportingTaskBundle
      */
-    var addReportingTask = function (reportingTaskType) {
+    var addReportingTask = function (reportingTaskType, reportingTaskBundle) {
         // build the reporting task entity
         var reportingTaskEntity = {
             'revision': nfClient.getRevision({
@@ -427,7 +424,8 @@
                 }
             }),
             'component': {
-                'type': reportingTaskType
+                'type': reportingTaskType,
+                'bundle': reportingTaskBundle
             }
         };
 
@@ -442,7 +440,10 @@
             // add the item
             var reportingTaskGrid = $('#reporting-tasks-table').data('gridInstance');
             var reportingTaskData = reportingTaskGrid.getData();
-            reportingTaskData.addItem(reportingTaskEntity);
+            reportingTaskData.addItem($.extend({
+                type: 'ReportingTask',
+                bulletins: []
+            }, reportingTaskEntity));
 
             // resort
             reportingTaskData.reSort();
@@ -484,21 +485,29 @@
             }
         });
 
-        // initialize the processor type table
+        // initialize the reporting task type table
         var reportingTaskTypesColumns = [
             {
                 id: 'type',
                 name: 'Type',
                 field: 'label',
                 formatter: nfCommon.typeFormatter,
-                sortable: false,
+                sortable: true,
+                resizable: true
+            },
+            {
+                id: 'version',
+                name: 'Version',
+                field: 'version',
+                formatter: nfCommon.typeVersionFormatter,
+                sortable: true,
                 resizable: true
             },
             {
                 id: 'tags',
                 name: 'Tags',
                 field: 'tags',
-                sortable: false,
+                sortable: true,
                 resizable: true
             }
         ];
@@ -513,11 +522,23 @@
         });
         reportingTaskTypesData.setFilter(filterReportingTaskTypes);
 
+        // initialize the sort
+        nfCommon.sortType({
+            columnId: 'type',
+            sortAsc: true
+        }, reportingTaskTypesData);
+
         // initialize the grid
         var reportingTaskTypesGrid = new Slick.Grid('#reporting-task-types-table', reportingTaskTypesData, reportingTaskTypesColumns, gridOptions);
         reportingTaskTypesGrid.setSelectionModel(new Slick.RowSelectionModel());
         reportingTaskTypesGrid.registerPlugin(new Slick.AutoTooltips());
         reportingTaskTypesGrid.setSortColumn('type', true);
+        reportingTaskTypesGrid.onSort.subscribe(function (e, args) {
+            nfCommon.sortType({
+                columnId: args.sortCol.field,
+                sortAsc: args.sortAsc
+            }, reportingTaskTypesData);
+        });
         reportingTaskTypesGrid.onSelectedRowsChanged.subscribe(function (e, args) {
             if ($.isArray(args.rows) && args.rows.length === 1) {
                 var reportingTaskTypeIndex = args.rows[0];
@@ -539,10 +560,14 @@
                             .ellipsis();
                     }
 
+                    var bundle = nfCommon.formatBundle(reportingTaskType.bundle);
+                    var type = nfCommon.formatType(reportingTaskType);
+
                     // populate the dom
-                    $('#reporting-task-type-name').text(reportingTaskType.label).ellipsis();
+                    $('#reporting-task-type-name').text(type).attr('title', type);
+                    $('#reporting-task-type-bundle').text(bundle).attr('title', bundle);
                     $('#selected-reporting-task-name').text(reportingTaskType.label);
-                    $('#selected-reporting-task-type').text(reportingTaskType.type);
+                    $('#selected-reporting-task-type').text(reportingTaskType.type).data('bundle', reportingTaskType.bundle);
 
                     // refresh the buttons based on the current selection
                     $('#new-reporting-task-dialog').modal('refreshButtons');
@@ -553,7 +578,7 @@
             var reportingTaskType = reportingTaskTypesGrid.getDataItem(args.row);
 
             if (isSelectable(reportingTaskType)) {
-                addReportingTask(reportingTaskType.type);
+                addReportingTask(reportingTaskType.type, reportingTaskType.bundle);
             }
         });
         reportingTaskTypesGrid.onViewportChanged.subscribe(function (e, args) {
@@ -609,17 +634,22 @@
         }).done(function (response) {
             var id = 0;
             var tags = [];
+            var groups = d3.set();
 
             // begin the update
             reportingTaskTypesData.beginUpdate();
 
             // go through each reporting task type
             $.each(response.reportingTaskTypes, function (i, documentedType) {
+                // record the group
+                groups.add(documentedType.bundle.group);
+
                 // add the documented type
                 reportingTaskTypesData.addItem({
                     id: id++,
                     label: nfCommon.substringAfterLast(documentedType.type, '.'),
                     type: documentedType.type,
+                    bundle: documentedType.bundle,
                     description: nfCommon.escapeHtml(documentedType.description),
                     usageRestriction: nfCommon.escapeHtml(documentedType.usageRestriction),
                     tags: documentedType.tags.join(', ')
@@ -631,9 +661,13 @@
                 });
             });
 
-            // end the udpate
+            // end the update
             reportingTaskTypesData.endUpdate();
 
+            // resort
+            reportingTaskTypesData.reSort();
+            reportingTaskTypesGrid.invalidate();
+
             // set the total number of processors
             $('#total-reporting-task-types, #displayed-reporting-task-types').text(response.reportingTaskTypes.length);
 
@@ -643,6 +677,24 @@
                 select: applyReportingTaskTypeFilter,
                 remove: applyReportingTaskTypeFilter
             });
+
+            // build the combo options
+            var options = [{
+                text: 'all groups',
+                value: ''
+            }];
+            groups.forEach(function (group) {
+                options.push({
+                    text: group,
+                    value: group
+                });
+            });
+
+            // initialize the bundle group combo
+            $('#reporting-task-bundle-group-combo').combo({
+                options: options,
+                select: applyReportingTaskTypeFilter
+            });
         }).fail(nfErrorHandler.handleAjaxError);
 
         // initialize the reporting task dialog
@@ -697,6 +749,11 @@
                     // clear the tagcloud
                     $('#reporting-task-tag-cloud').tagcloud('clearSelectedTags');
 
+                    // reset the group combo
+                    $('#reporting-task-bundle-group-combo').combo('setSelectedOption', {
+                        value: ''
+                    });
+
                     // reset the filter
                     applyReportingTaskTypeFilter();
 
@@ -797,6 +854,14 @@
                     if (dataContext.component.state === 'STOPPED' && nfCommon.isEmpty(dataContext.component.validationErrors)) {
                         markup += '<div title="Start" class="pointer start-reporting-task fa fa-play" style="margin-top: 2px; margin-right: 3px;"></div>';
                     }
+
+                    if (dataContext.component.multipleVersionsAvailable === true) {
+                        markup += '<div title="Change Version" class="pointer change-version-reporting-task fa fa-exchange" style="margin-top: 2px; margin-right: 3px;" ></div>';
+                    }
+
+                    if (nfCommon.canModifyController()) {
+                        markup += '<div title="Remove" class="pointer delete-reporting-task fa fa-trash" style="margin-top: 2px; margin-right: 3px;" ></div>';
+                    }
                 }
 
                 if (dataContext.component.persistsState === true) {
@@ -804,10 +869,6 @@
                 }
             }
 
-            if (dataContext.permissions.canWrite && nfCommon.canModifyController()) {
-                markup += '<div title="Remove" class="pointer delete-reporting-task fa fa-trash" style="margin-top: 2px; margin-right: 3px;" ></div>';
-            }
-
             // allow policy configuration conditionally
             if (nfCanvasUtils.isConfigurableAuthorizer() && nfCommon.canAccessTenants()) {
                 markup += '<div title="Access Policies" class="pointer edit-access-policies fa fa-key" style="margin-top: 2px;"></div>';
@@ -828,8 +889,27 @@
                 maxWidth: 90,
                 toolTip: 'Sorts based on presence of bulletins'
             },
-            {id: 'name', name: 'Name', sortable: true, resizable: true, formatter: nameFormatter},
-            {id: 'type', name: 'Type', sortable: true, resizable: true, formatter: typeFormatter},
+            {
+                id: 'name',
+                name: 'Name',
+                sortable: true,
+                resizable: true,
+                formatter: nameFormatter
+            },
+            {
+                id: 'type',
+                name: 'Type',
+                formatter: nfCommon.instanceTypeFormatter,
+                sortable: true,
+                resizable: true
+            },
+            {
+                id: 'bundle',
+                name: 'Bundle',
+                formatter: nfCommon.instanceBundleFormatter,
+                sortable: true,
+                resizable: true
+            },
             {
                 id: 'state',
                 name: 'Run Status',
@@ -894,6 +974,8 @@
                 } else if (target.hasClass('view-state-reporting-task')) {
                     var canClear = reportingTaskEntity.component.state === 'STOPPED' && reportingTaskEntity.component.activeThreadCount === 0;
                     nfComponentState.showState(reportingTaskEntity, canClear);
+                } else if (target.hasClass('change-version-reporting-task')) {
+                    nfComponentVersion.promptForVersionChange(reportingTaskEntity);
                 } else if (target.hasClass('edit-access-policies')) {
                     // show the policies for this service
                     nfPolicyManagement.showReportingTaskPolicy(reportingTaskEntity);
@@ -1081,6 +1163,7 @@
             var tasks = [];
             $.each(response.reportingTasks, function (_, task) {
                 tasks.push($.extend({
+                    type: 'ReportingTask',
                     bulletins: []
                 }, task));
             });
@@ -1201,11 +1284,17 @@
                 } else if (selectedTab === 'Reporting Tasks') {
                     $('#new-reporting-task-dialog').modal('show');
 
-                    // reset the canvas size after the dialog is shown
                     var reportingTaskTypesGrid = $('#reporting-task-types-table').data('gridInstance');
                     if (nfCommon.isDefinedAndNotNull(reportingTaskTypesGrid)) {
-                        reportingTaskTypesGrid.setSelectedRows([0]);
+                        var reportingTaskTypesData = reportingTaskTypesGrid.getData();
+
+                        // reset the canvas size after the dialog is shown
                         reportingTaskTypesGrid.resizeCanvas();
+
+                        // select the first row if possible
+                        if (reportingTaskTypesData.getLength() > 0) {
+                            reportingTaskTypesGrid.setSelectedRows([0]);
+                        }
                     }
 
                     // set the initial focus
@@ -1264,6 +1353,24 @@
         },
 
         /**
+         * Selects the specified reporting task.
+         *
+         * @param {string} reportingTaskId
+         */
+        selectReportingTask: function (reportingTaskId) {
+            var reportingTaskGrid = $('#reporting-tasks-table').data('gridInstance');
+            var reportingTaskData = reportingTaskGrid.getData();
+
+            // select the desired service
+            var row = reportingTaskData.getRowById(reportingTaskId);
+            reportingTaskGrid.setSelectedRows([row]);
+            reportingTaskGrid.scrollRowIntoView(row);
+
+            // select the controller services tab
+            $('#settings-tabs').find('li:eq(2)').click();
+        },
+
+        /**
          * Sets the controller service and reporting task bulletins in their respective tables.
          *
          * @param {object} controllerServiceBulletins

http://git-wip-us.apache.org/repos/asf/nifi/blob/d90cf846/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
index 682ad4c..a039d3b 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
@@ -185,6 +185,124 @@
         currentUser: undefined,
 
         /**
+         * Sorts the specified version strings.
+         *
+         * @param aRawVersion version string
+         * @param bRawVersion version string
+         * @returns {number} negative if a before b, positive if a after b, 0 otherwise
+         */
+        sortVersion: function (aRawVersion, bRawVersion) {
+            if (aRawVersion === bRawVersion) {
+                return 0;
+            }
+
+            // attempt to parse the raw strings
+            var aTokens = aRawVersion.split(/-/);
+            var bTokens = bRawVersion.split(/-/);
+
+            // ensure there is at least one token
+            if (aTokens.length >= 1 && bTokens.length >= 1) {
+                var aVersionTokens = aTokens[0].split(/\./);
+                var bVersionTokens = bTokens[0].split(/\./);
+
+                // ensure both versions have at least one token
+                if (aVersionTokens.length >= 1 && bVersionTokens.length >= 1) {
+                    // find the number of tokens a and b have in common
+                    var commonTokenLength = Math.min(aVersionTokens.length, bVersionTokens.length);
+
+                    // consider all tokens in common
+                    for (var i = 0; i < commonTokenLength; i++) {
+                        var aVersionSegment = parseInt(aVersionTokens[i], 10);
+                        var bVersionSegment = parseInt(bVersionTokens[i], 10);
+
+                        // if both are non numeric, consider the next token
+                        if (isNaN(aVersionSegment) && isNaN(bVersionSegment)) {
+                            continue;
+                        }  else if (isNaN(aVersionSegment)) {
+                            // NaN is considered less
+                            return -1;
+                        } else if (isNaN(bVersionSegment)) {
+                            // NaN is considered less
+                            return 1;
+                        }
+
+                        // if a version at any point does not match
+                        if (aVersionSegment !== bVersionSegment) {
+                            return aVersionSegment - bVersionSegment;
+                        }
+                    }
+
+                    if (aVersionTokens.length === bVersionTokens.length) {
+                        if (aTokens.length === bTokens.length) {
+                            // same version for all tokens so consider the trailing bits (1.1-RC vs 1.1-SNAPSHOT)
+                            var aExtraBits = nfCommon.substringAfterFirst(aRawVersion, aTokens[0]);
+                            var bExtraBits = nfCommon.substringAfterFirst(bRawVersion, bTokens[0]);
+                            return aExtraBits === bExtraBits ? 0 : aExtraBits > bExtraBits ? 1 : -1;
+                        } else {
+                            // in this case, extra bits means it's consider less than no extra bits (1.1 vs 1.1-SNAPSHOT)
+                            return bTokens.length - aTokens.length;
+                        }
+                    } else {
+                        // same version for all tokens in common (ie 1.1 vs 1.1.1)
+                        return aVersionTokens.length - bVersionTokens.length;
+                    }
+                } else if (aVersionTokens.length >= 1) {
+                    // presence of version tokens is considered greater
+                    return 1;
+                } else if (bVersionTokens.length >= 1) {
+                    // presence of version tokens is considered greater
+                    return -1;
+                } else {
+                    return 0;
+                }
+            } else if (aTokens.length >= 1) {
+                // presence of tokens is considered greater
+                return 1;
+            } else if (bTokens.length >= 1) {
+                // presence of tokens is considered greater
+                return -1;
+            } else {
+                return 0;
+            }
+        },
+
+        /**
+         * Sorts the specified type data using the specified sort details.
+         *
+         * @param {object} sortDetails
+         * @param {object} data
+         */
+        sortType: function (sortDetails, data) {
+            // compares two bundles
+            var compareBundle = function (a, b) {
+                var aBundle = nfCommon.formatBundle(a.bundle);
+                var bBundle = nfCommon.formatBundle(b.bundle);
+                return aBundle === bBundle ? 0 : aBundle > bBundle ? 1 : -1;
+            };
+
+            // defines a function for sorting
+            var comparer = function (a, b) {
+                if (sortDetails.columnId === 'version') {
+                    var aVersion = nfCommon.isDefinedAndNotNull(a.bundle[sortDetails.columnId]) ? a.bundle[sortDetails.columnId] : '';
+                    var bVersion = nfCommon.isDefinedAndNotNull(b.bundle[sortDetails.columnId]) ? b.bundle[sortDetails.columnId] : '';
+                    var versionResult = nfCommon.sortVersion(aVersion, bVersion);
+                    return versionResult === 0 ? compareBundle(a, b) : versionResult;
+                } else if (sortDetails.columnId === 'type') {
+                    var aType = nfCommon.substringAfterLast(a[sortDetails.columnId], '.');
+                    var bType = nfCommon.substringAfterLast(b[sortDetails.columnId], '.');
+                    return aType === bType ? 0 : aType > bType ? 1 : -1;
+                } else {
+                    var aString = nfCommon.isDefinedAndNotNull(a[sortDetails.columnId]) ? a[sortDetails.columnId] : '';
+                    var bString = nfCommon.isDefinedAndNotNull(b[sortDetails.columnId]) ? b[sortDetails.columnId] : '';
+                    return aString === bString ? compareBundle(a, b) : aString > bString ? 1 : -1;
+                }
+            };
+
+            // perform the sort
+            data.sort(comparer, sortDetails.sortAsc);
+        },
+
+        /**
          * Formats type of a component for a new instance dialog.
          *
          * @param row
@@ -205,12 +323,116 @@
             }
 
             // type
-            markup += value;
+            markup += nfCommon.escapeHtml(value);
 
             return markup;
         },
 
         /**
+         * Formats the bundle of a component type for the new instance dialog.
+         *
+         * @param row
+         * @param cell
+         * @param value
+         * @param columnDef
+         * @param dataContext
+         * @returns {string}
+         */
+        typeBundleFormatter: function (row, cell, value, columnDef, dataContext) {
+            return nfCommon.escapeHtml(nfCommon.formatBundle(dataContext.bundle));
+        },
+
+        /**
+         * Formats the bundle of a component type for the new instance dialog.
+         *
+         * @param row
+         * @param cell
+         * @param value
+         * @param columnDef
+         * @param dataContext
+         * @returns {string}
+         */
+        typeVersionFormatter: function (row, cell, value, columnDef, dataContext) {
+            var markup = '';
+
+            if (nfCommon.isDefinedAndNotNull(dataContext.bundle)) {
+                markup += ('<div style="float: left;">' + nfCommon.escapeHtml(dataContext.bundle.version) + '</div>');
+            } else {
+                markup += '<div style="float: left;">unversioned</div>';
+            }
+
+            if (!nfCommon.isEmpty(dataContext.controllerServiceApis)) {
+                markup += '<div class="controller-service-apis fa fa-list" title="Compatible Controller Service" style="margin-top: 2px; margin-left: 4px;"></div><span class="hidden row-id">' + nfCommon.escapeHtml(dataContext.id) + '</span>';
+            }
+
+            markup += '<div class="clear"></div>';
+
+            return markup;
+        },
+
+        /**
+         * Formatter for the type column.
+         *
+         * @param {type} row
+         * @param {type} cell
+         * @param {type} value
+         * @param {type} columnDef
+         * @param {type} dataContext
+         * @returns {String}
+         */
+        instanceTypeFormatter: function (row, cell, value, columnDef, dataContext) {
+            if (!dataContext.permissions.canRead) {
+                return '';
+            }
+
+            return nfCommon.escapeHtml(nfCommon.formatType(dataContext.component));
+        },
+
+        /**
+         * Formats the bundle of a component instance for the component listing table.
+         *
+         * @param row
+         * @param cell
+         * @param value
+         * @param columnDef
+         * @param dataContext
+         * @returns {string}
+         */
+        instanceBundleFormatter: function (row, cell, value, columnDef, dataContext) {
+            if (!dataContext.permissions.canRead) {
+                return '';
+            }
+
+            return nfCommon.typeBundleFormatter(row, cell, value, columnDef, dataContext.component);
+        },
+
+        /**
+         * Formats the type of this component.
+         *
+         * @param dataContext component datum
+         */
+        formatType: function (dataContext) {
+            var typeString = nfCommon.substringAfterLast(dataContext.type, '.');
+            if (dataContext.bundle.version !== 'unversioned') {
+                typeString += (' ' + dataContext.bundle.version);
+            }
+            return typeString;
+        },
+
+        /**
+         * Formats the bundle label.
+         *
+         * @param bundle
+         */
+        formatBundle: function (bundle) {
+            var groupString = '';
+            if (bundle.group !== 'default') {
+                groupString = bundle.group + ' - ';
+            }
+            return groupString + bundle.artifact;
+        },
+
+        /**
          * Sets the current user.
          *
          * @param currentUser
@@ -588,6 +810,14 @@
                 if (!nfCommon.isBlank(propertyDescriptor.supportsEl)) {
                     tipContent.push('<b>Supports expression language:</b> ' + nfCommon.escapeHtml(propertyDescriptor.supportsEl));
                 }
+                if (!nfCommon.isBlank(propertyDescriptor.identifiesControllerService)) {
+                    var formattedType = nfCommon.formatType({
+                        'type': propertyDescriptor.identifiesControllerService,
+                        'bundle': propertyDescriptor.identifiesControllerServiceBundle
+                    });
+                    var formattedBundle = nfCommon.formatBundle(propertyDescriptor.identifiesControllerServiceBundle);
+                    tipContent.push('<b>Requires Controller Service:</b> ' + nfCommon.escapeHtml(formattedType + ' from ' + formattedBundle));
+                }
             }
 
             if (nfCommon.isDefinedAndNotNull(propertyHistory)) {
@@ -1252,6 +1482,25 @@
             return formattedBulletinEntities;
         },
 
+        /**
+         * Formats the specified controller service list.
+         *
+         * @param {array} controllerServiceApis
+         * @returns {array}
+         */
+        getFormattedServiceApis: function (controllerServiceApis) {
+            var formattedControllerServiceApis = [];
+            $.each(controllerServiceApis, function (i, controllerServiceApi) {
+                var formattedType = nfCommon.formatType({
+                    'type': controllerServiceApi.type,
+                    'bundle': controllerServiceApi.bundle
+                });
+                var formattedBundle = nfCommon.formatBundle(controllerServiceApi.bundle);
+                formattedControllerServiceApis.push($('<div></div>').text(formattedType + ' from ' + formattedBundle));
+            });
+            return formattedControllerServiceApis;
+        },
+
         getPolicyTypeListing: function (value) {
             var nest = d3.nest()
                 .key(function (d) {

http://git-wip-us.apache.org/repos/asf/nifi/blob/d90cf846/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-processor-details.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-processor-details.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-processor-details.js
index 1b6b28a..69969e3 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-processor-details.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-processor-details.js
@@ -179,7 +179,8 @@
 
                     // populate the processor settings
                     nfCommon.populateField('read-only-processor-id', details['id']);
-                    nfCommon.populateField('read-only-processor-type', nfCommon.substringAfterLast(details['type'], '.'));
+                    nfCommon.populateField('read-only-processor-type', nfCommon.formatType(details));
+                    nfCommon.populateField('read-only-processor-bundle', nfCommon.formatBundle(details['bundle']));
                     nfCommon.populateField('read-only-processor-name', details['name']);
                     nfCommon.populateField('read-only-concurrently-schedulable-tasks', details.config['concurrentlySchedulableTaskCount']);
                     nfCommon.populateField('read-only-scheduling-period', details.config['schedulingPeriod']);

http://git-wip-us.apache.org/repos/asf/nifi/blob/d90cf846/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/pom.xml
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/pom.xml
index 6d7d94b..f803a36 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/pom.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/pom.xml
@@ -41,6 +41,7 @@
         <module>nifi-documentation</module>
         <module>nifi-authorizer</module>
         <module>nifi-properties-loader</module>
+        <module>nifi-standard-prioritizers</module>
     </modules>
     <dependencies>
         <dependency>