New backend copy package support
authorBob Gallagher <bob.gallagher@riftio.com>
Mon, 10 Apr 2017 11:58:39 +0000 (07:58 -0400)
committerBob Gallagher <bob.gallagher@riftio.com>
Mon, 10 Apr 2017 19:54:11 +0000 (15:54 -0400)
- some reorganization of composer api
- rift-15577

Change-Id: I075b4eecd307665e76525d33595607974ce65f1e
Signed-off-by: Bob Gallagher <bob.gallagher@riftio.com>
skyquake/plugins/composer/api/composer.js
skyquake/plugins/composer/routes.js
skyquake/plugins/composer/src/src/actions/CatalogPackageManagerActions.js
skyquake/plugins/composer/src/src/components/CatalogPackageManager.js
skyquake/plugins/composer/src/src/components/CatalogPanelToolbar.js
skyquake/plugins/composer/src/src/sources/CatalogPackageManagerSource.js
skyquake/plugins/composer/src/src/stores/CatalogDataStore.js
skyquake/plugins/composer/src/src/stores/CatalogPackageManagerStore.js

index ebe5636..f9f0591 100644 (file)
@@ -29,6 +29,7 @@ var PackageFileHandler = require('./packageFileHandler.js');
 
 var Composer = {};
 var FileManager = {};
+var PackageManager = {};
 var DataCenters = {};
 // Catalog module methods
 Composer.get = function(req) {
@@ -303,8 +304,8 @@ Composer.updateSave = function(req) {
     });
 }
 
