aboutsummaryrefslogtreecommitdiffstats
path: root/openecomp-ui/src/sdc-app/common/merge
diff options
context:
space:
mode:
authortalig <talig@amdocs.com>2017-12-20 14:30:43 +0200
committerVitaly Emporopulo <Vitaliy.Emporopulo@amdocs.com>2017-12-21 11:12:33 +0000
commit8e9c0653dd6c6862123c9609ae34e1206d86456e (patch)
tree5eeef00ec0677133baa439ca8d7ffd7aca4804b6 /openecomp-ui/src/sdc-app/common/merge
parent785ebcc95de3e064e843bec04ba7a209d854fc7c (diff)
Add collaboration feature
Issue-ID: SDC-767 Change-Id: I14fb4c1f54086ed03a56a7ff7fab9ecd40381795 Signed-off-by: talig <talig@amdocs.com>
Diffstat (limited to 'openecomp-ui/src/sdc-app/common/merge')
-rw-r--r--openecomp-ui/src/sdc-app/common/merge/MergeEditor.js37
-rw-r--r--openecomp-ui/src/sdc-app/common/merge/MergeEditorActionHelper.js443
-rw-r--r--openecomp-ui/src/sdc-app/common/merge/MergeEditorConstants.js224
-rw-r--r--openecomp-ui/src/sdc-app/common/merge/MergeEditorReducer.js66
-rw-r--r--openecomp-ui/src/sdc-app/common/merge/MergeEditorView.jsx256
5 files changed, 1026 insertions, 0 deletions
diff --git a/openecomp-ui/src/sdc-app/common/merge/MergeEditor.js b/openecomp-ui/src/sdc-app/common/merge/MergeEditor.js
new file mode 100644
index 0000000000..baf00cf0cf
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/common/merge/MergeEditor.js
@@ -0,0 +1,37 @@
+/*!
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+import {connect} from 'react-redux';
+import MergeEditorView from './MergeEditorView.jsx';
+import MergeEditorActionHelper from './MergeEditorActionHelper.js';
+
+export const mapStateToProps = ({mergeEditor, currentScreen}) => {
+ let {props} = currentScreen;
+ let item = {
+ id: props.softwareProductId || props.licenseModelId,
+ version: props.version
+ };
+ return {...mergeEditor, item, currentScreen};
+};
+
+export const mapActionsToProps = (dispatch) => {
+ return {
+ fetchConflict: ({cid, itemId, version}) => MergeEditorActionHelper.fetchConflict(dispatch, {itemId, version, cid}),
+ onResolveConflict: ({conflictId, resolution, itemId, version, currentScreen}) =>
+ MergeEditorActionHelper.resolveConflict(dispatch, {itemId, version, conflictId, resolution, currentScreen})
+ };
+};
+
+export default connect(mapStateToProps, mapActionsToProps)(MergeEditorView);
diff --git a/openecomp-ui/src/sdc-app/common/merge/MergeEditorActionHelper.js b/openecomp-ui/src/sdc-app/common/merge/MergeEditorActionHelper.js
new file mode 100644
index 0000000000..3885ee4051
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/common/merge/MergeEditorActionHelper.js
@@ -0,0 +1,443 @@
+/*!
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the 'License');
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an 'AS IS' BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+import {actionTypes, rules, dataRules, SyncStates} from './MergeEditorConstants.js';
+import cloneDeep from 'lodash/cloneDeep.js';
+import RestAPIUtil from 'nfvo-utils/RestAPIUtil.js';
+import Configuration from 'sdc-app/config/Configuration.js';
+import ItemsHelper from '../../common/helpers/ItemsHelper.js';
+import {modalContentMapper} from 'sdc-app/common/modal/ModalContentMapper.js';
+import {actionTypes as modalActionTypes} from 'nfvo-components/modal/GlobalModalConstants.js';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import {optionsInputValues as epOptionsValues} from 'sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsConstants.js';
+import {optionsInputValues as laOptionsValues} from 'sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementConstants.js';
+import {optionsInputValues as processOptionValues} from 'sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesConstants.js';
+import {selectValues as limitSelectValues} from 'sdc-app/onboarding/licenseModel/limits/LimitEditorConstants.js';
+import FeatureGroupsActionHelper from 'sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupsActionHelper.js';
+import LicenseAgreementActionHelper from 'sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementActionHelper.js';
+import moment from 'moment';
+import {DATE_FORMAT} from 'sdc-app/onboarding/OnboardingConstants.js';
+import ScreensHelper from 'sdc-app/common/helpers/ScreensHelper.js';
+
+function softwareProductCategoriesUrl() {
+ const restATTPrefix = Configuration.get('restATTPrefix');
+ return `${restATTPrefix}/v1/categories/resources/`;
+}
+
+function versionUrl(itemId, versionId) {
+ const restPrefix = Configuration.get('restPrefix');
+ return `${restPrefix}/v1.0/items/${itemId}/versions/${versionId}`;
+}
+
+function baseUrl(itemId, version, conflictId) {
+ const versionId = version.id;
+ const restPrefix = Configuration.get('restPrefix');
+ let baseUrl = `${restPrefix}/v1.0/items/${itemId}/versions/${versionId}/conflicts`;
+ return conflictId ? `${baseUrl}/${conflictId}` : baseUrl;
+}
+
+function fetchConflicts({itemId, version}) {
+ return RestAPIUtil.fetch(`${baseUrl(itemId, version)}`);
+}
+
+function fetchConflictById({itemId, version, cid}) {
+ return RestAPIUtil.fetch(`${baseUrl(itemId, version, cid)}`);
+}
+
+function resolveConflict({itemId, version, conflictId, resolution}) {
+ return RestAPIUtil.put(`${baseUrl(itemId, version, conflictId)}`, {resolution});
+}
+
+function fetchCategories() {
+ return RestAPIUtil.fetch(softwareProductCategoriesUrl());
+}
+
+function fetchVersion({vendorId, licensingVersion}) {
+ return RestAPIUtil.fetch(versionUrl(vendorId, licensingVersion));
+}
+
+function createCategoryStr(data, {categories}) {
+
+ let {category, subCategory} = data;
+ let foundCat = categories.find(element => element.uniqueId === category);
+ if (!foundCat) { return ''; }
+
+ let catName = foundCat.name;
+ let foundSub = foundCat.subcategories.find(element => element.uniqueId === subCategory);
+ if (!foundSub) { return `${catName}`; }
+
+ let subcatName = foundSub.name;
+ return `${catName} - ${subcatName}`;
+
+}
+
+function getEnumValues({enums, list}) {
+
+ if (!list) { return ''; }
+ return list.map(item => enums.find(el => el.enum === item).title);
+
+}
+
+const MergeEditorActionHelper = {
+
+ analyzeSyncResult(dispatch, {itemId, version}) {
+ return ItemsHelper.checkItemStatus(dispatch, {itemId, versionId: version.id}).then((response) => {
+ let inMerge = response && response.state && response.state.synchronizationState === SyncStates.MERGE;
+ if (inMerge) {
+ MergeEditorActionHelper.fetchConflicts(dispatch, {itemId, version}).then(() =>
+ dispatch({
+ type: modalActionTypes.GLOBAL_MODAL_SHOW,
+ data: {
+ modalComponentName: modalContentMapper.MERGE_EDITOR,
+ modalClassName: 'merge-editor-modal',
+ title: `${i18n('Merge Required')} - ${version.description}`,
+ onDeclined: () => {
+ dispatch({
+ type: modalActionTypes.GLOBAL_MODAL_CLOSE
+ });
+ },
+ modalComponentProps: {
+ size: 'lg',
+ type: 'default'
+ }
+ }
+ })
+ );
+ }
+ return Promise.resolve({updatedVersion: response, inMerge, isDirty: response.state.dirty});
+ });
+ },
+
+ fetchConflicts(dispatch, {itemId, version}) {
+ return fetchConflicts({itemId, version}).then(
+ (data) => {
+ dispatch({
+ type: actionTypes.LOAD_CONFLICTS,
+ data
+ });
+ return data;
+ }
+ );
+ },
+
+ fetchConflict(dispatch, {itemId, version, cid}) {
+ fetchConflictById({itemId, version, cid}).then(
+ (data) => {
+ let newData = {};
+ newData = MergeEditorActionHelper.processConflict(dispatch, {conflict: data, itemId, cid, version});
+ dispatch({
+ type: actionTypes.LOAD_CONFLICT,
+ data: newData
+ });
+ }
+ );
+ },
+
+ resolveConflict(dispatch, {itemId, version, conflictId, resolution, currentScreen}) {
+ resolveConflict({itemId, version, conflictId, resolution}).then(() => {
+ MergeEditorActionHelper.fetchConflicts(dispatch, {itemId, version}).then(conflicts => {
+ if(conflicts.conflictInfoList && conflicts.conflictInfoList.length === 0) {
+ dispatch({
+ type: modalActionTypes.GLOBAL_MODAL_CLOSE
+ });
+ ScreensHelper.loadLandingScreen(dispatch, {previousScreenName: currentScreen.screen, props: currentScreen.props});
+ ItemsHelper.checkItemStatus(dispatch, {itemId, versionId: version.id});
+ }
+ });
+ });
+ },
+
+ createConflictObject(data, {cid, conflict, dispatch, itemId, version, isYours}) {
+
+ let newData = {};
+
+ for (let key in data) {
+
+ if (data.hasOwnProperty(key)) {
+ let value = data[key];
+ let fieldRule = dataRules[conflict.type] && dataRules[conflict.type][key] || dataRules.general[key];
+
+ if (fieldRule) {
+ switch (fieldRule.rule) {
+
+ case rules.SKIP:
+ break;
+
+ case rules.BOOLEAN:
+ let {trueValue, falseValue} = fieldRule;
+ newData[key] = value === trueValue ? true : value === falseValue ? false : undefined;
+ break;
+
+ case rules.PARSE:
+ let {moveFields, subFields} = fieldRule;
+ if (moveFields) {
+ let fields = subFields || Object.keys(value);
+ fields.forEach(field => {
+ newData[field] = MergeEditorActionHelper.createConflictObject(
+ value[field], {cid, conflict, dispatch, itemId, version, isYours}
+ );
+ });
+ } else {
+ newData[key] = MergeEditorActionHelper.createConflictObject(
+ value, {cid, conflict, dispatch, itemId, version, isYours}
+ );
+ }
+ break;
+
+ case rules.FUNCTION:
+ let {args, functionName} = fieldRule;
+ newData[key] = MergeEditorActionHelper[functionName](data, {
+ cid, conflict, dispatch, version, fieldName: key, isYours, itemId, args
+ });
+ break;
+
+ default:
+ newData[key] = value;
+ break;
+ }
+
+ } else {
+ newData[key] = value;
+
+ }
+ }
+ }
+
+ return newData;
+
+ },
+
+ getNamesFromIDs(data, {version, cid, dispatch, itemId, fieldName, isYours, args}) {
+
+ let idList = data[fieldName] || [];
+ let {fetchFunction, fetchField} = args;
+
+ let promises = idList.map(id =>
+ new Promise(resolve =>
+ MergeEditorActionHelper[fetchFunction](
+ dispatch, {licenseModelId: itemId, [fetchField]: id, version}
+ ).then(item => resolve(item.name))
+ )
+ );
+
+ Promise.all(promises).then(fetchedItems => {
+ let yoursOrTheirs = isYours ? 'yoursField' : 'theirsField';
+ dispatch({
+ type: actionTypes.DATA_PROCESSED,
+ data: {
+ cid,
+ [yoursOrTheirs]: { name: fieldName, value: fetchedItems }
+ }
+ });
+ });
+
+ return idList;
+
+ },
+
+ getFeatureGroups(data, {version, cid, dispatch, itemId, fieldName, isYours}) {
+
+ let featureGroups = data[fieldName] || [];
+ if (!(featureGroups instanceof Array)) {
+ featureGroups = [featureGroups];
+ }
+
+ let promises = featureGroups.map(featureGroupId =>
+ new Promise(resolve =>
+ FeatureGroupsActionHelper.fetchFeatureGroup(
+ dispatch, {licenseModelId: itemId, featureGroupId, version}
+ ).then(featureGroup => resolve(featureGroup.name))
+ .catch(reason => console.log(`getFeatureGroups Promise rejected ('${reason}')`))
+ )
+ );
+
+ Promise.all(promises).then(fetchedGroups => {
+ let yoursOrTheirs = isYours ? 'yoursField' : 'theirsField';
+ dispatch({
+ type: actionTypes.DATA_PROCESSED,
+ data: {
+ cid,
+ [yoursOrTheirs]: { name: fieldName, value: fetchedGroups }
+ }
+ });
+ });
+
+ return featureGroups;
+
+ },
+
+ getLicenseAgreements(data, {version, cid, dispatch, itemId, fieldName, isYours}) {
+
+ let licenseAgreements = data[fieldName] || [];
+ if (!(licenseAgreements instanceof Array)) {
+ licenseAgreements = [licenseAgreements];
+ }
+
+ let promises = licenseAgreements.map(licenseAgreementId =>
+ new Promise(resolve =>
+ LicenseAgreementActionHelper.fetchLicenseAgreement(
+ dispatch, {licenseModelId: itemId, licenseAgreementId, version}
+ ).then(licenseAgreement => resolve(licenseAgreement.name))
+ .catch(reason => console.log(`getLicenseAgreements Promise rejected ('${reason}')`))
+ )
+ );
+
+ Promise.all(promises).then(fetchedAgreements => {
+ let yoursOrTheirs = isYours ? 'yoursField' : 'theirsField';
+ dispatch({
+ type: actionTypes.DATA_PROCESSED,
+ data: {
+ cid,
+ [yoursOrTheirs]: { name: fieldName, value: fetchedAgreements }
+ }
+ });
+ });
+
+ return licenseAgreements;
+
+ },
+
+ processConflict(dispatch, {conflict, cid, version, itemId,}) {
+
+ let {id, type, yours, theirs} = conflict;
+
+ let newYours = MergeEditorActionHelper.createConflictObject(
+ cloneDeep(yours), {cid, conflict, dispatch, itemId, version, isYours: true}
+ );
+ let newTheirs = MergeEditorActionHelper.createConflictObject(
+ cloneDeep(theirs), {cid, conflict, dispatch, itemId, version, isYours: false}
+ );
+
+ return {
+ id,
+ type,
+ yours: newYours,
+ theirs: newTheirs
+ };
+
+ },
+
+ reduceList(data, {fieldName, args}) {
+
+ let {subField} = args;
+ return data[fieldName].map(el => el[subField]);
+
+ },
+
+ getEnumList({fieldName}) {
+
+ const enumLists = {
+ 'licenseTerm': laOptionsValues.LICENSE_MODEL_TYPE,
+ 'operationalScope': epOptionsValues.OPERATIONAL_SCOPE,
+ 'processType': processOptionValues.PROCESS_TYPE,
+ 'limitType': [
+ {title: 'Service Provider', enum: 'ServiceProvider'},
+ {title: 'Vendor', enum: 'Vendor'}
+ ],
+ 'limitUnit': limitSelectValues.UNIT
+ };
+
+ return enumLists[fieldName];
+
+ },
+
+ getEnumValue(data, {fieldName, args = {}}) {
+
+ let value = data[fieldName];
+ let enumValues = MergeEditorActionHelper.getEnumList({fieldName: args.listName || fieldName});
+ let enumValue = enumValues.find(el => el.enum === value);
+
+ return enumValue && enumValue.title || value;
+
+ },
+
+ processChoice(data, {fieldName, args = {}}) {
+
+ let value = data[fieldName];
+ let enumValues = MergeEditorActionHelper.getEnumList({fieldName: args.listName || fieldName});
+ let newValue = value.other || enumValues && enumValues.find(el => el.enum === value.choice).title || value.choice;
+
+ return newValue;
+
+ },
+
+ processChoices(data, {fieldName, args = {}}) {
+
+ let value = data[fieldName];
+ let enumValues = MergeEditorActionHelper.getEnumList({fieldName: args.listName || fieldName});
+ let newValue = value.other || getEnumValues({enums: enumValues, list: value.choices}) || value.choices;
+
+ return newValue;
+
+ },
+
+ convertArrayToObject(data, {fieldName}) {
+ let value = data[fieldName];
+ let newValue = {};
+ value.forEach((el, index) => {
+ newValue[index] = el;
+ });
+ return newValue;
+ },
+
+ fetchCategory(data, {cid, isYours, fieldName, dispatch}) {
+
+ fetchCategories().then((categories) => {
+ let value = createCategoryStr(data, {categories});
+ let yoursOrTheirs = isYours ? 'yoursField' : 'theirsField';
+
+ dispatch({
+ type: actionTypes.DATA_PROCESSED,
+ data: {
+ cid,
+ [yoursOrTheirs]: { name: fieldName, value }
+ }
+ });
+
+ });
+ },
+
+ fetchLMVersion(data, {cid, dispatch, isYours}) {
+
+ let {licensingVersion, vendorId} = data;
+ let yoursOrTheirs = isYours ? 'yoursField' : 'theirsField';
+
+ if (licensingVersion) {
+ fetchVersion({licensingVersion, vendorId}).then(response => {
+ dispatch({
+ type: actionTypes.DATA_PROCESSED,
+ data: {
+ cid,
+ [yoursOrTheirs]: {
+ name: 'licensingVersion',
+ value: response.name
+ }
+ }
+ });
+ });
+ }
+
+ },
+
+ parseDate(data, {fieldName}) {
+
+ let date = data[fieldName];
+ return date && moment(date, DATE_FORMAT).format(DATE_FORMAT);
+
+ }
+
+};
+
+export default MergeEditorActionHelper;
diff --git a/openecomp-ui/src/sdc-app/common/merge/MergeEditorConstants.js b/openecomp-ui/src/sdc-app/common/merge/MergeEditorConstants.js
new file mode 100644
index 0000000000..f7f6d4195e
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/common/merge/MergeEditorConstants.js
@@ -0,0 +1,224 @@
+/*!
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import keyMirror from 'nfvo-utils/KeyMirror.js';
+
+export const actionTypes = keyMirror({
+ LOAD_CONFLICTS: null,
+ ADD_ACTIONS: null,
+ LOAD_CONFLICT: null,
+ DATA_PROCESSED: null
+});
+
+export const rules = {
+ SKIP: 'skip',
+ PARSE: 'parse',
+ FUNCTION: 'function',
+ BOOLEAN: 'boolean'
+};
+
+export const SyncStates = {
+ MERGE : 'Merging',
+ OUT_OF_SYNC: 'OutOfSync',
+ UP_TO_DATE: 'UpToDate'
+};
+
+export const ResolutionTypes = {
+ YOURS: 'YOURS',
+ THEIRS: 'THEIRS'
+};
+
+export const fileTypes = {
+ LKG : 'LicenseKeyGroup',
+ VLM : 'VendorLicenseModel',
+ EP : 'EntitlementPool',
+ FG : 'FeatureGroup',
+ LA : 'LicenseAgreement',
+ VSP : 'VendorSoftwareProduct',
+ LIMIT : 'Limit',
+ VSP_Q : 'VSPQuestionnaire',
+ COMPONENT : 'Component',
+ COMPONENT_Q : 'ComponentQuestionnaire',
+ COMPONENT_DEP : 'ComponentDependencies',
+ COMPUTE_Q : 'ComputeQuestionnaire',
+ COMPUTE : 'Compute',
+ COMPUTE_FLAVOR: 'ComputeFlavor',
+ NIC : 'Nic',
+ NIC_Q : 'NicQuestionnaire',
+ IMAGE : 'Image',
+ IMAGE_Q : 'ImageQuestionnaire',
+ PROCESS : 'Process',
+ DEPLOYMENT_FLAVOR : 'DeploymentFlavor',
+ VENDOR : 'Vendor',
+ NETWORK : 'Network',
+ ORCHESTRATION_TEMPLATE_CANDIDATE : 'OrchestrationTemplateCandidate'
+};
+
+export const dataRules = {
+ general: {
+ id: {
+ rule: rules.SKIP
+ },
+ questionareData: {
+ rule: rules.PARSE,
+ moveFields: true
+ },
+ startDate: {
+ rule: rules.FUNCTION,
+ functionName: 'parseDate'
+ },
+ expiryDate: {
+ rule: rules.FUNCTION,
+ functionName: 'parseDate'
+ },
+ featureGroups: {
+ rule: rules.FUNCTION,
+ functionName: 'reduceList',
+ args: {subField: 'name'}
+ },
+ licenseKeyGroups: {
+ rule: rules.FUNCTION,
+ functionName: 'reduceList',
+ args: {subField: 'name'}
+ },
+ entitlementPools: {
+ rule: rules.FUNCTION,
+ functionName: 'reduceList',
+ args: {subField: 'name'}
+ },
+ },
+ [fileTypes.COMPONENT] : {
+ },
+ [fileTypes.COMPUTE_FLAVOR] : {
+ associatedToDeploymentFlavor: {
+ rule: rules.BOOLEAN,
+ trueValue: 'true'
+ }
+ },
+ [fileTypes.COMPUTE_Q] : {
+ },
+ [fileTypes.COMPONENT_Q] : {
+ isComponentMandatory: {
+ rule: rules.BOOLEAN,
+ trueValue: 'YES',
+ falseValue: 'NO'
+ }
+ },
+ [fileTypes.EP] : {
+ referencingFeatureGroups: {
+ rule: rules.SKIP,
+ functionName: 'getFeatureGroups'
+ },
+ operationalScope: {
+ rule: rules.FUNCTION,
+ functionName: 'processChoices'
+ },
+ },
+ [fileTypes.FG] : {
+ referencingLicenseAgreements: {
+ rule: rules.SKIP,
+ functionName: 'getLicenseAgreements'
+ }
+ },
+ [fileTypes.LA] : {
+ licenseTerm : {
+ rule: rules.FUNCTION,
+ functionName: 'processChoice'
+ }
+ },
+ [fileTypes.LIMIT] : {
+ type: {
+ rule: rules.FUNCTION,
+ functionName: 'getEnumValue',
+ args: {listName: 'limitType'}
+ },
+ unit: {
+ rule: rules.FUNCTION,
+ functionName: 'getEnumValue',
+ args: {listName: 'limitUnit'}
+ }
+ },
+ [fileTypes.LKG] : {
+ operationalScope: {
+ rule: rules.FUNCTION,
+ functionName: 'processChoices'
+ },
+ referencingFeatureGroups: {
+ rule: rules.SKIP,
+ functionName: 'getFeatureGroups'
+ },
+ },
+ [fileTypes.NIC] : {
+ networkId: {
+ rule: rules.SKIP
+ }
+ },
+ [fileTypes.NIC_Q] : {
+ },
+ [fileTypes.PROCESS] : {
+ type: {
+ rule: rules.FUNCTION,
+ functionName: 'getEnumValue',
+ args: {listName: 'processType'}
+ }
+ },
+ [fileTypes.VLM] : {
+ iconRef: {
+ rule: rules.SKIP
+ }
+ },
+ [fileTypes.VSP] : {
+ vendorId: {
+ rule: rules.SKIP
+ },
+ onboardingMethod: {
+ rule: rules.SKIP
+ },
+ validationData: {
+ rule: rules.SKIP
+ },
+ isOldVersion: {
+ rule: rules.SKIP
+ },
+ licensingVersion: {
+ rule: rules.FUNCTION,
+ functionName: 'fetchLMVersion'
+ },
+ category: {
+ rule: rules.FUNCTION,
+ functionName: 'fetchCategory'
+ },
+ subCategory: {
+ rule: rules.SKIP
+ },
+ },
+ [fileTypes.VSP_Q] : {
+ affinityData: {
+ rule: rules.SKIP
+ },
+ storageReplicationAcrossRegion: {
+ rule: rules.BOOLEAN,
+ trueValue: 'true',
+ falseValue: 'false'
+ }
+ },
+ [fileTypes.ORCHESTRATION_TEMPLATE_CANDIDATE] : {
+ modules: {
+ rule: rules.FUNCTION,
+ functionName: 'convertArrayToObject'
+ },
+ },
+};
diff --git a/openecomp-ui/src/sdc-app/common/merge/MergeEditorReducer.js b/openecomp-ui/src/sdc-app/common/merge/MergeEditorReducer.js
new file mode 100644
index 0000000000..6985fcfaca
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/common/merge/MergeEditorReducer.js
@@ -0,0 +1,66 @@
+/*!
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the 'License');
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an 'AS IS' BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+import {actionTypes} from './MergeEditorConstants.js';
+
+export default (state = [], action) => {
+ switch (action.type) {
+ case actionTypes.LOAD_CONFLICT: {
+ let cdata = {...action.data};
+ // let data = state.conflicts ? {...state.conflicts.data} : {} ;
+ // data[cdata.id] = cdata;
+ let conflicts = state.conflicts ? {...state.conflicts} : {};
+ conflicts[cdata.id] = cdata;
+ return {
+ ...state,
+ conflicts
+ };
+ }
+ case actionTypes.DATA_PROCESSED: {
+ let conflicts = {...state.conflicts};
+ let {data} = action;
+ if (data && data.cid) {
+ let yours = {...conflicts[data.cid].yours};
+ let theirs = {...conflicts[data.cid].theirs};
+ let {yoursField, theirsField} = data;
+ if (yoursField) {
+ yours[yoursField.name] = yoursField.value;
+ conflicts[data.cid].yours = yours;
+ }
+ if (theirsField) {
+ theirs[theirsField.name] = theirsField.value;
+ conflicts[data.cid].theirs = theirs;
+ }
+ }
+ return {
+ ...state,
+ conflicts: {
+ ...conflicts
+ }
+ };
+ }
+ case actionTypes.LOAD_CONFLICTS:
+ let conflictFiles = [];
+ if (action.data) {
+ conflictFiles = [...action.data.conflictInfoList];
+ }
+ return {
+ inMerge: conflictFiles.length > 0,
+ conflictFiles
+ };
+ default:
+ return state;
+ }
+};
diff --git a/openecomp-ui/src/sdc-app/common/merge/MergeEditorView.jsx b/openecomp-ui/src/sdc-app/common/merge/MergeEditorView.jsx
new file mode 100644
index 0000000000..34d86419e7
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/common/merge/MergeEditorView.jsx
@@ -0,0 +1,256 @@
+/*!
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+import React from 'react';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import union from 'lodash/union.js';
+import Button from 'sdc-ui/lib/react/Button.js';
+// import Checkbox from 'sdc-ui/lib/react/Checkbox.js';
+import Input from 'nfvo-components/input/validation/Input.jsx';
+import GridSection from 'nfvo-components/grid/GridSection.jsx';
+import GridItem from 'nfvo-components/grid/GridItem.jsx';
+import SVGIcon from 'sdc-ui/lib/react/SVGIcon.js';
+import Radio from 'sdc-ui/lib/react/Radio.js';
+import equal from 'deep-equal';
+import {ResolutionTypes} from './MergeEditorConstants.js';
+
+class ConflictCategory extends React.Component {
+ state = {
+ resolution: ResolutionTypes.YOURS
+ };
+
+ getTitle(conflictType, conflictName) {
+ if (typeof conflictName === 'undefined' || conflictType === conflictName) {
+ return i18n(conflictType);
+ } else {
+ return `${i18n(conflictType)}: ${conflictName}`;
+ }
+ }
+
+ render() {
+ let {collapseExpand, conflict: {id: conflictId, type, name}, isCollapsed, item: {id: itemId, version}, onResolveConflict} = this.props;
+ let {resolution} = this.state;
+ const iconClass = isCollapsed ? 'merge-chevron' : 'merge-chevron right';
+
+ return (
+ <div key={'conflictCategory_' + conflictId} >
+ <GridSection className='conflict-section'>
+ <GridItem >
+ <div className='collapsible-section' onClick={collapseExpand}>
+ <SVGIcon name={isCollapsed ? 'chevronDown' : 'chevronUp'} iconClassName={iconClass} />
+ <div className='conflict-title'>{this.getTitle(type, name)}</div>
+ </div>
+ </GridItem>
+ <GridItem className='yours'>
+ <Radio name={'radio_' + conflictId} checked={resolution === ResolutionTypes.YOURS} value='yours'
+ onChange={() => this.setState({resolution: ResolutionTypes.YOURS})} data-test-id={'radio_' + conflictId + '_yours'} />
+ </GridItem>
+ <GridItem className='theirs'>
+ <Radio name={'radio_' + conflictId} checked={resolution === ResolutionTypes.THEIRS} value='theirs'
+ onChange={() => this.setState({resolution: ResolutionTypes.THEIRS})} data-test-id={'radio_' + conflictId + '_theirs'} /></GridItem>
+ <GridItem className='resolve'>
+ <Button className='conflict-resolve-btn' btnType='outline' color='gray'
+ onClick={() => onResolveConflict({conflictId, resolution, itemId, version})}>
+ {i18n('Resolve')}
+ </Button>
+ </GridItem>
+ </GridSection>
+ <div>
+ {isCollapsed && this.props.children}
+ </div>
+ </div>
+ );
+ }
+
+};
+
+class TextCompare extends React.Component {
+ render() {
+ // let rand = Math.random() * (3000 - 1) + 1;
+ let {yours, theirs, field, type, isObjName, conflictsOnly} = this.props;
+ let typeYours = typeof yours;
+ let typeTheirs = typeof theirs;
+
+ let parsedType = `${type}/${field}`.replace(/\/[0-9]+/g,'/index');
+ let level = type.split('/').length;
+
+ if (typeYours === 'boolean' || typeTheirs === 'boolean') {
+ yours = yours ? i18n('Yes') : i18n('No');
+ theirs = theirs ? i18n('Yes') : i18n('No');
+ }
+
+
+ /*if ((typeYours !== 'string' && typeYours !== 'undefined') || (typeTheirs !== 'string' && typeTheirs !== 'undefined')) {
+ return (<div className='merge-editor-text-field field-error'>{field} cannot be parsed for display</div>);
+ }*/
+ let isDiff = yours !== theirs;
+ if (!isObjName &&
+ ((!isDiff && conflictsOnly) ||
+ (yours === '' && theirs === '') ||
+ (typeYours === 'undefined' && typeTheirs === 'undefined')
+ )
+ ) {
+ return null;
+ }
+
+ return (
+ <GridSection className={isDiff ? 'merge-editor-text-field diff' : 'merge-editor-text-field'}>
+ <GridItem className='field-col grid-col-title' stretch>
+ <div className={`field ${isDiff ? 'diff' : ''} field-name level-${level} ${isObjName ? 'field-object-name' : ''}`}>
+ {i18n(parsedType)}
+ </div>
+ </GridItem>
+ <GridItem className='field-col grid-col-yours' stretch>
+ <div className={`field field-yours ${!yours ? 'empty-field' : ''}`} >{yours || (isObjName ? '' : '━━')}</div>
+ </GridItem>
+ <GridItem className='field-col grid-col-theirs' stretch>
+ <div className={`field field-theirs ${!theirs ? 'empty-field' : ''}`}>{theirs || (isObjName ? '' : '━━')}</div>
+ </GridItem>
+ <GridItem stretch/>
+ </GridSection>
+ );
+ }
+};
+
+class MergeEditorView extends React.Component {
+ state = {
+ collapsingSections: {},
+ conflictsOnly: false
+ };
+
+ render() {
+ let {conflicts, item, conflictFiles, onResolveConflict, currentScreen, resolution} = this.props;
+
+ return (
+ <div className='merge-editor'>
+ {conflictFiles && this.renderConflictTableTitles()}
+ <div className='merge-editor-body'>
+ {conflictFiles && conflictFiles.sort((a, b) => a.type > b.type).map(file => (
+ <ConflictCategory key={'conflict_' + file.id} conflict={file} item={item} isCollapsed={this.state.collapsingSections[file.id]}
+ collapseExpand={()=>{this.updateCollapseState(file.id);}}
+ onResolveConflict={cDetails => onResolveConflict({...cDetails, currentScreen})}>
+ {(conflicts && conflicts[file.id]) &&
+ this.getUnion(conflicts[file.id].yours, conflicts[file.id].theirs).map(field => {
+ return this.renderField(field, file, conflicts[file.id].yours[field], conflicts[file.id].theirs[field], resolution);
+ })}
+ </ConflictCategory>))}
+ </div>
+ </div>);
+ }
+
+ renderConflictTableTitles()
+ {
+ return (<GridSection className='conflict-titles-section'>
+ <GridItem>
+ {i18n('Page')}
+ </GridItem>
+ <GridItem className='yours'>
+ {i18n('Local (Me)')}
+ </GridItem>
+ <GridItem className='theirs'>
+ {i18n('Last Committed')}
+ </GridItem>
+ <GridItem className='resolve'>
+ <Input
+ label={i18n('Show Conflicts Only')}
+ type='checkbox'
+ value={this.state.conflictsOnly}
+ onChange={e => this.setState({conflictsOnly: e}) } />
+ </GridItem>
+ </GridSection>);
+ }
+ // <Checkbox
+ // label={i18n('Show Conflicts Only')}
+ // value={this.state.conflictsOnly}
+ // checked={this.state.conflictsOnly}
+ // onChange={checked => this.setState({conflictsOnly: checked})} />
+
+ renderObjects(yours, theirs, fileType, field, id, resolution) {
+ if (equal(yours, theirs)) {
+ return;
+ }
+ let {conflictsOnly} = this.state;
+ return (
+ <div key={`obj_${fileType}/${field}_${id}`}>
+ <TextCompare field={field} type={fileType} conflictsOnly={conflictsOnly} yours='' theirs='' isObjName resolution={resolution} />
+ <div className='field-objects'>
+ <div>
+ {this.getUnion(yours, theirs).map(key =>
+ this.renderField(
+ key,
+ {type: `${fileType}/${field}`, id},
+ yours && yours[key],
+ theirs && theirs[key]
+ )
+ )}
+ </div>
+ </div>
+ </div>
+ );
+ }
+
+ renderList(yours = [], theirs = [], type, field, id, resolution) {
+ let theirsList = theirs.join(', ');
+ let yoursList = yours.join(', ');
+ let {conflictsOnly} = this.state;
+ return (<TextCompare key={'text_' + id + '_' + field}
+ field={field} type={type} yours={yoursList} theirs={theirsList} conflictsOnly={conflictsOnly} resolution={resolution} />);
+ }
+
+ renderField(field, file, yours, theirs, resolution) {
+ if (yours) {
+ if (Array.isArray(yours)) {
+ return this.renderList(yours, theirs, file.type, field, file.id, resolution);
+ }
+ else if (typeof yours === 'object') {
+ return this.renderObjects(yours, theirs, file.type, field, file.id, resolution);
+ }
+ } else if (theirs) {
+ if (Array.isArray(theirs)) {
+ return this.renderList(yours, theirs, file.type, field, file.id, resolution);
+ }
+ else if (typeof theirs === 'object') {
+ return this.renderObjects(yours, theirs, file.type, field, file.id, resolution);
+ }
+ }
+ let {conflictsOnly} = this.state;
+ return (<TextCompare key={'text_' + file.id + '_' + field} resolution={resolution}
+ field={field} type={file.type} yours={yours} theirs={theirs} conflictsOnly={conflictsOnly} />);
+ }
+
+ getUnion(yours = {},theirs = {}) {
+ let yoursKeys = Object.keys(yours);
+ let theirsKeys = Object.keys(theirs);
+ let myUn = union(yoursKeys, theirsKeys);
+ return myUn;//.sort((a, b) => a > b);
+ }
+
+ updateCollapseState(conflictId) {
+ const {fetchConflict, item: {id: itemId, version}, /*conflicts*/} = this.props;
+ let isCollapsed = this.state.collapsingSections[conflictId];
+ // if (!isCollapsed && !(conflicts && conflictId in conflicts)) {
+ if (!isCollapsed) {
+ fetchConflict({cid: conflictId, itemId, version});
+ }
+ this.setState({
+ collapsingSections: {
+ ...this.state.collapsingSections,
+ [conflictId]: !isCollapsed
+ }
+ });
+ }
+}
+
+export default MergeEditorView;