From 40667140982c8d9453d09f7ce27880801db294bb Mon Sep 17 00:00:00 2001 From: Bob Gallagher Date: Mon, 10 Apr 2017 07:58:39 -0400 Subject: [PATCH] New backend copy package support - some reorganization of composer api - rift-15577 Change-Id: I075b4eecd307665e76525d33595607974ce65f1e Signed-off-by: Bob Gallagher --- skyquake/plugins/composer/api/composer.js | 173 +++++++++++----- skyquake/plugins/composer/routes.js | 54 +++-- .../actions/CatalogPackageManagerActions.js | 12 +- .../src/components/CatalogPackageManager.js | 6 +- .../src/src/components/CatalogPanelToolbar.js | 1 + .../sources/CatalogPackageManagerSource.js | 194 ++++++++++++------ .../src/src/stores/CatalogDataStore.js | 25 +-- .../src/stores/CatalogPackageManagerStore.js | 127 ++++++++---- 8 files changed, 392 insertions(+), 200 deletions(-) diff --git a/skyquake/plugins/composer/api/composer.js b/skyquake/plugins/composer/api/composer.js index ebe56360c..f9f059164 100644 --- a/skyquake/plugins/composer/api/composer.js +++ b/skyquake/plugins/composer/api/composer.js @@ -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 }; diff --git a/skyquake/plugins/composer/routes.js b/skyquake/plugins/composer/routes.js index 378220927..b3641aa22 100644 --- a/skyquake/plugins/composer/routes.js +++ b/skyquake/plugins/composer/routes.js @@ -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); diff --git a/skyquake/plugins/composer/src/src/actions/CatalogPackageManagerActions.js b/skyquake/plugins/composer/src/src/actions/CatalogPackageManagerActions.js index 22e32d37b..2769c33bc 100644 --- a/skyquake/plugins/composer/src/src/actions/CatalogPackageManagerActions.js +++ b/skyquake/plugins/composer/src/src/actions/CatalogPackageManagerActions.js @@ -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' + ); } } diff --git a/skyquake/plugins/composer/src/src/components/CatalogPackageManager.js b/skyquake/plugins/composer/src/src/components/CatalogPackageManager.js index c0b996cbc..0811093df 100644 --- a/skyquake/plugins/composer/src/src/components/CatalogPackageManager.js +++ b/skyquake/plugins/composer/src/src/components/CatalogPackageManager.js @@ -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 (
- {packages.map(createItem)} + {operations.map(createItem)}
); diff --git a/skyquake/plugins/composer/src/src/components/CatalogPanelToolbar.js b/skyquake/plugins/composer/src/src/components/CatalogPanelToolbar.js index 5e3e3c091..1501ecda6 100644 --- a/skyquake/plugins/composer/src/src/components/CatalogPanelToolbar.js +++ b/skyquake/plugins/composer/src/src/components/CatalogPanelToolbar.js @@ -94,6 +94,7 @@ const CatalogHeader = React.createClass({ CatalogItemsActions.createCatalogItem(type); }, onClickDuplicateCatalogItem() { + CatalogPanelTrayActions.open(); CatalogItemsActions.duplicateSelectedCatalogItem(); }, onClickExportCatalogItems() { diff --git a/skyquake/plugins/composer/src/src/sources/CatalogPackageManagerSource.js b/skyquake/plugins/composer/src/src/sources/CatalogPackageManagerSource.js index 48d697b96..290d71518 100644 --- a/skyquake/plugins/composer/src/src/sources/CatalogPackageManagerSource.js +++ b/skyquake/plugins/composer/src/src/sources/CatalogPackageManagerSource.js @@ -18,71 +18,98 @@ */ '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, diff --git a/skyquake/plugins/composer/src/src/stores/CatalogDataStore.js b/skyquake/plugins/composer/src/src/stores/CatalogDataStore.js index 3e4ac7f11..06d134276 100644 --- a/skyquake/plugins/composer/src/src/stores/CatalogDataStore.js +++ b/skyquake/plugins/composer/src/src/stores/CatalogDataStore.js @@ -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); } } diff --git a/skyquake/plugins/composer/src/src/stores/CatalogPackageManagerStore.js b/skyquake/plugins/composer/src/src/stores/CatalogPackageManagerStore.js index 5ffe83f90..c964c6796 100644 --- a/skyquake/plugins/composer/src/src/stores/CatalogPackageManagerStore.js +++ b/skyquake/plugins/composer/src/src/stores/CatalogPackageManagerStore.js @@ -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'); -- 2.17.1