import Ember from 'ember';
import isTrackingHeaderField from 'wherehows-web/utils/validators/tracking-headers';
import { defaultFieldDataTypeClassification, classifiers, datasetClassifiers } from 'wherehows-web/constants';
const {
get,
set,
isBlank,
isPresent,
computed,
getWithDefault,
setProperties,
Component,
String: { htmlSafe }
} = Ember;
/**
* 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!';
const failedUpdating = 'Oops! We are having trouble updating this dataset at the moment.';
const missingTypes = 'Looks like some fields are marked as `Confidential` or ' +
'`Highly Confidential` but do not have a specified `Field Format`?';
const hiddenTrackingFieldsMsg = htmlSafe(
'
Hey! Just a heads up that some fields in this dataset have been hidden from the table(s) below. ' +
'These are tracking fields for which we\'ve been able to predetermine the compliance classification.
' +
'
For example: header.memberId, requestHeader. ' +
'Hopefully, this saves you some scrolling!
'
);
/**
* Takes a string, returns a formatted string. Niche , single use case
* for now, so no need to make into a helper
* @param {String} string
*/
const formatAsCapitalizedStringWithSpaces = string =>
string.replace(/[A-Z]/g, match => ` ${match}`).capitalize();
export default Component.extend({
sortColumnWithName: 'identifierField',
filterBy: 'identifierField',
sortDirection: 'asc',
searchTerm: '',
hiddenTrackingFields: hiddenTrackingFieldsMsg,
// Map classifiers to options better consumed by drop down
classifiers: classifiers.map(value => ({
value,
label: formatAsCapitalizedStringWithSpaces(value)
})),
// Map logicalTypes to options better consumed by drop down
logicalTypes: ['', ...logicalTypes].map(value => {
const label = value ?
value.replace(/_/g, ' ')
.replace(/([A-Z]{3,})/g, f => f.toLowerCase().capitalize()) :
'Not Specified';
return {
value,
label
};
}),
/**
* Checks that all tags/ dataset content types have a boolean value
* @type {Ember.computed}
*/
isDatasetFullyClassified: computed('datasetClassification', function() {
const datasetClassification = get(this, 'datasetClassification');
return Object.keys(datasetClassification)
.map(key => ({ value: datasetClassification[key].value }))
.every(({ value }) => [true, false].includes(value));
}),
/**
* Computed property that is dependent on all the keys in the datasetClassification map
* Returns a new map of datasetClassificationKey: String-> Object.
* @type {Ember.computed}
*/
datasetClassification: computed(`${datasetClassificationKey}.{${datasetClassifiersKeys.join(',')}}`, function() {
const sourceDatasetClassification = get(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
* is one-to-one hence no check to ensure a fieldName gets clobbered
* in the lookup assignment
*/
fieldNameToClass: computed(
`${sourceClassificationKey}.{confidential,limitedDistribution,highlyConfidential}.[]`,
function () {
const sourceClasses = getWithDefault(this, sourceClassificationKey, []);
// Creates a lookup table of fieldNames to classification
// Also, the expectation is that the association from fieldName -> classification
// is one-to-one hence no check to ensure a fieldName gets clobbered
// in the lookup assignment
return Object.keys(sourceClasses)
.reduce((lookup, classificationKey) =>
// For the provided classificationKey, iterate over it's fieldNames,
// and assign the classificationKey to the fieldName in the table
(sourceClasses[classificationKey] || []).reduce((lookup, field) => {
const { identifierField } = field;
// cKey -> 1...fieldNameList => fieldName -> cKey
lookup[identifierField] = classificationKey;
return lookup;
}, lookup),
{}
);
}),
/**
* Lists all the dataset fields found in the `columns` api, and intersects
* each with the currently classified field names in
* securitySpecification.classification or null if not found
*/
classificationDataFields: computed(
`${sourceClassificationKey}.{confidential,limitedDistribution,highlyConfidential}.[]`,
'schemaFieldNamesMappedToDataTypes',
function () {
// Set default or if already in policy, retrieve current values from
// privacyCompliancePolicy.compliancePurgeEntities
return getWithDefault(
this, 'schemaFieldNamesMappedToDataTypes', []
).map(({ fieldName: identifierField, dataType }) => {
// Get the current classification list
const currentClassLookup = get(this, 'fieldNameToClass');
const classification = currentClassLookup[identifierField];
// If the classification type exists, then find the identifierField, and
// assign to field, otherwise null
// Rather than assigning the default classification here, nulling gives the benefit of allowing
// subsequent consumer know that this field did not have a previous classification
const field = classification ?
get(this, `${sourceClassificationKey}.${classification}`)
.findBy('identifierField', identifierField) :
null;
// Extract the logicalType from the field
const logicalType = isPresent(field) ? field.logicalType : null;
// Map to a new literal containing these props
return {
dataType,
identifierField,
classification,
logicalType
};
});
}
),
/**
* @type {Boolean} cached boolean flag indicating that fields do contain a `kafka type`
* tracking header.
* Used to indicate to viewer that these fields are hidden.
*/
containsHiddenTrackingFields: computed(
'classificationDataFieldsSansHiddenTracking.length',
function () {
// If their is a diff in complianceDataFields and complianceDataFieldsSansHiddenTracking,
// then we have hidden tracking fields
return get(this, 'classificationDataFieldsSansHiddenTracking.length') !== get(this, 'classificationDataFields.length');
}),
/**
* @type {Array.