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:
Seyi Adebajo 2016-10-10 09:29:16 -07:00 committed by Mars Lan
parent 36e583b19e
commit 1a974c2d38
6 changed files with 686 additions and 142 deletions

2
.gitignore vendored
View File

@ -19,3 +19,5 @@ bin/
backend-service/lib
metadata-etl/src/main/resources/application.properties
**/test/resources/*.properties
logs/
.DS_Store

View File

@ -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>

View File

@ -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

View File

@ -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({
})
}
}
})
});

View File

@ -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'));
}
}
});

View File

@ -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")