Merge pull request #1428 from cptran777/refactor-schema-entities

Refactor schema entities
This commit is contained in:
Charlie Tran 2018-10-01 16:22:11 -07:00 committed by GitHub
commit 1c7df4fa2a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 310 additions and 247 deletions

View File

@ -1,8 +1,7 @@
import Component from '@ember/component';
import { computed, set, get, setProperties, getProperties, getWithDefault } from '@ember/object';
import ComputedProperty, { not, or, alias } from '@ember/object/computed';
import { run, schedule, next } from '@ember/runloop';
import { classify, htmlSafe } from '@ember/string';
import { run, next } from '@ember/runloop';
import { assert } from '@ember/debug';
import { IDatasetView } from 'wherehows-web/typings/api/datasets/dataset';
import { IDataPlatform } from 'wherehows-web/typings/api/list/platforms';
@ -14,7 +13,6 @@ import {
getFieldIdentifierOptions,
getDefaultSecurityClassification,
compliancePolicyStrings,
getComplianceSteps,
isExempt,
ComplianceFieldIdValue,
IDatasetClassificationOption,
@ -101,12 +99,6 @@ const datasetClassificationKey = 'complianceInfo.datasetClassification';
*/
const datasetClassifiersKeys = <Array<keyof typeof DatasetClassifiers>>Object.keys(DatasetClassifiers);
/**
* The initial state of the compliance step for a zero based array
* @type {number}
*/
const initialStepIndex = -1;
export default class DatasetCompliance extends Component {
isNewComplianceInfo: boolean;
datasetName: string;
@ -300,12 +292,6 @@ export default class DatasetCompliance extends Component {
* @memberof DatasetCompliance
*/
schemaless: boolean;
/**
* Tracks the current index of the compliance policy update wizard flow
* @type {number}
* @memberof DatasetCompliance
*/
editStepIndex: number;
/**
* List of complianceDataType values
@ -393,7 +379,6 @@ export default class DatasetCompliance extends Component {
super(...arguments);
//sets default values for class fields
this.editStepIndex = initialStepIndex;
this.sortColumnWithName || set(this, 'sortColumnWithName', 'identifierField');
this.filterBy || set(this, 'filterBy', '0'); // first element in field type is identifierField
this.sortDirection || set(this, 'sortDirection', 'asc');
@ -404,20 +389,6 @@ export default class DatasetCompliance extends Component {
set(this, 'suggestionConfidenceThreshold', lowQualitySuggestionConfidenceThreshold);
}
/**
* Lists the compliance wizard edit steps based on the datasets schemaless property
* @memberof DatasetCompliance
*/
editSteps = computed('schemaless', function(this: DatasetCompliance): Array<{ name: string }> {
const hasSchema = !getWithDefault(this, 'schemaless', false);
const steps = getComplianceSteps(hasSchema);
// Ensure correct step ordering
return Object.keys(steps)
.sort()
.map((key: string): { name: string } => steps[+key]);
});
/**
* Reads the complianceDataTypes property and transforms into a list of drop down options for the field
* identifier type
@ -490,78 +461,6 @@ export default class DatasetCompliance extends Component {
];
});
/**
* e-c Task to update the current edit step in the wizard flow.
* Handles the transitions between steps, including performing each step's
* post processing action once a user has completed a step, or reverting the step
* and stepping backward if the post process fails
* @type {Task<void, (a?: void) => TaskInstance<void>>}
* @memberof DatasetCompliance
*/
updateEditStepTask = (function() {
// initialize the previous action with a no-op function
let previousAction = noop;
// initialize the last seen index to the same value as editStepIndex
let lastIndex = initialStepIndex;
return task(function*(this: DatasetCompliance): IterableIterator<void> {
const { editStepIndex: currentIndex, editSteps } = getProperties(this, ['editStepIndex', 'editSteps']);
// the current step in the edit sequence
const editStep = editSteps[currentIndex] || { name: '' };
const { name } = editStep;
if (name) {
// using the steps name, construct a reference to the step process handler
const nextAction = this.actions[`did${classify(name)}`];
let previousActionResult: void;
// if the transition is backward, then the previous action is ignored
currentIndex > lastIndex && (previousActionResult = previousAction.call(this));
lastIndex = currentIndex;
try {
yield previousActionResult;
// if the previous action is resolved successfully, then replace with the next processor
previousAction = typeof nextAction === 'function' ? nextAction : noop;
set(this, 'editStep', editStep);
} catch {
// if the previous action settles in a rejected state, replace with no-op before
// invoking the previousStep action to go back in the sequence
// batch previousStep invocation in a afterRender queue due to editStepIndex update
previousAction = noop;
run(
(): void => {
if (this.isDestroyed || this.isDestroying) {
return;
}
schedule('afterRender', this, this.actions.previousStep);
}
);
}
}
}).enqueue();
})();
/**
* Holds a reference to the current step in the compliance edit wizard flow
* @type {{ name: string }}
*/
editStep: { name: string } = { name: '' };
/**
* A list of ui values and labels for review filter drop-down
* @type {Array<{value: TagFilter, label:string}>}
* @memberof DatasetCompliance
*/
fieldReviewOptions: Array<{ value: DatasetCompliance['fieldReviewOption']; label: string }> = [
{ value: TagFilter.showAll, label: ' Show all fields' },
{ value: TagFilter.showReview, label: '? Show fields missing a data type' },
{ value: TagFilter.showSuggested, label: '! Show fields that need review' },
//@ts-ignore htmlSafe type definition is incorrect in @types/ember contains TODO: to return Handlebars.SafeStringStatic
{ value: TagFilter.showCompleted, label: <string>htmlSafe(`&#10003; Show completed fields`) }
];
didReceiveAttrs(): void {
// Perform validation step on the received component attributes
this.validateAttrs();

View File

@ -1,7 +1,130 @@
import Component from '@ember/component';
import { action } from '@ember-decorators/object';
import { set } from '@ember/object';
import { noop } from 'wherehows-web/utils/helpers/functions';
import { ComplianceEdit, TagFilter, ComplianceFieldIdValue } from 'wherehows-web/constants';
import { htmlSafe } from '@ember/string';
import { IComplianceChangeSet, IDropDownOption } from 'wherehows-web/typings/app/dataset-compliance';
import { TrackableEventCategory, trackableEvent } from 'wherehows-web/constants/analytics/event-tracking';
export default class DatasetsComplianceSchemaEntities extends Component.extend({
// anything which *must* be merged to prototype here
}) {
// normal class body definition here
/**
* The DatasetComplianceEntities component allows the user to individually tag fields to annotate the
* data contained in the dataset schema, found in the Compliance tab for datasets.
*/
export default class DatasetComplianceEntities extends Component.extend({}) {
/**
* Passed in flag determining whether or not we are in an editing mode for the schema entities.
* @type {boolean}
*/
isEditing: boolean;
/**
* Passed in computed prop, Lists the IComplianceChangeSet / tags without an identifierType value
* @type {Array<IComplianceChangeSet>}
*/
unspecifiedTags: Array<IComplianceChangeSet>;
/**
* Passed in action/method from parent, updates the editing mode and corresponding target
*/
toggleEditing: (isEditing: boolean, target?: ComplianceEdit) => void;
/**
* Passed in action from parent, updates showGuidedEditMode state on parent
*/
// Note: [REFACTOR-COMPLIANCE] This passed in function is to maintain status quo while we migrate the
// refactor logic and should eventually no longer be required
showGuidedEditMode: (isShowingGuidedEditMode: boolean) => void;
/**
* Passed in action from parent, updates fieldReviewOption state on parent
*/
// Note: [REFACTOR-COMPLIANCE] Same as above showGuidedEditMode
fieldReviewChange: (o: { value: TagFilter }) => IDropDownOption<TagFilter>;
/**
* Specifies the filter to be applied on the list of fields shown in the compliance policy table
* @type {TagFilter}
*/
// Note: [REFACTOR-COMPLIANCE] This value will currently be passed in from the parent but should
// eventually live only on this component
fieldReviewOption!: TagFilter;
/**
* Used in the template to help pass values for the edit target
* @type {ComplianceEdit}
*/
ComplianceEdit = ComplianceEdit;
/**
* References the ComplianceFieldIdValue enum, which specifies hardcoded values for field
* identifiers
* @type {ComplianceFieldIdValue}
*/
ComplianceFieldIdValue = ComplianceFieldIdValue;
/**
* Enum of categories that can be tracked for this component
* @type {TrackableEventCategory}
*/
trackableCategory = TrackableEventCategory;
/**
* Map of events that can be tracked
* @type {ITrackableEventCategoryEvent}
*/
trackableEvent = trackableEvent;
/**
* Flag indicating the current compliance policy edit-view mode. Guided edit mode allows users
* to go through a wizard to edit the schema entities while the other method is direct JSON editing
* @type {boolean}
* @memberof DatasetComplianceEntities
*/
showGuidedComplianceEditMode = true;
/**
* A list of ui values and labels for review filter drop-down
* @type {Array<{value: TagFilter, label:string}>}
* @memberof DatasetComplianceEntities
*/
fieldReviewOptions: Array<IDropDownOption<DatasetComplianceEntities['fieldReviewOption']>> = [
{ value: TagFilter.showAll, label: ' Show all fields' },
{ value: TagFilter.showReview, label: '? Show fields missing a data type' },
{ value: TagFilter.showSuggested, label: '! Show fields that need review' },
//@ts-ignore htmlSafe type definition is incorrect in @types/ember contains TODO: to return Handlebars.SafeStringStatic
{ value: TagFilter.showCompleted, label: <string>htmlSafe(`&#10003; Show completed fields`) }
];
constructor() {
super(...arguments);
// Default values for undefined properties
this.unspecifiedTags || (this.unspecifiedTags = []);
this.toggleEditing || (this.toggleEditing = noop);
this.showGuidedEditMode || (this.showGuidedEditMode = noop);
this.fieldReviewChange || (this.fieldReviewChange = noop);
}
/**
* Toggle the visibility of the guided compliance edit view vs the advanced (json) edit view modes
* @param {boolean} toggle flag ,if true, show guided edit mode, otherwise, advanced
*/
@action
onShowGuidedEditMode(this: DatasetComplianceEntities, toggle: boolean): void {
const isShowingGuidedEditMode = set(this, 'showGuidedComplianceEditMode', toggle);
// Note: [REFACTOR-COMPLIANCE] Should be deleted once full functionality lives on this component
this.showGuidedEditMode(isShowingGuidedEditMode);
}
/**
* Invokes the external action to update the tagFilter query
* @param {{value: TagFilter}} { value }
* @returns {TagFilter}
*/
@action
onFieldReviewChange(this: DatasetComplianceEntities, option: { value: TagFilter }): IDropDownOption<TagFilter> {
// Note: [REFACTOR-COMPLIANCE] The passed in effects should eventually live only on this component
return this.fieldReviewChange(option);
}
}

View File

@ -118,22 +118,6 @@ enum ComplianceEdit {
ExportPolicy = 'editExportPolicy'
}
/**
* Takes a map of dataset options and constructs the relevant compliance edit wizard steps to build the wizard flow
* @param {boolean} [hasSchema=true] flag indicating if the dataset has a schema or otherwise
* @returns {({ [x: number]: { name: string } })}
*/
const getComplianceSteps = (hasSchema: boolean = true): { [x: number]: { name: string } } => {
// Step to tag dataset with PII data, this is at the dataset level for schema-less datasets
const piiTaggingStep = { 0: { name: 'editDatasetLevelCompliancePolicy' } };
if (!hasSchema) {
return { ...complianceSteps, ...piiTaggingStep };
}
return complianceSteps;
};
/**
* Returns true if argument of type IComplianceEntity has its readonly attribute not set to true
* @param {IComplianceEntity} { readonly }
@ -705,7 +689,6 @@ export {
getFieldIdentifierOption,
getFieldIdentifierOptions,
complianceSteps,
getComplianceSteps,
editableTags,
isTagFilter,
isAutoGeneratedPolicy,

View File

@ -25,7 +25,7 @@
{{#if showAdvancedEditApplyStep}}
{{#track-ui-event category=trackableCategory.Compliance action=trackableEvent.Compliance.ManualApply
name=editStep.name as |metrics|}}
name=editTarget as |metrics|}}
<button
class="nacho-button nacho-button--large-inverse action-bar__item"
title="Apply JSON"
@ -172,7 +172,19 @@
{{else}}
{{#if (or isReadOnly (eq editTarget ComplianceEdit.CompliancePolicy))}}
{{partial "datasets/dataset-compliance/dataset-compliance-entities"}}
{{#datasets/compliance/schema-entities
isEditing=(readonly isEditing)
wikiLinks=(readonly wikiLinks)
fieldReviewOption=(readonly fieldReviewOption)
foldedChangeSet=(readonly foldedChangeSet)
unspecifiedTags=(readonly unspecifiedTags)
setUnspecifiedTagsAsNoneTask=setUnspecifiedTagsAsNoneTask
toggleEditing=toggleEditing
showGuidedEditMode=(action "onShowGuidedEditMode")
fieldReviewChange=(action "onFieldReviewChange")}}
{{partial "datasets/dataset-compliance/dataset-compliance-entities"}}
{{/datasets/compliance/schema-entities}}
{{/if}}
{{/if}}

View File

@ -1 +1,92 @@
{{yield}}
<section class="metadata-prompt">
<header class="metadata-prompt__header">
<p>
{{if isEditing
"Does any field in the schema contain an IDs (e.g. Member ID, Enterprise Profile ID etc) or other PII
information?"
"IDs and PII in the schema"}}
{{more-info
link=@wikiLinks.gdprPii
tooltip="Click for more information on Schema field format and types"
}}
</p>
</header>
</section>
<section class="compliance-entities-meta">
<button
class="nacho-button nacho-button{{if showGuidedComplianceEditMode '--inverse' '--secondary'}}"
onclick={{action "onShowGuidedEditMode" true}}>
{{tooltip-on-element
text="Show Guided View"
}}
{{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")
}}
<span class="dataset-compliance-fields__filter-count">
{{pluralize foldedChangeSet.length "field"}}
</span>
{{/if}}
{{#if isEditing}}
<div class="compliance-entities-meta__secondary">
{{#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)}}>
{{#if setUnspecifiedTagsAsNoneTask.isRunning}}
{{pendulum-ellipsis-animation}}
{{else}}
Set {{pluralize unspecifiedTags.length "tag"}} to {{ComplianceFieldIdValue.None}}
{{/if}}
</button>
{{/track-ui-event}}
{{/if}}
</div>
{{else}}
<div class="compliance-entities-meta__secondary">
<button
class="nacho-button nacho-button--tertiary"
onclick={{action toggleEditing true ComplianceEdit.CompliancePolicy}}>
<i class="fa fa-pencil" aria-label="Edit Compliance Policy"></i>
Edit
</button>
</div>
{{/if}}
</section>
{{yield}}

View File

@ -2,21 +2,83 @@
{{#if isEditing}}
<div class="container action-bar__content">
<button
class="nacho-button nacho-button--large-inverse action-bar__item"
title="{{unless isDatasetFullyClassified
'Ensure you have provided a yes/no value for all dataset tags'
'Save'}}"
onclick={{action "saveCompliance"}} disabled={{shouldDisableEditSaving}}>
Save
</button>
{{#if showApplyButton}}
<button
class="nacho-button nacho-button--large nacho-button--secondary action-bar__item"
onclick={{action "onCancel"}}>
Cancel
</button>
{{#if trackApplyEvent}}
{{#track-ui-event category=trackApplyEvent.category action=trackApplyEvent.action
name=trackApplyEvent.name as |metrics|}}
<button
class="nacho-button nacho-button--large-inverse action-bar__item"
title={{applyButton.title}}
onclick={{action metrics.trackOnAction (action "onApplyComplianceJson")}}
disabled={{shouldDisableManualApply}}>
Apply
</button>
{{/track-ui-event}}
{{else}}
<button
class="nacho-button nacho-button--large-inverse action-bar__item"
title={{applyButton.title}}
onclick={{action "onApplyComplianceJson"}}
disabled={{shouldDisableManualApply}}>
Apply
</button>
{{/if}}
{{else}}
{{#if trackSaveEvent}}
{{#track-ui-event category=trackSaveEvent.category action=trackSaveEvent.action
name=trackSaveEvent.name as |metrics|}}
<button
class="nacho-button nacho-button--large-inverse action-bar__item"
title="{{unless isDatasetFullyClassified
'Ensure you have provided a yes/no value for all dataset tags'
'Save'}}"
onclick={{action metrics.trackOnAction (action "saveCompliance")}} disabled={{shouldDisableEditSaving}}>
Save
</button>
{{/track-ui-event}}
{{else}}
<button
class="nacho-button nacho-button--large-inverse action-bar__item"
title="{{unless isDatasetFullyClassified
'Ensure you have provided a yes/no value for all dataset tags'
'Save'}}"
onclick={{action "saveCompliance"}} disabled={{shouldDisableEditSaving}}>
Save
</button>
{{/if}}
{{/if}}
{{#if trackCancelEvent}}
{{#track-ui-event category=trackCancelEvent.category action=trackCancelEvent.action
name=trackCancelEvent.name as |metrics|}}
<button
class="nacho-button nacho-button--large nacho-button--secondary action-bar__item"
onclick={{action metrics.trackOnAction (action "onCancel")}}>
Cancel
</button>
{{/track-ui-event}}
{{else}}
<button
class="nacho-button nacho-button--large nacho-button--secondary action-bar__item"
onclick={{action "onCancel"}}>
Cancel
</button>
{{/if}}
{{#if (and manualParseError (not hideManualParseErrors))}}
<div class="action-bar__content__error-messages">
{{fa-icon "times-circle-o"}}
<span class="dataset-compliance-fields__manual-entry-errors">
{{manualParseError}}
</span>
</div>
{{/if}}
</div>
{{/if}}
</section>
</section>

View File

@ -1,92 +1,3 @@
<section class="metadata-prompt">
<header class="metadata-prompt__header">
<p>
{{if isEditing
"Does any field in the schema contain an IDs (e.g. Member ID, Enterprise Profile ID etc) or other PII information?"
"IDs and PII in the schema"}}
{{more-info
link=@wikiLinks.gdprPii
tooltip="Click for more information on Schema field format and types"
}}
</p>
</header>
</section>
<section class="compliance-entities-meta">
<button
class="nacho-button nacho-button{{if showGuidedComplianceEditMode '--inverse' '--secondary'}}"
onclick={{action "onShowGuidedEditMode" true}}>
{{tooltip-on-element
text="Show Guided View"
}}
{{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")
}}
<span class="dataset-compliance-fields__filter-count">
{{pluralize foldedChangeSet.length "field"}}
</span>
{{/if}}
{{#if isEditing}}
<div class="compliance-entities-meta__secondary">
{{#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)}}>
{{#if setUnspecifiedTagsAsNoneTask.isRunning}}
{{pendulum-ellipsis-animation}}
{{else}}
Set {{pluralize unspecifiedTags.length "tag"}} to {{ComplianceFieldIdValue.None}}
{{/if}}
</button>
{{/track-ui-event}}
{{/if}}
</div>
{{/if}}
{{#if isReadOnly}}
<div class="compliance-entities-meta__secondary">
<button
class="nacho-button nacho-button--tertiary"
onclick={{action toggleEditing true ComplianceEdit.CompliancePolicy}}>
<i class="fa fa-pencil" aria-label="Edit Compliance Policy"></i>
Edit
</button>
</div>
{{/if}}
</section>
<div class="dataset-compliance-fields__subtitle">
<div class="dataset-compliance-fields__review-hint">
{{fieldReviewHint}}

View File

@ -62,24 +62,6 @@ module('Unit | Constants | dataset compliance', function() {
);
});
test('getComplianceSteps function should behave as expected', function(assert) {
assert.expect(3);
const piiTaggingStep = { 0: { name: 'editDatasetLevelCompliancePolicy' } };
let result;
assert.equal(typeof getComplianceSteps, 'function', 'getComplianceSteps is a function');
result = getComplianceSteps();
assert.deepEqual(result, complianceSteps, 'getComplianceSteps result is expected shape when no args are passed');
result = getComplianceSteps(false);
assert.deepEqual(
result,
{ ...complianceSteps, ...piiTaggingStep },
'getComplianceSteps result is expected shape when hasSchema attribute is false'
);
});
test('getFieldIdentifierOption function should behave as expected', function(assert) {
const [complianceType] = complianceDataTypes;
let result;