mirror of
https://github.com/datahub-project/datahub.git
synced 2025-09-01 21:33:11 +00:00
Merge pull request #1181 from theseyi/c-json-upload
updates schema for json metadata. initial refactor of function to valdate metadata schema properties. refactors action handler for upload: moves to container component
This commit is contained in:
commit
f7f28b6c05
@ -35,7 +35,6 @@ import {
|
|||||||
singleTagsInChangeSet,
|
singleTagsInChangeSet,
|
||||||
tagsForIdentifierField
|
tagsForIdentifierField
|
||||||
} from 'wherehows-web/constants';
|
} from 'wherehows-web/constants';
|
||||||
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';
|
||||||
import { arrayMap, compact, isListUnique, iterateArrayAsync } from 'wherehows-web/utils/array';
|
import { arrayMap, compact, isListUnique, iterateArrayAsync } from 'wherehows-web/utils/array';
|
||||||
import noop from 'wherehows-web/utils/noop';
|
import noop from 'wherehows-web/utils/noop';
|
||||||
@ -73,8 +72,6 @@ const {
|
|||||||
complianceFieldNotUnique,
|
complianceFieldNotUnique,
|
||||||
missingTypes,
|
missingTypes,
|
||||||
helpText,
|
helpText,
|
||||||
successUploading,
|
|
||||||
invalidPolicyData,
|
|
||||||
missingPurgePolicy,
|
missingPurgePolicy,
|
||||||
missingDatasetSecurityClassification
|
missingDatasetSecurityClassification
|
||||||
} = compliancePolicyStrings;
|
} = compliancePolicyStrings;
|
||||||
@ -127,6 +124,9 @@ export default class DatasetCompliance extends Component {
|
|||||||
schemaFieldNamesMappedToDataTypes: Array<Pick<IDatasetColumn, 'dataType' | 'fieldName'>>;
|
schemaFieldNamesMappedToDataTypes: Array<Pick<IDatasetColumn, 'dataType' | 'fieldName'>>;
|
||||||
onReset: <T>() => Promise<T>;
|
onReset: <T>() => Promise<T>;
|
||||||
onSave: <T>() => Promise<T>;
|
onSave: <T>() => Promise<T>;
|
||||||
|
|
||||||
|
onComplianceUpload: (jsonString: string) => void;
|
||||||
|
|
||||||
notifyOnChangeSetSuggestions: (hasSuggestions: boolean) => void;
|
notifyOnChangeSetSuggestions: (hasSuggestions: boolean) => void;
|
||||||
notifyOnChangeSetRequiresReview: (hasChangeSetDrift: boolean) => void;
|
notifyOnChangeSetRequiresReview: (hasChangeSetDrift: boolean) => void;
|
||||||
|
|
||||||
@ -815,9 +815,9 @@ export default class DatasetCompliance extends Component {
|
|||||||
* checked in the function. If criteria is not met, an the returned promise is settled
|
* checked in the function. If criteria is not met, an the returned promise is settled
|
||||||
* in a rejected state, otherwise fulfilled
|
* in a rejected state, otherwise fulfilled
|
||||||
* @method
|
* @method
|
||||||
* @return {Promise<never> | void}
|
* @return {Promise<never | void>}
|
||||||
*/
|
*/
|
||||||
validateFields(this: DatasetCompliance): Promise<never> | void {
|
async validateFields(this: DatasetCompliance): Promise<never | void> {
|
||||||
const { notify } = get(this, 'notifications');
|
const { notify } = get(this, 'notifications');
|
||||||
const { complianceEntities = [] } = get(this, 'complianceInfo') || {};
|
const { complianceEntities = [] } = get(this, 'complianceInfo') || {};
|
||||||
const idTypeComplianceEntities = complianceEntities.filter(isTagIdType(get(this, 'complianceDataTypes')));
|
const idTypeComplianceEntities = complianceEntities.filter(isTagIdType(get(this, 'complianceDataTypes')));
|
||||||
@ -1161,47 +1161,6 @@ export default class DatasetCompliance extends Component {
|
|||||||
set(tag, 'suggestionAuthority', intent);
|
set(tag, 'suggestionAuthority', intent);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Receives the json representation for compliance and applies each key to the policy
|
|
||||||
* @param {string} textString string representation for the JSON file
|
|
||||||
*/
|
|
||||||
onComplianceJsonUpload(this: DatasetCompliance, textString: string): void {
|
|
||||||
const complianceInfo = get(this, 'complianceInfo');
|
|
||||||
const { notify } = get(this, 'notifications');
|
|
||||||
let policy;
|
|
||||||
|
|
||||||
if (!complianceInfo) {
|
|
||||||
notify(NotificationEvent.error, {
|
|
||||||
content: 'Could not find compliance current compliance policy for this dataset'
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
policy = JSON.parse(textString);
|
|
||||||
} catch (e) {
|
|
||||||
notify(NotificationEvent.error, {
|
|
||||||
content: invalidPolicyData
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isPolicyExpectedShape(policy)) {
|
|
||||||
setProperties(complianceInfo, {
|
|
||||||
complianceEntities: policy.complianceEntities,
|
|
||||||
datasetClassification: policy.datasetClassification
|
|
||||||
});
|
|
||||||
|
|
||||||
notify(NotificationEvent.info, {
|
|
||||||
content: successUploading
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
notify(NotificationEvent.error, {
|
|
||||||
content: invalidPolicyData
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the compliance policy download action
|
* Handles the compliance policy download action
|
||||||
*/
|
*/
|
||||||
@ -1237,6 +1196,14 @@ export default class DatasetCompliance extends Component {
|
|||||||
anchor.click();
|
anchor.click();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receives the json representation for compliance and applies each key to the policy
|
||||||
|
* @param {string} jsonString string representation for the JSON file
|
||||||
|
*/
|
||||||
|
onComplianceJsonUpload(this: DatasetCompliance, jsonString: string): void {
|
||||||
|
get(this, 'onComplianceUpload')(jsonString);
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the source object representing the current datasetClassification map
|
* Updates the source object representing the current datasetClassification map
|
||||||
* @param {keyof typeof DatasetClassifiers} classifier the property on the datasetClassification to update
|
* @param {keyof typeof DatasetClassifiers} classifier the property on the datasetClassification to update
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import Component from '@ember/component';
|
import Component from '@ember/component';
|
||||||
import { get, set, setProperties } from '@ember/object';
|
import { get, set, setProperties, getProperties } from '@ember/object';
|
||||||
import ComputedProperty from '@ember/object/computed';
|
import ComputedProperty from '@ember/object/computed';
|
||||||
import { inject } from '@ember/service';
|
import { inject } from '@ember/service';
|
||||||
import { task } from 'ember-concurrency';
|
import { task } from 'ember-concurrency';
|
||||||
@ -28,6 +28,10 @@ import {
|
|||||||
SuggestionIntent
|
SuggestionIntent
|
||||||
} from 'wherehows-web/constants';
|
} from 'wherehows-web/constants';
|
||||||
import { iterateArrayAsync } from 'wherehows-web/utils/array';
|
import { iterateArrayAsync } from 'wherehows-web/utils/array';
|
||||||
|
import validateMetadataObject, {
|
||||||
|
datasetComplianceMetadataTaxonomy
|
||||||
|
} from 'wherehows-web/utils/datasets/compliance/metadata-schema';
|
||||||
|
import { notificationDialogActionFactory } from 'wherehows-web/utils/notifications/notifications';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type alias for the response when container data items are batched
|
* Type alias for the response when container data items are batched
|
||||||
@ -52,7 +56,7 @@ type BatchContainerDataResult = Pick<
|
|||||||
| 'schemaless'
|
| 'schemaless'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
const { successUpdating, failedUpdating } = compliancePolicyStrings;
|
const { successUpdating, failedUpdating, successUploading, invalidPolicyData } = compliancePolicyStrings;
|
||||||
|
|
||||||
export default class DatasetComplianceContainer extends Component {
|
export default class DatasetComplianceContainer extends Component {
|
||||||
/**
|
/**
|
||||||
@ -336,4 +340,43 @@ export default class DatasetComplianceContainer extends Component {
|
|||||||
onSuggestionsComplianceFeedback(uid: string | null = null, feedback: SuggestionIntent) {
|
onSuggestionsComplianceFeedback(uid: string | null = null, feedback: SuggestionIntent) {
|
||||||
saveDatasetComplianceSuggestionFeedbackByUrn(get(this, 'urn'), uid, feedback);
|
saveDatasetComplianceSuggestionFeedbackByUrn(get(this, 'urn'), uid, feedback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reapplies the uploaded compliance policy to the container property
|
||||||
|
* @param {string} jsonString string representation for the JSON file
|
||||||
|
* @memberof DatasetComplianceContainer
|
||||||
|
*/
|
||||||
|
@action
|
||||||
|
onComplianceUpload(this: DatasetComplianceContainer, jsonString: string): void {
|
||||||
|
const {
|
||||||
|
complianceInfo,
|
||||||
|
notifications: { notify }
|
||||||
|
} = getProperties(this, ['complianceInfo', 'notifications']);
|
||||||
|
|
||||||
|
if (complianceInfo) {
|
||||||
|
try {
|
||||||
|
const policy = JSON.parse(jsonString);
|
||||||
|
|
||||||
|
if (validateMetadataObject(policy, datasetComplianceMetadataTaxonomy)) {
|
||||||
|
const { complianceEntities, datasetClassification } = policy;
|
||||||
|
const resolvedComplianceInfo = { ...complianceInfo, complianceEntities, datasetClassification };
|
||||||
|
const { dialogActions } = notificationDialogActionFactory();
|
||||||
|
|
||||||
|
set(this, 'complianceInfo', resolvedComplianceInfo);
|
||||||
|
|
||||||
|
notify(NotificationEvent.confirm, {
|
||||||
|
header: 'Successfully applied uploaded metadata',
|
||||||
|
content: successUploading,
|
||||||
|
dialogActions,
|
||||||
|
dismissButtonText: false,
|
||||||
|
confirmButtonText: 'Dismiss'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
notify(NotificationEvent.error, {
|
||||||
|
content: invalidPolicyData
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ const compliancePolicyStrings = {
|
|||||||
missingTypes: 'Looks like you may have forgotten to specify a `Field Format` for all ID fields?',
|
missingTypes: 'Looks like you may have forgotten to specify a `Field Format` for all ID fields?',
|
||||||
successUpdating: 'Changes have been successfully saved!',
|
successUpdating: 'Changes have been successfully saved!',
|
||||||
failedUpdating: 'An error occurred while saving.',
|
failedUpdating: 'An error occurred while saving.',
|
||||||
successUploading: 'Metadata successfully updated! Please "Save" when ready.',
|
successUploading: 'Metadata successfully updated! Please confirm and "Save" when ready.',
|
||||||
invalidPolicyData: 'Received policy in an unexpected format! Please check the provided attributes and try again.',
|
invalidPolicyData: 'Received policy in an unexpected format! Please check the provided attributes and try again.',
|
||||||
helpText: {
|
helpText: {
|
||||||
classification:
|
classification:
|
||||||
@ -107,7 +107,7 @@ const getComplianceSteps = (hasSchema: boolean = true): { [x: number]: { name: s
|
|||||||
* @param {IComplianceEntity} { readonly }
|
* @param {IComplianceEntity} { readonly }
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
const isEditableComplianceEntity = ({ readonly }: IComplianceEntity): boolean => readonly !== true;
|
const isEditableComplianceEntity = ({ readonly }: IComplianceEntity): boolean => readonly !== true; // do not simplify, readonly may be undefined
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filters out from a list of compliance entities, entities that are editable
|
* Filters out from a list of compliance entities, entities that are editable
|
||||||
@ -299,26 +299,26 @@ const idTypeFieldsHaveLogicalType = arrayEvery(idTypeFieldHasLogicalType);
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes the function interface for tagsForIdentifierField
|
* Describes the function interface for tagsForIdentifierField
|
||||||
* @interface TagsForIdentifierFieldFn
|
* @interface ITagsForIdentifierFieldFn
|
||||||
*/
|
*/
|
||||||
interface TagsForIdentifierFieldFn {
|
interface ITagsForIdentifierFieldFn {
|
||||||
(identifierField: string): (tags: Array<IComplianceChangeSet>) => Array<IComplianceChangeSet>;
|
(identifierField: string): (tags: Array<IComplianceChangeSet>) => Array<IComplianceChangeSet>;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Gets the tags for a specific identifier field
|
* Gets the tags for a specific identifier field
|
||||||
* @param {string} identifierField
|
* @param {string} identifierField
|
||||||
* @return {TagsForIdentifierFieldFn}
|
* @return {ITagsForIdentifierFieldFn}
|
||||||
*/
|
*/
|
||||||
const tagsForIdentifierField: TagsForIdentifierFieldFn = (identifierField: string) =>
|
const tagsForIdentifierField: ITagsForIdentifierFieldFn = (identifierField: string) =>
|
||||||
arrayFilter(isSchemaFieldTag<IComplianceChangeSet>(identifierField));
|
arrayFilter(isSchemaFieldTag<IComplianceChangeSet>(identifierField));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lists tags that occur for only one identifier type in the list of tags
|
* Lists tags that occur for only one identifier type in the list of tags
|
||||||
* @param {Array<IComplianceChangeSet>} tags the full list of tags to iterate through
|
* @param {Array<IComplianceChangeSet>} tags the full list of tags to iterate through
|
||||||
* @param {TagsForIdentifierFieldFn} tagsForIdentifierFieldFn
|
* @param {ITagsForIdentifierFieldFn} tagsForIdentifierFieldFn
|
||||||
* @return {(singleTags: Array<IComplianceChangeSet>, { identifierField }: IComplianceChangeSet) => (any)[] | Array<IComplianceChangeSet>}
|
* @return {(singleTags: Array<IComplianceChangeSet>, { identifierField }: IComplianceChangeSet) => (any)[] | Array<IComplianceChangeSet>}
|
||||||
*/
|
*/
|
||||||
const singleTagsIn = (tags: Array<IComplianceChangeSet>, tagsForIdentifierFieldFn: TagsForIdentifierFieldFn) => (
|
const singleTagsIn = (tags: Array<IComplianceChangeSet>, tagsForIdentifierFieldFn: ITagsForIdentifierFieldFn) => (
|
||||||
singleTags: Array<IComplianceChangeSet>,
|
singleTags: Array<IComplianceChangeSet>,
|
||||||
{ identifierField }: IComplianceChangeSet
|
{ identifierField }: IComplianceChangeSet
|
||||||
): Array<IComplianceChangeSet> => {
|
): Array<IComplianceChangeSet> => {
|
||||||
@ -329,12 +329,12 @@ const singleTagsIn = (tags: Array<IComplianceChangeSet>, tagsForIdentifierFieldF
|
|||||||
/**
|
/**
|
||||||
* Lists the tags in a list of tags that occur for only one identifier type
|
* Lists the tags in a list of tags that occur for only one identifier type
|
||||||
* @param {Array<IComplianceChangeSet>} tags
|
* @param {Array<IComplianceChangeSet>} tags
|
||||||
* @param {TagsForIdentifierFieldFn} tagsForIdentifierFieldFn
|
* @param {ITagsForIdentifierFieldFn} tagsForIdentifierFieldFn
|
||||||
* @return {Array<IComplianceChangeSet>}
|
* @return {Array<IComplianceChangeSet>}
|
||||||
*/
|
*/
|
||||||
const singleTagsInChangeSet = (
|
const singleTagsInChangeSet = (
|
||||||
tags: Array<IComplianceChangeSet>,
|
tags: Array<IComplianceChangeSet>,
|
||||||
tagsForIdentifierFieldFn: TagsForIdentifierFieldFn
|
tagsForIdentifierFieldFn: ITagsForIdentifierFieldFn
|
||||||
): Array<IComplianceChangeSet> => arrayReduce(singleTagsIn(tags, tagsForIdentifierFieldFn), [])(tags);
|
): Array<IComplianceChangeSet> => arrayReduce(singleTagsIn(tags, tagsForIdentifierFieldFn), [])(tags);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -415,21 +415,18 @@ const foldComplianceChangeSets = async (
|
|||||||
/**
|
/**
|
||||||
* Builds a default shape for securitySpecification & privacyCompliancePolicy with default / unset values
|
* Builds a default shape for securitySpecification & privacyCompliancePolicy with default / unset values
|
||||||
* for non null properties as per Avro schema
|
* for non null properties as per Avro schema
|
||||||
* @param {string} datasetId identifier for the dataset that this privacy object applies to
|
* @param {string} datasetUrn identifier for the dataset that this privacy object applies to
|
||||||
|
* @return {IComplianceInfo}
|
||||||
*/
|
*/
|
||||||
const createInitialComplianceInfo = (datasetId: string): IComplianceInfo => {
|
const initialComplianceObjectFactory = (datasetUrn: string): IComplianceInfo => ({
|
||||||
const identifier = typeof datasetId === 'string' ? { datasetUrn: decodeUrn(datasetId) } : { datasetId };
|
datasetUrn: decodeUrn(datasetUrn),
|
||||||
|
|
||||||
return {
|
|
||||||
...identifier,
|
|
||||||
datasetId: null,
|
datasetId: null,
|
||||||
confidentiality: null,
|
confidentiality: null,
|
||||||
complianceType: '',
|
complianceType: '',
|
||||||
compliancePurgeNote: '',
|
compliancePurgeNote: '',
|
||||||
complianceEntities: [],
|
complianceEntities: [],
|
||||||
datasetClassification: null
|
datasetClassification: null
|
||||||
};
|
});
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
@ -581,7 +578,7 @@ export {
|
|||||||
tagsHaveNoneType,
|
tagsHaveNoneType,
|
||||||
fieldTagsRequiringReview,
|
fieldTagsRequiringReview,
|
||||||
tagsHaveNoneAndNotNoneType,
|
tagsHaveNoneAndNotNoneType,
|
||||||
createInitialComplianceInfo,
|
initialComplianceObjectFactory,
|
||||||
getIdTypeDataTypes,
|
getIdTypeDataTypes,
|
||||||
fieldTagsHaveIdentifierType,
|
fieldTagsHaveIdentifierType,
|
||||||
idTypeFieldHasLogicalType,
|
idTypeFieldHasLogicalType,
|
||||||
|
@ -33,6 +33,7 @@
|
|||||||
notifyOnChangeSetRequiresReview=(action "onCompliancePolicyChangeSetDrift")
|
notifyOnChangeSetRequiresReview=(action "onCompliancePolicyChangeSetDrift")
|
||||||
onSave=(action "savePrivacyCompliancePolicy")
|
onSave=(action "savePrivacyCompliancePolicy")
|
||||||
onReset=(action "resetPrivacyCompliancePolicy")
|
onReset=(action "resetPrivacyCompliancePolicy")
|
||||||
|
onComplianceUpload=(action "onComplianceUpload")
|
||||||
}}
|
}}
|
||||||
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
10
wherehows-web/app/typings/generic.d.ts
vendored
10
wherehows-web/app/typings/generic.d.ts
vendored
@ -10,4 +10,12 @@ type StringUnionKeyToValue<U extends string> = { [K in U]: K };
|
|||||||
*/
|
*/
|
||||||
type StringEnumKeyToEnumValue<T extends string, V> = { [K in T]: V };
|
type StringEnumKeyToEnumValue<T extends string, V> = { [K in T]: V };
|
||||||
|
|
||||||
export { StringUnionKeyToValue, StringEnumKeyToEnumValue };
|
/**
|
||||||
|
* Describes the index signature for a generic object
|
||||||
|
* @interface IObject
|
||||||
|
*/
|
||||||
|
interface IObject<T> {
|
||||||
|
[K: string]: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { StringUnionKeyToValue, StringEnumKeyToEnumValue, IObject };
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { createInitialComplianceInfo } from 'wherehows-web/constants';
|
import { initialComplianceObjectFactory } from 'wherehows-web/constants';
|
||||||
import { SuggestionIntent } from 'wherehows-web/constants/datasets/compliance';
|
import { SuggestionIntent } from 'wherehows-web/constants/datasets/compliance';
|
||||||
import { notFoundApiError } from 'wherehows-web/utils/api';
|
import { notFoundApiError } from 'wherehows-web/utils/api';
|
||||||
import { datasetUrlById, datasetUrlByUrn } from 'wherehows-web/utils/api/datasets/shared';
|
import { datasetUrlById, datasetUrlByUrn } from 'wherehows-web/utils/api/datasets/shared';
|
||||||
@ -62,7 +62,7 @@ export interface IReadComplianceResult {
|
|||||||
* @return {Promise<IReadComplianceResult>}
|
* @return {Promise<IReadComplianceResult>}
|
||||||
*/
|
*/
|
||||||
const readDatasetComplianceByUrn = async (urn: string): Promise<IReadComplianceResult> => {
|
const readDatasetComplianceByUrn = async (urn: string): Promise<IReadComplianceResult> => {
|
||||||
let complianceInfo: IComplianceGetResponse['complianceInfo'] = createInitialComplianceInfo(urn);
|
let complianceInfo: IComplianceGetResponse['complianceInfo'] = initialComplianceObjectFactory(urn);
|
||||||
let isNewComplianceInfo = false;
|
let isNewComplianceInfo = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -71,7 +71,7 @@ const readDatasetComplianceByUrn = async (urn: string): Promise<IReadComplianceR
|
|||||||
}));
|
}));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (notFoundApiError(e)) {
|
if (notFoundApiError(e)) {
|
||||||
complianceInfo = createInitialComplianceInfo(urn);
|
complianceInfo = initialComplianceObjectFactory(urn);
|
||||||
isNewComplianceInfo = true;
|
isNewComplianceInfo = true;
|
||||||
} else {
|
} else {
|
||||||
throw e;
|
throw e;
|
||||||
|
116
wherehows-web/app/utils/datasets/compliance/metadata-schema.ts
Normal file
116
wherehows-web/app/utils/datasets/compliance/metadata-schema.ts
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
import { typeOf } from '@ember/utils';
|
||||||
|
import { DatasetClassifiers } from 'wherehows-web/constants';
|
||||||
|
import { arrayEvery, arrayReduce } from 'wherehows-web/utils/array';
|
||||||
|
import { Classification } from 'wherehows-web/constants/datasets/compliance';
|
||||||
|
import { IObject } from 'wherehows-web/typings/generic';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the interface for schemas that are used for compliance metadata objects
|
||||||
|
* @interface IMetadataTaxonomy
|
||||||
|
*/
|
||||||
|
interface IMetadataTaxonomy {
|
||||||
|
readonly '@props': Array<string>;
|
||||||
|
readonly '@type': string;
|
||||||
|
|
||||||
|
readonly [K: string]: IMetadataTaxonomy | any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines the shape of the dataset compliance metadata json object using the IMetadataTaxonomy interface
|
||||||
|
* @type {({'@type': string; '@props': string[]} | {'@type': string; '@props': string[]; securityClassification: {'@type': string; '@props': any[]}})[]}
|
||||||
|
*/
|
||||||
|
const datasetComplianceMetadataTaxonomy: Array<IMetadataTaxonomy> = [
|
||||||
|
{
|
||||||
|
'@type': 'datasetClassification:object',
|
||||||
|
'@props': Object.keys(DatasetClassifiers).map(key => `${key}:boolean`)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'@type': 'complianceEntities:array',
|
||||||
|
'@props': [
|
||||||
|
'identifierField:string',
|
||||||
|
'identifierType:string|null',
|
||||||
|
'securityClassification:string|null',
|
||||||
|
'logicalType:string|null',
|
||||||
|
'nonOwner:boolean|null',
|
||||||
|
'valuePattern:string|null'
|
||||||
|
],
|
||||||
|
securityClassification: {
|
||||||
|
'@type': 'securityClassification:string',
|
||||||
|
'@props': Object.values(Classification)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks that a value type matches an expected pattern string
|
||||||
|
* @param {*} value the value to check
|
||||||
|
* @param {string} expectedTypePattern the pattern string to match against
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
const valueEquiv = (value: any, expectedTypePattern: string): boolean => expectedTypePattern.includes(typeOf(value));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the type key and the pattern string from the string mapping into a tuple pair
|
||||||
|
* @param {string} objectKeyTypePattern string value consisting of a pair of key/property name and allowed types separated by a colon ":"
|
||||||
|
* @returns {[string, string]}
|
||||||
|
*/
|
||||||
|
const typePatternMap = (objectKeyTypePattern: string): [string, string] =>
|
||||||
|
<[string, string]>objectKeyTypePattern.split(':');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a iteratee bound to an object that checks that a key matches the expected value in the typeMap
|
||||||
|
* @param {IObject<any>} object the object with keys to check
|
||||||
|
* @return {(typeMap: string) => boolean}
|
||||||
|
*/
|
||||||
|
const keyValueHasMatch = (object: IObject<any>) => (typeMap: string) => {
|
||||||
|
const [key, typeString] = typePatternMap(typeMap);
|
||||||
|
return valueEquiv(object[key], typeString);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks each key on an object matches the expected types in the typeMap
|
||||||
|
* @param {IObject<any>} object the object with keys to check
|
||||||
|
* @param {Array<string>} typeMaps the colon delimited type string
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
const keysEquiv = (object: IObject<any>, typeMaps: Array<string>): boolean =>
|
||||||
|
arrayEvery(keyValueHasMatch(object))(typeMaps);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks that a compliance metadata object has a schema that matches the taxonomy / schema provided
|
||||||
|
* @param {IObject<any>} object an instance of a compliance metadata object
|
||||||
|
* @param {Array<IMetadataTaxonomy>} taxonomy schema shape to check against
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
const validateMetadataObject = (object: IObject<any>, taxonomy: Array<IMetadataTaxonomy>): boolean => {
|
||||||
|
const rootTypeMaps = taxonomy.map(category => category['@type']);
|
||||||
|
let isValid = keysEquiv(object, rootTypeMaps);
|
||||||
|
|
||||||
|
if (isValid) {
|
||||||
|
const downlevelAccumulator = (validity: boolean, typeMap: string): boolean => {
|
||||||
|
const [key, pattern]: [string, string] = typePatternMap(typeMap);
|
||||||
|
|
||||||
|
if (pattern.includes('object')) {
|
||||||
|
validity = keysEquiv(object[key], taxonomy.findBy('@type', typeMap)!['@props']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pattern.includes('array') && Array.isArray(object[key])) {
|
||||||
|
validity = arrayReduce(
|
||||||
|
(validity: boolean, value: IObject<string>) =>
|
||||||
|
validity && keysEquiv(value, taxonomy.findBy('@type', typeMap)!['@props']),
|
||||||
|
validity
|
||||||
|
)(object[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return validity;
|
||||||
|
};
|
||||||
|
|
||||||
|
return arrayReduce(downlevelAccumulator, isValid)(rootTypeMaps);
|
||||||
|
}
|
||||||
|
|
||||||
|
return isValid;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default validateMetadataObject;
|
||||||
|
|
||||||
|
export { datasetComplianceMetadataTaxonomy };
|
@ -2,7 +2,7 @@ import { moduleForComponent, test } from 'ember-qunit';
|
|||||||
import hbs from 'htmlbars-inline-precompile';
|
import hbs from 'htmlbars-inline-precompile';
|
||||||
import { waitUntil, find } from 'ember-native-dom-helpers';
|
import { waitUntil, find } from 'ember-native-dom-helpers';
|
||||||
import { urn } from 'wherehows-web/mirage/fixtures/urn';
|
import { urn } from 'wherehows-web/mirage/fixtures/urn';
|
||||||
import { createInitialComplianceInfo } from 'wherehows-web/constants';
|
import { initialComplianceObjectFactory } from 'wherehows-web/constants';
|
||||||
import sinon from 'sinon';
|
import sinon from 'sinon';
|
||||||
|
|
||||||
moduleForComponent(
|
moduleForComponent(
|
||||||
@ -27,7 +27,7 @@ test('it renders', async function(assert) {
|
|||||||
200,
|
200,
|
||||||
{ 'Content-Type': 'application/json' },
|
{ 'Content-Type': 'application/json' },
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
complianceInfo: createInitialComplianceInfo(urn)
|
complianceInfo: initialComplianceObjectFactory(urn)
|
||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
this.server.respondWith(/.*\/compliance-data-types/, [
|
this.server.respondWith(/.*\/compliance-data-types/, [
|
||||||
|
@ -6,20 +6,22 @@ import {
|
|||||||
getFieldIdentifierOptions,
|
getFieldIdentifierOptions,
|
||||||
isAutoGeneratedPolicy,
|
isAutoGeneratedPolicy,
|
||||||
PurgePolicy,
|
PurgePolicy,
|
||||||
createInitialComplianceInfo,
|
initialComplianceObjectFactory,
|
||||||
isRecentSuggestion,
|
isRecentSuggestion,
|
||||||
tagNeedsReview
|
tagNeedsReview
|
||||||
} from 'wherehows-web/constants';
|
} from 'wherehows-web/constants';
|
||||||
import complianceDataTypes from 'wherehows-web/mirage/fixtures/compliance-data-types';
|
import complianceDataTypes from 'wherehows-web/mirage/fixtures/compliance-data-types';
|
||||||
import { mockTimeStamps } from 'wherehows-web/tests/helpers/datasets/compliance-policy/recent-suggestions-constants';
|
import { mockTimeStamps } from 'wherehows-web/tests/helpers/datasets/compliance-policy/recent-suggestions-constants';
|
||||||
import { mockFieldChangeSets } from 'wherehows-web/tests/helpers/datasets/compliance-policy/field-changeset-constants';
|
import { mockFieldChangeSets } from 'wherehows-web/tests/helpers/datasets/compliance-policy/field-changeset-constants';
|
||||||
|
import { hdfsUrn } from 'wherehows-web/mirage/fixtures/urn';
|
||||||
|
|
||||||
module('Unit | Constants | dataset compliance');
|
module('Unit | Constants | dataset compliance');
|
||||||
|
|
||||||
test('createInitialComplianceInfo', function(assert) {
|
test('initialComplianceObjectFactory', function(assert) {
|
||||||
assert.expect(2);
|
assert.expect(2);
|
||||||
const mockId = 1337;
|
const mockUrn = hdfsUrn;
|
||||||
const initialComplianceInfo = {
|
const initialComplianceInfo = {
|
||||||
|
datasetUrn: mockUrn,
|
||||||
datasetId: null,
|
datasetId: null,
|
||||||
complianceType: '',
|
complianceType: '',
|
||||||
compliancePurgeNote: '',
|
compliancePurgeNote: '',
|
||||||
@ -28,8 +30,12 @@ test('createInitialComplianceInfo', function(assert) {
|
|||||||
confidentiality: null
|
confidentiality: null
|
||||||
};
|
};
|
||||||
|
|
||||||
assert.ok(typeof createInitialComplianceInfo === 'function', 'createInitialComplianceInfo is a function');
|
assert.ok(typeof initialComplianceObjectFactory === 'function', 'initialComplianceObjectFactory is a function');
|
||||||
assert.deepEqual(createInitialComplianceInfo(mockId), initialComplianceInfo, 'generates policy in expected shape');
|
assert.deepEqual(
|
||||||
|
initialComplianceObjectFactory(mockUrn),
|
||||||
|
initialComplianceInfo,
|
||||||
|
'generates policy in expected shape'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('isRecentSuggestion exists', function(assert) {
|
test('isRecentSuggestion exists', function(assert) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user