update from RIFT as of 696b75d2fe9fb046261b08c616f1bcf6c0b54a9b third try
[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 const API_SERVER = utils.getSearchParams(window.location).api_server;
72 const FILE_SERVER = window.location.hostname === 'localhost'
73 ? API_SERVER : window.location.protocol + '//' + window.location.hostname;
74
75 function getCatalogPackageManagerServerOrigin() {
76 return FILE_SERVER + ':8008';
77 }
78
79 function delayStatusCheck(statusCheckFunction, operation) {
80 if (!operation.checkStatusTimeoutId) {
81 const delayCallback = function () {
82 delete operation.checkStatusTimeoutId;
83 statusCheckFunction(operation).catch(exception);
84 };
85 operation.checkStatusTimeoutId = _delay(delayCallback, defaults.checkStatusDelayInSeconds * 1000);
86 }
87 }
88
89 class CatalogPackageManagerStore {
90
91 constructor() {
92
93 this.operations = [];
94
95 this.registerAsync(CatalogDataSource);
96 this.registerAsync(CatalogPackageManagerSource);
97 this.bindAction(CatalogPackageManagerActions.REMOVE_CATALOG_OPERATION, this.removeCatalogOperation);
98 this.bindAction(CatalogPackageManagerActions.DOWNLOAD_CATALOG_PACKAGE, this.downloadCatalogPackage);
99 this.bindAction(CatalogPackageManagerActions.DOWNLOAD_CATALOG_PACKAGE_STATUS_UPDATED, this.onDownloadCatalogPackageStatusUpdated);
100 this.bindAction(CatalogPackageManagerActions.DOWNLOAD_CATALOG_PACKAGE_ERROR, this.onDownloadCatalogPackageError);
101 this.bindAction(CatalogPackageManagerActions.UPLOAD_CATALOG_PACKAGE, this.uploadCatalogPackage);
102 this.bindAction(CatalogPackageManagerActions.UPLOAD_CATALOG_PACKAGE_STATUS_UPDATED, this.onUploadCatalogPackageStatusUpdated);
103 this.bindAction(CatalogPackageManagerActions.UPLOAD_CATALOG_PACKAGE_ERROR, this.onUploadCatalogPackageError);
104 this.bindAction(CatalogPackageManagerActions.COPY_CATALOG_PACKAGE, this.copyCatalogPackage);
105 this.bindAction(CatalogPackageManagerActions.UPDATE_STATUS, this.updateOperationStatus);
106 }
107
108 addOperation(operation) {
109 const operations = [operation].concat(this.operations);
110 this.setState({operations});
111 }
112
113 updateOperation(operation) {
114 const operations = this.operations.map(d => {
115 if (d.id === operation.id) {
116 return Object.assign({}, d, operation);
117 }
118 return d;
119 });
120 this.setState({operations});
121 }
122
123 removeCatalogOperation(operation) {
124 const operations = this.operations.filter(d => d.id !== operation.id);
125 this.setState({operations});
126 }
127
128 copyCatalogPackage(sourcePackage) {
129 let operationInfo = Object.assign({}, defaults.operation);
130
131 operationInfo.args.packageType = sourcePackage['uiState']['type'].toUpperCase();
132 operationInfo.args.id = sourcePackage.id;
133 operationInfo.args.name = sourcePackage.name + ' copy';
134
135 operationInfo.id = guid();
136 operationInfo.icon = imgCopy;
137 operationInfo.type = 'copy';
138 operationInfo.name = "Creating " + operationInfo.args.name;
139 operationInfo.message = "Requesting duplication";
140
141 this.addOperation(operationInfo);
142 this.getInstance().requestCatalogPackageCopy(operationInfo, sourcePackage);
143 }
144
145 updateOperationStatus(operation) {
146 console.debug('package manager operation status update', operation);
147 this.updateOperation(operation);
148 if (operation.pending) {
149 delayStatusCheck(this.getInstance().requestCatalogPackageCopyStatus, operation);
150 }
151 }
152
153 uploadCatalogPackage(file) {
154 file.id = file.id || guid();
155 const operation = _pick(file, packagePropertyNames);
156 operation.icon = file.riftAction === 'onboard' ? imgOnboard : imgUpdate;
157 operation.type = 'upload';
158 this.addOperation(operation);
159 // note DropZone.js handles the async upload so we don't have to invoke any async action creators
160 }
161
162 onUploadCatalogPackageStatusUpdated(response) {
163 const upload = updateStatusInfo(response);
164 this.updateOperation(upload);
165 console.log('updating package upload')
166 // if pending with no transaction id - do nothing
167 // bc DropZone.js will notify upload progress
168 if (upload.pending && upload.transactionId) {
169 console.log('checking status')
170 delayStatusCheck(this.getInstance().requestCatalogPackageUploadStatus, upload);
171 } else if (upload.success) {
172 this.getInstance().loadCatalogs();
173 console.log('finished uploading to node, requesting status from rest')
174 }
175 }
176
177 onUploadCatalogPackageError(response) {
178 console.warn('onUploadCatalogPackageError', response);
179 const operation = updateStatusInfo(response);
180 this.updateOperation(operation);
181 }
182
183 downloadCatalogPackage(data) {
184 let catalogItems = data['selectedItems'] || [];
185 let schema = data['selectedFormat'] || 'mano';
186 let grammar = data['selectedGrammar'] || 'osm';
187 let format = "YAML";
188 if (catalogItems.length) {
189 const operation = Object.assign({}, defaults.downloadPackage, {id: guid()});
190 operation.name = catalogItems[0].name;
191 operation.type = 'download';
192 if (catalogItems.length > 1) {
193 operation.name += ' (' + catalogItems.length + ' items)';
194 }
195 operation.ids = catalogItems.map(d => d.id).sort().toString();
196 operation.catalogItems = catalogItems;
197 this.addOperation(operation);
198 this.getInstance().requestCatalogPackageDownload(operation, format, grammar, schema).catch(exception);
199 }
200 }
201
202 onDownloadCatalogPackageStatusUpdated(response) {
203 const download = updateStatusInfo(response);
204 this.updateOperation(download);
205 if (download.pending) {
206 delayStatusCheck(this.getInstance().requestCatalogPackageDownloadStatus, download);
207 }
208 }
209
210 onDownloadCatalogPackageError(response) {
211 console.warn('onDownloadCatalogPackageError', response);
212 const operation = updateStatusInfo(response);
213 this.updateOperation(operation);
214 }
215
216 }
217
218 function calculateUploadProgressMessage(size = 0, progress = 0, bytesSent = 0) {
219 const amount = parseFloat(progress) || 0;
220 const loaded = amount === 100 ? size : size * amount / 100;
221 let progressText;
222 if (amount === 100) {
223 progressText = numeral(loaded).format('0.0b') + ' loaded ';
224 } else if (typeof amount === 'number' && amount != 0) {
225 progressText = numeral(bytesSent).format('0.0b') + ' out of ' + numeral(size).format('0.0b');
226 } else {
227 progressText = progress;
228 }
229 return progressText;
230 }
231
232 function updateStatusInfo(response) {
233 // returns the operation object with the status fields updated based on the server response
234 const statusInfo = {
235 pending: false,
236 success: false,
237 error: false
238 };
239 const responseData = (response.data.output) ? response.data.output : response.data;
240 const operation = response.state;
241 if ( typeof response.data.status !== "number" ) {
242 switch(response.data.status) {
243 case 'upload-progress':
244 statusInfo.pending = true;
245 statusInfo.progress = parseFloat(responseData.progress) || 0;
246 statusInfo.message = calculateUploadProgressMessage(operation.size, responseData.progress, responseData.bytesSent);
247 break;
248 case 'upload-success':
249 statusInfo.pending = true;
250 statusInfo.progress = 50;
251 statusInfo.message = 'Upload completed.';
252 statusInfo.transactionId = responseData['transaction_id'] || responseData['transaction-id'] || operation.transactionId;
253 break;
254 case 'upload-error':
255 statusInfo.error = true;
256 statusInfo.message = responseData.message;
257 break;
258 case 'download-requested':
259 statusInfo.pending = true;
260 statusInfo.progress = 25;
261 statusInfo.transactionId = responseData['transaction_id'] || responseData['transaction-id'] || operation.transactionId;
262 break;
263 case 'pending':
264 statusInfo.pending = true;
265 statusInfo.progress = 50;
266 statusInfo.message = responseData.events && responseData.events.length && responseData.events[responseData.events.length - 1].text;
267 break;
268 case 'success':
269 statusInfo.success = true;
270 statusInfo.progress = 100;
271 statusInfo.message = responseData.events[responseData.events.length - 1].text;
272 if (operation.type === 'download') {
273 statusInfo.urlValidUntil = moment().add(defaults.downloadUrlTimeToLiveInMinutes, 'minutes').toISOString();
274 if (responseData.filename) {
275 statusInfo.url = getCatalogPackageManagerServerOrigin() + '/mano/export/' + responseData.filename;
276 } else {
277 statusInfo.url = getCatalogPackageManagerServerOrigin() + '/mano/export/' + operation.transactionId + '.tar.gz';
278 }
279 }
280 break;
281 case 'failure':
282 statusInfo.error = true;
283 statusInfo.message = responseData.errors[0].value;
284 break;
285 case 'COMPLETED':
286 statusInfo.success = true;
287 statusInfo.progress = 100;
288 statusInfo.message = "Onboarding completed";
289 break;
290 case 'IN_PROGRESS':
291 statusInfo.pending = true;
292 statusInfo.progress = (operation.progress || 25) + ((100 - operation.progress) / 2);
293 statusInfo.message = "Onboarding in progress";
294 break;
295 case 'FAILED':
296 statusInfo.error = true;
297 statusInfo.message = "Operation failed";
298 break;
299 default:
300 statusInfo.error = true;
301 statusInfo.message = responseData.message || "Operation failed";
302 }
303 } else {
304 // typically get here due to unexpected development errors (backend exceptions, down/up load server access issues)
305 statusInfo.error = true;
306 statusInfo.message = responseData.statusText || 'Error';
307 }
308 return Object.assign({}, operation, statusInfo);
309 }
310
311 export default alt.createStore(CatalogPackageManagerStore, 'CatalogPackageManagerStore');