diff --git a/wherehows-web/app/components/dataset-compliance-rollup-row.ts b/wherehows-web/app/components/dataset-compliance-rollup-row.ts index 7879c5e8e7..52f29ae6d1 100644 --- a/wherehows-web/app/components/dataset-compliance-rollup-row.ts +++ b/wherehows-web/app/components/dataset-compliance-rollup-row.ts @@ -11,7 +11,7 @@ import { changeSetFieldsRequiringReview, changeSetReviewableAttributeTriggers, ComplianceFieldIdValue, - complianceFieldTagFactory, + complianceFieldChangeSetItemFactory, SuggestionIntent } from 'wherehows-web/constants'; import { getTagSuggestions } from 'wherehows-web/utils/datasets/compliance-suggestions'; @@ -245,7 +245,7 @@ export default class DatasetComplianceRollupRow extends Component.extend({ if (isFieldTagged(fieldChangeSet)) { onFieldTagAdded( - complianceFieldTagFactory({ + complianceFieldChangeSetItemFactory({ identifierField, dataType, identifierType, diff --git a/wherehows-web/app/components/dataset-compliance.ts b/wherehows-web/app/components/dataset-compliance.ts index febad8e8d6..e6f2f64fec 100644 --- a/wherehows-web/app/components/dataset-compliance.ts +++ b/wherehows-web/app/components/dataset-compliance.ts @@ -23,7 +23,7 @@ import { SuggestionIntent, PurgePolicy, getSupportedPurgePolicies, - mergeMappedColumnFieldsWithSuggestions, + mergeComplianceEntitiesWithSuggestions, getFieldsRequiringReview, isFieldIdType, idTypeFieldsHaveLogicalType, @@ -661,7 +661,7 @@ export default class DatasetCompliance extends Component { 'identifierFieldToSuggestion', function(this: DatasetCompliance): Array { // 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, 'identifierFieldToSuggestion') ); diff --git a/wherehows-web/app/constants/dataset-compliance.ts b/wherehows-web/app/constants/dataset-compliance.ts index 54d7197e3c..227ac11e4c 100644 --- a/wherehows-web/app/constants/dataset-compliance.ts +++ b/wherehows-web/app/constants/dataset-compliance.ts @@ -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 { fleece, hasEnumerableKeys } from 'wherehows-web/utils/object'; import { lastSeenSuggestionInterval } from 'wherehows-web/constants/metadata-acquisition'; -import { pick } from 'lodash'; import { decodeUrn } from 'wherehows-web/utils/validators/urn'; import { IComplianceChangeSet, @@ -12,13 +11,15 @@ import { IdentifierFieldWithFieldChangeSetTuple, IIdentifierFieldWithFieldChangeSetObject, ISchemaFieldsToPolicy, - ISchemaFieldsToSuggested + ISchemaFieldsToSuggested, + IComplianceEntityWithMetadata } from 'wherehows-web/typings/app/dataset-compliance'; import { IColumnFieldProps, - ICompliancePolicyReducerFactory, - ISchemaColumnMappingProps + ISchemaColumnMappingProps, + ISchemaWithPolicyTagsReducingFn } 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 @@ -246,39 +247,35 @@ const changeSetFieldsRequiringReview = (complianceDataTypes: Array(fieldChangeSetRequiresReview(complianceDataTypes)); /** - * Merges the column fields with the suggestion for the field if available - * @param {object} mappedColumnFields a map of column fields to compliance entity properties - * @param {object} fieldSuggestionMap a map of field suggestion properties keyed by field name - * @return {Array} mapped column field augmented with suggestion if available + * Extracts a suggestion for a field from a suggestion map and merges a compliance entity with the suggestion + * @param {ISchemaFieldsToSuggested} suggestionMap a hash of compliance fields to suggested values + * @return {(entity: IComplianceEntityWithMetadata) => IComplianceChangeSet} */ -const mergeMappedColumnFieldsWithSuggestions = ( - mappedColumnFields: ISchemaFieldsToPolicy = {}, - fieldSuggestionMap: ISchemaFieldsToSuggested = {} +const complianceEntityWithSuggestions = (suggestionMap: ISchemaFieldsToSuggested) => ( + entity: IComplianceEntityWithMetadata +): 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} + */ +const mergeComplianceEntitiesWithSuggestions = ( + schemaEntityMap: ISchemaFieldsToPolicy = {}, + suggestionMap: ISchemaFieldsToSuggested = {} ): Array => - Object.keys(mappedColumnFields).map(fieldName => { - 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; - }); + arrayMap(complianceEntityWithSuggestions(suggestionMap))([].concat.apply([], Object.values(schemaEntityMap))); /** * 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} [keys=[]] - * @param {string} fieldName - * @param {IComplianceInfo.complianceEntities} [source=[]] - * @returns {({ [V in K]: IComplianceEntity[V] } | {})} - */ -const getKeysOnComplianceEntity = ( - keys: Array = [], - 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 >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 * @param {ISchemaColumnMappingProps} { @@ -365,14 +335,14 @@ const mapSchemaColumnPropsToCurrentPrivacyPolicy = ({ complianceEntities, policyModificationTime }: 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 * @param {IColumnFieldProps} { identifierField, dataType } the runtime properties to apply to the created instance - * @returns {SchemaFieldToPolicyValue} + * @returns {IComplianceEntityWithMetadata} */ -const complianceFieldTagFactory = ({ +const complianceFieldChangeSetItemFactory = ({ identifierField, dataType, identifierType, @@ -396,38 +366,72 @@ const complianceFieldTagFactory = ({ 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 * instances and maps each entry to a compliance entity on the current compliance policy * @param {IComplianceInfo.complianceEntities} currentEntities * @param {IComplianceInfo.modifiedTime} policyModificationTime - * @type ICompliancePolicyReducerFactory + * @return {(schemaFieldsToPolicy: ISchemaFieldsToPolicy, { identifierField, dataType}: IColumnFieldProps) => ISchemaFieldsToPolicy} */ -const columnToPolicyReducingFn: ICompliancePolicyReducerFactory = ( +const schemaFieldsWithPolicyTagsReducingFn: ISchemaWithPolicyTagsReducingFn = ( currentEntities: IComplianceInfo['complianceEntities'], policyModificationTime: IComplianceInfo['modifiedTime'] -) => (acc: ISchemaFieldsToPolicy, { identifierField, dataType }: IColumnFieldProps) => { - const currentPrivacyAttrs = getKeysOnComplianceEntity( - ['identifierType', 'logicalType', 'securityClassification', 'nonOwner', 'readonly'], - identifierField, - currentEntities +) => ( + schemaFieldsToPolicy: ISchemaFieldsToPolicy, + { identifierField, dataType }: IColumnFieldProps +): ISchemaFieldsToPolicy => { + let complianceEntitiesWithMetadata: Array; + 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 { - ...acc, - [identifierField]: { - identifierField, - dataType, - readonly: false, // default value overridden by value in currentPrivacyAttrs below - ...currentPrivacyAttrs, - policyModificationTime, - privacyPolicyExists: hasEnumerableKeys(currentPrivacyAttrs), - isDirty: false - } - }; + return { ...schemaFieldsToPolicy, [identifierField]: complianceEntitiesWithMetadata }; }; +/** + * 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 * @param {Array} tuples @@ -455,7 +459,7 @@ export { removeReadonlyAttr, fieldChangeSetRequiresReview, isFieldIdType, - mergeMappedColumnFieldsWithSuggestions, + mergeComplianceEntitiesWithSuggestions, isRecentSuggestion, getFieldsRequiringReview, createInitialComplianceInfo, @@ -467,6 +471,6 @@ export { changeSetReviewableAttributeTriggers, mapSchemaColumnPropsToCurrentPrivacyPolicy, foldComplianceChangeSets, - complianceFieldTagFactory, + complianceFieldChangeSetItemFactory, sortFoldedChangeSetTuples }; diff --git a/wherehows-web/app/typings/app/dataset-columns.d.ts b/wherehows-web/app/typings/app/dataset-columns.d.ts index 352aa883f6..e1f95f6584 100644 --- a/wherehows-web/app/typings/app/dataset-columns.d.ts +++ b/wherehows-web/app/typings/app/dataset-columns.d.ts @@ -27,14 +27,14 @@ interface ISchemaColumnMappingProps { /** * 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 - * @interface ICompliancePolicyReducerFactory + * and returns a function that accumulates an instance of ISchemaFieldsToPolicy + * @interface ISchemaWithPolicyTagsReducingFn */ -interface ICompliancePolicyReducerFactory { +interface ISchemaWithPolicyTagsReducingFn { (currentEntities: IComplianceInfo['complianceEntities'], policyModificationTime: IComplianceInfo['modifiedTime']): ( - acc: ISchemaFieldsToPolicy, + schemaFieldsToPolicy: ISchemaFieldsToPolicy, props: IColumnFieldProps ) => ISchemaFieldsToPolicy; } -export { IColumnFieldProps, ISchemaColumnMappingProps, ICompliancePolicyReducerFactory }; +export { IColumnFieldProps, ISchemaColumnMappingProps, ISchemaWithPolicyTagsReducingFn }; diff --git a/wherehows-web/app/typings/app/dataset-compliance.d.ts b/wherehows-web/app/typings/app/dataset-compliance.d.ts index 7c350e1f38..8f14a58c3e 100644 --- a/wherehows-web/app/typings/app/dataset-compliance.d.ts +++ b/wherehows-web/app/typings/app/dataset-compliance.d.ts @@ -22,7 +22,7 @@ interface IDatasetComplianceActions { * 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 */ -type SchemaFieldToPolicyValue = Pick< +type IComplianceEntityWithMetadata = Pick< IComplianceEntity, '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 { - [fieldName: string]: SchemaFieldToPolicyValue; + [fieldName: string]: Array; } /** @@ -68,7 +68,7 @@ interface ISchemaFieldsToSuggested { type IComplianceChangeSet = { suggestion?: SchemaFieldToSuggestedValue; suggestionAuthority?: SuggestionIntent; -} & SchemaFieldToPolicyValue; +} & IComplianceEntityWithMetadata; /** * Describes the mapping of an identifier field to it's compliance changeset list @@ -136,7 +136,7 @@ export { IComplianceChangeSet, ShowAllShowReview, IDatasetComplianceActions, - SchemaFieldToPolicyValue, + IComplianceEntityWithMetadata, ISchemaFieldsToPolicy, SchemaFieldToSuggestedValue, ISchemaFieldsToSuggested,