2017-02-13 14:51:31 -08:00
|
|
|
import Ember from 'ember';
|
|
|
|
|
2017-03-24 20:27:43 -07:00
|
|
|
const {
|
|
|
|
Component,
|
|
|
|
computed,
|
|
|
|
set,
|
|
|
|
get,
|
2017-03-26 23:27:18 -07:00
|
|
|
isBlank,
|
2017-03-24 20:27:43 -07:00
|
|
|
getWithDefault
|
|
|
|
} = Ember;
|
|
|
|
|
|
|
|
const complianceListKey = 'privacyCompliancePolicy.compliancePurgeEntities';
|
2017-03-30 15:24:01 -07:00
|
|
|
// TODO: DSS-6671 Extract to constants module
|
|
|
|
const logicalTypes = ['ID', 'URN', 'REVERSED_URN', 'COMPOSITE_URN'];
|
2017-03-30 15:08:12 -07:00
|
|
|
/**
|
|
|
|
* Duplicate check using every to short-circuit iteration
|
|
|
|
* @param {Array} names = [] the list to check for dupes
|
|
|
|
* @return {Boolean} true is unique, false otherwise
|
|
|
|
*/
|
|
|
|
const fieldNamesAreUnique = (names = []) =>
|
|
|
|
names.every((name, index) => names.indexOf(name) === index);
|
2017-03-24 20:27:43 -07:00
|
|
|
|
|
|
|
/**
|
2017-03-27 18:26:09 -07:00
|
|
|
* Returns a computed macro based on a provided type will return a list of
|
|
|
|
* Compliance fields that are of that identifierType or have no type
|
|
|
|
* @param {String} type string to match against identifierType
|
2017-03-24 20:27:43 -07:00
|
|
|
*/
|
2017-03-27 18:26:09 -07:00
|
|
|
const complianceEntitiesMatchingType = type => {
|
|
|
|
return computed('complianceDataFields.[]', function() {
|
|
|
|
const fieldRegex = new RegExp(`${type}`, 'i');
|
2017-03-24 20:27:43 -07:00
|
|
|
|
2017-03-27 18:26:09 -07:00
|
|
|
return get(this, 'complianceDataFields').filter(({ identifierType }) => {
|
|
|
|
return fieldRegex.test(identifierType) || isBlank(identifierType);
|
|
|
|
});
|
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-03-24 20:27:43 -07:00
|
|
|
|
2017-03-27 18:26:09 -07:00
|
|
|
/**
|
|
|
|
* Map of radio Group state values
|
|
|
|
* Each initially has an indeterminate state, as the user
|
|
|
|
* progresses through the prompts
|
|
|
|
* @type {Object.<Boolean, null>}
|
|
|
|
*/
|
|
|
|
userIndicatesDatasetHas: {
|
|
|
|
member: null,
|
|
|
|
org: null,
|
|
|
|
group: null
|
2017-02-13 14:51:31 -08:00
|
|
|
},
|
|
|
|
|
2017-03-30 15:08:12 -07:00
|
|
|
didReceiveAttrs() {
|
|
|
|
this._super(...arguments);
|
|
|
|
// Perform validation step on the received component attributes
|
|
|
|
this.validateAttrs();
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Ensure that props received from on this component
|
|
|
|
* are valid, otherwise flag
|
|
|
|
*/
|
|
|
|
validateAttrs() {
|
|
|
|
const fieldNames = getWithDefault(
|
|
|
|
this, 'schemaFieldNamesMappedToDataTypes', []
|
|
|
|
).mapBy('fieldName');
|
|
|
|
|
|
|
|
if (fieldNamesAreUnique(fieldNames.sort())) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Flag this component's data as problematic
|
|
|
|
set(this, '_hasBadData', true);
|
|
|
|
},
|
|
|
|
|
2017-03-27 18:26:09 -07:00
|
|
|
// Component ui state transitions based on the userIndicatesDatasetHas map
|
|
|
|
hasUserRespondedToMemberPrompt: computed.notEmpty(
|
|
|
|
'userIndicatesDatasetHas.member'
|
|
|
|
),
|
2017-03-30 15:08:12 -07:00
|
|
|
|
2017-03-27 18:26:09 -07:00
|
|
|
showOrgPrompt: computed.bool('hasUserRespondedToMemberPrompt'),
|
|
|
|
hasUserRespondedToOrgPrompt: computed.notEmpty('userIndicatesDatasetHas.org'),
|
|
|
|
showGroupPrompt: computed.bool('hasUserRespondedToOrgPrompt'),
|
|
|
|
|
|
|
|
// Map logicalTypes to options consumable by ui
|
|
|
|
logicalTypes: ['', ...logicalTypes].map(value => ({
|
|
|
|
value,
|
2017-03-30 15:24:01 -07:00
|
|
|
label: value ? value.replace('_', ' ').toLowerCase().capitalize() : 'Please Select'
|
2017-03-24 20:27:43 -07:00
|
|
|
})),
|
|
|
|
|
2017-03-27 18:26:09 -07:00
|
|
|
/**
|
|
|
|
* Lists all dataset fields found in the `columns` performs an intersection
|
|
|
|
* of fields with the currently persisted and/or updated
|
|
|
|
* privacyCompliancePolicy.compliancePurgeEntities.
|
|
|
|
* The returned list is a map of fields with current or default privacy properties
|
|
|
|
*/
|
|
|
|
complianceDataFields: computed(
|
|
|
|
`${complianceListKey}.@each.identifierType`,
|
|
|
|
`${complianceListKey}.[]`,
|
|
|
|
'schemaFieldNamesMappedToDataTypes',
|
|
|
|
function() {
|
|
|
|
const sourceEntities = getWithDefault(this, complianceListKey, []);
|
|
|
|
const complianceFieldNames = sourceEntities.mapBy('identifierField');
|
|
|
|
|
|
|
|
const getAttributeOnField = (attribute, fieldName) => {
|
|
|
|
const sourceField = getWithDefault(this, complianceListKey, []).find(
|
|
|
|
({ identifierField }) => identifierField === fieldName
|
|
|
|
);
|
|
|
|
return sourceField ? sourceField[attribute] : null;
|
|
|
|
};
|
|
|
|
|
2017-03-30 15:08:12 -07:00
|
|
|
/**
|
|
|
|
* Get value for a list of attributes
|
|
|
|
* @param {Array} attributes list of attribute keys to pull from
|
|
|
|
* sourceField
|
|
|
|
* @param {String} fieldName name of the field to lookup
|
|
|
|
* @return {Array} list of attribute values
|
|
|
|
*/
|
|
|
|
const getAttributesOnField = (attributes = [], fieldName) =>
|
|
|
|
attributes.map((attr) => getAttributeOnField(attr, fieldName));
|
|
|
|
|
2017-03-27 18:26:09 -07:00
|
|
|
// Set default or if already in policy, retrieve current values from
|
|
|
|
// privacyCompliancePolicy.compliancePurgeEntities
|
2017-03-30 15:08:12 -07:00
|
|
|
return getWithDefault(this, 'schemaFieldNamesMappedToDataTypes', [])
|
|
|
|
.map(({ fieldName: identifierField, dataType }) => {
|
|
|
|
const hasPrivacyData = complianceFieldNames.includes(identifierField);
|
|
|
|
const [
|
|
|
|
identifierType,
|
|
|
|
isSubject,
|
|
|
|
logicalType
|
|
|
|
] = getAttributesOnField([
|
|
|
|
'identifierType',
|
|
|
|
'isSubject',
|
|
|
|
'logicalType'
|
|
|
|
], identifierField);
|
|
|
|
|
|
|
|
return {
|
|
|
|
dataType,
|
|
|
|
identifierField,
|
|
|
|
identifierType,
|
|
|
|
isSubject,
|
|
|
|
logicalType,
|
|
|
|
hasPrivacyData
|
|
|
|
};
|
|
|
|
});
|
2017-03-27 18:26:09 -07:00
|
|
|
}
|
|
|
|
),
|
|
|
|
|
|
|
|
// Compliance entities filtered for each identifierType
|
|
|
|
memberComplianceEntities: complianceEntitiesMatchingType('member'),
|
|
|
|
orgComplianceEntities: complianceEntitiesMatchingType('organization'),
|
|
|
|
groupComplianceEntities: complianceEntitiesMatchingType('group'),
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Changes the logicalType on a field.
|
|
|
|
* Ensures that the logicalType / format is applicable to the specified field
|
|
|
|
* @param {String} fieldName the fieldName identifying the field to be updated
|
|
|
|
* @param {String} format logicalType or format te field is in
|
|
|
|
* @return {String| void}
|
|
|
|
*/
|
|
|
|
changeFieldLogicalType(fieldName, format) {
|
|
|
|
const sourceField = get(this, complianceListKey).findBy(
|
|
|
|
'identifierField',
|
|
|
|
fieldName
|
|
|
|
);
|
|
|
|
|
|
|
|
if (sourceField && logicalTypes.includes(format)) {
|
|
|
|
return set(sourceField, 'logicalType', String(format).toUpperCase());
|
|
|
|
}
|
2017-02-13 14:51:31 -08:00
|
|
|
},
|
|
|
|
|
2017-03-27 18:26:09 -07:00
|
|
|
/**
|
|
|
|
* Adds or removes a field onto the
|
|
|
|
* privacyCompliancePolicy.compliancePurgeEntities list.
|
|
|
|
* @param {Object} props initial props for the field to be added
|
2017-03-30 15:08:12 -07:00
|
|
|
* @prop {String} props.identifierField
|
|
|
|
* @prop {String} props.dataType
|
2017-03-27 18:26:09 -07:00
|
|
|
* @param {String} identifierType the type of the field to toggle
|
|
|
|
* @param {('add'|'remove')} toggle operation to perform, can either be
|
|
|
|
* add or remove
|
|
|
|
* @return {Ember.Array|*}
|
|
|
|
*/
|
|
|
|
toggleFieldOnComplianceList(props, identifierType, toggle) {
|
2017-03-30 15:08:12 -07:00
|
|
|
const { identifierField, dataType } = props;
|
2017-03-27 18:26:09 -07:00
|
|
|
const sourceEntities = get(this, complianceListKey);
|
|
|
|
|
|
|
|
if (!['add', 'remove'].includes(toggle)) {
|
|
|
|
throw new Error(`Unsupported toggle operation ${toggle}`);
|
2017-02-13 14:51:31 -08:00
|
|
|
}
|
|
|
|
|
2017-03-27 18:26:09 -07:00
|
|
|
return {
|
2017-03-24 20:27:43 -07:00
|
|
|
add() {
|
2017-03-27 18:26:09 -07:00
|
|
|
// Ensure that we don't currently have this field present on the
|
|
|
|
// privacyCompliancePolicy.compliancePurgeEntities list
|
|
|
|
if (!sourceEntities.findBy('identifierField', identifierField)) {
|
2017-03-30 15:08:12 -07:00
|
|
|
const addPurgeEntity = { identifierField, identifierType, dataType };
|
2017-03-27 18:26:09 -07:00
|
|
|
|
|
|
|
return sourceEntities.setObjects([addPurgeEntity, ...sourceEntities]);
|
2017-02-13 14:51:31 -08:00
|
|
|
}
|
2017-03-24 20:27:43 -07:00
|
|
|
},
|
|
|
|
|
2017-03-30 15:08:12 -07:00
|
|
|
remove() {
|
2017-03-27 18:26:09 -07:00
|
|
|
// Remove the identifierType since we are removing it from the
|
|
|
|
// privacyCompliancePolicy.compliancePurgeEntities in case it
|
|
|
|
// is added back during the session
|
|
|
|
set(props, 'identifierType', null);
|
|
|
|
return sourceEntities.setObjects(
|
|
|
|
sourceEntities.filter(
|
|
|
|
item => item.identifierField !== identifierField
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
2017-03-27 18:50:55 -07:00
|
|
|
}[toggle]();
|
2017-02-13 14:51:31 -08:00
|
|
|
},
|
|
|
|
|
2017-03-27 18:50:55 -07:00
|
|
|
/**
|
|
|
|
* Checks that each privacyCompliancePolicy.compliancePurgeEntities has
|
|
|
|
* a valid identifierType
|
|
|
|
* @param {Ember.Array} sourceEntities compliancePurgeEntities
|
|
|
|
* @return {Boolean} has or does not
|
|
|
|
*/
|
|
|
|
ensureTypeContainsFormat: sourceEntities =>
|
|
|
|
sourceEntities.every(entity =>
|
|
|
|
['MEMBER', 'ORGANIZATION', 'GROUP'].includes(
|
|
|
|
get(entity, 'identifierType')
|
|
|
|
)),
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks that each privacyCompliancePolicy.compliancePurgeEntities has
|
|
|
|
* a valid logicalType
|
|
|
|
* @param {Ember.Array}sourceEntities compliancePurgeEntities
|
|
|
|
* @return {Boolean|*} Contains or does not
|
|
|
|
*/
|
|
|
|
ensureTypeContainsLogicalType: sourceEntities => {
|
2017-03-29 18:51:26 -07:00
|
|
|
const logicalTypesInUppercase = logicalTypes.map(type => type.toUpperCase());
|
2017-03-27 18:50:55 -07:00
|
|
|
|
|
|
|
return sourceEntities.every(entity =>
|
2017-03-29 18:51:26 -07:00
|
|
|
logicalTypesInUppercase.includes(get(entity, 'logicalType')));
|
2017-03-27 18:50:55 -07:00
|
|
|
},
|
2017-03-24 20:27:43 -07:00
|
|
|
|
2017-02-13 14:51:31 -08:00
|
|
|
actions: {
|
2017-03-27 18:26:09 -07:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param {String} identifierField id for the field to update
|
|
|
|
* @param {String} logicalType updated format to apply to the field
|
|
|
|
* @return {*|String|void} logicalType or void
|
|
|
|
*/
|
|
|
|
onFieldFormatChange({ identifierField }, { value: logicalType }) {
|
|
|
|
return this.changeFieldLogicalType(identifierField, logicalType);
|
2017-02-13 14:51:31 -08:00
|
|
|
},
|
|
|
|
|
2017-03-27 18:26:09 -07:00
|
|
|
/**
|
|
|
|
* Toggles a field on / off the compliance list
|
|
|
|
* @param {String} identifierType the type of the field to be toggled on
|
|
|
|
* the privacyCompliancePolicy.compliancePurgeEntities list
|
|
|
|
* @param {Object|Ember.Object} props containing the props to be added
|
2017-03-30 15:08:12 -07:00
|
|
|
* @prop {Boolean} props.hasPrivacyData checked or not checked
|
2017-03-27 18:26:09 -07:00
|
|
|
* @return {*}
|
|
|
|
*/
|
|
|
|
onFieldPrivacyChange(identifierType, props) {
|
|
|
|
// If checked, add, otherwise remove
|
|
|
|
const { hasPrivacyData } = props;
|
2017-03-24 20:27:43 -07:00
|
|
|
const toggle = !hasPrivacyData ? 'add' : 'remove';
|
2017-02-13 14:51:31 -08:00
|
|
|
|
2017-03-27 18:26:09 -07:00
|
|
|
return this.toggleFieldOnComplianceList(props, identifierType, toggle);
|
2017-02-13 14:51:31 -08:00
|
|
|
},
|
|
|
|
|
2017-03-27 00:08:08 -07:00
|
|
|
/**
|
|
|
|
* Toggles the isSubject property of a member identifiable field
|
2017-03-27 18:26:09 -07:00
|
|
|
* @param {Object} props the props on the member field to update
|
|
|
|
* @prop {Boolean} isSubject flag indicating this field as a subject owner
|
|
|
|
* when true
|
|
|
|
* @prop {String} identifierField unique field to update isSubject property
|
2017-03-27 00:08:08 -07:00
|
|
|
*/
|
2017-03-27 18:26:09 -07:00
|
|
|
onMemberFieldSubjectChange(props) {
|
|
|
|
const { isSubject, identifierField: name } = props;
|
|
|
|
|
|
|
|
// Ensure that a flag isSubject is present on the props
|
|
|
|
if (props && 'isSubject' in props) {
|
|
|
|
const sourceField = get(this, complianceListKey).find(
|
|
|
|
({ identifierField }) => identifierField === name
|
|
|
|
);
|
2017-03-27 00:08:08 -07:00
|
|
|
|
2017-03-27 18:26:09 -07:00
|
|
|
set(sourceField, 'isSubject', !isSubject);
|
2017-03-27 00:08:08 -07:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2017-03-27 18:30:44 -07:00
|
|
|
/**
|
|
|
|
* Updates the state flags that transition the prompts from one to the next
|
|
|
|
* @param {String} sectionName name of the section that was changed
|
|
|
|
* @param {Boolean} isPrivacyIdentifiable flag indicating that a section has
|
|
|
|
* or does not have privacy identifier
|
|
|
|
*/
|
|
|
|
didChangePrivacyIdentifiable(sectionName, isPrivacyIdentifiable) {
|
|
|
|
const section = {
|
|
|
|
'has-group': 'group',
|
|
|
|
'has-org': 'org',
|
|
|
|
'has-member': 'member'
|
|
|
|
}[sectionName];
|
|
|
|
|
|
|
|
return set(
|
|
|
|
this,
|
|
|
|
`userIndicatesDatasetHas.${section}`,
|
|
|
|
isPrivacyIdentifiable
|
|
|
|
);
|
2017-02-13 14:51:31 -08:00
|
|
|
},
|
|
|
|
|
2017-03-27 18:50:55 -07:00
|
|
|
/**
|
|
|
|
* If all validity checks are passed, invoke onSave action on controller
|
|
|
|
*/
|
|
|
|
saveCompliance() {
|
|
|
|
const allEntitiesHaveValidFormat = this.ensureTypeContainsFormat(
|
|
|
|
get(this, complianceListKey)
|
|
|
|
);
|
|
|
|
const allEntitiesHaveValidLogicalType = this.ensureTypeContainsLogicalType(
|
|
|
|
get(this, complianceListKey)
|
|
|
|
);
|
|
|
|
|
|
|
|
if (allEntitiesHaveValidFormat && allEntitiesHaveValidLogicalType) {
|
|
|
|
return this.get('onSave')();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
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-02-13 14:51:31 -08:00
|
|
|
this.get('onReset')();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|