5ffe83f9001502f809817098fdaed2fe3ea86a3d
[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
36 const defaults = {
37 downloadPackage: {
38 id: '',
39 name: '',
40 icon: imgDownload,
41 catalogItems: [],
42 transactionId: '',
43 progress: 0,
44 message: 'Requesting catalog package export...',
45 pending: false,
46 success: false,
47 error: false,
48 url: '',
49 urlValidUntil: ''
50 },
51 checkStatusDelayInSeconds: 2,
52 downloadUrlTimeToLiveInMinutes: 5
53 };
54
55 const exception = function ignoreException() {};
56
57 const packagePropertyNames = Object.keys(defaults.downloadPackage);
58
59 function getCatalogPackageManagerServerOrigin() {
60 return utils.getSearchParams(window.location).upload_server + ':4567';
61 }
62
63 function delayStatusCheck(statusCheckFunction, catalogPackage) {
64 if (!catalogPackage.checkStatusTimeoutId) {
65 const delayCallback = function () {
66 delete catalogPackage.checkStatusTimeoutId;
67 statusCheckFunction(catalogPackage).catch(exception);
68 };
69 catalogPackage.checkStatusTimeoutId = _delay(delayCallback, defaults.checkStatusDelayInSeconds * 1000);
70 }
71 }
72
73 class CatalogPackageManagerStore {
74
75 constructor() {
76
77 this.packages = [];
78
79 this.registerAsync(CatalogDataSource);
80 this.registerAsync(CatalogPackageManagerSource);
81 this.bindAction(CatalogPackageManagerActions.REMOVE_CATALOG_PACKAGE, this.removeCatalogPackage);
82 this.bindAction(CatalogPackageManagerActions.DOWNLOAD_CATALOG_PACKAGE, this.downloadCatalogPackage);
83 this.bindAction(CatalogPackageManagerActions.DOWNLOAD_CATALOG_PACKAGE_STATUS_UPDATED, this.onDownloadCatalogPackageStatusUpdated);
84 this.bindAction(CatalogPackageManagerActions.DOWNLOAD_CATALOG_PACKAGE_ERROR, this.onDownloadCatalogPackageError);
85 this.bindAction(CatalogPackageManagerActions.UPLOAD_CATALOG_PACKAGE, this.uploadCatalogPackage);
86 this.bindAction(CatalogPackageManagerActions.UPLOAD_CATALOG_PACKAGE_STATUS_UPDATED, this.onUploadCatalogPackageStatusUpdated);
87 this.bindAction(CatalogPackageManagerActions.UPLOAD_CATALOG_PACKAGE_ERROR, this.onUploadCatalogPackageError);
88
89 }
90
91 addPackage(catalogPackage) {
92 const packages = [catalogPackage].concat(this.packages);
93 this.setState({packages: packages});
94 }
95
96 updatePackage(catalogPackage) {
97 const packages = this.packages.map(d => {
98 if (d.id === catalogPackage.id) {
99 return Object.assign({}, d, catalogPackage);
100 }
101 return d;
102 });
103 this.setState({packages: packages});
104 }
105
106 removeCatalogPackage(catalogPackage) {
107 const packages = this.packages.filter(d => d.id !== catalogPackage.id);
108 this.setState({packages: packages});
109 }
110
111 uploadCatalogPackage(file) {
112 file.id = file.id || guid();
113 const catalogPackage = _pick(file, packagePropertyNames);
114 catalogPackage.icon = file.riftAction === 'onboard' ? imgOnboard : imgUpdate;
115 catalogPackage.type = 'upload';
116 this.addPackage(catalogPackage);
117 // note DropZone.js handles the async upload so we don't have to invoke any async action creators
118 }
119
120 onUploadCatalogPackageStatusUpdated(response) {
121 const upload = updateStatusInfo(response);
122 this.updatePackage(upload);
123 console.log('updating package upload')
124 // if pending with no transaction id - do nothing
125 // bc DropZone.js will notify upload progress
126 if (upload.pending && upload.transactionId) {
127 console.log('checking status')
128 delayStatusCheck(this.getInstance().requestCatalogPackageUploadStatus, upload);
129 } else if (upload.success) {
130 this.getInstance().loadCatalogs();
131 console.log('finished uploading to node, requesting status from rest')
132 }
133 }
134
135 onUploadCatalogPackageError(response) {
136 console.warn('onUploadCatalogPackageError', response);
137 const catalogPackage = updateStatusInfo(response);
138 this.updatePackage(catalogPackage);
139 }
140
141 downloadCatalogPackage(data) {
142 let catalogItems = data['selectedItems'] || [];
143 let schema = data['selectedFormat'] || 'mano';
144 let grammar = data['selectedGrammar'] || 'osm';
145 let format = "YAML";
146 if (catalogItems.length) {
147 const catalogPackage = Object.assign({}, defaults.downloadPackage, {id: guid()});
148 catalogPackage.name = catalogItems[0].name;
149 catalogPackage.type = 'download';
150 if (catalogItems.length > 1) {
151 catalogPackage.name += ' (' + catalogItems.length + ' items)';
152 }
153 catalogPackage.ids = catalogItems.map(d => d.id).sort().toString();
154 catalogPackage.catalogItems = catalogItems;
155 this.addPackage(catalogPackage);
156 this.getInstance().requestCatalogPackageDownload(catalogPackage, format, grammar, schema).catch(exception);
157 }
158 }
159
160 onDownloadCatalogPackageStatusUpdated(response) {
161 const download = updateStatusInfo(response);
162 this.updatePackage(download);
163 if (download.pending) {
164 delayStatusCheck(this.getInstance().requestCatalogPackageDownloadStatus, download);
165 }
166 }
167
168 onDownloadCatalogPackageError(response) {
169 console.warn('onDownloadCatalogPackageError', response);
170 const catalogPackage = updateStatusInfo(response);
171 this.updatePackage(catalogPackage);
172 }
173
174 }
175
176 function calculateUploadProgressMessage(size = 0, progress = 0, bytesSent = 0) {
177 const amount = parseFloat(progress) || 0;
178 const loaded = amount === 100 ? size : size * amount / 100;
179 let progressText;
180 if (amount === 100) {
181 progressText = numeral(loaded).format('0.0b') + ' loaded ';
182 } else if (typeof amount === 'number' && amount != 0) {
183 progressText = numeral(bytesSent).format('0.0b') + ' out of ' + numeral(size).format('0.0b');
184 } else {
185 progressText = progress;
186 }
187 return progressText;
188 }
189
190 function updateStatusInfo(response) {
191 // returns the catalogPackage object with the status fields updated based on the server response
192 const statusInfo = {
193 pending: false,
194 success: false,
195 error: false
196 };
197 const responseData = (response.data.output) ? response.data.output : response.data;
198 const catalogPackage = response.state;
199 if ( typeof response.data.status !== "number" ) {
200 switch(response.data.status) {
201 case 'upload-progress':
202 statusInfo.pending = true;
203 statusInfo.progress = parseFloat(responseData.progress) || 0;
204 statusInfo.message = calculateUploadProgressMessage(catalogPackage.size, responseData.progress, responseData.bytesSent);
205 break;
206 case 'upload-success':
207 statusInfo.pending = true;
208 statusInfo.progress = 100;
209 statusInfo.message = 'Upload completed.';
210 statusInfo.transactionId = responseData['transaction_id'] || responseData['transaction-id'] || catalogPackage.transactionId;
211 break;
212 case 'upload-error':
213 statusInfo.error = true;
214 statusInfo.message = responseData.message;
215 break;
216 case 'download-requested':
217 statusInfo.pending = true;
218 statusInfo.progress = 25;
219 statusInfo.transactionId = responseData['transaction_id'] || responseData['transaction-id'] || catalogPackage.transactionId;
220 break;
221 case 'pending':
222 statusInfo.pending = true;
223 statusInfo.progress = 50;
224 statusInfo.message = responseData.events[responseData.events.length - 1].text;
225 break;
226 case 'success':
227 statusInfo.success = true;
228 statusInfo.progress = 100;
229 statusInfo.message = responseData.events[responseData.events.length - 1].text;
230 if (catalogPackage.type === 'download') {
231 statusInfo.urlValidUntil = moment().add(defaults.downloadUrlTimeToLiveInMinutes, 'minutes').toISOString();
232 if (responseData.filename) {
233 statusInfo.url = getCatalogPackageManagerServerOrigin() + '/api/export/' + responseData.filename;
234 } else {
235 statusInfo.url = getCatalogPackageManagerServerOrigin() + '/api/export/' + catalogPackage.transactionId + '.tar.gz';
236 }
237 }
238 break;
239 case 'failure':
240 statusInfo.error = true;
241 statusInfo.message = responseData.errors[0].value;
242 break;
243 default:
244 throw new ReferenceError('a status of "request", "success", "failure", "pending", "upload-completed", "upload-error", "download-requested", "upload-progress", "upload-action" is required');
245 }
246 } else {
247 // typically get here due to unexpected development errors (backend exceptions, down/up load server access issues)
248 statusInfo.error = true;
249 statusInfo.message = responseData.statusText || 'Error';
250 }
251 return Object.assign({}, catalogPackage, statusInfo);
252 }
253
254 export default alt.createStore(CatalogPackageManagerStore, 'CatalogPackageManagerStore');