5408 Prevents adding a new field if the field identifier type is already None, adds an error indicator if the field identifier type has a None type added when it already has another that is not None

This commit is contained in:
Seyi Adebajo 2018-04-18 10:38:58 -07:00
parent d4e2aeb9c0
commit 895a252006
9 changed files with 161 additions and 94 deletions

View File

@ -1,7 +1,7 @@
import Component from '@ember/component';
import ComputedProperty from '@ember/object/computed';
import { get, getProperties, computed } from '@ember/object';
import { ComplianceFieldIdValue, idTypeFieldHasLogicalType, isFieldIdType } from 'wherehows-web/constants';
import { ComplianceFieldIdValue, idTypeFieldHasLogicalType, isTagIdType } from 'wherehows-web/constants';
import {
IComplianceChangeSet,
IComplianceFieldFormatOption,
@ -61,7 +61,7 @@ export default class DatasetComplianceFieldTag extends Component {
this: DatasetComplianceFieldTag
): boolean {
const { tag, complianceDataTypes } = getProperties(this, ['tag', 'complianceDataTypes']);
return isFieldIdType(complianceDataTypes)(tag);
return isTagIdType(complianceDataTypes)(tag);
});
/**
@ -69,27 +69,25 @@ export default class DatasetComplianceFieldTag extends Component {
* @type ComputedProperty<Array<IComplianceFieldFormatOption>>
* @memberof DatasetComplianceFieldTag
*/
fieldFormats: ComputedProperty<Array<IComplianceFieldFormatOption>> = computed(
'isIdType',
'complianceDataTypes',
function(this: DatasetComplianceFieldTag): Array<IComplianceFieldFormatOption> {
const identifierType = get(this, 'tag')['identifierType'] || '';
const { isIdType, complianceDataTypes } = getProperties(this, ['isIdType', 'complianceDataTypes']);
const complianceDataType = complianceDataTypes.findBy('id', identifierType);
let fieldFormatOptions: Array<IComplianceFieldFormatOption> = [];
fieldFormats: ComputedProperty<Array<IComplianceFieldFormatOption>> = computed('isIdType', function(
this: DatasetComplianceFieldTag
): Array<IComplianceFieldFormatOption> {
const identifierType = get(this, 'tag')['identifierType'] || '';
const { isIdType, complianceDataTypes } = getProperties(this, ['isIdType', 'complianceDataTypes']);
const complianceDataType = complianceDataTypes.findBy('id', identifierType);
let fieldFormatOptions: Array<IComplianceFieldFormatOption> = [];
if (complianceDataType && isIdType) {
const supportedFieldFormats = complianceDataType.supportedFieldFormats || [];
const supportedFormatOptions = supportedFieldFormats.map(format => ({ value: format, label: format }));
if (complianceDataType && isIdType) {
const supportedFieldFormats = complianceDataType.supportedFieldFormats || [];
const supportedFormatOptions = supportedFieldFormats.map(format => ({ value: format, label: format }));
return supportedFormatOptions.length
? [unSelectedFieldFormatValue, ...supportedFormatOptions]
: supportedFormatOptions;
}
return fieldFormatOptions;
return supportedFormatOptions.length
? [unSelectedFieldFormatValue, ...supportedFormatOptions]
: supportedFormatOptions;
}
);
return fieldFormatOptions;
});
/**
* Checks if the field format / logical type for this tag is missing, if the field is of ID type

View File

@ -8,15 +8,17 @@ import {
ISuggestedFieldTypeValues
} from 'wherehows-web/typings/app/dataset-compliance';
import {
changeSetFieldsRequiringReview,
changeSetReviewableAttributeTriggers,
ComplianceFieldIdValue,
complianceFieldChangeSetItemFactory,
SuggestionIntent
SuggestionIntent,
fieldTagsRequiringReview,
tagsHaveNoneAndNotNoneType,
tagsHaveNoneType
} from 'wherehows-web/constants';
import { getTagSuggestions } from 'wherehows-web/utils/datasets/compliance-suggestions';
import { IColumnFieldProps } from 'wherehows-web/typings/app/dataset-columns';
import { isFieldTagged } from 'wherehows-web/constants/dataset-compliance';
import { fieldTagsHaveIdentifierType } from 'wherehows-web/constants/dataset-compliance';
import { IComplianceDataType } from 'wherehows-web/typings/api/list/compliance-datatypes';
export default class DatasetComplianceRollupRow extends Component.extend({
@ -75,7 +77,10 @@ export default class DatasetComplianceRollupRow extends Component.extend({
`fieldChangeSet.@each.{${changeSetReviewableAttributeTriggers}}`,
'complianceDataTypes',
function(this: DatasetComplianceRollupRow): boolean {
return !!changeSetFieldsRequiringReview(get(this, 'complianceDataTypes'))(get(this, 'fieldChangeSet')).length;
const tags = get(this, 'fieldChangeSet');
const { length } = fieldTagsRequiringReview(get(this, 'complianceDataTypes'))(get(this, 'identifierField'))(tags);
return !!length || tagsHaveNoneAndNotNoneType(tags);
}
);
@ -133,6 +138,16 @@ export default class DatasetComplianceRollupRow extends Component.extend({
*/
hasSingleTag: ComputedProperty<boolean> = equal('fieldChangeSet.length', 1);
/**
* Checks if any of the tags on this field have a ComplianceFieldIdValue.None identifierType
* @type {ComputedProperty<boolean>}
*/
hasNoneTag: ComputedProperty<boolean> = computed('fieldChangeSet', function(
this: DatasetComplianceRollupRow
): boolean {
return tagsHaveNoneType(get(this, 'fieldChangeSet'));
});
/**
* Checks if any of the field tags for this row are dirty
* @type {ComputedProperty<boolean>}
@ -244,7 +259,7 @@ export default class DatasetComplianceRollupRow extends Component.extend({
'fieldProps'
]);
if (isFieldTagged(fieldChangeSet)) {
if (fieldTagsHaveIdentifierType(fieldChangeSet)) {
onFieldTagAdded(
complianceFieldChangeSetItemFactory({
identifierField,
@ -293,7 +308,7 @@ export default class DatasetComplianceRollupRow extends Component.extend({
// Accept the suggestion for either identifierType and/or logicalType
if (suggestion && intent === SuggestionIntent.accept) {
const { identifierType, logicalType } = suggestion;
const updateDefault = hasSingleTag && !isFieldTagged(get(this, 'fieldChangeSet'));
const updateDefault = hasSingleTag && !fieldTagsHaveIdentifierType(get(this, 'fieldChangeSet'));
if (identifierType && !suggestedValuesInChangeSet.includes(identifierType)) {
if (updateDefault) {

View File

@ -8,8 +8,8 @@ import {
ComplianceFieldIdValue,
SuggestionIntent,
getDefaultSecurityClassification,
fieldChangeSetRequiresReview,
isFieldIdType,
tagNeedsReview,
isTagIdType,
changeSetReviewableAttributeTriggers,
idTypeFieldHasLogicalType
} from 'wherehows-web/constants';
@ -151,7 +151,7 @@ export default class DatasetComplianceRow extends DatasetTableRow {
isReviewRequested = computed(`field.{${changeSetReviewableAttributeTriggers}}`, 'complianceDataTypes', function(
this: DatasetComplianceRow
): boolean {
return fieldChangeSetRequiresReview(get(this, 'complianceDataTypes'))(get(this, 'field'));
return tagNeedsReview(get(this, 'complianceDataTypes'))(get(this, 'field'));
});
/**
@ -292,7 +292,7 @@ export default class DatasetComplianceRow extends DatasetTableRow {
this: DatasetComplianceRow
): boolean {
const { field, complianceDataTypes } = getProperties(this, ['field', 'complianceDataTypes']);
return isFieldIdType(complianceDataTypes)(field);
return isTagIdType(complianceDataTypes)(field);
});
/**

View File

@ -24,10 +24,9 @@ import {
PurgePolicy,
getSupportedPurgePolicies,
mergeComplianceEntitiesWithSuggestions,
getFieldsRequiringReview,
isFieldIdType,
tagsRequiringReview,
isTagIdType,
idTypeFieldsHaveLogicalType,
changeSetFieldsRequiringReview,
changeSetReviewableAttributeTriggers,
mapSchemaColumnPropsToCurrentPrivacyPolicy,
foldComplianceChangeSets,
@ -110,6 +109,12 @@ export default class DatasetCompliance extends Component {
showAllDatasetMemberData: boolean;
complianceInfo: void | IComplianceInfo;
/**
* References the ComplianceFieldIdValue enum
* @type {ComplianceFieldIdValue}
*/
ComplianceFieldIdValue = ComplianceFieldIdValue;
/**
* Suggested values for compliance types e.g. identifier type and/or logical type
* @type {IComplianceSuggestion | void}
@ -691,7 +696,7 @@ export default class DatasetCompliance extends Component {
]);
return get(this, 'fieldReviewOption') === 'showReview'
? changeSetFieldsRequiringReview(complianceDataTypes)(changeSet)
? tagsRequiringReview(complianceDataTypes)(changeSet)
: changeSet;
}
);
@ -718,21 +723,20 @@ export default class DatasetCompliance extends Component {
};
/**
* Invokes external action with flag indicating that a field in the changeSet requires user review
* Invokes external action with flag indicating that a field in the tags requires user review
* @param {Array<IComplianceDataType>} complianceDataTypes
* @param {Array<IComplianceChangeSet>} changeSet
* @param {Array<IComplianceChangeSet>} tags
*/
notifyHandlerOfFieldsRequiringReview = (
complianceDataTypes: Array<IComplianceDataType>,
changeSet: Array<IComplianceChangeSet>
tags: Array<IComplianceChangeSet>
) => {
// adding assertions for run-loop callback invocation, because static type checks are bypassed
assert('expected complianceDataTypes to be of type `array`', Array.isArray(complianceDataTypes));
assert('expected changeSet to be of type `array`', Array.isArray(changeSet));
assert('expected tags to be of type `array`', Array.isArray(tags));
const hasChangeSetDrift = !!tagsRequiringReview(complianceDataTypes)(tags).length;
const hasChangeSetDrift = getFieldsRequiringReview(complianceDataTypes)(changeSet).some(
(isReviewRequired: boolean) => isReviewRequired
);
this.notifyOnChangeSetRequiresReview(hasChangeSetDrift);
};
@ -745,8 +749,7 @@ export default class DatasetCompliance extends Component {
`compliancePolicyChangeSet.@each.{${changeSetReviewableAttributeTriggers}}`,
'complianceDataTypes',
function(this: DatasetCompliance): number {
return changeSetFieldsRequiringReview(get(this, 'complianceDataTypes'))(get(this, 'compliancePolicyChangeSet'))
.length;
return tagsRequiringReview(get(this, 'complianceDataTypes'))(get(this, 'compliancePolicyChangeSet')).length;
}
);
@ -857,7 +860,7 @@ export default class DatasetCompliance extends Component {
validateFields(this: DatasetCompliance) {
const { notify } = get(this, 'notifications');
const { complianceEntities = [] } = get(this, 'complianceInfo') || {};
const idTypeComplianceEntities = complianceEntities.filter(isFieldIdType(get(this, 'complianceDataTypes')));
const idTypeComplianceEntities = complianceEntities.filter(isTagIdType(get(this, 'complianceDataTypes')));
// Validation operations
const idFieldsHaveValidLogicalType: boolean = idTypeFieldsHaveLogicalType(idTypeComplianceEntities);

View File

@ -1,7 +1,7 @@
import { PurgePolicy } from 'wherehows-web/constants/index';
import { IComplianceEntity, IComplianceInfo } from 'wherehows-web/typings/api/datasets/compliance';
import { IComplianceDataType } from 'wherehows-web/typings/api/list/compliance-datatypes';
import { arrayEvery, arrayFilter, arrayMap, arrayReduce } from 'wherehows-web/utils/array';
import { arrayEvery, arrayFilter, arrayMap, arrayReduce, arraySome } from 'wherehows-web/utils/array';
import { fleece, hasEnumerableKeys } from 'wherehows-web/utils/object';
import { lastSeenSuggestionInterval } from 'wherehows-web/constants/metadata-acquisition';
import { decodeUrn } from 'wherehows-web/utils/validators/urn';
@ -20,6 +20,7 @@ import {
ISchemaWithPolicyTagsReducingFn
} from 'wherehows-web/typings/app/dataset-columns';
import { IDatasetColumn } from 'wherehows-web/typings/api/datasets/columns';
import { ComplianceFieldIdValue } from 'wherehows-web/constants/datasets/compliance';
/**
* Defines a map of values for the compliance policy on a dataset
@ -165,22 +166,17 @@ const isRecentSuggestion = (
* Checks if a compliance policy changeSet field requires user attention: if a suggestion
* is available but the user has not indicated intent or a policy for the field does not currently exist remotely
* and the related field changeSet has not been modified on the client and isn't readonly
* @param {boolean} isDirty
* @return {boolean}
*/
/**
*
* @param {Array<IComplianceDataType>} complianceDataTypes
* @return {(changeSet: IComplianceChangeSet) => boolean}
* @return {(tag: IComplianceChangeSet) => boolean}
*/
const fieldChangeSetRequiresReview = (complianceDataTypes: Array<IComplianceDataType>) =>
const tagNeedsReview = (complianceDataTypes: Array<IComplianceDataType>) =>
/**
*
* @param {IComplianceChangeSet} changeSet
* @param {IComplianceChangeSet} tag
* @return {boolean}
*/
(changeSet: IComplianceChangeSet): boolean => {
const { isDirty, suggestion, privacyPolicyExists, suggestionAuthority, readonly, identifierType } = changeSet;
(tag: IComplianceChangeSet): boolean => {
const { isDirty, suggestion, privacyPolicyExists, suggestionAuthority, readonly, identifierType } = tag;
let isReviewRequired = false;
if (readonly) {
@ -192,33 +188,69 @@ const fieldChangeSetRequiresReview = (complianceDataTypes: Array<IComplianceData
isReviewRequired = isReviewRequired || !suggestionAuthority;
}
if (isFieldIdType(complianceDataTypes)(changeSet)) {
isReviewRequired = isReviewRequired || !idTypeFieldHasLogicalType(changeSet);
if (isTagIdType(complianceDataTypes)(tag)) {
isReviewRequired = isReviewRequired || !idTypeFieldHasLogicalType(tag);
}
// If either the privacy policy doesn't exists, or user hasn't made changes, then review is required
return isReviewRequired || !(privacyPolicyExists || isDirty);
};
/**
* Asserts that a compliance field tag has an identifierType of ComplianceFieldIdValue.None
* @param {ComplianceFieldIdValue | NonIdLogicalType | null} identifierType
* @return {boolean}
*/
const isTagNoneType = ({ identifierType }: IComplianceChangeSet): boolean =>
identifierType === ComplianceFieldIdValue.None;
/**
* Asserts the inverse of isTagNoneType
* @param {IComplianceChangeSet} tag
* @return {boolean}
*/
const isTagNotNoneType = (tag: IComplianceChangeSet): boolean => !isTagNoneType(tag);
/**
* Asserts that a list of tags have at least 1 type of ComplianceFieldIdValue.None
* @type {(array: Array<IComplianceChangeSet>) => boolean}
*/
const tagsHaveNoneType = arraySome(isTagNoneType);
/**
* Asserts that a list of tags have at least 1 type that is not of ComplianceFieldIdValue.None
* @type {(array: Array<IComplianceChangeSet>) => boolean}
*/
const tagsHaveNotNoneType = arraySome(isTagNotNoneType);
/**
* Asserts that a list of tags have a type of ComplianceFieldIdValue.None and not ComplianceFieldIdValue.None
* @param {Array<IComplianceChangeSet>} tags
* @return {boolean}
*/
const tagsHaveNoneAndNotNoneType = (tags: Array<IComplianceChangeSet>) =>
tagsHaveNoneType(tags) && tagsHaveNotNoneType(tags);
/**
* Asserts that a tag / change set item has an identifier type that is in the list of compliance data types
* @param {Array<IComplianceDataType>} [complianceDataTypes=[]]
*/
const isFieldIdType = (complianceDataTypes: Array<IComplianceDataType> = []) => ({
const isTagIdType = (complianceDataTypes: Array<IComplianceDataType> = []) => ({
identifierType
}: IComplianceChangeSet): boolean => getIdTypeDataTypes(complianceDataTypes).includes(<string>identifierType);
/**
* Asserts that a tag has a value for it's identifierType property
* Asserts that a tag has a value for it's identifierType property and the identifierType is not ComplianceFieldIdValue.None
* @param {IComplianceChangeSet} {identifierType}
* @returns {boolean}
*/
const tagHasIdentifierType = ({ identifierType }: IComplianceChangeSet): boolean => !!identifierType;
const tagHasIdentifierType = ({ identifierType }: IComplianceChangeSet): boolean =>
!!identifierType && identifierType !== ComplianceFieldIdValue.None;
/**
* Takes an array of compliance change sets and checks that each item has an identifierType
* @type {(array: IComplianceChangeSet[]) => boolean}
*/
const isFieldTagged = arrayEvery(tagHasIdentifierType);
const fieldTagsHaveIdentifierType = arrayEvery(tagHasIdentifierType);
/**
* Asserts that a compliance entity has a logical type
@ -232,19 +264,31 @@ const idTypeFieldHasLogicalType = ({ logicalType }: IComplianceEntity): boolean
* @type {(array: IComplianceEntity[]) => boolean}
*/
const idTypeFieldsHaveLogicalType = arrayEvery(idTypeFieldHasLogicalType);
/**
* Gets the fields requiring review
* @type {(array: Array<IComplianceChangeSet>) => Array<boolean>}
*/
const getFieldsRequiringReview = (complianceDataTypes: Array<IComplianceDataType>) =>
arrayMap(fieldChangeSetRequiresReview(complianceDataTypes));
/**
* Returns a list of changeSet fields that requires user attention
* @type {function({}): Array<{ isDirty, suggestion, privacyPolicyExists, suggestionAuthority, identifierType }>}
* Gets the tags for a specific identifier field
* @param {string} identifierField
* @return {(array: Array<IComplianceChangeSet>) => Array<IComplianceChangeSet>}
*/
const changeSetFieldsRequiringReview = (complianceDataTypes: Array<IComplianceDataType>) =>
arrayFilter<IComplianceChangeSet>(fieldChangeSetRequiresReview(complianceDataTypes));
const tagsForIdentifierField = (identifierField: string) =>
arrayFilter(isSchemaFieldTag<IComplianceChangeSet>(identifierField));
/**
* Returns a list of changeSet tags that requires user attention
* @param {Array<IComplianceDataType>} complianceDataTypes
* @return {(array: Array<IComplianceChangeSet>) => Array<IComplianceChangeSet>}
*/
const tagsRequiringReview = (complianceDataTypes: Array<IComplianceDataType>) =>
arrayFilter<IComplianceChangeSet>(tagNeedsReview(complianceDataTypes));
/**
* Lists the tags for a specific identifier field that need to be reviewed
* @param {Array<IComplianceDataType>} complianceDataTypes
* @return {(identifierField: string) => (tags: Array<IComplianceChangeSet>) => Array<IComplianceChangeSet>}
*/
const fieldTagsRequiringReview = (complianceDataTypes: Array<IComplianceDataType>) => (identifierField: string) => (
tags: Array<IComplianceChangeSet>
) => tagsRequiringReview(complianceDataTypes)(tagsForIdentifierField(identifierField)(tags));
/**
* Extracts a suggestion for a field from a suggestion map and merges a compliance entity with the suggestion
@ -371,8 +415,9 @@ const complianceFieldChangeSetItemFactory = ({
* @param {string} identifierFieldMatch the field name to match to the IComplianceEntity
* @return {({ identifierField }: IComplianceEntity) => boolean}
*/
const isSchemaFieldTag = (identifierFieldMatch: string) => ({ identifierField }: IComplianceEntity): boolean =>
identifierFieldMatch === identifierField;
const isSchemaFieldTag = <T extends { identifierField: string }>(identifierFieldMatch: string) => ({
identifierField
}: T): boolean => identifierFieldMatch === identifierField;
/**
* Creates an instance of a compliance entity with client side metadata about the entity
@ -408,7 +453,7 @@ const schemaFieldsWithPolicyTagsReducingFn: ISchemaWithPolicyTagsReducingFn = (
{ identifierField, dataType }: IColumnFieldProps
): ISchemaFieldsToPolicy => {
let complianceEntitiesWithMetadata: Array<IComplianceEntityWithMetadata>;
let schemaFieldTags = arrayFilter(isSchemaFieldTag(identifierField))(currentEntities);
let schemaFieldTags = arrayFilter(isSchemaFieldTag<IComplianceEntity>(identifierField))(currentEntities);
schemaFieldTags = schemaFieldTags.length ? schemaFieldTags : [complianceFieldTagFactory(identifierField)];
complianceEntitiesWithMetadata = arrayMap(complianceEntityWithMetadata(policyModificationTime, dataType))(
@ -457,17 +502,19 @@ export {
filterEditableEntities,
isAutoGeneratedPolicy,
removeReadonlyAttr,
fieldChangeSetRequiresReview,
isFieldIdType,
tagNeedsReview,
isTagIdType,
mergeComplianceEntitiesWithSuggestions,
isRecentSuggestion,
getFieldsRequiringReview,
tagsRequiringReview,
tagsHaveNoneType,
fieldTagsRequiringReview,
tagsHaveNoneAndNotNoneType,
createInitialComplianceInfo,
getIdTypeDataTypes,
isFieldTagged,
fieldTagsHaveIdentifierType,
idTypeFieldHasLogicalType,
idTypeFieldsHaveLogicalType,
changeSetFieldsRequiringReview,
changeSetReviewableAttributeTriggers,
mapSchemaColumnPropsToCurrentPrivacyPolicy,
foldComplianceChangeSets,

View File

@ -5,6 +5,7 @@
identifierField=identifierField
dataType=dataType
isIdType=isIdType
hasNoneTag=hasNoneTag
fieldFormats=fieldFormats
fieldProps=fieldProps
suggestion=suggestion

View File

@ -154,7 +154,6 @@
{{row.suggestion.confidence}}%
&nbsp;
{{#if isEditing}}
{{#if row.suggestionResolution}}
<div class="dataset-compliance-fields__resolution {{if (eq row.suggestionResolution 'Accepted')
'dataset-compliance-fields__resolution--ok'}}">
@ -184,7 +183,7 @@
{{/row.cell}}
{{#row.cell}}
{{#if (and isEditing (not row.isReadonly))}}
{{#if (and isEditing (and (not row.isReadonly) (not row.hasNoneTag)))}}
<button
class="nacho-button nacho-button--tertiary dataset-compliance-fields__add-field"
onclick={{action row.onAddFieldTag}}>
@ -236,6 +235,7 @@
values=complianceFieldIdDropdownOptions
selected=(readonly tag.identifierType)
selectionDidChange=(action tagRowComponent.tagIdentifierTypeDidChange)
class=(if (and row.hasNoneTag (not-eq tag.identifierType ComplianceFieldIdValue.None)) "dataset-compliance-fields--missing-selection")
}}
</div>
{{/row.cell}}

View File

@ -23,6 +23,15 @@ const arrayFilter = <T>(filtrationFunction: (param: T) => boolean): ((array: Arr
const arrayEvery = <T>(filter: (param: T) => boolean): ((array: Array<T>) => boolean) => (array = []) =>
array.every(filter);
/**
* Type safe utility `iterate-first data-last` function for array some
* @template T
* @param {(param: T) => boolean} filter
* @return {(array: Array<T>) => boolean}
*/
const arraySome = <T>(filter: (param: T) => boolean): ((array: Array<T>) => boolean) => (array = []) =>
array.some(filter);
/**
* Composable reducer abstraction, curries a reducing iteratee and returns a reducing function that takes a list
* @template U
@ -51,4 +60,4 @@ const isListUnique = <T>(list: Array<T> = []): boolean => new Set(list).size ===
*/
const compact = <T>(list: Array<T> = []): Array<T> => list.filter(item => item);
export { arrayMap, arrayFilter, arrayReduce, isListUnique, compact, arrayEvery };
export { arrayMap, arrayFilter, arrayReduce, isListUnique, compact, arrayEvery, arraySome };

View File

@ -8,7 +8,7 @@ import {
PurgePolicy,
createInitialComplianceInfo,
isRecentSuggestion,
fieldChangeSetRequiresReview
tagNeedsReview
} from 'wherehows-web/constants';
import complianceDataTypes from 'wherehows-web/mirage/fixtures/compliance-data-types';
import { mockTimeStamps } from 'wherehows-web/tests/helpers/datasets/compliance-policy/recent-suggestions-constants';
@ -46,23 +46,17 @@ test('isRecentSuggestion correctly determines if a suggestion is recent or not',
});
});
test('fieldChangeSetRequiresReview exists', function(assert) {
assert.ok(typeof fieldChangeSetRequiresReview === 'function', 'fieldChangeSetRequiresReview is a function');
test('tagNeedsReview exists', function(assert) {
assert.ok(typeof tagNeedsReview === 'function', 'tagNeedsReview is a function');
assert.ok(
typeof fieldChangeSetRequiresReview([])({}) === 'boolean',
'fieldChangeSetRequiresReview returns a boolean'
);
assert.ok(typeof tagNeedsReview([])({}) === 'boolean', 'tagNeedsReview returns a boolean');
});
test('fieldChangeSetRequiresReview correctly determines if a fieldChangeSet requires review', function(assert) {
test('tagNeedsReview correctly determines if a fieldChangeSet requires review', function(assert) {
assert.expect(mockFieldChangeSets.length);
mockFieldChangeSets.forEach(changeSet =>
assert.ok(
fieldChangeSetRequiresReview(complianceDataTypes)(changeSet) === changeSet.__requiresReview__,
changeSet.__msg__
)
assert.ok(tagNeedsReview(complianceDataTypes)(changeSet) === changeSet.__requiresReview__, changeSet.__msg__)
);
});