updates security specification on datasets to obtain data for gdpr related to each dataset

This commit is contained in:
Seyi Adebajo 2017-05-01 22:58:16 -07:00 committed by Mars Lan
parent 8388d41fa1
commit d9eb5c4d11
10 changed files with 177 additions and 19 deletions

View File

@ -1,6 +1,6 @@
import Ember from 'ember';
import isTrackingHeaderField from 'wherehows-web/utils/validators/tracking-headers';
import { defaultFieldDataTypeClassification, classifiers } from 'wherehows-web/constants';
import { defaultFieldDataTypeClassification, classifiers, datasetClassifiers } from 'wherehows-web/constants';
const {
get,
@ -14,15 +14,29 @@ const {
String: { htmlSafe }
} = Ember;
// String constant identifying the classified fields on the security spec
/**
* String constant identifying the classified fields on the security spec
* @type {String}
*/
const sourceClassificationKey = 'securitySpecification.classification';
/**
* String constant identifying the datasetClassification on the security spec
* @type {String}
*/
const datasetClassificationKey = 'securitySpecification.datasetClassification';
/**
* List of logical types / field level data types
* https://iwww.corp.linkedin.com/wiki/cf/display/DWH/List+of+Metadata+for+Data+Sets
* @type {Array}
*/
const logicalTypes = Object.keys(defaultFieldDataTypeClassification);
/**
* A list of available keys for the datasetClassification map on the security specification
* @type {Array}
*/
const datasetClassifiersKeys = Object.keys(datasetClassifiers);
// TODO: DSS-6671 Extract to constants module
const successUpdating = 'Your changes have been successfully saved!';
@ -69,6 +83,24 @@ export default Component.extend({
};
}),
/**
* Computed property that is dependent on all the keys in the datasetClassification map
* Returns a new map of datasetClassificationKey: String-> Object.<Boolean|undefined,String>
* @type {Ember.computed}
*/
datasetClassification: computed(`${datasetClassificationKey}.{${datasetClassifiersKeys.join(',')}}`, function() {
const sourceDatasetClassification = getWithDefault(this, datasetClassificationKey, {});
return Object.keys(datasetClassifiers).reduce((datasetClassification, classifier) => {
datasetClassification[classifier] = {
value: sourceDatasetClassification[classifier],
label: datasetClassifiers[classifier]
};
return datasetClassification;
}, {});
}),
/**
* Creates a lookup table of fieldNames to classification
* Also, the expectation is that the association from fieldName -> classification
@ -76,9 +108,7 @@ export default Component.extend({
* in the lookup assignment
*/
fieldNameToClass: computed(
`${sourceClassificationKey}.confidential.[]`,
`${sourceClassificationKey}.limitedDistribution.[]`,
`${sourceClassificationKey}.highlyConfidential.[]`,
`${sourceClassificationKey}.{confidential,limitedDistribution,highlyConfidential}.[]`,
function () {
const sourceClasses = getWithDefault(this, sourceClassificationKey, []);
// Creates a lookup table of fieldNames to classification
@ -105,9 +135,7 @@ export default Component.extend({
* securitySpecification.classification or null if not found
*/
classificationDataFields: computed(
`${sourceClassificationKey}.confidential.[]`,
`${sourceClassificationKey}.limitedDistribution.[]`,
`${sourceClassificationKey}.highlyConfidential.[]`,
`${sourceClassificationKey}.{confidential,limitedDistribution,highlyConfidential}.[]`,
'schemaFieldNamesMappedToDataTypes',
function () {
// Set default or if already in policy, retrieve current values from
@ -330,6 +358,16 @@ export default Component.extend({
}
},
/**
* Updates the source object representing the current datasetClassification map
* @param {String} classifier the property on the datasetClassification to update
* @param {Boolean} value flag indicating if this dataset contains member data for the specified classifier
*/
didChangeDatasetClassification(classifier, value) {
const sourceDatasetClassification = getWithDefault(this, datasetClassificationKey, {});
return set(sourceDatasetClassification, classifier, value);
},
/**
* Notify controller to propagate changes
* @return {Boolean}

View File

@ -0,0 +1,24 @@
const datasetClassifiers = {
CONNECTIONS_FOLLOWERS_FOLLOWING: 'Connections + Followers + Following',
PROFILE_DATA: 'Profile Data',
MESSAGING_DATA: 'Messaging Data (Metadata + Content)',
THIRD_PARTY_INTEGRATIONS: 'Third Party Integrations In Use',
ACTIVITY: 'Activity (Newsfeed Posts + Shares + Likes)',
JOB_APPLICATION_FLOW_DATA: 'Job Application Flow Data (Job Application + AWLI + Resumes + Application Answers)',
ENTERPRISE_PRODUCT_DATA: 'Enterprise Product Data',
ACCOUNT_STATUS: 'Account Status',
ADDRESS_BOOK_IMPORT_DATA: 'Address Book Import Data',
MICROSOFT_DATA: 'Data from Microsoft',
SUBSIDIARY_DATA: 'Data from companies LinkedIn acquired (Lynda, Slideshare, Connectifier, Bizo, etcetera)',
THIRD_PARTY_DATA: 'Data from Third-party Integrations',
DEVICE_DATA: 'Device Data',
SEARCH_HISTORY: 'Search History',
COURSE_VIEWING_HISTORY: 'Course Viewing History',
WVMP: "Who's Viewed My Profile",
PROFILE_VIEWS_BY_ME: 'Profile Views (by me)',
ADVERTISING_DATA: 'Advertising related (LMS) Data',
USAGE_ERROR_CONNECTIVITY_DATA: 'Usage, Error Reporting, Connectivity Data',
OTHER_CLICKSTREAM_BROWSING_DATA: 'Other Clickstream Data + Browsing history'
};
export { datasetClassifiers };

View File

@ -1 +1,2 @@
export * from 'wherehows-web/constants/metadata-acquisition';
export * from 'wherehows-web/constants/dataset-classification';

View File

@ -1,14 +1,9 @@
$user-name-width: 170px;
$zebra: set-color(grey, light);
.ownership-actions {
margin-bottom: 10px;
}
.dataset-author-row:nth-child(odd) {
background-color: tint($zebra, 80%);
}
.dataset-author-cell {
max-width: $user-name-width;
width: $user-name-width;

View File

@ -27,3 +27,25 @@
background-color: tint(set-color(green, green5), 65%);
}
}
/// Wraps a status notification icon for a given dataset
.dataset-tag-container {
width: 20px;
display: inline-block;
}
/// A notification that a dataset contains a tag
.dataset-tagged {
&::before {
color: set-color(green, green5);
}
}
/// A notification that a dataset does not contains a tag
.dataset-not-tagged {
&::before {
color: set-color(red, red5);
}
}

View File

@ -5,6 +5,7 @@
restyle-var(header-color): set-color(grey, light),
restyle-var(horizontal-padding): 10px,
restyle-var(font-size): 16px,
restyle-var(zebra): tint(set-color(grey, light), 80%),
border-collapse: collapse,
border-spacing: 0,
text-align: left,
@ -30,6 +31,11 @@
restyle-modifiers: (
'with a border': (
border: 1px solid restyle-var(border-color)
),
'with stripped rows': (
'& tr:nth-child(odd)': (
background-color: restyle-var(zebra)
)
)
)
));
@ -40,4 +46,8 @@
&--bordered {
@include restyle(table with a border);
}
&--stripped {
@include restyle(table with stripped rows);
}
}

View File

@ -60,7 +60,7 @@
<div class="alert alert-success" role="alert">{{actionMessage}}</div>
{{/if}}
<table class="nacho-table nacho-table--bordered">
<table class="nacho-table nacho-table--bordered nacho-table--stripped">
<thead>
<tr>
<th>User Name</th>
@ -111,8 +111,8 @@
<td>{{owner.idType}}</td>
<td>{{owner.source}}</td>
<td class="dataset-author-cell__owner-last-mod">
{{!-- e.g Jul 18th '16, 11:11 am --}}
{{moment-calendar owner.modifiedTime sameElse="MMM Do 'YY, h:m a"}}
{{!-- e.g Jul 18th 2016, 11:11 am --}}
{{moment-calendar owner.modifiedTime sameElse="MMM Do YYYY, h:mm a"}}
</td>
<td>
{{ember-selector

View File

@ -26,6 +26,73 @@
</div>
{{/if}}
<section class="metadata-prompt">
<header class="metadata-prompt__header">
<p>
This dataset contains the following types of Member data:
<!--TODO: DSS-6716-->
<!-- DRY out with wrapper component that takes the link as an attribute-->
<a
target="_blank"
href="https://iwww.corp.linkedin.com/wiki/cf/display/DWH/List+of+Metadata+for+Data+Sets">
<sup>
More Info
<span class="glyphicon glyphicon-question-sign"
title="More information on Dataset classification with examples"></span>
</sup>
</a>
</p>
</header>
</section>
<table class="nacho-table nacho-table--bordered nacho-table--stripped">
<thead>
<tr>
<th>Dataset Content Type</th>
<th>Is this type of member data contained in this dataset?</th>
</tr>
</thead>
<tbody>
{{#each-in datasetClassification as |classification props|}}
<tr>
<td>
{{props.label}}
<span class="dataset-tag-container">
{{#if (eq props.value true)}}
<i class="glyphicon glyphicon-ok dataset-tagged" title="{{props.label}} is in dataset"></i>
{{/if}}
{{#if (eq props.value false)}}
<i class="glyphicon glyphicon-remove dataset-not-tagged" title="{{props.label}} is not in dataset"></i>
{{/if}}
</span>
</td>
<td>
{{#radio-button-composer
value=true
name=classification
groupValue=(readonly props.value)
changed=(action "didChangeDatasetClassification")}}
Yes
{{/radio-button-composer}}
{{#radio-button-composer
value=false
name=classification
groupValue=(readonly props.value)
changed=(action "didChangeDatasetClassification")}}
No
{{/radio-button-composer}}
</td>
</tr>
{{/each-in}}
</tbody>
</table>
<br>
{{!--Renders content of `hiddenTrackingFields` to the viewer if the dataset contains hidden tracking fields--}}
{{#if containsHiddenTrackingFields}}
<div class="alert alert-info" role="alert">

View File

@ -31,7 +31,8 @@ const createSecuritySpecification = id => {
datasetId: id,
geographicAffinity: { affinity: '' },
recordOwnerType: '',
retentionPolicy: { retentionType: '' }
retentionPolicy: { retentionType: '' },
datasetClassification: {}
};
return JSON.parse(JSON.stringify(securitySpecification));

View File

@ -1,10 +1,10 @@
/**
* Matches a url string with a `urn` query. urn query with letters or underscore segment of any length greater
* than 1 followed by colon and 3 forward slashes and a segment containing letters, _ or /, or none
* than 1 followed by colon and 3 forward slashes and a segment containing letters, {, }, _ or /, or none
* The value following the urn key is retained
* @type {RegExp}
*/
const urnRegex = /([a-z_]+):\/{3}([a-z0-9_\-\/]*)/i;
const urnRegex = /([a-z_]+):\/{3}([a-z0-9_\-\/\{\}]*)/i;
/**
* Asserts that a provided string matches the urn pattern above
* @param {String} candidateUrn the string to test on