X-Git-Url: https://osm.etsi.org/gitweb/?p=osm%2FUI.git;a=blobdiff_plain;f=skyquake%2Fplugins%2Fcomposer%2Fsrc%2Fsrc%2Fstores%2FCatalogDataStore.js;h=714861f71bcd91ca61e3662e9f64f3744fe0dc7f;hp=1098f0e2342132e0c3f5beb4ca2bd2f8c24d1a13;hb=refs%2Fchanges%2F79%2F5479%2F2;hpb=7a89e546697979b07b160a15fbcafe7442468892 diff --git a/skyquake/plugins/composer/src/src/stores/CatalogDataStore.js b/skyquake/plugins/composer/src/src/stores/CatalogDataStore.js index 1098f0e23..714861f71 100644 --- a/skyquake/plugins/composer/src/src/stores/CatalogDataStore.js +++ b/skyquake/plugins/composer/src/src/stores/CatalogDataStore.js @@ -1,6 +1,6 @@ /* - * + * * Copyright 2016 RIFT.IO Inc * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,11 @@ */ 'use strict'; -import _ from 'lodash' +import _pick from 'lodash/pick' +import _isEqual from 'lodash/isEqual' +import _cloneDeep from 'lodash/cloneDeep' +import _merge from 'lodash/merge' +import _debounce from 'lodash/debounce'; import cc from 'change-case' import alt from '../alt' import UID from '../libraries/UniqueId' @@ -26,6 +30,7 @@ import guid from '../libraries/guid' import React from 'react' import DescriptorModel from '../libraries/model/DescriptorModel' import DescriptorModelMetaFactory from '../libraries/model/DescriptorModelMetaFactory' +import DescriptorModelFactory from '../libraries/model/DescriptorModelFactory' import CatalogPackageManagerActions from '../actions/CatalogPackageManagerActions' import CatalogDataSourceActions from '../actions/CatalogDataSourceActions' import CatalogItemsActions from '../actions/CatalogItemsActions' @@ -34,7 +39,6 @@ import ComposerAppActions from '../actions/ComposerAppActions' import CatalogDataSource from '../sources/CatalogDataSource' import ComposerAppStore from '../stores/ComposerAppStore' import SelectionManager from '../libraries/SelectionManager' -import ExportSelectorDialog from '../components/ExportSelectorDialog' const defaults = { catalogs: [], @@ -42,21 +46,27 @@ const defaults = { catalogItemExportGrammars: ['osm', 'tosca'] }; -const areCatalogItemsMetaDataEqual = function (a, b) { - const metaProps = ['id', 'name', 'short-name', 'description', 'vendor', 'version']; - const aMetaData = _.pick(a, metaProps); - const bMetaData = _.pick(b, metaProps); - return _.isEqual(aMetaData, bMetaData); +const areCatalogItemsMetaDataEqual = function (catItem, activeItem) { + function getDefaultPositionMap() { + if (!activeItem.uiState.containerPositionMap) { + return activeItem.uiState.containerPositionMap; + } + let defaultPositionMap = {}; + defaultPositionMap[activeItem.id] = activeItem.uiState.defaultLayoutPosition; + return defaultPositionMap; + } + const activeItemMetaData = activeItem.uiState.containerPositionMap; + const catItemMetaData = catItem.uiState.containerPositionMap; + return catItemMetaData === undefined || _isEqual(catItemMetaData, activeItemMetaData); }; -function createItem (type) { +function createItem(type) { let newItem = DescriptorModelMetaFactory.createModelInstanceForType(type); - if (newItem){ + if (newItem) { newItem.id = guid(); - UID.assignUniqueId(newItem.uiState); + UID.assignUniqueId(newItem); newItem.uiState.isNew = true; newItem.uiState.modified = true; - newItem.uiState['instance-ref-count'] = 0; } return newItem; } @@ -66,7 +76,6 @@ class CatalogDataStore { constructor() { this.catalogs = defaults.catalogs; this.isLoading = true; - this.requiresSave = false; this.snapshots = {}; this.selectedFormat = defaults.catalogItemExportFormats[0]; this.selectedGrammar = defaults.catalogItemExportGrammars[0]; @@ -74,13 +83,15 @@ class CatalogDataStore { this.bindActions(CatalogDataSourceActions); this.bindActions(CatalogItemsActions); this.exportPublicMethods({ - getCatalogs: this.getCatalogs, - getCatalogItemById: this.getCatalogItemById, - getCatalogItemByUid: this.getCatalogItemByUid, - getTransientCatalogs: this.getTransientCatalogs, - getTransientCatalogItemById: this.getTransientCatalogItemById, - getTransientCatalogItemByUid: this.getTransientCatalogItemByUid - }); + getCatalogs: this.getCatalogs, + getCatalogItemById: this.getCatalogItemById, + getCatalogItemByUid: this.getCatalogItemByUid, + getTransientCatalogs: this.getTransientCatalogs, + getTransientCatalogItemById: this.getTransientCatalogItemById, + getTransientCatalogItemByUid: this.getTransientCatalogItemByUid, + setUserProfile: this.setUserProfile + }); + this.queueDirtyCheck = _debounce(() => this.saveDirtyDescriptorsToSessionStorage(), 500); } resetSelectionState = () => { @@ -92,6 +103,61 @@ class CatalogDataStore { return this.catalogs || (this.catalogs = []); } + saveDirtyDescriptorsToSessionStorage() { + const dirtyCatalogs = this.catalogs.reduce((result, catalog) => { + const dirtyDescriptors = catalog.descriptors.reduce((result, descriptor) => { + if (descriptor.uiState.modified && !descriptor.uiState.deleted) { + result.push(descriptor); + } + return result; + }, []); + if (dirtyDescriptors.length) { + let newCatalog = Object.assign({}, catalog); + newCatalog.descriptors = dirtyDescriptors; + result.push(newCatalog); + } + return result; + }, []); + window.sessionStorage.setItem(this.userProfile.userId + '@' + this.userProfile.domain, JSON.stringify({ + dirtyCatalogs + })); + } + + mergeDirtyDescriptorsFromSessionStorage(catalogs) { + let userProfileDirtyCatalogs = window.sessionStorage.getItem(this.userProfile.userId + '@' + this.userProfile.domain); + let dirtyCatalogs = []; + if (userProfileDirtyCatalogs) { + dirtyCatalogs = JSON.parse(userProfileDirtyCatalogs).dirtyCatalogs; + } + dirtyCatalogs.forEach((dirtyCatalog) => { + let catalog = catalogs.find((c) => c.id === dirtyCatalog.id); + dirtyCatalog.descriptors.forEach((dirtyDescriptor, index) => { + let descriptor = catalog.descriptors.find((d) => d.id === dirtyDescriptor.id); + if (descriptor) { + this.addSnapshot(descriptor); + _merge(descriptor, dirtyDescriptor); + } else { + dirtyCatalog.descriptors.splice(index, 1); + this.queueDirtyCheck(); + } + }) + }); + this.isNotMergedWithSessionStorage = false; + return catalogs; + } + + setUserProfile = (userProfile) => { + if (!this.userProfile) { + this.userProfile = userProfile; + if (this.catalogs.length) { + const catalogs = this.mergeDirtyDescriptorsFromSessionStorage(this.catalogs); + this.setState({ catalogs }); + } else { + this.isNotMergedWithSessionStorage = true; + } + } + } + getTransientCatalogs() { return this.state.catalogs || (this.state.catalogs = []); } @@ -186,9 +252,8 @@ class CatalogDataStore { }); return catalog; }); - updatedCatalogs.filter(d => d.type === 'nsd').forEach(catalog => { + updatedCatalogs.filter(d => d.type === 'nsd').forEach(catalog => { catalog.descriptors = catalog.descriptors.map(descriptor => { - const instanceRefCount = parseInt(descriptor.uiState['instance-ref-count'], 10); if (descriptor['constituent-vnfd']) { descriptor.vnfd = descriptor['constituent-vnfd'].map(d => { const vnfdId = d['vnfd-id-ref']; @@ -196,12 +261,8 @@ class CatalogDataStore { if (!vnfd) { throw new ReferenceError('no VNFD found in the VNFD Catalog for the constituent-vnfd: ' + d); } - if (!isNaN(instanceRefCount) && instanceRefCount > 0) { - // this will notify user that this item cannot be updated when/if they make a change to it - vnfd.uiState['instance-ref-count'] = instanceRefCount; - } // create an instance of this vnfd to carry transient ui state properties - const instance = _.cloneDeep(vnfd); + const instance = _cloneDeep(vnfd); instance.uiState['member-vnf-index'] = d['member-vnf-index']; instance['vnf-configuration'] = d['vnf-configuration']; instance['start-by-default'] = d['start-by-default']; @@ -227,7 +288,7 @@ class CatalogDataStore { } return catalog; }); - this.setState({catalogs: catalogs}); + this.setState({ catalogs: catalogs }); } mergeEditsIntoLatestFromServer(catalogsFromServer = []) { @@ -278,30 +339,47 @@ class CatalogDataStore { loadCatalogsSuccess(context) { const fromServer = this.updateCatalogIndexes(context.data); - const catalogs = this.mergeEditsIntoLatestFromServer(fromServer); + let catalogs = this.mergeEditsIntoLatestFromServer(fromServer); + if (this.isNotMergedWithSessionStorage) { + catalogs = this.mergeDirtyDescriptorsFromSessionStorage(catalogs); + } this.setState({ catalogs: catalogs, isLoading: false }); } - deleteCatalogItemSuccess (response) { + deleteCatalogItemSuccess(response) { let catalogType = response.catalogType; let itemId = response.itemId; const catalogs = this.getCatalogs().map(catalog => { if (catalog.type === catalogType) { - catalog.descriptors = catalog.descriptors.filter(d => d.id !== itemId); + catalog.descriptors = catalog.descriptors.map(d => { + // We are just going to mark it as deleted here so it will be hidden from view. + // We will let the next catalog refresh actually remove it from the in memory store. + // This is to avoid having it reappear because a timing issue with a catalog refresh. + // The incoming refresh may still contain the item and it would then reappear till the next refresh. + if (d.id === itemId) { + d.uiState.deleted = true; + const activeItem = ComposerAppStore.getState().item; + if (activeItem && activeItem.id === itemId) { + ComposerAppActions.showDescriptor.defer(); + CatalogItemsActions.editCatalogItem.defer(null); + } + } + return d; + }); } return catalog; }); - - this.setState({catalogs: catalogs}); + this.setState({ catalogs: catalogs }); + this.queueDirtyCheck(); } - deleteCatalogItemError (data) { + deleteCatalogItemError(data) { console.log('Unable to delete', data.catalogType, 'id:', data.itemId, 'Error:', data.error.responseText); ComposerAppActions.showError.defer({ - errorMessage: 'Unable to delete ' + data.catalogType + ' id: ' + data.itemId + '. Check if it is in use' + errorMessage: 'Unable to delete ' + data.catalogType + ' id: ' + data.itemId + '. Check to see if it is in use.' }); } @@ -311,34 +389,14 @@ class CatalogDataStore { catalogItemMetaDataChanged(item) { let requiresSave = false; - const catalogs = this.getCatalogs().map(catalog => { - if (catalog.id === item.uiState.catalogId) { - catalog.descriptors = catalog.descriptors.map(d => { - if (d.id === item.id) { - // compare just the catalog uiState data (id, name, short-name, description, etc.) - const modified = !areCatalogItemsMetaDataEqual(d, item); - if (modified) { - if (d.uiState['instance-ref-count'] > 0) { - console.log('cannot edit NSD/VNFD with references to instantiated Network Services'); - ComposerAppActions.showError.defer({ - errorMessage: 'Cannot edit NSD/VNFD with references to instantiated Network Services' - }); - return _.cloneDeep(d); - } else { - item.uiState.modified = modified; - requiresSave = true; - this.addSnapshot(item); - } - } - return item; - } - return d; - }); - } - return catalog; - }); - if (requiresSave) { - this.setState({catalogs: catalogs, requiresSave: true}); + let previousVersion = this.getLatestSnapshot(item); + // compare just the catalog uiState data + const modified = !areCatalogItemsMetaDataEqual(previousVersion, item); + if (modified) { + item.uiState.modified = true; + this.updateCatalogItem(item); + this.addSnapshot(item); + this.queueDirtyCheck(); } } @@ -352,24 +410,17 @@ class CatalogDataStore { // replace the old descriptor with the updated one catalog.descriptors = catalog.descriptors.map(d => { if (d.id === descriptorId) { - if (d.uiState['instance-ref-count'] > 0) { - console.log('cannot edit NSD/VNFD with references to instantiated Network Services'); - ComposerAppActions.showError.defer({ - errorMessage: 'Cannot edit NSD/VNFD with references to instantiated Network Services' - }); - return _.cloneDeep(d); - } else { - itemDescriptor.model.uiState.modified = true; - this.addSnapshot(itemDescriptor.model); - return itemDescriptor.model; - } + itemDescriptor.model.uiState.modified = true; + this.addSnapshot(itemDescriptor.model); + return itemDescriptor.model; } return d; }); } return catalog; }); - this.setState({catalogs: catalogs, requiresSave: true}) + this.setState({ catalogs: catalogs }) + this.queueDirtyCheck(); } deleteSelectedCatalogItem() { @@ -383,63 +434,21 @@ class CatalogDataStore { } deleteCatalogItem(item) { - const snapshot = JSON.stringify(item); - function confirmDeleteCancel(event) { - undo(); - event.preventDefault(); - ModalOverlayActions.hideModalOverlay(); - } - const remove = () => { - // item is deleted or does not exist on server, so remove from ui - this.removeCatalogItem(item); - this.setState({catalogs: this.getCatalogs()}); - const activeItem = ComposerAppStore.getState().item; - if (activeItem && activeItem.id === item.id) { - CatalogItemsActions.editCatalogItem.defer(null); - } - ModalOverlayActions.hideModalOverlay(); - }; - const undo = () => { - // item failed to delete on server so revert ui - const revertTo = JSON.parse(snapshot); - this.updateCatalogItem(revertTo); - const activeItem = ComposerAppStore.getState().item; - if (activeItem && activeItem.id === revertTo.id) { - SelectionManager.select(activeItem); - CatalogItemsActions.editCatalogItem.defer(revertTo); - SelectionManager.refreshOutline(); - } - }; if (item) { - if (item.uiState.isNew) { - CatalogDataStore.confirmDelete(remove, confirmDeleteCancel); - } else { - if (item.uiState['instance-ref-count'] > 0) { - console.log('cannot delete NSD/VNFD with references to instantiated Network Services'); - ComposerAppActions.showError.defer({ - errorMessage: 'Cannot delete NSD/VNFD with references to instantiated Network Services' + CatalogDataStore.confirmDelete(event => { + event.preventDefault(); + ModalOverlayActions.showModalOverlay.defer(); + this.getInstance().deleteCatalogItem(item.uiState.type, item.id) + .then(ModalOverlayActions.hideModalOverlay, ModalOverlayActions.hideModalOverlay) + .catch(function () { + console.log('overcoming ES6 unhandled rejection red herring'); }); - undo(); - } else { - const confirmDeleteOK = event => { - event.preventDefault(); - item.uiState.deleted = true; - this.setState({catalogs: this.getCatalogs()}); - ModalOverlayActions.showModalOverlay.defer(); - this.getInstance().deleteCatalogItem(item.uiState.type, item.id) - .then(remove, undo) - .then(ModalOverlayActions.hideModalOverlay, ModalOverlayActions.hideModalOverlay) - .catch(function() { - console.log('overcoming ES6 unhandled rejection red herring'); - }); - }; - CatalogDataStore.confirmDelete(confirmDeleteOK, confirmDeleteCancel); - } - } + }, ); } } static confirmDelete(onClickYes, onClickCancel) { + const cancelDelete = onClickCancel || (e => ModalOverlayActions.hideModalOverlay.defer()); ModalOverlayActions.showModalOverlay.defer((
@@ -447,7 +456,7 @@ class CatalogDataStore {
Yes, delete selected catalog item - No, cancel + No, cancel
)); @@ -459,27 +468,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); } } @@ -488,8 +480,28 @@ class CatalogDataStore { if (!this.snapshots[item.id]) { this.snapshots[item.id] = []; } + // save the snapshot with a new id for an in memory instance + let uid = UID.from(item); + UID.assignUniqueId(item); this.snapshots[item.id].push(JSON.stringify(item)); + UID.assignUniqueId(item, uid); + } + } + + getLatestSnapshot(item) { + if (this.snapshots[item.id]) { + return JSON.parse(this.snapshots[item.id][this.snapshots[item.id].length - 1]); } + this.getCatalogs().forEach(catalog => { + if (catalog.id === item.uiState.catalogId) { + catalog.descriptors.forEach(d => { + if (d.id === item.id) { + return d; + } + }); + } + }); + return {}; } resetSnapshots(item) { @@ -515,7 +527,7 @@ class CatalogDataStore { }); return catalog; }); - this.setState({catalogs: catalogs}); + this.setState({ catalogs: catalogs }); this.catalogItemMetaDataChanged(item); } } @@ -529,8 +541,8 @@ class CatalogDataStore { this.updateCatalogItem(revertTo); // TODO should the cancel action clear the undo/redo stack back to the beginning? this.resetSnapshots(revertTo); - this.setState({requiresSave: false}); CatalogItemsActions.editCatalogItem.defer(revertTo); + this.queueDirtyCheck(); } } } @@ -544,13 +556,6 @@ class CatalogDataStore { saveItem(item) { if (item) { - if (item.uiState['instance-ref-count'] > 0) { - console.log('cannot save NSD/VNFD with references to instantiated Network Services'); - ComposerAppActions.showError.defer({ - errorMessage: 'Cannot save NSD/VNFD with references to instantiated Network Services' - }); - return; - } const success = () => { delete item.uiState.modified; if (item.uiState.isNew) { @@ -563,6 +568,7 @@ class CatalogDataStore { this.resetSnapshots(item); ModalOverlayActions.hideModalOverlay.defer(); CatalogItemsActions.editCatalogItem.defer(item); + this.queueDirtyCheck(); }; const failure = () => { ModalOverlayActions.hideModalOverlay.defer(); @@ -579,61 +585,43 @@ class CatalogDataStore { } exportSelectedCatalogItems(draggedItem) { - const onSelectFormat = (selectedFormat, event) => { - this.setState({ - selectedFormat: selectedFormat - }); - }; - - const onSelectGrammar = (selectedGrammar, event) => { - this.setState({ - selectedGrammar: selectedGrammar - }); - } - - - const onCancel = () => { - this.resetSelectionState(); - ModalOverlayActions.hideModalOverlay(); - }; - - const onDownload = (event) => { + // collect the selected items and delegate to the catalog package manager action creator + const selectedItems = this.getAllSelectedCatalogItems(); + if (selectedItems.length) { CatalogPackageManagerActions.downloadCatalogPackage.defer({ selectedItems: selectedItems, - selectedFormat: this.selectedFormat, - selectedGrammar: this.selectedGrammar + selectedFormat: 'mano', + selectedGrammar: 'osm' }); this.resetSelectionState(); - ModalOverlayActions.hideModalOverlay(); - return; - } - - if (draggedItem) { - // if item is given make sure it is also selected - //draggedItem.uiState.selected = true; - SelectionManager.addSelection(draggedItem); - this.updateCatalogItem(draggedItem); - } - // collect the selected items and delegate to the catalog package manager action creator - const selectedItems = this.getAllSelectedCatalogItems(); - if (selectedItems.length) { - CatalogDataStore.chooseExportFormat(onSelectFormat, onSelectGrammar, onDownload, onCancel); } } - - static chooseExportFormat(onSelectFormat, onSelectGrammar, onDownload, onCancel) { - ModalOverlayActions.showModalOverlay.defer( - - ); + saveCatalogItemError(data) { + const descriptor = this.getCatalogs().reduce((gotIt, catalog) => { + if (!gotIt && (catalog.type === data.catalogType)) { + return catalog.descriptors.find(d => { + if (d.id === data.itemId) { + return d; + } + }); + } + return gotIt; + }, null); + const container = data.catalogType === 'nsd' ? + DescriptorModelFactory.newNetworkService(descriptor, null) + : DescriptorModelFactory.newVirtualNetworkFunction(descriptor, null) + ComposerAppActions.showError.defer({ + errorMessage: 'Unable to save the descriptor.', + rpcError: data.error.responseText + }); + ComposerAppActions.recordDescriptorError.defer({ + descriptor: container, + type: data.catalogType, + id: data.itemId, + error: data.error.responseText + }) } - } export default alt.createStore(CatalogDataStore, 'CatalogDataStore'); +