fixes issue with rendering saved field multi-tags: allows for compliance entities to contain duplicate values

This commit is contained in:
Seyi Adebajo 2018-04-17 22:49:01 -07:00
parent dde6ff6fbe
commit 38f8ab6bc3
5 changed files with 105 additions and 101 deletions

View File

@ -11,7 +11,7 @@ import {
changeSetFieldsRequiringReview, changeSetFieldsRequiringReview,
changeSetReviewableAttributeTriggers, changeSetReviewableAttributeTriggers,
ComplianceFieldIdValue, ComplianceFieldIdValue,
complianceFieldTagFactory, complianceFieldChangeSetItemFactory,
SuggestionIntent SuggestionIntent
} from 'wherehows-web/constants'; } from 'wherehows-web/constants';
import { getTagSuggestions } from 'wherehows-web/utils/datasets/compliance-suggestions'; import { getTagSuggestions } from 'wherehows-web/utils/datasets/compliance-suggestions';
@ -245,7 +245,7 @@ export default class DatasetComplianceRollupRow extends Component.extend({
if (isFieldTagged(fieldChangeSet)) { if (isFieldTagged(fieldChangeSet)) {
onFieldTagAdded( onFieldTagAdded(
complianceFieldTagFactory({ complianceFieldChangeSetItemFactory({
identifierField, identifierField,
dataType, dataType,
identifierType, identifierType,

View File

@ -23,7 +23,7 @@ import {
SuggestionIntent, SuggestionIntent,
PurgePolicy, PurgePolicy,
getSupportedPurgePolicies, getSupportedPurgePolicies,
mergeMappedColumnFieldsWithSuggestions, mergeComplianceEntitiesWithSuggestions,
getFieldsRequiringReview, getFieldsRequiringReview,
isFieldIdType, isFieldIdType,
idTypeFieldsHaveLogicalType, idTypeFieldsHaveLogicalType,
@ -661,7 +661,7 @@ export default class DatasetCompliance extends Component {
'identifierFieldToSuggestion', 'identifierFieldToSuggestion',
function(this: DatasetCompliance): Array<IComplianceChangeSet> { function(this: DatasetCompliance): Array<IComplianceChangeSet> {
// schemaFieldNamesMappedToDataTypes is a dependency for CP columnIdFieldsToCurrentPrivacyPolicy, so no need to dep on that directly // schemaFieldNamesMappedToDataTypes is a dependency for CP columnIdFieldsToCurrentPrivacyPolicy, so no need to dep on that directly
const changeSet = mergeMappedColumnFieldsWithSuggestions( const changeSet = mergeComplianceEntitiesWithSuggestions(
get(this, 'columnIdFieldsToCurrentPrivacyPolicy'), get(this, 'columnIdFieldsToCurrentPrivacyPolicy'),
get(this, 'identifierFieldToSuggestion') get(this, 'identifierFieldToSuggestion')
); );

View File

@ -4,7 +4,6 @@ import { IComplianceDataType } from 'wherehows-web/typings/api/list/compliance-d
import { arrayEvery, arrayFilter, arrayMap, arrayReduce } from 'wherehows-web/utils/array'; import { arrayEvery, arrayFilter, arrayMap, arrayReduce } from 'wherehows-web/utils/array';
import { fleece, hasEnumerableKeys } from 'wherehows-web/utils/object'; import { fleece, hasEnumerableKeys } from 'wherehows-web/utils/object';
import { lastSeenSuggestionInterval } from 'wherehows-web/constants/metadata-acquisition'; import { lastSeenSuggestionInterval } from 'wherehows-web/constants/metadata-acquisition';
import { pick } from 'lodash';
import { decodeUrn } from 'wherehows-web/utils/validators/urn'; import { decodeUrn } from 'wherehows-web/utils/validators/urn';
import { import {
IComplianceChangeSet, IComplianceChangeSet,
@ -12,13 +11,15 @@ import {
IdentifierFieldWithFieldChangeSetTuple, IdentifierFieldWithFieldChangeSetTuple,
IIdentifierFieldWithFieldChangeSetObject, IIdentifierFieldWithFieldChangeSetObject,
ISchemaFieldsToPolicy, ISchemaFieldsToPolicy,
ISchemaFieldsToSuggested ISchemaFieldsToSuggested,
IComplianceEntityWithMetadata
} from 'wherehows-web/typings/app/dataset-compliance'; } from 'wherehows-web/typings/app/dataset-compliance';
import { import {
IColumnFieldProps, IColumnFieldProps,
ICompliancePolicyReducerFactory, ISchemaColumnMappingProps,
ISchemaColumnMappingProps ISchemaWithPolicyTagsReducingFn
} from 'wherehows-web/typings/app/dataset-columns'; } from 'wherehows-web/typings/app/dataset-columns';
import { IDatasetColumn } from 'wherehows-web/typings/api/datasets/columns';
/** /**
* Defines a map of values for the compliance policy on a dataset * Defines a map of values for the compliance policy on a dataset
@ -246,39 +247,35 @@ const changeSetFieldsRequiringReview = (complianceDataTypes: Array<IComplianceDa
arrayFilter<IComplianceChangeSet>(fieldChangeSetRequiresReview(complianceDataTypes)); arrayFilter<IComplianceChangeSet>(fieldChangeSetRequiresReview(complianceDataTypes));
/** /**
* Merges the column fields with the suggestion for the field if available * Extracts a suggestion for a field from a suggestion map and merges a compliance entity with the suggestion
* @param {object} mappedColumnFields a map of column fields to compliance entity properties * @param {ISchemaFieldsToSuggested} suggestionMap a hash of compliance fields to suggested values
* @param {object} fieldSuggestionMap a map of field suggestion properties keyed by field name * @return {(entity: IComplianceEntityWithMetadata) => IComplianceChangeSet}
* @return {Array<object>} mapped column field augmented with suggestion if available
*/ */
const mergeMappedColumnFieldsWithSuggestions = ( const complianceEntityWithSuggestions = (suggestionMap: ISchemaFieldsToSuggested) => (
mappedColumnFields: ISchemaFieldsToPolicy = {}, entity: IComplianceEntityWithMetadata
fieldSuggestionMap: ISchemaFieldsToSuggested = {} ): IComplianceChangeSet => {
const { identifierField, policyModificationTime } = entity;
const suggestion = suggestionMap[identifierField];
return suggestion && isRecentSuggestion(policyModificationTime, suggestion.suggestionsModificationTime)
? { ...entity, suggestion }
: entity;
};
/**
* Creates a list of IComplianceChangeSet instances by merging compliance entities with the related suggestions for
* the identifier field
* @param {ISchemaFieldsToPolicy} [schemaEntityMap={}] a map of fields on the dataset schema to the current compliance
* entities found on the policy
* @param {ISchemaFieldsToSuggested} [suggestionMap={}] map of fields on the dataset schema to suggested compliance
* values
* @returns {Array<IComplianceChangeSet>}
*/
const mergeComplianceEntitiesWithSuggestions = (
schemaEntityMap: ISchemaFieldsToPolicy = {},
suggestionMap: ISchemaFieldsToSuggested = {}
): Array<IComplianceChangeSet> => ): Array<IComplianceChangeSet> =>
Object.keys(mappedColumnFields).map(fieldName => { arrayMap(complianceEntityWithSuggestions(suggestionMap))([].concat.apply([], Object.values(schemaEntityMap)));
const field: IComplianceChangeSet = pick(mappedColumnFields[fieldName], [
'identifierField',
'dataType',
'identifierType',
'logicalType',
'securityClassification',
'policyModificationTime',
'privacyPolicyExists',
'isDirty',
'nonOwner',
'readonly'
]);
const { identifierField, policyModificationTime } = field;
const suggestion = fieldSuggestionMap[identifierField];
// If a suggestion exists for this field add the suggestion attribute to the field properties / changeSet
// Check if suggestion isRecent before augmenting, otherwise, suggestion will not be considered on changeSet
if (suggestion && isRecentSuggestion(policyModificationTime, suggestion.suggestionsModificationTime)) {
return { ...field, suggestion };
}
return field;
});
/** /**
* Creates a map of compliance changeSet identifier field to compliance change sets * Creates a map of compliance changeSet identifier field to compliance change sets
@ -324,33 +321,6 @@ const createInitialComplianceInfo = (datasetId: string): IComplianceInfo => {
}; };
}; };
/**
* Extracts the values on a compliance Entity for a given list of keys
* @template K IComplianceEntity instance attribute
* @param {Array<K>} [keys=[]]
* @param {string} fieldName
* @param {IComplianceInfo.complianceEntities} [source=[]]
* @returns {({ [V in K]: IComplianceEntity[V] } | {})}
*/
const getKeysOnComplianceEntity = <K extends keyof IComplianceEntity>(
keys: Array<K> = [],
fieldName: string,
source: IComplianceInfo['complianceEntities'] = []
): { [V in K]: IComplianceEntity[V] } | {} => {
const sourceField: IComplianceEntity | void = source.find(({ identifierField }) => identifierField === fieldName);
let result = {};
if (sourceField) {
for (const [key, value] of <Array<[K, IComplianceEntity[K]]>>Object.entries(sourceField)) {
if (keys.includes(key)) {
result = { ...result, [key]: value };
}
}
}
return result;
};
/** /**
* Maps the fields found in the column property on the schema api to the values returned in the current privacy policy * Maps the fields found in the column property on the schema api to the values returned in the current privacy policy
* @param {ISchemaColumnMappingProps} { * @param {ISchemaColumnMappingProps} {
@ -365,14 +335,14 @@ const mapSchemaColumnPropsToCurrentPrivacyPolicy = ({
complianceEntities, complianceEntities,
policyModificationTime policyModificationTime
}: ISchemaColumnMappingProps): ISchemaFieldsToPolicy => }: ISchemaColumnMappingProps): ISchemaFieldsToPolicy =>
arrayReduce(columnToPolicyReducingFn(complianceEntities, policyModificationTime), {})(columnProps); arrayReduce(schemaFieldsWithPolicyTagsReducingFn(complianceEntities, policyModificationTime), {})(columnProps);
/** /**
* Creates a new tag / change set item for a compliance entity / field with default properties * Creates a new tag / change set item for a compliance entity / field with default properties
* @param {IColumnFieldProps} { identifierField, dataType } the runtime properties to apply to the created instance * @param {IColumnFieldProps} { identifierField, dataType } the runtime properties to apply to the created instance
* @returns {SchemaFieldToPolicyValue} * @returns {IComplianceEntityWithMetadata}
*/ */
const complianceFieldTagFactory = ({ const complianceFieldChangeSetItemFactory = ({
identifierField, identifierField,
dataType, dataType,
identifierType, identifierType,
@ -396,38 +366,72 @@ const complianceFieldTagFactory = ({
suggestionAuthority ? { suggestionAuthority } : void 0 suggestionAuthority ? { suggestionAuthority } : void 0
); );
/**
* Asserts that a schema field name matches the compliance entity supplied later
* @param {string} identifierFieldMatch the field name to match to the IComplianceEntity
* @return {({ identifierField }: IComplianceEntity) => boolean}
*/
const isSchemaFieldTag = (identifierFieldMatch: string) => ({ identifierField }: IComplianceEntity): boolean =>
identifierFieldMatch === identifierField;
/**
* Creates an instance of a compliance entity with client side metadata about the entity
* @param {IComplianceInfo.modifiedTime} policyLastModified time the compliance policy was last modified
* @param {IDatasetColumn.dataType} dataType the field data type
* @return {(arg: IComplianceEntity) => IComplianceEntityWithMetadata}
*/
const complianceEntityWithMetadata = (
policyLastModified: IComplianceInfo['modifiedTime'],
dataType: IDatasetColumn['dataType']
): ((arg: IComplianceEntity) => IComplianceEntityWithMetadata) => (
tag: IComplianceEntity
): IComplianceEntityWithMetadata => ({
...tag,
policyModificationTime: policyLastModified,
dataType,
privacyPolicyExists: hasEnumerableKeys(tag),
isDirty: false
});
/** /**
* Takes the current compliance entities, and mod time and returns a reducer that consumes a list of IColumnFieldProps * Takes the current compliance entities, and mod time and returns a reducer that consumes a list of IColumnFieldProps
* instances and maps each entry to a compliance entity on the current compliance policy * instances and maps each entry to a compliance entity on the current compliance policy
* @param {IComplianceInfo.complianceEntities} currentEntities * @param {IComplianceInfo.complianceEntities} currentEntities
* @param {IComplianceInfo.modifiedTime} policyModificationTime * @param {IComplianceInfo.modifiedTime} policyModificationTime
* @type ICompliancePolicyReducerFactory * @return {(schemaFieldsToPolicy: ISchemaFieldsToPolicy, { identifierField, dataType}: IColumnFieldProps) => ISchemaFieldsToPolicy}
*/ */
const columnToPolicyReducingFn: ICompliancePolicyReducerFactory = ( const schemaFieldsWithPolicyTagsReducingFn: ISchemaWithPolicyTagsReducingFn = (
currentEntities: IComplianceInfo['complianceEntities'], currentEntities: IComplianceInfo['complianceEntities'],
policyModificationTime: IComplianceInfo['modifiedTime'] policyModificationTime: IComplianceInfo['modifiedTime']
) => (acc: ISchemaFieldsToPolicy, { identifierField, dataType }: IColumnFieldProps) => { ) => (
const currentPrivacyAttrs = getKeysOnComplianceEntity( schemaFieldsToPolicy: ISchemaFieldsToPolicy,
['identifierType', 'logicalType', 'securityClassification', 'nonOwner', 'readonly'], { identifierField, dataType }: IColumnFieldProps
identifierField, ): ISchemaFieldsToPolicy => {
currentEntities let complianceEntitiesWithMetadata: Array<IComplianceEntityWithMetadata>;
let schemaFieldTags = arrayFilter(isSchemaFieldTag(identifierField))(currentEntities);
schemaFieldTags = schemaFieldTags.length ? schemaFieldTags : [complianceFieldTagFactory(identifierField)];
complianceEntitiesWithMetadata = arrayMap(complianceEntityWithMetadata(policyModificationTime, dataType))(
schemaFieldTags
); );
// assertion required due to TS spread object limitation, not present with Object#assign, but this reads cleaner return { ...schemaFieldsToPolicy, [identifierField]: complianceEntitiesWithMetadata };
return <ISchemaFieldsToPolicy>{
...acc,
[identifierField]: {
identifierField,
dataType,
readonly: false, // default value overridden by value in currentPrivacyAttrs below
...currentPrivacyAttrs,
policyModificationTime,
privacyPolicyExists: hasEnumerableKeys(currentPrivacyAttrs),
isDirty: false
}
};
}; };
/**
* Constructs an instance of IComplianceEntity with default values, and an identifierField
* @param {IComplianceEntity.identifierField} identifierField
* @return {IComplianceEntity}
*/
const complianceFieldTagFactory = (identifierField: IComplianceEntity['identifierField']): IComplianceEntity => ({
identifierField,
identifierType: null,
logicalType: null,
securityClassification: null,
nonOwner: null,
readonly: false
});
/** /**
* Sorts a list of change set tuples by identifierField * Sorts a list of change set tuples by identifierField
* @param {Array<IdentifierFieldWithFieldChangeSetTuple>} tuples * @param {Array<IdentifierFieldWithFieldChangeSetTuple>} tuples
@ -455,7 +459,7 @@ export {
removeReadonlyAttr, removeReadonlyAttr,
fieldChangeSetRequiresReview, fieldChangeSetRequiresReview,
isFieldIdType, isFieldIdType,
mergeMappedColumnFieldsWithSuggestions, mergeComplianceEntitiesWithSuggestions,
isRecentSuggestion, isRecentSuggestion,
getFieldsRequiringReview, getFieldsRequiringReview,
createInitialComplianceInfo, createInitialComplianceInfo,
@ -467,6 +471,6 @@ export {
changeSetReviewableAttributeTriggers, changeSetReviewableAttributeTriggers,
mapSchemaColumnPropsToCurrentPrivacyPolicy, mapSchemaColumnPropsToCurrentPrivacyPolicy,
foldComplianceChangeSets, foldComplianceChangeSets,
complianceFieldTagFactory, complianceFieldChangeSetItemFactory,
sortFoldedChangeSetTuples sortFoldedChangeSetTuples
}; };

View File

@ -27,14 +27,14 @@ interface ISchemaColumnMappingProps {
/** /**
* Describes the function interface for the mapping reducer function that takes current entities and modification time * Describes the function interface for the mapping reducer function that takes current entities and modification time
* and returns a function that accumulates and instance of ISchemaFieldsToPolicy * and returns a function that accumulates an instance of ISchemaFieldsToPolicy
* @interface ICompliancePolicyReducerFactory * @interface ISchemaWithPolicyTagsReducingFn
*/ */
interface ICompliancePolicyReducerFactory { interface ISchemaWithPolicyTagsReducingFn {
(currentEntities: IComplianceInfo['complianceEntities'], policyModificationTime: IComplianceInfo['modifiedTime']): ( (currentEntities: IComplianceInfo['complianceEntities'], policyModificationTime: IComplianceInfo['modifiedTime']): (
acc: ISchemaFieldsToPolicy, schemaFieldsToPolicy: ISchemaFieldsToPolicy,
props: IColumnFieldProps props: IColumnFieldProps
) => ISchemaFieldsToPolicy; ) => ISchemaFieldsToPolicy;
} }
export { IColumnFieldProps, ISchemaColumnMappingProps, ICompliancePolicyReducerFactory }; export { IColumnFieldProps, ISchemaColumnMappingProps, ISchemaWithPolicyTagsReducingFn };

View File

@ -22,7 +22,7 @@ interface IDatasetComplianceActions {
* Alias for the properties defined on an object indicating the values for a compliance entity object in * Alias for the properties defined on an object indicating the values for a compliance entity object in
* addition to related component metadata using in processing ui interactions / rendering for the field * addition to related component metadata using in processing ui interactions / rendering for the field
*/ */
type SchemaFieldToPolicyValue = Pick< type IComplianceEntityWithMetadata = Pick<
IComplianceEntity, IComplianceEntity,
'identifierField' | 'identifierType' | 'logicalType' | 'securityClassification' | 'nonOwner' | 'readonly' 'identifierField' | 'identifierType' | 'logicalType' | 'securityClassification' | 'nonOwner' | 'readonly'
> & { > & {
@ -35,11 +35,11 @@ type SchemaFieldToPolicyValue = Pick<
}; };
/** /**
* Describes the interface for a mapping of field names to type, SchemaFieldToPolicyValue * Describes the interface for a mapping of field names to type, IComplianceEntityWithMetadata
* @interface ISchemaFieldsToPolicy * @interface ISchemaFieldsToPolicy
*/ */
interface ISchemaFieldsToPolicy { interface ISchemaFieldsToPolicy {
[fieldName: string]: SchemaFieldToPolicyValue; [fieldName: string]: Array<IComplianceEntityWithMetadata>;
} }
/** /**
@ -68,7 +68,7 @@ interface ISchemaFieldsToSuggested {
type IComplianceChangeSet = { type IComplianceChangeSet = {
suggestion?: SchemaFieldToSuggestedValue; suggestion?: SchemaFieldToSuggestedValue;
suggestionAuthority?: SuggestionIntent; suggestionAuthority?: SuggestionIntent;
} & SchemaFieldToPolicyValue; } & IComplianceEntityWithMetadata;
/** /**
* Describes the mapping of an identifier field to it's compliance changeset list * Describes the mapping of an identifier field to it's compliance changeset list
@ -136,7 +136,7 @@ export {
IComplianceChangeSet, IComplianceChangeSet,
ShowAllShowReview, ShowAllShowReview,
IDatasetComplianceActions, IDatasetComplianceActions,
SchemaFieldToPolicyValue, IComplianceEntityWithMetadata,
ISchemaFieldsToPolicy, ISchemaFieldsToPolicy,
SchemaFieldToSuggestedValue, SchemaFieldToSuggestedValue,
ISchemaFieldsToSuggested, ISchemaFieldsToSuggested,