2017-02-13 14:51:31 -08:00
|
|
|
import Ember from 'ember';
|
2017-04-25 10:50:02 -07:00
|
|
|
import isTrackingHeaderField from 'wherehows-web/utils/validators/tracking-headers';
|
2017-05-19 08:45:05 -07:00
|
|
|
import {
|
2017-12-06 10:23:33 -08:00
|
|
|
securityClassificationDropdownOptions,
|
2017-10-16 11:35:13 -07:00
|
|
|
DatasetClassifiers,
|
2017-05-19 08:45:05 -07:00
|
|
|
fieldIdentifierTypes,
|
2017-12-12 14:20:19 -08:00
|
|
|
getFieldIdentifierOptions,
|
2017-05-19 08:45:05 -07:00
|
|
|
idLogicalTypes,
|
2017-05-23 12:18:55 -07:00
|
|
|
nonIdFieldLogicalTypes,
|
2017-12-12 22:19:01 -08:00
|
|
|
getDefaultSecurityClassification,
|
2017-09-18 16:13:30 -07:00
|
|
|
compliancePolicyStrings,
|
|
|
|
logicalTypesForIds,
|
|
|
|
hasPredefinedFieldFormat,
|
2017-10-20 21:34:58 -07:00
|
|
|
getDefaultLogicalType,
|
2017-12-06 10:23:33 -08:00
|
|
|
getComplianceSteps,
|
2017-10-20 21:34:58 -07:00
|
|
|
hiddenTrackingFields,
|
|
|
|
isExempt
|
2017-05-19 08:45:05 -07:00
|
|
|
} from 'wherehows-web/constants';
|
2017-09-27 09:33:20 -07:00
|
|
|
import {
|
|
|
|
isPolicyExpectedShape,
|
|
|
|
fieldChangeSetRequiresReview,
|
|
|
|
mergeMappedColumnFieldsWithSuggestions
|
|
|
|
} from 'wherehows-web/utils/datasets/compliance-policy';
|
2017-08-07 22:21:05 -07:00
|
|
|
import scrollMonitor from 'scrollmonitor';
|
2017-09-27 09:33:20 -07:00
|
|
|
import { hasEnumerableKeys } from 'wherehows-web/utils/object';
|
|
|
|
import { arrayFilter, isListUnique } from 'wherehows-web/utils/array';
|
2017-10-20 21:34:58 -07:00
|
|
|
import noop from 'wherehows-web/utils/noop';
|
2017-06-01 13:11:26 -07:00
|
|
|
|
|
|
|
const {
|
|
|
|
Component,
|
|
|
|
computed,
|
2017-10-20 21:34:58 -07:00
|
|
|
computed: { gt },
|
2017-06-01 13:11:26 -07:00
|
|
|
set,
|
|
|
|
get,
|
2017-10-20 21:34:58 -07:00
|
|
|
run,
|
2017-06-01 13:11:26 -07:00
|
|
|
setProperties,
|
|
|
|
getProperties,
|
|
|
|
getWithDefault,
|
2017-10-20 21:34:58 -07:00
|
|
|
String: { classify },
|
2017-08-28 01:38:47 -07:00
|
|
|
inject: { service }
|
2017-06-01 13:11:26 -07:00
|
|
|
} = Ember;
|
2017-03-24 20:27:43 -07:00
|
|
|
|
2017-10-20 21:34:58 -07:00
|
|
|
const { schedule } = run;
|
2017-08-30 10:26:44 -07:00
|
|
|
const {
|
|
|
|
complianceDataException,
|
|
|
|
missingTypes,
|
|
|
|
successUpdating,
|
|
|
|
failedUpdating,
|
|
|
|
helpText,
|
|
|
|
successUploading,
|
|
|
|
invalidPolicyData
|
|
|
|
} = compliancePolicyStrings;
|
2017-07-18 15:01:28 -07:00
|
|
|
|
2017-12-12 17:47:28 -08:00
|
|
|
/**
|
|
|
|
* Takes a list of compliance data types and maps a list of compliance id's with idType set to true
|
|
|
|
* @param {Array<IComplianceDataType>} [complianceDataTypes=[]] the list of compliance data types to transform
|
|
|
|
* @return {Array<ComplianceFieldIdValue>}
|
|
|
|
*/
|
|
|
|
const getIdTypeDataTypes = (complianceDataTypes = []) =>
|
|
|
|
complianceDataTypes.filter(complianceDataType => complianceDataType.idType).mapBy('id');
|
|
|
|
|
2017-05-23 12:18:55 -07:00
|
|
|
/**
|
|
|
|
* List of non Id field data type classifications
|
|
|
|
* @type {Array}
|
|
|
|
*/
|
|
|
|
const genericLogicalTypes = Object.keys(nonIdFieldLogicalTypes).sort();
|
|
|
|
|
2017-05-19 08:45:05 -07:00
|
|
|
/**
|
|
|
|
* String constant referencing the datasetClassification on the privacy policy
|
|
|
|
* @type {String}
|
|
|
|
*/
|
|
|
|
const datasetClassificationKey = 'complianceInfo.datasetClassification';
|
|
|
|
/**
|
|
|
|
* A list of available keys for the datasetClassification map on the security specification
|
2017-10-16 11:35:13 -07:00
|
|
|
* @type {Array<keyof typeof DatasetClassifiers>}
|
2017-05-19 08:45:05 -07:00
|
|
|
*/
|
2017-10-16 11:35:13 -07:00
|
|
|
const datasetClassifiersKeys = Object.keys(DatasetClassifiers);
|
2017-05-19 08:45:05 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* A reference to the compliance policy entities on the complianceInfo map
|
|
|
|
* @type {string}
|
|
|
|
*/
|
|
|
|
const policyComplianceEntitiesKey = 'complianceInfo.complianceEntities';
|
2017-03-24 20:27:43 -07:00
|
|
|
|
2017-09-27 09:33:20 -07:00
|
|
|
/**
|
|
|
|
* Returns a list of changeSet fields that requires user attention
|
|
|
|
* @type {function({}): Array<{ isDirty, suggestion, privacyPolicyExists, suggestionAuthority }>}
|
|
|
|
*/
|
|
|
|
const changeSetFieldsRequiringReview = arrayFilter(fieldChangeSetRequiresReview);
|
|
|
|
|
2017-10-20 21:34:58 -07:00
|
|
|
/**
|
|
|
|
* The initial state of the compliance step for a zero based array
|
|
|
|
* @type {number}
|
|
|
|
*/
|
|
|
|
const initialStepIndex = -1;
|
|
|
|
|
2017-03-24 20:27:43 -07:00
|
|
|
export default Component.extend({
|
2017-03-27 20:12:09 -07:00
|
|
|
sortColumnWithName: 'identifierField',
|
2017-03-27 18:26:09 -07:00
|
|
|
filterBy: 'identifierField',
|
2017-03-24 20:27:43 -07:00
|
|
|
sortDirection: 'asc',
|
2017-02-13 14:51:31 -08:00
|
|
|
searchTerm: '',
|
2017-05-19 08:45:05 -07:00
|
|
|
helpText,
|
2017-10-20 21:34:58 -07:00
|
|
|
hiddenTrackingFields,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Tracks the current index of the
|
|
|
|
* @type {number}
|
|
|
|
*/
|
|
|
|
editStepIndex: initialStepIndex,
|
2017-12-06 10:23:33 -08:00
|
|
|
|
2017-12-12 22:19:01 -08:00
|
|
|
/**
|
|
|
|
* List of complianceDataType values, defaults to an empty list
|
|
|
|
* @type {Array<IComplianceDataType>}
|
|
|
|
*/
|
|
|
|
complianceDataTypes: [],
|
|
|
|
|
2017-10-20 21:34:58 -07:00
|
|
|
/**
|
|
|
|
* Converts the hash of complianceSteps to a list of steps
|
2017-12-06 10:23:33 -08:00
|
|
|
* @type {ComputedProperty<Array<{}>>}
|
2017-10-20 21:34:58 -07:00
|
|
|
*/
|
2017-12-06 10:23:33 -08:00
|
|
|
editSteps: computed('schemaless', function() {
|
|
|
|
const hasSchema = !getWithDefault(this, 'schemaless', false);
|
|
|
|
const steps = getComplianceSteps(hasSchema);
|
|
|
|
|
|
|
|
// Ensure correct step ordering
|
|
|
|
return Object.keys(steps)
|
|
|
|
.sort()
|
|
|
|
.map(key => steps[key]);
|
|
|
|
}),
|
2017-10-20 21:34:58 -07:00
|
|
|
|
2017-12-12 14:20:19 -08:00
|
|
|
/**
|
|
|
|
* Reads the complianceDataTypes property and transforms into a list of drop down options for the field
|
|
|
|
* identifier type
|
|
|
|
* @type {ComputedProperty<Array<IComplianceFieldIdentifierOption>>}
|
|
|
|
*/
|
|
|
|
complianceFieldIdDropdownOptions: computed('complianceDataTypes', function() {
|
|
|
|
return getFieldIdentifierOptions(get(this, 'complianceDataTypes'));
|
|
|
|
}),
|
|
|
|
|
2017-10-20 21:34:58 -07:00
|
|
|
/**
|
|
|
|
* Handles the transition between steps in the compliance edit wizard
|
|
|
|
* including performing each step's post processing action once a user has
|
|
|
|
* completed a step
|
|
|
|
* @type {Ember.ComputedProperty}
|
|
|
|
* @returns {object} the editStep
|
|
|
|
* TODO: improve ergonomics by enabling async awareness in the templates
|
|
|
|
* visually, step transition happens before the post step action is actually completed,
|
|
|
|
* even though this is reversed if the object is rejected
|
|
|
|
*/
|
|
|
|
editStep: computed(
|
|
|
|
'editStepIndex',
|
|
|
|
(function() {
|
|
|
|
// initialize the previous action with a no-op function
|
|
|
|
let previousAction = noop;
|
|
|
|
// initialize the last seen index to the same value as editStepIndex
|
|
|
|
let lastIndex = initialStepIndex;
|
|
|
|
|
|
|
|
return function() {
|
2017-12-04 00:12:48 -08:00
|
|
|
const { editStepIndex: currentIndex, editSteps } = getProperties(this, ['editStepIndex', 'editSteps']);
|
2017-10-20 21:34:58 -07:00
|
|
|
// the current step in the edit sequence
|
2017-12-04 00:12:48 -08:00
|
|
|
const editStep = editSteps[currentIndex] || {};
|
2017-10-20 21:34:58 -07:00
|
|
|
const { name } = editStep;
|
|
|
|
|
|
|
|
if (name) {
|
|
|
|
// using the steps name, construct a reference to the step process handler
|
|
|
|
const nextAction = this.actions[`did${classify(name)}`];
|
|
|
|
let previousActionResult;
|
|
|
|
|
|
|
|
// if the transition is backward, then the previous action is ignored
|
|
|
|
currentIndex > lastIndex && (previousActionResult = previousAction.call(this));
|
|
|
|
lastIndex = currentIndex;
|
|
|
|
|
|
|
|
Promise.resolve(previousActionResult)
|
|
|
|
.then(() => {
|
|
|
|
// if the previous action is resolved successfully, then replace with the next processor
|
|
|
|
if (typeof nextAction === 'function') {
|
|
|
|
return (previousAction = nextAction);
|
|
|
|
}
|
2017-12-06 10:23:33 -08:00
|
|
|
// otherwise clear the previous action
|
2017-10-20 21:34:58 -07:00
|
|
|
previousAction = noop;
|
|
|
|
})
|
|
|
|
.catch(() => {
|
|
|
|
// if the previous action settles in a rejected state, replace with no-op before
|
|
|
|
// invoking the previousStep action to go back in the sequence
|
|
|
|
// batch previousStep invocation in a afterRender queue due to editStepIndex update
|
|
|
|
previousAction = noop;
|
|
|
|
run(() => {
|
|
|
|
if (this.isDestroyed || this.isDestroyed) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
schedule('afterRender', this, this.actions.previousStep);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return editStep;
|
|
|
|
};
|
|
|
|
})()
|
|
|
|
),
|
|
|
|
|
2017-06-05 09:49:37 -07:00
|
|
|
classNames: ['compliance-container'],
|
2017-12-12 22:19:01 -08:00
|
|
|
|
2017-06-05 09:49:37 -07:00
|
|
|
classNameBindings: ['isEditing:compliance-container--edit-mode'],
|
2017-10-20 21:34:58 -07:00
|
|
|
|
2017-07-18 15:01:28 -07:00
|
|
|
/**
|
|
|
|
* Flag indicating that the component is in edit mode
|
2017-10-20 21:34:58 -07:00
|
|
|
* @type {Ember.ComputedProperty}
|
|
|
|
* @return {boolean}
|
2017-07-18 15:01:28 -07:00
|
|
|
*/
|
2017-10-20 21:34:58 -07:00
|
|
|
isEditing: gt('editStepIndex', initialStepIndex),
|
2017-08-29 16:50:44 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Convenience flag indicating the policy is not currently being edited
|
|
|
|
* @type {Ember.computed}
|
|
|
|
* @return {boolean}
|
|
|
|
*/
|
|
|
|
isReadOnly: computed.not('isEditing'),
|
2017-09-27 09:33:20 -07:00
|
|
|
|
2017-07-18 15:01:28 -07:00
|
|
|
/**
|
|
|
|
* Flag indicating that the component is currently saving / attempting to save the privacy policy
|
|
|
|
* @type {String}
|
|
|
|
*/
|
|
|
|
isSaving: false,
|
2017-03-24 20:27:43 -07:00
|
|
|
|
2017-09-27 09:33:20 -07:00
|
|
|
/**
|
|
|
|
* Returns a list of ui values and labels for review filter drop-down
|
2017-11-22 07:38:12 -08:00
|
|
|
* @type {Ember.ComputedProperty<{value: string, label:string}>}
|
2017-09-27 09:33:20 -07:00
|
|
|
*/
|
|
|
|
fieldReviewOptions: computed(function() {
|
|
|
|
return [
|
|
|
|
{ value: 'showAll', label: 'Showing all fields' },
|
|
|
|
{
|
|
|
|
value: 'showReview',
|
|
|
|
label: 'Showing only fields to review'
|
|
|
|
}
|
|
|
|
];
|
|
|
|
}),
|
|
|
|
|
|
|
|
/**
|
2017-11-22 07:38:12 -08:00
|
|
|
* Default to show all fields to review
|
2017-09-27 09:33:20 -07:00
|
|
|
* @type {string}
|
|
|
|
*/
|
2017-11-22 07:38:12 -08:00
|
|
|
fieldReviewOption: 'showAll',
|
2017-09-27 09:33:20 -07:00
|
|
|
|
2017-08-28 01:38:47 -07:00
|
|
|
/**
|
|
|
|
* Reference to the application notifications Service
|
|
|
|
* @type {Ember.Service}
|
|
|
|
*/
|
|
|
|
notifications: service(),
|
|
|
|
|
2017-03-30 15:08:12 -07:00
|
|
|
didReceiveAttrs() {
|
2017-08-29 16:50:44 -07:00
|
|
|
this._super(...Array.from(arguments));
|
2017-03-30 15:08:12 -07:00
|
|
|
// Perform validation step on the received component attributes
|
|
|
|
this.validateAttrs();
|
2017-12-06 10:23:33 -08:00
|
|
|
|
|
|
|
// Set the current step to first edit step if compliance policy is new / doesn't exist
|
|
|
|
if (get(this, 'isNewComplianceInfo')) {
|
|
|
|
this.updateStep(0);
|
|
|
|
}
|
2017-03-30 15:08:12 -07:00
|
|
|
},
|
|
|
|
|
2017-08-07 22:21:05 -07:00
|
|
|
/**
|
|
|
|
* @override
|
|
|
|
*/
|
|
|
|
didRender() {
|
|
|
|
this._super(...arguments);
|
|
|
|
// Hides DOM elements that are not currently visible in the UI and unhides them once the user scrolls the
|
|
|
|
// elements into view
|
|
|
|
this.enableDomCloaking();
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A `lite` / intermediary step to occlusion culling, this helps to improve the rendering of
|
|
|
|
* elements that are currently rendered in the viewport by hiding that aren't.
|
|
|
|
* Setting them to visibility hidden doesn't remove them from the document flow, but the browser
|
|
|
|
* doesn't have to deal with layout for the affected elements since they are off-screen
|
|
|
|
*/
|
|
|
|
enableDomCloaking() {
|
|
|
|
const [dom] = this.$('.dataset-compliance-fields');
|
|
|
|
const triggerCount = 100;
|
|
|
|
if (dom) {
|
|
|
|
const rows = dom.querySelectorAll('tbody tr');
|
|
|
|
|
|
|
|
// if we already have watchers for elements, or if the elements previously cached are no longer valid,
|
|
|
|
// e.g. those elements were destroyed when new data was received, pagination etc
|
|
|
|
if (rows.length > triggerCount && (!this.complianceWatchers || !this.complianceWatchers.has(rows[0]))) {
|
|
|
|
/**
|
|
|
|
* If an item is not in the viewport add a class to occlude it
|
|
|
|
*/
|
|
|
|
const cloaker = function() {
|
|
|
|
if (!this.isInViewport) {
|
|
|
|
return this.watchItem.classList.add('compliance-row--off-screen');
|
|
|
|
}
|
|
|
|
this.watchItem.classList.remove('compliance-row--off-screen');
|
|
|
|
};
|
|
|
|
this.watchers = [];
|
|
|
|
|
|
|
|
// Retain a weak reference to DOM nodes
|
|
|
|
this.complianceWatchers = new WeakMap(
|
|
|
|
[...rows].map(row => {
|
|
|
|
const watcher = scrollMonitor.create(row);
|
|
|
|
watcher['stateChange'](cloaker);
|
|
|
|
cloaker.call(watcher);
|
|
|
|
this.watchers = [...this.watchers, watcher];
|
|
|
|
|
|
|
|
return [watcher.watchItem, watcher];
|
|
|
|
})
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Cleans up the artifacts from the dom cloaking operation, drops references held by scroll monitor
|
|
|
|
*/
|
|
|
|
disableDomCloaking() {
|
|
|
|
if (!this.watchers || !Array.isArray(this.watchers)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.watchers.forEach(watcher => watcher.destroy());
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @override
|
|
|
|
*/
|
|
|
|
willDestroyElement() {
|
|
|
|
this.disableDomCloaking();
|
|
|
|
},
|
|
|
|
|
2017-03-30 15:08:12 -07:00
|
|
|
/**
|
|
|
|
* Ensure that props received from on this component
|
|
|
|
* are valid, otherwise flag
|
|
|
|
*/
|
|
|
|
validateAttrs() {
|
2017-05-01 09:57:17 -07:00
|
|
|
const fieldNames = getWithDefault(this, 'schemaFieldNamesMappedToDataTypes', []).mapBy('fieldName');
|
2017-03-30 15:08:12 -07:00
|
|
|
|
2017-07-18 15:01:28 -07:00
|
|
|
// identifier field names from the column api should be unique
|
2017-09-27 09:33:20 -07:00
|
|
|
if (isListUnique(fieldNames.sort())) {
|
2017-03-31 17:30:21 -07:00
|
|
|
return set(this, '_hasBadData', false);
|
2017-03-30 15:08:12 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Flag this component's data as problematic
|
|
|
|
set(this, '_hasBadData', true);
|
|
|
|
},
|
|
|
|
|
2017-05-19 08:45:05 -07:00
|
|
|
// Map logicalTypes to options consumable by DOM
|
2017-08-07 22:21:05 -07:00
|
|
|
idLogicalTypes: logicalTypesForIds,
|
2017-05-19 08:45:05 -07:00
|
|
|
|
2017-12-06 10:23:33 -08:00
|
|
|
// Map of classifiers options for drop down
|
|
|
|
classifiers: securityClassificationDropdownOptions,
|
2017-03-24 20:27:43 -07:00
|
|
|
|
2017-04-18 15:02:45 -07:00
|
|
|
/**
|
|
|
|
* @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.
|
|
|
|
*/
|
2017-08-07 22:21:05 -07:00
|
|
|
containsHiddenTrackingFields: computed('truncatedColumnFields.length', function() {
|
|
|
|
// If their is a diff in schemaFieldNamesMappedToDataTypes and truncatedColumnFields,
|
2017-05-01 09:57:17 -07:00
|
|
|
// then we have hidden tracking fields
|
2017-08-07 22:21:05 -07:00
|
|
|
return get(this, 'truncatedColumnFields.length') !== get(this, 'schemaFieldNamesMappedToDataTypes.length');
|
2017-05-01 09:57:17 -07:00
|
|
|
}),
|
2017-04-18 15:02:45 -07:00
|
|
|
|
|
|
|
/**
|
2017-10-16 11:35:13 -07:00
|
|
|
* @type {Ember.ComputedProperty} Filters the mapped compliance data fields without `kafka type`
|
2017-04-18 15:02:45 -07:00
|
|
|
* tracking headers
|
2017-10-16 11:35:13 -07:00
|
|
|
* @return {Array<object>}
|
2017-04-18 15:02:45 -07:00
|
|
|
*/
|
2017-08-07 22:21:05 -07:00
|
|
|
truncatedColumnFields: computed('schemaFieldNamesMappedToDataTypes', function() {
|
2017-07-18 17:09:43 -07:00
|
|
|
return getWithDefault(this, 'schemaFieldNamesMappedToDataTypes', []).filter(
|
|
|
|
({ fieldName }) => !isTrackingHeaderField(fieldName)
|
|
|
|
);
|
2017-04-18 15:02:45 -07:00
|
|
|
}),
|
|
|
|
|
2017-05-19 08:45:05 -07:00
|
|
|
/**
|
|
|
|
* 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));
|
|
|
|
}),
|
|
|
|
|
2017-10-11 00:16:41 -07:00
|
|
|
/**
|
|
|
|
* Checks if any of the attributes on the dataset classification is false
|
|
|
|
* @type {Ember.ComputedProperty}
|
|
|
|
* @return {boolean}
|
|
|
|
*/
|
|
|
|
excludesSomeMemberData: computed(datasetClassificationKey, function() {
|
|
|
|
const sourceDatasetClassification = get(this, datasetClassificationKey) || {};
|
|
|
|
|
|
|
|
return Object.values(sourceDatasetClassification).some(hasMemberData => !hasMemberData);
|
|
|
|
}),
|
|
|
|
|
2017-08-20 18:53:01 -07:00
|
|
|
/**
|
|
|
|
* Determines if all member data fields should be shown in the member data table i.e. show only fields contained in
|
|
|
|
* this dataset or otherwise
|
|
|
|
*/
|
2017-10-11 00:16:41 -07:00
|
|
|
shouldShowAllMemberData: computed.or('showAllDatasetMemberData', 'isEditing'),
|
2017-08-20 18:53:01 -07:00
|
|
|
|
2017-07-18 15:01:28 -07:00
|
|
|
/**
|
|
|
|
* Determines if the save feature is allowed for the current dataset, otherwise e.g. interface should be disabled
|
|
|
|
* @type {Ember.computed}
|
|
|
|
*/
|
|
|
|
isSavingDisabled: computed('isDatasetFullyClassified', 'isSaving', function() {
|
|
|
|
const { isDatasetFullyClassified, isSaving } = getProperties(this, ['isDatasetFullyClassified', 'isSaving']);
|
|
|
|
|
|
|
|
return !isDatasetFullyClassified || isSaving;
|
|
|
|
}),
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks to ensure the the number of fields added to compliance entities is less than or equal
|
|
|
|
* to what is available on the dataset schema
|
|
|
|
* @return {boolean}
|
|
|
|
*/
|
|
|
|
isSchemaFieldLengthGreaterThanComplianceEntities() {
|
|
|
|
const { length: columnFieldsLength } = getWithDefault(this, 'schemaFieldNamesMappedToDataTypes', []);
|
|
|
|
const { length: complianceListLength } = get(this, policyComplianceEntitiesKey);
|
|
|
|
|
|
|
|
return columnFieldsLength >= complianceListLength;
|
|
|
|
},
|
|
|
|
|
2017-05-19 08:45:05 -07:00
|
|
|
/**
|
|
|
|
* 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 = get(this, datasetClassificationKey) || {};
|
|
|
|
|
2017-12-12 22:19:01 -08:00
|
|
|
return datasetClassifiersKeys.sort().reduce((datasetClassifiers, classifier) => {
|
2017-08-20 16:46:43 -07:00
|
|
|
return [
|
2017-12-12 22:19:01 -08:00
|
|
|
...datasetClassifiers,
|
2017-08-20 16:46:43 -07:00
|
|
|
{
|
|
|
|
classifier,
|
2017-05-19 08:45:05 -07:00
|
|
|
value: sourceDatasetClassification[classifier],
|
2017-10-16 11:35:13 -07:00
|
|
|
label: DatasetClassifiers[classifier]
|
2017-05-19 08:45:05 -07:00
|
|
|
}
|
2017-08-20 16:46:43 -07:00
|
|
|
];
|
|
|
|
}, []);
|
2017-05-19 08:45:05 -07:00
|
|
|
}),
|
|
|
|
|
2017-08-07 22:21:05 -07:00
|
|
|
/**
|
|
|
|
*
|
2017-09-27 09:33:20 -07:00
|
|
|
* @param {Array<object>} columnFieldProps
|
|
|
|
* @param {Array<object>} complianceEntities
|
2017-10-07 17:49:57 -07:00
|
|
|
* @param {policyModificationTime}
|
2017-09-27 09:33:20 -07:00
|
|
|
* @return {object}
|
2017-08-07 22:21:05 -07:00
|
|
|
*/
|
2017-10-07 17:49:57 -07:00
|
|
|
mapColumnIdFieldsToCurrentPrivacyPolicy(columnFieldProps, complianceEntities, { policyModificationTime }) {
|
2017-08-07 22:21:05 -07:00
|
|
|
const getKeysOnField = (keys = [], fieldName, source = []) => {
|
|
|
|
const sourceField = source.find(({ identifierField }) => identifierField === fieldName) || {};
|
|
|
|
let ret = {};
|
|
|
|
|
|
|
|
for (const [key, value] of Object.entries(sourceField)) {
|
|
|
|
if (keys.includes(key)) {
|
|
|
|
ret = { ...ret, [key]: value };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
};
|
|
|
|
|
2017-09-27 09:33:20 -07:00
|
|
|
return columnFieldProps.reduce((acc, { identifierField, dataType }) => {
|
2017-08-07 22:21:05 -07:00
|
|
|
const currentPrivacyAttrs = getKeysOnField(
|
2017-08-15 23:16:38 -07:00
|
|
|
['identifierType', 'logicalType', 'securityClassification'],
|
2017-08-07 22:21:05 -07:00
|
|
|
identifierField,
|
|
|
|
complianceEntities
|
|
|
|
);
|
|
|
|
|
2017-09-27 09:33:20 -07:00
|
|
|
return {
|
|
|
|
...acc,
|
|
|
|
[identifierField]: {
|
|
|
|
identifierField,
|
|
|
|
dataType,
|
|
|
|
...currentPrivacyAttrs,
|
2017-10-07 17:49:57 -07:00
|
|
|
policyModificationTime,
|
2017-09-27 09:33:20 -07:00
|
|
|
privacyPolicyExists: hasEnumerableKeys(currentPrivacyAttrs),
|
|
|
|
isDirty: false
|
|
|
|
}
|
|
|
|
};
|
2017-08-07 22:21:05 -07:00
|
|
|
}, {});
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Computed prop over the current Id fields in the Privacy Policy
|
|
|
|
* @type {Ember.computed}
|
|
|
|
*/
|
|
|
|
columnIdFieldsToCurrentPrivacyPolicy: computed(
|
2017-10-11 00:16:41 -07:00
|
|
|
`{truncatedColumnFields,${policyComplianceEntitiesKey}.[]}`,
|
2017-08-07 22:21:05 -07:00
|
|
|
function() {
|
2017-09-27 09:33:20 -07:00
|
|
|
// Truncated list of Dataset field names and data types currently returned from the column endpoint
|
|
|
|
const columnFieldProps = get(this, 'truncatedColumnFields').map(({ fieldName, dataType }) => ({
|
|
|
|
identifierField: fieldName,
|
|
|
|
dataType
|
|
|
|
}));
|
|
|
|
// Dataset fields that currently have a compliance policy
|
|
|
|
const currentComplianceEntities = get(this, policyComplianceEntitiesKey) || [];
|
|
|
|
|
2017-10-07 17:49:57 -07:00
|
|
|
return this.mapColumnIdFieldsToCurrentPrivacyPolicy(columnFieldProps, currentComplianceEntities, {
|
|
|
|
policyModificationTime: getWithDefault(this, 'complianceInfo.modifiedTime', 0)
|
|
|
|
});
|
2017-03-27 18:26:09 -07:00
|
|
|
}
|
|
|
|
),
|
|
|
|
|
2017-08-07 22:21:05 -07:00
|
|
|
/**
|
|
|
|
* Caches a reference to the generated list of merged data between the column api and the current compliance entities list
|
2017-09-27 09:33:20 -07:00
|
|
|
* @type {Array<{identifierType: string, logicalType: string, securityClassification: string, privacyPolicyExists: boolean, isDirty: boolean, [suggestion]: object}>}
|
2017-08-07 22:21:05 -07:00
|
|
|
*/
|
2017-09-27 09:33:20 -07:00
|
|
|
compliancePolicyChangeSet: computed('columnIdFieldsToCurrentPrivacyPolicy', function() {
|
2017-08-07 22:21:05 -07:00
|
|
|
// truncatedColumnFields is a dependency for cp columnIdFieldsToCurrentPrivacyPolicy, so no need to dep on that directly
|
2017-09-27 09:33:20 -07:00
|
|
|
return mergeMappedColumnFieldsWithSuggestions(
|
2017-08-07 22:21:05 -07:00
|
|
|
get(this, 'columnIdFieldsToCurrentPrivacyPolicy'),
|
2017-08-18 03:42:40 -07:00
|
|
|
get(this, 'identifierFieldToSuggestion')
|
2017-08-07 22:21:05 -07:00
|
|
|
);
|
|
|
|
}),
|
|
|
|
|
2017-09-27 09:33:20 -07:00
|
|
|
/**
|
|
|
|
* Returns a list of changeSet fields that meets the user selected filter criteria
|
|
|
|
* @type {Ember.computed}
|
|
|
|
* @return {Array<{}>}
|
|
|
|
*/
|
|
|
|
filteredChangeSet: computed('changeSetReviewCount', 'fieldReviewOption', 'compliancePolicyChangeSet', function() {
|
|
|
|
const changeSet = get(this, 'compliancePolicyChangeSet');
|
|
|
|
|
|
|
|
return get(this, 'fieldReviewOption') === 'showReview' ? changeSetFieldsRequiringReview(changeSet) : changeSet;
|
|
|
|
}),
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a count of changeSet fields that require user attention
|
|
|
|
* @type {Ember.computed}
|
|
|
|
* @return {Array<{}>}
|
|
|
|
*/
|
|
|
|
changeSetReviewCount: computed(
|
|
|
|
'compliancePolicyChangeSet.@each.{isDirty,suggestion,privacyPolicyExists,suggestionAuthority}',
|
|
|
|
function() {
|
|
|
|
return changeSetFieldsRequiringReview(get(this, 'compliancePolicyChangeSet')).length;
|
|
|
|
}
|
|
|
|
),
|
|
|
|
|
2017-08-18 03:42:40 -07:00
|
|
|
/**
|
|
|
|
* Creates a mapping of compliance suggestions to identifierField
|
|
|
|
* This improves performance in a subsequent merge op since this loop
|
|
|
|
* happens only once and is cached
|
2017-09-27 09:33:20 -07:00
|
|
|
* @type {object}
|
2017-08-18 03:42:40 -07:00
|
|
|
*/
|
2017-08-20 20:05:18 -07:00
|
|
|
identifierFieldToSuggestion: computed('complianceSuggestion', function() {
|
2017-08-18 03:42:40 -07:00
|
|
|
const identifierFieldToSuggestion = {};
|
2017-10-07 17:49:57 -07:00
|
|
|
const complianceSuggestion = get(this, 'complianceSuggestion') || {};
|
2017-10-16 11:35:13 -07:00
|
|
|
const { lastModified: suggestionsModificationTime, suggestedFieldClassification = [] } = complianceSuggestion;
|
2017-10-07 17:49:57 -07:00
|
|
|
|
2017-10-16 11:35:13 -07:00
|
|
|
// If the compliance suggestions array contains suggestions the create reduced lookup map,
|
2017-08-18 03:42:40 -07:00
|
|
|
// otherwise, ignore
|
2017-10-16 11:35:13 -07:00
|
|
|
if (suggestedFieldClassification.length) {
|
|
|
|
return suggestedFieldClassification.reduce(
|
|
|
|
(
|
|
|
|
identifierFieldToSuggestion,
|
|
|
|
{ suggestion: { identifierField, identifierType, logicalType, securityClassification }, confidenceLevel }
|
|
|
|
) => ({
|
2017-08-18 03:42:40 -07:00
|
|
|
...identifierFieldToSuggestion,
|
2017-10-16 11:35:13 -07:00
|
|
|
[identifierField]: {
|
|
|
|
identifierType,
|
|
|
|
logicalType,
|
|
|
|
securityClassification,
|
|
|
|
confidenceLevel,
|
2017-10-07 17:49:57 -07:00
|
|
|
suggestionsModificationTime
|
2017-08-18 03:42:40 -07:00
|
|
|
}
|
|
|
|
}),
|
|
|
|
identifierFieldToSuggestion
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return identifierFieldToSuggestion;
|
|
|
|
}),
|
|
|
|
|
2017-03-27 18:50:55 -07:00
|
|
|
/**
|
2017-05-19 18:26:38 -07:00
|
|
|
* Checks that each entity in sourceEntities has a generic
|
|
|
|
* @param {Array} sourceEntities = [] the source entities to be matched against
|
|
|
|
* @param {Array} logicalTypes = [] list of logicalTypes to check against
|
2017-03-27 18:50:55 -07:00
|
|
|
*/
|
2017-05-19 18:26:38 -07:00
|
|
|
checkEachEntityByLogicalType: (sourceEntities = [], logicalTypes = []) =>
|
|
|
|
sourceEntities.every(
|
|
|
|
({ logicalType }) =>
|
2017-06-12 17:57:53 -07:00
|
|
|
typeof logicalType === 'object' ? logicalTypes.includes(logicalType.value) : logicalTypes.includes(logicalType)
|
2017-05-19 18:26:38 -07:00
|
|
|
),
|
2017-03-24 20:27:43 -07:00
|
|
|
|
2017-04-02 15:40:43 -07:00
|
|
|
/**
|
|
|
|
* TODO:DSS-6719 refactor into mixin
|
|
|
|
* Clears recently shown user messages
|
|
|
|
*/
|
|
|
|
clearMessages() {
|
|
|
|
return setProperties(this, {
|
|
|
|
_message: '',
|
|
|
|
_alertType: ''
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper method to update user when an async server update to the
|
|
|
|
* security specification is handled.
|
2017-05-19 08:45:05 -07:00
|
|
|
* @param {Promise|*} request the server request
|
2017-08-30 11:09:33 -07:00
|
|
|
* @param {String} [successMessage] optional message for successful response
|
2017-05-19 08:45:05 -07:00
|
|
|
* @param { Boolean} [isSaving = false] optional flag indicating when the user intends to persist / save
|
2017-04-02 15:40:43 -07:00
|
|
|
*/
|
2017-05-19 08:45:05 -07:00
|
|
|
whenRequestCompletes(request, { successMessage, isSaving = false } = {}) {
|
2017-08-30 11:09:33 -07:00
|
|
|
const notify = get(this, 'notifications.notify');
|
|
|
|
|
2017-07-18 15:01:28 -07:00
|
|
|
return Promise.resolve(request)
|
2017-04-11 16:02:41 -07:00
|
|
|
.then(({ status = 'error' }) => {
|
2017-05-01 09:57:17 -07:00
|
|
|
return status === 'ok'
|
2017-08-30 11:09:33 -07:00
|
|
|
? notify('success', { content: successMessage || successUpdating })
|
2017-05-01 09:57:17 -07:00
|
|
|
: Promise.reject(new Error(`Reason code for this is ${status}`));
|
2017-04-02 15:40:43 -07:00
|
|
|
})
|
2017-04-11 16:02:41 -07:00
|
|
|
.catch(err => {
|
2017-08-30 11:09:33 -07:00
|
|
|
let message = `${failedUpdating} \n ${err}`;
|
2017-04-02 15:40:43 -07:00
|
|
|
|
2017-05-19 08:45:05 -07:00
|
|
|
if (get(this, 'isNewComplianceInfo') && !isSaving) {
|
2017-08-30 11:09:33 -07:00
|
|
|
return notify('info', {
|
2017-08-30 10:26:44 -07:00
|
|
|
content: 'This dataset does not have any previously saved fields with a identifying information.'
|
|
|
|
});
|
2017-04-02 15:40:43 -07:00
|
|
|
}
|
|
|
|
|
2017-08-30 11:09:33 -07:00
|
|
|
notify('error', { content: message });
|
2017-04-02 15:40:43 -07:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2017-05-19 08:45:05 -07:00
|
|
|
/**
|
|
|
|
* Sets the default classification for the given identifier field
|
2017-12-12 22:19:01 -08:00
|
|
|
* Using the identifierType, determine the field's default security classification based on a values
|
|
|
|
* supplied by complianceDataTypes endpoint
|
2017-06-06 11:34:53 -07:00
|
|
|
* @param {String} identifierField the field for which the default classification should apply
|
2017-12-12 22:19:01 -08:00
|
|
|
* @param {ComplianceFieldIdValue} identifierType the value of the field's identifier type
|
2017-05-19 08:45:05 -07:00
|
|
|
*/
|
2017-12-12 22:19:01 -08:00
|
|
|
setDefaultClassification({ identifierField, identifierType }) {
|
|
|
|
const complianceDataTypes = get(this, 'complianceDataTypes');
|
|
|
|
const defaultSecurityClassification = getDefaultSecurityClassification(complianceDataTypes, identifierType);
|
|
|
|
|
|
|
|
this.actions.onFieldClassificationChange.call(this, { identifierField }, { value: defaultSecurityClassification });
|
2017-05-19 08:45:05 -07:00
|
|
|
},
|
|
|
|
|
2017-05-19 18:26:38 -07:00
|
|
|
/**
|
|
|
|
* Requires that the user confirm that any non-id fields are ok to be saved without a field format specified
|
|
|
|
* @return {Boolean}
|
|
|
|
*/
|
2017-08-28 01:38:47 -07:00
|
|
|
async confirmUnformattedFields() {
|
2017-05-19 18:26:38 -07:00
|
|
|
// Current list of compliance entities on policy
|
2017-07-18 15:01:28 -07:00
|
|
|
const complianceEntities = get(this, policyComplianceEntitiesKey);
|
2017-07-18 17:09:43 -07:00
|
|
|
// All candidate fields that can be on policy, excluding tracking type fields
|
2017-08-07 22:21:05 -07:00
|
|
|
const datasetFields = get(
|
|
|
|
this,
|
2017-09-27 09:33:20 -07:00
|
|
|
'compliancePolicyChangeSet'
|
2017-08-18 03:42:40 -07:00
|
|
|
).map(({ identifierField, identifierType, logicalType, classification }) => ({
|
2017-08-07 22:21:05 -07:00
|
|
|
identifierField,
|
|
|
|
identifierType,
|
2017-08-18 03:42:40 -07:00
|
|
|
logicalType,
|
2017-08-23 11:00:10 -07:00
|
|
|
securityClassification: classification
|
2017-08-07 22:21:05 -07:00
|
|
|
}));
|
2017-07-18 15:01:28 -07:00
|
|
|
// Fields that do not have a logicalType, and no identifierType or identifierType is `fieldIdentifierTypes.none`
|
2017-08-07 22:21:05 -07:00
|
|
|
const { formatted, unformatted } = datasetFields.reduce(
|
|
|
|
({ formatted, unformatted }, field) => {
|
|
|
|
const { identifierType, logicalType } = getProperties(field, ['identifierType', 'logicalType']);
|
|
|
|
if (!logicalType && (fieldIdentifierTypes.none.value === identifierType || !identifierType)) {
|
|
|
|
unformatted = [...unformatted, field];
|
|
|
|
} else {
|
|
|
|
formatted = [...formatted, field];
|
|
|
|
}
|
2017-08-18 03:42:40 -07:00
|
|
|
|
2017-08-07 22:21:05 -07:00
|
|
|
return { formatted, unformatted };
|
|
|
|
},
|
|
|
|
{ formatted: [], unformatted: [] }
|
2017-05-19 18:26:38 -07:00
|
|
|
);
|
2017-08-28 01:38:47 -07:00
|
|
|
|
2017-08-29 16:50:44 -07:00
|
|
|
const dialogActions = {};
|
2017-05-19 18:26:38 -07:00
|
|
|
let isConfirmed = true;
|
2017-08-07 22:21:05 -07:00
|
|
|
let unformattedComplianceEntities = [];
|
2017-05-19 18:26:38 -07:00
|
|
|
|
|
|
|
// If there are unformatted fields, require confirmation from user
|
2017-08-07 22:21:05 -07:00
|
|
|
if (unformatted.length) {
|
|
|
|
unformattedComplianceEntities = unformatted.map(({ identifierField }) => ({
|
|
|
|
identifierField,
|
|
|
|
identifierType: fieldIdentifierTypes.none.value,
|
2017-08-23 11:00:10 -07:00
|
|
|
logicalType: null,
|
|
|
|
securityClassification: null
|
2017-08-07 22:21:05 -07:00
|
|
|
}));
|
2017-07-18 15:01:28 -07:00
|
|
|
|
2017-08-28 01:38:47 -07:00
|
|
|
const confirmHandler = (function() {
|
|
|
|
return new Promise((resolve, reject) => {
|
2017-08-29 16:50:44 -07:00
|
|
|
dialogActions['didConfirm'] = () => resolve();
|
|
|
|
dialogActions['didDismiss'] = () => reject();
|
2017-08-28 01:38:47 -07:00
|
|
|
});
|
|
|
|
})();
|
|
|
|
|
|
|
|
// Create confirmation dialog
|
|
|
|
get(this, 'notifications').notify('confirm', {
|
|
|
|
header: 'Some field formats are unspecified',
|
|
|
|
content:
|
|
|
|
`There are ${unformatted.length} non-ID fields that have no field format specified. ` +
|
2017-06-14 12:39:25 -07:00
|
|
|
`Are you sure they don't contain any of the following PII?\n\n` +
|
2017-08-28 01:38:47 -07:00
|
|
|
`Name, Email, Phone, Address, Location, IP Address, Payment Info, Password, National ID, Device ID etc.`,
|
2017-08-29 16:50:44 -07:00
|
|
|
dialogActions: dialogActions
|
2017-08-28 01:38:47 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
try {
|
|
|
|
await confirmHandler;
|
|
|
|
} catch (e) {
|
|
|
|
isConfirmed = false;
|
|
|
|
}
|
2017-05-19 18:26:38 -07:00
|
|
|
}
|
|
|
|
|
2017-08-30 18:07:01 -07:00
|
|
|
isConfirmed && complianceEntities.setObjects([...formatted, ...unformattedComplianceEntities]);
|
|
|
|
|
2017-05-19 18:26:38 -07:00
|
|
|
return isConfirmed;
|
|
|
|
},
|
|
|
|
|
2017-07-18 15:01:28 -07:00
|
|
|
/**
|
|
|
|
* Ensures the fields in the updated list of compliance entities meet the criteria
|
|
|
|
* checked in the function. If criteria is not met, an the returned promise is settled
|
|
|
|
* in a rejected state, otherwise fulfilled
|
|
|
|
* @method
|
|
|
|
* @return {any | Promise<any>}
|
|
|
|
*/
|
|
|
|
validateFields() {
|
2017-08-30 18:41:23 -07:00
|
|
|
const notify = get(this, 'notifications.notify');
|
2017-07-18 15:01:28 -07:00
|
|
|
const complianceEntities = get(this, policyComplianceEntitiesKey);
|
|
|
|
const idFieldsHaveValidLogicalType = this.checkEachEntityByLogicalType(
|
2017-12-12 17:47:28 -08:00
|
|
|
complianceEntities.filter(({ identifierType }) =>
|
|
|
|
getIdTypeDataTypes(get(this, 'complianceDataTypes')).includes(identifierType)
|
|
|
|
),
|
2017-07-18 15:01:28 -07:00
|
|
|
[...genericLogicalTypes, ...idLogicalTypes]
|
|
|
|
);
|
2017-09-27 09:33:20 -07:00
|
|
|
const fieldIdentifiersAreUnique = isListUnique(complianceEntities.mapBy('identifierField'));
|
2017-07-18 15:01:28 -07:00
|
|
|
const schemaFieldLengthGreaterThanComplianceEntities = this.isSchemaFieldLengthGreaterThanComplianceEntities();
|
|
|
|
|
|
|
|
if (!fieldIdentifiersAreUnique || !schemaFieldLengthGreaterThanComplianceEntities) {
|
2017-08-30 18:41:23 -07:00
|
|
|
notify('error', { content: complianceDataException });
|
2017-07-18 15:01:28 -07:00
|
|
|
return Promise.reject(new Error(complianceDataException));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!idFieldsHaveValidLogicalType) {
|
2017-08-30 18:41:23 -07:00
|
|
|
return Promise.reject(notify('error', { content: missingTypes }));
|
2017-07-18 15:01:28 -07:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2017-08-20 18:12:56 -07:00
|
|
|
/**
|
|
|
|
* Gets a reference to the current dataset classification object
|
|
|
|
*/
|
|
|
|
getDatasetClassificationRef() {
|
|
|
|
let sourceDatasetClassification = getWithDefault(this, datasetClassificationKey, {});
|
|
|
|
|
|
|
|
// For datasets initially without a datasetClassification, the default value is null
|
|
|
|
if (sourceDatasetClassification === null) {
|
|
|
|
sourceDatasetClassification = set(this, datasetClassificationKey, {});
|
|
|
|
}
|
|
|
|
|
|
|
|
return sourceDatasetClassification;
|
|
|
|
},
|
|
|
|
|
2017-10-19 18:17:16 -07:00
|
|
|
/**
|
|
|
|
* Display a modal dialog requesting that the user check affirm that the purge type is exempt
|
|
|
|
* @return {Promise<void>}
|
|
|
|
*/
|
|
|
|
showPurgeExemptionWarning() {
|
|
|
|
const dialogActions = {};
|
|
|
|
|
|
|
|
get(this, 'notifications').notify('confirm', {
|
|
|
|
header: 'Confirm purge exemption',
|
|
|
|
content:
|
|
|
|
'By choosing this option you understand that either Legal or HSEC may contact you to verify the purge exemption',
|
|
|
|
dialogActions
|
|
|
|
});
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
dialogActions['didConfirm'] = () => resolve();
|
|
|
|
dialogActions['didDismiss'] = () => reject();
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2017-10-20 21:34:58 -07:00
|
|
|
/**
|
|
|
|
* Updates the currently active step in the edit sequence
|
|
|
|
* @param {number} step
|
|
|
|
*/
|
|
|
|
updateStep(step) {
|
|
|
|
set(this, 'editStepIndex', step);
|
|
|
|
},
|
|
|
|
|
2017-02-13 14:51:31 -08:00
|
|
|
actions: {
|
2017-08-20 18:12:56 -07:00
|
|
|
/**
|
|
|
|
* Sets each datasetClassification value as false
|
|
|
|
*/
|
2017-08-29 16:50:44 -07:00
|
|
|
async markDatasetAsNotContainingMemberData() {
|
|
|
|
const dialogActions = {};
|
|
|
|
const confirmMarkAllHandler = new Promise((resolve, reject) => {
|
|
|
|
dialogActions.didDismiss = () => reject();
|
|
|
|
dialogActions.didConfirm = () => resolve();
|
|
|
|
});
|
|
|
|
let willMarkAllAsNo = true;
|
|
|
|
|
|
|
|
get(this, 'notifications').notify('confirm', {
|
2017-08-30 10:26:44 -07:00
|
|
|
content: 'Are you sure that any this dataset does not contain any of the listed types of member data?',
|
|
|
|
header: 'Dataset contains no member data',
|
2017-08-29 16:50:44 -07:00
|
|
|
dialogActions
|
|
|
|
});
|
|
|
|
|
|
|
|
try {
|
|
|
|
await confirmMarkAllHandler;
|
|
|
|
} catch (e) {
|
|
|
|
willMarkAllAsNo = false;
|
|
|
|
}
|
2017-08-20 18:12:56 -07:00
|
|
|
|
|
|
|
return (
|
|
|
|
willMarkAllAsNo &&
|
|
|
|
setProperties(
|
|
|
|
this.getDatasetClassificationRef(),
|
|
|
|
datasetClassifiersKeys.reduce(
|
|
|
|
(classification, classifier) => ({ ...classification, ...{ [classifier]: false } }),
|
|
|
|
{}
|
|
|
|
)
|
|
|
|
)
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
2017-08-20 18:53:01 -07:00
|
|
|
/**
|
2017-10-10 20:02:33 -07:00
|
|
|
* Toggles the flag to show all member potential member data fields that may be contained in this dataset
|
2017-08-20 18:53:01 -07:00
|
|
|
*/
|
|
|
|
onShowAllDatasetMemberData() {
|
2017-10-10 20:02:33 -07:00
|
|
|
return this.toggleProperty('showAllDatasetMemberData');
|
2017-08-20 18:53:01 -07:00
|
|
|
},
|
|
|
|
|
2017-09-27 09:33:20 -07:00
|
|
|
/**
|
|
|
|
* Updates the fieldReviewOption with the user selected value
|
|
|
|
* @param {string} value
|
|
|
|
*/
|
|
|
|
onFieldReviewChange({ value }) {
|
|
|
|
return set(this, 'fieldReviewOption', value);
|
|
|
|
},
|
|
|
|
|
2017-06-05 09:49:37 -07:00
|
|
|
/**
|
2017-10-20 21:34:58 -07:00
|
|
|
* Progresses 1 step backward in the edit sequence
|
|
|
|
*/
|
|
|
|
previousStep() {
|
|
|
|
const editStepIndex = get(this, 'editStepIndex');
|
|
|
|
const nextState = editStepIndex > 0 ? editStepIndex - 1 : editStepIndex;
|
|
|
|
this.updateStep(nextState);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Progresses 1 step forward in the edit sequence
|
2017-06-05 09:49:37 -07:00
|
|
|
*/
|
2017-10-20 21:34:58 -07:00
|
|
|
nextStep() {
|
|
|
|
const { editStepIndex, editSteps } = getProperties(this, ['editStepIndex', 'editSteps']);
|
|
|
|
const nextState = editStepIndex < editSteps.length - 1 ? editStepIndex + 1 : editStepIndex;
|
|
|
|
this.updateStep(nextState);
|
2017-08-29 16:50:44 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handler for setting the dataset classification into edit mode and rendering into DOM
|
|
|
|
*/
|
2017-10-20 21:34:58 -07:00
|
|
|
async didEditCompliancePolicy() {
|
2017-08-29 16:50:44 -07:00
|
|
|
const isConfirmed = await this.confirmUnformattedFields();
|
|
|
|
|
2017-09-10 19:31:54 -07:00
|
|
|
if (isConfirmed) {
|
|
|
|
// Ensure that the fields on the policy meet the validation criteria before proceeding
|
|
|
|
// Otherwise exit early
|
|
|
|
try {
|
|
|
|
await this.validateFields();
|
|
|
|
} catch (e) {
|
|
|
|
// Flag this dataset's data as problematic
|
|
|
|
if (e instanceof Error && e.message === complianceDataException) {
|
|
|
|
set(this, '_hasBadData', true);
|
|
|
|
window.scrollTo(0, 0);
|
|
|
|
}
|
2017-08-30 18:41:23 -07:00
|
|
|
|
2017-10-20 21:34:58 -07:00
|
|
|
// return;
|
|
|
|
throw e;
|
2017-09-10 19:31:54 -07:00
|
|
|
}
|
2017-08-30 18:41:23 -07:00
|
|
|
|
2017-09-10 19:31:54 -07:00
|
|
|
// If user provides confirmation for unformatted fields or there are none,
|
|
|
|
// then validate fields against expectations
|
|
|
|
// otherwise inform user of validation exception
|
2017-10-20 21:34:58 -07:00
|
|
|
// setProperties(this, { isEditingCompliancePolicy: false, isEditingDatasetClassification: true });
|
|
|
|
} else {
|
|
|
|
throw new Error('unConfirmedUnformattedFields');
|
|
|
|
}
|
|
|
|
|
|
|
|
return isConfirmed;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handles post processing tasks after the purge policy step has been completed
|
|
|
|
* @return {void|Promise.<void>}
|
|
|
|
*/
|
|
|
|
didEditPurgePolicy() {
|
|
|
|
if (isExempt(get(this, 'complianceInfo.complianceType'))) {
|
|
|
|
return this.showPurgeExemptionWarning();
|
2017-08-29 16:50:44 -07:00
|
|
|
}
|
2017-06-05 09:49:37 -07:00
|
|
|
},
|
|
|
|
|
2017-08-18 11:10:08 -07:00
|
|
|
/**
|
|
|
|
* Augments the field props with w a suggestionAuthority indicating that the field
|
|
|
|
* suggestion has either been accepted or ignored, and assigns the value of that change to the prop
|
|
|
|
* @param {object} field field for which this suggestion intent should apply
|
|
|
|
* @param {string | void} [intent] user's intended action for suggestion, Defaults to `ignore`
|
|
|
|
*/
|
|
|
|
onFieldSuggestionIntentChange(field, intent = 'ignore') {
|
|
|
|
set(field, 'suggestionAuthority', intent);
|
|
|
|
},
|
|
|
|
|
2017-06-01 13:11:26 -07:00
|
|
|
/**
|
|
|
|
* Receives the json representation for compliance and applies each key to the policy
|
2017-08-30 10:26:44 -07:00
|
|
|
* @param {string} textString string representation for the JSON file
|
2017-06-01 13:11:26 -07:00
|
|
|
*/
|
|
|
|
onComplianceJsonUpload(textString) {
|
2017-08-30 10:26:44 -07:00
|
|
|
let policy;
|
|
|
|
try {
|
|
|
|
policy = JSON.parse(textString);
|
|
|
|
} catch (e) {
|
|
|
|
get(this, 'notifications').notify('error', {
|
|
|
|
content: invalidPolicyData
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-06-01 13:11:26 -07:00
|
|
|
if (isPolicyExpectedShape(policy)) {
|
2017-08-30 10:26:44 -07:00
|
|
|
setProperties(this, {
|
|
|
|
'complianceInfo.complianceEntities': policy.complianceEntities,
|
|
|
|
'complianceInfo.datasetClassification': policy.datasetClassification
|
|
|
|
});
|
2017-06-05 11:22:48 -07:00
|
|
|
|
2017-08-30 10:26:44 -07:00
|
|
|
get(this, 'notifications').notify('info', {
|
|
|
|
content: successUploading
|
|
|
|
});
|
2017-06-01 13:11:26 -07:00
|
|
|
}
|
|
|
|
|
2017-08-30 10:26:44 -07:00
|
|
|
get(this, 'notifications').notify('error', {
|
|
|
|
content: invalidPolicyData
|
|
|
|
});
|
2017-06-01 13:11:26 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handles the compliance policy download action
|
|
|
|
*/
|
|
|
|
onComplianceDownloadJson() {
|
|
|
|
const currentPolicy = get(this, 'complianceInfo');
|
2017-08-15 23:16:38 -07:00
|
|
|
const policyProps = [datasetClassificationKey, policyComplianceEntitiesKey].map(name => name.split('.').pop());
|
2017-06-01 13:11:26 -07:00
|
|
|
const policy = Object.assign({}, getProperties(currentPolicy, policyProps));
|
|
|
|
const href = `data:text/json;charset=utf-8,${encodeURIComponent(JSON.stringify(policy))}`;
|
|
|
|
const download = `${get(this, 'datasetName')}_policy.json`;
|
|
|
|
const anchor = document.createElement('a');
|
2017-07-13 09:56:40 -07:00
|
|
|
const anchorParent = document.body;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Post download housekeeping
|
|
|
|
*/
|
|
|
|
const cleanupPostDownload = () => {
|
|
|
|
anchor.removeEventListener('click', cleanupPostDownload);
|
|
|
|
anchorParent.removeChild(anchor);
|
|
|
|
};
|
|
|
|
|
2017-06-01 13:11:26 -07:00
|
|
|
Object.assign(anchor, { download, href });
|
2017-07-13 09:56:40 -07:00
|
|
|
anchor.addEventListener('click', cleanupPostDownload);
|
|
|
|
|
|
|
|
// Element needs to be in DOM to receive event in firefox
|
|
|
|
anchorParent.appendChild(anchor);
|
|
|
|
|
2017-06-01 13:11:26 -07:00
|
|
|
anchor.click();
|
|
|
|
},
|
|
|
|
|
2017-03-27 18:26:09 -07:00
|
|
|
/**
|
2017-05-19 08:45:05 -07:00
|
|
|
* When a user updates the identifierFieldType in the DOM, update the backing store
|
2017-05-19 18:26:38 -07:00
|
|
|
* @param {String} identifierField
|
|
|
|
* @param {String} logicalType
|
|
|
|
* @param {String} identifierType
|
2017-03-27 18:26:09 -07:00
|
|
|
*/
|
2017-08-15 23:16:38 -07:00
|
|
|
onFieldIdentifierTypeChange({ identifierField }, { value: identifierType }) {
|
2017-09-27 09:33:20 -07:00
|
|
|
const currentComplianceEntities = get(this, 'compliancePolicyChangeSet');
|
2017-08-15 23:16:38 -07:00
|
|
|
// A reference to the current field in the compliance list, it should exist even for empty complianceEntities
|
2017-09-27 09:33:20 -07:00
|
|
|
// since this is a reference created in the working copy: compliancePolicyChangeSet
|
2017-08-07 22:21:05 -07:00
|
|
|
const currentFieldInComplianceList = currentComplianceEntities.findBy('identifierField', identifierField);
|
2017-09-27 09:33:20 -07:00
|
|
|
let logicalType;
|
2017-09-18 16:13:30 -07:00
|
|
|
if (hasPredefinedFieldFormat(identifierType)) {
|
|
|
|
logicalType = getDefaultLogicalType(identifierType);
|
|
|
|
}
|
2017-09-27 09:33:20 -07:00
|
|
|
|
2017-08-07 22:21:05 -07:00
|
|
|
setProperties(currentFieldInComplianceList, {
|
2017-08-15 23:16:38 -07:00
|
|
|
identifierType,
|
2017-09-27 09:33:20 -07:00
|
|
|
logicalType,
|
|
|
|
isDirty: true
|
2017-05-19 08:45:05 -07:00
|
|
|
});
|
2017-06-06 11:34:53 -07:00
|
|
|
// Set the defaultClassification for the identifierField,
|
|
|
|
// although the classification is based on the logicalType,
|
|
|
|
// an identifierField may only have one valid logicalType for it's given identifierType
|
2017-08-15 23:16:38 -07:00
|
|
|
this.setDefaultClassification({ identifierField, identifierType });
|
2017-02-13 14:51:31 -08:00
|
|
|
},
|
|
|
|
|
2017-03-27 18:26:09 -07:00
|
|
|
/**
|
2017-05-19 08:45:05 -07:00
|
|
|
* Updates the logical type for the given identifierField
|
2017-12-12 22:19:01 -08:00
|
|
|
* @param {IComplianceEntity} field
|
2017-05-19 08:45:05 -07:00
|
|
|
* @prop {String} field.identifierField
|
2017-12-12 17:47:28 -08:00
|
|
|
* @param {IComplianceField.logicalType} logicalType
|
|
|
|
* @return {*|void}
|
2017-03-27 18:26:09 -07:00
|
|
|
*/
|
2017-12-12 17:47:28 -08:00
|
|
|
onFieldLogicalTypeChange(field, logicalType) {
|
|
|
|
// default to undefined for falsey values
|
|
|
|
logicalType || (logicalType = void 0);
|
2017-05-19 08:45:05 -07:00
|
|
|
|
2017-12-12 22:19:01 -08:00
|
|
|
setProperties(field, { logicalType, isDirty: true });
|
2017-02-13 14:51:31 -08:00
|
|
|
},
|
|
|
|
|
2017-03-27 00:08:08 -07:00
|
|
|
/**
|
2017-05-19 08:45:05 -07:00
|
|
|
* Updates the filed classification
|
|
|
|
* @param {String} identifierField the identifier field to update the classification for
|
|
|
|
* @param {String} classification
|
|
|
|
* @return {*}
|
2017-03-27 00:08:08 -07:00
|
|
|
*/
|
2017-05-19 08:45:05 -07:00
|
|
|
onFieldClassificationChange({ identifierField }, { value: classification = null }) {
|
2017-09-27 09:33:20 -07:00
|
|
|
const currentFieldInComplianceList = get(this, 'compliancePolicyChangeSet').findBy(
|
2017-08-07 22:21:05 -07:00
|
|
|
'identifierField',
|
|
|
|
identifierField
|
|
|
|
);
|
2017-04-02 15:40:43 -07:00
|
|
|
// TODO:DSS-6719 refactor into mixin
|
|
|
|
this.clearMessages();
|
|
|
|
|
2017-08-07 22:21:05 -07:00
|
|
|
// Apply the updated classification value to the current instance of the field in working copy
|
2017-09-27 09:33:20 -07:00
|
|
|
setProperties(currentFieldInComplianceList, { classification, isDirty: true });
|
2017-03-27 00:08:08 -07:00
|
|
|
},
|
|
|
|
|
2017-03-27 18:30:44 -07:00
|
|
|
/**
|
2017-05-19 08:45:05 -07:00
|
|
|
* 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
|
2017-05-19 18:26:38 -07:00
|
|
|
* @return {*}
|
2017-03-27 18:30:44 -07:00
|
|
|
*/
|
2017-05-19 08:45:05 -07:00
|
|
|
onChangeDatasetClassification(classifier, value) {
|
2017-08-20 18:12:56 -07:00
|
|
|
return set(this.getDatasetClassificationRef(), classifier, value);
|
2017-02-13 14:51:31 -08:00
|
|
|
},
|
|
|
|
|
2017-10-19 18:17:16 -07:00
|
|
|
/**
|
|
|
|
* Updates the complianceType on the compliance policy
|
|
|
|
* @param {PurgePolicy} purgePolicy
|
|
|
|
*/
|
|
|
|
onDatasetPurgePolicyChange(purgePolicy) {
|
|
|
|
// directly set the complianceType to the updated value
|
|
|
|
return set(this, 'complianceInfo.complianceType', purgePolicy);
|
|
|
|
},
|
|
|
|
|
2017-12-06 10:23:33 -08:00
|
|
|
/**
|
|
|
|
* Updates the policy flag indicating that this dataset contains personal data
|
|
|
|
* @param {boolean} containsPersonalData
|
|
|
|
* @returns boolean
|
|
|
|
*/
|
|
|
|
onDatasetLevelPolicyChange(containsPersonalData) {
|
|
|
|
// directly mutate the attribute on the complianceInfo object
|
|
|
|
return set(this, 'complianceInfo.containingPersonalData', containsPersonalData);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Updates the confidentiality flag on the dataset compliance
|
|
|
|
* @param {null | Classification} [securityClassification=null]
|
|
|
|
* @returns null | Classification
|
|
|
|
*/
|
|
|
|
onDatasetSecurityClassificationChange(securityClassification = null) {
|
|
|
|
return set(this, 'complianceInfo.confidentiality', securityClassification);
|
|
|
|
},
|
|
|
|
|
2017-03-27 18:50:55 -07:00
|
|
|
/**
|
|
|
|
* If all validity checks are passed, invoke onSave action on controller
|
|
|
|
*/
|
2017-07-18 15:01:28 -07:00
|
|
|
async saveCompliance() {
|
|
|
|
const setSaveFlag = (flag = false) => set(this, 'isSaving', flag);
|
2017-05-19 18:26:38 -07:00
|
|
|
|
2017-08-29 16:50:44 -07:00
|
|
|
try {
|
|
|
|
const isSaving = true;
|
|
|
|
const onSave = get(this, 'onSave');
|
|
|
|
setSaveFlag(isSaving);
|
|
|
|
|
|
|
|
return await this.whenRequestCompletes(onSave(), { isSaving });
|
|
|
|
} finally {
|
|
|
|
setSaveFlag();
|
2017-03-27 18:50:55 -07:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2017-02-13 14:51:31 -08:00
|
|
|
// Rolls back changes made to the compliance spec to current
|
|
|
|
// server state
|
2017-03-27 18:26:09 -07:00
|
|
|
resetCompliance() {
|
2017-04-02 15:40:43 -07:00
|
|
|
const options = {
|
|
|
|
successMessage: 'Field classification has been reset to the previously saved state.'
|
|
|
|
};
|
|
|
|
this.whenRequestCompletes(get(this, 'onReset')(), options);
|
2017-02-13 14:51:31 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|