mirror of
https://github.com/datahub-project/datahub.git
synced 2025-11-02 11:49:23 +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,
|
||||
asyncMapSchemaColumnPropsToCurrentPrivacyPolicy,
|
||||
foldComplianceChangeSets,
|
||||
sortFoldedChangeSetTuples
|
||||
sortFoldedChangeSetTuples,
|
||||
tagsWithoutIdentifierType
|
||||
} from 'wherehows-web/constants';
|
||||
import { isPolicyExpectedShape } from 'wherehows-web/utils/datasets/compliance-policy';
|
||||
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 { isValidCustomValuePattern } from 'wherehows-web/utils/validators/urn';
|
||||
import { emptyRegexSource } from 'wherehows-web/utils/validators/regexp';
|
||||
import { NonIdLogicalType } from 'wherehows-web/constants/datasets/compliance';
|
||||
import { pick } from 'lodash';
|
||||
|
||||
const {
|
||||
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>}
|
||||
* @memberof DatasetCompliance
|
||||
*/
|
||||
@ -679,6 +682,18 @@ export default class DatasetCompliance extends Component {
|
||||
set(this, 'foldedChangeSet', sortFoldedChangeSetTuples(foldedChangeSet));
|
||||
}).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
|
||||
* @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
|
||||
* @returns {Promise<boolean>}
|
||||
* Maps attributes from the working copy to the compliance entities to be persisted remotely
|
||||
* @returns {Promise<Array<IComplianceEntity>>}
|
||||
*/
|
||||
async confirmUnformattedFields(this: DatasetCompliance): Promise<boolean> {
|
||||
type FormattedAndUnformattedEntities = {
|
||||
formatted: Array<IComplianceEntity>;
|
||||
unformatted: Array<IComplianceEntity>;
|
||||
};
|
||||
async applyWorkingCopy(this: DatasetCompliance): Promise<Array<IComplianceEntity>> {
|
||||
// Current list of compliance entities on policy
|
||||
const { complianceEntities = [] } = get(this, 'complianceInfo') || {};
|
||||
const formattedAndUnformattedEntities: FormattedAndUnformattedEntities = { formatted: [], unformatted: [] };
|
||||
// All candidate fields that can be on policy, excluding tracking type fields
|
||||
const changeSetEntities: Array<IComplianceEntity> = get(this, 'compliancePolicyChangeSet').map(
|
||||
({
|
||||
identifierField,
|
||||
identifierType = null,
|
||||
logicalType,
|
||||
nonOwner,
|
||||
securityClassification,
|
||||
readonly,
|
||||
valuePattern
|
||||
}) => ({
|
||||
identifierField,
|
||||
identifierType,
|
||||
logicalType,
|
||||
nonOwner,
|
||||
securityClassification,
|
||||
readonly,
|
||||
valuePattern
|
||||
})
|
||||
);
|
||||
const { complianceInfo, compliancePolicyChangeSet: workingCopy } = getProperties(this, [
|
||||
'complianceInfo',
|
||||
'compliancePolicyChangeSet'
|
||||
]);
|
||||
const { complianceEntities } = complianceInfo!;
|
||||
// All changeSet attrs that can be on policy, excluding changeSet metadata
|
||||
const entityAttrs = [
|
||||
'identifierField',
|
||||
'identifierType',
|
||||
'logicalType',
|
||||
'nonOwner',
|
||||
'securityClassification',
|
||||
'readonly',
|
||||
'valuePattern'
|
||||
];
|
||||
const updatingComplianceEntities = arrayMap(
|
||||
(tag: IComplianceChangeSet) => <IComplianceEntity>pick(tag, entityAttrs)
|
||||
)(workingCopy);
|
||||
|
||||
// Fields that do not have a logicalType, and no identifierType or identifierType is ComplianceFieldIdValue.None
|
||||
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;
|
||||
return complianceEntities.setObjects(updatingComplianceEntities);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -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
|
||||
* @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
|
||||
* @returns {Promise<boolean>}
|
||||
* Handler applies fields changeSet working copy to compliance policy to be persisted amd validates fields
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async didEditCompliancePolicy(this: DatasetCompliance): Promise<boolean> {
|
||||
const isConfirmed = await this.confirmUnformattedFields();
|
||||
|
||||
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 && [complianceDataException, complianceFieldNotUnique].includes(e.message)) {
|
||||
set(this, '_hasBadData', true);
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
|
||||
// return;
|
||||
throw e;
|
||||
async didEditCompliancePolicy(this: DatasetCompliance): Promise<void> {
|
||||
// Ensure that the fields on the policy meet the validation criteria before proceeding
|
||||
// Otherwise exit early
|
||||
try {
|
||||
await this.applyWorkingCopy();
|
||||
await this.validateFields();
|
||||
} catch (e) {
|
||||
// Flag this dataset's data as problematic
|
||||
if (e instanceof Error && [complianceDataException, complianceFieldNotUnique].includes(e.message)) {
|
||||
set(this, '_hasBadData', true);
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
|
||||
// If user provides confirmation for unformatted fields or there are none,
|
||||
// then validate fields against expectations
|
||||
// otherwise inform user of validation exception
|
||||
// setProperties(this, { isEditingCompliancePolicy: false, isEditingDatasetClassification: true });
|
||||
} else {
|
||||
throw new Error('unConfirmedUnformattedFields');
|
||||
throw e;
|
||||
}
|
||||
|
||||
return isConfirmed;
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@ -219,6 +219,21 @@ const tagNeedsReview = (complianceDataTypes: Array<IComplianceDataType>) =>
|
||||
const isTagNoneType = ({ identifierType }: IComplianceChangeSet): boolean =>
|
||||
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
|
||||
* @param {IComplianceChangeSet} tag
|
||||
@ -542,5 +557,6 @@ export {
|
||||
asyncMapSchemaColumnPropsToCurrentPrivacyPolicy,
|
||||
foldComplianceChangeSets,
|
||||
complianceFieldChangeSetItemFactory,
|
||||
sortFoldedChangeSetTuples
|
||||
sortFoldedChangeSetTuples,
|
||||
tagsWithoutIdentifierType
|
||||
};
|
||||
|
||||
@ -60,7 +60,18 @@
|
||||
}
|
||||
|
||||
.compliance-entities-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: item-spacing(2 0 2);
|
||||
color: #777777;
|
||||
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
|
||||
</span>
|
||||
{{/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>
|
||||
|
||||
{{#if foldedChangeSet.length}}
|
||||
|
||||
@ -12,7 +12,7 @@ import { Classification, ComplianceFieldIdValue, IdLogicalType } from 'wherehows
|
||||
* access to actions using `did${editStepName}` accessors
|
||||
*/
|
||||
interface IDatasetComplianceActions {
|
||||
didEditCompliancePolicy: () => Promise<boolean>;
|
||||
didEditCompliancePolicy: () => Promise<void>;
|
||||
didEditPurgePolicy: () => Promise<{} | void>;
|
||||
didEditDatasetLevelCompliancePolicy: () => Promise<void>;
|
||||
[K: string]: (...args: Array<any>) => any;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user