mirror of
				https://github.com/datahub-project/datahub.git
				synced 2025-10-31 10:49:00 +00:00 
			
		
		
		
	DSS-5178 DSS-5277: Implements Compliance and Confidential Spec
Adds 'logs/' to ignored files Updates EmberSelectorComponent to handle a list of string options or list of options with value and label, flags the currently selected option, and bubble change actions with 'selectionDidChange' action DSS-5178: Removes previous updates to search.js: moving jQuery + DOM heavy imperative implementation to Ember component DSS-5178: Adds templates and components DropRegion and DraggableItem DSS-5178: Adds getSecuritySpec action and compliance types to Dataset controller, cleans up Datasets route and removes inline securitySpec fetch from route DSS-5178: Updates templates for compliance spec DSS-5178: Adds compliance component and updates template Adds .DS_Store to gitignore DSS-5277: Adds dataset-confidential component to DOM, Creates DatasetConfidential component, refactors out data handling from component DSS-5277: Moves data fetching to Dataset Route model and set model data on controller, Adds template for confidential spec component DSS-5178: Moves view related complianceTypes to component DSS-5277 DSS-5178: Adds styling for tab content
This commit is contained in:
		
							parent
							
								
									36e583b19e
								
							
						
					
					
						commit
						1a974c2d38
					
				
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -19,3 +19,5 @@ bin/ | ||||
| backend-service/lib | ||||
| metadata-etl/src/main/resources/application.properties | ||||
| **/test/resources/*.properties | ||||
| logs/ | ||||
| .DS_Store | ||||
|  | ||||
| @ -249,21 +249,15 @@ | ||||
|       </div> | ||||
|     </script> | ||||
| 
 | ||||
|     <script type="text/x-handlebars" id="components/ember-selector"> | ||||
|       <select id="ember-selector" class="form-control"> | ||||
|         {{#each renderValues as |value|}} | ||||
|           {{#if value.isSelected}} | ||||
|             <option value="{{value.value}}" selected> | ||||
|               {{value.value}} | ||||
| <script type="text/x-handlebars" id="components/ember-selector"> | ||||
|     <select class={{class}} {{action 'change' on='change'}}> | ||||
|         {{#each content as |option|}} | ||||
|             <option value="{{option.value}}" selected={{if option.isSelected 'selected' ''}}> | ||||
|                 {{if option.label option.label option.value}} | ||||
|             </option> | ||||
|           {{else}} | ||||
|             <option value="{{value.value}}"> | ||||
|               {{value.value}} | ||||
|             </option> | ||||
|           {{/if}} | ||||
|         {{/each}} | ||||
|     </select> | ||||
|     </script> | ||||
| </script> | ||||
| 
 | ||||
|     <script type="text/x-handlebars" id="components/dataset-schema"> | ||||
|       {{#if hasSchemas}} | ||||
| @ -691,6 +685,13 @@ | ||||
|                   Compliance | ||||
|               </a> | ||||
|           </li> | ||||
|             <li id="confidential"> | ||||
|                 <a data-toggle="tab" | ||||
|                    title="Confidential" | ||||
|                    href="#confidentialtab"> | ||||
|                     Confidential | ||||
|                 </a> | ||||
|             </li> | ||||
|         </ul> | ||||
|         <div class="tab-content"> | ||||
|           {{#unless isPinot}} | ||||
| @ -734,9 +735,21 @@ | ||||
|             {{/dataset-access}} | ||||
|           </div> | ||||
|             <div id="compliancetab" class="tab-pane"> | ||||
|             {{#dataset-compliance hasCompliance=hasCompliance compliance=compliance}} | ||||
|                 {{#dataset-compliance securitySpec=securitySpec | ||||
|                                       fieldList=fieldList | ||||
|                                       typeList=typeList | ||||
|                                       onSave=(action "saveSecuritySpec") | ||||
|                                       onReset=(action "getSecuritySpec")}} | ||||
|                 {{/dataset-compliance}} | ||||
|             </div> | ||||
|             <div id="confidentialtab" class="tab-pane"> | ||||
|                 {{#dataset-confidential securitySpec=securitySpec | ||||
|                                         fieldList=fieldList | ||||
|                                         typeList=typeList | ||||
|                                         onSave=(action "saveSecuritySpec") | ||||
|                                         onReset=(action "getSecuritySpec")}} | ||||
|                 {{/dataset-confidential}} | ||||
|             </div> | ||||
|         </div> | ||||
|       {{else}} | ||||
|       <div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true"> | ||||
| @ -898,12 +911,38 @@ | ||||
|           </div> | ||||
|           <div id="accessview" class="panel-collapse collapse" role="tabpanel" aria-labelledby="complianceHeading"> | ||||
|               <div class="panel-body"> | ||||
|               {{#dataset-compliance hasCompliance=hasCompliance compliance=compliance}} | ||||
|                   {{#dataset-compliance fieldList=fieldList | ||||
|                                         typeList=typeList | ||||
|                                         securitySpec=securitySpec | ||||
|                                         onSave=(action "saveSecuritySpec") | ||||
|                                         onReset=(action "getSecuritySpec")}} | ||||
|                   {{/dataset-compliance}} | ||||
|               </div> | ||||
|           </div> | ||||
|         </div> | ||||
| 
 | ||||
|           <div class="panel panel-default"> | ||||
|               <div class="panel-heading" role="tab" id="confidentialHeading"> | ||||
|                   <h4 class="panel-title"> | ||||
|                       <a class="collapsed" data-toggle="collapse" data-parent="#accordion" | ||||
|                          href="#accessview" aria-expanded="false" aria-controls="accessData"> | ||||
|                           Confidential | ||||
|                       </a> | ||||
|                   </h4> | ||||
|               </div> | ||||
|               <div id="accessview" class="panel-collapse collapse" role="tabpanel" | ||||
|                    aria-labelledby="confidentialHeading"> | ||||
|                   <div class="panel-body"> | ||||
|                       {{#dataset-confidential securitySpec=securitySpec | ||||
|                                               fieldList=fieldList | ||||
|                                               typeList=typeList | ||||
|                                               onSave=(action "saveSecuritySpec") | ||||
|                                               onReset=(action "getSecuritySpec")}} | ||||
|                       {{/dataset-confidential}} | ||||
|                   </div> | ||||
|               </div> | ||||
|           </div> | ||||
| 
 | ||||
|       </div> | ||||
|       {{/if}} | ||||
|     </script> | ||||
|  | ||||
| @ -163,11 +163,6 @@ | ||||
| 									class="form-control input-sm keyword-search" | ||||
| 									placeholder="Enter Keywords..." | ||||
| 								/> | ||||
| 								<input id="schemaInput" | ||||
| 									type="text" | ||||
| 									class="form-control input-sm keyword-search hide" | ||||
| 									placeholder="Enter Keywords..." | ||||
| 								/> | ||||
| 								<span class="input-group-btn"> | ||||
| 									<button id="searchBtn" | ||||
| 										type="button" | ||||
| @ -1020,8 +1015,13 @@ | ||||
|         </div><!-- /.modal-dialog --> | ||||
|       </div><!-- /.modal --> | ||||
|     </script> | ||||
|         <script type="text/x-handlebars" id="components/drop-region"> | ||||
|             {{yield}} | ||||
|         </script> | ||||
| 
 | ||||
| 
 | ||||
|         <script type="text/x-handlebars" id="components/draggable-item"> | ||||
|             {{yield}} | ||||
|         </script> | ||||
| 		<script type="text/x-handlebars" id="components/dataset-relations"> | ||||
|       {{#if hasDepends}} | ||||
| 			<table id="depends-table" class="columntreegrid tree table table-bordered dataset-detail-table"> | ||||
| @ -1364,84 +1364,169 @@ | ||||
| 		</script> | ||||
| 
 | ||||
|         <script type="text/x-handlebars" id="components/dataset-compliance"> | ||||
|             <div id="compliance" class="tab-body"> | ||||
|                 {{#if securitySpec}} | ||||
|                     <label class="lbl"> | ||||
|                         <span class="lbl__text">Compliance Type</span> | ||||
|                         {{#ember-selector values=complianceTypes | ||||
|                                           selected=complianceType | ||||
|                                           selectionDidChange="updateComplianceType"}} | ||||
|                         {{/ember-selector}} | ||||
|                     </label> | ||||
| 
 | ||||
|       <div id="compliance"> | ||||
| 
 | ||||
|       {{#if hasCompliance}} | ||||
| 
 | ||||
|         <span class="ctitle">Compliance Type</span> | ||||
|         <div class="compliance-type cmodule"> | ||||
|           <span class="csubt">Choose Type</span> | ||||
|           <select class="purge"> | ||||
|             <option value="select" selected>Select Purge Type</option> | ||||
|             <option value="auto">Auto Purge</option> | ||||
|             <option value="custom">Custom Purge</option> | ||||
|             <option value="retention">Retention Purge</option> | ||||
|             <option value="na">Not Applicable</option> | ||||
|           </select> | ||||
|           <select id="action" class="hide"> | ||||
|             <option value="member-id">Member ID</option> | ||||
|             <option value="member-urn">Member URN</option> | ||||
|             <option value="subject-id">Subject ID</option> | ||||
|             <option value="subject-urn">Subject URN</option> | ||||
|           </select> | ||||
|                     <div class="field-search"> | ||||
|                         <input type="text" | ||||
|                                id="compliance-typeahead" | ||||
|                                class="field-search__input form-control" | ||||
|                                placeholder="Search for field names..."> | ||||
|                     </div> | ||||
| 
 | ||||
|         <span class="ctitle">Field Selection</span> | ||||
|         <div class="compliance-fields cmodule"> | ||||
|           <div class="cfheader"> | ||||
|             <div class="cfname">Field name</div> | ||||
|             <div class="cftype">Field Type</div> | ||||
|             <div class="cfaction">Field Action</div> | ||||
|           </div> | ||||
|           <div class="cfrow hide"> | ||||
|             <div class="cfname"></div> | ||||
|             <div class="cftype"></div> | ||||
|             <div class="cfaction"></div> | ||||
|           </div> | ||||
|           <i id="cf-closer" class="fa fa-times-circle fa-lg hide"></i> | ||||
|         </div> | ||||
| 
 | ||||
|         <span class="ctitle">Record Owner</span> | ||||
|         <div class="compliance-owner cmodule group"> | ||||
|           <div class="memberfield"> | ||||
|           	<div class="csubt">Member Identifier</div> | ||||
|           	<div class="idname"><div class="labelx">ID</div><div id="member-id" class="droppable"></div></div> | ||||
|           	<div class="urnname"><div class="labelx">URN</div><div id="member-urn" class="droppable"></div></div> | ||||
|           </div> | ||||
|           <div class="subjectfield"> | ||||
|           	<div class="csubt">Subject Identifier</div> | ||||
|           	<div class="idname"><div class="labelx">ID</div><div id="subject-id" class="droppable"></div></div> | ||||
|           	<div class="urnname"><div class="labelx">URN</div><div id="subject-urn" class="droppable"></div></div> | ||||
|           </div> | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="cta"> | ||||
|         	<button class="upload" disabled>Save</button> | ||||
|         	<button class="upload" disabled>Cancel</button> | ||||
|         </div> | ||||
| 
 | ||||
| 				<!--  Once our DB schema includes compliance ownership fields, display here | ||||
| 				<div> | ||||
|           {{#each compliance as |item|}} | ||||
|             <div class="prop-row group"> | ||||
|               <div class="prop-label">{{item.key}}:</div> | ||||
|               {{#if item.isSelectController}} | ||||
|                 <div class="prop-value-query">{{item.value}}</div> | ||||
|                     <section class="typeahead-matches"> | ||||
|                         {{#each matchingFields as |matchingField|}} | ||||
|                             {{#draggable-item content=matchingField class="draggable__item"}} | ||||
|                                 {{matchingField}} | ||||
|                             {{/draggable-item}} | ||||
|                         {{else}} | ||||
|                 <div class="prop-value">{{item.value}}</div> | ||||
|               {{/if}} | ||||
|             </div> | ||||
|                             <span class="classification-list-help">No results found.</span> | ||||
|                         {{/each}} | ||||
|                     </section> | ||||
|                     <section class="drop-bucket panel panel-default"> | ||||
|                         <div class="panel-heading"> | ||||
|                             <h4>Purgeable Ids</h4> | ||||
|                             <sub>Add items by dragging to relevant cells below</sub> | ||||
|                         </div> | ||||
|         --> | ||||
| 
 | ||||
|       {{else}} | ||||
|         <p>Compliance is not available</p> | ||||
|                         <table class="table table-bordered"> | ||||
|                             <tbody> | ||||
|                             {{#each purgeEntities as |purgeableId|}} | ||||
|                                 <tr> | ||||
|                                     <th>{{purgeableId.identifierType}}</th> | ||||
|                                     <td class="drop-bucket__td"> | ||||
|                                         {{#drop-region dropped="addPurgeId" param=purgeableId.identifierType}} | ||||
|                                             {{#if purgeableId.identifierField}} | ||||
|                                                 <ul class="drop-bucket__ul"> | ||||
|                                                     {{#each purgeableId.identifierField as |field|}} | ||||
|                                                         {{#if field}} | ||||
|                                                             <li class="draggable__item"> | ||||
|                                                                 {{field}} | ||||
|                                                                 <i class="glyphicon glyphicon-remove" aria-hidden="true" | ||||
|                                                                     {{action 'removePurgeId' field | ||||
|                                                                              purgeableId.identifierType | ||||
|                                                                              on='click'}}></i> | ||||
|                                                             </li> | ||||
|                                                         {{/if}} | ||||
| 
 | ||||
|                                                     {{/each}} | ||||
|                                                 </ul> | ||||
|                                             {{else}} | ||||
|                                                 <span class="classification-list-help">Drag relevant search result items here...</span> | ||||
|                                             {{/if}} | ||||
|                                         {{/drop-region}} | ||||
|                                     </td> | ||||
|                                 </tr> | ||||
|                             {{/each}} | ||||
|                             </tbody> | ||||
|                         </table> | ||||
|                     </section> | ||||
|                     <div class="cta"> | ||||
|                         <button class="btn btn-primary" {{action 'saveCompliance'}}>Save</button> | ||||
|                         <button class="btn btn-default" {{action 'resetCompliance'}}>Reset</button> | ||||
|                     </div> | ||||
|                 {{else}} | ||||
|                     <h3>Compliance is not available</h3> | ||||
|                 {{/if}} | ||||
|             </div> | ||||
|         </script> | ||||
| 
 | ||||
|         <script type="text/x-handlebars" id="components/dataset-confidential"> | ||||
|             <div id="confidential" class="tab-body"> | ||||
|                 {{#if securitySpec}} | ||||
|                     <label class="lbl"> | ||||
|                         <span class="lbl__text">Retention</span> | ||||
|                         {{#ember-selector values=retentionTypes | ||||
|                                           selection=retention | ||||
|                                           selectionDidChange="updateRetentionType"}} | ||||
|                         {{/ember-selector}} | ||||
|                     </label> | ||||
|                     <label class="lbl"> | ||||
|                         <span class="lbl__text">Geographic Affinity</span> | ||||
|                         {{#ember-selector values=affinityTypes | ||||
|                                           selection=geographicAffinity | ||||
|                                           selectionDidChange="updateGeographicAffinity"}} | ||||
|                         {{/ember-selector}} | ||||
|                     </label> | ||||
|                     <label class="lbl"> | ||||
|                         <span class="lbl__text">Record Owner Type</span> | ||||
|                         {{#ember-selector values=recordOwnerTypes | ||||
|                                           selection=recordOwnerType | ||||
|                                           selectionDidChange="updateRecordOwnerType"}} | ||||
|                         {{/ember-selector}} | ||||
|                     </label> | ||||
| 
 | ||||
|                     <div class="field-search"> | ||||
|                         <input type="text" | ||||
|                                id="confidential-typeahead" | ||||
|                                class="field-search__input form-control" | ||||
|                                placeholder="Search for field names..."> | ||||
|                     </div> | ||||
|                     <section class="typeahead-matches"> | ||||
|                         {{#each matchingFields as |matchingField|}} | ||||
|                             {{#draggable-item content=matchingField class="draggable__item"}} | ||||
|                                 {{matchingField}} | ||||
|                             {{/draggable-item}} | ||||
|                         {{else}} | ||||
|                             <span class="classification-list-help">No results found.</span> | ||||
|                         {{/each}} | ||||
|                     </section> | ||||
|                     <section class="drop-bucket panel panel-default"> | ||||
|                         <!-- TODO: Refactor as component --> | ||||
|                         <div class="panel-heading"> | ||||
|                             <h4>Confidential Classification</h4> | ||||
|                             <sub>Add items by dragging to relevant cells below</sub> | ||||
|                         </div> | ||||
|                         <table class="table table-bordered"> | ||||
|                             <tbody> | ||||
|                             {{#each classification as |classifier|}} | ||||
|                                 <tr> | ||||
|                                     <th>{{classifier.label}}</th> | ||||
|                                     <td class="drop-bucket__td"> | ||||
|                                         {{#drop-region dropped="addToClassification" param=classifier.key}} | ||||
|                                             {{#if classifier.values}} | ||||
|                                                 <ul class="drop-bucket__ul"> | ||||
|                                                     {{#each classifier.values as |value|}} | ||||
|                                                         {{#if value}} | ||||
|                                                             <li class="draggable__item"> | ||||
|                                                                 {{value}} | ||||
|                                                                 <i class="glyphicon glyphicon-remove" aria-hidden="true" | ||||
|                                                                     {{action "removeFromClassification" value | ||||
|                                                                              classifier.key | ||||
|                                                                              on='click'}}></i> | ||||
|                                                             </li> | ||||
|                                                         {{/if}} | ||||
|                                                     {{/each}} | ||||
|                                                 </ul> | ||||
|                                             {{else}} | ||||
|                                                 <span class="classification-list-help">Drag relevant search result items here...</span> | ||||
|                                             {{/if}} | ||||
|                                         {{/drop-region}} | ||||
|                                     </td> | ||||
|                                 </tr> | ||||
|                             {{/each}} | ||||
|                             </tbody> | ||||
|                         </table> | ||||
|                     </section> | ||||
|                     <div class="cta"> | ||||
|                         {{#if isApproved}}<!-- TODO: Add flag to component --> | ||||
|                             <button class="btn btn-primary" {{action 'disapproveCompliance'}}> | ||||
|                                 Remove Approval | ||||
|                             </button> | ||||
|                         {{else}} | ||||
|                             <button class="btn btn-primary" {{action 'approveCompliance'}}>Approve</button> | ||||
|                         {{/if}} | ||||
|                         <button class="btn btn-primary" {{action 'saveCompliance'}}>Save</button> | ||||
|                         <button class="btn btn-default" {{action 'resetCompliance'}}>Reset</button> | ||||
|                     </div> | ||||
|                 {{else}} | ||||
|                     <h3>Confidential spec is not available</h3> | ||||
|                 {{/if}} | ||||
|             </div> | ||||
|         </script> | ||||
| 
 | ||||
|     <script type="text/x-handlebars" id="components/schema-comment"> | ||||
|  | ||||
| @ -73,20 +73,86 @@ function setOwnerNameAutocomplete(controller) | ||||
| } | ||||
| 
 | ||||
| App.EmberSelectorComponent = Ember.Component.extend({ | ||||
|   init: function() { | ||||
|     this._super(); | ||||
|     var values = this.get('values'); | ||||
|     var selected = this.get('selected'); | ||||
|     var renderValues = []; | ||||
|     if (values && values.length > 0) | ||||
|     { | ||||
|       for(var i = 0; i < values.length; i++) | ||||
|       { | ||||
|         renderValues.push({'value': values[i], 'isSelected': values[i] === selected}); | ||||
|   class: 'form-control', | ||||
|   content: [], | ||||
| 
 | ||||
|   init() { | ||||
|     this._super(...arguments); | ||||
|     this.updateContent(); | ||||
|   }, | ||||
| 
 | ||||
|   onSelectionChanged: Ember.observer('selected', function () { | ||||
|     this.updateContent(); | ||||
|   }), | ||||
| 
 | ||||
|   /** | ||||
|    * Parse and transform the values list into a list of objects with the currently | ||||
|    * selected option flagged as `isSelected` | ||||
|    */ | ||||
|   updateContent() { | ||||
|     let selected = this.get('selected') || ''; | ||||
|     selected && (selected = String(selected).toLowerCase()); | ||||
| 
 | ||||
|     const options = this.get('values') || []; | ||||
|     const content = options.map(option => { | ||||
|       if (typeof option === 'object' && typeof option.value !== 'undefined') { | ||||
|         const isSelected = String(option.value).toLowerCase() === selected; | ||||
|         return {value: option.value, label: option.label, isSelected}; | ||||
|       } | ||||
| 
 | ||||
|       return {value: option, isSelected: String(option).toLowerCase() === selected}; | ||||
|     }); | ||||
| 
 | ||||
|     this.set('content', content); | ||||
|   }, | ||||
| 
 | ||||
