mirror of
https://github.com/datahub-project/datahub.git
synced 2025-12-29 02:48:24 +00:00
implements advanced edit mode for compliance policy updates
This commit is contained in:
parent
ace801d1c1
commit
ce97189833
@ -67,6 +67,10 @@ import { IdLogicalType, NonIdLogicalType } from 'wherehows-web/constants/dataset
|
||||
import { pick } from 'lodash';
|
||||
import { trackableEvent, TrackableEventCategory } from 'wherehows-web/constants/analytics/event-tracking';
|
||||
import { notificationDialogActionFactory } from 'wherehows-web/utils/notifications/notifications';
|
||||
import validateMetadataObject, {
|
||||
complianceEntitiesTaxonomy
|
||||
} from 'wherehows-web/utils/datasets/compliance/metadata-schema';
|
||||
import { fleece } from 'wherehows-web/utils/object';
|
||||
|
||||
const {
|
||||
complianceDataException,
|
||||
@ -102,14 +106,74 @@ export default class DatasetCompliance extends Component {
|
||||
sortDirection: string;
|
||||
searchTerm: string;
|
||||
helpText = helpText;
|
||||
watchers: Array<{ stateChange: (fn: () => void) => void; watchItem: Element; destroy?: Function }>;
|
||||
complianceWatchers: WeakMap<Element, {}>;
|
||||
_hasBadData: boolean;
|
||||
platform: IDatasetView['platform'];
|
||||
isCompliancePolicyAvailable: boolean = false;
|
||||
showAllDatasetMemberData: boolean;
|
||||
complianceInfo: undefined | IComplianceInfo;
|
||||
|
||||
/**
|
||||
* Lists the compliance entities that are entered via the advanced edititing interface
|
||||
* @type {Pick<IComplianceInfo, 'complianceEntities'>}
|
||||
* @memberof DatasetCompliance
|
||||
*/
|
||||
manuallyEnteredComplianceEntities: Pick<IComplianceInfo, 'complianceEntities'>;
|
||||
|
||||
/**
|
||||
* Flag enabling or disabling the manual apply button
|
||||
* @type {boolean}
|
||||
* @memberof DatasetCompliance
|
||||
*/
|
||||
isManualApplyDisabled: boolean = false;
|
||||
|
||||
/**
|
||||
* Flag indicating the current compliance policy edit-view mode
|
||||
* @type {boolean}
|
||||
*/
|
||||
showGuidedComplianceEditMode: boolean = true;
|
||||
|
||||
/**
|
||||
* Formatted JSON string representing the compliance entities for this dataset
|
||||
* @type {ComputedProperty<string>}
|
||||
*/
|
||||
jsonComplianceEntities: ComputedProperty<string> = computed('complianceInfo.complianceEntities.[]', function(
|
||||
this: DatasetCompliance
|
||||
): string {
|
||||
//@ts-ignore property access path using dot notation limitation
|
||||
const entities: Array<IComplianceEntity> = get(this, 'complianceInfo.complianceEntities');
|
||||
const entitiesWithModifiableKeys = arrayMap(fleece<IComplianceEntity, 'readonly' | 'pii'>(['readonly', 'pii']))(
|
||||
entities
|
||||
);
|
||||
|
||||
return JSON.stringify(entitiesWithModifiableKeys, null, '\t');
|
||||
});
|
||||
|
||||
/**
|
||||
* Convenience computed property flag indicates if current edit step is the first step in the wizard flow
|
||||
* @type {ComputedProperty<boolean>}
|
||||
* @memberof DatasetCompliance
|
||||
*/
|
||||
isInitialEditStep = computed('editStep', 'editSteps.0.name', function(this: DatasetCompliance): boolean {
|
||||
const { editStep, editSteps } = getProperties(this, ['editStep', 'editSteps']);
|
||||
const [initialStep] = editSteps;
|
||||
return editStep.name === initialStep.name;
|
||||
});
|
||||
|
||||
/**
|
||||
* Flag indicating if the Guided vs Advanced mode should be shown for the initial edit step
|
||||
* @type {ComputedProperty<boolean>}
|
||||
* @memberof DatasetCompliance
|
||||
*/
|
||||
showAdvancedEditApplyStep = computed('isInitialEditStep', 'showGuidedComplianceEditMode', function(
|
||||
this: DatasetCompliance
|
||||
): boolean {
|
||||
const { isInitialEditStep, showGuidedComplianceEditMode } = getProperties(this, [
|
||||
'isInitialEditStep',
|
||||
'showGuidedComplianceEditMode'
|
||||
]);
|
||||
return isInitialEditStep && !showGuidedComplianceEditMode;
|
||||
});
|
||||
|
||||
/**
|
||||
* Flag indicating the readonly confirmation dialog should not be shown again for this compliance form
|
||||
* @type {boolean}
|
||||
@ -132,7 +196,10 @@ export default class DatasetCompliance extends Component {
|
||||
onReset: <T>() => Promise<T>;
|
||||
onSave: <T>() => Promise<T>;
|
||||
|
||||
onComplianceUpload: (jsonString: string) => void;
|
||||
/**
|
||||
* External action to handle manual compliance entity metadata entry
|
||||
*/
|
||||
onComplianceJsonUpdate: (jsonString: string) => Promise<void>;
|
||||
|
||||
notifyOnChangeSetSuggestions: (hasSuggestions: boolean) => void;
|
||||
notifyOnChangeSetRequiresReview: (hasChangeSetDrift: boolean) => void;
|
||||
@ -386,7 +453,7 @@ export default class DatasetCompliance extends Component {
|
||||
* Holds a reference to the current step in the compliance edit wizard flow
|
||||
* @type {{ name: string }}
|
||||
*/
|
||||
editStep: { name: string };
|
||||
editStep: { name: string } = { name: '' };
|
||||
|
||||
/**
|
||||
* A list of ui values and labels for review filter drop-down
|
||||
@ -902,6 +969,56 @@ export default class DatasetCompliance extends Component {
|
||||
}
|
||||
|
||||
actions: IDatasetComplianceActions = {
|
||||
/**
|
||||
* Toggle the visibility of the guided compliance edit view vs the advanced edit view modes
|
||||
* @param {boolean} toggle flag ,if true, show guided edit mode, otherwise, advanced
|
||||
*/
|
||||
onShowGuidedEditMode(this: DatasetCompliance, toggle: boolean): void {
|
||||
const isShowingGuidedEditMode = set(this, 'showGuidedComplianceEditMode', toggle);
|
||||
|
||||
if (!isShowingGuidedEditMode) {
|
||||
this.actions.onManualComplianceUpdate.call(this, get(this, 'jsonComplianceEntities'));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles updating the list of compliance entities when a user manually enters values
|
||||
* for the compliance entity metadata
|
||||
* @param {string} updatedEntities json string of entities
|
||||
*/
|
||||
onManualComplianceUpdate(this: DatasetCompliance, updatedEntities: string): void {
|
||||
try {
|
||||
// check if the string is parseable as a JSON object
|
||||
const entities = JSON.parse(updatedEntities);
|
||||
const metadataObject = {
|
||||
complianceEntities: entities
|
||||
};
|
||||
const isValid = validateMetadataObject(metadataObject, complianceEntitiesTaxonomy);
|
||||
|
||||
set(this, 'isManualApplyDisabled', !isValid);
|
||||
|
||||
if (isValid) {
|
||||
set(this, 'manuallyEnteredComplianceEntities', metadataObject);
|
||||
}
|
||||
} catch {
|
||||
set(this, 'isManualApplyDisabled', true);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler to apply manually entered compliance entities to the actual list of
|
||||
* compliance metadata entities to be saved
|
||||
*/
|
||||
async onApplyComplianceJson(this: DatasetCompliance) {
|
||||
try {
|
||||
await get(this, 'onComplianceJsonUpdate')(JSON.stringify(get(this, 'manuallyEnteredComplianceEntities')));
|
||||
// Proceed to next step if application of entites is successful
|
||||
this.actions.nextStep.call(this);
|
||||
} catch {
|
||||
noop();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Action handles wizard step cancellation
|
||||
*/
|
||||
@ -1254,7 +1371,7 @@ export default class DatasetCompliance extends Component {
|
||||
* @param {string} jsonString string representation for the JSON file
|
||||
*/
|
||||
onComplianceJsonUpload(this: DatasetCompliance, jsonString: string): void {
|
||||
get(this, 'onComplianceUpload')(jsonString);
|
||||
get(this, 'onComplianceJsonUpdate')(jsonString);
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@ -29,7 +29,7 @@ import {
|
||||
} from 'wherehows-web/constants';
|
||||
import { iterateArrayAsync } from 'wherehows-web/utils/array';
|
||||
import validateMetadataObject, {
|
||||
complianceMetadataTaxonomy
|
||||
complianceEntitiesTaxonomy
|
||||
} from 'wherehows-web/utils/datasets/compliance/metadata-schema';
|
||||
import { notificationDialogActionFactory } from 'wherehows-web/utils/notifications/notifications';
|
||||
|
||||
@ -347,7 +347,7 @@ export default class DatasetComplianceContainer extends Component {
|
||||
* @memberof DatasetComplianceContainer
|
||||
*/
|
||||
@action
|
||||
onComplianceUpload(this: DatasetComplianceContainer, jsonString: string): void {
|
||||
async onComplianceJsonUpdate(this: DatasetComplianceContainer, jsonString: string): Promise<void> {
|
||||
const {
|
||||
complianceInfo,
|
||||
notifications: { notify }
|
||||
@ -357,35 +357,38 @@ export default class DatasetComplianceContainer extends Component {
|
||||
* Inner function to wrap call to notify method of notification service
|
||||
* @return {void}
|
||||
*/
|
||||
const metadataInvalid = (): void =>
|
||||
const updateError = (error: string): void => {
|
||||
notify(NotificationEvent.error, {
|
||||
content: invalidPolicyData
|
||||
content: error
|
||||
});
|
||||
|
||||
throw new Error(error);
|
||||
};
|
||||
|
||||
if (complianceInfo) {
|
||||
try {
|
||||
const policy = JSON.parse(jsonString);
|
||||
const entityMetadata: Pick<IComplianceInfo, 'complianceEntities'> = JSON.parse(jsonString);
|
||||
|
||||
if (validateMetadataObject(policy, complianceMetadataTaxonomy)) {
|
||||
const { complianceEntities, datasetClassification } = policy;
|
||||
const resolvedComplianceInfo = { ...complianceInfo, complianceEntities, datasetClassification };
|
||||
const { dialogActions } = notificationDialogActionFactory();
|
||||
if (validateMetadataObject(entityMetadata, complianceEntitiesTaxonomy)) {
|
||||
const { complianceEntities } = entityMetadata;
|
||||
const resolvedComplianceInfo = { ...complianceInfo, complianceEntities };
|
||||
const { dialogActions, dismissedOrConfirmed } = notificationDialogActionFactory();
|
||||
|
||||
set(this, 'complianceInfo', resolvedComplianceInfo);
|
||||
set(this, 'complianceInfo', resolvedComplianceInfo);
|
||||
|
||||
return notify(NotificationEvent.confirm, {
|
||||
header: 'Successfully applied uploaded metadata',
|
||||
content: successUploading,
|
||||
dialogActions,
|
||||
dismissButtonText: false,
|
||||
confirmButtonText: 'Dismiss'
|
||||
});
|
||||
}
|
||||
notify(NotificationEvent.confirm, {
|
||||
header: 'Successfully applied compliance entity metadata',
|
||||
content: successUploading,
|
||||
dialogActions,
|
||||
dismissButtonText: false,
|
||||
confirmButtonText: 'Next'
|
||||
});
|
||||
|
||||
metadataInvalid();
|
||||
} catch (e) {
|
||||
metadataInvalid();
|
||||
return await dismissedOrConfirmed;
|
||||
}
|
||||
|
||||
return updateError(invalidPolicyData);
|
||||
}
|
||||
|
||||
updateError('No Compliance policy found');
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
enum ComplianceEvent {
|
||||
Cancel = 'CancelEditComplianceMetadata',
|
||||
Next = 'NextComplianceMetadataStep',
|
||||
ManualApply = 'AdvancedEditComplianceMetadataStep',
|
||||
Previous = 'PreviousComplianceMetadataStep',
|
||||
Edit = 'BeginEditComplianceMetadata',
|
||||
Download = 'DownloadComplianceMetadata',
|
||||
|
||||
@ -4,6 +4,7 @@ import { delay } from 'wherehows-web/utils/promise-delay';
|
||||
import { action } from '@ember-decorators/object';
|
||||
import { fleece } from 'wherehows-web/utils/object';
|
||||
import { notificationDialogActionFactory } from 'wherehows-web/utils/notifications/notifications';
|
||||
import noop from 'wherehows-web/utils/noop';
|
||||
|
||||
/**
|
||||
* Flag indicating the current notification queue is being processed
|
||||
@ -152,7 +153,7 @@ const notificationHandlers: INotificationHandler = {
|
||||
};
|
||||
// Set default values for button text if none are provided by consumer
|
||||
props = { dismissButtonText: 'No', confirmButtonText: 'Yes', ...props };
|
||||
const { dismissButtonText, confirmButtonText } = props;
|
||||
const { dismissButtonText, confirmButtonText, onDialogToggle } = props;
|
||||
// Removes dismiss or confirm buttons if set to false
|
||||
let resolvedProps: IConfirmOptions =
|
||||
dismissButtonText === false
|
||||
@ -162,6 +163,7 @@ const notificationHandlers: INotificationHandler = {
|
||||
confirmButtonText === false
|
||||
? <IConfirmOptions>fleece<IConfirmOptions, 'confirmButtonText'>(['confirmButtonText'])(props)
|
||||
: props;
|
||||
resolvedProps = typeof onDialogToggle === 'function' ? props : { ...props, onDialogToggle: noop };
|
||||
|
||||
return {
|
||||
props: resolvedProps,
|
||||
|
||||
@ -303,6 +303,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
.dataset-compliance-editor {
|
||||
pre {
|
||||
border-radius: item-spacing(1) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
.compliance-depends {
|
||||
display: none;
|
||||
opacity: 0;
|
||||
|
||||
@ -22,16 +22,35 @@
|
||||
|
||||
<div class="container action-bar__content">
|
||||
{{#if (has-next editStep editSteps)}}
|
||||
{{#track-ui-event category=trackableCategory.Compliance action=trackableEvent.Compliance.Next
|
||||
name=editStep.name as |metrics|}}
|
||||
|
||||
{{#if showAdvancedEditApplyStep}}
|
||||
|
||||
{{#track-ui-event category=trackableCategory.Compliance action=trackableEvent.Compliance.ManualApply
|
||||
name=editStep.name as |metrics|}}
|
||||
<button
|
||||
class="nacho-button nacho-button--large-inverse action-bar__item"
|
||||
title="Next"
|
||||
onclick={{action metrics.trackOnAction (action "nextStep")}}
|
||||
disabled={{and (gt changeSetReviewCount 0) (eq editStep.name editSteps.0.name)}}>
|
||||
Next
|
||||
title="Apply JSON"
|
||||
onclick={{action metrics.trackOnAction (action "onApplyComplianceJson")}}
|
||||
disabled={{isManualApplyDisabled}}>
|
||||
Apply
|
||||
</button>
|
||||
{{/track-ui-event}}
|
||||
{{/track-ui-event}}
|
||||
|
||||
{{else}}
|
||||
|
||||
{{#track-ui-event category=trackableCategory.Compliance action=trackableEvent.Compliance.Next
|
||||
name=editStep.name as |metrics|}}
|
||||
<button
|
||||
class="nacho-button nacho-button--large-inverse action-bar__item"
|
||||
title="Next"
|
||||
onclick={{action metrics.trackOnAction (action "nextStep")}}
|
||||
disabled={{and (gt changeSetReviewCount 0) isInitialEditStep}}>
|
||||
Next
|
||||
</button>
|
||||
{{/track-ui-event}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{else}}
|
||||
|
||||
{{#track-ui-event category=trackableCategory.Compliance
|
||||
@ -125,7 +144,7 @@
|
||||
{{/if}}
|
||||
|
||||
{{#unless schemaless}}
|
||||
{{#if (and (eq editStep.name editSteps.0.name) (not _hasBadData))}}
|
||||
{{#if (and isInitialEditStep (not _hasBadData))}}
|
||||
{{#track-ui-event category=trackableCategory.Compliance
|
||||
action=trackableEvent.Compliance.Upload as |metrics|}}
|
||||
|
||||
@ -192,7 +211,7 @@
|
||||
|
||||
{{#if schemaless}}
|
||||
|
||||
{{#if (or isReadOnly (eq editStep.name editSteps.0.name))}}
|
||||
{{#if (or isReadOnly isInitialEditStep)}}
|
||||
{{datasets/schemaless-tagging
|
||||
isEditable=(not isReadOnly)
|
||||
classification=(readonly complianceInfo.confidentiality)
|
||||
@ -204,7 +223,7 @@
|
||||
|
||||
{{else}}
|
||||
|
||||
{{#if (or isReadOnly (eq editStep.name editSteps.0.name))}}
|
||||
{{#if (or isReadOnly isInitialEditStep)}}
|
||||
{{partial "datasets/dataset-compliance/dataset-compliance-entities"}}
|
||||
{{/if}}
|
||||
|
||||
|
||||
@ -33,7 +33,7 @@
|
||||
notifyOnChangeSetRequiresReview=(action "onCompliancePolicyChangeSetDrift")
|
||||
onSave=(action "savePrivacyCompliancePolicy")
|
||||
onReset=(action "resetPrivacyCompliancePolicy")
|
||||
onComplianceUpload=(action "onComplianceUpload")
|
||||
onComplianceJsonUpdate=(action "onComplianceJsonUpdate")
|
||||
}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
@ -15,36 +15,63 @@
|
||||
</section>
|
||||
|
||||
<section class="compliance-entities-meta">
|
||||
{{ember-selector
|
||||
values=fieldReviewOptions
|
||||
selected=(readonly fieldReviewOption)
|
||||
selectionDidChange=(action "onFieldReviewChange")
|
||||
}}
|
||||
<button
|
||||
class="nacho-button nacho-button{{if showGuidedComplianceEditMode '--inverse' '--secondary'}}"
|
||||
onclick={{action "onShowGuidedEditMode" true}}>
|
||||
{{tooltip-on-element
|
||||
text="Show Guided View"
|
||||
}}
|
||||
|
||||
{{#if changeSetReviewCount}}
|
||||
<span class="dataset-compliance-fields__has-suggestions">
|
||||
{{fa-icon "table" aria-label="Show Guided View"}}
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="nacho-button nacho-button{{if showGuidedComplianceEditMode '--secondary' '--inverse'}}"
|
||||
onclick={{action "onShowGuidedEditMode" false}}>
|
||||
{{tooltip-on-element
|
||||
text="Show Advanced View"
|
||||
}}
|
||||
|
||||
{{fa-icon "code" aria-label="Show Advanced View"}}
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<section class="compliance-entities-meta">
|
||||
{{#if showGuidedComplianceEditMode}}
|
||||
{{ember-selector
|
||||
values=fieldReviewOptions
|
||||
selected=(readonly fieldReviewOption)
|
||||
selectionDidChange=(action "onFieldReviewChange")
|
||||
}}
|
||||
|
||||
{{#if changeSetReviewCount}}
|
||||
<span class="dataset-compliance-fields__has-suggestions">
|
||||
{{changeSetReviewCount}} fields to be reviewed
|
||||
</span>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if (and isEditing unspecifiedTags)}}
|
||||
{{#if isEditing}}
|
||||
|
||||
<div class="compliance-entities-meta__secondary">
|
||||
<p class="set-fields-to-none-text">Set all unspecified field types to {{ComplianceFieldIdValue.None}}</p>
|
||||
{{#if (and unspecifiedTags showGuidedComplianceEditMode)}}
|
||||
<p class="set-fields-to-none-text">Set all unspecified field types to {{ComplianceFieldIdValue.None}}</p>
|
||||
|
||||
{{#track-ui-event category=trackableCategory.Compliance
|
||||
action=trackableEvent.Compliance.SetUnspecifiedAsNone as |metrics|}}
|
||||
<button
|
||||
class="nacho-button nacho-button--large nacho-button--secondary action-bar__item"
|
||||
onclick={{action metrics.trackOnAction (perform setUnspecifiedTagsAsNoneTask)}}>
|
||||
{{#track-ui-event category=trackableCategory.Compliance
|
||||
action=trackableEvent.Compliance.SetUnspecifiedAsNone as |metrics|}}
|
||||
<button
|
||||
class="nacho-button nacho-button--large nacho-button--secondary action-bar__item"
|
||||
onclick={{action metrics.trackOnAction (perform setUnspecifiedTagsAsNoneTask)}}>
|
||||
|
||||
{{#if setUnspecifiedTagsAsNoneTask.isRunning}}
|
||||
{{pendulum-ellipsis-animation}}
|
||||
{{else}}
|
||||
Set {{pluralize unspecifiedTags.length "tag"}} to {{ComplianceFieldIdValue.None}}
|
||||
{{/if}}
|
||||
{{#if setUnspecifiedTagsAsNoneTask.isRunning}}
|
||||
{{pendulum-ellipsis-animation}}
|
||||
{{else}}
|
||||
Set {{pluralize unspecifiedTags.length "tag"}} to {{ComplianceFieldIdValue.None}}
|
||||
{{/if}}
|
||||
|
||||
</button>
|
||||
{{/track-ui-event}}
|
||||
</button>
|
||||
{{/track-ui-event}}
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
@ -62,381 +89,400 @@
|
||||
{{/if}}
|
||||
</section>
|
||||
|
||||
{{#if foldedChangeSet.length}}
|
||||
{{#dataset-table
|
||||
class="dataset-compliance-fields"
|
||||
fields=foldedChangeSet
|
||||
filterBy=filterBy
|
||||
tableRowComponent='dataset-compliance-rollup-row'
|
||||
searchTerm=searchTerm as |table|
|
||||
}}
|
||||
{{#if showGuidedComplianceEditMode}}
|
||||
{{#if foldedChangeSet.length}}
|
||||
{{#dataset-table
|
||||
class="dataset-compliance-fields"
|
||||
fields=foldedChangeSet
|
||||
filterBy=filterBy
|
||||
tableRowComponent='dataset-compliance-rollup-row'
|
||||
searchTerm=searchTerm as |table|
|
||||
}}
|
||||
|
||||
{{#table.head as |head|}}
|
||||
{{#head.column class="dataset-compliance-fields__notification-column"}}{{/head.column}}
|
||||
{{#head.column class="dataset-compliance-fields__identifier-column"}}
|
||||
Field
|
||||
{{#table.head as |head|}}
|
||||
{{#head.column class="dataset-compliance-fields__notification-column"}}{{/head.column}}
|
||||
{{#head.column class="dataset-compliance-fields__identifier-column"}}
|
||||
Field
|
||||
|
||||
{{more-info
|
||||
link="http://go/tms-schema"
|
||||
tooltip="Click for more information on Schema"
|
||||
}}
|
||||
{{/head.column}}
|
||||
{{#head.column class="nacho-table-cell-wrapped"}}Compliance Information{{/head.column}}
|
||||
{{#head.column class="nacho-table-cell-wrapped"}}
|
||||
System Suggestion & Confidence
|
||||
{{/head.column}}
|
||||
{{/table.head}}
|
||||
|
||||
<tr>
|
||||
<th>{{!--spacer--}}</th>
|
||||
<th colspan="3">
|
||||
<div class="dataset-compliance-fields__actions">
|
||||
{{disable-bubble-input
|
||||
title="Search field names"
|
||||
placeholder="Search field names"
|
||||
value=table.searchTerm
|
||||
class="dataset-compliance-fields__search"
|
||||
on-input=(action table.filterDidChange value="target.value")
|
||||
{{more-info
|
||||
link="http://go/tms-schema"
|
||||
tooltip="Click for more information on Schema"
|
||||
}}
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
{{/head.column}}
|
||||
{{#head.column class="nacho-table-cell-wrapped"}}Compliance Information{{/head.column}}
|
||||
{{#head.column class="nacho-table-cell-wrapped"}}
|
||||
System Suggestion & Confidence
|
||||
{{/head.column}}
|
||||
{{/table.head}}
|
||||
|
||||
{{#table.body as |body|}}
|
||||
{{#each table.data as |field|}}
|
||||
{{#body.row
|
||||
field=field
|
||||
isNewComplianceInfo=isNewComplianceInfo
|
||||
complianceDataTypes=complianceDataTypes
|
||||
onFieldDblClick=(action "onFieldDblClick")
|
||||
onFieldTagAdded=(action "onFieldTagAdded")
|
||||
onFieldTagRemoved=(action "onFieldTagRemoved")
|
||||
onTagReadOnlyDisable=(action "onTagReadOnlyDisable")
|
||||
onTagIdentifierTypeChange=(action "tagIdentifierChanged")
|
||||
onSuggestionIntent=(action "onFieldSuggestionIntentChange") as |row|
|
||||
}}
|
||||
<tr class="{{if row.isReadonly 'dataset-compliance-fields--readonly'}}" ondblclick={{action
|
||||
row.onFragmentDblClick}}>
|
||||
{{#row.cell}}
|
||||
{{#if
|
||||
(and row.suggestion (and (not row.suggestionMatchesCurrentValue) (not row.suggestionResolution)))}}
|
||||
<tr>
|
||||
<th>{{!--spacer--}}</th>
|
||||
<th colspan="3">
|
||||
<div class="dataset-compliance-fields__actions">
|
||||
{{disable-bubble-input
|
||||
title="Search field names"
|
||||
placeholder="Search field names"
|
||||
value=table.searchTerm
|
||||
class="dataset-compliance-fields__search"
|
||||
on-input=(action table.filterDidChange value="target.value")
|
||||
}}
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
|
||||
<span class="nacho-tooltip" title="Has suggestions">
|
||||
{{#table.body as |body|}}
|
||||
{{#each table.data as |field|}}
|
||||
{{#body.row
|
||||
field=field
|
||||
isNewComplianceInfo=isNewComplianceInfo
|
||||
complianceDataTypes=complianceDataTypes
|
||||
onFieldDblClick=(action "onFieldDblClick")
|
||||
onFieldTagAdded=(action "onFieldTagAdded")
|
||||
onFieldTagRemoved=(action "onFieldTagRemoved")
|
||||
onTagReadOnlyDisable=(action "onTagReadOnlyDisable")
|
||||
onTagIdentifierTypeChange=(action "tagIdentifierChanged")
|
||||
onSuggestionIntent=(action "onFieldSuggestionIntentChange") as |row|
|
||||
}}
|
||||
<tr class="{{if row.isReadonly 'dataset-compliance-fields--readonly'}}" ondblclick={{action
|
||||
row.onFragmentDblClick}}>
|
||||
{{#row.cell}}
|
||||
{{#if
|
||||
(and row.suggestion (and (not row.suggestionMatchesCurrentValue) (not row.suggestionResolution)))}}
|
||||
|
||||
<span class="nacho-tooltip" title="Has suggestions">
|
||||
<i class="fa fa-exclamation dataset-compliance-fields__has-suggestions__icon"
|
||||
title="Compliance field has suggested values"></i>
|
||||
</span>
|
||||
|
||||
{{else}}
|
||||
{{else}}
|
||||
|
||||
{{#if row.isReviewRequested}}
|
||||
{{#if row.isReviewRequested}}
|
||||
|
||||
<span class="nacho-tooltip" title="Please review">
|
||||
<span class="nacho-tooltip" title="Please review">
|
||||
<i class="fa fa-question dataset-compliance-fields--review-required__icon"
|
||||
title="Compliance policy information needs review"></i>
|
||||
</span>
|
||||
|
||||
{{else}}
|
||||
{{else}}
|
||||
|
||||
<span class="nacho-tooltip" title="All good!">
|
||||
<span class="nacho-tooltip" title="All good!">
|
||||
<i class="fa fa-check dataset-compliance-fields--ok__icon" title="All good!"></i>
|
||||
</span>
|
||||
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{/if}}
|
||||
{{/row.cell}}
|
||||
|
||||
{{/if}}
|
||||
{{/row.cell}}
|
||||
{{#row.cell}}
|
||||
<div class="dataset-compliance-fields__id-field-wrap">
|
||||
<div title="{{row.identifierField}}">
|
||||
<strong>
|
||||
{{if isShowingFullFieldNames row.identifierField (split-text row.identifierField 23)}}
|
||||
</strong>
|
||||
</div>
|
||||
|
||||
{{#row.cell}}
|
||||
<div class="dataset-compliance-fields__id-field-wrap">
|
||||
<div title="{{row.identifierField}}">
|
||||
<strong>
|
||||
{{if isShowingFullFieldNames row.identifierField (split-text row.identifierField 23)}}
|
||||
</strong>
|
||||
<div title="{{row.dataType}}">
|
||||
{{if isShowingFullFieldNames row.dataType (split-text row.dataType 23)}}
|
||||
</div>
|
||||
</div>
|
||||
{{/row.cell}}
|
||||
|
||||
<div title="{{row.dataType}}">
|
||||
{{if isShowingFullFieldNames row.dataType (split-text row.dataType 23)}}
|
||||
</div>
|
||||
</div>
|
||||
{{/row.cell}}
|
||||
{{#row.cell}}
|
||||
{{#if (and isEditing (not row.isReadonly))}}
|
||||
{{#each row.fieldChangeSet as |tag|}}
|
||||
|
||||
{{#row.cell}}
|
||||
{{#if (and isEditing (not row.isReadonly))}}
|
||||
{{#each row.fieldChangeSet as |tag|}}
|
||||
{{#basic-dropdown as |tagDrop|}}
|
||||
{{#tagDrop.trigger
|
||||
class="dataset-compliance-fields__tag-info dataset-compliance-fields__tag-info--editable"}}
|
||||
|
||||
{{#basic-dropdown as |tagDrop|}}
|
||||
{{#tagDrop.trigger
|
||||
class="dataset-compliance-fields__tag-info dataset-compliance-fields__tag-info--editable"}}
|
||||
|
||||
<p class="dataset-compliance-fields__tag-info__text">
|
||||
{{#if tag.identifierType}}
|
||||
{{tag.identifierType}}{{if tag.logicalType (concat ", " tag.logicalType)}}
|
||||
{{else}}
|
||||
<span
|
||||
class="dataset-compliance-fields__tag-info__text dataset-compliance-fields__tag-info__text--obscure">
|
||||
<p class="dataset-compliance-fields__tag-info__text">
|
||||
{{#if tag.identifierType}}
|
||||
{{tag.identifierType}}{{if tag.logicalType (concat ", " tag.logicalType)}}
|
||||
{{else}}
|
||||
<span
|
||||
class="dataset-compliance-fields__tag-info__text dataset-compliance-fields__tag-info__text--obscure">
|
||||
Select Field Type ...
|
||||
</span>
|
||||
{{/if}}
|
||||
</p>
|
||||
{{/if}}
|
||||
</p>
|
||||
|
||||
{{/tagDrop.trigger}}
|
||||
{{/tagDrop.trigger}}
|
||||
|
||||
{{#tagDrop.content overlay=true class="dataset-compliance-fields__guided-modal"}}
|
||||
{{#dataset-compliance-field-tag
|
||||
tag=tag
|
||||
parentHasSingleTag=row.hasSingleTag
|
||||
onTagIdentifierTypeChange=(action "tagIdentifierChanged")
|
||||
onTagLogicalTypeChange=(action "tagLogicalTypeChanged")
|
||||
onTagValuePatternChange=(action "tagValuePatternChanged")
|
||||
onTagOwnerChange=(action "tagOwnerChanged")
|
||||
complianceFieldIdDropdownOptions=complianceFieldIdDropdownOptions
|
||||
complianceDataTypes=complianceDataTypes as |tagRowComponent|
|
||||
}}
|
||||
|
||||
<section class="dataset-compliance-fields__compliance-info-column">
|
||||
<header class="dataset-compliance-fields__compliance-info-column__title">
|
||||
<strong>Select field type</strong>
|
||||
</header>
|
||||
|
||||
<div class="dataset-compliance-fields__compliance-info-column__content">
|
||||
{{#track-ui-event category=trackableCategory.Compliance
|
||||
action=trackableEvent.Compliance.FieldIndentifier
|
||||
name=tag.identifierType as |metrics|}}
|
||||
|
||||
{{#each tagRowComponent.tagIdOptions as |tagOption|}}
|
||||
{{#radio-button-composer
|
||||
value=tagOption.value
|
||||
name="datasetFieldClassification"
|
||||
groupValue=(readonly tag.identifierType)
|
||||
class="dataset-compliance-fields__tag-radio"
|
||||
disabled=tagOption.isDisabled
|
||||
disabledClass="dataset-compliance-fields__tag-radio--disabled"
|
||||
checkedClass="dataset-compliance-fields__tag-radio--checked"
|
||||
onMouseEnter=(action tagRowComponent.onFieldTagIdentifierEnter)
|
||||
onMouseLeave=(action tagRowComponent.onFieldTagIdentifierLeave)
|
||||
changed=(action metrics.trackOnAction (action tagRowComponent.tagIdentifierTypeDidChange))}}
|
||||
{{tagOption.label}}
|
||||
{{/radio-button-composer}}
|
||||
{{/each}}
|
||||
|
||||
{{/track-ui-event}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{#if tagRowComponent.quickDesc}}
|
||||
{{#tagDrop.content overlay=true class="dataset-compliance-fields__guided-modal"}}
|
||||
{{#dataset-compliance-field-tag
|
||||
tag=tag
|
||||
parentHasSingleTag=row.hasSingleTag
|
||||
onTagIdentifierTypeChange=(action "tagIdentifierChanged")
|
||||
onTagLogicalTypeChange=(action "tagLogicalTypeChanged")
|
||||
onTagValuePatternChange=(action "tagValuePatternChanged")
|
||||
onTagOwnerChange=(action "tagOwnerChanged")
|
||||
complianceFieldIdDropdownOptions=complianceFieldIdDropdownOptions
|
||||
complianceDataTypes=complianceDataTypes as |tagRowComponent|
|
||||
}}
|
||||
|
||||
<section class="dataset-compliance-fields__compliance-info-column">
|
||||
<div class="dataset-compliance-fields__field-tag__quick-desc">
|
||||
<strong>
|
||||
{{tagRowComponent.quickDesc.title}}:
|
||||
</strong>
|
||||
<header class="dataset-compliance-fields__compliance-info-column__title">
|
||||
<strong>Select field type</strong>
|
||||
</header>
|
||||
|
||||
<p>
|
||||
{{tagRowComponent.quickDesc.description}}
|
||||
</p>
|
||||
<div class="dataset-compliance-fields__compliance-info-column__content">
|
||||
{{#track-ui-event category=trackableCategory.Compliance
|
||||
action=trackableEvent.Compliance.FieldIndentifier
|
||||
name=tag.identifierType as |metrics|}}
|
||||
|
||||
{{#each tagRowComponent.tagIdOptions as |tagOption|}}
|
||||
{{#radio-button-composer
|
||||
value=tagOption.value
|
||||
name="datasetFieldClassification"
|
||||
groupValue=(readonly tag.identifierType)
|
||||
class="dataset-compliance-fields__tag-radio"
|
||||
disabled=tagOption.isDisabled
|
||||
disabledClass="dataset-compliance-fields__tag-radio--disabled"
|
||||
checkedClass="dataset-compliance-fields__tag-radio--checked"
|
||||
onMouseEnter=(action tagRowComponent.onFieldTagIdentifierEnter)
|
||||
onMouseLeave=(action tagRowComponent.onFieldTagIdentifierLeave)
|
||||
changed=(action metrics.trackOnAction (action tagRowComponent.tagIdentifierTypeDidChange))}}
|
||||
{{tagOption.label}}
|
||||
{{/radio-button-composer}}
|
||||
{{/each}}
|
||||
|
||||
{{/track-ui-event}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{else}}
|
||||
{{#if tagRowComponent.quickDesc}}
|
||||
|
||||
{{#if tagRowComponent.isIdType}}
|
||||
<section class="dataset-compliance-fields__compliance-info-column">
|
||||
<header class="dataset-compliance-fields__compliance-info-column__title">
|
||||
<strong>Select field format</strong>
|
||||
</header>
|
||||
<div class="dataset-compliance-fields__field-tag__quick-desc">
|
||||
<strong>
|
||||
{{tagRowComponent.quickDesc.title}}:
|
||||
</strong>
|
||||
|
||||
<div class="dataset-compliance-fields__compliance-info-column__content">
|
||||
{{#track-ui-event category=trackableCategory.Compliance
|
||||
action=trackableEvent.Compliance.FieldFormat
|
||||
name=tag.logicalType as |metrics|}}
|
||||
|
||||
{{#each tagRowComponent.fieldFormats as |fieldFormat|}}
|
||||
{{#radio-button-composer
|
||||
value=fieldFormat.value
|
||||
name="datasetFieldFieldFormat"
|
||||
groupValue=(readonly tag.logicalType)
|
||||
class="dataset-compliance-fields__tag-radio"
|
||||
checkedClass="dataset-compliance-fields__tag-radio--checked"
|
||||
changed=(action metrics.trackOnAction (action tagRowComponent.tagLogicalTypeDidChange))}}
|
||||
{{fieldFormat.label}}
|
||||
{{/radio-button-composer}}
|
||||
{{/each}}
|
||||
|
||||
{{/track-ui-event}}
|
||||
<p>
|
||||
{{tagRowComponent.quickDesc.description}}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{#if tagRowComponent.showCustomInput}}
|
||||
{{else}}
|
||||
|
||||
{{#if tagRowComponent.isIdType}}
|
||||
<section class="dataset-compliance-fields__compliance-info-column">
|
||||
<header class="dataset-compliance-fields__compliance-info-column__title">
|
||||
<strong>Custom RegEx</strong>
|
||||
</header>
|
||||
|
||||
<div
|
||||
class="dataset-compliance-fields__compliance-info-column__content">
|
||||
|
||||
<div class="dataset-compliance-fields__text-pattern-wrap">
|
||||
<div class="dataset-compliance-fields__text-pattern-wrap--input">
|
||||
<input
|
||||
placeholder="Enter regex"
|
||||
value="{{readonly tag.valuePattern}}"
|
||||
disabled={{or (not isEditing) row.isReadonly}}
|
||||
class="dataset-compliance-fields__text-pattern {{if
|
||||
tagRowComponent.valuePatternError
|
||||
'dataset-compliance-fields--missing-selection'}}"
|
||||
oninput={{action tagRowComponent.tagValuePatternDidChange value="target.value"}}
|
||||
>
|
||||
</div>
|
||||
|
||||
{{more-info
|
||||
link="http://go/metadata-custom-regex"
|
||||
tooltip="Click for more information on RegExp format"
|
||||
}}
|
||||
|
||||
{{#if tagRowComponent.valuePatternError}}
|
||||
<div class="dataset-compliance-fields__text-pattern-wrap--error">
|
||||
{{tagRowComponent.valuePatternError}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{{/if}}
|
||||
|
||||
{{#unless tagRowComponent.isTagFormatMissing}}
|
||||
<section class="dataset-compliance-fields__compliance-info-column">
|
||||
<header class="dataset-compliance-fields__compliance-info-column__title">
|
||||
<strong>Has Ownership?</strong>
|
||||
<strong>Select field format</strong>
|
||||
</header>
|
||||
|
||||
<div class="dataset-compliance-fields__compliance-info-column__content">
|
||||
{{#track-ui-event category=trackableCategory.Compliance
|
||||
action=trackableEvent.Compliance.FieldFormat
|
||||
name=tag.logicalType as |metrics|}}
|
||||
|
||||
{{#radio-button-composer
|
||||
value=false
|
||||
name="datasetFieldOwnerToggle"
|
||||
groupValue=(readonly tag.nonOwner)
|
||||
class="dataset-compliance-fields__tag-radio"
|
||||
checkedClass="dataset-compliance-fields__tag-radio--checked"
|
||||
changed=(action tagRowComponent.tagOwnerDidChange)}}
|
||||
Yes
|
||||
{{/radio-button-composer}}
|
||||
{{#each tagRowComponent.fieldFormats as |fieldFormat|}}
|
||||
{{#radio-button-composer
|
||||
value=fieldFormat.value
|
||||
name="datasetFieldFieldFormat"
|
||||
groupValue=(readonly tag.logicalType)
|
||||
class="dataset-compliance-fields__tag-radio"
|
||||
checkedClass="dataset-compliance-fields__tag-radio--checked"
|
||||
changed=(action metrics.trackOnAction (action tagRowComponent.tagLogicalTypeDidChange))}}
|
||||
{{fieldFormat.label}}
|
||||
{{/radio-button-composer}}
|
||||
{{/each}}
|
||||
|
||||
{{#radio-button-composer
|
||||
value=true
|
||||
name="datasetFieldOwnerToggle"
|
||||
groupValue=(readonly tag.nonOwner)
|
||||
class="dataset-compliance-fields__tag-radio"
|
||||
checkedClass="dataset-compliance-fields__tag-radio--checked"
|
||||
changed=(action tagRowComponent.tagOwnerDidChange)}}
|
||||
No
|
||||
{{/radio-button-composer}}
|
||||
{{/track-ui-event}}
|
||||
</div>
|
||||
</section>
|
||||
{{/unless}}
|
||||
|
||||
{{#if tagRowComponent.showCustomInput}}
|
||||
<section class="dataset-compliance-fields__compliance-info-column">
|
||||
<header class="dataset-compliance-fields__compliance-info-column__title">
|
||||
<strong>Custom RegEx</strong>
|
||||
</header>
|
||||
|
||||
<div
|
||||
class="dataset-compliance-fields__compliance-info-column__content">
|
||||
|
||||
<div class="dataset-compliance-fields__text-pattern-wrap">
|
||||
<div class="dataset-compliance-fields__text-pattern-wrap--input">
|
||||
<input
|
||||
placeholder="Enter regex"
|
||||
value="{{readonly tag.valuePattern}}"
|
||||
disabled={{or (not isEditing) row.isReadonly}}
|
||||
class="dataset-compliance-fields__text-pattern {{if
|
||||
tagRowComponent.valuePatternError
|
||||
'dataset-compliance-fields--missing-selection'}}"
|
||||
oninput={{action tagRowComponent.tagValuePatternDidChange value="target.value"}}
|
||||
>
|
||||
</div>
|
||||
|
||||
{{more-info
|
||||
link="http://go/metadata-custom-regex"
|
||||
tooltip="Click for more information on RegExp format"
|
||||
}}
|
||||
|
||||
{{#if tagRowComponent.valuePatternError}}
|
||||
<div class="dataset-compliance-fields__text-pattern-wrap--error">
|
||||
{{tagRowComponent.valuePatternError}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{{/if}}
|
||||
|
||||
{{#unless tagRowComponent.isTagFormatMissing}}
|
||||
<section class="dataset-compliance-fields__compliance-info-column">
|
||||
<header class="dataset-compliance-fields__compliance-info-column__title">
|
||||
<strong>Has Ownership?</strong>
|
||||
</header>
|
||||
|
||||
<div class="dataset-compliance-fields__compliance-info-column__content">
|
||||
|
||||
{{#radio-button-composer
|
||||
value=false
|
||||
name="datasetFieldOwnerToggle"
|
||||
groupValue=(readonly tag.nonOwner)
|
||||
class="dataset-compliance-fields__tag-radio"
|
||||
checkedClass="dataset-compliance-fields__tag-radio--checked"
|
||||
changed=(action tagRowComponent.tagOwnerDidChange)}}
|
||||
Yes
|
||||
{{/radio-button-composer}}
|
||||
|
||||
{{#radio-button-composer
|
||||
value=true
|
||||
name="datasetFieldOwnerToggle"
|
||||
groupValue=(readonly tag.nonOwner)
|
||||
class="dataset-compliance-fields__tag-radio"
|
||||
checkedClass="dataset-compliance-fields__tag-radio--checked"
|
||||
changed=(action tagRowComponent.tagOwnerDidChange)}}
|
||||
No
|
||||
{{/radio-button-composer}}
|
||||
</div>
|
||||
</section>
|
||||
{{/unless}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{/dataset-compliance-field-tag}}
|
||||
{{/tagDrop.content}}
|
||||
{{/basic-dropdown}}
|
||||
{{/dataset-compliance-field-tag}}
|
||||
{{/tagDrop.content}}
|
||||
{{/basic-dropdown}}
|
||||
|
||||
{{#if (and isEditing (not row.hasSingleTag))}}
|
||||
<span class="nacho-tooltip" title="Delete Tag">
|
||||
{{#if (and isEditing (not row.hasSingleTag))}}
|
||||
<span class="nacho-tooltip" title="Delete Tag">
|
||||
<button class="nacho-button nacho-button--tertiary dataset-compliance-fields__remove-tag"
|
||||
onclick={{action row.onRemoveFieldTag tag}}>
|
||||
{{fa-icon "trash"}}
|
||||
</button>
|
||||
</span>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
|
||||
{{#unless row.hasNoneTag}}
|
||||
<br>
|
||||
<button
|
||||
class="nacho-button nacho-button--tertiary dataset-compliance-fields__add-field"
|
||||
onclick={{action row.onAddFieldTag}}>
|
||||
+ Add new
|
||||
</button>
|
||||
{{/unless}}
|
||||
{{#unless row.hasNoneTag}}
|
||||
<br>
|
||||
<button
|
||||
class="nacho-button nacho-button--tertiary dataset-compliance-fields__add-field"
|
||||
onclick={{action row.onAddFieldTag}}>
|
||||
+ Add new
|
||||
</button>
|
||||
{{/unless}}
|
||||
|
||||
{{else}}
|
||||
{{#each row.fieldChangeSet as |tag|}}
|
||||
{{else}}
|
||||
{{#each row.fieldChangeSet as |tag|}}
|
||||
|
||||
<div class="dataset-compliance-fields__tag-info">
|
||||
{{tag.identifierType}}{{if tag.logicalType (concat ", " tag.logicalType)}}
|
||||
<div class="dataset-compliance-fields__tag-info">
|
||||
{{tag.identifierType}}{{if tag.logicalType (concat ", " tag.logicalType)}}
|
||||
|
||||
{{#if row.isReadonly}}
|
||||
<span class="nacho-tooltip" title="Readonly">
|
||||
{{#if row.isReadonly}}
|
||||
<span class="nacho-tooltip" title="Readonly">
|
||||
<button class="nacho-button nacho-button--tertiary dataset-compliance-fields--readonly__icon"
|
||||
disabled={{not isEditing}}
|
||||
onclick={{action row.onEditReadonlyTag tag}}>
|
||||
onclick={{action row.onEditReadonlyTag tag}}>
|
||||
{{fa-icon "lock"}}
|
||||
</button>
|
||||
</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
{{/row.cell}}
|
||||
|
||||
{{#row.cell}}
|
||||
<div class="dataset-compliance-fields__suggested-values">
|
||||
{{#if row.suggestion}}
|
||||
<span class="dataset-compliance-fields__suggested-value {{unless row.suggestionResolution
|
||||
'dataset-compliance-fields__suggested-value--no-res'}}">
|
||||
{{row.suggestion.identifierType}}
|
||||
</span>
|
||||
|
||||
<span class="dataset-compliance-fields__suggested-value {{unless row.suggestionResolution
|
||||
'dataset-compliance-fields__suggested-value--no-res'}}">
|
||||
{{row.suggestion.logicalType}}
|
||||
</span>
|
||||
|
||||
{{row.suggestion.confidence}}%
|
||||
|
||||
{{#if isEditing}}
|
||||
{{#if row.suggestionResolution}}
|
||||
<div class="dataset-compliance-fields__resolution {{if (eq row.suggestionResolution 'Accepted')
|
||||
'dataset-compliance-fields__resolution--ok'}}">
|
||||
{{row.suggestionResolution}}
|
||||
</div>
|
||||
{{else}}
|
||||
|
||||
{{auto-suggest-action
|
||||
type="accept"
|
||||
field=row.fieldProps
|
||||
feedbackAction=(action notifyOnComplianceSuggestionFeedback)
|
||||
onSuggestionClick=(action row.onSuggestionClick)
|
||||
}}
|
||||
|
||||
{{auto-suggest-action
|
||||
field=row.fieldProps
|
||||
feedbackAction=(action notifyOnComplianceSuggestionFeedback)
|
||||
onSuggestionClick=(action row.onSuggestionClick)
|
||||
}}
|
||||
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{else}}
|
||||
—
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/row.cell}}
|
||||
</tr>
|
||||
{{/row.cell}}
|
||||
|
||||
{{/body.row}}
|
||||
{{/each}}
|
||||
{{/table.body}}
|
||||
{{#row.cell}}
|
||||
<div class="dataset-compliance-fields__suggested-values">
|
||||
{{#if row.suggestion}}
|
||||
<span class="dataset-compliance-fields__suggested-value {{unless row.suggestionResolution
|
||||
'dataset-compliance-fields__suggested-value--no-res'}}">
|
||||
{{row.suggestion.identifierType}}
|
||||
</span>
|
||||
|
||||
{{/dataset-table}}
|
||||
<span class="dataset-compliance-fields__suggested-value {{unless row.suggestionResolution
|
||||
'dataset-compliance-fields__suggested-value--no-res'}}">
|
||||
{{row.suggestion.logicalType}}
|
||||
</span>
|
||||
|
||||
{{row.suggestion.confidence}}%
|
||||
|
||||
{{#if isEditing}}
|
||||
{{#if row.suggestionResolution}}
|
||||
<div class="dataset-compliance-fields__resolution {{if (eq row.suggestionResolution 'Accepted')
|
||||
'dataset-compliance-fields__resolution--ok'}}">
|
||||
{{row.suggestionResolution}}
|
||||
</div>
|
||||
{{else}}
|
||||
|
||||
{{auto-suggest-action
|
||||
type="accept"
|
||||
field=row.fieldProps
|
||||
feedbackAction=(action notifyOnComplianceSuggestionFeedback)
|
||||
onSuggestionClick=(action row.onSuggestionClick)
|
||||
}}
|
||||
|
||||
{{auto-suggest-action
|
||||
field=row.fieldProps
|
||||
feedbackAction=(action notifyOnComplianceSuggestionFeedback)
|
||||
onSuggestionClick=(action row.onSuggestionClick)
|
||||
}}
|
||||
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{else}}
|
||||
—
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/row.cell}}
|
||||
</tr>
|
||||
|
||||
{{/body.row}}
|
||||
{{/each}}
|
||||
{{/table.body}}
|
||||
|
||||
{{/dataset-table}}
|
||||
|
||||
{{else}}
|
||||
|
||||
{{empty-state
|
||||
heading="No fields found"
|
||||
subHead="If you have a filter applied, setting this to the least restrictive option may yield more results."
|
||||
}}
|
||||
|
||||
{{/if}}
|
||||
{{else}}
|
||||
|
||||
{{empty-state
|
||||
heading="No fields found"
|
||||
subHead="If you have a filter applied, setting this to the least restrictive option may yield more results."
|
||||
}}
|
||||
{{ember-ace
|
||||
readOnly=isReadOnly
|
||||
update=(action "onManualComplianceUpdate")
|
||||
class="dataset-compliance-editor"
|
||||
value=jsonComplianceEntities
|
||||
enableAutocompletion=true
|
||||
enableLiveAutocompletion=true
|
||||
minLines=10
|
||||
maxLines=50
|
||||
showLineNumbers=true
|
||||
useWrapMode=true
|
||||
mode="ace/mode/json"
|
||||
worker="ace/mode/json_worker"
|
||||
theme="ace/theme/github"}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
@ -31,8 +31,10 @@ export interface IComplianceEntity {
|
||||
// Flag indicating that this compliance field is not editable by the end user
|
||||
// field should also be filtered from persisted policy
|
||||
readonly readonly?: boolean;
|
||||
//Optional attribute for the value of a CUSTOM regex. Required for CUSTOM field format
|
||||
// Optional attribute for the value of a CUSTOM regex. Required for CUSTOM field format
|
||||
valuePattern?: string | null;
|
||||
// Flags this entity as containing pii data
|
||||
pii?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -31,6 +31,7 @@ type IComplianceEntityWithMetadata = Pick<
|
||||
| 'nonOwner'
|
||||
| 'readonly'
|
||||
| 'valuePattern'
|
||||
| 'pii'
|
||||
> & {
|
||||
// flag indicating that the field has a current policy upstream
|
||||
privacyPolicyExists: boolean;
|
||||
|
||||
@ -32,15 +32,10 @@ const datasetClassificationPropType = (prop: string): IMetadataType => ({
|
||||
});
|
||||
|
||||
/**
|
||||
* Defines the shape of the dataset compliance metadata json object using the IMetadataType interface
|
||||
* @type {Array<IMetadataType>}
|
||||
* Lists the types for objects or instances in the the compliance metadata entities list
|
||||
* @type Array<IMetadataType>
|
||||
*/
|
||||
const complianceMetadataTaxonomy: Array<IMetadataType> = [
|
||||
{
|
||||
'@type': 'object',
|
||||
'@name': 'datasetClassification',
|
||||
'@props': arrayMap(datasetClassificationPropType)(Object.keys(DatasetClassifiers))
|
||||
},
|
||||
const complianceEntitiesTaxonomy: Array<IMetadataType> = [
|
||||
{
|
||||
'@type': 'array',
|
||||
'@name': 'complianceEntities',
|
||||
@ -71,7 +66,20 @@ const complianceMetadataTaxonomy: Array<IMetadataType> = [
|
||||
'@type': ['string', 'null']
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* Defines the shape of the dataset compliance metadata json object using the IMetadataType interface
|
||||
* @type {Array<IMetadataType>}
|
||||
*/
|
||||
const complianceMetadataTaxonomy: Array<IMetadataType> = [
|
||||
{
|
||||
'@type': 'object',
|
||||
'@name': 'datasetClassification',
|
||||
'@props': arrayMap(datasetClassificationPropType)(Object.keys(DatasetClassifiers))
|
||||
},
|
||||
...complianceEntitiesTaxonomy,
|
||||
{
|
||||
'@type': ['string', 'null'],
|
||||
'@name': 'compliancePurgeNote'
|
||||
@ -131,6 +139,20 @@ const keyValueHasMatch = (object: IObject<any>) => (metadataType: IMetadataType)
|
||||
return rootValueEquiv;
|
||||
};
|
||||
|
||||
/**
|
||||
* Ensures that the keys on the supplied object are equivalent to the names in the type definition list
|
||||
* @param {IObject<any>} object
|
||||
* @param {Array<IMetadataType>} typeMaps
|
||||
* @return {boolean}
|
||||
*/
|
||||
const keysMatchNames = (object: IObject<any>, typeMaps: Array<IMetadataType>): boolean =>
|
||||
Object.keys(object)
|
||||
.sort()
|
||||
.toString() ===
|
||||
arrayMap((typeMap: IMetadataType) => typeMap['@name'])(typeMaps)
|
||||
.sort()
|
||||
.toString();
|
||||
|
||||
/**
|
||||
* Checks each key on an object matches the expected types in the typeMap
|
||||
* @param {IObject<any>} object the object with keys to check
|
||||
@ -138,17 +160,8 @@ const keyValueHasMatch = (object: IObject<any>) => (metadataType: IMetadataType)
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const keysEquiv = (object: IObject<any>, typeMaps: Array<IMetadataType>): boolean =>
|
||||
arrayEvery(keyValueHasMatch(object))(typeMaps);
|
||||
arrayEvery(keyValueHasMatch(object))(typeMaps) && keysMatchNames(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<IMetadataType>} taxonomy schema shape to check against
|
||||
* @return {boolean}
|
||||
*/
|
||||
const validateMetadataObject = (object: IObject<any>, taxonomy: Array<IMetadataType>): boolean =>
|
||||
keysEquiv(object, taxonomy);
|
||||
export default keysEquiv;
|
||||
|
||||
export default validateMetadataObject;
|
||||
|
||||
export { complianceMetadataTaxonomy };
|
||||
export { complianceMetadataTaxonomy, complianceEntitiesTaxonomy };
|
||||
|
||||
@ -6,6 +6,12 @@ const MergeTrees = require('broccoli-merge-trees');
|
||||
|
||||
module.exports = function(defaults) {
|
||||
const app = new EmberApp(defaults, {
|
||||
ace: {
|
||||
modes: ['json'],
|
||||
workers: ['json'],
|
||||
exts: ['searchbox']
|
||||
},
|
||||
|
||||
babel: {
|
||||
plugins: ['transform-object-rest-spread', 'transform-class-properties'],
|
||||
sourceMaps: 'inline'
|
||||
|
||||
@ -38,6 +38,7 @@
|
||||
"broccoli-funnel": "^2.0.1",
|
||||
"broccoli-merge-trees": "^3.0.0",
|
||||
"codecov": "^3.0.0",
|
||||
"ember-ace": "^1.3.1",
|
||||
"ember-ajax": "^3.0.0",
|
||||
"ember-basic-dropdown": "^1.0.0",
|
||||
"ember-cli": "~2.18.0",
|
||||
|
||||
@ -231,6 +231,10 @@ accepts@~1.3.4:
|
||||
mime-types "~2.1.16"
|
||||
negotiator "0.6.1"
|
||||
|
||||
ace-builds@^1.2.8:
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/ace-builds/-/ace-builds-1.3.3.tgz#c9746028d1485e5d7595fb2e825e665bd6648970"
|
||||
|
||||
acorn-globals@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.1.0.tgz#ab716025dbe17c54d3ef81d32ece2b2d99fe2538"
|
||||
@ -2814,6 +2818,17 @@ elegant-spinner@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e"
|
||||
|
||||
ember-ace@^1.3.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/ember-ace/-/ember-ace-1.3.1.tgz#b690cd1fe09a65a264dec40f9a54050af982a8da"
|
||||
dependencies:
|
||||
ace-builds "^1.2.8"
|
||||
broccoli-merge-trees "^2.0.0"
|
||||
broccoli-plugin "^1.2.1"
|
||||
ember-cli-babel "^6.6.0"
|
||||
ember-cli-htmlbars "^2.0.2"
|
||||
ember-cli-node-assets "^0.2.2"
|
||||
|
||||
ember-ajax@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ember-ajax/-/ember-ajax-3.0.0.tgz#8f21e9da0c1d433cf879aa855fce464d517e9ab5"
|
||||
@ -3131,6 +3146,17 @@ ember-cli-node-assets@^0.1.4, ember-cli-node-assets@^0.1.6:
|
||||
lodash "^4.5.1"
|
||||
resolve "^1.1.7"
|
||||
|
||||
ember-cli-node-assets@^0.2.2:
|
||||
version "0.2.2"
|
||||
resolved "https://registry.yarnpkg.com/ember-cli-node-assets/-/ember-cli-node-assets-0.2.2.tgz#d2d55626e7cc6619f882d7fe55751f9266022708"
|
||||
dependencies:
|
||||
broccoli-funnel "^1.0.1"
|
||||
broccoli-merge-trees "^1.1.1"
|
||||
broccoli-source "^1.1.0"
|
||||
debug "^2.2.0"
|
||||
lodash "^4.5.1"
|
||||
resolve "^1.1.7"
|
||||
|
||||
ember-cli-normalize-entity-name@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ember-cli-normalize-entity-name/-/ember-cli-normalize-entity-name-1.0.0.tgz#0b14f7bcbc599aa117b5fddc81e4fd03c4bad5b7"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user