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 Ember from 'ember';
import isTrackingHeaderField from 'wherehows-web/utils/validators/tracking-headers'; 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 { const {
get, get,
@ -14,15 +14,29 @@ const {
String: { htmlSafe } String: { htmlSafe }
} = Ember; } = 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'; 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 * List of logical types / field level data types
* https://iwww.corp.linkedin.com/wiki/cf/display/DWH/List+of+Metadata+for+Data+Sets * https://iwww.corp.linkedin.com/wiki/cf/display/DWH/List+of+Metadata+for+Data+Sets
* @type {Array} * @type {Array}
*/ */
const logicalTypes = Object.keys(defaultFieldDataTypeClassification); 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 // TODO: DSS-6671 Extract to constants module
const successUpdating = 'Your changes have been successfully saved!'; 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 * Creates a lookup table of fieldNames to classification
* Also, the expectation is that the association from fieldName -> classification * Also, the expectation is that the association from fieldName -> classification
@ -76,9 +108,7 @@ export default Component.extend({
* in the lookup assignment * in the lookup assignment
*/ */
fieldNameToClass: computed( fieldNameToClass: computed(
`${sourceClassificationKey}.confidential.[]`, `${sourceClassificationKey}.{confidential,limitedDistribution,highlyConfidential}.[]`,
`${sourceClassificationKey}.limitedDistribution.[]`,
`${sourceClassificationKey}.highlyConfidential.[]`,
function () { function () {
const sourceClasses = getWithDefault(this, sourceClassificationKey, []); const sourceClasses = getWithDefault(this, sourceClassificationKey, []);
// Creates a lookup table of fieldNames to classification // Creates a lookup table of fieldNames to classification
@ -105,9 +135,7 @@ export default Component.extend({
* securitySpecification.classification or null if not found * securitySpecification.classification or null if not found
*/ */
classificationDataFields: computed( classificationDataFields: computed(
`${sourceClassificationKey}.confidential.[]`, `${sourceClassificationKey}.{confidential,limitedDistribution,highlyConfidential}.[]`,
`${sourceClassificationKey}.limitedDistribution.[]`,
`${sourceClassificationKey}.highlyConfidential.[]`,
'schemaFieldNamesMappedToDataTypes', 'schemaFieldNamesMappedToDataTypes',
function () { function () {
// Set default or if already in policy, retrieve current values from // 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 * Notify controller to propagate changes
* @return {Boolean} * @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/metadata-acquisition';
export * from 'wherehows-web/constants/dataset-classification';

View File

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

View File

@ -27,3 +27,25 @@
background-color: tint(set-color(green, green5), 65%); 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(header-color): set-color(grey, light),
restyle-var(horizontal-padding): 10px, restyle-var(horizontal-padding): 10px,
restyle-var(font-size): 16px, restyle-var(font-size): 16px,
restyle-var(zebra): tint(set-color(grey, light), 80%),
border-collapse: collapse, border-collapse: collapse,
border-spacing: 0, border-spacing: 0,
text-align: left, text-align: left,
@ -30,6 +31,11 @@
restyle-modifiers: ( restyle-modifiers: (
'with a border': ( 'with a border': (
border: 1px solid restyle-var(border-color) border: 1px solid restyle-var(border-color)
),
'with stripped rows': (
'& tr:nth-child(odd)': (
background-color: restyle-var(zebra)
)
) )
) )
)); ));
@ -40,4 +46,8 @@
&--bordered { &--bordered {
@include restyle(table with a border); @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> <div class="alert alert-success" role="alert">{{actionMessage}}</div>
{{/if}} {{/if}}
<table class="nacho-table nacho-table--bordered"> <table class="nacho-table nacho-table--bordered nacho-table--stripped">
<thead> <thead>
<tr> <tr>
<th>User Name</th> <th>User Name</th>
@ -111,8 +111,8 @@
<td>{{owner.idType}}</td> <td>{{owner.idType}}</td>
<td>{{owner.source}}</td> <td>{{owner.source}}</td>
<td class="dataset-author-cell__owner-last-mod"> <td class="dataset-author-cell__owner-last-mod">
{{!-- e.g Jul 18th '16, 11:11 am --}} {{!-- e.g Jul 18th 2016, 11:11 am --}}
{{moment-calendar owner.modifiedTime sameElse="MMM Do 'YY, h:m a"}} {{moment-calendar owner.modifiedTime sameElse="MMM Do YYYY, h:mm a"}}
</td> </td>
<td> <td>
{{ember-selector {{ember-selector

View File

@ -26,6 +26,73 @@
</div> </div>
{{/if}} {{/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--}} {{!--Renders content of `hiddenTrackingFields` to the viewer if the dataset contains hidden tracking fields--}}
{{#if containsHiddenTrackingFields}} {{#if containsHiddenTrackingFields}}
<div class="alert alert-info" role="alert"> <div class="alert alert-info" role="alert">

View File

@ -31,7 +31,8 @@ const createSecuritySpecification = id => {
datasetId: id, datasetId: id,
geographicAffinity: { affinity: '' }, geographicAffinity: { affinity: '' },
recordOwnerType: '', recordOwnerType: '',
retentionPolicy: { retentionType: '' } retentionPolicy: { retentionType: '' },
datasetClassification: {}
}; };
return JSON.parse(JSON.stringify(securitySpecification)); 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 * 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 * The value following the urn key is retained
* @type {RegExp} * @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 * Asserts that a provided string matches the urn pattern above
* @param {String} candidateUrn the string to test on * @param {String} candidateUrn the string to test on