|   actions: { | ||||
|     // Reflect UI changes in the component and bubble the `selectionDidChange` action
 | ||||
|     change() { | ||||
|       const {selectedIndex} = this.$('select')[0]; | ||||
|       const values = this.get('values'); | ||||
|       const _selected = values[selectedIndex]; | ||||
|       const selected = typeof _selected.value !== 'undefined' ? _selected.value : _selected; | ||||
| 
 | ||||
|       this.set('selected', selected); | ||||
| 
 | ||||
|       this.sendAction('selectionDidChange', _selected); | ||||
|     } | ||||
|     this.set('renderValues', renderValues); | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
| // Component  wrapper for a droppable DOM region
 | ||||
| App.DropRegionComponent = Ember.Component.extend({ | ||||
|   classNames: ['drop-region'], | ||||
|   classNameBindings: ['dragClass'], | ||||
|   dragClass: 'deactivated', | ||||
| 
 | ||||
|   dragLeave(e) { | ||||
|     e.preventDefault(); | ||||
|     this.set('dragClass', 'deactivated'); | ||||
|   }, | ||||
| 
 | ||||
|   dragOver(e) { | ||||
|     e.preventDefault(); | ||||
|     this.set('dragClass', 'activated'); | ||||
|   }, | ||||
| 
 | ||||
|   drop (e) { | ||||
|     const data = e.dataTransfer.getData('text/data'); | ||||
|     this.sendAction('dropped', data, this.get('param')); | ||||
|     this.set('dragClass', 'deactivated'); | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
| // Component wrapper for a draggable item
 | ||||
| App.DraggableItemComponent = Ember.Component.extend({ | ||||
|   tagName: 'span', | ||||
|   classNames: ['draggable-item'], | ||||
|   attributeBindings: ['draggable'], | ||||
|   draggable: 'true', | ||||
| 
 | ||||
|   dragStart(e) { | ||||
|     return e.dataTransfer.setData('text/data', this.get('content')); | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
| @ -176,6 +242,288 @@ App.DatasetOwnerListComponent = Ember.Component.extend({ | ||||
|   }), | ||||
| }); | ||||
| 
 | ||||
| App.DatasetComplianceComponent = Ember.Component.extend({ | ||||
|   searchTerm: '', | ||||
|   complianceType: Ember.computed.alias('privacyCompliancePolicy.complianceType'), | ||||
|   get complianceTypes() { | ||||
|     return ['CUSTOM_PURGE', 'AUTO_PURGE', 'RETENTION_PURGE', 'NOT_APPLICABLE'].map(complianceType => ({ | ||||
|       value: complianceType, | ||||
|       label: complianceType.replace('_', ' ').toLowerCase().capitalize() | ||||
|     })) | ||||
|   }, | ||||
| 
 | ||||
|   // Cached list of dataset field names
 | ||||
|   datasetSchemaFieldNames: Ember.computed('datasetSchemaFieldsAndTypes', function () { | ||||
|     return this.get('datasetSchemaFieldsAndTypes').mapBy('name'); | ||||
|   }), | ||||
| 
 | ||||
|   matchingFields: Ember.computed('searchTerm', 'datasetSchemaFieldsAndTypes', function () { | ||||
|     if (this.get('datasetSchemaFieldsAndTypes')) { | ||||
|       const searchTerm = this.get('searchTerm'); | ||||
|       const matches = $.ui.autocomplete.filter(this.get('datasetSchemaFieldNames'), searchTerm); | ||||
|       return matches.map(value => { | ||||
|         const {type} = this.get('datasetSchemaFieldsAndTypes').filterBy('name', value).get('firstObject'); | ||||
|         const dataType = Array.isArray(type) && type.toString().toUpperCase(); | ||||
| 
 | ||||
|         return { | ||||
|           value, | ||||
|           dataType | ||||
|         }; | ||||
|       }); | ||||
|     } | ||||
|   }), | ||||
| 
 | ||||
|   /** | ||||
|    * Aliases compliancePurgeEntities on privacyCompliancePolicy, and transforms each nested comma-delimited identifierField string | ||||
|    * into an array of fields that can easily be iterated over. Dependency on each identifierField will update | ||||
|    * UI on updates | ||||
|    */ | ||||
|   purgeEntities: Ember.computed('privacyCompliancePolicy.compliancePurgeEntities.@each.identifierField', function () { | ||||
|     const compliancePurgeEntities = this.get('privacyCompliancePolicy.compliancePurgeEntities'); | ||||
|     // Type ENUM is locally saved in the client.
 | ||||
|     // The user is able to add a values to the identifier types that are not provided in the schema returned by the server.
 | ||||
|     // Values are manually extracted from Nuage schema at develop time
 | ||||
|     const purgeableEntityFieldIdentifierTypes = [ | ||||
|       'MEMBER_ID', 'SUBJECT_MEMBER_ID', 'URN', 'SUBJECT_URN', 'COMPANY_ID', 'GROUP_ID', 'CUSTOMER_ID' | ||||
|     ]; | ||||
| 
 | ||||
|     // Create an object list for each purgeableEntityFieldIdentifier with a mapping to label and identifierField value
 | ||||
|     return purgeableEntityFieldIdentifierTypes.map(identifierType => { | ||||
|       // Find entity with matching identifierType that has been remotely persisted
 | ||||
|       const savedPurgeEntity = compliancePurgeEntities.filterBy('identifierType', identifierType).shift(); | ||||
|       const label = identifierType.replace(/_/g, ' ').toLowerCase().capitalize(); | ||||
| 
 | ||||
|       return { | ||||
|         identifierType, | ||||
|         label, | ||||
|         identifierField: savedPurgeEntity ? savedPurgeEntity.identifierField.split(',') : [] | ||||
|       }; | ||||
|     }); | ||||
|   }), | ||||
| 
 | ||||
|   didRender() { | ||||
|     const $typeahead = this.$('#compliance-typeahead') || []; | ||||
|     if ($typeahead.length) { | ||||
|       this.enableTypeaheadOn($typeahead); | ||||
|     } | ||||
|   }, | ||||
| 
 | ||||
|   enableTypeaheadOn(selector) { | ||||
|     selector.autocomplete({ | ||||
|       minLength: 0, | ||||
|       source: request => { | ||||
|         const {term = ''} = request; | ||||
|         this.set('searchTerm', term); | ||||
|       } | ||||
|     }); | ||||
|   }, | ||||
| 
 | ||||
|   /** | ||||
|    * Returns a compliancePurgeEntity matching the given Id. | ||||
|    * @param {string} id value representing the identifierType | ||||
|    * @returns {*} | ||||
|    */ | ||||
|   getPurgeEntity(id) { | ||||
|     // There should be only one match in the resulting array
 | ||||
|     return this.get('privacyCompliancePolicy.compliancePurgeEntities') | ||||
|         .filterBy('identifierType', id) | ||||
|         .get('firstObject'); | ||||
|   }, | ||||
| 
 | ||||
|   addPurgeEntityToComplianceEntities(identifierType) { | ||||
|     this.get('privacyCompliancePolicy.compliancePurgeEntities').addObject({identifierType, identifierField: ''}); | ||||
|     return this.getPurgeEntity(identifierType); | ||||
|   }, | ||||
| 
 | ||||
|   /** | ||||
|    * Internal abstraction for adding and removing an Id from an identifierField | ||||
|    * @param {string} fieldValue name of identifier to add/remove from identifier type | ||||
|    * @param {string} idType the identifierType for a compliancePurgeEntity | ||||
|    * @param {string} toggleOperation string representing the operation to be performed | ||||
|    * @returns {boolean|*} true on success | ||||
|    * @private | ||||
|    */ | ||||
|   _togglePurgeIdOnIdentifierField(fieldValue, idType, toggleOperation) { | ||||
|     function updateIdentifierFieldOn(entity, updatedValue) { | ||||
|       return Ember.set(entity, 'identifierField', updatedValue); | ||||
|     } | ||||
| 
 | ||||
|     const op = { | ||||
|       /** | ||||
|        * Adds the fieldValue to the specified idType if available, otherwise creates a new compliancePurgeEntity | ||||
|        * @param purgeableEntityField | ||||
|        * @returns {*|Object} the updated compliancePurgeEntity | ||||
|        */ | ||||
|       add: (purgeableEntityField = this.addPurgeEntityToComplianceEntities(idType)) => { | ||||
|         const currentId = purgeableEntityField.identifierField; | ||||
|         const updatedIds = currentId.length ? currentId.split(',').addObject(fieldValue).join(',') : fieldValue; | ||||
| 
 | ||||
|         return updateIdentifierFieldOn(purgeableEntityField, updatedIds); | ||||
|       }, | ||||
|       /** | ||||
|        * Removes the fieldValue from the specified idType if available, otherwise function is no-op | ||||
|        * @param purgeableEntityField | ||||
|        * @returns {*|Object} the updated compliancePurgeEntity | ||||
|        */ | ||||
|       remove: (purgeableEntityField) => { | ||||
|         if (purgeableEntityField) { | ||||
|           const currentId = purgeableEntityField.identifierField; | ||||
|           const updatedIds = currentId.length ? currentId.split(',').removeObject(fieldValue).join(',') : ''; | ||||
| 
 | ||||
|           return updateIdentifierFieldOn(purgeableEntityField, updatedIds); | ||||
|         } | ||||
|       } | ||||
|     }[toggleOperation]; | ||||
| 
 | ||||
|     return typeof op === 'function' && op(this.getPurgeEntity(idType)); | ||||
|   }, | ||||
| 
 | ||||
|   actions: { | ||||
|     addPurgeId(name, idType) { | ||||
|       this._togglePurgeIdOnIdentifierField(name, idType, `add`); | ||||
|     }, | ||||
| 
 | ||||
|     removePurgeId(name, idType) { | ||||
|       this._togglePurgeIdOnIdentifierField(name, idType, `remove`); | ||||
|     }, | ||||
| 
 | ||||
|     updateComplianceType ({value}) { | ||||
|       this.set('privacyCompliancePolicy.complianceType', value); | ||||
|     }, | ||||
| 
 | ||||
|     saveCompliance () { | ||||
|       this.get('onSave')(); | ||||
|       return false; | ||||
|     }, | ||||
| 
 | ||||
|     // Rolls back changes made to the compliance spec to current
 | ||||
|     // server state
 | ||||
|     resetCompliance () { | ||||
|       this.get('onReset')(); | ||||
|     } | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
| App.DatasetConfidentialComponent = Ember.Component.extend({ | ||||
|   searchTerm: '', | ||||
|   retention: Ember.computed.alias('securitySpecification.retentionPolicy.retentionType'), | ||||
|   geographicAffinity: Ember.computed.alias('securitySpecification.geographicAffinity.affinity'), | ||||
|   recordOwnerType: Ember.computed.alias('securitySpecification.recordOwnerType'), | ||||
|   datasetSchemaFieldNames: Ember.computed('datasetSchemaFieldsAndTypes', function () { | ||||
|     return this.get('datasetSchemaFieldsAndTypes').mapBy('name'); | ||||
|   }), | ||||
| 
 | ||||
|   matchingFields: Ember.computed('searchTerm', 'datasetSchemaFieldsAndTypes', function () { | ||||
|     if (this.get('datasetSchemaFieldsAndTypes')) { | ||||
|       const searchTerm = this.get('searchTerm'); | ||||
|       const matches = $.ui.autocomplete.filter(this.get('datasetSchemaFieldNames'), searchTerm); | ||||
|       return matches.map(value => { | ||||
|         const {type} = this.get('datasetSchemaFieldsAndTypes').filterBy('name', value).get('firstObject'); | ||||
|         const dataType = Array.isArray(type) && type.toString().toUpperCase(); | ||||
| 
 | ||||
|         return { | ||||
|           value, | ||||
|           dataType | ||||
|         }; | ||||
|       }); | ||||
|     } | ||||
|   }), | ||||
| 
 | ||||
|   didRender() { | ||||
|     const $typeahead = this.$('#confidential-typeahead') || []; | ||||
|     if ($typeahead.length) { | ||||
|       this.enableTypeaheadOn($typeahead); | ||||
|     } | ||||
|   }, | ||||
| 
 | ||||
|   enableTypeaheadOn(selector) { | ||||
|     selector.autocomplete({ | ||||
|       minLength: 0, | ||||
|       source: request => { | ||||
|         const {term = ''} = request; | ||||
|         this.set('searchTerm', term); | ||||
|       } | ||||
|     }); | ||||
|   }, | ||||
| 
 | ||||
|   get recordOwnerTypes() { | ||||
|     return ['MEMBER', 'CUSTOMER', 'JOINT', 'INTERNAL', 'COMPANY'].map(ownerType => ({ | ||||
|       value: ownerType, | ||||
|       label: ownerType.toLowerCase().capitalize() | ||||
|     })); | ||||
|   }, | ||||
| 
 | ||||
|   get retentionTypes() { | ||||
|     return ['LIMITED', 'LEGAL_HOLD', 'UNLIMITED'].map(retention => ({ | ||||
|       value: retention, | ||||
|       label: retention.replace('_', ' ').toLowerCase().capitalize() | ||||
|     })); | ||||
|   }, | ||||
| 
 | ||||
|   get affinityTypes() { | ||||
|     return ['LIMITED', 'EXCLUDED'].map(affinity => ({ | ||||
|       value: affinity, | ||||
|       label: affinity.toLowerCase().capitalize() | ||||
|     })); | ||||
|   }, | ||||
| 
 | ||||
|   classification: Ember.computed('securitySpecification.classification', function () { | ||||
|     const confidentialClassification = this.get('securitySpecification.classification'); | ||||
|     const formatAsCapitalizedStringWithSpaces = string => string.replace(/[A-Z]/g, match => ` ${match}`).capitalize(); | ||||
| 
 | ||||
|     return Object.keys(confidentialClassification).map(classifier => ({ | ||||
|       key: classifier, | ||||
|       label: formatAsCapitalizedStringWithSpaces(classifier), | ||||
|       values: Ember.get(confidentialClassification, classifier) | ||||
|     })); | ||||
|   }), | ||||
| 
 | ||||
|   _toggleOnClassification(classifier, key, operation) { | ||||
|     this.get(`securitySpecification.classification.${key}`)[`${operation}Object`](classifier); | ||||
|   }, | ||||
| 
 | ||||
|   actions: { | ||||
|     addToClassification(classifier, classifierKey) { | ||||
|       this._toggleOnClassification(classifier, classifierKey, `add`); | ||||
|     }, | ||||
| 
 | ||||
|     removeFromClassification(classifier, classifierKey) { | ||||
|       this._toggleOnClassification(classifier, classifierKey, `remove`); | ||||
|     }, | ||||
| 
 | ||||
|     updateRetentionType({value}) { | ||||
|       this.set('securitySpecification.retentionPolicy.retentionType', value); | ||||
|     }, | ||||
| 
 | ||||
|     updateGeographicAffinity({value}) { | ||||
|       this.set('securitySpecification.geographicAffinity.affinity', value); | ||||
|     }, | ||||
| 
 | ||||
|     updateRecordOwnerType({value}) { | ||||
|       this.set('securitySpecification.recordOwnerType', value); | ||||
|     }, | ||||
| 
 | ||||
|     saveSecuritySpecification () { | ||||
|       this.get('onSave')(); | ||||
|       return false; | ||||
|     }, | ||||
| 
 | ||||
|     approveCompliance () { | ||||
|       //TODO: not implemented
 | ||||
|     }, | ||||
| 
 | ||||
|     disapproveCompliance () { | ||||
|       //TODO: not implemented
 | ||||
|     }, | ||||
| 
 | ||||
|     // Rolls back changes made to the compliance spec to current
 | ||||
|     // server state
 | ||||
|     resetSecuritySpecification () { | ||||
|       this.get('onReset')(); | ||||
|     } | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
| App.DatasetAuthorComponent = Ember.Component.extend({ | ||||
|   $ownerTable: null, | ||||
| 
 | ||||
| @ -862,4 +1210,4 @@ App.DatasetWatchComponent = Ember.Component.extend({ | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
| }) | ||||
| }); | ||||
| @ -302,6 +302,19 @@ App.DatasetController = Ember.Controller.extend({ | ||||
| 
 | ||||
|         _this.set('currentVersion', version); | ||||
|     }, | ||||
|     saveJson(data) { | ||||
|         const postRequest = { | ||||
|             type: 'POST', | ||||
|             url: `api/v1/datasets/${this.get('datasetId')}/security`, | ||||
|             data: JSON.stringify(data), | ||||
|             contentType: 'application/json' | ||||
|         }; | ||||
| 
 | ||||
|         // If the return_code is not 200 reject the Promise
 | ||||
|         return Promise.resolve(Ember.$.ajax(postRequest)) | ||||
|             .then(({return_code}) => return_code === 200 ? arguments[0] : Promise.reject(return_code)); | ||||
|     }, | ||||
| 
 | ||||
|     actions: { | ||||
|         setView: function(view) { | ||||
|             switch(view) { | ||||
| @ -437,6 +450,22 @@ App.DatasetController = Ember.Controller.extend({ | ||||
| 
 | ||||
|             _this.set('currentInstance', instance.dbId); | ||||
|             _this.refreshVersions(instance.dbId); | ||||
|         }, | ||||
|         /** | ||||
|          * Requests the security specification for the current dataset id | ||||
|          * and sets the result on the controller `securitySpec` | ||||
|          * @returns {Promise.<*>} | ||||
|          */ | ||||
|         getSecuritySpec() { | ||||
|             const securitySpecUrl = `api/v1/datasets/${this.get('datasetId')}/security`; | ||||
|             Ember.$.getJSON(securitySpecUrl, ({return_code, securitySpec}) => { | ||||
|                 if (return_code === 200 && typeof securitySpec === 'object') { | ||||
|                     this.set('securitySpec', securitySpec); | ||||
|                 } | ||||
|             }); | ||||
|         }, | ||||
|         saveSecuritySpec() { | ||||
|             this.saveJson(this.get('securitySpec')); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
|  | ||||
| @ -133,6 +133,69 @@ App.DatasetRoute = Ember.Route.extend({ | ||||
|     var source = ''; | ||||
|     var urn = ''; | ||||
|     var name = ''; | ||||
| 
 | ||||
|     function processSchema(schemaData) { | ||||
|       var concatName, | ||||
|           schemaArr = [], | ||||
|           typeArr = [], | ||||
|           schemaObj = {}; | ||||
| 
 | ||||
| 
 | ||||
|       function processSchemaChildren(rootName, fieldObj) { | ||||
|         var concatName, | ||||
|             subRootName; | ||||
| 
 | ||||
|         concatName = rootName + '.' + fieldObj.name; | ||||
|         if (typeof fieldObj.type !== 'object') { | ||||
|           schemaArr.push(concatName); | ||||
|           typeArr[concatName] = fieldObj.type; | ||||
|         } else { | ||||
|           if (typeof fieldObj.type[1] === 'string') { | ||||
|             schemaArr.push(concatName); | ||||
|             typeArr[concatName] = fieldObj.type[1]; | ||||
|           } else if (fieldObj.type.name) { | ||||
|             concatName = concatName + '.' + fieldObj.type.name; | ||||
|             schemaArr.push(concatName); | ||||
|             typeArr[concatName] = fieldObj.type.type; | ||||
|           } else if (typeof fieldObj.type[1].type === 'string' && !fieldObj.type[1].fields) { | ||||
|             concatName = concatName + '.' + fieldObj.type[1].name; | ||||
|             schemaArr.push(concatName); | ||||
|             typeArr[concatName] = fieldObj.type[1].type; | ||||
|           } else { | ||||
|             subRootName = concatName + '.' + fieldObj.type[1].name; | ||||
|             fieldObj.type[1].fields.forEach(function (subFieldObj) { | ||||
|               processSchemaChildren(subRootName, subFieldObj); | ||||
|             }); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       // Create typeahead-friendly array of fields
 | ||||
|       schemaData.fields.forEach(function (field) { | ||||
|         if (field.type.fields) { | ||||
|           concatNameRoot = field.name + '.' + field.type.name; | ||||
|           field.type.fields.forEach(function (fieldObj) { | ||||
|             processSchemaChildren(concatNameRoot, fieldObj); | ||||
|           }); | ||||
|         } else { | ||||
|           concatName = field.name; | ||||
|           schemaArr.push(concatName); | ||||
|           typeArr[concatName] = typeof field.type === 'string' ? field.type : field.type[this.length]; | ||||
|         } | ||||
|       }); | ||||
| 
 | ||||
|       schemaObj.fieldList = schemaArr; | ||||
|       schemaObj.typeArr = typeArr; | ||||
| 
 | ||||
|       return {fieldList: schemaArr, typeList: typeArr}; | ||||
|     } | ||||
| 
 | ||||
|     const {fieldList, typeList} = processSchema(JSON.parse(params.dataset.schema)); | ||||
|     controller.set('fieldList', fieldList); | ||||
|     controller.set('typeList', typeList); | ||||
|     controller.set('securitySpec', params.securitySpec); | ||||
| 
 | ||||
| 
 | ||||
|     controller.set("hasProperty", false); | ||||
| 
 | ||||
|     if (params && params.id) { | ||||
| @ -415,28 +478,6 @@ App.DatasetRoute = Ember.Route.extend({ | ||||
|                     } | ||||
|                 }); | ||||
| 
 | ||||
|     var datasetComplianceUrl = 'api/v1/datasets/' + id + "/security"; | ||||
| 
 | ||||
|     // Pull schema field chooser into DOM after all has rendered
 | ||||
|     setTimeout(function(){ | ||||
|       $('#schemaInput').insertAfter($('.cfheader .cfname')).removeClass('hide'); | ||||
|     }, 3000); | ||||
| 
 | ||||
|     // Fetch compliance API data and add to controller
 | ||||
|     $.get(datasetComplianceUrl, function(data) { | ||||
|       if (data && data.return_code === 200) { | ||||
|         if (data.securitySpec && data.securitySpec.complianceType) { | ||||
|           controller.set("hasCompliance", true); | ||||
|           controller.set("compliance", data.securitySpec); | ||||
|         } else { | ||||
|             controller.set("hasCompliance", false); | ||||
|         } | ||||
|       } else { | ||||
|         controller.set("hasCompliance", false); | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
| 
 | ||||
|     var datasetDependsUrl = 'api/v1/datasets/' + id + "/depends"; | ||||
|     $.get(datasetDependsUrl, function(data) { | ||||
|       if (data && data.status == "ok") | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Seyi Adebajo
						Seyi Adebajo