Update package copy operation
[osm/UI.git] / skyquake / plugins / composer / src / src / stores / CatalogPackageManagerStore.js
1
2 /*
3 *
4 * Copyright 2016 RIFT.IO Inc
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 *
18 */
19 'use strict';
20
21 import _delay from 'lodash/delay'
22 import _pick from 'lodash/pick'
23 import alt from '../alt'
24 import guid from '../libraries/guid'
25 import numeral from 'numeral'
26 import moment from 'moment'
27 import utils from '../libraries/utils'
28 import CatalogPackageManagerSource from '../sources/CatalogPackageManagerSource'
29 import CatalogPackageManagerActions from '../actions/CatalogPackageManagerActions'
30 import CatalogDataSource from '../sources/CatalogDataSource'
31
32 import imgDownload from '../../../node_modules/open-iconic/svg/cloud-download.svg'
33 import imgOnboard from '../../../node_modules/open-iconic/svg/cloud-upload.svg'
34 import imgUpdate from '../../../node_modules/open-iconic/svg/data-transfer-upload.svg'
35 import imgCopy from '../../../node_modules/open-iconic/svg/layers.svg'
36
37 const defaults = {
38 operation: {
39 id: '',
40 name: '',
41 icon: '',
42 transactionId: '',
43 progress: 0,
44 message: 'Requested',
45 args: {},
46 pending: false,
47 success: false,
48 error: false,
49 },
50 downloadPackage: {
51 id: '',
52 name: '',
53 icon: imgDownload,
54 catalogItems: [],
55 transactionId: '',
56 progress: 0,
57 message: 'Requesting catalog package export...',
58 pending: false,
59 success: false,
60 error: false,
61 url: '',
62 urlValidUntil: ''
63 },
64 checkStatusDelayInSeconds: 2,
65 downloadUrlTimeToLiveInMinutes: 5
66 };
67
68 const exception = function ignoreException() {};
69
70 const packagePropertyNames = Object.keys(defaults.downloadPackage);
71
72 function getCatalogPackageManagerServerOrigin() {
73 return utils.getSearchParams(window.location).upload_server + ':4567';
74 }
75
76 function delayStatusCheck(statusCheckFunction, operation) {
77 if (!operation.checkStatusTimeoutId) {
78 const delayCallback = function () {
79 delete operation.checkStatusTimeoutId;
80 statusCheckFunction(operation).catch(exception);
81 };
82 operation.checkStatusTimeoutId = _delay(delayCallback, defaults.checkStatusDelayInSeconds * 1000);
83 }
84 }
85
86 class CatalogPackageManagerStore {
87
88 constructor() {
89
90 this.operations = [];
91
92 this.registerAsync(CatalogDataSource);
93 this.registerAsync(CatalogPackageManagerSource);
94 this.bindAction(CatalogPackageManagerActions.REMOVE_CATALOG_OPERATION, this.removeCatalogOperation);
95 this.bindAction(CatalogPackageManagerActions.DOWNLOAD_CATALOG_PACKAGE, this.downloadCatalogPackage);
96 this.bindAction(CatalogPackageManagerActions.DOWNLOAD_CATALOG_PACKAGE_STATUS_UPDATED, this.onDownloadCatalogPackageStatusUpdated);
97 this.bindAction(CatalogPackageManagerActions.DOWNLOAD_CATALOG_PACKAGE_ERROR, this.onDownloadCatalogPackageError);
98 this.bindAction(CatalogPackageManagerActions.UPLOAD_CATALOG_PACKAGE, this.uploadCatalogPackage);
99 this.bindAction(CatalogPackageManagerActions.UPLOAD_CATALOG_PACKAGE_STATUS_UPDATED, this.onUploadCatalogPackageStatusUpdated);
100 this.bindAction(CatalogPackageManagerActions.UPLOAD_CATALOG_PACKAGE_ERROR, this.onUploadCatalogPackageError);
101 this.bindAction(CatalogPackageManagerActions.COPY_CATALOG_PACKAGE, this.copyCatalogPackage);
102 this.bindAction(CatalogPackageManagerActions.UPDATE_STATUS, this.updateOperationStatus);
103 }
104
105 addOperation(operation) {
106 const operations = [operation].concat(this.operations);
107 this.setState({operations});
108 }
109
110 updateOperation(operation) {
111 const operations = this.operations.map(d => {
112 if (d.id === operation.id) {
113 return Object.assign({}, d, operation);
114 }
115 return d;
116 });
117 this.setState({operations});
118 }
119
120 removeCatalogOperation(operation) {
121 const operations = this.operations.filter(d => d.id !== operation.id);
122 this.setState({operations});
123 }
124
125 copyCatalogPackage(sourcePackage) {
126 let operationInfo = Object.assign({}, defaults.operation);
127
128 operationInfo.args.packageType = sourcePackage['uiState']['type'].toUpperCase();
129 operationInfo.args.id = sourcePackage.id;
130 operationInfo.args.name = sourcePackage.name + ' copy';
131
132 operationInfo.id = guid();
133 operationInfo.icon = imgCopy;
134 operationInfo.type = 'copy';
135 operationInfo.name = "Creating " + operationInfo.args.name;
136 operationInfo.message = "Requesting duplication";
137
138 this.addOperation(operationInfo);
139 this.getInstance().requestCatalogPackageCopy(operationInfo, sourcePackage);
140 }
141
142 updateOperationStatus(operation) {
143 console.debug('package manager operation status update', operation);
144 this.updateOperation(operation);
145 if (operation.pending) {
146 delayStatusCheck(this.getInstance().requestCatalogPackageCopyStatus, operation);
147 }
148 }
149
150 uploadCatalogPackage(file) {
151 file.id = file.id || guid();
152 const operation = _pick(file, packagePropertyNames);
153 operation.icon = file.riftAction === 'onboard' ? imgOnboard : imgUpdate;
154 operation.type = 'upload';
155 this.addOperation(operation);
156 // note DropZone.js handles the async upload so we don't have to invoke any async action creators
157 }
158
159 onUploadCatalogPackageStatusUpdated(response) {
160 const upload = updateStatusInfo(response);
161 this.updateOperation(upload);
162 console.log('updating package upload')
163 // if pending with no transaction id - do nothing
164 // bc DropZone.js will notify upload progress
165 if (upload.pending && upload.transactionId) {
166 console.log('checking status')
167 delayStatusCheck(this.getInstance().requestCatalogPackageUploadStatus, upload);
168 } else if (upload.success) {
169 this.getInstance().loadCatalogs();
170 console.log('finished uploading to node, requesting status from rest')
171 }
172 }
173
174 onUploadCatalogPackageError(response) {
175 console.warn('onUploadCatalogPackageError', response);
176 const operation = updateStatusInfo(response);
177 this.updateOperation(operation);
178 }
179
180 downloadCatalogPackage(data) {
181 let catalogItems = data['selectedItems'] || [];
182 let schema = data['selectedFormat'] || 'mano';
183 let grammar = data['selectedGrammar'] || 'osm';
184 let format = "YAML";
185 if (catalogItems.length) {
186 const operation = Object.assign({}, defaults.downloadPackage, {id: guid()});
187 operation.name = catalogItems[0].name;
188 operation.type = 'download';
189 if (catalogItems.length > 1) {
190 operation.name += ' (' + catalogItems.length + ' items)';
191 }
192 operation.ids = catalogItems.map(d => d.id).sort().toString();
193 operation.catalogItems = catalogItems;
194 this.addOperation(operation);
195 this.getInstance().requestCatalogPackageDownload(operation, format, grammar, schema).catch(exception);
196 }
197 }
198
199 onDownloadCatalogPackageStatusUpdated(response) {
200 const download = updateStatusInfo(response);
201 this.updateOperation(download);
202 if (download.pending) {
203 delayStatusCheck(this.getInstance().requestCatalogPackageDownloadStatus, download);
204 }
205 }
206
207 onDownloadCatalogPackageError(response) {
208 console.warn('onDownloadCatalogPackageError', response);
209 const operation = updateStatusInfo(response);
210 this.updateOperation(operation);
211 }
212
213 }
214
215 function calculateUploadProgressMessage(size = 0, progress = 0, bytesSent = 0) {
216 const amount = parseFloat(progress) || 0;
217 const loaded = amount === 100 ? size : size * amount / 100;
218 let progressText;
219 if (amount === 100) {
220 progressText = numeral(loaded).format('0.0b') + ' loaded ';
221 } else if (typeof amount === 'number' && amount != 0) {
222 progressText = numeral(bytesSent).format('0.0b') + ' out of ' + numeral(size).format('0.0b');
223 } else {
224 progressText = progress;
225 }
226 return progressText;
227 }
228
229 function updateStatusInfo(response) {
230 // returns the operation object with the status fields updated based on the server response
231 const statusInfo = {
232 pending: false,
233 success: false,
234 error: false
235 };
236 const responseData = (response.data.output) ? response.data.output : response.data;
237 const operation = response.state;
238 if ( typeof response.data.status !== "number" ) {
239 switch(response.data.status) {
240 case 'upload-progress':
241 statusInfo.pending = true;
242 statusInfo.progress = parseFloat(responseData.progress) || 0;
243 statusInfo.message = calculateUploadProgressMessage(operation.size, responseData.progress, responseData.bytesSent);
244 break;
245 case 'upload-success':
246 statusInfo.pending = true;
247 statusInfo.progress = 100;
248 statusInfo.message = 'Upload completed.';
249 statusInfo.transactionId = responseData['transaction_id'] || responseData['transaction-id'] || operation.transactionId;
250 break;
251 case 'upload-error':
252 statusInfo.error = true;
253 statusInfo.message = responseData.message;
254 break;
255 case 'download-requested':
256 statusInfo.pending = true;
257 statusInfo.progress = 25;
258 statusInfo.transactionId = responseData['transaction_id'] || responseData['transaction-id'] || operation.transactionId;
259 break;
260 case 'pending':
261 statusInfo.pending = true;
262 statusInfo.progress = 50;
263 statusInfo.message = responseData.events[responseData.events.length - 1].text;
264 break;
265 case 'success':
266 statusInfo.success = true;
267 statusInfo.progress = 100;
268 statusInfo.message = responseData.events[responseData.events.length - 1].text;
269 if (operation.type === 'download') {
270 statusInfo.urlValidUntil = moment().add(defaults.downloadUrlTimeToLiveInMinutes, 'minutes').toISOString();
271 if (responseData.filename) {
272 statusInfo.url = getCatalogPackageManagerServerOrigin() + '/api/export/' + responseData.filename;
273 } else {
274 statusInfo.url = getCatalogPackageManagerServerOrigin() + '/api/export/' + operation.transactionId + '.tar.gz';
275 }
276 }
277 break;
278 case 'failure':
279 statusInfo.error = true;
280 statusInfo.message = responseData.errors[0].value;
281 break;
282 default:
283 throw new ReferenceError('a status of "request", "success", "failure", "pending", "upload-completed", "upload-error", "download-requested", "upload-progress", "upload-action" is required');
284 }
285 } else {
286 // typically get here due to unexpected development errors (backend exceptions, down/up load server access issues)
287 statusInfo.error = true;
288 statusInfo.message = responseData.statusText || 'Error';
289 }
290 return Object.assign({}, operation, statusInfo);
291 }
292
293 export default alt.createStore(CatalogPackageManagerStore, 'CatalogPackageManagerStore');