adds support for the nonOwner attribute to compliance policy change set. updates type definitions related to compliance policy. fixes issue with saving a compliance policy after edit.

This commit is contained in:
Seyi Adebajo 2017-12-14 12:57:17 -08:00
parent fcc1c5544b
commit f100e66235
9 changed files with 105 additions and 102 deletions

View File

@ -5,10 +5,10 @@ import {
Classification,
ComplianceFieldIdValue,
IComplianceField,
IFieldIdentifierOption,
SuggestionIntent,
getDefaultSecurityClassification,
IComplianceFieldFormatOption
IComplianceFieldFormatOption,
IComplianceFieldIdentifierOption
} from 'wherehows-web/constants';
import { IComplianceDataType } from 'wherehows-web/typings/api/list/compliance-datatypes';
import { fieldChangeSetRequiresReview } from 'wherehows-web/utils/datasets/compliance-policy';
@ -90,10 +90,10 @@ export default class DatasetComplianceRow extends DatasetTableRow {
/**
* Dropdown options for each compliance field / record
* @type {Array<IFieldIdentifierOption>}
* @type {Array<IComplianceFieldIdentifierOption>}
* @memberof DatasetComplianceRow
*/
complianceFieldIdDropdownOptions: Array<IFieldIdentifierOption>;
complianceFieldIdDropdownOptions: Array<IComplianceFieldIdentifierOption>;
/**
* Reference to the current value of the field's SuggestionIntent if present
@ -140,11 +140,11 @@ export default class DatasetComplianceRow extends DatasetTableRow {
/**
* Takes a field property and extracts the value on the current policy if a suggestion currently exists for the field
* @param {('logicalType' | 'identifierType')} fieldProp
* @returns {(string | void)}
* @param {('logicalType' | 'identifierType')} fieldProp
* @returns {(string | null)}
* @memberof DatasetComplianceRow
*/
getCurrentValueBeforeSuggestion(fieldProp: 'logicalType' | 'identifierType'): string | void {
getCurrentValueBeforeSuggestion(fieldProp: 'logicalType' | 'identifierType'): string | null {
/**
* Current value on policy prior to the suggested value
* @type {string}
@ -152,40 +152,40 @@ export default class DatasetComplianceRow extends DatasetTableRow {
const value = get(get(this, 'field'), fieldProp);
if (hasEnumerableKeys(get(this, 'prediction')) && value) {
/**
* Field drop down options
*/
const complianceFieldIdDropdownOptions = get(this, 'complianceFieldIdDropdownOptions');
const { label: currentIdType } = getWithDefault(this, 'complianceFieldIdDropdownOptions', []).findBy(
'value',
value
) || { label: null };
/**
* Convenience function to get `label` attribute on the display properties object
* @param {(Array<IFieldIdentifierOption> | IFieldIdentifierOption)} [dropDownOptions=[]]
*/
const getLabel = (dropDownOptions: Array<IFieldIdentifierOption> = []) =>
((Array.isArray(dropDownOptions) && dropDownOptions.findBy('value', value)) || { label: void 0 }).label;
const { value: currentLogicalType }: Pick<IComplianceFieldFormatOption, 'value'> = getWithDefault(
this,
'fieldFormats',
[]
).findBy('value', value) || { value: null };
return {
identifierType: getLabel(complianceFieldIdDropdownOptions),
logicalType:
(get(this, 'fieldFormats').find(({ value: format }) => format === value) || { value: void 0 }).value || void 0
identifierType: currentIdType,
logicalType: currentLogicalType
}[fieldProp];
}
return null;
}
/**
* Returns a computed value for the field identifierType
* @type {ComputedProperty<ComplianceFieldIdValue>}
* @type {ComputedProperty<IComplianceField['identifierType']>}
* @memberof DatasetComplianceRow
*/
identifierType = computed('field.identifierType', 'prediction', function(
this: DatasetComplianceRow
): ComplianceFieldIdValue {
): IComplianceField['identifierType'] {
/**
* Describes the interface for the options bag param passed into the getIdentifierType function below
* @interface IGetIdentParams
*/
interface IGetIdentParams {
identifierType: ComplianceFieldIdValue;
identifierType: IComplianceField['identifierType'];
prediction: { identifierType: ComplianceFieldIdValue } | void;
}
@ -193,15 +193,12 @@ export default class DatasetComplianceRow extends DatasetTableRow {
/**
* Inner function takes the field.identifierType and prediction values, and
* returns the identifierType to be rendered in the ui
* @param {IGetIdentParams} params
* @returns {ComplianceFieldIdValue}
* @param {IGetIdentParams} params
* @returns {IComplianceField.identifierType}
*/
const getIdentifierType = (params: IGetIdentParams): ComplianceFieldIdValue => {
const {
identifierType,
prediction: { identifierType: suggestedIdentifierType } = { identifierType: void 0 }
} = params;
return suggestedIdentifierType || identifierType;
const getIdentifierType = (params: IGetIdentParams): IComplianceField['identifierType'] => {
const { identifierType, prediction } = params;
return prediction ? prediction.identifierType : identifierType;
};
return getIdentifierType({ identifierType, prediction });
@ -209,40 +206,43 @@ export default class DatasetComplianceRow extends DatasetTableRow {
/**
* Gets the identifierType on the compliance policy before the suggested value
* @type {ComputedProperty<string>}
* @type {ComputedProperty<string | null>}
* @memberof DatasetComplianceRow
*/
identifierTypeBeforeSuggestion = computed('identifierType', function(): string | void {
identifierTypeBeforeSuggestion = computed('identifierType', function(): string | null {
return this.getCurrentValueBeforeSuggestion('identifierType');
});
/**
* Gets the logicalType on the compliance policy before the suggested value
* @type {ComputedProperty<string>}
* @type {ComputedProperty<string | null>}
* @memberof DatasetComplianceRow
*/
logicalTypeBeforeSuggestion = computed('logicalType', function(): string | void {
logicalTypeBeforeSuggestion = computed('logicalType', function(): string | null {
return this.getCurrentValueBeforeSuggestion('logicalType');
});
/**
* A list of field formats that are determined based on the field identifierType
* @type ComputedProperty<Array<IComplianceDataType.supportedFieldFormats> | void>
* @type ComputedProperty<Array<IComplianceFieldFormatOption>>
* @memberof DatasetComplianceRow
*/
fieldFormats = computed('isIdType', function(this: DatasetComplianceRow): Array<IComplianceFieldFormatOption> {
const identifierType: ComplianceFieldIdValue = get(get(this, 'field'), 'identifierType');
const identifierType = get(get(this, 'field'), '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 fieldFormatOptions = supportedFieldFormats.map(format => ({ value: format, label: format }));
const supportedFieldFormats = complianceDataType.supportedFieldFormats || fieldFormatOptions;
const supportedFormatOptions = supportedFieldFormats.map(format => ({ value: format, label: format }));
return fieldFormatOptions.length > 1 ? [unSelectedFieldFormatValue, ...fieldFormatOptions] : fieldFormatOptions;
return supportedFormatOptions.length
? [unSelectedFieldFormatValue, ...supportedFormatOptions]
: supportedFormatOptions;
}
return [];
return fieldFormatOptions;
});
/**
@ -251,11 +251,10 @@ export default class DatasetComplianceRow extends DatasetTableRow {
* @memberof DatasetComplianceRow
*/
isIdType: ComputedProperty<boolean> = computed('field.identifierType', function(this: DatasetComplianceRow): boolean {
const identifierType: ComplianceFieldIdValue = get(get(this, 'field'), 'identifierType');
const complianceDataTypes = get(this, 'complianceDataTypes');
const complianceDataType = complianceDataTypes.findBy('id', identifierType) || { idType: false };
const { field: { identifierType }, complianceDataTypes } = getProperties(this, ['field', 'complianceDataTypes']);
const { idType } = complianceDataTypes.findBy('id', identifierType || '') || { idType: false };
return complianceDataType.idType;
return idType;
});
/**
@ -264,15 +263,16 @@ export default class DatasetComplianceRow extends DatasetTableRow {
* @memberof DatasetComplianceRow
*/
isPiiType = computed('field.identifierType', function(this: DatasetComplianceRow): boolean {
const identifierType: ComplianceFieldIdValue = get(get(this, 'field'), 'identifierType');
const { identifierType } = get(this, 'field');
return !get(this, 'isIdType') && ![ComplianceFieldIdValue.None, null].includes(identifierType);
// If identifierType exists, and field is not idType or None or null
return !!identifierType && !get(this, 'isIdType') && ![ComplianceFieldIdValue.None, null].includes(identifierType);
});
/**
* The fields logical type, rendered as an Object
* If a prediction exists for this field, the predicted value is shown instead
* @type {(ComputedProperty<IFieldFormatDropdownOption | void>)}
* @type {(ComputedProperty<IComplianceField.logicalType>)}
* @memberof DatasetComplianceRow
*/
logicalType = computed('field.logicalType', 'prediction', function(
@ -296,7 +296,7 @@ export default class DatasetComplianceRow extends DatasetTableRow {
*/
classification = computed('field.classification', 'field.identifierType', 'complianceDataTypes', function(
this: DatasetComplianceRow
): Classification | null {
): IComplianceField['classification'] {
const { field: { identifierType, classification }, complianceDataTypes } = getProperties(this, [
'field',
'complianceDataTypes'

View File

@ -468,7 +468,7 @@ export default Component.extend({
return columnFieldProps.reduce((acc, { identifierField, dataType }) => {
const currentPrivacyAttrs = getKeysOnField(
['identifierType', 'logicalType', 'securityClassification'],
['identifierType', 'logicalType', 'securityClassification', 'nonOwner'],
identifierField,
complianceEntities
);
@ -654,10 +654,11 @@ export default Component.extend({
const datasetFields = get(
this,
'compliancePolicyChangeSet'
).map(({ identifierField, identifierType, logicalType, classification }) => ({
).map(({ identifierField, identifierType, logicalType, nonOwner, classification }) => ({
identifierField,
identifierType,
logicalType,
nonOwner,
securityClassification: classification
}));
// Fields that do not have a logicalType, and no identifierType or identifierType is `fieldIdentifierTypes.none`
@ -685,7 +686,8 @@ export default Component.extend({
identifierField,
identifierType: fieldIdentifierTypes.none.value,
logicalType: null,
securityClassification: null
securityClassification: null,
nonOwner: false
}));
const confirmHandler = (function() {
@ -976,14 +978,16 @@ export default Component.extend({
* @param {String} identifierType
*/
onFieldIdentifierTypeChange({ identifierField }, { value: identifierType = null }) {
const currentComplianceEntities = get(this, 'compliancePolicyChangeSet');
const complianceEntitiesChangeSet = get(this, 'compliancePolicyChangeSet');
// A reference to the current field in the compliance list, it should exist even for empty complianceEntities
// since this is a reference created in the working copy: compliancePolicyChangeSet
const currentFieldInComplianceList = currentComplianceEntities.findBy('identifierField', identifierField);
const changeSetComplianceField = complianceEntitiesChangeSet.findBy('identifierField', identifierField);
setProperties(currentFieldInComplianceList, {
// Reset field attributes on change to field in change set
setProperties(changeSetComplianceField, {
identifierType,
logicalType: null,
nonOwner: false,
isDirty: true
});

View File

@ -1,12 +1,6 @@
import Component from '@ember/component';
import { get } from '@ember/object';
import {
Classification,
ISecurityClassificationOption,
securityClassificationDropdownOptions
} from 'wherehows-web/constants';
type NullOrClassification = null | Classification;
import { ISecurityClassificationOption, securityClassificationDropdownOptions } from 'wherehows-web/constants';
export default class SchemalessTagging extends Component {
classNames = ['schemaless-tagging'];
@ -21,7 +15,9 @@ export default class SchemalessTagging extends Component {
* Interface for parent supplied onClassificationChange action
* @memberof SchemalessTagging
*/
onClassificationChange: (securityClassification: NullOrClassification) => NullOrClassification;
onClassificationChange: (
securityClassification: ISecurityClassificationOption['value']
) => ISecurityClassificationOption['value'];
/**
* Flag indicating that the dataset contains personally identifiable data
@ -46,10 +42,10 @@ export default class SchemalessTagging extends Component {
/**
* The current dataset classification value
* @type {(null | Classification)}
* @type { ISecurityClassificationOption.value}
* @memberof SchemalessTagging
*/
classification: null | Classification;
classification: ISecurityClassificationOption['value'];
actions = {
/**

View File

@ -1,34 +1,41 @@
import Ember from 'ember';
import { ComplianceFieldIdValue, IdLogicalType } from 'wherehows-web/constants/datasets/compliance';
import { Classification, ComplianceFieldIdValue, IdLogicalType } from 'wherehows-web/constants/datasets/compliance';
import { IComplianceDataType } from 'wherehows-web/typings/api/list/compliance-datatypes';
import { arrayMap } from 'wherehows-web/utils/array';
const { String: { htmlSafe } } = Ember;
/**
* Defines the interface field identifier drop downs
* Defines the generic interface field identifier drop downs
* @interface IFieldIdentifierOption
* @template T
*/
interface IFieldIdentifierOption {
value: string;
interface IFieldIdentifierOption<T> {
value: T;
label: string;
isDisabled?: boolean;
}
/**
* Defines the interface for compliance data type field options
* Defines the interface for compliance data type field option
* @interface IComplianceFieldIdentifierOption
* @extends {IFieldIdentifierOption}
* @extends {IFieldIdentifierOption<ComplianceFieldIdValue>}
*/
interface IComplianceFieldIdentifierOption extends IFieldIdentifierOption {
value: ComplianceFieldIdValue;
}
interface IComplianceFieldIdentifierOption extends IFieldIdentifierOption<ComplianceFieldIdValue> {}
interface IComplianceFieldFormatOption {
value: IdLogicalType | null;
label: string;
isDisabled?: boolean;
}
/**
* Defines the interface for a compliance field format dropdown option
* @interface IComplianceFieldFormatOption
* @extends {(IFieldIdentifierOption<IdLogicalType | null>)}
*/
interface IComplianceFieldFormatOption extends IFieldIdentifierOption<IdLogicalType | null> {}
/**
* Defines the interface for an each security classification dropdown option
* @interface ISecurityClassificationOption
* @extends {(IFieldIdentifierOption<Classification | null>)}
*/
interface ISecurityClassificationOption extends IFieldIdentifierOption<Classification | null> {}
/**
* Defines a map of values for the compliance policy on a dataset
@ -44,7 +51,7 @@ const compliancePolicyStrings = {
invalidPolicyData: 'Received policy in an unexpected format! Please check the provided attributes and try again.',
helpText: {
classification:
'The default value is taken from go/dht and should be good enough in most cases. ' +
'This security classification is from go/dht and should be good enough in most cases. ' +
'You can optionally override it if required by house security.'
}
};
@ -114,7 +121,7 @@ export {
complianceSteps,
hiddenTrackingFields,
getComplianceSteps,
IFieldIdentifierOption,
IComplianceFieldIdentifierOption,
IComplianceFieldFormatOption
IComplianceFieldFormatOption,
ISecurityClassificationOption
};

View File

@ -98,10 +98,11 @@ interface INonIdLogicalTypesSignature {
* @interface IComplianceField
*/
interface IComplianceField {
identifierType: ComplianceFieldIdValue;
identifierType: ComplianceFieldIdValue | void;
logicalType: IdLogicalType | null;
classification: Classification | null;
privacyPolicyExists: boolean;
nonOwner: boolean;
isDirty: boolean;
suggestion?: {
identifierType: ComplianceFieldIdValue;

View File

@ -1,5 +1,6 @@
import Ember from 'ember';
import { capitalize } from '@ember/string';
import { ISecurityClassificationOption } from 'wherehows-web/constants/dataset-compliance';
import {
Classification,
nonIdFieldLogicalTypes,
@ -10,18 +11,9 @@ import {
IdLogicalType,
ComplianceFieldIdValue
} from 'wherehows-web/constants/datasets/compliance';
import { IComplianceField } from 'wherehows-web/constants/index';
import { IComplianceDataType } from 'wherehows-web/typings/api/list/compliance-datatypes';
/**
* Defines the interface for an each security classification dropdown option
* @export
* @interface ISecurityClassificationOption
*/
export interface ISecurityClassificationOption {
value: '' | Classification;
label: string;
}
/**
* Describes the interface for a drop down option for the field format column in the compliance table
*
@ -65,11 +57,12 @@ const formatAsCapitalizedStringWithSpaces = (string: string) => capitalize(strin
* @type {Array<ISecurityClassificationOption>}
*/
const securityClassificationDropdownOptions: Array<ISecurityClassificationOption> = [
'',
null,
...classifiers.sort()
].map((value: '' | Classification) => ({
].map((value: ISecurityClassificationOption['value']) => ({
value,
label: value ? formatAsCapitalizedStringWithSpaces(value) : '...'
label: value ? formatAsCapitalizedStringWithSpaces(value) : 'Unspecified',
isDisabled: !value
}));
/**
@ -144,9 +137,9 @@ const fieldIdentifierTypeValues: Array<ComplianceFieldIdValue> = Object.values(C
*/
const getDefaultSecurityClassification = (
complianceDataTypes: Array<IComplianceDataType> = [],
identifierType: ComplianceFieldIdValue
identifierType: IComplianceField['identifierType']
): IComplianceDataType['defaultSecurityClassification'] | null => {
const complianceDataType = complianceDataTypes.findBy('id', identifierType);
const complianceDataType = complianceDataTypes.findBy('id', identifierType || '');
return complianceDataType ? complianceDataType.defaultSecurityClassification : null;
};

View File

@ -127,7 +127,7 @@
{{#row.cell}}
<section class="compliance-depends compliance-depends--visible">
<div class="dataset-compliance-fields__form-input">
Field ID Type
Field Type
<a
target="_blank"
href="http://go/metadata_acquisition#ProjectOverview-compliance">
@ -157,7 +157,7 @@
Field Format
<a
target="_blank"
href="http://go/gdpr-taxonomy#MetadataTaxonomyforDataSets-DatasetLevelTags">
href="http://go/metadata_acquisition#ProjectOverview-compliance">
<sup>
More Info

View File

@ -152,7 +152,8 @@ const mergeMappedColumnFieldsWithSuggestions = (mappedColumnFields = {}, fieldSu
securityClassification,
policyModificationTime,
privacyPolicyExists,
isDirty
isDirty,
nonOwner
} = mappedColumnFields[fieldName];
const suggestion = fieldSuggestionMap[identifierField];
@ -163,6 +164,7 @@ const mergeMappedColumnFieldsWithSuggestions = (mappedColumnFields = {}, fieldSu
logicalType,
privacyPolicyExists,
isDirty,
nonOwner,
classification: securityClassification
};

View File

@ -22,7 +22,7 @@ test('it shows the current classification', function(assert) {
assert.expect(3);
this.render(hbs`{{datasets/schemaless-tagging classification=classification}}`);
assert.equal(document.querySelector(`select`).value, '', "displays '' when not set");
assert.equal(document.querySelector(`select`).value, 'Unspecified', "displays 'Unspecified' when not set");
this.set('classification', Classification.LimitedDistribution);