mirror of
https://github.com/datahub-project/datahub.git
synced 2025-10-28 17:33:04 +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>
|
||||
</select>
|
||||
</script>
|
||||
|
||||
<script type="text/x-handlebars" id="components/dataset-schema">
|
||||
{{#if hasSchemas}}
|
||||
@ -685,12 +679,19 @@
|
||||
</a>
|
||||
</li>
|
||||
<li id="compliance">
|
||||
<a data-toggle="tab"
|
||||
title="Compliance"
|
||||
href="#compliancetab">
|
||||
Compliance
|
||||
</a>
|
||||
<a data-toggle="tab"
|
||||
title="Compliance"
|
||||
href="#compliancetab">
|
||||
Compliance
|
||||
</a>
|
||||
</li>
|
||||
<li id="confidential">
|
||||
<a data-toggle="tab"
|
||||
title="Confidential"
|
||||
href="#confidentialtab">
|
||||
Confidential
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
{{#unless isPinot}}
|
||||
@ -733,10 +734,22 @@
|
||||
{{#dataset-access hasAccess=hasAccess accessibilities=accessibilities}}
|
||||
{{/dataset-access}}
|
||||
</div>
|
||||
<div id="compliancetab" class="tab-pane">
|
||||
{{#dataset-compliance hasCompliance=hasCompliance compliance=compliance}}
|
||||
{{/dataset-compliance}}
|
||||
</div>
|
||||
<div id="compliancetab" class="tab-pane">
|
||||
{{#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">
|
||||
@ -897,13 +910,39 @@
|
||||
</h4>
|
||||
</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}}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
{{#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">
|
||||
@ -1363,86 +1363,171 @@
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-handlebars" id="components/dataset-compliance">
|
||||
<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>
|
||||
|
||||
<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>
|
||||
{{else}}
|
||||
<div class="prop-value">{{item.value}}</div>
|
||||
{{/if}}
|
||||
<div class="field-search">
|
||||
<input type="text"
|
||||
id="compliance-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">
|
||||
<div class="panel-heading">
|
||||
<h4>Purgeable Ids</h4>
|
||||
<sub>Add items by dragging to relevant cells below</sub>
|
||||
</div>
|
||||
<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>
|
||||
{{/each}}
|
||||
</div>
|
||||
-->
|
||||
</script>
|
||||
|
||||
{{else}}
|
||||
<p>Compliance is not available</p>
|
||||
{{/if}}
|
||||
<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>
|
||||
|
||||
</script>
|
||||
<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">
|
||||
<i
|
||||
|
||||
@ -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