+
No results match this query.
+
+
There are no buckets to display.
+
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/explorer/grid-list/registry/nf-registry-grid-list-viewer.spec.js b/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/explorer/grid-list/registry/nf-registry-grid-list-viewer.spec.js
index 117f635ae9..381327ad3c 100644
--- a/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/explorer/grid-list/registry/nf-registry-grid-list-viewer.spec.js
+++ b/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/explorer/grid-list/registry/nf-registry-grid-list-viewer.spec.js
@@ -60,7 +60,8 @@ describe('NfRegistryGridListViewer Component', function () {
spyOn(nfRegistryApi, 'getBuckets').and.callFake(function () {
}).and.returnValue(of([{
identifier: '2f7f9e54-dc09-4ceb-aa58-9fe581319cdc',
- name: 'Bucket #1'
+ name: 'Bucket #1',
+ permissions: {'canDelete': true, 'canRead': true, 'canWrite': true}
}]));
spyOn(nfRegistryService, 'filterDroplets');
@@ -84,7 +85,8 @@ describe('NfRegistryGridListViewer Component', function () {
'rel': 'self'
},
'href': 'flows/2e04b4fb-9513-47bb-aa74-1ae34616bfdc'
- }
+ },
+ 'permissions': {'canDelete': true, 'canRead': true, 'canWrite': true}
}]));
// 1st change detection triggers ngOnInit which makes getBuckets and getDroplets calls
fixture.detectChanges();
@@ -125,7 +127,8 @@ describe('NfRegistryGridListViewer Component', function () {
'rel': 'self'
},
'href': 'flows/2e04b4fb-9513-47bb-aa74-1ae34616bfdc'
- }
+ },
+ 'permissions': {'canDelete': true, 'canRead': true, 'canWrite': true}
}]));
// 1st change detection triggers ngOnInit which makes getBuckets and getDroplets calls
fixture.detectChanges();
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/nf-registry.module.js b/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/nf-registry.module.js
index 59c7262f36..2f9a4bd1e4 100644
--- a/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/nf-registry.module.js
+++ b/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/nf-registry.module.js
@@ -52,6 +52,9 @@ import {
NfRegistryUsersAdministrationAuthGuard,
NfRegistryWorkflowsAdministrationAuthGuard
} from 'services/nf-registry.auth-guard.service';
+import NfRegistryImportVersionedFlow from './components/explorer/grid-list/dialogs/import-versioned-flow/nf-registry-import-versioned-flow';
+import NfRegistryImportNewFlow from './components/explorer/grid-list/dialogs/import-new-flow/nf-registry-import-new-flow';
+import NfRegistryExportVersionedFlow from './components/explorer/grid-list/dialogs/export-versioned-flow/nf-registry-export-versioned-flow';
function NfRegistryModule() {
}
@@ -89,7 +92,10 @@ NfRegistryModule.annotations = [
NfRegistryDropletGridListViewer,
NfPageNotFoundComponent,
NfLoginComponent,
- NfUserLoginComponent
+ NfUserLoginComponent,
+ NfRegistryExportVersionedFlow,
+ NfRegistryImportVersionedFlow,
+ NfRegistryImportNewFlow
],
entryComponents: [
NfRegistryAddUser,
@@ -99,7 +105,10 @@ NfRegistryModule.annotations = [
NfRegistryAddUsersToGroup,
NfRegistryAddPolicyToBucket,
NfRegistryEditBucketPolicy,
- NfUserLoginComponent
+ NfUserLoginComponent,
+ NfRegistryExportVersionedFlow,
+ NfRegistryImportVersionedFlow,
+ NfRegistryImportNewFlow
],
providers: [
NfRegistryService,
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/services/nf-registry.api.js b/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/services/nf-registry.api.js
index 99cacf8e38..6c48004d44 100644
--- a/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/services/nf-registry.api.js
+++ b/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/services/nf-registry.api.js
@@ -19,7 +19,7 @@ import NfStorage from 'services/nf-storage.service';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { FdsDialogService } from '@nifi-fds/core';
import { of } from 'rxjs';
-import { map, catchError } from 'rxjs/operators';
+import { map, catchError, take, switchMap } from 'rxjs/operators';
var MILLIS_PER_SECOND = 1000;
var headers = new Headers({'Content-Type': 'application/json'});
@@ -75,6 +75,138 @@ NfRegistryApi.prototype = {
);
},
+ /**
+ * Retrieves the specified versioned flow snapshot for an existing droplet the registry has stored.
+ *
+ * @param {string} dropletUri The uri of the droplet to request.
+ * @param {number} versionNumber The version of the flow to request.
+ * @returns {*}
+ */
+ exportDropletVersionedSnapshot: function (dropletUri, versionNumber) {
+ var self = this;
+ var url = '../nifi-registry-api/' + dropletUri + '/versions/' + versionNumber + '/export';
+ var options = {
+ headers: headers,
+ observe: 'response',
+ responseType: 'text'
+ };
+
+ return self.http.get(url, options).pipe(
+ map(function (response) {
+ // export the VersionedFlowSnapshot by creating a hidden anchor element
+ var stringSnapshot = encodeURIComponent(response.body);
+ var filename = response.headers.get('Filename');
+
+ var anchorElement = document.createElement('a');
+ anchorElement.href = 'data:application/json;charset=utf-8,' + stringSnapshot;
+ anchorElement.download = filename;
+ anchorElement.style = 'display: none;';
+
+ document.body.appendChild(anchorElement);
+ anchorElement.click();
+ document.body.removeChild(anchorElement);
+
+ return response;
+ }),
+ catchError(function (error) {
+ self.dialogService.openConfirm({
+ title: 'Error',
+ message: error.error,
+ acceptButton: 'Ok',
+ acceptButtonColor: 'fds-warn'
+ });
+ return of(error);
+ })
+ );
+ },
+
+ /**
+ * Uploads a new versioned flow snapshot to the existing droplet the registry has stored.
+ *
+ * @param {string} dropletUri The uri of the droplet to request.
+ * @param file The file to be uploaded.
+ * @param {string} comments The optional comments.
+ * @returns {*}
+ */
+ uploadVersionedFlowSnapshot: function (dropletUri, file, comments) {
+ var self = this;
+ var url = '../nifi-registry-api/' + dropletUri + '/versions/import';
+ var versionHeaders = new HttpHeaders()
+ .set('Content-Type', 'application/json')
+ .set('Comments', comments);
+
+ return self.http.post(url, file, { 'headers': versionHeaders }).pipe(
+ map(function (response) {
+ return response;
+ }),
+ catchError(function (error) {
+ self.dialogService.openConfirm({
+ title: 'Error',
+ message: error.error,
+ acceptButton: 'Ok',
+ acceptButtonColor: 'fds-warn'
+ });
+ return of(error);
+ })
+ );
+ },
+
+ /**
+ * Uploads a new flow to the existing droplet the registry has stored.
+ *
+ * @param {string} bucketUri The uri of the droplet to request.
+ * @param file The file to be uploaded.
+ * @param {string} name The flow name.
+ * @param {string} description The optional description.
+ * @returns {*}
+ */
+ uploadFlow: function (bucketUri, file, name, description) {
+ var self = this;
+
+ var url = '../nifi-registry-api/' + bucketUri + '/flows';
+ var flow = { 'name': name, 'description': description };
+
+ // first, create Flow version 0
+ return self.http.post(url, flow, headers).pipe(
+ take(1),
+ switchMap(function (response) {
+ var flowUri = response.link.href;
+ var importVersionUrl = '../nifi-registry-api/' + flowUri + '/versions/import';
+
+ // then, import file as Flow version 1
+ return self.http.post(importVersionUrl, file, headers).pipe(
+ map(function (snapshot) {
+ return snapshot;
+ }),
+ catchError(function (error) {
+ // delete Flow version 0
+ var deleteUri = flowUri + '?versions=0';
+ self.deleteDroplet(deleteUri).subscribe(function (response) {
+ return response;
+ });
+
+ self.dialogService.openConfirm({
+ title: 'Error',
+ message: error.error,
+ acceptButton: 'Ok',
+ acceptButtonColor: 'fds-warn'
+ });
+ return of(error);
+ })
+ );
+ }),
+ catchError(function (error) {
+ self.dialogService.openConfirm({
+ title: 'Error',
+ message: error.error,
+ acceptButton: 'Ok',
+ acceptButtonColor: 'fds-warn'
+ });
+ return of(error);
+ })
+ );
+ },
+
/**
* Retrieves the given droplet with or without snapshot metadata.
*
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/services/nf-registry.api.spec.js b/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/services/nf-registry.api.spec.js
index bd942c2b74..8b1d3dc818 100644
--- a/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/services/nf-registry.api.spec.js
+++ b/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/services/nf-registry.api.spec.js
@@ -1400,4 +1400,166 @@ describe('NfRegistry API w/ Angular testing utils', function () {
// Finally, assert that there are no outstanding requests.
httpMock.verify();
}));
+
+ it('should GET to export versioned snapshot.', inject([HttpTestingController], function (httpMock) {
+ var url = 'testUrl';
+ var versionNumber = 1;
+ var reqUrl = '../nifi-registry-api/' + url + '/versions/' + versionNumber + '/export';
+
+ var response = '{'
+ + 'body: {'
+ + 'flowContents: {'
+ + 'componentType: \'PROCESS_GROUP\','
+ + 'connections: [],'
+ + 'controllerServices: [],'
+ + 'funnels: [],'
+ + 'identifier: \'123\','
+ + 'inputPorts: [],'
+ + 'labels: [],'
+ + 'name: \'Test snapshot\','
+ + 'outputPorts: [],'
+ + 'processGroups: []'
+ + '},'
+ + 'snapshotMetadata: {'
+ + 'author: \'anonymous\','
+ + 'bucketIdentifier: \'123\','
+ + 'comments: \'Test comments\','
+ + 'flowIdentifier: \'555\','
+ + 'link: {'
+ + 'href: \'buckets/123/flows/555/versions/2\','
+ + 'params: {}'
+ + '},'
+ + 'version: 2'
+ + '}'
+ + '},'
+ + 'headers: {'
+ + 'headers: ['
+ + '{\'filename\': [\'Test-flow-version-1\']}'
+ + '],'
+ + 'normalizedNames: {\'filename\': \'filename\'}'
+ + '},'
+ + 'ok: true,'
+ + 'status: 200,'
+ + 'statusText: \'OK\','
+ + 'type: 4,'
+ + 'url: \'testUrl\''
+ + '}';
+
+ var stringResponse = encodeURIComponent(response);
+
+ var anchor = document.createElement('a');
+
+ anchor.href = 'data:application/json;charset=utf-8,' + stringResponse;
+ anchor.download = 'Test-flow-version-3.json';
+ anchor.style = 'display: none;';
+
+ spyOn(document.body, 'appendChild');
+ spyOn(document.body, 'removeChild');
+
+ // api call
+ nfRegistryApi.exportDropletVersionedSnapshot(url, versionNumber).subscribe(function (res) {
+ expect(res.body).toEqual(response);
+ expect(res.status).toEqual(200);
+ });
+
+ // the request it made
+ req = httpMock.expectOne(reqUrl);
+ expect(req.request.method).toEqual('GET');
+
+ // Next, fulfill the request by transmitting a response.
+ req.flush(response);
+
+ // Finally, assert that there are no outstanding requests.
+ httpMock.verify();
+
+ expect(document.body.appendChild).toHaveBeenCalled();
+ expect(document.body.removeChild).toHaveBeenCalled();
+ }));
+
+ it('should POST to upload versioned flow snapshot.', inject([HttpTestingController], function (httpMock) {
+ var url = 'testUrl';
+ var reqUrl = '../nifi-registry-api/' + url + '/versions/import';
+
+ var response = {
+ flowContents: {
+ componentType: 'PROCESS_GROUP',
+ connections: [],
+ controllerServices: [],
+ funnels: [],
+ name: 'Test name',
+ identifier: '123'
+ },
+ snapshotMetadata: {
+ author: 'anonymous',
+ comments: 'This is snapshot #5',
+ timestamp: 1619806926583,
+ version: 3
+ }
+ };
+
+ var testFile = new File([], 'filename');
+
+ // api call
+ nfRegistryApi.uploadVersionedFlowSnapshot(url, testFile, '').subscribe(function (res) {
+ expect(res).toEqual(response);
+ expect(res.flowContents.name).toEqual('Test name');
+ expect(res.snapshotMetadata.comments).toEqual('This is snapshot #5');
+ });
+
+ // the request it made
+ req = httpMock.expectOne(reqUrl);
+ expect(req.request.method).toEqual('POST');
+
+ // Next, fulfill the request by transmitting a response.
+ req.flush(response);
+
+ // Finally, assert that there are no outstanding requests.
+ httpMock.verify();
+ }));
+
+ it('should POST to upload new flow snapshot.', inject([HttpTestingController], function (httpMock) {
+ var bucketUri = 'buckets/123';
+ var flowUri = 'buckets/123/flows/456';
+ var createFlowReqUrl = '../nifi-registry-api/' + bucketUri + '/flows';
+ var importFlowReqUrl = '../nifi-registry-api/' + flowUri + '/versions/import';
+ var headers = new Headers({'Content-Type': 'application/json'});
+
+ var response = {
+ bucketIdentifier: '123',
+ bucketName: 'Bucket 1',
+ createdTimestamp: 1620168949158,
+ description: 'Test description',
+ identifier: '456',
+ link: {
+ href: 'buckets/123/flows/456',
+ params: {}
+ },
+ modifiedTimestamp: 1620175586179,
+ name: 'Test Flow name',
+ permissions: {canDelete: true, canRead: true, canWrite: true},
+ type: 'Flow',
+ versionCount: 0
+ };
+
+ var testFile = new File([], 'filename.json');
+
+ // api call
+ nfRegistryApi.uploadFlow(bucketUri, testFile, headers).subscribe(function (res) {
+ expect(res).toEqual(response);
+ });
+
+ // the request it made
+ req = httpMock.expectOne(createFlowReqUrl);
+ expect(req.request.method).toEqual('POST');
+
+ // Next, fulfill the request by transmitting a response.
+ req.flush(response);
+
+ // the inner request it made
+ req = httpMock.expectOne(importFlowReqUrl);
+ expect(req.request.method).toEqual('POST');
+
+ // Finally, assert that there are no outstanding requests.
+ httpMock.verify();
+ }));
});
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/services/nf-registry.service.js b/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/services/nf-registry.service.js
index 8e1241b795..6e2c875ec6 100644
--- a/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/services/nf-registry.service.js
+++ b/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/services/nf-registry.service.js
@@ -17,9 +17,13 @@
import { TdDataTableService } from '@covalent/core/data-table';
import { Router } from '@angular/router';
+import { MatDialog } from '@angular/material';
import { FdsDialogService, FdsSnackBarService } from '@nifi-fds/core';
import NfRegistryApi from 'services/nf-registry.api.js';
import NfStorage from 'services/nf-storage.service.js';
+import NfRegistryExportVersionedFlow from '../components/explorer/grid-list/dialogs/export-versioned-flow/nf-registry-export-versioned-flow';
+import NfRegistryImportVersionedFlow from '../components/explorer/grid-list/dialogs/import-versioned-flow/nf-registry-import-versioned-flow';
+import NfRegistryImportNewFlow from '../components/explorer/grid-list/dialogs/import-new-flow/nf-registry-import-new-flow';
/**
* NfRegistryService constructor.
@@ -30,9 +34,10 @@ import NfStorage from 'services/nf-storage.service.js';
* @param router The angular router module.
* @param fdsDialogService The FDS dialog service.
* @param fdsSnackBarService The FDS snack bar service module.
+ * @param matDialog The angular material dialog module.
* @constructor
*/
-function NfRegistryService(nfRegistryApi, nfStorage, tdDataTableService, router, fdsDialogService, fdsSnackBarService) {
+function NfRegistryService(nfRegistryApi, nfStorage, tdDataTableService, router, fdsDialogService, fdsSnackBarService, matDialog) {
var self = this;
this.registry = {
name: 'NiFi Registry',
@@ -52,6 +57,7 @@ function NfRegistryService(nfRegistryApi, nfStorage, tdDataTableService, router,
this.dialogService = fdsDialogService;
this.snackBarService = fdsSnackBarService;
this.dataTableService = tdDataTableService;
+ this.matDialog = matDialog;
// data table column definitions
this.userColumns = [
@@ -133,9 +139,28 @@ function NfRegistryService(nfRegistryApi, nfStorage, tdDataTableService, router,
];
this.dropletActions = [
{
- name: 'Delete',
+ name: 'Import new version',
+ icon: 'fa fa-upload',
+ tooltip: 'Import new flow version',
+ disabled: function (droplet) {
+ return !droplet.permissions.canWrite;
+ }
+ },
+ {
+ name: 'Export version',
+ icon: 'fa fa-download',
+ tooltip: 'Export flow version',
+ disabled: function (droplet) {
+ return !droplet.permissions.canRead;
+ }
+ },
+ {
+ name: 'Delete flow',
icon: 'fa fa-trash',
- tooltip: 'Delete'
+ tooltip: 'Delete',
+ disabled: function (droplet) {
+ return !droplet.permissions.canDelete;
+ }
}
];
this.disableMultiDeleteAction = false;
@@ -393,6 +418,105 @@ NfRegistryService.prototype = {
return label;
},
+ /**
+ * Delete the latest flow snapshot.
+ *
+ * @param droplet The droplet object.
+ */
+ deleteDroplet: function (droplet) {
+ var self = this;
+ this.dialogService.openConfirm({
+ title: 'Delete Flow',
+ message: 'All versions of this ' + droplet.type.toLowerCase() + ' will be deleted.',
+ cancelButton: 'Cancel',
+ acceptButton: 'Delete',
+ acceptButtonColor: 'fds-warn'
+ }).afterClosed().subscribe(
+ function (accept) {
+ if (accept) {
+ var deleteUrl = droplet.link.href;
+ if (droplet.type === 'Flow') {
+ deleteUrl = deleteUrl + '?version=' + droplet.revision.version;
+ }
+ self.api.deleteDroplet(deleteUrl).subscribe(function (response) {
+ if (!response.status || response.status === 200) {
+ self.droplets = self.droplets.filter(function (d) {
+ return (d.identifier !== droplet.identifier);
+ });
+ self.snackBarService.openCoaster({
+ title: 'Success',
+ message: 'All versions of this ' + droplet.type.toLowerCase() + ' have been deleted.',
+ verticalPosition: 'bottom',
+ horizontalPosition: 'right',
+ icon: 'fa fa-check-circle-o',
+ color: '#1EB475',
+ duration: 3000
+ });
+ self.droplet = {};
+ self.filterDroplets();
+ }
+ });
+ }
+ }
+ );
+ },
+
+ /**
+ * Opens the export version dialog.
+ *
+ * @param droplet The droplet object.
+ */
+ openExportVersionedFlowDialog: function (droplet) {
+ this.matDialog.open(NfRegistryExportVersionedFlow, {
+ disableClose: true,
+ width: '400px',
+ data: {
+ droplet: droplet
+ }
+ });
+ },
+
+ /**
+ * Opens the import new flow dialog.
+ *
+ * @param buckets The buckets object.
+ * @param activeBucket The active bucket object.
+ */
+ openImportNewFlowDialog: function (buckets, activeBucket) {
+ var self = this;
+ this.matDialog.open(NfRegistryImportNewFlow, {
+ disableClose: true,
+ width: '550px',
+ data: {
+ buckets: buckets,
+ activeBucket: activeBucket
+ }
+ }).afterClosed().subscribe(function (flowUri) {
+ if (flowUri != null) {
+ self.router.navigateByUrl('explorer/grid-list/' + flowUri);
+ }
+ });
+ },
+
+ /**
+ * Opens the import new version dialog.
+ *
+ * @param droplet The droplet object.
+ */
+ openImportVersionedFlowDialog: function (droplet) {
+ var self = this;
+
+ this.matDialog.open(NfRegistryImportVersionedFlow, {
+ disableClose: true,
+ width: '550px',
+ data: {
+ droplet: droplet
+ }
+ }).afterClosed().subscribe(function () {
+ self.getDropletSnapshotMetadata(droplet);
+ });
+ },
+
/**
* Execute the given droplet action.
*
@@ -400,42 +524,20 @@ NfRegistryService.prototype = {
* @param droplet The droplet object the `action` will act upon.
*/
executeDropletAction: function (action, droplet) {
- var self = this;
- if (action.name.toLowerCase() === 'delete') {
- this.dialogService.openConfirm({
- title: 'Delete ' + droplet.type.toLowerCase(),
- message: 'All versions of this ' + droplet.type.toLowerCase() + ' will be deleted.',
- cancelButton: 'Cancel',
- acceptButton: 'Delete',
- acceptButtonColor: 'fds-warn'
- }).afterClosed().subscribe(
- function (accept) {
- if (accept) {
- var deleteUrl = droplet.link.href;
- if (droplet.type === 'Flow') {
- deleteUrl = deleteUrl + '?version=' + droplet.revision.version;
- }
- self.api.deleteDroplet(deleteUrl).subscribe(function (response) {
- if (!response.status || response.status === 200) {
- self.droplets = self.droplets.filter(function (d) {
- return (d.identifier !== droplet.identifier);
- });
- self.snackBarService.openCoaster({
- title: 'Success',
- message: 'All versions of this ' + droplet.type.toLowerCase() + ' have been deleted.',
- verticalPosition: 'bottom',
- horizontalPosition: 'right',
- icon: 'fa fa-check-circle-o',
- color: '#1EB475',
- duration: 3000
- });
- self.droplet = {};
- self.filterDroplets();
- }
- });
- }
- }
- );
+ switch (action.name.toLowerCase()) {
+ case 'import new version':
+ // Opens the import versioned flow dialog
+ this.openImportVersionedFlowDialog(droplet);
+ break;
+ case 'export version':
+ // Opens the export flow version dialog
+ this.openExportVersionedFlowDialog(droplet);
+ break;
+ case 'delete flow':
+ // Deletes the entire data flow
+ this.deleteDroplet(droplet);
+ break;
+ default: // do nothing
}
},
@@ -633,6 +735,22 @@ NfRegistryService.prototype = {
this.getAutoCompleteBuckets();
},
+ /**
+ * Gets the buckets the user has permissions to write.
+ *
+ * @param buckets The buckets object.
+ */
+ filterWritableBuckets: function (buckets) {
+ var writableBuckets = [];
+
+ buckets.forEach(function (b) {
+ if (b.permissions.canWrite) {
+ writableBuckets.push(b);
+ }
+ });
+ return writableBuckets;
+ },
+
/**
* Generates the `autoCompleteBuckets` options for the bucket filter.
*/
@@ -1213,7 +1331,8 @@ NfRegistryService.parameters = [
TdDataTableService,
Router,
FdsDialogService,
- FdsSnackBarService
+ FdsSnackBarService,
+ MatDialog
];
export default NfRegistryService;
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/services/nf-registry.service.spec.js b/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/services/nf-registry.service.spec.js
index 455963d3c0..a21007da33 100644
--- a/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/services/nf-registry.service.spec.js
+++ b/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/services/nf-registry.service.spec.js
@@ -22,6 +22,12 @@ import NfRegistryApi from 'services/nf-registry.api';
import NfRegistryService from 'services/nf-registry.service';
import { Router } from '@angular/router';
import { FdsDialogService } from '@nifi-fds/core';
+import NfRegistryExportVersionedFlow
+ from '../components/explorer/grid-list/dialogs/export-versioned-flow/nf-registry-export-versioned-flow';
+import NfRegistryImportVersionedFlow
+ from '../components/explorer/grid-list/dialogs/import-versioned-flow/nf-registry-import-versioned-flow';
+import NfRegistryImportNewFlow
+ from '../components/explorer/grid-list/dialogs/import-new-flow/nf-registry-import-new-flow';
describe('NfRegistry Service isolated unit tests', function () {
@@ -702,7 +708,12 @@ describe('NfRegistry Service w/ Angular testing utils', function () {
it('should execute the `delete` droplet action.', function () {
//Setup the nfRegistryService state for this test
- nfRegistryService.droplets = [{identifier: '2e04b4fb-9513-47bb-aa74-1ae34616bfdc'}];
+ nfRegistryService.droplets = [
+ {
+ identifier: '2e04b4fb-9513-47bb-aa74-1ae34616bfdc',
+ permissions: {canDelete: true, canRead: true, canWrite: true}
+ }
+ ];
//Spy
spyOn(nfRegistryService.dialogService, 'openConfirm').and.returnValue({
@@ -716,17 +727,18 @@ describe('NfRegistry Service w/ Angular testing utils', function () {
});
// The function to test
- nfRegistryService.executeDropletAction({name: 'delete'}, {
+ nfRegistryService.executeDropletAction({name: 'delete flow'}, {
identifier: '2e04b4fb-9513-47bb-aa74-1ae34616bfdc',
type: 'testTYPE',
- link: {href: 'testhref'}
+ link: {href: 'testhref'},
+ permissions: {canDelete: true, canRead: true, canWrite: true}
});
//assertions
expect(nfRegistryService.droplets.length).toBe(0);
expect(nfRegistryService.filterDroplets).toHaveBeenCalled();
const openConfirmCall = nfRegistryService.dialogService.openConfirm.calls.first();
- expect(openConfirmCall.args[0].title).toBe('Delete testtype');
+ expect(openConfirmCall.args[0].title).toBe('Delete Flow');
const deleteDropletCall = nfRegistryApi.deleteDroplet.calls.first();
expect(deleteDropletCall.args[0]).toBe('testhref');
});
@@ -795,10 +807,10 @@ describe('NfRegistry Service w/ Angular testing utils', function () {
}).and.returnValue(of({identifier: '2e04b4fb-9513-47bb-aa74-1ae34616bfdc', link: null}));
// object to be updated by the test
- const bucket = {identifier: '999', revision: { version: 0}};
+ const bucket = {identifier: '999', revision: {version: 0}};
// set up the bucket to be deleted
- nfRegistryService.buckets = [bucket, {identifier: 1, revision: { version: 0}}];
+ nfRegistryService.buckets = [bucket, {identifier: 1, revision: {version: 0}}];
// The function to test
nfRegistryService.executeBucketAction({name: 'delete'}, bucket);
@@ -850,7 +862,7 @@ describe('NfRegistry Service w/ Angular testing utils', function () {
const user = {identifier: '999', revision: {version: 0}};
// set up the user to be deleted
- nfRegistryService.users = [user, {identifier: 1, revision: { version: 0}}];
+ nfRegistryService.users = [user, {identifier: 1, revision: {version: 0}}];
// The function to test
nfRegistryService.executeUserAction({name: 'delete'}, user);
@@ -1023,10 +1035,10 @@ describe('NfRegistry Service w/ Angular testing utils', function () {
}).and.returnValue(of({identifier: 999, link: null}));
// object to be updated by the test
- const bucket = {identifier: 999, checked: true, revision: { version: 0}};
+ const bucket = {identifier: 999, checked: true, revision: {version: 0}};
// set up the bucket to be deleted
- nfRegistryService.buckets = [bucket, {identifier: 1, revision: { version: 0}}];
+ nfRegistryService.buckets = [bucket, {identifier: 1, revision: {version: 0}}];
nfRegistryService.filteredBuckets = nfRegistryService.buckets;
// The function to test
@@ -1065,13 +1077,13 @@ describe('NfRegistry Service w/ Angular testing utils', function () {
}).and.returnValue(of({identifier: 99, link: null}));
// object to be updated by the test
- const group = {identifier: 999, checked: true, revision: { version: 0}};
- const user = {identifier: 999, checked: true, revision: { version: 0}};
+ const group = {identifier: 999, checked: true, revision: {version: 0}};
+ const user = {identifier: 999, checked: true, revision: {version: 0}};
// set up the group to be deleted
- nfRegistryService.groups = [group, {identifier: 1, revision: { version: 0}}];
+ nfRegistryService.groups = [group, {identifier: 1, revision: {version: 0}}];
nfRegistryService.filteredUserGroups = nfRegistryService.groups;
- nfRegistryService.users = [user, {identifier: 12, revision: { version: 0}}];
+ nfRegistryService.users = [user, {identifier: 12, revision: {version: 0}}];
nfRegistryService.filteredUsers = nfRegistryService.users;
// The function to test
@@ -1091,4 +1103,112 @@ describe('NfRegistry Service w/ Angular testing utils', function () {
expect(nfRegistryService.users.length).toBe(1);
expect(nfRegistryService.users[0].identifier).toBe(12);
});
+
+ it('should open the Export Version dialog.', function () {
+ //Setup the nfRegistryService state for this test
+ var droplet = {
+ identifier: '2e04b4fb-9513-47bb-aa74-1ae34616bfdc',
+ type: 'testTYPE',
+ link: {href: 'testhref'},
+ permissions: {canDelete: true, canRead: true, canWrite: true}
+ };
+
+ //Spy
+ spyOn(nfRegistryService.matDialog, 'open');
+
+ nfRegistryService.executeDropletAction({name: 'export version'}, droplet);
+ expect(nfRegistryService.matDialog.open).toHaveBeenCalledWith(NfRegistryExportVersionedFlow, {
+ disableClose: true,
+ width: '400px',
+ data: {
+ droplet: droplet
+ }
+ });
+ });
+
+ it('should open the Import Versioned Flow dialog.', function () {
+ //Setup the nfRegistryService state for this test
+ var droplet = {
+ identifier: '2e04b4fb-9513-47bb-aa74-1ae34616bfdc',
+ type: 'testTYPE',
+ link: {href: 'testhref'},
+ permissions: {canDelete: true, canRead: true, canWrite: true}
+ };
+
+ //Spy
+ spyOn(nfRegistryService.matDialog, 'open').and.returnValue({
+ afterClosed: function () {
+ return of(true);
+ }
+ });
+
+ nfRegistryService.executeDropletAction({name: 'import new version'}, droplet);
+ expect(nfRegistryService.matDialog.open).toHaveBeenCalledWith(NfRegistryImportVersionedFlow, {
+ disableClose: true,
+ width: '550px',
+ data: {
+ droplet: droplet
+ }
+ });
+ });
+
+ it('should open the Import New Flow dialog.', function () {
+ //Setup the nfRegistryService state for this test
+ nfRegistryService.buckets = [{
+ identifier: '2e04b4fb-9513-47bb-aa74-1ae34616bfdc',
+ name: 'Bucket #1',
+ checked: true,
+ permissions: {canDelete: true, canRead: true, canWrite: false}
+ }, {
+ identifier: '5c04b4fb-9513-47bb-aa74-1ae34616bfdc',
+ name: 'Bucket #2',
+ checked: true,
+ permissions: {canDelete: true, canRead: true, canWrite: true}
+ }];
+
+ nfRegistryService.bucket = {
+ identifier: '5c04b4fb-9513-47bb-aa74-1ae34616bfdc',
+ name: 'Bucket #2',
+ checked: true,
+ permissions: {canDelete: true, canRead: true, canWrite: true}
+ };
+
+ //Spy
+ spyOn(nfRegistryService.matDialog, 'open').and.returnValue({
+ afterClosed: function () {
+ return of(true);
+ }
+ });
+
+ nfRegistryService.openImportNewFlowDialog(nfRegistryService.buckets, nfRegistryService.bucket);
+ expect(nfRegistryService.matDialog.open).toHaveBeenCalledWith(NfRegistryImportNewFlow, {
+ disableClose: true,
+ width: '550px',
+ data: {
+ buckets: nfRegistryService.buckets,
+ activeBucket: nfRegistryService.bucket
+ }
+ });
+ });
+
+ it('should filter writable buckets.', function () {
+ nfRegistryService.buckets = [{
+ identifier: '2e04b4fb-9513-47bb-aa74-1ae34616bfdc',
+ name: 'Bucket #1',
+ checked: true,
+ permissions: {canDelete: true, canRead: true, canWrite: false}
+ }, {
+ identifier: '5c04b4fb-9513-47bb-aa74-1ae34616bfdc',
+ name: 'Bucket #2',
+ checked: true,
+ permissions: {canDelete: true, canRead: true, canWrite: true}
+ }];
+
+ // The function to test
+ const writableBuckets = nfRegistryService.filterWritableBuckets(nfRegistryService.buckets);
+
+ // assertions
+ expect(writableBuckets.length).toBe(1);
+ expect(writableBuckets[0].name).toBe('Bucket #2');
+ });
});
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/theming/components/explorer/dialogs/_structureElements.scss b/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/theming/components/explorer/dialogs/_structureElements.scss
new file mode 100644
index 0000000000..6535f6ccf4
--- /dev/null
+++ b/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/theming/components/explorer/dialogs/_structureElements.scss
@@ -0,0 +1,220 @@
+/*
+ * 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.
+ */
+
+$gray: #666;
+$light-gray: #999;
+$dark-gray: #ced3d7;
+$teal-gray: #6b8791;
+
+.label-name {
+ font-weight: 500;
+ color: $gray;
+}
+
+.version-count {
+ font-weight: 500;
+ padding: 9px 18px;
+ margin-left: 5px;
+ border-radius: 17px;
+ background-color: #eee;
+ color: $gray;
+}
+
+#flow-version-name {
+ border: none;
+ height: 34px;
+ outline: 0;
+ font-family: Roboto, sans-serif;
+ font-size: 14px;
+ color: $gray;
+
+ span {
+ overflow: hidden;
+ }
+}
+
+#flow-name {
+ border: none;
+ height: 34px;
+ outline: 0;
+ font-family: Roboto, sans-serif;
+ font-size: 14px;
+ color: $gray;
+
+ span {
+ overflow: hidden;
+ }
+}
+
+#new-data-flow-version-placeholder {
+ border: none;
+ outline: 0;
+ color: $light-gray;
+ font-family: Roboto, sans-serif;
+ font-size: 14px;
+ font-weight: 300;
+}
+
+#select-flow-file-button,
+#select-flow-version-file-button {
+ position: absolute;
+ right: 16px;
+ top: 8px;
+ border: none;
+ background-color: transparent;
+ cursor: pointer;
+
+ i {
+ color: $teal-gray;
+ padding: 9px 5px;
+ outline: none;
+ }
+
+ span {
+ text-transform: uppercase;
+ color: $teal-gray;
+ }
+}
+
+input#upload-flow-file-field,
+input#upload-versioned-flow-file-field {
+ display: none;
+}
+
+#flow-version-comments {
+ padding-bottom: 26px;
+
+ textarea {
+ width: 515px;
+ height: 48px;
+ border: solid 1px #cfd3d7;
+ border-radius: 2px;
+ padding: 9px 16px 9px 12px;
+ resize: none;
+ color: $gray;
+ }
+}
+
+#new-flow-description {
+ textarea {
+ width: 515px;
+ height: 48px;
+ border: solid 1px #cfd3d7;
+ border-radius: 2px;
+ padding: 9px 16px 9px 12px;
+ color: $gray;
+ }
+}
+
+#flow-version-comments,
+#new-flow-description {
+ textarea:focus {
+ outline: 0;
+ border-color: $teal-gray;
+ }
+}
+
+#versioned-flow-file-upload-message-container,
+#new-flow-file-upload-message-container {
+ & span.file-upload-message {
+ font-size: 12px;
+ color: $light-gray;
+ }
+
+ i {
+ font-size: 15px;
+ }
+}
+
+#nifi-registry-export-versioned-flow-dialog .bucket-dropdown-field .mat-select-value {
+ color: $gray;
+}
+
+.bucket-dropdown-field .mat-form-field-appearance-fill {
+ .mat-form-field-infix {
+ border: 0;
+ padding: 0.68em 0;
+
+ .mat-select-value-text {
+ color: $gray;
+ }
+
+ .mat-select-placeholder {
+ font-weight: 300;
+ }
+ }
+
+ .mat-select-arrow-wrapper {
+ transform: none;
+ }
+
+ .mat-form-field-flex {
+ border: 1px solid $dark-gray;
+ background-color: transparent;
+ border-radius: 2px;
+ padding-top: 0;
+ }
+}
+
+.bucket-dropdown-select {
+ .mat-select-panel {
+ color: $gray;
+ }
+}
+
+#new-flow-definition,
+#flow-version-definition {
+ mat-form-field {
+ line-height: 28px;
+ }
+
+ .mat-form-field-infix {
+ border-top: 0;
+ }
+
+ input.mat-input-element {
+ &.file-hover-error {
+ border: solid 1px #ef6162;
+ }
+
+ &.file-hover-valid {
+ border: solid 1px #2ab377;
+ }
+ }
+}
+
+#new-flow-container .mat-form-field-appearance-fill .mat-form-field-infix {
+ color: $light-gray;
+}
+
+input[type=text]#flow-version-definition-input,
+input[type=text]#new-flow-definition-input {
+ padding-right: 125px;
+ width: 373px;
+ text-overflow: ellipsis;
+ cursor: pointer;
+}
+
+body[fds] .fds-primary-dropdown-button-menu.fds-primary-dropdown-button-menu .mat-menu-item:focus:not([disabled]) {
+ color: rgba(0, 0, 0, 0.87);
+ background-color: rgba(0, 0, 0, 0.04);
+}
+
+body[fds] .fds-primary-dropdown-button-menu.fds-primary-dropdown-button-menu .mat-menu-item:hover:not([disabled]) {
+ color: #fff;
+ background-color: #915d69;
+}
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/theming/components/explorer/grid-list/_structureElements.scss b/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/theming/components/explorer/grid-list/_structureElements.scss
index 68e26b592e..9b4b7ac8be 100644
--- a/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/theming/components/explorer/grid-list/_structureElements.scss
+++ b/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/theming/components/explorer/grid-list/_structureElements.scss
@@ -54,3 +54,8 @@ button.nf-registry-change-log-refresh.mat-icon-button {
overflow: auto;
margin-bottom: 0;
}
+
+#import-new-flow-disabled-message {
+ font-size: 12px;
+ color: #5a656d;
+}
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/theming/nf-registry.scss b/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/theming/nf-registry.scss
index 37d9ea4977..740aa7d223 100644
--- a/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/theming/nf-registry.scss
+++ b/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/theming/nf-registry.scss
@@ -33,6 +33,7 @@ $fdsFontPath: './node_modules/roboto-fontface/fonts';
@import 'components/administration/users/structureElements';
@import 'components/administration/workflow/structureElements';
@import 'components/explorer/grid-list/structureElements';
+@import 'components/explorer/dialogs/structureElements';
$primaryColor: $rose1; //$green2
$primaryColorHover: $rose2; //$green3