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=144332cb61a1ccafc1541174b8e6e97077a42b4e;hb=refs%2Fchanges%2F79%2F5479%2F2;hpb=e29efc315df33d546237e270470916e26df391d6 diff --git a/skyquake/plugins/composer/src/src/stores/CatalogDataStore.js b/skyquake/plugins/composer/src/src/stores/CatalogDataStore.js index 144332cb6..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,25 +46,52 @@ 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) { + let newItem = DescriptorModelMetaFactory.createModelInstanceForType(type); + if (newItem) { + newItem.id = guid(); + UID.assignUniqueId(newItem); + newItem.uiState.isNew = true; + newItem.uiState.modified = true; + } + return newItem; +} + 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]; this.registerAsync(CatalogDataSource); 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, + setUserProfile: this.setUserProfile + }); + this.queueDirtyCheck = _debounce(() => this.saveDirtyDescriptorsToSessionStorage(), 500); } resetSelectionState = () => { @@ -72,6 +103,65 @@ 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 = []); + } + getAllSelectedCatalogItems() { return this.getCatalogs().reduce((r, d) => { d.descriptors.forEach(d => { @@ -95,12 +185,24 @@ class CatalogDataStore { }, [])[0]; } + getTransientCatalogItemById(id) { + return this.getTransientCatalogs().reduce((r, catalog) => { + return r.concat(catalog.descriptors.filter(d => d.id === id)); + }, [])[0]; + } + getCatalogItemByUid(uid) { return this.getCatalogs().reduce((r, catalog) => { return r.concat(catalog.descriptors.filter(d => UID.from(d) === uid)); }, [])[0]; } + getTransientCatalogItemByUid(uid) { + return this.getTransientCatalogs().reduce((r, catalog) => { + return r.concat(catalog.descriptors.filter(d => UID.from(d) === uid)); + }, [])[0]; + } + removeCatalogItem(deleteItem = {}) { this.getCatalogs().map(catalog => { catalog.descriptors = catalog.descriptors.filter(d => d.id !== deleteItem.id); @@ -109,16 +211,13 @@ class CatalogDataStore { } addNewItemToCatalog(newItem) { - const id = guid(); const type = newItem.uiState.type; - newItem.id = id; - UID.assignUniqueId(newItem.uiState); this.getCatalogs().filter(d => d.type === type).forEach(catalog => { catalog.descriptors.push(newItem); }); // update indexes and integrate new model into catalog this.updateCatalogIndexes(this.getCatalogs()); - return this.getCatalogItemById(id); + return this.getCatalogItemById(newItem.id); } updateCatalogIndexes(catalogs) { @@ -153,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']; @@ -163,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']; @@ -194,7 +288,7 @@ class CatalogDataStore { } return catalog; }); - this.setState({catalogs: catalogs}); + this.setState({ catalogs: catalogs }); } mergeEditsIntoLatestFromServer(catalogsFromServer = []) { @@ -245,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.' }); } @@ -278,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(); } } @@ -319,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() { @@ -350,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((