mirror of
https://github.com/datahub-project/datahub.git
synced 2025-11-07 06:13:40 +00:00
implements feature to mark unspecified tags as ComplianceFieldIdValue.None
This commit is contained in:
parent
2e20784879
commit
5e0492eb99
@ -30,7 +30,8 @@ import {
|
|||||||
changeSetReviewableAttributeTriggers,
|
changeSetReviewableAttributeTriggers,
|
||||||
asyncMapSchemaColumnPropsToCurrentPrivacyPolicy,
|
asyncMapSchemaColumnPropsToCurrentPrivacyPolicy,
|
||||||
foldComplianceChangeSets,
|
foldComplianceChangeSets,
|
||||||
sortFoldedChangeSetTuples
|
sortFoldedChangeSetTuples,
|
||||||
|
tagsWithoutIdentifierType
|
||||||
} from 'wherehows-web/constants';
|
} from 'wherehows-web/constants';
|
||||||
import { isPolicyExpectedShape } from 'wherehows-web/utils/datasets/compliance-policy';
|
import { isPolicyExpectedShape } from 'wherehows-web/utils/datasets/compliance-policy';
|
||||||
import { getTagsSuggestions } from 'wherehows-web/utils/datasets/compliance-suggestions';
|
import { getTagsSuggestions } from 'wherehows-web/utils/datasets/compliance-suggestions';
|
||||||
@ -60,6 +61,8 @@ import { uniqBy } from 'lodash';
|
|||||||
import { IColumnFieldProps } from 'wherehows-web/typings/app/dataset-columns';
|
import { IColumnFieldProps } from 'wherehows-web/typings/app/dataset-columns';
|
||||||
import { isValidCustomValuePattern } from 'wherehows-web/utils/validators/urn';
|
import { isValidCustomValuePattern } from 'wherehows-web/utils/validators/urn';
|
||||||
import { emptyRegexSource } from 'wherehows-web/utils/validators/regexp';
|
import { emptyRegexSource } from 'wherehows-web/utils/validators/regexp';
|
||||||
|
import { NonIdLogicalType } from 'wherehows-web/constants/datasets/compliance';
|
||||||
|
import { pick } from 'lodash';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
complianceDataException,
|
complianceDataException,
|
||||||
@ -436,7 +439,7 @@ export default class DatasetCompliance extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks that all tags/ dataset content types have a boolean value
|
* Checks that dataset content types have a boolean value
|
||||||
* @type {ComputedProperty<boolean>}
|
* @type {ComputedProperty<boolean>}
|
||||||
* @memberof DatasetCompliance
|
* @memberof DatasetCompliance
|
||||||
*/
|
*/
|
||||||
@ -679,6 +682,18 @@ export default class DatasetCompliance extends Component {
|
|||||||
set(this, 'foldedChangeSet', sortFoldedChangeSetTuples(foldedChangeSet));
|
set(this, 'foldedChangeSet', sortFoldedChangeSetTuples(foldedChangeSet));
|
||||||
}).enqueue();
|
}).enqueue();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists the IComplianceChangeSet / tags without an identifierType value
|
||||||
|
* @type {ComputedProperty<Array<IComplianceChangeSet>>}
|
||||||
|
* @memberof DatasetCompliance
|
||||||
|
*/
|
||||||
|
unspecifiedTags = computed(`compliancePolicyChangeSet.@each.{${changeSetReviewableAttributeTriggers}}`, function(
|
||||||
|
this: DatasetCompliance
|
||||||
|
): Array<IComplianceChangeSet> {
|
||||||
|
const tags = get(this, 'compliancePolicyChangeSet');
|
||||||
|
return tagsWithoutIdentifierType(tags);
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invokes external action with flag indicating that at least 1 suggestion exists for a field in the changeSet
|
* Invokes external action with flag indicating that at least 1 suggestion exists for a field in the changeSet
|
||||||
* @param {Array<IComplianceChangeSet>} changeSet
|
* @param {Array<IComplianceChangeSet>} changeSet
|
||||||
@ -737,92 +752,31 @@ export default class DatasetCompliance extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requires that the user confirm that any non-id fields are ok to be saved without a field format specified
|
* Maps attributes from the working copy to the compliance entities to be persisted remotely
|
||||||
* @returns {Promise<boolean>}
|
* @returns {Promise<Array<IComplianceEntity>>}
|
||||||
*/
|
*/
|
||||||
async confirmUnformattedFields(this: DatasetCompliance): Promise<boolean> {
|
async applyWorkingCopy(this: DatasetCompliance): Promise<Array<IComplianceEntity>> {
|
||||||
type FormattedAndUnformattedEntities = {
|
|
||||||
formatted: Array<IComplianceEntity>;
|
|
||||||
unformatted: Array<IComplianceEntity>;
|
|
||||||
};
|
|
||||||
// Current list of compliance entities on policy
|
// Current list of compliance entities on policy
|
||||||
const { complianceEntities = [] } = get(this, 'complianceInfo') || {};
|
const { complianceInfo, compliancePolicyChangeSet: workingCopy } = getProperties(this, [
|
||||||
const formattedAndUnformattedEntities: FormattedAndUnformattedEntities = { formatted: [], unformatted: [] };
|
'complianceInfo',
|
||||||
// All candidate fields that can be on policy, excluding tracking type fields
|
'compliancePolicyChangeSet'
|
||||||
const changeSetEntities: Array<IComplianceEntity> = get(this, 'compliancePolicyChangeSet').map(
|
]);
|
||||||
({
|
const { complianceEntities } = complianceInfo!;
|
||||||
identifierField,
|
// All changeSet attrs that can be on policy, excluding changeSet metadata
|
||||||
identifierType = null,
|
const entityAttrs = [
|
||||||
logicalType,
|
'identifierField',
|
||||||
nonOwner,
|
'identifierType',
|
||||||
securityClassification,
|
'logicalType',
|
||||||
readonly,
|
'nonOwner',
|
||||||
valuePattern
|
'securityClassification',
|
||||||
}) => ({
|
'readonly',
|
||||||
identifierField,
|
'valuePattern'
|
||||||
identifierType,
|
];
|
||||||
logicalType,
|
const updatingComplianceEntities = arrayMap(
|
||||||
nonOwner,
|
(tag: IComplianceChangeSet) => <IComplianceEntity>pick(tag, entityAttrs)
|
||||||
securityClassification,
|
)(workingCopy);
|
||||||
readonly,
|
|
||||||
valuePattern
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// Fields that do not have a logicalType, and no identifierType or identifierType is ComplianceFieldIdValue.None
|
return complianceEntities.setObjects(updatingComplianceEntities);
|
||||||
const { formatted, unformatted }: FormattedAndUnformattedEntities = changeSetEntities.reduce(
|
|
||||||
({ formatted, unformatted }, field) => {
|
|
||||||
const { identifierType, logicalType } = getProperties(field, ['identifierType', 'logicalType']);
|
|
||||||
|
|
||||||
if (!logicalType && (ComplianceFieldIdValue.None === identifierType || !identifierType)) {
|
|
||||||
unformatted = [...unformatted, field];
|
|
||||||
} else {
|
|
||||||
formatted = [...formatted, field];
|
|
||||||
}
|
|
||||||
|
|
||||||
return { formatted, unformatted };
|
|
||||||
},
|
|
||||||
formattedAndUnformattedEntities
|
|
||||||
);
|
|
||||||
|
|
||||||
const dialogActions = <IConfirmOptions['dialogActions']>{};
|
|
||||||
let isConfirmed = true;
|
|
||||||
let unformattedChangeSetEntities: Array<IComplianceEntity> = [];
|
|
||||||
|
|
||||||
// If there are unformatted fields, require confirmation from user
|
|
||||||
if (unformatted.length) {
|
|
||||||
unformattedChangeSetEntities = unformatted.map(({ identifierField }) => ({
|
|
||||||
identifierField,
|
|
||||||
identifierType: ComplianceFieldIdValue.None,
|
|
||||||
logicalType: null,
|
|
||||||
securityClassification: null,
|
|
||||||
nonOwner: null
|
|
||||||
}));
|
|
||||||
|
|
||||||
const confirmHandler = (function() {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
dialogActions['didConfirm'] = () => resolve();
|
|
||||||
dialogActions['didDismiss'] = () => reject();
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Create confirmation dialog
|
|
||||||
get(this, 'notifications').notify(NotificationEvent.confirm, {
|
|
||||||
header: `Fields will be tagged with \`${ComplianceFieldIdValue.None}\` field type`,
|
|
||||||
content: `There are ${unformatted.length} non-ID fields`,
|
|
||||||
dialogActions: dialogActions
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
await confirmHandler;
|
|
||||||
} catch (e) {
|
|
||||||
isConfirmed = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isConfirmed && complianceEntities.setObjects([...formatted, ...unformattedChangeSetEntities]);
|
|
||||||
|
|
||||||
return isConfirmed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1056,6 +1010,20 @@ export default class DatasetCompliance extends Component {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the identifierType attribute on IComplianceChangeSetFields without an identifierType to ComplianceFieldIdValue.None
|
||||||
|
* @returns {Promise<Array<IComplianceChangeSet>>}
|
||||||
|
*/
|
||||||
|
async onSetUnspecifiedTagsAsNone(this: DatasetCompliance): Promise<Array<IComplianceChangeSet>> {
|
||||||
|
const unspecifiedTags = get(this, 'unspecifiedTags');
|
||||||
|
const setTagIdentifier = (value: ComplianceFieldIdValue | NonIdLogicalType) => (tag: IComplianceChangeSet) =>
|
||||||
|
set(tag, 'identifierType', value);
|
||||||
|
|
||||||
|
await iterateArrayAsync(arrayMap(setTagIdentifier(ComplianceFieldIdValue.None)))(unspecifiedTags);
|
||||||
|
|
||||||
|
return unspecifiedTags; // Now specified as ComplianceFieldIdValue.None
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggles the flag to show all member potential member data fields that may be contained in this dataset
|
* Toggles the flag to show all member potential member data fields that may be contained in this dataset
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
@ -1095,37 +1063,24 @@ export default class DatasetCompliance extends Component {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler for setting the dataset classification into edit mode and rendering into DOM
|
* Handler applies fields changeSet working copy to compliance policy to be persisted amd validates fields
|
||||||
* @returns {Promise<boolean>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async didEditCompliancePolicy(this: DatasetCompliance): Promise<boolean> {
|
async didEditCompliancePolicy(this: DatasetCompliance): Promise<void> {
|
||||||
const isConfirmed = await this.confirmUnformattedFields();
|
// Ensure that the fields on the policy meet the validation criteria before proceeding
|
||||||
|
// Otherwise exit early
|
||||||
if (isConfirmed) {
|
try {
|
||||||
// Ensure that the fields on the policy meet the validation criteria before proceeding
|
await this.applyWorkingCopy();
|
||||||
// Otherwise exit early
|
await this.validateFields();
|
||||||
try {
|
} catch (e) {
|
||||||
await this.validateFields();
|
// Flag this dataset's data as problematic
|
||||||
} catch (e) {
|
if (e instanceof Error && [complianceDataException, complianceFieldNotUnique].includes(e.message)) {
|
||||||
// Flag this dataset's data as problematic
|
set(this, '_hasBadData', true);
|
||||||
if (e instanceof Error && [complianceDataException, complianceFieldNotUnique].includes(e.message)) {
|
window.scrollTo(0, 0);
|
||||||
set(this, '_hasBadData', true);
|
|
||||||
window.scrollTo(0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// return;
|
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If user provides confirmation for unformatted fields or there are none,
|
throw e;
|
||||||
// then validate fields against expectations
|
|
||||||
// otherwise inform user of validation exception
|
|
||||||
// setProperties(this, { isEditingCompliancePolicy: false, isEditingDatasetClassification: true });
|
|
||||||
} else {
|
|
||||||
throw new Error('unConfirmedUnformattedFields');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return isConfirmed;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -219,6 +219,21 @@ const tagNeedsReview = (complianceDataTypes: Array<IComplianceDataType>) =>
|
|||||||
const isTagNoneType = ({ identifierType }: IComplianceChangeSet): boolean =>
|
const isTagNoneType = ({ identifierType }: IComplianceChangeSet): boolean =>
|
||||||
identifierType === ComplianceFieldIdValue.None;
|
identifierType === ComplianceFieldIdValue.None;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a tag has an identifier type
|
||||||
|
* @param {ComplianceFieldIdValue | NonIdLogicalType | null} identifierType
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
const isTagWithoutIdentifierType = ({ identifierType }: IComplianceChangeSet): boolean => !identifierType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters a list of tags without an identifier type value
|
||||||
|
* @param {Array<IComplianceChangeSet>} tags
|
||||||
|
* @return {Array<IComplianceChangeSet>}
|
||||||
|
*/
|
||||||
|
const tagsWithoutIdentifierType = (tags: Array<IComplianceChangeSet>): Array<IComplianceChangeSet> =>
|
||||||
|
arrayFilter(isTagWithoutIdentifierType)(tags);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asserts the inverse of isTagNoneType
|
* Asserts the inverse of isTagNoneType
|
||||||
* @param {IComplianceChangeSet} tag
|
* @param {IComplianceChangeSet} tag
|
||||||
@ -542,5 +557,6 @@ export {
|
|||||||
asyncMapSchemaColumnPropsToCurrentPrivacyPolicy,
|
asyncMapSchemaColumnPropsToCurrentPrivacyPolicy,
|
||||||
foldComplianceChangeSets,
|
foldComplianceChangeSets,
|
||||||
complianceFieldChangeSetItemFactory,
|
complianceFieldChangeSetItemFactory,
|
||||||
sortFoldedChangeSetTuples
|
sortFoldedChangeSetTuples,
|
||||||
|
tagsWithoutIdentifierType
|
||||||
};
|
};
|
||||||
|
|||||||
@ -60,7 +60,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.compliance-entities-meta {
|
.compliance-entities-meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
padding: item-spacing(2 0 2);
|
padding: item-spacing(2 0 2);
|
||||||
color: #777777;
|
color: #777777;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
|
||||||
|
&__secondary {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.set-fields-to-none-text {
|
||||||
|
margin-right: item-spacing(2);
|
||||||
|
display: inline;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,6 +26,18 @@
|
|||||||
{{changeSetReviewCount}} fields to be reviewed
|
{{changeSetReviewCount}} fields to be reviewed
|
||||||
</span>
|
</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if (and isEditing unspecifiedTags)}}
|
||||||
|
<div class="compliance-entities-meta__secondary">
|
||||||
|
<p class="set-fields-to-none-text">Set all unspecified field types to {{ComplianceFieldIdValue.None}}</p>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="nacho-button nacho-button--large nacho-button--secondary action-bar__item"
|
||||||
|
onclick={{action "onSetUnspecifiedTagsAsNone"}}>
|
||||||
|
Set {{pluralize unspecifiedTags.length "tag"}} to {{ComplianceFieldIdValue.None}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{{#if foldedChangeSet.length}}
|
{{#if foldedChangeSet.length}}
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import { Classification, ComplianceFieldIdValue, IdLogicalType } from 'wherehows
|
|||||||
* access to actions using `did${editStepName}` accessors
|
* access to actions using `did${editStepName}` accessors
|
||||||
*/
|
*/
|
||||||
interface IDatasetComplianceActions {
|
interface IDatasetComplianceActions {
|
||||||
didEditCompliancePolicy: () => Promise<boolean>;
|
didEditCompliancePolicy: () => Promise<void>;
|
||||||
didEditPurgePolicy: () => Promise<{} | void>;
|
didEditPurgePolicy: () => Promise<{} | void>;
|
||||||
didEditDatasetLevelCompliancePolicy: () => Promise<void>;
|
didEditDatasetLevelCompliancePolicy: () => Promise<void>;
|
||||||
[K: string]: (...args: Array<any>) => any;
|
[K: string]: (...args: Array<any>) => any;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user