You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by kd...@apache.org on 2018/09/22 02:11:03 UTC

[04/51] [partial] nifi-registry git commit: NIFIREG-201 Refactoring project structure to better isolate extensions

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/nf-registry-administration.html
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/nf-registry-administration.html b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/nf-registry-administration.html
new file mode 100644
index 0000000..23f40a0
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/nf-registry-administration.html
@@ -0,0 +1,40 @@
+<!--
+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.
+-->
+
+<div id="nifi-registry-administration-perspective">
+    <mat-button-toggle-group name="nifi-registry-administration-perspective" fxLayout="row"
+                             fxLayoutAlign="space-between center" class="tab-toggle-group">
+        <mat-button-toggle
+                [disabled]="!nfRegistryService.currentUser.resourcePermissions.buckets.canRead"
+                [matTooltip]="'Manage NiFi Registry buckets.'"
+                [checked]="nfRegistryService.adminPerspective === 'workflow'" value="workflow"
+                class="uppercase"
+                (change)="navigateToAdminPerspective($event)"
+                i18n="Workflow administration tab|A description of the type of administration options available.@@nf-admin-workflow-tab-title">
+            Buckets
+        </mat-button-toggle>
+        <mat-button-toggle
+                [disabled]="nfRegistryService.currentUser.anonymous || !nfRegistryService.currentUser.resourcePermissions.tenants.canRead"
+                [matTooltip]="getUserTooltip()"
+                [checked]="nfRegistryService.adminPerspective === 'users'" value="users" class="uppercase"
+                (change)="navigateToAdminPerspective($event)"
+                i18n="Users administration tab|A description of the type of administration options available.@@nf-admin-users-tab-title">
+            Users
+        </mat-button-toggle>
+    </mat-button-toggle-group>
+</div>
+<router-outlet></router-outlet>

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/nf-registry-administration.js
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/nf-registry-administration.js b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/nf-registry-administration.js
new file mode 100644
index 0000000..fa37658
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/nf-registry-administration.js
@@ -0,0 +1,98 @@
+/*
+ * 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.
+ */
+var ngCore = require('@angular/core');
+var NfRegistryService = require('nifi-registry/services/nf-registry.service.js');
+var nfRegistryAnimations = require('nifi-registry/nf-registry.animations.js');
+var ngRouter = require('@angular/router');
+
+/**
+ * NfRegistryAdministration constructor.
+ *
+ * @param nfRegistryService     The nf-registry.service module.
+ * @param router                The angular router module.
+ * @constructor
+ */
+function NfRegistryAdministration(nfRegistryService, router) {
+    //Services
+    this.router = router;
+    this.nfRegistryService = nfRegistryService;
+};
+
+NfRegistryAdministration.prototype = {
+    constructor: NfRegistryAdministration,
+
+    /**
+     * Initialize the component.
+     */
+    ngOnInit: function () {
+        var self = this;
+        this.nfRegistryService.perspective = 'administration';
+        this.nfRegistryService.setBreadcrumbState('in');
+    },
+
+    /**
+     * Destroy the component.
+     */
+    ngOnDestroy: function () {
+        this.nfRegistryService.perspective = '';
+        this.nfRegistryService.setBreadcrumbState('out');
+    },
+
+    /**
+     * Navigates to admin perspective.
+     *
+     * @param $event
+     */
+    navigateToAdminPerspective: function($event) {
+        this.router.navigateByUrl('nifi-registry/administration/' + $event.value);
+    },
+
+    /**
+     * Generate the user tab tooltip.
+     *
+     * @returns {*}
+     */
+    getUserTooltip: function() {
+        if(this.nfRegistryService.currentUser.anonymous) {
+            return 'Please configure NiFi Registry security to enable.';
+        }
+        else {
+            if(!this.nfRegistryService.currentUser.resourcePermissions.tenants.canRead) {
+                return 'You do not have permission. Please contact your System Administrator.'
+            } else {
+                return 'Manage NiFi Registry users and groups.'
+            }
+        }
+    }
+};
+
+NfRegistryAdministration.annotations = [
+    new ngCore.Component({
+        template: require('./nf-registry-administration.html!text'),
+        animations: [nfRegistryAnimations.slideInLeftAnimation],
+        host: {
+            '[@routeAnimation]': 'routeAnimation'
+        }
+    })
+];
+
+NfRegistryAdministration.parameters = [
+    NfRegistryService,
+    ngRouter.Router
+];
+
+module.exports = NfRegistryAdministration;

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/nf-registry-administration.spec.js
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/nf-registry-administration.spec.js b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/nf-registry-administration.spec.js
new file mode 100644
index 0000000..fbf228a
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/nf-registry-administration.spec.js
@@ -0,0 +1,146 @@
+/*
+ * 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.
+ */
+
+var NfRegistryRoutes = require('nifi-registry/nf-registry.routes.js');
+var ngCoreTesting = require('@angular/core/testing');
+var ngCommon = require('@angular/common');
+var ngCommonHttp = require('@angular/common/http');
+var NfRegistryTokenInterceptor = require('nifi-registry/services/nf-registry.token.interceptor.js');
+var NfStorage = require('nifi-registry/services/nf-storage.service.js');
+var ngPlatformBrowser = require('@angular/platform-browser');
+var NfRegistry = require('nifi-registry/nf-registry.js');
+var NfRegistryApi = require('nifi-registry/services/nf-registry.api.js');
+var NfRegistryService = require('nifi-registry/services/nf-registry.service.js');
+var NfPageNotFoundComponent = require('nifi-registry/components/page-not-found/nf-registry-page-not-found.js');
+var NfRegistryExplorer = require('nifi-registry/components/explorer/nf-registry-explorer.js');
+var NfRegistryAdministration = require('nifi-registry/components/administration/nf-registry-administration.js');
+var NfRegistryUsersAdministration = require('nifi-registry/components/administration/users/nf-registry-users-administration.js');
+var NfRegistryAddUser = require('nifi-registry/components/administration/users/dialogs/add-user/nf-registry-add-user.js');
+var NfRegistryManageUser = require('nifi-registry/components/administration/users/sidenav/manage-user/nf-registry-manage-user.js');
+var NfRegistryManageGroup = require('nifi-registry/components/administration/users/sidenav/manage-group/nf-registry-manage-group.js');
+var NfRegistryManageBucket = require('nifi-registry/components/administration/workflow/sidenav/manage-bucket/nf-registry-manage-bucket.js');
+var NfRegistryWorkflowAdministration = require('nifi-registry/components/administration/workflow/nf-registry-workflow-administration.js');
+var NfRegistryGridListViewer = require('nifi-registry/components/explorer/grid-list/registry/nf-registry-grid-list-viewer.js');
+var NfRegistryBucketGridListViewer = require('nifi-registry/components/explorer/grid-list/registry/nf-registry-bucket-grid-list-viewer.js');
+var NfRegistryDropletGridListViewer = require('nifi-registry/components/explorer/grid-list/registry/nf-registry-droplet-grid-list-viewer.js');
+var fdsCore = require('@flow-design-system/core');
+var ngMoment = require('angular2-moment');
+var rxjs = require('rxjs/Rx');
+var NfLoginComponent = require('nifi-registry/components/login/nf-registry-login.js');
+var NfUserLoginComponent = require('nifi-registry/components/login/dialogs/nf-registry-user-login.js');
+
+describe('NfRegistryAdministration Component', function () {
+    var comp;
+    var fixture;
+    var de;
+    var el;
+    var nfRegistryService;
+    var nfRegistryApi;
+
+    beforeEach(function () {
+        ngCoreTesting.TestBed.configureTestingModule({
+            imports: [
+                ngMoment.MomentModule,
+                ngCommonHttp.HttpClientModule,
+                fdsCore,
+                NfRegistryRoutes
+            ],
+            declarations: [
+                NfRegistry,
+                NfRegistryExplorer,
+                NfRegistryAdministration,
+                NfRegistryUsersAdministration,
+                NfRegistryManageUser,
+                NfRegistryManageGroup,
+                NfRegistryManageBucket,
+                NfRegistryAddUser,
+                NfRegistryWorkflowAdministration,
+                NfRegistryGridListViewer,
+                NfRegistryBucketGridListViewer,
+                NfRegistryDropletGridListViewer,
+                NfPageNotFoundComponent,
+                NfLoginComponent,
+                NfUserLoginComponent
+            ],
+            providers: [
+                NfRegistryService,
+                NfRegistryApi,
+                NfStorage,
+                {
+                    provide: ngCommonHttp.HTTP_INTERCEPTORS,
+                    useClass: NfRegistryTokenInterceptor,
+                    multi: true
+                },
+                {
+                    provide: ngCommon.APP_BASE_HREF,
+                    useValue: '/'
+                }
+            ]
+        });
+
+        fixture = ngCoreTesting.TestBed.createComponent(NfRegistryAdministration);
+
+        // test instance
+        comp = fixture.componentInstance;
+
+        // from the root injector
+        nfRegistryService = ngCoreTesting.TestBed.get(NfRegistryService);
+        nfRegistryApi = ngCoreTesting.TestBed.get(NfRegistryApi);
+        de = fixture.debugElement.query(ngPlatformBrowser.By.css('#nifi-registry-administration-perspective'));
+        el = de.nativeElement;
+
+        // Spy
+        spyOn(nfRegistryApi, 'getDroplets').and.callFake(function () {
+        }).and.returnValue(rxjs.Observable.of([{
+            "identifier": "2e04b4fb-9513-47bb-aa74-1ae34616bfdc",
+            "name": "Flow #1",
+            "description": "This is flow #1",
+            "bucketIdentifier": "2f7f9e54-dc09-4ceb-aa58-9fe581319cdc",
+            "createdTimestamp": 1505931890999,
+            "modifiedTimestamp": 1505931890999,
+            "type": "FLOW",
+            "snapshotMetadata": null,
+            "link": {
+                "params": {
+                    "rel": "self"
+                },
+                "href": "flows/2e04b4fb-9513-47bb-aa74-1ae34616bfdc"
+            }
+        }]));
+    });
+
+    it('should have a defined component', function () {
+        fixture.detectChanges();
+
+        //assertions
+        expect(comp).toBeDefined();
+        expect(nfRegistryService.perspective).toBe('administration');
+        expect(nfRegistryService.breadCrumbState).toBe('in');
+        expect(de).toBeDefined();
+    });
+
+    it('should destroy the component', function () {
+        fixture.detectChanges();
+
+        // The function to test
+        comp.ngOnDestroy();
+
+        //assertions
+        expect(nfRegistryService.perspective).toBe('');
+        expect(nfRegistryService.breadCrumbState).toBe('out');
+    });
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/add-user-to-groups/nf-registry-add-user-to-groups.html
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/add-user-to-groups/nf-registry-add-user-to-groups.html b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/add-user-to-groups/nf-registry-add-user-to-groups.html
new file mode 100644
index 0000000..70e59ed
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/add-user-to-groups/nf-registry-add-user-to-groups.html
@@ -0,0 +1,81 @@
+<!--
+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.
+-->
+
+<div id="nifi-registry-admin-add-selected-users-to-group-dialog">
+    <div class="pad-bottom-md" fxLayout="row" fxLayoutAlign="space-between center">
+        <span class="md-card-title">Add user to groups</span>
+        <button mat-icon-button (click)="cancel()">
+            <mat-icon color="primary">close</mat-icon>
+        </button>
+    </div>
+    <div *ngIf="filteredUserGroups.length > 0" class="pad-bottom-md">
+        <div id="nifi-registry-users-administration-list-container-column-header" class="td-data-table">
+            <div class="td-data-table-column" (click)="sortUserGroups(column)"
+                 *ngFor="let column of userGroupsColumns"
+                 fxFlex="{{column.width}}">
+                {{column.label}}
+                <i *ngIf="column.active && column.sortable && column.sortOrder === 'ASC'" class="fa fa-caret-up"
+                   aria-hidden="true"></i>
+                <i *ngIf="column.active && column.sortable && column.sortOrder === 'DESC'" class="fa fa-caret-down"
+                   aria-hidden="true"></i>
+            </div>
+            <div class="td-data-table-column">
+                <mat-checkbox [(ngModel)]="allGroupsSelected"
+                              (checked)="allGroupsSelected"
+                              (change)="toggleUserGroupsSelectAll()"></mat-checkbox>
+            </div>
+        </div>
+        <div id="nifi-registry-add-selected-users-to-group-list-container">
+            <div fxLayout="row" fxLayoutAlign="space-between center"
+                 class="td-data-table-row"
+                 [ngClass]="{'selected' : group.checked}"
+                 *ngFor="let group of filteredUserGroups"
+                 (click)="group.checked = !group.checked;determineAllUserGroupsSelectedState();">
+                <div class="td-data-table-cell" *ngFor="let column of userGroupsColumns"
+                     fxFlex="{{column.width}}">
+                    <div class="ellipsis" matTooltip="{{column.format ? column.format(group[column.name]) : group[column.name]}}">
+                        <i class="fa fa-users push-right-sm" aria-hidden="true"></i>{{column.format ?
+                        column.format(group[column.name]) : group[column.name]}}
+                    </div>
+                </div>
+                <div class="td-data-table-cell">
+                    <mat-checkbox [(ngModel)]="group.checked"
+                                  [checked]="group.checked"
+                                  (change)="determineAllUserGroupsSelectedState()"
+                                  (click)="group.checked = !group.checked;determineAllUserGroupsSelectedState()">
+                    </mat-checkbox>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div class="mat-padding push-bottom-md" *ngIf="filteredUserGroups.length === 0" layout="row"
+         layout-align="center center">
+        <h3>User belongs to all groups.</h3>
+    </div>
+    <div fxLayout="row">
+        <span fxFlex></span>
+        <button (click)="cancel()" color="fds-regular" mat-raised-button
+                i18n="Cancel addition of selected users to group|A button for cancelling the addition of selected users to a group in the registry.@@nf-admin-workflow-cancel-add-selected-users-to-group-button">
+            Cancel
+        </button>
+        <button [disabled]="isAddToSelectedGroupsDisabled" class="push-left-sm" (click)="addToSelectedGroups()"
+                color="fds-primary" mat-raised-button
+                i18n="Add selected users to group button|A button for adding users to an existing group in the registry.@@nf-admin-workflow-add-selected-users-to-group-button">
+            Add
+        </button>
+    </div>
+</div>

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/add-user-to-groups/nf-registry-add-user-to-groups.js
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/add-user-to-groups/nf-registry-add-user-to-groups.js b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/add-user-to-groups/nf-registry-add-user-to-groups.js
new file mode 100644
index 0000000..e808803
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/add-user-to-groups/nf-registry-add-user-to-groups.js
@@ -0,0 +1,254 @@
+/*
+ * 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.
+ */
+
+var covalentCore = require('@covalent/core');
+var NfRegistryApi = require('nifi-registry/services/nf-registry.api.js');
+var ngCore = require('@angular/core');
+var fdsSnackBarsModule = require('@flow-design-system/snackbars');
+var NfRegistryService = require('nifi-registry/services/nf-registry.service.js');
+var ngMaterial = require('@angular/material');
+var $ = require('jquery');
+
+/**
+ * NfRegistryAddUserToGroups constructor.
+ *
+ * @param nfRegistryApi         The api service.
+ * @param tdDataTableService    The covalent data table service module.
+ * @param nfRegistryService     The nf-registry.service module.
+ * @param matDialogRef          The angular material dialog ref.
+ * @param fdsSnackBarService    The FDS snack bar service module.
+ * @param data                  The data passed into this component.
+ * @constructor
+ */
+function NfRegistryAddUserToGroups(nfRegistryApi, tdDataTableService, nfRegistryService, matDialogRef, fdsSnackBarService, data) {
+    //Services
+    this.dataTableService = tdDataTableService;
+    this.snackBarService = fdsSnackBarService;
+    this.nfRegistryService = nfRegistryService;
+    this.nfRegistryApi = nfRegistryApi;
+    this.dialogRef = matDialogRef;
+    this.data = data;
+
+    // local state
+    //make an independent copy of the groups for sorting and selecting within the scope of this component
+    this.groups = $.extend(true, [], this.nfRegistryService.groups);
+    this.filteredUserGroups = [];
+    this.isAddToSelectedGroupsDisabled = true;
+    this.userGroupsSearchTerms = [];
+    this.allGroupsSelected = false;
+    this.userGroupsColumns = [
+        {
+            name: 'identity',
+            label: 'Display Name',
+            sortable: true,
+            tooltip: 'Group name.',
+            width: 100
+        }
+    ];
+};
+
+NfRegistryAddUserToGroups.prototype = {
+    constructor: NfRegistryAddUserToGroups,
+
+    /**
+     * Initialize the component.
+     */
+    ngOnInit: function () {
+        var self = this;
+
+        // filter out any groups that
+        // 1) that are not configurable
+        self.groups = self.groups.filter(function (group) {
+            return (group.configurable) ? true : false
+        });
+        // 2) the user already belongs to
+        this.data.user.userGroups.forEach(function (userGroup) {
+            self.groups = self.groups.filter(function (group) {
+                return (group.identifier !== userGroup.identifier) ? true : false
+            });
+        });
+
+        this.filterGroups();
+        this.deselectAllUserGroups();
+        this.determineAllUserGroupsSelectedState();
+    },
+
+    /**
+     * Filter groups.
+     *
+     * @param {string} [sortBy]       The column name to sort `userGroupsColumns` by.
+     * @param {string} [sortOrder]    The order. Either 'ASC' or 'DES'
+     */
+    filterGroups: function (sortBy, sortOrder) {
+        // if `sortOrder` is `undefined` then use 'ASC'
+        if (sortOrder === undefined) {
+            sortOrder = 'ASC'
+        }
+        // if `sortBy` is `undefined` then find the first sortable column in `dropletColumns`
+        if (sortBy === undefined) {
+            var arrayLength = this.userGroupsColumns.length;
+            for (var i = 0; i < arrayLength; i++) {
+                if (this.userGroupsColumns[i].sortable === true) {
+                    sortBy = this.userGroupsColumns[i].name;
+                    //only one column can be actively sorted so we reset all to inactive
+                    this.userGroupsColumns.forEach(function (c) {
+                        c.active = false;
+                    });
+                    //and set this column as the actively sorted column
+                    this.userGroupsColumns[i].active = true;
+                    this.userGroupsColumns[i].sortOrder = sortOrder;
+                    break;
+                }
+            }
+        }
+
+        var newUserGroupsData = this.groups;
+
+        for (var i = 0; i < this.userGroupsSearchTerms.length; i++) {
+            newUserGroupsData = this.dataTableService.filterData(newUserGroupsData, this.userGroupsSearchTerms[i], true);
+        }
+
+        newUserGroupsData = this.dataTableService.sortData(newUserGroupsData, sortBy, sortOrder);
+        this.filteredUserGroups = newUserGroupsData;
+    },
+
+    /**
+     * Sort `filteredUserGroups` by `column`.
+     *
+     * @param column    The column to sort by.
+     */
+    sortUserGroups: function (column) {
+        if (column.sortable) {
+            var sortBy = column.name;
+            var sortOrder = column.sortOrder = (column.sortOrder === 'ASC') ? 'DESC' : 'ASC';
+            this.filterGroups(sortBy, sortOrder);
+        }
+    },
+
+    /**
+     * Checks the `allGroupsSelected` property state and either selects
+     * or deselects each of the `filteredUserGroups`.
+     */
+    toggleUserGroupsSelectAll: function () {
+        if (this.allGroupsSelected) {
+            this.selectAllUserGroups();
+        } else {
+            this.deselectAllUserGroups();
+        }
+    },
+
+    /**
+     * Sets the `checked` property of each of the `filteredUserGroups` to true
+     * and sets the `isAddToSelectedGroupsDisabled` and the `allGroupsSelected`
+     * properties accordingly.
+     */
+    selectAllUserGroups: function () {
+        this.filteredUserGroups.forEach(function (c) {
+            c.checked = true;
+        });
+        this.isAddToSelectedGroupsDisabled = false;
+        this.allGroupsSelected = true;
+    },
+
+    /**
+     * Sets the `checked` property of each group to false
+     * and sets the `isAddToSelectedGroupsDisabled` and the `allGroupsSelected`
+     * properties accordingly.
+     */
+    deselectAllUserGroups: function () {
+        this.filteredUserGroups.forEach(function (c) {
+            c.checked = false;
+        });
+        this.isAddToSelectedGroupsDisabled = true;
+        this.allGroupsSelected = false;
+    },
+
+    /**
+     * Checks of each of the `filteredUserGroups`'s checked property state
+     * and sets the `allBucketsSelected` and `isAddToSelectedGroupsDisabled`
+     * property accordingly.
+     */
+    determineAllUserGroupsSelectedState: function () {
+        var selected = 0;
+        var allSelected = true;
+        this.filteredUserGroups.forEach(function (c) {
+            if (c.checked) {
+                selected++;
+            }
+            if (c.checked === undefined || c.checked === false) {
+                allSelected = false;
+            }
+        });
+
+        if (selected > 0) {
+            this.isAddToSelectedGroupsDisabled = false;
+        } else {
+            this.isAddToSelectedGroupsDisabled = true;
+        }
+
+        this.allGroupsSelected = allSelected;
+    },
+
+    /**
+     * Adds users to each of the selected groups.
+     */
+    addToSelectedGroups: function () {
+        var self = this;
+        var selectedGroups = this.filteredUserGroups.filter(function (filteredUserGroup) {
+            return filteredUserGroup.checked;
+        });
+        selectedGroups.forEach(function (selectedGroup) {
+            selectedGroup.users.push(self.data.user);
+            self.nfRegistryApi.updateUserGroup(selectedGroup.identifier, selectedGroup.identity, selectedGroup.users).subscribe(function (group) {
+                self.dialogRef.close();
+                var snackBarRef = self.snackBarService.openCoaster({
+                    title: 'Success',
+                    message: 'User has been added to the ' + group.identity + ' group.',
+                    verticalPosition: 'bottom',
+                    horizontalPosition: 'right',
+                    icon: 'fa fa-check-circle-o',
+                    color: '#1EB475',
+                    duration: 3000
+                });
+            });
+        });
+    },
+
+    /**
+     * Cancel adding selected users to groups and close the dialog.
+     */
+    cancel: function () {
+        this.dialogRef.close();
+    }
+};
+
+NfRegistryAddUserToGroups.annotations = [
+    new ngCore.Component({
+        template: require('./nf-registry-add-user-to-groups.html!text')
+    })
+];
+
+NfRegistryAddUserToGroups.parameters = [
+    NfRegistryApi,
+    covalentCore.TdDataTableService,
+    NfRegistryService,
+    ngMaterial.MatDialogRef,
+    fdsSnackBarsModule.FdsSnackBarService,
+    ngMaterial.MAT_DIALOG_DATA
+];
+
+module.exports = NfRegistryAddUserToGroups;

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/add-user-to-groups/nf-registry-add-user-to-groups.spec.js
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/add-user-to-groups/nf-registry-add-user-to-groups.spec.js b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/add-user-to-groups/nf-registry-add-user-to-groups.spec.js
new file mode 100644
index 0000000..1e0140d
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/add-user-to-groups/nf-registry-add-user-to-groups.spec.js
@@ -0,0 +1,172 @@
+/*
+ * 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.
+ */
+
+var NfRegistryApi = require('nifi-registry/services/nf-registry.api.js');
+var NfRegistryService = require('nifi-registry/services/nf-registry.service.js');
+var NfRegistryAddUserToGroups = require('nifi-registry/components/administration/users/dialogs/add-user-to-groups/nf-registry-add-user-to-groups.js');
+var rxjs = require('rxjs/Rx');
+var covalentCore = require('@covalent/core');
+var fdsSnackBarsModule = require('@flow-design-system/snackbars');
+
+describe('NfRegistryAddUserToGroups Component isolated unit tests', function () {
+    var comp;
+    var nfRegistryService;
+    var nfRegistryApi;
+    var snackBarService;
+    var dataTableService;
+
+    beforeEach(function () {
+        nfRegistryService = new NfRegistryService();
+        // setup the nfRegistryService
+        nfRegistryService.user = {identifier: 3, identity: 'User 3', userGroups: []};
+        nfRegistryService.groups = [{identifier: 1, identity: 'Group 1', configurable: true, checked: true, users: []}];
+
+        nfRegistryApi = new NfRegistryApi();
+        snackBarService = new fdsSnackBarsModule.FdsSnackBarService();
+        dataTableService = new covalentCore.TdDataTableService();
+        comp = new NfRegistryAddUserToGroups(nfRegistryApi, dataTableService, nfRegistryService, {
+            close: function () {
+            }
+        }, snackBarService, {user: nfRegistryService.user});
+
+        // Spy
+        spyOn(nfRegistryApi, 'getUserGroup').and.callFake(function () {
+        }).and.returnValue(rxjs.Observable.of({identifier: 1, identity: 'Group 1'}));
+        spyOn(nfRegistryApi, 'updateUserGroup').and.callFake(function () {
+        }).and.returnValue(rxjs.Observable.of({identifier: 1, identity: 'Group 1'}));
+        spyOn(comp.dialogRef, 'close');
+        spyOn(comp.snackBarService, 'openCoaster');
+        spyOn(comp, 'filterGroups').and.callThrough();
+
+        // initialize the component
+        comp.ngOnInit();
+
+        //assertions
+        expect(comp.filterGroups).toHaveBeenCalled();
+        expect(comp.filteredUserGroups[0].identity).toEqual('Group 1');
+        expect(comp.filteredUserGroups.length).toBe(1);
+        expect(comp).toBeDefined();
+    });
+
+    it('should make a call to the api to add user to selected groups', function () {
+        // select a group
+        comp.filteredUserGroups[0].checked = true;
+
+        // the function to test
+        comp.addToSelectedGroups();
+
+        //assertions
+        expect(comp.dialogRef.close).toHaveBeenCalled();
+        expect(comp.snackBarService.openCoaster).toHaveBeenCalled();
+    });
+
+    it('should determine if all groups are selected', function () {
+        // select a group
+        comp.filteredUserGroups[0].checked = true;
+
+        // the function to test
+        comp.determineAllUserGroupsSelectedState();
+
+        //assertions
+        expect(comp.allGroupsSelected).toBe(true);
+        expect(comp.isAddToSelectedGroupsDisabled).toBe(false);
+    });
+
+    it('should determine if all groups are not selected', function () {
+        // select a group
+        comp.filteredUserGroups[0].checked = false;
+
+        // the function to test
+        comp.determineAllUserGroupsSelectedState();
+
+        //assertions
+        expect(comp.allGroupsSelected).toBe(false);
+        expect(comp.isAddToSelectedGroupsDisabled).toBe(true);
+    });
+
+    it('should select all groups.', function () {
+        // The function to test
+        comp.selectAllUserGroups();
+
+        //assertions
+        expect(comp.filteredUserGroups[0].checked).toBe(true);
+        expect(comp.isAddToSelectedGroupsDisabled).toBe(false);
+        expect(comp.allGroupsSelected).toBe(true);
+    });
+
+    it('should deselect all groups.', function () {
+        // select a group
+        comp.filteredUserGroups[0].checked = true;
+
+        // The function to test
+        comp.deselectAllUserGroups();
+
+        //assertions
+        expect(comp.filteredUserGroups[0].checked).toBe(false);
+        expect(comp.isAddToSelectedGroupsDisabled).toBe(true);
+        expect(comp.allGroupsSelected).toBe(false);
+    });
+
+    it('should toggle all groups `checked` properties to true.', function () {
+        //Spy
+        spyOn(comp, 'selectAllUserGroups').and.callFake(function () {
+        });
+
+        comp.allGroupsSelected = true;
+
+        // The function to test
+        comp.toggleUserGroupsSelectAll();
+
+        //assertions
+        expect(comp.selectAllUserGroups).toHaveBeenCalled();
+    });
+
+    it('should toggle all groups `checked` properties to false.', function () {
+        //Spy
+        spyOn(comp, 'deselectAllUserGroups').and.callFake(function () {
+        });
+
+        comp.allGroupsSelected = false;
+
+        // The function to test
+        comp.toggleUserGroupsSelectAll();
+
+        //assertions
+        expect(comp.deselectAllUserGroups).toHaveBeenCalled();
+    });
+
+    it('should sort `groups` by `column`', function () {
+        // object to be updated by the test
+        var column = {name: 'name', label: 'Group Name', sortable: true};
+
+        // The function to test
+        comp.sortUserGroups(column);
+
+        //assertions
+        var filterGroupsCall = comp.filterGroups.calls.mostRecent();
+        expect(filterGroupsCall.args[0]).toBe('name');
+        expect(filterGroupsCall.args[1]).toBe('ASC');
+    });
+
+    it('should cancel the addition of the user to any group', function () {
+        // the function to test
+        comp.cancel();
+
+        //assertions
+        expect(comp.dialogRef.close).toHaveBeenCalled();
+    });
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/add-user/nf-registry-add-user.html
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/add-user/nf-registry-add-user.html b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/add-user/nf-registry-add-user.html
new file mode 100644
index 0000000..d7423ac
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/add-user/nf-registry-add-user.html
@@ -0,0 +1,46 @@
+<!--
+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.
+-->
+
+<div id="nifi-registry-admin-add-user-dialog">
+    <div class="pad-bottom-md" fxLayout="row" fxLayoutAlign="space-between center">
+        <span class="md-card-title">Add User</span>
+        <button mat-icon-button (click)="cancel()">
+            <mat-icon color="primary">close</mat-icon>
+        </button>
+    </div>
+    <div fxLayout="column" fxLayoutAlign="space-between start" class="pad-bottom-md">
+        <div class="pad-bottom-md fill-available-width">
+            <mat-input-container floatPlaceholder="always" fxFlex>
+                <input #newUserInput matInput floatPlaceholder="always" placeholder="Identity/Username">
+            </mat-input-container>
+        </div>
+        <mat-checkbox [(ngModel)]="keepDialogOpen">
+            Keep this dialog open after adding user
+        </mat-checkbox>
+    </div>
+    <div fxLayout="row">
+        <span fxFlex></span>
+        <button (click)="cancel()" color="fds-regular" mat-raised-button
+                i18n="Cancel creation of new user|A button for cancelling the creation of a new user in the registry.@@nf-admin-workflow-create-bucket-button">
+            Cancel
+        </button>
+        <button [disabled]="newUserInput.value.length === 0" class="push-left-sm" id="add-new-user-button" (click)="addUser(newUserInput)" color="fds-primary" mat-raised-button
+                i18n="Add new user button|A button for adding a new user in the registry.@@nf-admin-workflow-add-user-button">
+            Add
+        </button>
+    </div>
+</div>

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/add-user/nf-registry-add-user.js
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/add-user/nf-registry-add-user.js b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/add-user/nf-registry-add-user.js
new file mode 100644
index 0000000..586b7f7
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/add-user/nf-registry-add-user.js
@@ -0,0 +1,107 @@
+/*
+ * 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.
+ */
+
+var ngCore = require('@angular/core');
+var NfRegistryService = require('nifi-registry/services/nf-registry.service.js');
+var NfRegistryApi = require('nifi-registry/services/nf-registry.api.js');
+var ngMaterial = require('@angular/material');
+var fdsSnackBarsModule = require('@flow-design-system/snackbars');
+
+/**
+ * NfRegistryAddUser constructor.
+ *
+ * @param nfRegistryApi         The api service.
+ * @param nfRegistryService     The nf-registry.service module.
+ * @param fdsSnackBarService    The FDS snack bar service module.
+ * @param matDialogRef          The angular material dialog ref.
+ * @constructor
+ */
+function NfRegistryAddUser(nfRegistryApi, nfRegistryService, fdsSnackBarService, matDialogRef) {
+    // Services
+    this.snackBarService = fdsSnackBarService;
+    this.nfRegistryService = nfRegistryService;
+    this.nfRegistryApi = nfRegistryApi;
+    this.dialogRef = matDialogRef;
+    // local state
+    this.keepDialogOpen = false;
+};
+
+NfRegistryAddUser.prototype = {
+    constructor: NfRegistryAddUser,
+
+    /**
+     * Create a new user.
+     *
+     * @param addUserInput     The addUserInput element.
+     */
+    addUser: function (addUserInput) {
+        var self = this;
+        this.nfRegistryApi.addUser(addUserInput.value).subscribe(function (user) {
+            if (!user.error) {
+                self.nfRegistryService.users.push(user);
+                self.nfRegistryService.allUsersAndGroupsSelected = false;
+                self.nfRegistryService.filterUsersAndGroups();
+                if (self.keepDialogOpen !== true) {
+                    self.dialogRef.close();
+                }
+                self.snackBarService.openCoaster({
+                    title: 'Success',
+                    message: 'User has been added.',
+                    verticalPosition: 'bottom',
+                    horizontalPosition: 'right',
+                    icon: 'fa fa-check-circle-o',
+                    color: '#1EB475',
+                    duration: 3000
+                });
+            } else {
+                self.dialogRef.close();
+            }
+        });
+    },
+
+    /**
+     * Cancel creation of a new bucket and close dialog.
+     */
+    cancel: function () {
+        this.dialogRef.close();
+    },
+
+    /**
+     * Focus the new user input.
+     */
+    ngAfterViewChecked: function () {
+        this.newUserInput.nativeElement.focus();
+    }
+};
+
+NfRegistryAddUser.annotations = [
+    new ngCore.Component({
+        template: require('./nf-registry-add-user.html!text'),
+        queries: {
+            newUserInput: new ngCore.ViewChild('newUserInput')
+        }
+    })
+];
+
+NfRegistryAddUser.parameters = [
+    NfRegistryApi,
+    NfRegistryService,
+    fdsSnackBarsModule.FdsSnackBarService,
+    ngMaterial.MatDialogRef
+];
+
+module.exports = NfRegistryAddUser;

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/add-user/nf-registry-add-user.spec.js
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/add-user/nf-registry-add-user.spec.js b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/add-user/nf-registry-add-user.spec.js
new file mode 100644
index 0000000..17f12f5
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/add-user/nf-registry-add-user.spec.js
@@ -0,0 +1,86 @@
+/*
+ * 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.
+ */
+
+var NfRegistryApi = require('nifi-registry/services/nf-registry.api.js');
+var NfRegistryService = require('nifi-registry/services/nf-registry.service.js');
+var NfRegistryAddUser = require('nifi-registry/components/administration/users/dialogs/add-user/nf-registry-add-user.js');
+var rxjs = require('rxjs/Rx');
+
+describe('NfRegistryAddUser Component isolated unit tests', function () {
+    var comp;
+    var nfRegistryService;
+    var nfRegistryApi;
+
+    beforeEach(function () {
+        nfRegistryService = new NfRegistryService();
+        nfRegistryApi = new NfRegistryApi();
+        comp = new NfRegistryAddUser(nfRegistryApi,
+            nfRegistryService,
+            {
+                openCoaster: function () {
+                }
+            },
+            {
+                close: function () {
+                }
+            });
+
+        // Spy
+        spyOn(nfRegistryApi, 'addUser').and.callFake(function () {
+        }).and.returnValue(rxjs.Observable.of([{
+            'identifier': '2e04b4fb-9513-47bb-aa74-1ae34616bfdc',
+            'identity': 'New User #1'
+        }]));
+        spyOn(nfRegistryService, 'filterUsersAndGroups');
+        spyOn(comp.dialogRef, 'close');
+    });
+
+    it('should make a call to the api to create a new user and close the dialog', function () {
+        // the function to test
+        comp.addUser({value: 'New User #1'});
+
+        //assertions
+        expect(comp).toBeDefined();
+        expect(nfRegistryService.users.length).toBe(1);
+        expect(nfRegistryService.allUsersAndGroupsSelected).toBe(false);
+        expect(nfRegistryService.filterUsersAndGroups).toHaveBeenCalled();
+        expect(comp.dialogRef.close).toHaveBeenCalled();
+    });
+
+    it('should make a call to the api to create a new user and keep the dialog open', function () {
+        // setup the component
+        comp.keepDialogOpen = true;
+
+        // the function to test
+        comp.addUser({value: 'New User #1'});
+
+        //assertions
+        expect(comp).toBeDefined();
+        expect(nfRegistryService.users.length).toBe(1);
+        expect(nfRegistryService.allUsersAndGroupsSelected).toBe(false);
+        expect(nfRegistryService.filterUsersAndGroups).toHaveBeenCalled();
+        expect(comp.dialogRef.close.calls.count()).toEqual(0);
+    });
+
+    it('should cancel the creation of a new user', function () {
+        // the function to test
+        comp.cancel();
+
+        //assertions
+        expect(comp.dialogRef.close).toHaveBeenCalled();
+    });
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/add-users-to-group/nf-registry-add-users-to-group.html
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/add-users-to-group/nf-registry-add-users-to-group.html b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/add-users-to-group/nf-registry-add-users-to-group.html
new file mode 100644
index 0000000..6f8670c
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/add-users-to-group/nf-registry-add-users-to-group.html
@@ -0,0 +1,80 @@
+<!--
+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.
+-->
+
+<div id="nifi-registry-admin-add-selected-users-to-group-dialog">
+    <div class="pad-bottom-md" fxLayout="row" fxLayoutAlign="space-between center">
+        <span class="md-card-title">Add users to group</span>
+        <button mat-icon-button (click)="cancel()">
+            <mat-icon color="primary">close</mat-icon>
+        </button>
+    </div>
+    <div *ngIf="filteredUsers.length > 0" class="pad-bottom-md">
+        <div id="nifi-registry-users-administration-list-container-column-header" class="td-data-table">
+            <div class="td-data-table-column" (click)="sortUsers(column)"
+                 *ngFor="let column of usersColumns"
+                 fxFlex="{{column.width}}">
+                {{column.label}}
+                <i *ngIf="column.active && column.sortable && column.sortOrder === 'ASC'" class="fa fa-caret-up"
+                   aria-hidden="true"></i>
+                <i *ngIf="column.active && column.sortable && column.sortOrder === 'DESC'" class="fa fa-caret-down"
+                   aria-hidden="true"></i>
+            </div>
+            <div class="td-data-table-column">
+                <mat-checkbox [(ngModel)]="allUsersSelected"
+                              (checked)="allUsersSelected"
+                              (change)="toggleUsersSelectAll()"></mat-checkbox>
+            </div>
+        </div>
+        <div id="nifi-registry-add-selected-users-to-group-list-container">
+            <div fxLayout="row" fxLayoutAlign="space-between center"
+                 class="td-data-table-row"
+                 [ngClass]="{'selected' : user.checked}"
+                 *ngFor="let user of filteredUsers"
+                 (click)="user.checked = !user.checked;determineAllUsersSelectedState();">
+                <div class="td-data-table-cell" *ngFor="let column of usersColumns"
+                     fxFlex="{{column.width}}">
+                    <div class="ellipsis" matTooltip="{{column.format ? column.format(user[column.name]) : user[column.name]}}">
+                        {{column.format ? column.format(user[column.name]) : user[column.name]}}
+                    </div>
+                </div>
+                <div class="td-data-table-cell">
+                    <mat-checkbox [(ngModel)]="user.checked"
+                                  [checked]="user.checked"
+                                  (change)="determineAllUsersSelectedState()"
+                                  (click)="user.checked = !user.checked;determineAllUsersSelectedState()">
+                    </mat-checkbox>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div class="mat-padding push-bottom-md" *ngIf="filteredUsers.length === 0" layout="row"
+         layout-align="center center">
+        <h3>All users belong to this group.</h3>
+    </div>
+    <div fxLayout="row">
+        <span fxFlex></span>
+        <button (click)="cancel()" color="fds-regular" mat-raised-button
+                i18n="Cancel addition of selected users to group|A button for cancelling the addition of selected users to a group in the registry.@@nf-admin-workflow-cancel-add-selected-users-to-group-button">
+            Cancel
+        </button>
+        <button [disabled]="isAddSelectedUsersToGroupDisabled" class="push-left-sm" (click)="addSelectedUsersToGroup()"
+                color="fds-primary" mat-raised-button
+                i18n="Add selected users to group button|A button for adding users to an existing group in the registry.@@nf-admin-workflow-add-selected-users-to-group-button">
+            Add
+        </button>
+    </div>
+</div>

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/add-users-to-group/nf-registry-add-users-to-group.js
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/add-users-to-group/nf-registry-add-users-to-group.js b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/add-users-to-group/nf-registry-add-users-to-group.js
new file mode 100644
index 0000000..fff468b
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/add-users-to-group/nf-registry-add-users-to-group.js
@@ -0,0 +1,247 @@
+/*
+ * 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.
+ */
+
+var covalentCore = require('@covalent/core');
+var NfRegistryApi = require('nifi-registry/services/nf-registry.api.js');
+var ngCore = require('@angular/core');
+var fdsSnackBarsModule = require('@flow-design-system/snackbars');
+var NfRegistryService = require('nifi-registry/services/nf-registry.service.js');
+var ngMaterial = require('@angular/material');
+var $ = require('jquery');
+
+/**
+ * NfRegistryAddUsersToGroup constructor.
+ *
+ * @param nfRegistryApi         The api service.
+ * @param tdDataTableService    The covalent data table service module.
+ * @param nfRegistryService     The nf-registry.service module.
+ * @param matDialogRef          The angular material dialog ref.
+ * @param fdsSnackBarService    The FDS snack bar service module.
+ * @param data                  The data passed into this component.
+ * @constructor
+ */
+function NfRegistryAddUsersToGroup(nfRegistryApi, tdDataTableService, nfRegistryService, matDialogRef, fdsSnackBarService, data) {
+    //  Services
+    this.dataTableService = tdDataTableService;
+    this.snackBarService = fdsSnackBarService;
+    this.nfRegistryService = nfRegistryService;
+    this.nfRegistryApi = nfRegistryApi;
+    this.dialogRef = matDialogRef;
+    this.data = data;
+
+    // local state
+    //make an independent copy of the users for sorting and selecting within the scope of this component
+    this.users = $.extend(true, [], this.nfRegistryService.users);
+    this.filteredUsers = [];
+    this.isAddSelectedUsersToGroupDisabled = true;
+    this.usersSearchTerms = [];
+    this.allUsersSelected = false;
+    this.usersColumns = [
+        {
+            name: 'identity',
+            label: 'Display Name',
+            sortable: true,
+            tooltip: 'Group name.',
+            width: 100
+        }
+    ];
+};
+
+NfRegistryAddUsersToGroup.prototype = {
+    constructor: NfRegistryAddUsersToGroup,
+
+    /**
+     * Initialize the component.
+     */
+    ngOnInit: function () {
+        var self = this;
+
+        this.data.group.users.forEach(function (groupUser) {
+            self.users = self.users.filter(function (user) {
+                return (user.identifier !== groupUser.identifier) ? true : false
+            });
+        });
+
+        this.filterUsers();
+        this.deselectAllUsers();
+        this.determineAllUsersSelectedState();
+    },
+
+    /**
+     * Filter users.
+     *
+     * @param {string} [sortBy]       The column name to sort `usersColumns` by.
+     * @param {string} [sortOrder]    The order. Either 'ASC' or 'DES'
+     */
+    filterUsers: function (sortBy, sortOrder) {
+        // if `sortOrder` is `undefined` then use 'ASC'
+        if (sortOrder === undefined) {
+            sortOrder = 'ASC'
+        }
+        // if `sortBy` is `undefined` then find the first sortable column in `dropletColumns`
+        if (sortBy === undefined) {
+            var arrayLength = this.usersColumns.length;
+            for (var i = 0; i < arrayLength; i++) {
+                if (this.usersColumns[i].sortable === true) {
+                    sortBy = this.usersColumns[i].name;
+                    //only one column can be actively sorted so we reset all to inactive
+                    this.usersColumns.forEach(function (c) {
+                        c.active = false;
+                    });
+                    //and set this column as the actively sorted column
+                    this.usersColumns[i].active = true;
+                    this.usersColumns[i].sortOrder = sortOrder;
+                    break;
+                }
+            }
+        }
+
+        var newUsersData = this.users;
+
+        for (var i = 0; i < this.usersSearchTerms.length; i++) {
+            newUsersData = this.dataTableService.filterData(newUsersData, this.usersSearchTerms[i], true);
+        }
+
+        newUsersData = this.dataTableService.sortData(newUsersData, sortBy, sortOrder);
+        this.filteredUsers = newUsersData;
+    },
+
+    /**
+     * Sort `filteredUsers` by `column`.
+     *
+     * @param column    The column to sort by.
+     */
+    sortUsers: function (column) {
+        if (column.sortable) {
+            var sortBy = column.name;
+            var sortOrder = column.sortOrder = (column.sortOrder === 'ASC') ? 'DESC' : 'ASC';
+            this.filterUsers(sortBy, sortOrder);
+        }
+    },
+
+    /**
+     * Checks the `allUsersSelected` property state and either selects
+     * or deselects each of the `filteredUsers`.
+     */
+    toggleUsersSelectAll: function () {
+        if (this.allUsersSelected) {
+            this.selectAllUsers();
+        } else {
+            this.deselectAllUsers();
+        }
+    },
+
+    /**
+     * Sets the `checked` property of each of the `filteredUsers` to true
+     * and sets the `isAddSelectedUsersToGroupDisabled` and the `allUsersSelected`
+     * properties accordingly.
+     */
+    selectAllUsers: function () {
+        this.filteredUsers.forEach(function (c) {
+            c.checked = true;
+        });
+        this.isAddSelectedUsersToGroupDisabled = false;
+        this.allUsersSelected = true;
+    },
+
+    /**
+     * Sets the `checked` property of each group to false
+     * and sets the `isAddSelectedUsersToGroupDisabled` and the `allUsersSelected`
+     * properties accordingly.
+     */
+    deselectAllUsers: function () {
+        this.filteredUsers.forEach(function (c) {
+            c.checked = false;
+        });
+        this.isAddSelectedUsersToGroupDisabled = true;
+        this.allUsersSelected = false;
+    },
+
+    /**
+     * Checks of each of the `filteredUsers`'s checked property state
+     * and sets the `allBucketsSelected` and `isAddSelectedUsersToGroupDisabled`
+     * property accordingly.
+     */
+    determineAllUsersSelectedState: function () {
+        var selected = 0;
+        var allSelected = true;
+        this.filteredUsers.forEach(function (c) {
+            if (c.checked) {
+                selected++;
+            }
+            if (c.checked === undefined || c.checked === false) {
+                allSelected = false;
+            }
+        });
+
+        if (selected > 0) {
+            this.isAddSelectedUsersToGroupDisabled = false;
+        } else {
+            this.isAddSelectedUsersToGroupDisabled = true;
+        }
+
+        this.allUsersSelected = allSelected;
+    },
+
+    /**
+     * Adds each of the selected users to this group.
+     */
+    addSelectedUsersToGroup: function () {
+        var self = this;
+        this.filteredUsers.filter(function (filteredUser) {
+            if(filteredUser.checked) {
+                self.data.group.users.push(filteredUser);
+            }
+        });
+        this.nfRegistryApi.updateUserGroup(self.data.group.identifier, self.data.group.identity, self.data.group.users).subscribe(function (group) {
+            self.dialogRef.close();
+            var snackBarRef = self.snackBarService.openCoaster({
+                title: 'Success',
+                message: 'Selected users have been added to the ' + self.data.group.identity + ' group.',
+                verticalPosition: 'bottom',
+                horizontalPosition: 'right',
+                icon: 'fa fa-check-circle-o',
+                color: '#1EB475',
+                duration: 3000
+            });
+        });
+    },
+
+    /**
+     * Cancel adding selected users to groups and close the dialog.
+     */
+    cancel: function () {
+        this.dialogRef.close();
+    }
+};
+
+NfRegistryAddUsersToGroup.annotations = [
+    new ngCore.Component({
+        template: require('./nf-registry-add-users-to-group.html!text')
+    })
+];
+
+NfRegistryAddUsersToGroup.parameters = [
+    NfRegistryApi,
+    covalentCore.TdDataTableService,
+    NfRegistryService,
+    ngMaterial.MatDialogRef,
+    fdsSnackBarsModule.FdsSnackBarService,
+    ngMaterial.MAT_DIALOG_DATA
+];
+
+module.exports = NfRegistryAddUsersToGroup;

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/add-users-to-group/nf-registry-add-users-to-group.spec.js
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/add-users-to-group/nf-registry-add-users-to-group.spec.js b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/add-users-to-group/nf-registry-add-users-to-group.spec.js
new file mode 100644
index 0000000..c1d2c71
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/add-users-to-group/nf-registry-add-users-to-group.spec.js
@@ -0,0 +1,170 @@
+/*
+ * 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.
+ */
+
+var NfRegistryApi = require('nifi-registry/services/nf-registry.api.js');
+var NfRegistryService = require('nifi-registry/services/nf-registry.service.js');
+var NfRegistryAddUsersToGroup = require('nifi-registry/components/administration/users/dialogs/add-users-to-group/nf-registry-add-users-to-group.js');
+var rxjs = require('rxjs/Rx');
+var covalentCore = require('@covalent/core');
+var fdsSnackBarsModule = require('@flow-design-system/snackbars');
+
+describe('NfRegistryAddUsersToGroup Component isolated unit tests', function () {
+    var comp;
+    var nfRegistryService;
+    var nfRegistryApi;
+    var snackBarService;
+    var dataTableService;
+
+    beforeEach(function () {
+        nfRegistryService = new NfRegistryService();
+        // setup the nfRegistryService
+        nfRegistryService.group = {identifier: 1, identity: 'Group 1', users: []};
+        nfRegistryService.users = [{identifier: 2, identity: 'User 1', checked: true}];
+
+        nfRegistryApi = new NfRegistryApi();
+        snackBarService = new fdsSnackBarsModule.FdsSnackBarService();
+        dataTableService = new covalentCore.TdDataTableService();
+        comp = new NfRegistryAddUsersToGroup(nfRegistryApi, dataTableService, nfRegistryService, {
+            close: function () {
+            }
+        }, snackBarService, {group: nfRegistryService.group});
+
+        // Spy
+        spyOn(nfRegistryApi, 'updateUserGroup').and.callFake(function () {
+        }).and.returnValue(rxjs.Observable.of({identifier: 1, identity: 'Group 1'}));
+        spyOn(comp.dialogRef, 'close');
+        spyOn(comp.snackBarService, 'openCoaster');
+        spyOn(comp, 'filterUsers').and.callThrough();
+
+        // initialize the component
+        comp.ngOnInit();
+
+        //assertions
+        expect(comp.filterUsers).toHaveBeenCalled();
+        expect(comp.filteredUsers[0].identity).toEqual('User 1');
+        expect(comp.filteredUsers.length).toBe(1);
+        expect(comp).toBeDefined();
+    });
+
+    it('should make a call to the api to add selected users to the group', function () {
+        // select a group
+        comp.filteredUsers[0].checked = true;
+
+        // the function to test
+        comp.addSelectedUsersToGroup();
+
+        //assertions
+        expect(comp.dialogRef.close).toHaveBeenCalled();
+        expect(comp.snackBarService.openCoaster).toHaveBeenCalled();
+    });
+
+    it('should determine if all users are selected', function () {
+        // select a group
+        comp.filteredUsers[0].checked = true;
+
+        // the function to test
+        comp.determineAllUsersSelectedState();
+
+        //assertions
+        expect(comp.allUsersSelected).toBe(true);
+        expect(comp.isAddSelectedUsersToGroupDisabled).toBe(false);
+    });
+
+    it('should determine all user groups are not selected', function () {
+        // select a group
+        comp.filteredUsers[0].checked = false;
+
+        // the function to test
+        comp.determineAllUsersSelectedState();
+
+        //assertions
+        expect(comp.allUsersSelected).toBe(false);
+        expect(comp.isAddSelectedUsersToGroupDisabled).toBe(true);
+    });
+
+    it('should select all groups.', function () {
+        // The function to test
+        comp.selectAllUsers();
+
+        //assertions
+        expect(comp.filteredUsers[0].checked).toBe(true);
+        expect(comp.isAddSelectedUsersToGroupDisabled).toBe(false);
+        expect(comp.allUsersSelected).toBe(true);
+    });
+
+    it('should deselect all groups.', function () {
+        // select a group
+        comp.filteredUsers[0].checked = true;
+
+        // The function to test
+        comp.deselectAllUsers();
+
+        //assertions
+        expect(comp.filteredUsers[0].checked).toBe(false);
+        expect(comp.isAddSelectedUsersToGroupDisabled).toBe(true);
+        expect(comp.allUsersSelected).toBe(false);
+    });
+
+    it('should toggle all groups `checked` properties to true.', function () {
+        //Spy
+        spyOn(comp, 'selectAllUsers').and.callFake(function () {
+        });
+
+        comp.allUsersSelected = true;
+
+        // The function to test
+        comp.toggleUsersSelectAll();
+
+        //assertions
+        expect(comp.selectAllUsers).toHaveBeenCalled();
+    });
+
+    it('should toggle all groups `checked` properties to false.', function () {
+        //Spy
+        spyOn(comp, 'deselectAllUsers').and.callFake(function () {
+        });
+
+        comp.allUsersSelected = false;
+
+        // The function to test
+        comp.toggleUsersSelectAll();
+
+        //assertions
+        expect(comp.deselectAllUsers).toHaveBeenCalled();
+    });
+
+    it('should sort `groups` by `column`', function () {
+        // object to be updated by the test
+        var column = {name: 'name', label: 'Group Name', sortable: true};
+
+        // The function to test
+        comp.sortUsers(column);
+
+        //assertions
+        var filterUsersCall = comp.filterUsers.calls.mostRecent();
+        expect(filterUsersCall.args[0]).toBe('name');
+        expect(filterUsersCall.args[1]).toBe('ASC');
+    });
+
+    it('should cancel the creation of a new user', function () {
+        // the function to test
+        comp.cancel();
+
+        //assertions
+        expect(comp.dialogRef.close).toHaveBeenCalled();
+    });
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/create-new-group/nf-registry-create-new-group.html
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/create-new-group/nf-registry-create-new-group.html b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/create-new-group/nf-registry-create-new-group.html
new file mode 100644
index 0000000..d153651
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/create-new-group/nf-registry-create-new-group.html
@@ -0,0 +1,46 @@
+<!--
+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.
+-->
+
+<div id="nifi-registry-admin-create-new-group-dialog">
+    <div class="pad-bottom-md" fxLayout="row" fxLayoutAlign="space-between center">
+        <span class="md-card-title">Create New Group</span>
+        <button mat-icon-button (click)="cancel()">
+            <mat-icon color="primary">close</mat-icon>
+        </button>
+    </div>
+    <div fxLayout="column" fxLayoutAlign="space-between start" class="pad-bottom-md">
+        <div class="pad-bottom-md fill-available-width">
+            <mat-input-container floatPlaceholder="always" fxFlex>
+                <input #createNewGroupInput matInput floatPlaceholder="always" placeholder="Display Name">
+            </mat-input-container>
+        </div>
+        <mat-checkbox [(ngModel)]="keepDialogOpen">
+            Keep this dialog open after creating group
+        </mat-checkbox>
+    </div>
+    <div fxLayout="row">
+        <span fxFlex></span>
+        <button (click)="cancel()" color="fds-regular" mat-raised-button
+                i18n="Cancel creation of new group|A button for cancelling the creation of a new group in the registry.@@nf-admin-workflow-cancel-create-new-group-button">
+            Cancel
+        </button>
+        <button [disabled]="createNewGroupInput.value.length === 0" class="push-left-sm" (click)="createNewGroup(createNewGroupInput)" color="fds-primary" mat-raised-button
+                i18n="Create new group button|A button for creating a new group in the registry.@@nf-admin-workflow-create-new-group-button">
+            Create
+        </button>
+    </div>
+</div>

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/create-new-group/nf-registry-create-new-group.js
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/create-new-group/nf-registry-create-new-group.js b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/create-new-group/nf-registry-create-new-group.js
new file mode 100644
index 0000000..bf8075e
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/create-new-group/nf-registry-create-new-group.js
@@ -0,0 +1,108 @@
+/*
+ * 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.
+ */
+
+var ngCore = require('@angular/core');
+var NfRegistryService = require('nifi-registry/services/nf-registry.service.js');
+var NfRegistryApi = require('nifi-registry/services/nf-registry.api.js');
+var ngMaterial = require('@angular/material');
+var fdsSnackBarsModule = require('@flow-design-system/snackbars');
+
+/**
+ * NfRegistryCreateNewGroup constructor.
+ *
+ * @param nfRegistryApi         The api service.
+ * @param fdsSnackBarService    The FDS snack bar service module.
+ * @param nfRegistryService     The nf-registry.service module.
+ * @param matDialogRef          The angular material dialog ref.
+ * @constructor
+ */
+function NfRegistryCreateNewGroup(nfRegistryApi, fdsSnackBarService, nfRegistryService, matDialogRef) {
+    // Services
+    this.snackBarService = fdsSnackBarService;
+    this.nfRegistryService = nfRegistryService;
+    this.nfRegistryApi = nfRegistryApi;
+    this.dialogRef = matDialogRef;
+    // local state
+    this.keepDialogOpen = false;
+};
+
+NfRegistryCreateNewGroup.prototype = {
+    constructor: NfRegistryCreateNewGroup,
+
+    /**
+     * Create a new group.
+     *
+     * @param createNewGroupInput     The createNewGroupInput element.
+     */
+    createNewGroup: function (createNewGroupInput) {
+        var self = this;
+        // create new group with any selected users added to the new group
+        this.nfRegistryApi.createNewGroup(null, createNewGroupInput.value, this.nfRegistryService.getSelectedUsers()).subscribe(function (group) {
+            if (!group.error) {
+                self.nfRegistryService.groups.push(group);
+                self.nfRegistryService.filterUsersAndGroups();
+                self.nfRegistryService.allUsersAndGroupsSelected = false;
+                if (self.keepDialogOpen !== true) {
+                    self.dialogRef.close();
+                }
+                self.snackBarService.openCoaster({
+                    title: 'Success',
+                    message: 'Group has been added.',
+                    verticalPosition: 'bottom',
+                    horizontalPosition: 'right',
+                    icon: 'fa fa-check-circle-o',
+                    color: '#1EB475',
+                    duration: 3000
+                });
+            } else {
+                self.dialogRef.close();
+            }
+        });
+    },
+
+    /**
+     * Cancel creation of a new bucket and close dialog.
+     */
+    cancel: function () {
+        this.dialogRef.close();
+    },
+
+    /**
+     * Focus the new group input.
+     */
+    ngAfterViewChecked: function () {
+        this.createNewGroupInput.nativeElement.focus();
+    }
+};
+
+NfRegistryCreateNewGroup.annotations = [
+    new ngCore.Component({
+        template: require('./nf-registry-create-new-group.html!text'),
+        queries: {
+            createNewGroupInput: new ngCore.ViewChild('createNewGroupInput')
+        }
+    })
+];
+
+NfRegistryCreateNewGroup.parameters = [
+    NfRegistryApi,
+    fdsSnackBarsModule.FdsSnackBarService,
+    NfRegistryService,
+    ngMaterial.MatDialogRef
+];
+
+module.exports = NfRegistryCreateNewGroup;

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/create-new-group/nf-registry-create-new-group.spec.js
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/create-new-group/nf-registry-create-new-group.spec.js b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/create-new-group/nf-registry-create-new-group.spec.js
new file mode 100644
index 0000000..631e415
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/dialogs/create-new-group/nf-registry-create-new-group.spec.js
@@ -0,0 +1,85 @@
+/*
+ * 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.
+ */
+
+var NfRegistryApi = require('nifi-registry/services/nf-registry.api.js');
+var NfRegistryService = require('nifi-registry/services/nf-registry.service.js');
+var NfRegistryCreateNewGroup = require('nifi-registry/components/administration/users/dialogs/create-new-group/nf-registry-create-new-group.js');
+var rxjs = require('rxjs/Rx');
+
+describe('NfRegistryCreateNewGroup Component isolated unit tests', function () {
+    var comp;
+    var nfRegistryService;
+    var nfRegistryApi;
+
+    beforeEach(function () {
+        nfRegistryService = new NfRegistryService();
+        nfRegistryApi = new NfRegistryApi();
+        comp = new NfRegistryCreateNewGroup(nfRegistryApi, {
+                openCoaster: function () {
+                }
+            },
+            nfRegistryService,
+            {
+                close: function () {
+                }
+            });
+
+        // Spy
+        spyOn(nfRegistryApi, 'createNewGroup').and.callFake(function () {
+        }).and.returnValue(rxjs.Observable.of([{
+            'identifier': '2e04b4fb-9513-47bb-aa74-1ae34616bfdc',
+            'identity': 'New Group #1'
+        }]));
+        spyOn(nfRegistryService, 'filterUsersAndGroups');
+        spyOn(comp.dialogRef, 'close');
+    });
+
+    it('should make a call to the api to create a new group and close the dialog.', function () {
+        // the function to test
+        comp.createNewGroup({value: 'New Group #1'});
+
+        //assertions
+        expect(comp).toBeDefined();
+        expect(nfRegistryService.groups.length).toBe(1);
+        expect(nfRegistryService.allUsersAndGroupsSelected).toBe(false);
+        expect(nfRegistryService.filterUsersAndGroups).toHaveBeenCalled();
+        expect(comp.dialogRef.close).toHaveBeenCalled();
+    });
+
+    it('should make a call to the api to create a new group and keep the dialog open.', function () {
+        // setup the component
+        comp.keepDialogOpen = true;
+
+        // the function to test
+        comp.createNewGroup({value: 'New Group #1'});
+
+        //assertions
+        expect(comp).toBeDefined();
+        expect(nfRegistryService.groups.length).toBe(1);
+        expect(nfRegistryService.allUsersAndGroupsSelected).toBe(false);
+        expect(nfRegistryService.filterUsersAndGroups).toHaveBeenCalled();
+        expect(comp.dialogRef.close.calls.count()).toEqual(0);
+    });
+
+    it('should cancel the creation of a new group', function () {
+        // the function to test
+        comp.cancel();
+
+        //assertions
+        expect(comp.dialogRef.close).toHaveBeenCalled();
+    });
+});
\ No newline at end of file