-Composer.update = function(req) {
-    console.log(' Updating file', req.file.originalname, 'as', req.file.filename);
+PackageManager.upload = function(req) {
+    console.log(' Uploading file', req.file.originalname, 'as', req.file.filename);
     var api_server = req.query['api_server'];
     // dev_download_server is for testing purposes.
     // It is the direct IP address of the Node server where the
@@ -312,17 +313,13 @@ Composer.update = function(req) {
     var download_host = req.query['dev_download_server'];
 
     if (!download_host) {
-        download_host = req.protocol + '://' + req.get('host');//api_server + ':' + utils.getPortForProtocol(req.protocol);
-    }
-    var input = {
-        'external-url': download_host + '/composer/update/' + req.file.filename,
-        'package-type': 'VNFD',
-        'package-id': uuid()
+        download_host = req.protocol + '://' + req.get('host');//req.api_server + ':' + utils.getPortForProtocol(req.protocol);
     }
+
     return new Promise(function(resolve, reject) {
         Promise.all([
             rp({
-                uri: utils.confdPort(api_server) + '/api/operations/package-update',
+                uri: utils.confdPort(api_server) + '/api/operations/package-create',
                 method: 'POST',
                 headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
                     'Authorization': req.get('Authorization')
@@ -332,7 +329,11 @@ Composer.update = function(req) {
                 resolveWithFullResponse: true,
                 json: true,
                 body: {
-                    input: input
+                    input: {
+                        'external-url': download_host + '/composer/upload/' + req.file.filename,
+                        'package-type': 'VNFD',
+                        'package-id': uuid()
+                    }
                 }
             })
         ]).then(function(result) {
@@ -340,7 +341,7 @@ Composer.update = function(req) {
             data['transaction_id'] = result[0].body['output']['transaction-id'];
 
             // Add a status checker on the transaction and then to delete the file later
-            PackageFileHandler.checkCreatePackageStatusAndHandleFile(req, data['transaction_id'], true);
+            PackageFileHandler.checkCreatePackageStatusAndHandleFile(req, data['transaction_id']);
 
             // Return status to composer UI to update the status.
             resolve({
@@ -349,7 +350,7 @@ Composer.update = function(req) {
             });
         }).catch(function(error) {
             var res = {};
-            console.log('Problem with Composer.upload', error);
+            console.log('Problem with PackageManager.upload', error);
             res.statusCode = error.statusCode || 500;
             res.errorMessage = {
                 error: 'Failed to upload package ' + req.file.originalname + '. Error: ' + error
@@ -359,8 +360,8 @@ Composer.update = function(req) {
     });
 };
 
-Composer.upload = function(req) {
-    console.log(' Uploading file', req.file.originalname, 'as', req.file.filename);
+PackageManager.update = function(req) {
+    console.log(' Updating file', req.file.originalname, 'as', req.file.filename);
     var api_server = req.query['api_server'];
     // dev_download_server is for testing purposes.
     // It is the direct IP address of the Node server where the
@@ -368,13 +369,17 @@ Composer.upload = function(req) {
     var download_host = req.query['dev_download_server'];
 
     if (!download_host) {
-        download_host = req.protocol + '://' + req.get('host');//req.api_server + ':' + utils.getPortForProtocol(req.protocol);
+        download_host = req.protocol + '://' + req.get('host');//api_server + ':' + utils.getPortForProtocol(req.protocol);
+    }
+    var input = {
+        'external-url': download_host + '/composer/update/' + req.file.filename,
+        'package-type': 'VNFD',
+        'package-id': uuid()
     }
-
     return new Promise(function(resolve, reject) {
         Promise.all([
             rp({
-                uri: utils.confdPort(api_server) + '/api/operations/package-create',
+                uri: utils.confdPort(api_server) + '/api/operations/package-update',
                 method: 'POST',
                 headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
                     'Authorization': req.get('Authorization')
@@ -384,11 +389,7 @@ Composer.upload = function(req) {
                 resolveWithFullResponse: true,
                 json: true,
                 body: {
-                    input: {
-                        'external-url': download_host + '/composer/upload/' + req.file.filename,
-                        'package-type': 'VNFD',
-                        'package-id': uuid()
-                    }
+                    input: input
                 }
             })
         ]).then(function(result) {
@@ -396,7 +397,7 @@ Composer.upload = function(req) {
             data['transaction_id'] = result[0].body['output']['transaction-id'];
 
             // Add a status checker on the transaction and then to delete the file later
-            PackageFileHandler.checkCreatePackageStatusAndHandleFile(req, data['transaction_id']);
+            PackageFileHandler.checkCreatePackageStatusAndHandleFile(req, data['transaction_id'], true);
 
             // Return status to composer UI to update the status.
             resolve({
@@ -415,28 +416,13 @@ Composer.upload = function(req) {
     });
 };
 
-
-
-Composer.addFile = function(req) {
-    console.log(' Uploading file', req.file.originalname, 'as', req.file.filename);
+PackageManager.export = function(req) {
+    // /api/operations/package-export
     var api_server = req.query['api_server'];
-    var download_host = req.query['dev_download_server'];
-    var package_id = req.query['package_id'];
-    var package_type = req.query['package_type'].toUpperCase();
-    var package_path = req.query['package_path'];
-    if (!download_host) {
-        download_host = req.protocol + '://' + req.get('host');//api_server + ':' + utils.getPortForProtocol(req.protocol);
-    }
-    var input = {
-        'external-url': download_host + '/composer/upload/' + req.query['package_id'] + '/' + req.file.filename,
-        'package-type': package_type,
-        'package-id': package_id,
-        'package-path': package_path + '/' + req.file.filename
-    }
     return new Promise(function(resolve, reject) {
         Promise.all([
             rp({
-                uri: utils.confdPort(api_server) + '/api/operations/package-file-add',
+                uri: utils.confdPort(api_server) + '/api/operations/package-export',
                 method: 'POST',
                 headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
                     'Authorization': req.get('Authorization')
@@ -445,36 +431,33 @@ Composer.addFile = function(req) {
                 rejectUnauthorized: false,
                 resolveWithFullResponse: true,
                 json: true,
-                body: {
-                    input: input
-                }
+                body: { "input": req.body}
             })
         ]).then(function(result) {
             var data = {};
-            data['transaction_id'] = result[0].body['output']['task-id'];
             resolve({
                 statusCode: constants.HTTP_RESPONSE_CODES.SUCCESS.OK,
-                data: data
+                data: result[0].body
             });
         }).catch(function(error) {
             var res = {};
-            console.log('Problem with Composer.upload', error);
+            console.log('Problem with PackageManager.export', error);
             res.statusCode = error.statusCode || 500;
             res.errorMessage = {
-                error: 'Failed to upload package ' + req.file.originalname + '. Error: ' + error
+                error: error
             };
             reject(res);
         });
     });
 }
 
-Composer.exportPackage = function(req) {
-    // /api/operations/package-export
+PackageManager.copy = function(req) {
+    // /api/operations/package-copy
     var api_server = req.query['api_server'];
     return new Promise(function(resolve, reject) {
         Promise.all([
             rp({
-                uri: utils.confdPort(api_server) + '/api/operations/package-export',
+                uri: utils.confdPort(api_server) + '/api/operations/package-copy',
                 method: 'POST',
                 headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
                     'Authorization': req.get('Authorization')
@@ -493,7 +476,7 @@ Composer.exportPackage = function(req) {
             });
         }).catch(function(error) {
             var res = {};
-            console.log('Problem with Composer.exportPackage', error);
+            console.log('Problem with PackageManager.copy', error);
             res.statusCode = error.statusCode || 500;
             res.errorMessage = {
                 error: error
@@ -503,6 +486,89 @@ Composer.exportPackage = function(req) {
     });
 }
 
+PackageManager.getJobStatus = function(req) {
+    var api_server = req.query["api_server"];
+    var uri = utils.confdPort(api_server);
+    var url = '/api/operational/copy-jobs';
+    var id = req.params['id'];
+    return new Promise(function(resolve, reject) {
+        request({
+            url: uri + url + '?deep',
+            method: 'GET',
+            headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
+                'Authorization': req.get('Authorization')
+            }),
+            forever: constants.FOREVER_ON,
+            rejectUnauthorized: false,
+        }, function(error, response, body) {
+            if (utils.validateResponse('restconfAPI.streams', error, response, body, resolve, reject)) {
+                var data = JSON.parse(response.body)['rw-pkg-mgmt:copy-jobs'];
+                var returnData = [];
+                data && data.job.map(function(d) {
+                    if(d['transaction-id'] == id) {
+                        returnData.push(d)
+                    }
+                })
+                resolve({
+                    statusCode: response.statusCode,
+                    data: returnData
+                })
+            };
+        })
+    })
+}
+
+FileManager.addFile = function(req) {
+    console.log(' Uploading file', req.file.originalname, 'as', req.file.filename);
+    var api_server = req.query['api_server'];
+    var download_host = req.query['dev_download_server'];
+    var package_id = req.query['package_id'];
+    var package_type = req.query['package_type'].toUpperCase();
+    var package_path = req.query['package_path'];
+    if (!download_host) {
+        download_host = req.protocol + '://' + req.get('host');//api_server + ':' + utils.getPortForProtocol(req.protocol);
+    }
+    var input = {
+        'external-url': download_host + '/composer/upload/' + req.query['package_id'] + '/' + req.file.filename,
+        'package-type': package_type,
+        'package-id': package_id,
+        'package-path': package_path + '/' + req.file.filename
+    }
+    return new Promise(function(resolve, reject) {
+        Promise.all([
+            rp({
+                uri: utils.confdPort(api_server) + '/api/operations/package-file-add',
+                method: 'POST',
+                headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
+                    'Authorization': req.get('Authorization')
+                }),
+                forever: constants.FOREVER_ON,
+                rejectUnauthorized: false,
+                resolveWithFullResponse: true,
+                json: true,
+                body: {
+                    input: input
+                }
+            })
+        ]).then(function(result) {
+            var data = {};
+            data['transaction_id'] = result[0].body['output']['task-id'];
+            resolve({
+                statusCode: constants.HTTP_RESPONSE_CODES.SUCCESS.OK,
+                data: data
+            });
+        }).catch(function(error) {
+            var res = {};
+            console.log('Problem with Composer.upload', error);
+            res.statusCode = error.statusCode || 500;
+            res.errorMessage = {
+                error: 'Failed to upload package ' + req.file.originalname + '. Error: ' + error
+            };
+            reject(res);
+        });
+    });
+}
+
 FileManager.get = function(req) {
     var api_server = req.query['api_server'];
     var type = req.query['package_type'] && req.query['package_type'].toUpperCase();
@@ -654,5 +720,6 @@ FileManager.job = function(req) {
 }
 module.exports = {
     Composer:Composer,
-    FileManager: FileManager
+    FileManager: FileManager,
+    PackageManager: PackageManager
 };
index 3782209..b3641aa 100644 (file)
@@ -23,6 +23,7 @@ var constants = require('../../framework/core/api_utils/constants.js');
 var C = require('./api/composer.js');
 var Composer = C.Composer;
 var FileManager = C.FileManager;
+var PackageManager = C.PackageManager;
 var multer = require('multer');
 var fs = require('fs');
 var path = require('path');
@@ -103,58 +104,73 @@ router.put('/api/catalog/:catalogType/:id', cors(), function(req, res) {
         res.send(error.errorMessage);
     });
 });
-router.post('/upload', cors(), upload.single('package'), function (req, res, next) {
-    Composer.upload(req).then(function(data) {
+
+router.post('/api/file-manager', cors(), upload.single('package'), function (req, res, next) {
+    FileManager.addFile(req).then(function(data) {
         utils.sendSuccessResponse(data, res);
     }, function(error) {
         utils.sendErrorResponse(error, res);
     });
 });
-router.use('/upload', cors(), express.static('upload/packages'));
 
-router.post('/update', cors(), upload.single('package'), function (req, res, next) {
-    Composer.update(req).then(function(data) {
+router.get('/api/file-manager', cors(), function(req, res) {
+    FileManager.get(req).then(function(data) {
+        utils.sendSuccessResponse(data, res);
+    }, function(error) {
+        utils.sendErrorResponse(error, res);
+    });
+})
+router.get('/api/file-manager/jobs/:id', cors(), function(req, res) {
+    FileManager.job(req).then(function(data) {
+        utils.sendSuccessResponse(data, res);
+    }, function(error) {
+        utils.sendErrorResponse(error, res);
+    });
+});
+router.delete('/api/file-manager', cors(), function(req, res) {
+    FileManager.get(req).then(function(data) {
         utils.sendSuccessResponse(data, res);
     }, function(error) {
         utils.sendErrorResponse(error, res);
     });
 });
-router.use('/update', cors(), express.static('upload/packages'));
-
 
+// Catalog operations via package manager
 
-router.post('/api/file-manager', cors(), upload.single('package'), function (req, res, next) {
-    Composer.addFile(req).then(function(data) {
+router.post('/upload', cors(), upload.single('package'), function (req, res, next) {
+    PackageManager.upload(req).then(function(data) {
         utils.sendSuccessResponse(data, res);
     }, function(error) {
         utils.sendErrorResponse(error, res);
     });
 });
+router.use('/upload', cors(), express.static('upload/packages'));
 
-router.get('/api/file-manager', cors(), function(req, res) {
-    FileManager.get(req).then(function(data) {
+router.post('/update', cors(), upload.single('package'), function (req, res, next) {
+    PackageManager.update(req).then(function(data) {
         utils.sendSuccessResponse(data, res);
     }, function(error) {
         utils.sendErrorResponse(error, res);
     });
-})
-router.get('/api/file-manager/jobs/:id', cors(), function(req, res) {
-    FileManager.job(req).then(function(data) {
+});
+router.use('/update', cors(), express.static('upload/packages'));
+
+router.post('/api/package-export', cors(), function (req, res, next) {
+    PackageManager.export(req).then(function(data) {
         utils.sendSuccessResponse(data, res);
     }, function(error) {
         utils.sendErrorResponse(error, res);
     });
 });
-router.delete('/api/file-manager', cors(), function(req, res) {
-    FileManager.get(req).then(function(data) {
+router.post('/api/package-copy', cors(), function (req, res, next) {
+    PackageManager.copy(req).then(function(data) {
         utils.sendSuccessResponse(data, res);
     }, function(error) {
         utils.sendErrorResponse(error, res);
     });
 });
-
-router.post('/api/package-export', cors(), function (req, res, next) {
-    Composer.exportPackage(req).then(function(data) {
+router.get('/api/package-manager/jobs/:id', cors(), function (req, res, next) {
+    PackageManager.getJobStatus(req).then(function(data) {
         utils.sendSuccessResponse(data, res);
     }, function(error) {
         utils.sendErrorResponse(error, res);
index 22e32d3..2769c33 100644 (file)
@@ -21,7 +21,17 @@ import alt from '../alt';
 class CatalogPackageManagerActions {
 
        constructor() {
-               this.generateActions('downloadCatalogPackage', 'downloadCatalogPackageStatusUpdated', 'downloadCatalogPackageError', 'uploadCatalogPackage', 'uploadCatalogPackageStatusUpdated', 'uploadCatalogPackageError', 'removeCatalogPackage');
+               this.generateActions(
+                       'downloadCatalogPackage', 
+                       'downloadCatalogPackageStatusUpdated',
+                       'downloadCatalogPackageError', 
+                       'uploadCatalogPackage', 
+                       'uploadCatalogPackageStatusUpdated', 
+                       'uploadCatalogPackageError',
+                       'copyCatalogPackage', 
+                       'updateStatus', 
+                       'removeCatalogOperation'
+                       );
        }
 
 }
index c0b996c..0811093 100644 (file)
@@ -88,7 +88,7 @@ const CatalogPackageManager = React.createClass({
 
                var createItem = function (catalogPackage) {
                        const onClickRemove = function () {
-                               CatalogPackageManagerActions.removeCatalogPackage(catalogPackage);
+                               CatalogPackageManagerActions.removeCatalogOperation(catalogPackage);
                        };
                        const classNames = ClassNames('item', {'-error': catalogPackage.error, '-success': catalogPackage.success});
                        return (
@@ -106,11 +106,11 @@ const CatalogPackageManager = React.createClass({
                        );
                };
 
-               const packages = this.state.packages || [];
+               const operations = this.state.operations || [];
                return (
                        <div className="CatalogPackageManager">
                                <div className="items">
-                                       {packages.map(createItem)}
+                                       {operations.map(createItem)}
                                </div>
                        </div>
                );
index 5e3e3c0..1501ecd 100644 (file)
@@ -94,6 +94,7 @@ const CatalogHeader = React.createClass({
                CatalogItemsActions.createCatalogItem(type);
        },
        onClickDuplicateCatalogItem() {
+               CatalogPanelTrayActions.open();
                CatalogItemsActions.duplicateSelectedCatalogItem();
        },
        onClickExportCatalogItems() {
index 48d697b..290d715 100644 (file)
  */
 'use strict';
 
-import $ from 'jquery'
 import alt from '../alt'
-import utils from '../libraries/utils'
+import catalogUtils from '../libraries/utils'
 import CatalogPackageManagerActions from '../actions/CatalogPackageManagerActions'
-let Utils = require('utils/utils.js');
-function getApiServerOrigin() {
-       return utils.getSearchParams(window.location).upload_server + ':4567';
-}
+import Utils from 'utils/utils.js';
 
-function ajaxRequest(path, catalogPackage, resolve, reject, method = 'GET', input, urlOverride) {
-       let options = {
-               url: getApiServerOrigin() + path,
-               type: method,
-               beforeSend: Utils.addAuthorizationStub,
-               dataType: 'json',
-               success: function(data) {
-                       if (typeof data == 'string') {
-                               data = JSON.parse(data);
-                       }
-                       resolve({
-                               data: data,
-                               state: catalogPackage
-                       });
-               },
-               error: function(error) {
-                       if (typeof error == 'string') {
-                               error = JSON.parse(error);
-                       }
-                       reject({
-                               data: error,
-                               state: catalogPackage
-                       });
-               }
+const getAuthorization = () => 'Basic ' + window.sessionStorage.getItem("auth");
+
+const getStateApiPath = (operation, id) => 
+       catalogUtils.getSearchParams(window.location).upload_server + ':4567/api/' + operation + '/' + id + '/state';
+
+const getComposerApiPath = (api) =>
+       window.location.origin + '/composer/api/' + api + '?api_server=' + catalogUtils.getSearchParams(window.location).api_server;
+
+const SUCCESS = {
+               pending: false,
+               success: true,
+               error: false,
+               message: "Completely successfully"
        };
-       if(input) {
-               options.data = input;
+const FAILED = {
+               pending: false,
+               success: false,
+               error: true,
+               message: "Failed"
+       };
+const PENDING = {
+               pending: true,
+               success: false,
+               error: false,
+               message: "In progress"
+       };
+
+function ajaxFetch(url, operation, resolve, reject, method = 'GET', input, urlOverride) {
+       let credentials = 'same-origin';
+       let body = input ? JSON.stringify(input) : null;
+       let headers = new Headers();
+       headers.append('Authorization', getAuthorization());
+       headers.append('Accept', 'application/json');
+       if (input) {
+               headers.append('Content-Type', 'application/json');
        }
-       if (urlOverride) {
-               options.url = window.location.origin + path;
+
+       fetch(url, {method, credentials, headers, body})
+               .then(checkStatusGetJson)
+               .then(handleSuccess)
+               .catch(handleError);
+
+       function checkStatusGetJson(response) {
+               if (response.status >= 200 && response.status < 300) {
+                       return response.json();
+               } else {
+                       var error = new Error(response.statusText)
+                       error.status = response.status;
+                       error.statusText = response.statusText;
+                       throw error
+               }
        }
-       $.ajax(options).fail(function(xhr){
-                                   //Authentication and the handling of fail states should be wrapped up into a connection class.
-                                   Utils.checkAuthentication(xhr.status);
-                               });
-}
 
+       function handleSuccess (data) {
+               if (typeof data == 'string') {
+                       data = JSON.parse(data);
+               }
+               resolve({
+                       state: operation,
+                       data,
+                       operation
+               });
+       }
 
+       function handleError (data) {
+               if (typeof data == 'string') {
+                       data = JSON.parse(data);
+               }
+               reject({
+                       state: operation,
+                       data,
+                       operation
+               });
+       }
+}
 
 const CatalogPackageManagerSource = {
 
-               requestCatalogPackageDownload: function () {
+       requestCatalogPackageDownload: function () {
                return {
                        remote: function (state, download, format, grammar, schema) {
                                return new Promise((resolve, reject) => {
-                                       // the server does not add a status in the payload
-                                       // so we add one so that the success handler will
-                                       // be able to follow the flow of this download
-                                       const setStatusBeforeResolve = (response = {}) => {
+                                       // we need an initial status for UI (server does not send)
+                                       const setStatusBeforeResolve = (response) => {
                                                response.data.status = 'download-requested';
                                                resolve(response);
                                        };
-                                       // RIFT-13485 requires to send type (nsd/vnfd) as a path element.
-                                       // Backend no longer supports mixed multi-package download.
-                                       // Probably does not even support multi-package download of same type.
-                                       // Hence, pick the type from the first element.
                                        const data = {
                                                "package-type": download['catalogItems'][0]['uiState']['type'].toUpperCase(),
                                                "package-id": download.ids,
@@ -90,13 +117,9 @@ const CatalogPackageManagerSource = {
                                                "export-grammar": grammar && grammar.toUpperCase() || 'OSM',
                                                "export-schema": schema && schema.toUpperCase() || "RIFT"
                                        }
-                                       const path = "/composer/api/package-export?api_server=" + utils.getSearchParams(window.location).api_server;
-                                       ajaxRequest(path, download, setStatusBeforeResolve, reject, 'POST', data, true);
+                                       const path = getComposerApiPath('package-export');
+                                       ajaxFetch(path, download, setStatusBeforeResolve, reject, 'POST', data, true);
                                })
-                               //.then(function(data) {
-                               //      let filename = data.data.output.filename;
-                               //      window.open(getApiServerOrigin() + "/api/export/" + filename, "_blank")
-                               //});
                        },
                        success: CatalogPackageManagerActions.downloadCatalogPackageStatusUpdated,
                        error: CatalogPackageManagerActions.downloadCatalogPackageError
@@ -108,8 +131,8 @@ const CatalogPackageManagerSource = {
                        remote: function(state, download) {
                                const transactionId = download.transactionId;
                                return new Promise(function(resolve, reject) {
-                                       const path = '/api/export/' + transactionId + '/state';
-                                       ajaxRequest(path, download, resolve, reject);
+                                       const path = getStateApiPath('export', transactionId);
+                                       ajaxFetch(path, download, resolve, reject);
                                });
                        },
                        success: CatalogPackageManagerActions.downloadCatalogPackageStatusUpdated,
@@ -117,14 +140,69 @@ const CatalogPackageManagerSource = {
                }
        },
 
+       requestCatalogPackageCopy: function () {
+               return {
+                       remote: function (state, operationInfo) {
+                               return new Promise((resolve, reject) => {
+                                       // we need an initial status for UI (server does not send)
+                                       const successHandler = (response) => {
+                                               const status = response.data.output.status;
+                                               const state = status === "COMPLETED" ? SUCCESS : status === "FAILED" ? FAILED : PENDING;
+                                               state.progress = 25; // put something
+                                               let operation = Object.assign({}, operationInfo, state);
+                                               operation.transactionId = response.data.output['transaction-id'];
+                                               resolve(operation);
+                                       }
+                                       const failHandler = (response) => {
+                                               let operation = Object.assign({}, this, FAILED);
+                                               reject(operation);
+                                       };
+                                       const data = {
+                                               "package-type": operationInfo.packageType,
+                                               "package-id": operationInfo.id,
+                                               "package-name": operationInfo.name
+                                       }
+                                       const path = getComposerApiPath('package-copy');
+                                       ajaxFetch(path, operationInfo, successHandler, failHandler, 'POST', data, true);
+                               })
+                       },
+                       success: CatalogPackageManagerActions.updateStatus,
+                       error: CatalogPackageManagerActions.updateStatus
+               };
+       },
+
+       requestCatalogPackageCopyStatus: function() {
+               return {
+                       remote: function(state, operation) {
+                               return new Promise(function(resolve, reject) {
+                                       const successHandler = (response) => {
+                                               const status = response.data[0].status;
+                                               const state = status === "COMPLETED" ? SUCCESS : status === "FAILED" ? FAILED : PENDING;
+                                               state.progress = state.pending ? operation.progress + ((100 - operation.progress) / 2) : 100;
+                                               let newOp = Object.assign({}, operation, state);
+                                               resolve(newOp);
+                                       };
+                                       const failHandler = (response) => {
+                                               let operation = Object.assign({}, this, FAILED);
+                                               reject(operation);
+                                       };
+                                       const path = getComposerApiPath('package-manager/jobs/' + operation.transactionId);
+                                       ajaxFetch(path, operation, successHandler, failHandler);
+                               });
+                       },
+                       success: CatalogPackageManagerActions.updateStatus,
+                       error: CatalogPackageManagerActions.updateStatus
+               }
+       },
+
        requestCatalogPackageUploadStatus: function () {
                return {
                        remote: function (state, upload) {
                                const transactionId = upload.transactionId;
                                return new Promise(function (resolve, reject) {
                                        const action = upload.riftAction === 'onboard' ? 'upload' : 'update';
-                                       const path = '/api/' + action + '/' + transactionId + '/state';
-                                       ajaxRequest(path, upload, resolve, reject);
+                                       const path = getStateApiPath(action, transactionId);
+                                       ajaxFetch(path, upload, resolve, reject);
                                });
                        },
                        success: CatalogPackageManagerActions.uploadCatalogPackageStatusUpdated,
index 3e4ac7f..06d1342 100644 (file)
@@ -460,27 +460,10 @@ class CatalogDataStore {
        }
 
        duplicateSelectedCatalogItem() {
-               const item = this.getFirstSelectedCatalogItem();
-               if (item) {
-                       const newItem = _cloneDeep(item);
-                       newItem.name = newItem.name + ' Copy';
-                       newItem.id = guid();
-                       UID.assignUniqueId(newItem.uiState);
-                       const nsd = this.addNewItemToCatalog(newItem);
-                       this.selectCatalogItem(nsd);
-                       nsd.uiState.isNew = true;
-                       nsd.uiState.modified = true;
-                       nsd.uiState['instance-ref-count'] = 0;
-                       // note duplicated items get a new id, map the layout position
-                       // of the old id to the new id in order to preserve the layout
-                       if (nsd.uiState.containerPositionMap) {
-                               nsd.uiState.containerPositionMap[nsd.id] = nsd.uiState.containerPositionMap[item.id];
-                               delete nsd.uiState.containerPositionMap[item.id];
-                       }
-                       setTimeout(() => {
-                               this.selectCatalogItem(nsd);
-                               CatalogItemsActions.editCatalogItem.defer(nsd);
-                       }, 200);
+               // make request to backend to duplicate an item
+               const srcItem = this.getFirstSelectedCatalogItem();
+               if (srcItem) {
+                       CatalogPackageManagerActions.copyCatalogPackage.defer(srcItem);
                }
        }
 
index 5ffe83f..c964c67 100644 (file)
@@ -32,8 +32,21 @@ import CatalogDataSource from '../sources/CatalogDataSource'
 import imgDownload from '../../../node_modules/open-iconic/svg/cloud-download.svg'
 import imgOnboard from '../../../node_modules/open-iconic/svg/cloud-upload.svg'
 import imgUpdate from '../../../node_modules/open-iconic/svg/data-transfer-upload.svg'
+import imgCopy from '../../../node_modules/open-iconic/svg/layers.svg'
 
 const defaults = {
+       operation: {
+               id: '',
+               name: '',
+               icon: '',
+               transactionId: '',
+               progress: 0,
+               message: 'Requested',
+               args: {},
+               pending: false,
+               success: false,
+               error: false,
+       },
        downloadPackage: {
                id: '',
                name: '',
@@ -60,13 +73,13 @@ function getCatalogPackageManagerServerOrigin() {
        return utils.getSearchParams(window.location).upload_server + ':4567';
 }
 
-function delayStatusCheck(statusCheckFunction, catalogPackage) {
-       if (!catalogPackage.checkStatusTimeoutId) {
+function delayStatusCheck(statusCheckFunction, operation) {
+       if (!operation.checkStatusTimeoutId) {
                const delayCallback = function () {
-                       delete catalogPackage.checkStatusTimeoutId;
-                       statusCheckFunction(catalogPackage).catch(exception);
+                       delete operation.checkStatusTimeoutId;
+                       statusCheckFunction(operation).catch(exception);
                };
-               catalogPackage.checkStatusTimeoutId = _delay(delayCallback, defaults.checkStatusDelayInSeconds * 1000);
+               operation.checkStatusTimeoutId = _delay(delayCallback, defaults.checkStatusDelayInSeconds * 1000);
        }
 }
 
@@ -74,52 +87,76 @@ class CatalogPackageManagerStore {
 
        constructor() {
 
-               this.packages = [];
+               this.operations = [];
 
                this.registerAsync(CatalogDataSource);
                this.registerAsync(CatalogPackageManagerSource);
-               this.bindAction(CatalogPackageManagerActions.REMOVE_CATALOG_PACKAGE, this.removeCatalogPackage);
+               this.bindAction(CatalogPackageManagerActions.REMOVE_CATALOG_OPERATION, this.removeCatalogOperation);
                this.bindAction(CatalogPackageManagerActions.DOWNLOAD_CATALOG_PACKAGE, this.downloadCatalogPackage);
                this.bindAction(CatalogPackageManagerActions.DOWNLOAD_CATALOG_PACKAGE_STATUS_UPDATED, this.onDownloadCatalogPackageStatusUpdated);
                this.bindAction(CatalogPackageManagerActions.DOWNLOAD_CATALOG_PACKAGE_ERROR, this.onDownloadCatalogPackageError);
                this.bindAction(CatalogPackageManagerActions.UPLOAD_CATALOG_PACKAGE, this.uploadCatalogPackage);
                this.bindAction(CatalogPackageManagerActions.UPLOAD_CATALOG_PACKAGE_STATUS_UPDATED, this.onUploadCatalogPackageStatusUpdated);
                this.bindAction(CatalogPackageManagerActions.UPLOAD_CATALOG_PACKAGE_ERROR, this.onUploadCatalogPackageError);
-
+               this.bindAction(CatalogPackageManagerActions.COPY_CATALOG_PACKAGE, this.copyCatalogPackage);
+               this.bindAction(CatalogPackageManagerActions.UPDATE_STATUS, this.updateOperationStatus);
        }
 
-       addPackage(catalogPackage) {
-               const packages = [catalogPackage].concat(this.packages);
-               this.setState({packages: packages});
+       addOperation(operation) {
+               const operations = [operation].concat(this.operations);
+               this.setState({operations});
        }
 
-       updatePackage(catalogPackage) {
-               const packages = this.packages.map(d => {
-                       if (d.id === catalogPackage.id) {
-                               return Object.assign({}, d, catalogPackage);
+       updateOperation(operation) {
+               const operations = this.operations.map(d => {
+                       if (d.id === operation.id) {
+                               return Object.assign({}, d, operation);
                        }
                        return d;
                });
-               this.setState({packages: packages});
+               this.setState({operations});
+       }
+
+       removeCatalogOperation(operation) {
+               const operations = this.operations.filter(d => d.id !== operation.id);
+               this.setState({operations});
        }
 
-       removeCatalogPackage(catalogPackage) {
-               const packages = this.packages.filter(d => d.id !== catalogPackage.id);
-               this.setState({packages: packages});
+       copyCatalogPackage(sourcePackage) {
+               let operationInfo = Object.assign({}, defaults.operation);
+               operationInfo.name =  "Duplication of " + sourcePackage.name;
+               operationInfo.id = guid();
+               operationInfo.icon = imgCopy;
+               operationInfo.type = 'copy';
+               operationInfo.message = 'Requesting package duplication.';
+               operationInfo.args.packageType = sourcePackage['uiState']['type'].toUpperCase();
+               operationInfo.args.id =  sourcePackage.id;
+               operationInfo.args.name =  sourcePackage.name + ' copy';
+
+               this.addOperation(operationInfo);
+               this.getInstance().requestCatalogPackageCopy(operationInfo, sourcePackage);
+       }
+
+       updateOperationStatus(operation) {
+               console.debug('package manager operation status update', operation);
+               this.updateOperation(operation);
+               if (operation.pending) {
+                       delayStatusCheck(this.getInstance().requestCatalogPackageCopyStatus, operation);
+               }
        }
 
        uploadCatalogPackage(file) {
                file.id = file.id || guid();
-               const catalogPackage = _pick(file, packagePropertyNames);
-               catalogPackage.icon = file.riftAction === 'onboard' ? imgOnboard : imgUpdate;
-               catalogPackage.type = 'upload';
-               this.addPackage(catalogPackage);
+               const operation = _pick(file, packagePropertyNames);
+               operation.icon = file.riftAction === 'onboard' ? imgOnboard : imgUpdate;
+               operation.type = 'upload';
+               this.addOperation(operation);
                // note DropZone.js handles the async upload so we don't have to invoke any async action creators
        }
 
        onUploadCatalogPackageStatusUpdated(response) {
                const upload = updateStatusInfo(response);
-               this.updatePackage(upload);
+               this.updateOperation(upload);
                console.log('updating package upload')
                // if pending with no transaction id - do nothing
                // bc DropZone.js will notify upload progress
@@ -134,8 +171,8 @@ class CatalogPackageManagerStore {
 
        onUploadCatalogPackageError(response) {
                console.warn('onUploadCatalogPackageError', response);
-               const catalogPackage = updateStatusInfo(response);
-               this.updatePackage(catalogPackage);
+               const operation = updateStatusInfo(response);
+               this.updateOperation(operation);
        }
 
        downloadCatalogPackage(data) {
@@ -144,22 +181,22 @@ class CatalogPackageManagerStore {
                let grammar = data['selectedGrammar'] || 'osm';
                let format = "YAML";
                if (catalogItems.length) {
-                       const catalogPackage = Object.assign({}, defaults.downloadPackage, {id: guid()});
-                       catalogPackage.name = catalogItems[0].name;
-                       catalogPackage.type = 'download';
+                       const operation = Object.assign({}, defaults.downloadPackage, {id: guid()});
+                       operation.name = catalogItems[0].name;
+                       operation.type = 'download';
                        if (catalogItems.length > 1) {
-                               catalogPackage.name += ' (' + catalogItems.length + ' items)';
+                               operation.name += ' (' + catalogItems.length + ' items)';
                        }
-                       catalogPackage.ids = catalogItems.map(d => d.id).sort().toString();
-                       catalogPackage.catalogItems = catalogItems;
-                       this.addPackage(catalogPackage);
-                       this.getInstance().requestCatalogPackageDownload(catalogPackage, format, grammar, schema).catch(exception);
+                       operation.ids = catalogItems.map(d => d.id).sort().toString();
+                       operation.catalogItems = catalogItems;
+                       this.addOperation(operation);
+                       this.getInstance().requestCatalogPackageDownload(operation, format, grammar, schema).catch(exception);
                }
        }
 
        onDownloadCatalogPackageStatusUpdated(response) {
                const download = updateStatusInfo(response);
-               this.updatePackage(download);
+               this.updateOperation(download);
                if (download.pending) {
                        delayStatusCheck(this.getInstance().requestCatalogPackageDownloadStatus, download);
                }
@@ -167,8 +204,8 @@ class CatalogPackageManagerStore {
 
        onDownloadCatalogPackageError(response) {
                console.warn('onDownloadCatalogPackageError', response);
-               const catalogPackage = updateStatusInfo(response);
-               this.updatePackage(catalogPackage);
+               const operation = updateStatusInfo(response);
+               this.updateOperation(operation);
        }
 
 }
@@ -188,26 +225,26 @@ function calculateUploadProgressMessage(size = 0, progress = 0, bytesSent = 0) {
 }
 
 function updateStatusInfo(response) {
-       // returns the catalogPackage object with the status fields updated based on the server response
+       // returns the operation object with the status fields updated based on the server response
        const statusInfo = {
                pending: false,
                success: false,
                error: false
        };
        const responseData = (response.data.output) ? response.data.output :  response.data;
-       const catalogPackage = response.state;
+       const operation = response.state;
        if ( typeof response.data.status !== "number" ) {
                switch(response.data.status) {
                case 'upload-progress':
                        statusInfo.pending = true;
                        statusInfo.progress = parseFloat(responseData.progress) || 0;
-                       statusInfo.message = calculateUploadProgressMessage(catalogPackage.size, responseData.progress, responseData.bytesSent);
+                       statusInfo.message = calculateUploadProgressMessage(operation.size, responseData.progress, responseData.bytesSent);
                        break;
                case 'upload-success':
                        statusInfo.pending = true;
                        statusInfo.progress = 100;
                        statusInfo.message = 'Upload completed.';
-                       statusInfo.transactionId = responseData['transaction_id'] || responseData['transaction-id'] || catalogPackage.transactionId;
+                       statusInfo.transactionId = responseData['transaction_id'] || responseData['transaction-id'] || operation.transactionId;
                        break;
                case 'upload-error':
                        statusInfo.error = true;
@@ -216,7 +253,7 @@ function updateStatusInfo(response) {
                case 'download-requested':
                        statusInfo.pending = true;
                        statusInfo.progress = 25;
-                       statusInfo.transactionId = responseData['transaction_id'] || responseData['transaction-id']  || catalogPackage.transactionId;
+                       statusInfo.transactionId = responseData['transaction_id'] || responseData['transaction-id']  || operation.transactionId;
                        break;
                case 'pending':
                        statusInfo.pending = true;
@@ -227,12 +264,12 @@ function updateStatusInfo(response) {
                        statusInfo.success = true;
                        statusInfo.progress = 100;
                        statusInfo.message = responseData.events[responseData.events.length - 1].text;
-                       if (catalogPackage.type === 'download') {
+                       if (operation.type === 'download') {
                                statusInfo.urlValidUntil = moment().add(defaults.downloadUrlTimeToLiveInMinutes, 'minutes').toISOString();
                                if (responseData.filename) {
                                        statusInfo.url = getCatalogPackageManagerServerOrigin() + '/api/export/' + responseData.filename;
                                } else {
-                                       statusInfo.url = getCatalogPackageManagerServerOrigin() + '/api/export/' + catalogPackage.transactionId + '.tar.gz';
+                                       statusInfo.url = getCatalogPackageManagerServerOrigin() + '/api/export/' + operation.transactionId + '.tar.gz';
                                }
                        }
                        break;
@@ -248,7 +285,7 @@ function updateStatusInfo(response) {
                statusInfo.error = true;
                statusInfo.message = responseData.statusText || 'Error';
        }
-       return Object.assign({}, catalogPackage, statusInfo);
+       return Object.assign({}, operation, statusInfo);
 }
 
 export default alt.createStore(CatalogPackageManagerStore, 'CatalogPackageManagerStore');