react 16 upgrade
[sdc.git] / openecomp-ui / src / sdc-app / common / merge / MergeEditorView.jsx
1 /*!
2  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
13  * or implied. See the License for the specific language governing
14  * permissions and limitations under the License.
15  */
16 import React from 'react';
17 import i18n from 'nfvo-utils/i18n/i18n.js';
18 import union from 'lodash/union.js';
19 import Button from 'sdc-ui/lib/react/Button.js';
20 // import Checkbox from 'sdc-ui/lib/react/Checkbox.js';
21 import Input from 'nfvo-components/input/validation/Input.jsx';
22 import GridSection from 'nfvo-components/grid/GridSection.jsx';
23 import GridItem from 'nfvo-components/grid/GridItem.jsx';
24 import SVGIcon from 'sdc-ui/lib/react/SVGIcon.js';
25 import Radio from 'sdc-ui/lib/react/Radio.js';
26 import equal from 'deep-equal';
27 import { ResolutionTypes } from './MergeEditorConstants.js';
28
29 class ConflictCategory extends React.Component {
30     state = {
31         resolution: ResolutionTypes.YOURS
32     };
33
34     getTitle(conflictType, conflictName) {
35         if (
36             typeof conflictName === 'undefined' ||
37             conflictType === conflictName
38         ) {
39             return i18n(conflictType);
40         } else {
41             return `${i18n(conflictType)}: ${conflictName}`;
42         }
43     }
44
45     render() {
46         let {
47             collapseExpand,
48             conflict: { id: conflictId, type, name },
49             isCollapsed,
50             item: { id: itemId, version },
51             onResolveConflict
52         } = this.props;
53         let { resolution } = this.state;
54         const iconClass = isCollapsed ? 'merge-chevron' : 'merge-chevron right';
55
56         return (
57             <div key={'conflictCategory_' + conflictId}>
58                 <GridSection className="conflict-section">
59                     <GridItem>
60                         <div
61                             className="collapsible-section"
62                             onClick={collapseExpand}>
63                             <SVGIcon
64                                 name={isCollapsed ? 'chevronDown' : 'chevronUp'}
65                                 iconClassName={iconClass}
66                             />
67                             <div className="conflict-title">
68                                 {this.getTitle(type, name)}
69                             </div>
70                         </div>
71                     </GridItem>
72                     <GridItem className="yours">
73                         <Radio
74                             name={'radio_' + conflictId}
75                             checked={resolution === ResolutionTypes.YOURS}
76                             value="yours"
77                             onChange={() =>
78                                 this.setState({
79                                     resolution: ResolutionTypes.YOURS
80                                 })
81                             }
82                             data-test-id={'radio_' + conflictId + '_yours'}
83                         />
84                     </GridItem>
85                     <GridItem className="theirs">
86                         <Radio
87                             name={'radio_' + conflictId}
88                             checked={resolution === ResolutionTypes.THEIRS}
89                             value="theirs"
90                             onChange={() =>
91                                 this.setState({
92                                     resolution: ResolutionTypes.THEIRS
93                                 })
94                             }
95                             data-test-id={'radio_' + conflictId + '_theirs'}
96                         />
97                     </GridItem>
98                     <GridItem className="resolve">
99                         <Button
100                             className="conflict-resolve-btn"
101                             btnType="secondary"
102                             size="default"
103                             onClick={() =>
104                                 onResolveConflict({
105                                     conflictId,
106                                     resolution,
107                                     itemId,
108                                     version
109                                 })
110                             }>
111                             {i18n('Resolve')}
112                         </Button>
113                     </GridItem>
114                 </GridSection>
115                 <div>{isCollapsed && this.props.children}</div>
116             </div>
117         );
118     }
119 }
120
121 class TextCompare extends React.Component {
122     render() {
123         // let rand = Math.random() * (3000 - 1) + 1;
124         let {
125             yours,
126             theirs,
127             field,
128             type,
129             isObjName,
130             conflictsOnly
131         } = this.props;
132         let typeYours = typeof yours;
133         let typeTheirs = typeof theirs;
134
135         let parsedType = `${type}/${field}`.replace(/\/[0-9]+/g, '/index');
136         let level = type.split('/').length;
137
138         if (typeYours === 'boolean' || typeTheirs === 'boolean') {
139             yours = yours ? i18n('Yes') : i18n('No');
140             theirs = theirs ? i18n('Yes') : i18n('No');
141         }
142
143         /*if ((typeYours !== 'string' && typeYours !== 'undefined') || (typeTheirs !== 'string' && typeTheirs !== 'undefined')) {
144                         return (<div className='merge-editor-text-field field-error'>{field} cannot be parsed for display</div>);
145                 }*/
146         let isDiff = yours !== theirs;
147         if (
148             !isObjName &&
149             ((!isDiff && conflictsOnly) ||
150                 (yours === '' && theirs === '') ||
151                 (typeYours === 'undefined' && typeTheirs === 'undefined'))
152         ) {
153             return null;
154         }
155
156         return (
157             <GridSection
158                 className={
159                     isDiff
160                         ? 'merge-editor-text-field diff'
161                         : 'merge-editor-text-field'
162                 }>
163                 <GridItem className="field-col grid-col-title" stretch>
164                     <div
165                         className={`field ${
166                             isDiff ? 'diff' : ''
167                         } field-name level-${level} ${
168                             isObjName ? 'field-object-name' : ''
169                         }`}>
170                         {i18n(parsedType)}
171                     </div>
172                 </GridItem>
173                 <GridItem className="field-col grid-col-yours" stretch>
174                     <div
175                         className={`field field-yours ${
176                             !yours ? 'empty-field' : ''
177                         }`}>
178                         {yours || (isObjName ? '' : '━━')}
179                     </div>
180                 </GridItem>
181                 <GridItem className="field-col grid-col-theirs" stretch>
182                     <div
183                         className={`field field-theirs ${
184                             !theirs ? 'empty-field' : ''
185                         }`}>
186                         {theirs || (isObjName ? '' : '━━')}
187                     </div>
188                 </GridItem>
189                 <GridItem stretch />
190             </GridSection>
191         );
192     }
193 }
194
195 class MergeEditorView extends React.Component {
196     state = {
197         collapsingSections: {},
198         conflictsOnly: false
199     };
200
201     render() {
202         let {
203             conflicts,
204             item,
205             conflictFiles,
206             onResolveConflict,
207             currentScreen,
208             resolution
209         } = this.props;
210
211         return (
212             <div className="merge-editor">
213                 {conflictFiles && this.renderConflictTableTitles()}
214                 <div className="merge-editor-body">
215                     {conflictFiles &&
216                         conflictFiles
217                             .sort((a, b) => a.type > b.type)
218                             .map(file => (
219                                 <ConflictCategory
220                                     key={'conflict_' + file.id}
221                                     conflict={file}
222                                     item={item}
223                                     isCollapsed={
224                                         this.state.collapsingSections[file.id]
225                                     }
226                                     collapseExpand={() => {
227                                         this.updateCollapseState(file.id);
228                                     }}
229                                     onResolveConflict={cDetails =>
230                                         onResolveConflict({
231                                             ...cDetails,
232                                             currentScreen
233                                         })
234                                     }>
235                                     {conflicts &&
236                                         conflicts[file.id] &&
237                                         this.getUnion(
238                                             conflicts[file.id].yours,
239                                             conflicts[file.id].theirs
240                                         ).map(field => {
241                                             return this.renderField(
242                                                 field,
243                                                 file,
244                                                 conflicts[file.id].yours[field],
245                                                 conflicts[file.id].theirs[
246                                                     field
247                                                 ],
248                                                 resolution
249                                             );
250                                         })}
251                                 </ConflictCategory>
252                             ))}
253                 </div>
254             </div>
255         );
256     }
257
258     renderConflictTableTitles() {
259         return (
260             <GridSection className="conflict-titles-section">
261                 <GridItem>{i18n('Page')}</GridItem>
262                 <GridItem className="yours">{i18n('Local (Me)')}</GridItem>
263                 <GridItem className="theirs">{i18n('Last Committed')}</GridItem>
264                 <GridItem className="resolve">
265                     <Input
266                         label={i18n('Show Conflicts Only')}
267                         type="checkbox"
268                         value={this.state.conflictsOnly}
269                         onChange={e => this.setState({ conflictsOnly: e })}
270                     />
271                 </GridItem>
272             </GridSection>
273         );
274     }
275     // <Checkbox
276     //  label={i18n('Show Conflicts Only')}
277     //  value={this.state.conflictsOnly}
278     //  checked={this.state.conflictsOnly}
279     //  onChange={checked => this.setState({conflictsOnly: checked})} />
280
281     renderObjects(yours, theirs, fileType, field, id, resolution) {
282         if (equal(yours, theirs)) {
283             return;
284         }
285         let { conflictsOnly } = this.state;
286         return (
287             <div key={`obj_${fileType}/${field}_${id}`}>
288                 <TextCompare
289                     field={field}
290                     type={fileType}
291                     conflictsOnly={conflictsOnly}
292                     yours=""
293                     theirs=""
294                     isObjName
295                     resolution={resolution}
296                 />
297                 <div className="field-objects">
298                     <div>
299                         {this.getUnion(yours, theirs).map(key =>
300                             this.renderField(
301                                 key,
302                                 { type: `${fileType}/${field}`, id },
303                                 yours && yours[key],
304                                 theirs && theirs[key]
305                             )
306                         )}
307                     </div>
308                 </div>
309             </div>
310         );
311     }
312
313     renderList(yours = [], theirs = [], type, field, id, resolution) {
314         let theirsList = theirs.join(', ');
315         let yoursList = yours.join(', ');
316         let { conflictsOnly } = this.state;
317         return (
318             <TextCompare
319                 key={'text_' + id + '_' + field}
320                 field={field}
321                 type={type}
322                 yours={yoursList}
323                 theirs={theirsList}
324                 conflictsOnly={conflictsOnly}
325                 resolution={resolution}
326             />
327         );
328     }
329
330     renderField(field, file, yours, theirs, resolution) {
331         if (yours) {
332             if (Array.isArray(yours)) {
333                 return this.renderList(
334                     yours,
335                     theirs,
336                     file.type,
337                     field,
338                     file.id,
339                     resolution
340                 );
341             } else if (typeof yours === 'object') {
342                 return this.renderObjects(
343                     yours,
344                     theirs,
345                     file.type,
346                     field,
347                     file.id,
348                     resolution
349                 );
350             }
351         } else if (theirs) {
352             if (Array.isArray(theirs)) {
353                 return this.renderList(
354                     yours,
355                     theirs,
356                     file.type,
357                     field,
358                     file.id,
359                     resolution
360                 );
361             } else if (typeof theirs === 'object') {
362                 return this.renderObjects(
363                     yours,
364                     theirs,
365                     file.type,
366                     field,
367                     file.id,
368                     resolution
369                 );
370             }
371         }
372         let { conflictsOnly } = this.state;
373         return (
374             <TextCompare
375                 key={'text_' + file.id + '_' + field}
376                 resolution={resolution}
377                 field={field}
378                 type={file.type}
379                 yours={yours}
380                 theirs={theirs}
381                 conflictsOnly={conflictsOnly}
382             />
383         );
384     }
385
386     getUnion(yours = {}, theirs = {}) {
387         let yoursKeys = Object.keys(yours);
388         let theirsKeys = Object.keys(theirs);
389         let myUn = union(yoursKeys, theirsKeys);
390         return myUn; //.sort((a, b) => a > b);
391     }
392
393     updateCollapseState(conflictId) {
394         const {
395             fetchConflict,
396             item: { id: itemId, version } /*conflicts*/
397         } = this.props;
398         let isCollapsed = this.state.collapsingSections[conflictId];
399         // if (!isCollapsed && !(conflicts && conflictId in conflicts)) {
400         if (!isCollapsed) {
401             fetchConflict({ cid: conflictId, itemId, version });
402         }
403         this.setState({
404             collapsingSections: {
405                 ...this.state.collapsingSections,
406                 [conflictId]: !isCollapsed
407             }
408         });
409     }
410 }
411
412 export default MergeEditorView;