Add DCAE MOD design tool project
[dcaegen2/platform.git] / mod / designtool / designtool-web / src / main / webapp / js / nf / canvas / nf-flow-version.js
1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  *
17  * Modifications to the original nifi code for the ONAP project are made
18  * available under the Apache License, Version 2.0
19  */
20
21 /* global define, module, require, exports */
22
23 /**
24  * Handles versioning.
25  */
26 (function (root, factory) {
27     if (typeof define === 'function' && define.amd) {
28         define(['jquery',
29                 'nf.ng.Bridge',
30                 'nf.ErrorHandler',
31                 'nf.Dialog',
32                 'nf.Storage',
33                 'nf.Common',
34                 'nf.Client',
35                 'nf.CanvasUtils',
36                 'nf.ProcessGroup',
37                 'nf.ProcessGroupConfiguration',
38                 'nf.Graph',
39                 'nf.Birdseye'],
40             function ($, nfNgBridge, nfErrorHandler, nfDialog, nfStorage, nfCommon, nfClient, nfCanvasUtils, nfProcessGroup, nfProcessGroupConfiguration, nfGraph, nfBirdseye) {
41                 return (nf.FlowVersion = factory($, nfNgBridge, nfErrorHandler, nfDialog, nfStorage, nfCommon, nfClient, nfCanvasUtils, nfProcessGroup, nfProcessGroupConfiguration, nfGraph, nfBirdseye));
42             });
43     } else if (typeof exports === 'object' && typeof module === 'object') {
44         module.exports = (nf.FlowVerison =
45             factory(require('jquery'),
46                 require('nf.ng.Bridge'),
47                 require('nf.ErrorHandler'),
48                 require('nf.Dialog'),
49                 require('nf.Storage'),
50                 require('nf.Common'),
51                 require('nf.Client'),
52                 require('nf.CanvasUtils'),
53                 require('nf.ProcessGroup'),
54                 require('nf.ProcessGroupConfiguration'),
55                 require('nf.Graph'),
56                 require('nf.Birdseye')));
57     } else {
58         nf.FlowVersion = factory(root.$,
59             root.nf.ng.Bridge,
60             root.nf.ErrorHandler,
61             root.nf.Dialog,
62             root.nf.Storage,
63             root.nf.Common,
64             root.nf.Client,
65             root.nf.CanvasUtils,
66             root.nf.ProcessGroup,
67             root.nf.ProcessGroupConfiguration,
68             root.nf.Graph,
69             root.nf.Birdseye);
70     }
71 }(this, function ($, nfNgBridge, nfErrorHandler, nfDialog, nfStorage, nfCommon, nfClient, nfCanvasUtils, nfProcessGroup, nfProcessGroupConfiguration, nfGraph, nfBirdseye) {
72     'use strict';
73
74     var serverTimeOffset = null;
75
76     var gridOptions = {
77         forceFitColumns: true,
78         enableTextSelectionOnCells: true,
79         enableCellNavigation: true,
80         enableColumnReorder: false,
81         autoEdit: false,
82         multiSelect: false,
83         rowHeight: 24
84     };
85
86     /**
87      * Reset the save flow version dialog.
88      */
89     var resetSaveFlowVersionDialog = function () {
90         $('#save-flow-version-registry-combo').combo('destroy').hide();
91         $('#save-flow-version-bucket-combo').combo('destroy').hide();
92
93         $('#save-flow-version-label').text('');
94
95         $('#save-flow-version-registry').text('').hide();
96         $('#save-flow-version-bucket').text('').hide();
97
98         $('#save-flow-version-name').text('').hide();
99         $('#save-flow-version-description').removeClass('unset blank').text('').hide();
100
101         $('#save-flow-version-name-field').val('').hide();
102         $('#save-flow-version-description-field').val('').hide();
103         $('#save-flow-version-change-comments').val('');
104
105         $('#save-flow-version-process-group-id').removeData('versionControlInformation').removeData('revision').text('');
106     };
107
108     /**
109      * Reset the revert local changes dialog.
110      */
111     var resetRevertLocalChangesDialog = function () {
112         $('#revert-local-changes-process-group-id').text('');
113
114         clearLocalChangesGrid($('#revert-local-changes-table'), $('#revert-local-changes-filter'), $('#displayed-revert-local-changes-entries'), $('#total-revert-local-changes-entries'));
115     };
116
117     /**
118      * Reset the show local changes dialog.
119      */
120     var resetShowLocalChangesDialog = function () {
121         clearLocalChangesGrid($('#show-local-changes-table'), $('#show-local-changes-filter'), $('#displayed-show-local-changes-entries'), $('#total-show-local-changes-entries'));
122     };
123
124     /**
125      * Clears the local changes grid.
126      */
127     var clearLocalChangesGrid = function (localChangesTable, filterInput, displayedLabel, totalLabel) {
128         var localChangesGrid = localChangesTable.data('gridInstance');
129         if (nfCommon.isDefinedAndNotNull(localChangesGrid)) {
130             localChangesGrid.setSelectedRows([]);
131             localChangesGrid.resetActiveCell();
132
133             var localChangesData = localChangesGrid.getData();
134             localChangesData.setItems([]);
135             localChangesData.setFilterArgs({
136                 searchString: ''
137             });
138         }
139
140         filterInput.val('');
141
142         displayedLabel.text('0');
143         totalLabel.text('0');
144     };
145
146     /**
147      * Clears the version grid
148      */
149     var clearFlowVersionsGrid = function () {
150         var importFlowVersionGrid = $('#import-flow-version-table').data('gridInstance');
151         if (nfCommon.isDefinedAndNotNull(importFlowVersionGrid)) {
152             importFlowVersionGrid.setSelectedRows([]);
153             importFlowVersionGrid.resetActiveCell();
154
155             var importFlowVersionData = importFlowVersionGrid.getData();
156             importFlowVersionData.setItems([]);
157         }
158     };
159
160     /**
161      * Reset the import flow version dialog.
162      */
163     var resetImportFlowVersionDialog = function () {
164         $('#import-flow-version-dialog').removeData('pt');
165
166         $('#import-flow-version-registry-combo').combo('destroy').hide();
167         $('#import-flow-version-bucket-combo').combo('destroy').hide();
168         $('#import-flow-version-name-combo').combo('destroy').hide();
169
170         $('#import-flow-version-registry').text('').hide();
171         $('#import-flow-version-bucket').text('').hide();
172         $('#import-flow-version-name').text('').hide();
173
174         clearFlowVersionsGrid();
175
176         $('#import-flow-version-process-group-id').removeData('versionControlInformation').removeData('revision').text('');
177
178         $('#import-flow-version-container').hide();
179         $('#import-flow-version-label').text('');
180     };
181
182     /**
183      * Loads the registries into the specified registry combo.
184      *
185      * @param dialog
186      * @param registryCombo
187      * @param bucketCombo
188      * @param flowCombo
189      * @param selectBucket
190      * @param bucketCheck
191      * @returns {deferred}
192      */
193     var loadRegistries = function (dialog, registryCombo, bucketCombo, flowCombo, selectBucket, bucketCheck) {
194         return $.ajax({
195             type: 'GET',
196             url: '../nifi-api/flow/registries',
197             dataType: 'json'
198         }).done(function (registriesResponse) {
199             var registries = [];
200
201             if (nfCommon.isDefinedAndNotNull(registriesResponse.registries) && registriesResponse.registries.length > 0) {
202                 registriesResponse.registries.sort(function (a, b) {
203                     return a.registry.name > b.registry.name;
204                 });
205
206                 $.each(registriesResponse.registries, function (_, registryEntity) {
207                     var registry = registryEntity.registry;
208                     registries.push({
209                         text: registry.name,
210                         value: registry.id,
211                         description: nfCommon.escapeHtml(registry.description)
212                     });
213                 });
214             } else {
215                 registries.push({
216                     text: 'No available registries',
217                     value: null,
218                     optionClass: 'unset',
219                     disabled: true
220                 });
221             }
222
223             // load the registries
224             registryCombo.combo({
225                 options: registries,
226                 select: function (selectedOption) {
227                     selectRegistry(dialog, selectedOption, bucketCombo, flowCombo, selectBucket, bucketCheck)
228                 }
229             });
230         }).fail(nfErrorHandler.handleAjaxError);
231     };
232
233     /**
234      * Loads the buckets for the specified registryIdentifier for the current user.
235      *
236      * @param registryIdentifier
237      * @param bucketCombo
238      * @param flowCombo
239      * @param selectBucket
240      * @param bucketCheck
241      * @returns {*}
242      */
243     var loadBuckets = function (registryIdentifier, bucketCombo, flowCombo, selectBucket, bucketCheck) {
244         return $.ajax({
245             type: 'GET',
246             url: '../nifi-api/flow/registries/' + encodeURIComponent(registryIdentifier) + '/buckets',
247             dataType: 'json'
248         }).done(function (response) {
249             var buckets = [];
250
251             if (nfCommon.isDefinedAndNotNull(response.buckets) && response.buckets.length > 0) {
252                 response.buckets.sort(function (a, b) {
253                     if (a.permissions.canRead === false && b.permissions.canRead === false) {
254                         return 0;
255                     } else if (a.permissions.canRead === false) {
256                         return -1;
257                     } else if (b.permissions.canRead === false) {
258                         return 1;
259                     }
260
261                     return a.bucket.name > b.bucket.name;
262                 });
263
264                 $.each(response.buckets, function (_, bucketEntity) {
265                     if (bucketEntity.permissions.canRead === true) {
266                         var bucket = bucketEntity.bucket;
267
268                         if (bucketCheck(bucketEntity)) {
269                             buckets.push({
270                                 text: bucket.name,
271                                 value: bucket.id,
272                                 description: nfCommon.escapeHtml(bucket.description)
273                             });
274                         }
275                     }
276                 });
277             }
278
279             if (buckets.length === 0) {
280                 buckets.push({
281                     text: 'No available buckets',
282                     value: null,
283                     optionClass: 'unset',
284                     disabled: true
285                 });
286
287                 if (nfCommon.isDefinedAndNotNull(flowCombo)) {
288                     flowCombo.combo('destroy').combo({
289                         options: [{
290                             text: 'No available flows',
291                             value: null,
292                             optionClass: 'unset',
293                             disabled: true
294                         }]
295                     });
296                 }
297             }
298
299             // load the buckets
300             bucketCombo.combo('destroy').combo({
301                 options: buckets,
302                 select: selectBucket
303             });
304         }).fail(nfErrorHandler.handleAjaxError);
305     };
306
307     /**
308      * Select handler for the registries combo.
309      *
310      * @param dialog
311      * @param selectedOption
312      * @param bucketCombo
313      * @param flowCombo
314      * @param selectBucket
315      * @param bucketCheck
316      */
317     var selectRegistry = function (dialog, selectedOption, bucketCombo, flowCombo, selectBucket, bucketCheck) {
318         var showNoBucketsAvailable = function () {
319             bucketCombo.combo('destroy').combo({
320                 options: [{
321                     text: 'No available buckets',
322                     value: null,
323                     optionClass: 'unset',
324                     disabled: true
325                 }]
326             });
327
328             if (nfCommon.isDefinedAndNotNull(flowCombo)) {
329                 flowCombo.combo('destroy').combo({
330                     options: [{
331                         text: 'No available flows',
332                         value: null,
333                         optionClass: 'unset',
334                         disabled: true
335                     }]
336                 });
337             }
338
339             dialog.modal('refreshButtons');
340         };
341
342         if (selectedOption.disabled === true) {
343             showNoBucketsAvailable();
344         } else {
345             bucketCombo.combo('destroy').combo({
346                 options: [{
347                     text: 'Loading buckets...',
348                     value: null,
349                     optionClass: 'unset',
350                     disabled: true
351                 }]
352             });
353
354             if (nfCommon.isDefinedAndNotNull(flowCombo)) {
355                 flowCombo.combo('destroy').combo({
356                     options: [{
357                         text: 'Loading flows...',
358                         value: null,
359                         optionClass: 'unset',
360                         disabled: true
361                     }]
362                 });
363
364                 clearFlowVersionsGrid();
365             }
366
367             loadBuckets(selectedOption.value, bucketCombo, flowCombo, selectBucket, bucketCheck).fail(function () {
368                 showNoBucketsAvailable();
369             });
370         }
371     };
372
373     /**
374      * Select handler for the buckets combo.
375      *
376      * @param selectedOption
377      */
378     var selectBucketSaveFlowVersion = function (selectedOption) {
379         $('#save-flow-version-dialog').modal('refreshButtons');
380     };
381
382     /**
383      * Saves a flow version.
384      * @author: Renu
385      * @desc for lines 390-396: when a dflow is committed, then environment dropdown selection is enabled.
386      * If Env is pre-selected and enabled =>submit button is enabled
387      * @returns {*}
388      */
389     var saveFlowVersion = function () {
390         var processGroupId = $('#save-flow-version-process-group-id').text();
391         var processGroupRevision = $('#save-flow-version-process-group-id').data('revision');
392
393         $('#environmentType').prop('disabled', false);
394         console.log("test submit btn..... ");
395
396         if($('#environmentType').val() && !$('#environmentType').prop("disabled")){
397             $('#operate-submit-btn').prop('disabled', false);
398             console.log("button is enabled bcz env is already selected and not disabled");
399         }
400
401         var saveFlowVersionRequest = {
402             processGroupRevision: nfClient.getRevision({
403                 revision: {
404                     version: processGroupRevision.version
405                 }
406             }),
407             'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged()
408         };
409
410         var versionControlInformation = $('#save-flow-version-process-group-id').data('versionControlInformation');
411         if (nfCommon.isDefinedAndNotNull(versionControlInformation)) {
412             saveFlowVersionRequest['versionedFlow'] = {
413                 registryId: versionControlInformation.registryId,
414                 bucketId: versionControlInformation.bucketId,
415                 flowId: versionControlInformation.flowId,
416                 comments: $('#save-flow-version-change-comments').val()
417             }
418         } else {
419             var selectedRegistry =  $('#save-flow-version-registry-combo').combo('getSelectedOption');
420             var selectedBucket =  $('#save-flow-version-bucket-combo').combo('getSelectedOption');
421
422             saveFlowVersionRequest['versionedFlow'] = {
423                 registryId: selectedRegistry.value,
424                 bucketId: selectedBucket.value,
425                 flowName: $('#save-flow-version-name-field').val(),
426                 description: $('#save-flow-version-description-field').val(),
427                 comments: $('#save-flow-version-change-comments').val()
428             };
429         }
430
431         return $.ajax({
432             type: 'POST',
433             data: JSON.stringify(saveFlowVersionRequest),
434             url: '../nifi-api/versions/process-groups/' + encodeURIComponent(processGroupId),
435             dataType: 'json',
436             contentType: 'application/json'
437         }).fail(nfErrorHandler.handleAjaxError)
438
439
440     };
441
442     /**
443      * Sorts the specified data using the specified sort details.
444      *
445      * @param {object} sortDetails
446      * @param {object} data
447      */
448     var sort = function (sortDetails, data) {
449         // defines a function for sorting
450         var comparer = function (a, b) {
451             var aIsBlank = nfCommon.isBlank(a[sortDetails.columnId]);
452             var bIsBlank = nfCommon.isBlank(b[sortDetails.columnId]);
453
454             if (aIsBlank && bIsBlank) {
455                 return 0;
456             } else if (aIsBlank) {
457                 return 1;
458             } else if (bIsBlank) {
459                 return -1;
460             }
461
462             return a[sortDetails.columnId] === b[sortDetails.columnId] ? 0 : a[sortDetails.columnId] > b[sortDetails.columnId] ? 1 : -1;
463         };
464
465         // perform the sort
466         data.sort(comparer, sortDetails.sortAsc);
467     };
468
469     var initImportFlowVersionTable = function () {
470         var importFlowVersionTable = $('#import-flow-version-table');
471
472         var valueFormatter = function (row, cell, value, columnDef, dataContext) {
473             return nfCommon.escapeHtml(value);
474         };
475
476         var timestampFormatter = function (row, cell, value, columnDef, dataContext) {
477             // get the current user time to properly convert the server time
478             var now = new Date();
479
480             // convert the user offset to millis
481             var userTimeOffset = now.getTimezoneOffset() * 60 * 1000;
482
483             // create the proper date by adjusting by the offsets
484             var date = new Date(dataContext.timestamp + userTimeOffset + serverTimeOffset);
485             return nfCommon.formatDateTime(date);
486         };
487
488         // define the column model for flow versions
489         var importFlowVersionColumns = [
490             {
491                 id: 'version',
492                 name: 'Version',
493                 field: 'version',
494                 formatter: valueFormatter,
495                 sortable: true,
496                 resizable: true,
497                 width: 75,
498                 maxWidth: 75
499             },
500             {
501                 id: 'timestamp',
502                 name: 'Created',
503                 field: 'timestamp',
504                 formatter: timestampFormatter,
505                 sortable: true,
506                 resizable: true,
507                 width: 175,
508                 maxWidth: 175
509             },
510             {
511                 id: 'changeComments',
512                 name: 'Comments',
513                 field: 'comments',
514                 sortable: true,
515                 resizable: true,
516                 formatter: valueFormatter
517             }
518         ];
519
520         // initialize the dataview
521         var importFlowVersionData = new Slick.Data.DataView({
522             inlineFilters: false
523         });
524
525         // initialize the sort
526         sort({
527             columnId: 'version',
528             sortAsc: false
529         }, importFlowVersionData);
530
531         // initialize the grid
532         var importFlowVersionGrid = new Slick.Grid(importFlowVersionTable, importFlowVersionData, importFlowVersionColumns, gridOptions);
533         importFlowVersionGrid.setSelectionModel(new Slick.RowSelectionModel());
534         importFlowVersionGrid.registerPlugin(new Slick.AutoTooltips());
535         importFlowVersionGrid.setSortColumn('version', false);
536         importFlowVersionGrid.onSort.subscribe(function (e, args) {
537             sort({
538                 columnId: args.sortCol.id,
539                 sortAsc: args.sortAsc
540             }, importFlowVersionData);
541         });
542         importFlowVersionGrid.onSelectedRowsChanged.subscribe(function (e, args) {
543             $('#import-flow-version-dialog').modal('refreshButtons');
544         });
545         importFlowVersionGrid.onDblClick.subscribe(function (e, args) {
546             if ($('#import-flow-version-label').is(':visible')) {
547                 changeFlowVersion();
548             } else {
549                 importFlowVersion().always(function () {
550                     $('#import-flow-version-dialog').modal('hide');
551                 });
552             }
553         });
554
555         // wire up the dataview to the grid
556         importFlowVersionData.onRowCountChanged.subscribe(function (e, args) {
557             importFlowVersionGrid.updateRowCount();
558             importFlowVersionGrid.render();
559         });
560         importFlowVersionData.onRowsChanged.subscribe(function (e, args) {
561             importFlowVersionGrid.invalidateRows(args.rows);
562             importFlowVersionGrid.render();
563         });
564         importFlowVersionData.syncGridSelection(importFlowVersionGrid, true);
565
566         // hold onto an instance of the grid
567         importFlowVersionTable.data('gridInstance', importFlowVersionGrid);
568     };
569
570     /**
571      * Initializes the specified local changes table.
572      *
573      * @param localChangesTable
574      * @param filterInput
575      * @param displayedLabel
576      * @param totalLabel
577      */
578     var initLocalChangesTable = function (localChangesTable, filterInput, displayedLabel, totalLabel) {
579
580         var getFilterText = function () {
581             return filterInput.val();
582         };
583
584         var applyFilter = function () {
585             // get the dataview
586             var localChangesGrid = localChangesTable.data('gridInstance');
587
588             // ensure the grid has been initialized
589             if (nfCommon.isDefinedAndNotNull(localChangesGrid)) {
590                 var localChangesData = localChangesGrid.getData();
591
592                 // update the search criteria
593                 localChangesData.setFilterArgs({
594                     searchString: getFilterText()
595                 });
596                 localChangesData.refresh();
597             }
598         };
599
600         var filter = function (item, args) {
601             if (args.searchString === '') {
602                 return true;
603             }
604
605             try {
606                 // perform the row filtering
607                 var filterExp = new RegExp(args.searchString, 'i');
608             } catch (e) {
609                 // invalid regex
610                 return false;
611             }
612
613             // determine if the item matches the filter
614             var matchesId = item['componentId'].search(filterExp) >= 0;
615             var matchesDifferenceType = item['differenceType'].search(filterExp) >= 0;
616             var matchesDifference = item['difference'].search(filterExp) >= 0;
617
618             // conditionally consider the component name
619             var matchesComponentName = false;
620             if (nfCommon.isDefinedAndNotNull(item['componentName'])) {
621                 matchesComponentName = item['componentName'].search(filterExp) >= 0;
622             }
623
624             return matchesId || matchesComponentName || matchesDifferenceType || matchesDifference;
625         };
626
627         // initialize the component state filter
628         filterInput.on('keyup', function () {
629             applyFilter();
630         });
631
632         var valueFormatter = function (row, cell, value, columnDef, dataContext) {
633             return nfCommon.escapeHtml(value);
634         };
635
636         var actionsFormatter = function (row, cell, value, columnDef, dataContext) {
637             var markup = '';
638
639             if (dataContext.differenceType !== 'Component Removed' && nfCommon.isDefinedAndNotNull(dataContext.processGroupId)) {
640                 markup += '<div class="pointer go-to-component fa fa-long-arrow-right" title="Go To"></div>';
641             }
642
643             return markup;
644         };
645
646         // define the column model for local changes
647         var localChangesColumns = [
648             {
649                 id: 'componentName',
650                 name: 'Component Name',
651                 field: 'componentName',
652                 formatter: valueFormatter,
653                 sortable: true,
654                 resizable: true
655             },
656             {
657                 id: 'differenceType',
658                 name: 'Change Type',
659                 field: 'differenceType',
660                 formatter: valueFormatter,
661                 sortable: true,
662                 resizable: true
663             },
664             {
665                 id: 'difference',
666                 name: 'Difference',
667                 field: 'difference',
668                 formatter: valueFormatter,
669                 sortable: true,
670                 resizable: true
671             },
672             {
673                 id: 'actions',
674                 name: '&nbsp;',
675                 formatter: actionsFormatter,
676                 sortable: false,
677                 resizable: false,
678                 width: 25
679             }
680         ];
681
682         // initialize the dataview
683         var localChangesData = new Slick.Data.DataView({
684             inlineFilters: false
685         });
686         localChangesData.setFilterArgs({
687             searchString: getFilterText()
688         });
689         localChangesData.setFilter(filter);
690
691         // initialize the sort
692         sort({
693             columnId: 'componentName',
694             sortAsc: true
695         }, localChangesData);
696
697         // initialize the grid
698         var localChangesGrid = new Slick.Grid(localChangesTable, localChangesData, localChangesColumns, gridOptions);
699         localChangesGrid.setSelectionModel(new Slick.RowSelectionModel());
700         localChangesGrid.registerPlugin(new Slick.AutoTooltips());
701         localChangesGrid.setSortColumn('componentName', true);
702         localChangesGrid.onSort.subscribe(function (e, args) {
703             sort({
704                 columnId: args.sortCol.id,
705                 sortAsc: args.sortAsc
706             }, localChangesData);
707         });
708
709         // configure a click listener
710         localChangesGrid.onClick.subscribe(function (e, args) {
711             var target = $(e.target);
712
713             // get the node at this row
714             var componentDifference = localChangesData.getItem(args.row);
715
716             // determine the desired action
717             if (localChangesGrid.getColumns()[args.cell].id === 'actions') {
718                 if (target.hasClass('go-to-component')) {
719                     if (componentDifference.componentType === 'Controller Service') {
720                         nfProcessGroupConfiguration.showConfiguration(componentDifference.processGroupId).done(function () {
721                             nfProcessGroupConfiguration.selectControllerService(componentDifference.componentId);
722
723                             localChangesTable.closest('.large-dialog').modal('hide');
724                         });
725                     } else {
726                         nfCanvasUtils.showComponent(componentDifference.processGroupId, componentDifference.componentId).done(function () {
727                             localChangesTable.closest('.large-dialog').modal('hide');
728                         });
729                     }
730                 }
731             }
732         });
733
734         // wire up the dataview to the grid
735         localChangesData.onRowCountChanged.subscribe(function (e, args) {
736             localChangesGrid.updateRowCount();
737             localChangesGrid.render();
738
739             // update the total number of displayed items
740             displayedLabel.text(nfCommon.formatInteger(args.current));
741         });
742         localChangesData.onRowsChanged.subscribe(function (e, args) {
743             localChangesGrid.invalidateRows(args.rows);
744             localChangesGrid.render();
745         });
746         localChangesData.syncGridSelection(localChangesGrid, true);
747
748         // hold onto an instance of the grid
749         localChangesTable.data('gridInstance', localChangesGrid);
750
751         // initialize the number of display items
752         displayedLabel.text('0');
753         totalLabel.text('0');
754     };
755
756     /**
757      * Shows the import flow version dialog.
758      */
759     var showImportFlowVersionDialog = function () {
760         var pt = $('#new-process-group-dialog').data('pt');
761         $('#import-flow-version-dialog').data('pt', pt);
762
763         // update the registry and bucket visibility
764         var registryCombo = $('#import-flow-version-registry-combo').combo('destroy').combo({
765             options: [{
766                 text: 'Loading registries...',
767                 value: null,
768                 optionClass: 'unset',
769                 disabled: true
770             }]
771         }).show();
772         var bucketCombo = $('#import-flow-version-bucket-combo').combo('destroy').combo({
773             options: [{
774                 text: 'Loading buckets...',
775                 value: null,
776                 optionClass: 'unset',
777                 disabled: true
778             }]
779         }).show();
780         var flowCombo = $('#import-flow-version-name-combo').combo('destroy').combo({
781             options: [{
782                 text: 'Loading flows...',
783                 value: null,
784                 optionClass: 'unset',
785                 disabled: true
786             }]
787         }).show();
788
789         loadRegistries($('#import-flow-version-dialog'), registryCombo, bucketCombo, flowCombo, selectBucketImportVersion, function (bucketEntity) {
790             return true;
791         }).done(function () {
792             // show the import dialog
793             $('#import-flow-version-dialog').modal('setHeaderText', 'Import Version').modal('setButtonModel', [{
794                 buttonText: 'Import',
795                 color: {
796                     base: '#728E9B',
797                     hover: '#004849',
798                     text: '#ffffff'
799                 },
800                 disabled: disableImportOrChangeButton,
801                 handler: {
802                     click: function () {
803                         importFlowVersion().always(function () {
804                             $('#import-flow-version-dialog').modal('hide');
805                         });
806                     }
807                 }
808             }, {
809                 buttonText: 'Cancel',
810                 color: {
811                     base: '#E3E8EB',
812                     hover: '#C7D2D7',
813                     text: '#004849'
814                 },
815                 handler: {
816                     click: function () {
817                         $(this).modal('hide');
818                     }
819                 }
820             }]).modal('show');
821
822             // hide the new process group dialog
823             $('#new-process-group-dialog').modal('hide');
824         });
825     };
826
827     /**
828      * Loads the flow versions for the specified registry, bucket, and flow.
829      *
830      * @param registryIdentifier
831      * @param bucketIdentifier
832      * @param flowIdentifier
833      * @returns deferred
834      */
835     var loadFlowVersions = function (registryIdentifier, bucketIdentifier, flowIdentifier) {
836         var importFlowVersionGrid = $('#import-flow-version-table').data('gridInstance');
837         var importFlowVersionData = importFlowVersionGrid.getData();
838
839         // begin the update
840         importFlowVersionData.beginUpdate();
841
842         // remove the current versions
843         importFlowVersionGrid.setSelectedRows([]);
844         importFlowVersionGrid.resetActiveCell();
845         importFlowVersionData.setItems([]);
846
847         return $.ajax({
848             type: 'GET',
849             url: '../nifi-api/flow/registries/' + encodeURIComponent(registryIdentifier) + '/buckets/' + encodeURIComponent(bucketIdentifier) + '/flows/' + encodeURIComponent(flowIdentifier) + '/versions',
850             dataType: 'json'
851         }).done(function (response) {
852             if (nfCommon.isDefinedAndNotNull(response.versionedFlowSnapshotMetadataSet) && response.versionedFlowSnapshotMetadataSet.length > 0) {
853                 $.each(response.versionedFlowSnapshotMetadataSet, function (_, entity) {
854                     importFlowVersionData.addItem($.extend({
855                         id: entity.versionedFlowSnapshotMetadata.version
856                     }, entity.versionedFlowSnapshotMetadata));
857                 });
858             } else {
859                 nfDialog.showOkDialog({
860                     headerText: 'Flow Versions',
861                     dialogContent: 'This flow does not have any versions available.'
862                 });
863             }
864         }).fail(nfErrorHandler.handleAjaxError).always(function () {
865             // end the update
866             importFlowVersionData.endUpdate();
867
868             // resort
869             importFlowVersionData.reSort();
870             importFlowVersionGrid.invalidate();
871         });
872     };
873
874     /**
875      * Loads the versioned flows from the specified registry and bucket.
876      *
877      * @param registryIdentifier
878      * @param bucketIdentifier
879      * @param selectFlow
880      * @returns deferred
881      */
882     var loadFlows = function (registryIdentifier, bucketIdentifier, selectFlow) {
883         return $.ajax({
884             type: 'GET',
885             url: '../nifi-api/flow/registries/' + encodeURIComponent(registryIdentifier) + '/buckets/' + encodeURIComponent(bucketIdentifier) + '/flows',
886             dataType: 'json'
887         }).done(function (response) {
888             var versionedFlows = [];
889
890             if (nfCommon.isDefinedAndNotNull(response.versionedFlows) && response.versionedFlows.length > 0) {
891                 response.versionedFlows.sort(function (a, b) {
892                     return a.versionedFlow.flowName > b.versionedFlow.flowName;
893                 });
894
895                 $.each(response.versionedFlows, function (_, versionedFlowEntity) {
896                     var versionedFlow = versionedFlowEntity.versionedFlow;
897                     versionedFlows.push({
898                         text: versionedFlow.flowName,
899                         value: versionedFlow.flowId,
900                         description: nfCommon.escapeHtml(versionedFlow.description)
901                     });
902                 });
903             } else {
904                 versionedFlows.push({
905                     text: 'No available flows',
906                     value: null,
907                     optionClass: 'unset',
908                     disabled: true
909                 });
910             }
911
912             // load the buckets
913             $('#import-flow-version-name-combo').combo('destroy').combo({
914                 options: versionedFlows,
915                 select: function (selectedFlow) {
916                     if (nfCommon.isDefinedAndNotNull(selectedFlow.value)) {
917                         selectFlow(registryIdentifier, bucketIdentifier, selectedFlow.value)
918                     } else {
919                         var importFlowVersionGrid = $('#import-flow-version-table').data('gridInstance');
920                         var importFlowVersionData = importFlowVersionGrid.getData();
921
922                         // clear the current values
923                         importFlowVersionData.beginUpdate();
924                         importFlowVersionData.setItems([]);
925                         importFlowVersionData.endUpdate();
926                     }
927                 }
928             });
929         }).fail(nfErrorHandler.handleAjaxError);
930     };
931
932     /**
933      * Handler when a versioned flow is selected.
934      *
935      * @param registryIdentifier
936      * @param bucketIdentifier
937      * @param flowIdentifier
938      */
939     var selectVersionedFlow = function (registryIdentifier, bucketIdentifier, flowIdentifier) {
940         loadFlowVersions(registryIdentifier, bucketIdentifier, flowIdentifier).done(function () {
941             $('#import-flow-version-dialog').modal('refreshButtons');
942         });
943     };
944
945     /**
946      * Handler when a bucket is selected.
947      *
948      * @param selectedBucket
949      */
950     var selectBucketImportVersion = function (selectedBucket) {
951         // clear the flow versions grid
952         clearFlowVersionsGrid();
953
954         if (nfCommon.isDefinedAndNotNull(selectedBucket.value)) {
955             // mark the flows as loading
956             $('#import-flow-version-name-combo').combo('destroy').combo({
957                 options: [{
958                     text: 'Loading flows...',
959                     value: null,
960                     optionClass: 'unset',
961                     disabled: true
962                 }]
963             });
964
965             var selectedRegistry = $('#import-flow-version-registry-combo').combo('getSelectedOption');
966
967             // load the flows for the currently selected registry and bucket
968             loadFlows(selectedRegistry.value, selectedBucket.value, selectVersionedFlow);
969         } else {
970             // mark no flows available
971             $('#import-flow-version-name-combo').combo('destroy').combo({
972                 options: [{
973                     text: 'No available flows',
974                     value: null,
975                     optionClass: 'unset',
976                     disabled: true
977                 }]
978             });
979         }
980     };
981
982     /**
983      * Imports the selected flow version.
984      */
985     var importFlowVersion = function () {
986         var pt = $('#import-flow-version-dialog').data('pt');
987
988         var selectedRegistry =  $('#import-flow-version-registry-combo').combo('getSelectedOption');
989         var selectedBucket =  $('#import-flow-version-bucket-combo').combo('getSelectedOption');
990         var selectedFlow =  $('#import-flow-version-name-combo').combo('getSelectedOption');
991
992         var importFlowVersionGrid = $('#import-flow-version-table').data('gridInstance');
993         var selectedVersionIndex = importFlowVersionGrid.getSelectedRows();
994         var selectedVersion = importFlowVersionGrid.getDataItem(selectedVersionIndex[0]);
995
996         var processGroupEntity = {
997             'revision': nfClient.getRevision({
998                 'revision': {
999                     'version': 0
1000                 }
1001             }),
1002             'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(),
1003             'component': {
1004                 'position': {
1005                     'x': pt.x,
1006                     'y': pt.y
1007                 },
1008                 'versionControlInformation': {
1009                     'registryId': selectedRegistry.value,
1010                     'bucketId': selectedBucket.value,
1011                     'flowId': selectedFlow.value,
1012                     'version': selectedVersion.version
1013                 }
1014             }
1015         };
1016
1017         return $.ajax({
1018             type: 'POST',
1019             data: JSON.stringify(processGroupEntity),
1020             url: '../nifi-api/process-groups/' + encodeURIComponent(nfCanvasUtils.getGroupId()) + '/process-groups',
1021             dataType: 'json',
1022             contentType: 'application/json'
1023         }).done(function (response) {
1024             // add the process group to the graph
1025             nfGraph.add({
1026                 'processGroups': [response]
1027             }, {
1028                 'selectAll': true
1029             });
1030
1031             // update component visibility
1032             nfGraph.updateVisibility();
1033
1034             // update the birdseye
1035             nfBirdseye.refresh();
1036         }).fail(nfErrorHandler.handleAjaxError);
1037     };
1038
1039     /**
1040      * Determines whether the import/change button is disabled.
1041      *
1042      * @returns {boolean}
1043      */
1044     var disableImportOrChangeButton = function () {
1045         var importFlowVersionGrid = $('#import-flow-version-table').data('gridInstance');
1046         if (nfCommon.isDefinedAndNotNull(importFlowVersionGrid)) {
1047             var selected = importFlowVersionGrid.getSelectedRows();
1048
1049             // if the version label is visible, this is a change version request so disable when
1050             // the version that represents the current version is selected
1051             if ($('#import-flow-version-label').is(':visible')) {
1052                 if (selected.length === 1) {
1053                     var selectedFlow = importFlowVersionGrid.getDataItem(selected[0]);
1054
1055                     var currentVersion = parseInt($('#import-flow-version-label').text(), 10);
1056                     return currentVersion === selectedFlow.version;
1057                 } else {
1058                     return true;
1059                 }
1060             } else {
1061                 // if importing, enable when a single row is selecting
1062                 return selected.length !== 1;
1063             }
1064         } else {
1065             return true;
1066         }
1067     };
1068
1069     /**
1070      * Changes the flow version for the currently selected Process Group.
1071      *
1072      * @returns {deferred}
1073      */
1074     var changeFlowVersion = function () {
1075         var changeTimer = null;
1076         var changeRequest = null;
1077         var cancelled = false;
1078
1079         var processGroupId = $('#import-flow-version-process-group-id').text();
1080         var processGroupRevision = $('#import-flow-version-process-group-id').data('revision');
1081         var versionControlInformation = $('#import-flow-version-process-group-id').data('versionControlInformation');
1082
1083         var importFlowVersionGrid = $('#import-flow-version-table').data('gridInstance');
1084         var selectedVersionIndex = importFlowVersionGrid.getSelectedRows();
1085         var selectedVersion = importFlowVersionGrid.getDataItem(selectedVersionIndex[0]);
1086
1087         // update the button model of the change version status dialog
1088         $('#change-version-status-dialog').modal('setButtonModel', [{
1089             buttonText: 'Stop',
1090             color: {
1091                 base: '#728E9B',
1092                 hover: '#004849',
1093                 text: '#ffffff'
1094             },
1095             handler: {
1096                 click: function () {
1097                     cancelled = true;
1098
1099                     $('#change-version-status-dialog').modal('setButtonModel', []);
1100
1101                     // we are waiting for the next poll attempt
1102                     if (changeTimer !== null) {
1103                         // cancel it
1104                         clearTimeout(changeTimer);
1105
1106                         // cancel the change request
1107                         completeChangeRequest();
1108                     }
1109                 }
1110             }
1111         }]);
1112
1113         // hide the import dialog immediately
1114         $('#import-flow-version-dialog').modal('hide');
1115
1116         var submitChangeRequest = function () {
1117             var changeVersionRequest = {
1118                 'processGroupRevision': nfClient.getRevision({
1119                     'revision': {
1120                         'version': processGroupRevision.version
1121                     }
1122                 }),
1123                 'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(),
1124                 'versionControlInformation': {
1125                     'groupId': processGroupId,
1126                     'registryId': versionControlInformation.registryId,
1127                     'bucketId': versionControlInformation.bucketId,
1128                     'flowId': versionControlInformation.flowId,
1129                     'version': selectedVersion.version
1130                 }
1131             };
1132
1133             return $.ajax({
1134                 type: 'POST',
1135                 data: JSON.stringify(changeVersionRequest),
1136                 url: '../nifi-api/versions/update-requests/process-groups/' + encodeURIComponent(processGroupId),
1137                 dataType: 'json',
1138                 contentType: 'application/json'
1139             }).done(function () {
1140                 // initialize the progress bar value
1141                 updateProgress(0);
1142
1143                 // show the progress dialog
1144                 $('#change-version-status-dialog').modal('show');
1145             }).fail(nfErrorHandler.handleAjaxError);
1146         };
1147
1148         var pollChangeRequest = function () {
1149             getChangeRequest().done(processChangeResponse);
1150         };
1151
1152         var getChangeRequest = function () {
1153             return $.ajax({
1154                 type: 'GET',
1155                 url: changeRequest.uri,
1156                 dataType: 'json'
1157             }).fail(completeChangeRequest).fail(nfErrorHandler.handleAjaxError);
1158         };
1159
1160         var completeChangeRequest = function () {
1161             if (cancelled === true) {
1162                 // update the message to indicate successful completion
1163                 $('#change-version-status-message').text('The change version request has been cancelled.');
1164
1165                 // update the button model
1166                 $('#change-version-status-dialog').modal('setButtonModel', [{
1167                     buttonText: 'Close',
1168                     color: {
1169                         base: '#728E9B',
1170                         hover: '#004849',
1171                         text: '#ffffff'
1172                     },
1173                     handler: {
1174                         click: function () {
1175                             $(this).modal('hide');
1176                         }
1177                     }
1178                 }]);
1179             }
1180
1181             if (nfCommon.isDefinedAndNotNull(changeRequest)) {
1182                 $.ajax({
1183                     type: 'DELETE',
1184                     url: changeRequest.uri + '?' + $.param({
1185                         'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged()
1186                     }),
1187                     dataType: 'json'
1188                 }).done(function (response) {
1189                     changeRequest = response.request;
1190
1191                     // update the component that was changing
1192                     updateProcessGroup(processGroupId);
1193
1194                     if (nfCommon.isDefinedAndNotNull(changeRequest.failureReason)) {
1195                         // hide the progress dialog
1196                         $('#change-version-status-dialog').modal('hide');
1197
1198                         nfDialog.showOkDialog({
1199                             headerText: 'Change Version',
1200                             dialogContent: nfCommon.escapeHtml(changeRequest.failureReason)
1201                         });
1202                     } else {
1203                         // update the percent complete
1204                         updateProgress(changeRequest.percentCompleted);
1205
1206                         // update the message to indicate successful completion
1207                         $('#change-version-status-message').text('This Process Group version has changed.');
1208
1209                         // update the button model
1210                         $('#change-version-status-dialog').modal('setButtonModel', [{
1211                             buttonText: 'Close',
1212                             color: {
1213                                 base: '#728E9B',
1214                                 hover: '#004849',
1215                                 text: '#ffffff'
1216                             },
1217                             handler: {
1218                                 click: function () {
1219                                     $(this).modal('hide');
1220                                 }
1221                             }
1222                         }]);
1223                     }
1224                 });
1225             }
1226         };
1227
1228         var processChangeResponse = function (response) {
1229             changeRequest = response.request;
1230
1231             if (changeRequest.complete === true || cancelled === true) {
1232                 completeChangeRequest();
1233             } else {
1234                 // update the percent complete
1235                 updateProgress(changeRequest.percentCompleted);
1236
1237                 // update the status of the listing request
1238                 $('#change-version-status-message').text(changeRequest.state);
1239
1240                 changeTimer = setTimeout(function () {
1241                     // clear the timer since we've been invoked
1242                     changeTimer = null;
1243
1244                     // poll revert request
1245                     pollChangeRequest();
1246                 }, 2000);
1247             }
1248         };
1249
1250         submitChangeRequest().done(processChangeResponse);
1251     };
1252
1253     /**
1254      * Gets the version control information for the specified process group id.
1255      *
1256      * @param processGroupId
1257      * @return {deferred}
1258      */
1259     var getVersionControlInformation = function (processGroupId) {
1260         return $.Deferred(function (deferred) {
1261             if (processGroupId === nfCanvasUtils.getGroupId()) {
1262                 $.ajax({
1263                     type: 'GET',
1264                     url: '../nifi-api/versions/process-groups/' + encodeURIComponent(processGroupId),
1265                     dataType: 'json'
1266                 }).done(function (response) {
1267                     deferred.resolve(response);
1268                 }).fail(function () {
1269                     deferred.reject();
1270                 });
1271             } else {
1272                 var processGroup = nfProcessGroup.get(processGroupId);
1273                 if (processGroup.permissions.canRead === true && processGroup.permissions.canWrite === true) {
1274                     deferred.resolve({
1275                         'processGroupRevision': processGroup.revision,
1276                         'versionControlInformation': processGroup.component.versionControlInformation
1277                     });
1278                 } else {
1279                     deferred.reject();
1280                 }
1281             }
1282         }).promise();
1283     };
1284
1285     /**
1286      * Updates the specified process group with the specified version control information.
1287      *
1288      * @param processGroupId
1289      * @param versionControlInformation
1290      */
1291     var updateVersionControlInformation = function (processGroupId, versionControlInformation) {
1292         // refresh either selected PG or bread crumb to reflect connected/tracking status
1293         if (nfCanvasUtils.getGroupId() === processGroupId) {
1294             nfNgBridge.injector.get('breadcrumbsCtrl').updateVersionControlInformation(processGroupId, versionControlInformation);
1295             nfNgBridge.digest();
1296         } else {
1297             nfProcessGroup.reload(processGroupId);
1298         }
1299     };
1300
1301     /**
1302      * Updates the specified process group following an operation that may change it's contents.
1303      *
1304      * @param processGroupId
1305      */
1306     var updateProcessGroup = function (processGroupId) {
1307         if (nfCanvasUtils.getGroupId() === processGroupId) {
1308             // if reverting/changing current PG... reload/refresh this group/canvas
1309
1310             $.ajax({
1311                 type: 'GET',
1312                 url: '../nifi-api/flow/process-groups/' + encodeURIComponent(processGroupId),
1313                 dataType: 'json'
1314             }).done(function (response) {
1315                 // update the graph components
1316                 nfGraph.set(response.processGroupFlow.flow);
1317
1318                 // update the component visibility
1319                 nfGraph.updateVisibility();
1320
1321                 // update the breadcrumbs
1322                 var breadcrumbsCtrl = nfNgBridge.injector.get('breadcrumbsCtrl');
1323                 breadcrumbsCtrl.resetBreadcrumbs();
1324                 breadcrumbsCtrl.generateBreadcrumbs(response.processGroupFlow.breadcrumb);
1325
1326                 // inform Angular app values have changed
1327                 nfNgBridge.digest();
1328             }).fail(nfErrorHandler.handleAjaxError);
1329         } else {
1330             // if reverting selected PG... reload selected PG to update counts, etc
1331             nfProcessGroup.reload(processGroupId);
1332         }
1333     };
1334
1335     /**
1336      * Updates the progress bar to the specified percent complete.
1337      *
1338      * @param percentComplete
1339      */
1340     var updateProgress = function (percentComplete) {
1341         // remove existing labels
1342         var progressBar = $('#change-version-percent-complete');
1343         progressBar.find('div.progress-label').remove();
1344         progressBar.find('md-progress-linear').remove();
1345
1346         // update the progress
1347         var label = $('<div class="progress-label"></div>').text(percentComplete + '%');
1348         (nfNgBridge.injector.get('$compile')($('<md-progress-linear ng-cloak ng-value="' + percentComplete + '" class="md-hue-2" md-mode="determinate" aria-label="Searching Queue"></md-progress-linear>'))(nfNgBridge.rootScope)).appendTo(progressBar);
1349         progressBar.append(label);
1350     };
1351
1352     /**
1353      * Shows local changes for the specified process group.
1354      *
1355      * @param processGroupId
1356      * @param localChangesMessage
1357      * @param localChangesTable
1358      * @param totalLabel
1359      */
1360     var loadLocalChanges = function (processGroupId, localChangesMessage, localChangesTable, totalLabel) {
1361         var localChangesGrid = localChangesTable.data('gridInstance');
1362         var localChangesData = localChangesGrid.getData();
1363
1364         // begin the update
1365         localChangesData.beginUpdate();
1366
1367         // remove the current versions
1368         localChangesGrid.setSelectedRows([]);
1369         localChangesGrid.resetActiveCell();
1370         localChangesData.setItems([]);
1371
1372         // load the necessary details
1373         var loadMessage = getVersionControlInformation(processGroupId).done(function (response) {
1374             if (nfCommon.isDefinedAndNotNull(response.versionControlInformation)) {
1375                 var vci = response.versionControlInformation;
1376                 localChangesMessage.text('The following changes have been made to ' + vci.flowName + ' (Version ' + vci.version + ').');
1377             } else {
1378                 nfDialog.showOkDialog({
1379                     headerText: 'Change Version',
1380                     dialogContent: 'This Process Group is not currently under version control.'
1381                 });
1382             }
1383         });
1384         var loadChanges = $.ajax({
1385             type: 'GET',
1386             url: '../nifi-api/process-groups/' + encodeURIComponent(processGroupId) + '/local-modifications',
1387             dataType: 'json'
1388         }).done(function (response) {
1389             if (nfCommon.isDefinedAndNotNull(response.componentDifferences) && response.componentDifferences.length > 0) {
1390                 var totalDifferences = 0;
1391                 $.each(response.componentDifferences, function (_, componentDifference) {
1392                     $.each(componentDifference.differences, function (_, difference) {
1393                         localChangesData.addItem({
1394                             id: totalDifferences++,
1395                             componentId: componentDifference.componentId,
1396                             componentName: componentDifference.componentName,
1397                             componentType: componentDifference.componentType,
1398                             processGroupId: componentDifference.processGroupId,
1399                             differenceType: difference.differenceType,
1400                             difference: difference.difference
1401                         });
1402                     });
1403                 });
1404
1405                 // end the update
1406                 localChangesData.endUpdate();
1407
1408                 // resort
1409                 localChangesData.reSort();
1410                 localChangesGrid.invalidate();
1411
1412                 // update the total displayed
1413                 totalLabel.text(nfCommon.formatInteger(totalDifferences));
1414             } else {
1415                 nfDialog.showOkDialog({
1416                     headerText: 'Local Changes',
1417                     dialogContent: 'This Process Group does not have any local changes.'
1418                 });
1419             }
1420         }).fail(nfErrorHandler.handleAjaxError);
1421
1422         return $.when(loadMessage, loadChanges);
1423     };
1424
1425     /**
1426      * Revert local changes for the specified process group.
1427      *
1428      * @param processGroupId
1429      */
1430     var revertLocalChanges = function (processGroupId) {
1431         getVersionControlInformation(processGroupId).done(function (response) {
1432             if (nfCommon.isDefinedAndNotNull(response.versionControlInformation)) {
1433                 var revertTimer = null;
1434                 var revertRequest = null;
1435                 var cancelled = false;
1436
1437                 // update the button model of the revert status dialog
1438                 $('#change-version-status-dialog').modal('setButtonModel', [{
1439                     buttonText: 'Stop',
1440                     color: {
1441                         base: '#728E9B',
1442                         hover: '#004849',
1443                         text: '#ffffff'
1444                     },
1445                     handler: {
1446                         click: function () {
1447                             cancelled = true;
1448
1449                             $('#change-version-status-dialog').modal('setButtonModel', []);
1450
1451                             // we are waiting for the next poll attempt
1452                             if (revertTimer !== null) {
1453                                 // cancel it
1454                                 clearTimeout(revertTimer);
1455
1456                                 // cancel the revert request
1457                                 completeRevertRequest();
1458                             }
1459                         }
1460                     }
1461                 }]);
1462
1463                 // hide the import dialog immediately
1464                 $('#import-flow-version-dialog').modal('hide');
1465
1466                 var submitRevertRequest = function () {
1467                     var revertFlowVersionRequest = {
1468                         'processGroupRevision': nfClient.getRevision({
1469                             'revision': {
1470                                 'version': response.processGroupRevision.version
1471                             }
1472                         }),
1473                         'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(),
1474                         'versionControlInformation': response.versionControlInformation
1475                     };
1476
1477                     return $.ajax({
1478                         type: 'POST',
1479                         data: JSON.stringify(revertFlowVersionRequest),
1480                         url: '../nifi-api/versions/revert-requests/process-groups/' + encodeURIComponent(processGroupId),
1481                         dataType: 'json',
1482                         contentType: 'application/json'
1483                     }).done(function () {
1484                         // initialize the progress bar value
1485                         updateProgress(0);
1486
1487                         // show the progress dialog
1488                         $('#change-version-status-dialog').modal('show');
1489                     }).fail(nfErrorHandler.handleAjaxError);
1490                 };
1491
1492                 var pollRevertRequest = function () {
1493                     getRevertRequest().done(processRevertResponse);
1494                 };
1495
1496                 var getRevertRequest = function () {
1497                     return $.ajax({
1498                         type: 'GET',
1499                         url: revertRequest.uri,
1500                         dataType: 'json'
1501                     }).fail(completeRevertRequest).fail(nfErrorHandler.handleAjaxError);
1502                 };
1503
1504                 var completeRevertRequest = function () {
1505                     if (cancelled === true) {
1506                         // update the message to indicate successful completion
1507                         $('#change-version-status-message').text('The revert request has been cancelled.');
1508
1509                         // update the button model
1510                         $('#change-version-status-dialog').modal('setButtonModel', [{
1511                             buttonText: 'Close',
1512                             color: {
1513                                 base: '#728E9B',
1514                                 hover: '#004849',
1515                                 text: '#ffffff'
1516                             },
1517                             handler: {
1518                                 click: function () {
1519                                     $(this).modal('hide');
1520                                 }
1521                             }
1522                         }]);
1523                     }
1524
1525                     if (nfCommon.isDefinedAndNotNull(revertRequest)) {
1526                         $.ajax({
1527                             type: 'DELETE',
1528                             url: revertRequest.uri + '?' + $.param({
1529                                 'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged()
1530                             }),
1531                             dataType: 'json'
1532                         }).done(function (response) {
1533                             revertRequest = response.request;
1534
1535                             // update the component that was changing
1536                             updateProcessGroup(processGroupId);
1537
1538                             if (nfCommon.isDefinedAndNotNull(revertRequest.failureReason)) {
1539                                 // hide the progress dialog
1540                                 $('#change-version-status-dialog').modal('hide');
1541
1542                                 nfDialog.showOkDialog({
1543                                     headerText: 'Revert Local Changes',
1544                                     dialogContent: nfCommon.escapeHtml(revertRequest.failureReason)
1545                                 });
1546                             } else {
1547                                 // update the percent complete
1548                                 updateProgress(revertRequest.percentCompleted);
1549
1550                                 // update the message to indicate successful completion
1551                                 $('#change-version-status-message').text('This Process Group version has changed.');
1552
1553                                 // update the button model
1554                                 $('#change-version-status-dialog').modal('setButtonModel', [{
1555                                     buttonText: 'Close',
1556                                     color: {
1557                                         base: '#728E9B',
1558                                         hover: '#004849',
1559                                         text: '#ffffff'
1560                                     },
1561                                     handler: {
1562                                         click: function () {
1563                                             $(this).modal('hide');
1564                                         }
1565                                     }
1566                                 }]);
1567                             }
1568                         });
1569                     }
1570                 };
1571
1572                 var processRevertResponse = function (response) {
1573                     revertRequest = response.request;
1574
1575                     if (revertRequest.complete === true || cancelled === true) {
1576                         completeRevertRequest();
1577                     } else {
1578                         // update the percent complete
1579                         updateProgress(revertRequest.percentCompleted);
1580
1581                         // update the status of the revert request
1582                         $('#change-version-status-message').text(revertRequest.state);
1583
1584                         revertTimer = setTimeout(function () {
1585                             // clear the timer since we've been invoked
1586                             revertTimer = null;
1587
1588                             // poll revert request
1589                             pollRevertRequest();
1590                         }, 2000);
1591                     }
1592                 };
1593
1594                 submitRevertRequest().done(processRevertResponse);
1595             } else {
1596                 nfDialog.showOkDialog({
1597                     headerText: 'Revert Changes',
1598                     dialogContent: 'This Process Group is not currently under version control.'
1599                 });
1600             }
1601         }).fail(nfErrorHandler.handleAjaxError);
1602     };
1603
1604     return {
1605         init: function (timeOffset) {
1606             serverTimeOffset = timeOffset;
1607
1608             // initialize the flow version dialog
1609             $('#save-flow-version-dialog').modal({
1610                 scrollableContentStyle: 'scrollable',
1611                 headerText: 'Save Flow Version',
1612                 buttons: [{
1613                     buttonText: 'Save',
1614                     color: {
1615                         base: '#728E9B',
1616                         hover: '#004849',
1617                         text: '#ffffff'
1618                     },
1619                     disabled: function () {
1620                         if ($('#save-flow-version-registry-combo').is(':visible')) {
1621                             var selectedRegistry =  $('#save-flow-version-registry-combo').combo('getSelectedOption');
1622                             var selectedBucket =  $('#save-flow-version-bucket-combo').combo('getSelectedOption');
1623
1624                             if (nfCommon.isDefinedAndNotNull(selectedRegistry) && nfCommon.isDefinedAndNotNull(selectedBucket)) {
1625                                 return selectedRegistry.disabled === true || selectedBucket.disabled === true;
1626                             } else {
1627                                 return true;
1628                             }
1629                         } else {
1630                             return false;
1631                         }
1632                     },
1633                     handler: {
1634                         click: function () {
1635                             var processGroupId = $('#save-flow-version-process-group-id').text();
1636                             saveFlowVersion().done(function (response) {
1637                                 updateVersionControlInformation(processGroupId, response.versionControlInformation);
1638                             });
1639
1640                             $(this).modal('hide');
1641                         }
1642                     }
1643                 }, {
1644                     buttonText: 'Cancel',
1645                     color: {
1646                         base: '#E3E8EB',
1647                         hover: '#C7D2D7',
1648                         text: '#004849'
1649                     },
1650                     handler: {
1651                         click: function () {
1652                             $(this).modal('hide');
1653                         }
1654                     }
1655                 }],
1656                 handler: {
1657                     close: function () {
1658                         resetSaveFlowVersionDialog();
1659                     }
1660                 }
1661             });
1662
1663             // initialize the import flow version dialog
1664             $('#import-flow-version-dialog').modal({
1665                 scrollableContentStyle: 'scrollable',
1666                 handler: {
1667                     close: function () {
1668                         resetImportFlowVersionDialog();
1669                     }
1670                 }
1671             });
1672
1673             // configure the drop request status dialog
1674             $('#change-version-status-dialog').modal({
1675                 scrollableContentStyle: 'scrollable',
1676                 headerText: 'Change Flow Version',
1677                 handler: {
1678                     close: function () {
1679                         // clear the current button model
1680                         $('#change-version-status-dialog').modal('setButtonModel', []);
1681                     }
1682                 }
1683             });
1684
1685             // init the revert local changes dialog
1686             $('#revert-local-changes-dialog').modal({
1687                 scrollableContentStyle: 'scrollable',
1688                 headerText: 'Revert Local Changes',
1689                 buttons: [{
1690                     buttonText: 'Revert',
1691                     color: {
1692                         base: '#728E9B',
1693                         hover: '#004849',
1694                         text: '#ffffff'
1695                     },
1696                     handler: {
1697                         click: function () {
1698                             var processGroupId = $('#revert-local-changes-process-group-id').text();
1699                             revertLocalChanges(processGroupId);
1700
1701                             $(this).modal('hide');
1702                         }
1703                     }
1704                 }, {
1705                     buttonText: 'Cancel',
1706                     color: {
1707                         base: '#E3E8EB',
1708                         hover: '#C7D2D7',
1709                         text: '#004849'
1710                     },
1711                     handler: {
1712                         click: function () {
1713                             $(this).modal('hide');
1714                         }
1715                     }
1716                 }],
1717                 handler: {
1718                     close: function () {
1719                         resetRevertLocalChangesDialog();
1720                     }
1721                 }
1722             });
1723
1724             // init the show local changes dialog
1725             $('#show-local-changes-dialog').modal({
1726                 scrollableContentStyle: 'scrollable',
1727                 headerText: 'Show Local Changes',
1728                 buttons: [{
1729                     buttonText: 'Close',
1730                     color: {
1731                         base: '#728E9B',
1732                         hover: '#004849',
1733                         text: '#ffffff'
1734                     },
1735                     handler: {
1736                         click: function () {
1737                             $(this).modal('hide');
1738                         }
1739                     }
1740                 }],
1741                 handler: {
1742                     close: function () {
1743                         resetShowLocalChangesDialog();
1744                     }
1745                 }
1746             });
1747
1748             // handle the click for the process group import
1749             $('#import-process-group-link').on('click', function() {
1750                 showImportFlowVersionDialog();
1751             });
1752
1753             // initialize the import flow version table
1754             initImportFlowVersionTable();
1755             initLocalChangesTable($('#revert-local-changes-table'), $('#revert-local-changes-filter'), $('#displayed-revert-local-changes-entries'), $('#total-revert-local-changes-entries'));
1756             initLocalChangesTable($('#show-local-changes-table'), $('#show-local-changes-filter'), $('#displayed-show-local-changes-entries'), $('#total-show-local-changes-entries'));
1757         },
1758
1759         /**
1760          * Shows the flow version dialog.
1761          *
1762          * @param processGroupId
1763          */
1764         showFlowVersionDialog: function (processGroupId) {
1765             var focusName = true;
1766
1767             return $.Deferred(function (deferred) {
1768                 getVersionControlInformation(processGroupId).done(function (groupVersionControlInformation) {
1769                     if (nfCommon.isDefinedAndNotNull(groupVersionControlInformation.versionControlInformation)) {
1770                         var versionControlInformation = groupVersionControlInformation.versionControlInformation;
1771
1772                         // update the registry and bucket visibility
1773                         $('#save-flow-version-registry').text(versionControlInformation.registryName).show();
1774                         $('#save-flow-version-bucket').text(versionControlInformation.bucketName).show();
1775                         $('#save-flow-version-label').text(versionControlInformation.version + 1);
1776
1777                         $('#save-flow-version-name').text(versionControlInformation.flowName).show();
1778                         nfCommon.populateField('save-flow-version-description', versionControlInformation.flowDescription);
1779                         $('#save-flow-version-description').show();
1780
1781                         // record the versionControlInformation
1782                         $('#save-flow-version-process-group-id').data('versionControlInformation', versionControlInformation);
1783
1784                         // reposition the version label
1785                         $('#save-flow-version-label').css('margin-top', '-15px');
1786
1787                         focusName = false;
1788                         deferred.resolve();
1789                     } else {
1790                         // update the registry and bucket visibility
1791                         var registryCombo = $('#save-flow-version-registry-combo').combo('destroy').combo({
1792                             options: [{
1793                                 text: 'Loading registries...',
1794                                 value: null,
1795                                 optionClass: 'unset',
1796                                 disabled: true
1797                             }]
1798                         }).show();
1799                         var bucketCombo = $('#save-flow-version-bucket-combo').combo('destroy').combo({
1800                             options: [{
1801                                 text: 'Loading buckets...',
1802                                 value: null,
1803                                 optionClass: 'unset',
1804                                 disabled: true
1805                             }]
1806                         }).show();
1807
1808                         // set the initial version
1809                         $('#save-flow-version-label').text(1);
1810
1811                         $('#save-flow-version-name-field').show();
1812                         $('#save-flow-version-description-field').show();
1813
1814                         // reposition the version label
1815                         $('#save-flow-version-label').css('margin-top', '0');
1816
1817                         loadRegistries($('#save-flow-version-dialog'), registryCombo, bucketCombo, null, selectBucketSaveFlowVersion, function (bucketEntity) {
1818                             return bucketEntity.permissions.canWrite === true;
1819                         }).done(function () {
1820                             deferred.resolve();
1821                         }).fail(function () {
1822                             deferred.reject();
1823                         });
1824                     }
1825
1826                     // record the revision
1827                     $('#save-flow-version-process-group-id').data('revision', groupVersionControlInformation.processGroupRevision).text(processGroupId);
1828                 }).fail(nfErrorHandler.handleAjaxError);
1829             }).done(function () {
1830                 $('#save-flow-version-dialog').modal('show');
1831
1832                 if (focusName) {
1833                     $('#save-flow-version-name-field').focus();
1834                 } else {
1835                     $('#save-flow-version-change-comments').focus();
1836                 }
1837             }).fail(function () {
1838                 $('#save-flow-version-dialog').modal('refreshButtons');
1839             }).promise();
1840         },
1841
1842         /**
1843          * Reverts local changes for the specified Process Group.
1844          *
1845          * @param processGroupId
1846          */
1847         revertLocalChanges: function (processGroupId) {
1848             loadLocalChanges(processGroupId, $('#revert-local-changes-message'), $('#revert-local-changes-table'), $('#total-revert-local-changes-entries')).done(function () {
1849                 $('#revert-local-changes-process-group-id').text(processGroupId);
1850                 $('#revert-local-changes-dialog').modal('show');
1851             });
1852         },
1853
1854         /**
1855          * Shows local changes for the specified process group.
1856          *
1857          * @param processGroupId
1858          */
1859         showLocalChanges: function (processGroupId) {
1860             loadLocalChanges(processGroupId, $('#show-local-changes-message'), $('#show-local-changes-table'), $('#total-show-local-changes-entries')).done(function () {
1861                 $('#show-local-changes-dialog').modal('show');
1862             });
1863         },
1864
1865         /**
1866          * Shows the change flow version dialog.
1867          *
1868          * @param processGroupId
1869          */
1870         showChangeFlowVersionDialog: function (processGroupId) {
1871             return $.Deferred(function (deferred) {
1872                 getVersionControlInformation(processGroupId).done(function (groupVersionControlInformation) {
1873                     if (nfCommon.isDefinedAndNotNull(groupVersionControlInformation.versionControlInformation)) {
1874                         var versionControlInformation = groupVersionControlInformation.versionControlInformation;
1875
1876                         // update the registry and bucket visibility
1877                         $('#import-flow-version-registry').text(versionControlInformation.registryName).show();
1878                         $('#import-flow-version-bucket').text(versionControlInformation.bucketName).show();
1879                         $('#import-flow-version-name').text(versionControlInformation.flowName).show();
1880
1881                         // show the current version information
1882                         $('#import-flow-version-container').show();
1883                         $('#import-flow-version-label').text(versionControlInformation.version);
1884
1885                         // record the versionControlInformation
1886                         $('#import-flow-version-process-group-id').data('versionControlInformation', versionControlInformation).data('revision', groupVersionControlInformation.processGroupRevision).text(processGroupId);
1887
1888                         // load the flow versions
1889                         loadFlowVersions(versionControlInformation.registryId, versionControlInformation.bucketId, versionControlInformation.flowId).done(function () {
1890                             deferred.resolve();
1891                         }).fail(function () {
1892                             nfDialog.showOkDialog({
1893                                 headerText: 'Change Version',
1894                                 dialogContent: 'Unable to load available versions for this Process Group.'
1895                             });
1896
1897                             deferred.reject();
1898                         });
1899                     } else {
1900                         nfDialog.showOkDialog({
1901                             headerText: 'Change Version',
1902                             dialogContent: 'This Process Group is not currently under version control.'
1903                         });
1904
1905                         deferred.reject();
1906                     }
1907                 }).fail(nfErrorHandler.handleAjaxError);
1908             }).done(function () {
1909                 // show the dialog
1910                 $('#import-flow-version-dialog').modal('setHeaderText', 'Change Version').modal('setButtonModel', [{
1911                     buttonText: 'Change',
1912                     color: {
1913                         base: '#728E9B',
1914                         hover: '#004849',
1915                         text: '#ffffff'
1916                     },
1917                     disabled: disableImportOrChangeButton,
1918                     handler: {
1919                         click: function () {
1920                             changeFlowVersion();
1921                         }
1922                     }
1923                 }, {
1924                     buttonText: 'Cancel',
1925                     color: {
1926                         base: '#E3E8EB',
1927                         hover: '#C7D2D7',
1928                         text: '#004849'
1929                     },
1930                     handler: {
1931                         click: function () {
1932                             $(this).modal('hide');
1933                         }
1934                     }
1935                 }]).modal('show');
1936             }).promise();
1937         },
1938
1939         /**
1940          * Stops version control for the specified Process Group.
1941          *
1942          * @param processGroupId
1943          */
1944         stopVersionControl: function (processGroupId) {
1945             // prompt the user before disconnecting
1946             nfDialog.showYesNoDialog({
1947                 headerText: 'Stop Version Control',
1948                 dialogContent: 'Are you sure you want to stop version control?',
1949                 noText: 'Cancel',
1950                 yesText: 'Disconnect',
1951                 yesHandler: function () {
1952                     $.ajax({
1953                         type: 'GET',
1954                         url: '../nifi-api/versions/process-groups/' + encodeURIComponent(processGroupId),
1955                         dataType: 'json'
1956                     }).done(function (response) {
1957                         if (nfCommon.isDefinedAndNotNull(response.versionControlInformation)) {
1958                             var revision = nfClient.getRevision({
1959                                 revision: {
1960                                     version: response.processGroupRevision.version
1961                                 }
1962                             });
1963
1964                             $.ajax({
1965                                 type: 'DELETE',
1966                                 url: '../nifi-api/versions/process-groups/' + encodeURIComponent(processGroupId) + '?' + $.param($.extend({
1967                                     'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged()
1968                                 }, revision)),
1969                                 dataType: 'json',
1970                                 contentType: 'application/json'
1971                             }).done(function (response) {
1972                                 updateVersionControlInformation(processGroupId, undefined);
1973
1974                                 nfDialog.showOkDialog({
1975                                     headerText: 'Disconnect',
1976                                     dialogContent: 'This Process Group is no longer under version control.'
1977                                 });
1978                             }).fail(nfErrorHandler.handleAjaxError);
1979                         } else {
1980                             nfDialog.showOkDialog({
1981                                 headerText: 'Disconnect',
1982                                 dialogContent: 'This Process Group is not currently under version control.'
1983                             })
1984                         }
1985                     }).fail(nfErrorHandler.handleAjaxError);
1986                 }
1987             });
1988         }
1989     };
1990 }));