2017-12-11 13:13:34 -08:00
|
|
|
import DatasetTableRow from 'wherehows-web/components/dataset-table-row';
|
|
|
|
import ComputedProperty, { alias } from '@ember/object/computed';
|
|
|
|
import { computed, get, getProperties, getWithDefault } from '@ember/object';
|
|
|
|
import {
|
|
|
|
Classification,
|
2017-12-11 18:26:26 -08:00
|
|
|
ComplianceFieldIdValue,
|
2017-12-11 13:13:34 -08:00
|
|
|
IComplianceField,
|
|
|
|
IFieldIdentifierOption,
|
2017-12-12 22:19:01 -08:00
|
|
|
SuggestionIntent,
|
|
|
|
getDefaultSecurityClassification
|
2017-12-11 13:13:34 -08:00
|
|
|
} from 'wherehows-web/constants';
|
2017-12-12 17:47:28 -08:00
|
|
|
import { IComplianceDataType } from 'wherehows-web/typings/api/list/compliance-datatypes';
|
2017-12-11 13:13:34 -08:00
|
|
|
import { fieldChangeSetRequiresReview } from 'wherehows-web/utils/datasets/compliance-policy';
|
|
|
|
import { isHighConfidenceSuggestion } from 'wherehows-web/utils/datasets/compliance-suggestions';
|
2017-12-12 17:47:28 -08:00
|
|
|
import noop from 'wherehows-web/utils/noop';
|
2017-12-11 13:13:34 -08:00
|
|
|
import { hasEnumerableKeys } from 'wherehows-web/utils/object';
|
|
|
|
|
|
|
|
export default class DatasetComplianceRow extends DatasetTableRow {
|
|
|
|
/**
|
|
|
|
* Declares the field property on a DatasetTableRow. Contains attributes for a compliance field record
|
|
|
|
* @type {IComplianceField}
|
|
|
|
* @memberof DatasetComplianceRow
|
|
|
|
*/
|
|
|
|
field: IComplianceField;
|
|
|
|
|
2017-12-12 17:47:28 -08:00
|
|
|
/**
|
|
|
|
* Reference to the compliance data types
|
|
|
|
* @type {Array<IComplianceDataType>}
|
|
|
|
*/
|
|
|
|
complianceDataTypes: Array<IComplianceDataType>;
|
|
|
|
|
2017-12-11 13:13:34 -08:00
|
|
|
/**
|
2017-12-12 14:20:19 -08:00
|
|
|
* Describes action interface for `onFieldIdentifierTypeChange` action
|
2017-12-11 13:13:34 -08:00
|
|
|
* @memberof DatasetComplianceRow
|
|
|
|
*/
|
2017-12-11 18:26:26 -08:00
|
|
|
onFieldIdentifierTypeChange: (field: IComplianceField, option: { value: ComplianceFieldIdValue }) => void;
|
2017-12-11 13:13:34 -08:00
|
|
|
|
|
|
|
/**
|
2017-12-12 14:20:19 -08:00
|
|
|
* Describes action interface for `onFieldLogicalTypeChange` action
|
2017-12-11 13:13:34 -08:00
|
|
|
* @memberof DatasetComplianceRow
|
|
|
|
*/
|
2017-12-12 17:47:28 -08:00
|
|
|
onFieldLogicalTypeChange: (field: IComplianceField, value: IComplianceField['logicalType']) => void;
|
2017-12-11 13:13:34 -08:00
|
|
|
|
|
|
|
/**
|
2017-12-12 14:20:19 -08:00
|
|
|
* Describes action interface for `onFieldClassificationChange` action
|
2017-12-11 13:13:34 -08:00
|
|
|
*
|
|
|
|
* @memberof DatasetComplianceRow
|
|
|
|
*/
|
|
|
|
onFieldClassificationChange: (field: IComplianceField, option: { value: '' | Classification }) => void;
|
|
|
|
|
|
|
|
/**
|
2017-12-12 14:20:19 -08:00
|
|
|
* Describes action interface for `onSuggestionIntent` action
|
2017-12-11 13:13:34 -08:00
|
|
|
* @memberof DatasetComplianceRow
|
|
|
|
*/
|
|
|
|
onSuggestionIntent: (field: IComplianceField, intent?: SuggestionIntent) => void;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The field identifier attribute
|
|
|
|
* @type {ComputedProperty<string>}
|
|
|
|
* @memberof DatasetComplianceRow
|
|
|
|
*/
|
|
|
|
identifierField: ComputedProperty<string> = alias('field.identifierField');
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The field's dataType attribute
|
|
|
|
* @type {ComputedProperty<string>}
|
|
|
|
* @memberof DatasetComplianceRow
|
|
|
|
*/
|
|
|
|
dataType: ComputedProperty<string> = alias('field.dataType');
|
|
|
|
|
2017-12-12 14:20:19 -08:00
|
|
|
/**
|
|
|
|
* Dropdown options for each compliance field / record
|
|
|
|
* @type {Array<IFieldIdentifierOption>}
|
|
|
|
* @memberof DatasetComplianceRow
|
|
|
|
*/
|
|
|
|
complianceFieldIdDropdownOptions: Array<IFieldIdentifierOption>;
|
|
|
|
|
2017-12-11 13:13:34 -08:00
|
|
|
/**
|
|
|
|
* Reference to the current value of the field's SuggestionIntent if present
|
|
|
|
* indicates that the provided suggestion is either accepted or ignored
|
|
|
|
* @type {(ComputedProperty<SuggestionIntent | void>)}
|
|
|
|
* @memberof DatasetComplianceRow
|
|
|
|
*/
|
|
|
|
suggestionAuthority: ComputedProperty<SuggestionIntent | void> = alias('field.suggestionAuthority');
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Maps the suggestion response, if present, to a string resolution
|
2017-12-12 14:20:19 -08:00
|
|
|
* @type ComputedProperty<string | void>
|
2017-12-11 13:13:34 -08:00
|
|
|
* @memberof DatasetComplianceRow
|
|
|
|
*/
|
|
|
|
suggestionResolution = computed('suggestionAuthority', function(this: DatasetComplianceRow): string | void {
|
|
|
|
const suggestionAuthority = get(this, 'suggestionAuthority');
|
|
|
|
|
|
|
|
if (suggestionAuthority) {
|
|
|
|
return {
|
|
|
|
[SuggestionIntent.accept]: 'Accepted',
|
|
|
|
[SuggestionIntent.ignore]: 'Discarded'
|
|
|
|
}[suggestionAuthority];
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks that the field does not have a current policy value
|
|
|
|
* @type {ComputedProperty<boolean>}
|
|
|
|
* @memberof DatasetComplianceRow
|
|
|
|
*/
|
|
|
|
isReviewRequested = computed('field.{isDirty,suggestion,privacyPolicyExists,suggestionAuthority}', function(
|
|
|
|
this: DatasetComplianceRow
|
|
|
|
): boolean {
|
|
|
|
return fieldChangeSetRequiresReview(get(this, 'field'));
|
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks if the field format drop-down should be disabled based on the type of the field
|
2017-12-12 17:47:28 -08:00
|
|
|
* *WIP
|
|
|
|
* @type {ComputedProperty<void>}
|
2017-12-11 13:13:34 -08:00
|
|
|
* @memberof DatasetComplianceRow
|
|
|
|
*/
|
2017-12-12 17:47:28 -08:00
|
|
|
isFieldFormatDisabled = computed('field.identifierType', noop).readOnly();
|
2017-12-11 13:13:34 -08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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)}
|
|
|
|
* @memberof DatasetComplianceRow
|
|
|
|
*/
|
|
|
|
getCurrentValueBeforeSuggestion(fieldProp: 'logicalType' | 'identifierType'): string | void {
|
2017-12-12 17:47:28 -08:00
|
|
|
/**
|
|
|
|
* Current value on policy prior to the suggested value
|
|
|
|
* @type {string}
|
|
|
|
*/
|
|
|
|
const value = get(get(this, 'field'), fieldProp);
|
|
|
|
|
|
|
|
if (hasEnumerableKeys(get(this, 'prediction')) && value) {
|
2017-12-12 14:20:19 -08:00
|
|
|
/**
|
|
|
|
* Field drop down options
|
|
|
|
*/
|
|
|
|
const complianceFieldIdDropdownOptions = get(this, 'complianceFieldIdDropdownOptions');
|
2017-12-11 13:13:34 -08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
|
|
|
|
return {
|
2017-12-12 14:20:19 -08:00
|
|
|
identifierType: getLabel(complianceFieldIdDropdownOptions),
|
2017-12-12 17:47:28 -08:00
|
|
|
logicalType: get(this, 'fieldFormats').find(format => format === value)
|
2017-12-11 13:13:34 -08:00
|
|
|
}[fieldProp];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a computed value for the field identifierType
|
2017-12-11 18:26:26 -08:00
|
|
|
* @type {ComputedProperty<ComplianceFieldIdValue>}
|
2017-12-11 13:13:34 -08:00
|
|
|
* @memberof DatasetComplianceRow
|
|
|
|
*/
|
2017-12-11 18:26:26 -08:00
|
|
|
identifierType = computed('field.identifierType', 'prediction', function(
|
|
|
|
this: DatasetComplianceRow
|
|
|
|
): ComplianceFieldIdValue {
|
2017-12-11 13:13:34 -08:00
|
|
|
/**
|
|
|
|
* Describes the interface for the options bag param passed into the getIdentifierType function below
|
|
|
|
* @interface IGetIdentParams
|
|
|
|
*/
|
|
|
|
interface IGetIdentParams {
|
2017-12-11 18:26:26 -08:00
|
|
|
identifierType: ComplianceFieldIdValue;
|
|
|
|
prediction: { identifierType: ComplianceFieldIdValue } | void;
|
2017-12-11 13:13:34 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
const { field: { identifierType }, prediction } = getProperties(this, ['field', 'prediction']);
|
|
|
|
/**
|
|
|
|
* Inner function takes the field.identifierType and prediction values, and
|
|
|
|
* returns the identifierType to be rendered in the ui
|
|
|
|
* @param {IGetIdentParams} params
|
2017-12-11 18:26:26 -08:00
|
|
|
* @returns {ComplianceFieldIdValue}
|
2017-12-11 13:13:34 -08:00
|
|
|
*/
|
2017-12-11 18:26:26 -08:00
|
|
|
const getIdentifierType = (params: IGetIdentParams): ComplianceFieldIdValue => {
|
2017-12-11 13:13:34 -08:00
|
|
|
const {
|
|
|
|
identifierType,
|
|
|
|
prediction: { identifierType: suggestedIdentifierType } = { identifierType: void 0 }
|
|
|
|
} = params;
|
|
|
|
return suggestedIdentifierType || identifierType;
|
|
|
|
};
|
|
|
|
|
|
|
|
return getIdentifierType({ identifierType, prediction });
|
|
|
|
}).readOnly();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the identifierType on the compliance policy before the suggested value
|
|
|
|
* @type {ComputedProperty<string>}
|
|
|
|
* @memberof DatasetComplianceRow
|
|
|
|
*/
|
|
|
|
identifierTypeBeforeSuggestion = computed('identifierType', function(): string | void {
|
|
|
|
return this.getCurrentValueBeforeSuggestion('identifierType');
|
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the logicalType on the compliance policy before the suggested value
|
|
|
|
* @type {ComputedProperty<string>}
|
|
|
|
* @memberof DatasetComplianceRow
|
|
|
|
*/
|
|
|
|
logicalTypeBeforeSuggestion = computed('logicalType', function(): string | void {
|
|
|
|
return this.getCurrentValueBeforeSuggestion('logicalType');
|
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A list of field formats that are determined based on the field identifierType
|
|
|
|
* @type ComputedProperty<Array<IFieldFormatDropdownOption> | void>
|
|
|
|
* @memberof DatasetComplianceRow
|
|
|
|
*/
|
|
|
|
fieldFormats = computed('field.identifierType', function(
|
|
|
|
this: DatasetComplianceRow
|
2017-12-12 17:47:28 -08:00
|
|
|
): IComplianceDataType['supportedFieldFormats'] {
|
2017-12-11 18:26:26 -08:00
|
|
|
const identifierType: ComplianceFieldIdValue = get(get(this, 'field'), 'identifierType');
|
2017-12-12 17:47:28 -08:00
|
|
|
const complianceDataTypes = get(this, 'complianceDataTypes');
|
|
|
|
const complianceDataType = complianceDataTypes.findBy('id', identifierType);
|
2017-12-11 13:13:34 -08:00
|
|
|
|
2017-12-12 17:47:28 -08:00
|
|
|
if (complianceDataType && complianceDataType.idType) {
|
|
|
|
return complianceDataType.supportedFieldFormats;
|
2017-12-11 13:13:34 -08:00
|
|
|
}
|
|
|
|
|
2017-12-12 17:47:28 -08:00
|
|
|
return [];
|
2017-12-11 13:13:34 -08:00
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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>)}
|
|
|
|
* @memberof DatasetComplianceRow
|
|
|
|
*/
|
2017-12-12 17:47:28 -08:00
|
|
|
logicalType = computed('field.logicalType', 'prediction', function(
|
2017-12-11 13:13:34 -08:00
|
|
|
this: DatasetComplianceRow
|
2017-12-12 17:47:28 -08:00
|
|
|
): IComplianceField['logicalType'] {
|
|
|
|
const {
|
2017-12-11 13:13:34 -08:00
|
|
|
field: { logicalType },
|
2017-12-12 17:47:28 -08:00
|
|
|
prediction: { logicalType: suggestedLogicalType } = { logicalType: null }
|
|
|
|
} = getProperties(this, ['field', 'prediction']);
|
2017-12-11 13:13:34 -08:00
|
|
|
|
2017-12-12 17:47:28 -08:00
|
|
|
return suggestedLogicalType || logicalType;
|
2017-12-11 13:13:34 -08:00
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
2017-12-12 22:19:01 -08:00
|
|
|
* The field's security classification
|
|
|
|
* Retrieves the field security classification from the compliance field if it exists, otherwise
|
|
|
|
* defaults to the default security classification for the identifier type
|
|
|
|
* in other words, the field must have a security classification if it has an identifier type
|
|
|
|
* @type {ComputedProperty<Classification | null>}
|
2017-12-11 13:13:34 -08:00
|
|
|
* @memberof DatasetComplianceRow
|
|
|
|
*/
|
2017-12-12 22:19:01 -08:00
|
|
|
classification = computed('field.classification', 'field.identifierType', 'complianceDataTypes', function(
|
2017-12-11 13:13:34 -08:00
|
|
|
this: DatasetComplianceRow
|
2017-12-12 22:19:01 -08:00
|
|
|
): Classification | null {
|
|
|
|
const { field: { identifierType, classification }, complianceDataTypes } = getProperties(this, [
|
|
|
|
'field',
|
|
|
|
'complianceDataTypes'
|
|
|
|
]);
|
2017-12-11 13:13:34 -08:00
|
|
|
|
2017-12-12 22:19:01 -08:00
|
|
|
return classification || getDefaultSecurityClassification(complianceDataTypes, identifierType);
|
2017-12-11 13:13:34 -08:00
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Extracts the field suggestions into a cached computed property, if a suggestion exists
|
2017-12-11 18:26:26 -08:00
|
|
|
* @type {(ComputedProperty<{ identifierType: ComplianceFieldIdValue; logicalType: string; confidence: number } | void>)}
|
2017-12-11 13:13:34 -08:00
|
|
|
* @memberof DatasetComplianceRow
|
|
|
|
*/
|
|
|
|
prediction = computed('field.suggestion', 'field.suggestionAuthority', function(
|
|
|
|
this: DatasetComplianceRow
|
2017-12-12 17:47:28 -08:00
|
|
|
): {
|
|
|
|
identifierType: ComplianceFieldIdValue;
|
|
|
|
logicalType: IComplianceField['logicalType'];
|
|
|
|
confidence: number;
|
|
|
|
} | void {
|
2017-12-11 13:13:34 -08:00
|
|
|
const field = getWithDefault(this, 'field', <IComplianceField>{});
|
|
|
|
// If a suggestionAuthority property exists on the field, then the user has already either accepted or ignored
|
|
|
|
// the suggestion for this field. It's value should not be taken into account on re-renders
|
|
|
|
// in place, this substitutes an empty suggestion
|
|
|
|
const { suggestion } = field.hasOwnProperty('suggestionAuthority') ? { suggestion: void 0 } : field;
|
|
|
|
|
|
|
|
if (suggestion && isHighConfidenceSuggestion(suggestion)) {
|
|
|
|
const { identifierType, logicalType, confidenceLevel: confidence } = suggestion;
|
|
|
|
|
|
|
|
return { identifierType, logicalType, confidence: +(confidence * 100).toFixed(2) };
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @memberof DatasetComplianceRow
|
|
|
|
*/
|
|
|
|
actions = {
|
|
|
|
/**
|
|
|
|
* Handles UI changes to the field identifierType
|
2017-12-11 18:26:26 -08:00
|
|
|
* @param {{ value: ComplianceFieldIdValue }} { value }
|
2017-12-11 13:13:34 -08:00
|
|
|
*/
|
2017-12-11 18:26:26 -08:00
|
|
|
onFieldIdentifierTypeChange(this: DatasetComplianceRow, { value }: { value: ComplianceFieldIdValue }) {
|
2017-12-11 13:13:34 -08:00
|
|
|
const onFieldIdentifierTypeChange = get(this, 'onFieldIdentifierTypeChange');
|
|
|
|
if (typeof onFieldIdentifierTypeChange === 'function') {
|
|
|
|
onFieldIdentifierTypeChange(get(this, 'field'), { value });
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handles the updates when the field logical type changes on this field
|
2017-12-12 17:47:28 -08:00
|
|
|
* @param {(IComplianceField['logicalType'])} value contains the selected dropdown value
|
2017-12-11 13:13:34 -08:00
|
|
|
*/
|
2017-12-12 17:47:28 -08:00
|
|
|
onFieldLogicalTypeChange(this: DatasetComplianceRow, value: IComplianceField['logicalType']) {
|
2017-12-11 13:13:34 -08:00
|
|
|
const onFieldLogicalTypeChange = get(this, 'onFieldLogicalTypeChange');
|
2017-12-12 14:20:19 -08:00
|
|
|
if (typeof onFieldLogicalTypeChange === 'function') {
|
2017-12-12 17:47:28 -08:00
|
|
|
onFieldLogicalTypeChange(get(this, 'field'), value);
|
2017-12-11 13:13:34 -08:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handles UI change to field security classification
|
|
|
|
* @param {({ value: '' | Classification })} { value } contains the changed classification value
|
|
|
|
*/
|
|
|
|
onFieldClassificationChange(this: DatasetComplianceRow, { value }: { value: '' | Classification }) {
|
|
|
|
const onFieldClassificationChange = get(this, 'onFieldClassificationChange');
|
|
|
|
if (typeof onFieldClassificationChange === 'function') {
|
|
|
|
onFieldClassificationChange(get(this, 'field'), { value });
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handler for user interactions with a suggested value. Applies / ignores the suggestion
|
|
|
|
* Then invokes the parent supplied suggestion handler
|
|
|
|
* @param {string | void} intent a binary indicator to accept or ignore suggestion
|
|
|
|
* @param {SuggestionIntent} intent
|
|
|
|
*/
|
|
|
|
onSuggestionAction(this: DatasetComplianceRow, intent?: SuggestionIntent) {
|
|
|
|
const onSuggestionIntent = get(this, 'onSuggestionIntent');
|
|
|
|
|
|
|
|
// Accept the suggestion for either identifierType and/or logicalType
|
|
|
|
if (intent === SuggestionIntent.accept) {
|
|
|
|
const { identifierType, logicalType } = get(this, 'prediction') || {
|
|
|
|
identifierType: void 0,
|
|
|
|
logicalType: void 0
|
|
|
|
};
|
|
|
|
|
|
|
|
if (identifierType) {
|
|
|
|
this.actions.onFieldIdentifierTypeChange.call(this, { value: identifierType });
|
|
|
|
}
|
|
|
|
|
|
|
|
if (logicalType) {
|
2017-12-12 17:47:28 -08:00
|
|
|
this.actions.onFieldLogicalTypeChange.call(this, logicalType);
|
2017-12-11 13:13:34 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Invokes parent handle to runtime ignore future suggesting this suggestion
|
|
|
|
if (typeof onSuggestionIntent === 'function') {
|
|
|
|
onSuggestionIntent(get(this, 'field'), intent);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|