adds functions for async array traversal. adds CUSTOM pattern field for field formats. updates ember-concurrency to add support for waitForProperty, and updates typings.

This commit is contained in:
Seyi Adebajo 2018-05-02 09:45:26 -07:00
parent b7ba9e4f7a
commit 933982bc0d
19 changed files with 483 additions and 128 deletions

View File

@ -1,6 +1,6 @@
import Component from '@ember/component';
import ComputedProperty from '@ember/object/computed';
import { get, getProperties, computed } from '@ember/object';
import { set, get, getProperties, computed } from '@ember/object';
import { ComplianceFieldIdValue, idTypeFieldHasLogicalType, isTagIdType } from 'wherehows-web/constants';
import {
IComplianceChangeSet,
@ -12,6 +12,7 @@ import { IComplianceDataType } from 'wherehows-web/typings/api/list/compliance-d
import { action } from 'ember-decorators/object';
import { IComplianceEntity } from 'wherehows-web/typings/api/datasets/compliance';
import { arrayFilter } from 'wherehows-web/utils/array';
import { IdLogicalType } from 'wherehows-web/constants/datasets/compliance';
/**
* Constant definition for an unselected field format
@ -47,6 +48,11 @@ export default class DatasetComplianceFieldTag extends Component {
*/
onTagLogicalTypeChange: (tag: IComplianceChangeSet, value: IComplianceChangeSet['logicalType']) => void;
/**
* Describes the interface for the parent action `onTagValuePatternChange`
*/
onTagValuePatternChange: (tag: IComplianceChangeSet, pattern: string) => string | void;
/**
* Describes the parent action interface for `onTagOwnerChange`
*/
@ -66,6 +72,12 @@ export default class DatasetComplianceFieldTag extends Component {
*/
parentHasSingleTag: boolean;
/**
* Stores the value of error result if the valuePattern is invalid
* @type {string}
*/
valuePatternError: string = '';
/**
* List of identifierTypes for the parent field
* @type {Array<IComplianceEntity['identifierType']>}
@ -147,6 +159,15 @@ export default class DatasetComplianceFieldTag extends Component {
return fieldFormatOptions;
});
/**
* Determines if the CUSTOM input field should be shown for this row's tag
* @type {ComputedProperty<boolean>}
*/
showCustomInput = computed('tag.logicalType', function(this: DatasetComplianceFieldTag): boolean {
const { logicalType } = get(this, 'tag');
return logicalType === IdLogicalType.Custom;
});
/**
* Checks if the field format / logical type for this tag is missing, if the field is of ID type
* @type {ComputedProperty<boolean>}
@ -156,6 +177,14 @@ export default class DatasetComplianceFieldTag extends Component {
return get(this, 'isIdType') && !idTypeFieldHasLogicalType(get(this, 'tag'));
});
/**
* Sets the value of the pattern error string after p
* @param {string} errorString
*/
setPatternErrorString(errorString: string = '') {
set(this, 'valuePatternError', errorString.replace('SyntaxError: ', ''));
}
/**
* Handles UI changes to the tag identifierType
* @param {{ value: ComplianceFieldIdValue }} { value }
@ -191,4 +220,23 @@ export default class DatasetComplianceFieldTag extends Component {
// inverts the value of nonOwner, toggle is shown in the UI as `Owner` i.e. not nonOwner
get(this, 'onTagOwnerChange')(get(this, 'tag'), !nonOwner);
}
/**
* Invokes the parent action on user input for value pattern
* If an exception is thrown, valuePatternError is updated with string value
* @param {string} pattern user input string
*/
@action
tagValuePatternDidChange(this: DatasetComplianceFieldTag, pattern: string) {
try {
const valuePattern = get(this, 'onTagValuePatternChange')(get(this, 'tag'), pattern);
if (valuePattern) {
//clear pattern error
this.setPatternErrorString();
}
} catch (e) {
this.setPatternErrorString(e.toString());
}
}
}

View File

@ -36,13 +36,13 @@ export default class DatasetComplianceRollupRow extends Component.extend({
* References the parent external action to add a tag to the list of change sets
* @memberof DatasetComplianceRollupRow
*/
onFieldTagAdded: (tag: IComplianceChangeSet) => IComplianceChangeSet;
onFieldTagAdded: (tag: IComplianceChangeSet) => void;
/**
* References the parent external action to add a tag to the list of change sets
* @memberof DatasetComplianceRollupRow
*/
onFieldTagRemoved: (tag: IComplianceChangeSet) => IComplianceChangeSet;
onFieldTagRemoved: (tag: IComplianceChangeSet) => void;
/**
* Describes action interface for `onSuggestionIntent` action
@ -153,7 +153,7 @@ export default class DatasetComplianceRollupRow extends Component.extend({
* Checks if any of the tags on this field have a ComplianceFieldIdValue.None identifierType
* @type {ComputedProperty<boolean>}
*/
hasNoneTag: ComputedProperty<boolean> = computed('fieldChangeSet', function(
hasNoneTag: ComputedProperty<boolean> = computed('fieldChangeSet.@each.identifierType', function(
this: DatasetComplianceRollupRow
): boolean {
return tagsHaveNoneType(get(this, 'fieldChangeSet'));
@ -344,8 +344,10 @@ export default class DatasetComplianceRollupRow extends Component.extend({
// Accept the suggestion for either identifierType and/or logicalType
if (suggestion && intent === SuggestionIntent.accept) {
const { identifierType, logicalType } = suggestion;
// Field has only one tag, that tag does not currently have an identifierType
const updateDefault = hasSingleTag && !fieldTagsHaveIdentifierType(get(this, 'fieldChangeSet'));
// Identifier type and changeSet does not already have suggested type
if (identifierType && !suggestedValuesInChangeSet.includes(identifierType)) {
if (updateDefault) {
get(this, 'onTagIdentifierTypeChange')(get(this, 'fieldProps'), {

View File

@ -8,7 +8,7 @@ import { assert } from '@ember/debug';
import { IDatasetView } from 'wherehows-web/typings/api/datasets/dataset';
import { IDataPlatform } from 'wherehows-web/typings/api/list/platforms';
import { readPlatforms } from 'wherehows-web/utils/api/list/platforms';
import { task, TaskInstance } from 'ember-concurrency';
import { task, waitForProperty, TaskInstance } from 'ember-concurrency';
import {
getSecurityClassificationDropDownOptions,
DatasetClassifiers,
@ -28,13 +28,13 @@ import {
isTagIdType,
idTypeFieldsHaveLogicalType,
changeSetReviewableAttributeTriggers,
mapSchemaColumnPropsToCurrentPrivacyPolicy,
asyncMapSchemaColumnPropsToCurrentPrivacyPolicy,
foldComplianceChangeSets,
sortFoldedChangeSetTuples
} from 'wherehows-web/constants';
import { isPolicyExpectedShape } from 'wherehows-web/utils/datasets/compliance-policy';
import { getTagsSuggestions } from 'wherehows-web/utils/datasets/compliance-suggestions';
import { compact, isListUnique } from 'wherehows-web/utils/array';
import { arrayMap, compact, isListUnique, iterateArrayAsync } from 'wherehows-web/utils/array';
import noop from 'wherehows-web/utils/noop';
import { IComplianceDataType } from 'wherehows-web/typings/api/list/compliance-datatypes';
import Notifications, { NotificationEvent, IConfirmOptions } from 'wherehows-web/services/notifications';
@ -57,6 +57,9 @@ import {
ShowAllShowReview
} from 'wherehows-web/typings/app/dataset-compliance';
import { uniqBy } from 'lodash';
import { IColumnFieldProps } from 'wherehows-web/typings/app/dataset-columns';
import { isValidCustomValuePattern } from 'wherehows-web/utils/validators/urn';
import { emptyRegexSource } from 'wherehows-web/utils/validators/regexp';
const {
complianceDataException,
@ -80,12 +83,6 @@ const datasetClassificationKey = 'complianceInfo.datasetClassification';
*/
const datasetClassifiersKeys = <Array<keyof typeof DatasetClassifiers>>Object.keys(DatasetClassifiers);
/**
* A reference to the compliance policy entities on the complianceInfo map
* @type {string}
*/
const policyComplianceEntitiesKey = 'complianceInfo.complianceEntities';
/**
* The initial state of the compliance step for a zero based array
* @type {number}
@ -203,6 +200,12 @@ export default class DatasetCompliance extends Component {
*/
supportedPurgePolicies: Array<PurgePolicy> = [];
/**
* Computed prop over the current Id fields in the Privacy Policy
* @type {ISchemaFieldsToPolicy}
*/
columnIdFieldsToCurrentPrivacyPolicy: ISchemaFieldsToPolicy = {};
constructor() {
super(...arguments);
@ -378,6 +381,13 @@ export default class DatasetCompliance extends Component {
didInsertElement(this: DatasetCompliance) {
get(this, 'complianceAvailabilityTask').perform();
get(this, 'columnFieldsToCompliancePolicyTask').perform();
get(this, 'foldChangeSetTask').perform();
}
didUpdateAttrs() {
get(this, 'columnFieldsToCompliancePolicyTask').perform();
get(this, 'foldChangeSetTask').perform();
}
/**
@ -517,33 +527,46 @@ export default class DatasetCompliance extends Component {
});
/**
* Computed prop over the current Id fields in the Privacy Policy
* @type {ComputedProperty<ISchemaFieldsToPolicy>}
* Task to retrieve column fields async and set values on Component
* @type {Task<Promise<any>, () => TaskInstance<Promise<any>>>}
* @memberof DatasetCompliance
*/
columnIdFieldsToCurrentPrivacyPolicy: ComputedProperty<ISchemaFieldsToPolicy> = computed(
`{schemaFieldNamesMappedToDataTypes,${policyComplianceEntitiesKey}.[]}`,
function(this: DatasetCompliance): ISchemaFieldsToPolicy {
const {
complianceEntities = [],
modifiedTime
}: Pick<IComplianceInfo, 'complianceEntities' | 'modifiedTime'> = get(this, 'complianceInfo') || {
complianceEntities: []
};
// Truncated list of Dataset field names and data types currently returned from the column endpoint
const columnProps = getWithDefault(this, 'schemaFieldNamesMappedToDataTypes', []).map(
({ fieldName, dataType }) => ({
identifierField: fieldName,
dataType
})
);
columnFieldsToCompliancePolicyTask = task(function*(this: DatasetCompliance): IterableIterator<any> {
// Truncated list of Dataset field names and data types currently returned from the column endpoint
const schemaFieldNamesMappedToDataTypes: DatasetCompliance['schemaFieldNamesMappedToDataTypes'] = yield waitForProperty(
this,
'schemaFieldNamesMappedToDataTypes',
({ length }) => !!length
);
return mapSchemaColumnPropsToCurrentPrivacyPolicy({
const { complianceEntities = [], modifiedTime }: Pick<IComplianceInfo, 'complianceEntities' | 'modifiedTime'> = get(
this,
'complianceInfo'
)!;
const renameFieldNameAttr = ({
fieldName,
dataType
}: Pick<IDatasetColumn, 'dataType' | 'fieldName'>): {
identifierField: IDatasetColumn['fieldName'];
dataType: IDatasetColumn['dataType'];
} => ({
identifierField: fieldName,
dataType
});
const columnProps: Array<IColumnFieldProps> = yield iterateArrayAsync(arrayMap(renameFieldNameAttr))(
schemaFieldNamesMappedToDataTypes
);
const columnIdFieldsToCurrentPrivacyPolicy: ISchemaFieldsToPolicy = yield asyncMapSchemaColumnPropsToCurrentPrivacyPolicy(
{
columnProps,
complianceEntities,
policyModificationTime: modifiedTime
});
}
);
}
);
set(this, 'columnIdFieldsToCurrentPrivacyPolicy', columnIdFieldsToCurrentPrivacyPolicy);
}).enqueue();
/**
* Creates a mapping of compliance suggestions to identifierField
@ -635,15 +658,26 @@ export default class DatasetCompliance extends Component {
/**
* Reduces the current filtered changeSet to a list of IdentifierFieldWithFieldChangeSetTuple
* @type {ComputedProperty<Array<IdentifierFieldWithFieldChangeSetTuple>>}
* @type {Array<IdentifierFieldWithFieldChangeSetTuple>}
* @memberof DatasetCompliance
*/
foldedChangeSet: ComputedProperty<Array<IdentifierFieldWithFieldChangeSetTuple>> = computed(
'filteredChangeSet',
function(this: DatasetCompliance): Array<IdentifierFieldWithFieldChangeSetTuple> {
return sortFoldedChangeSetTuples(foldComplianceChangeSets(get(this, 'filteredChangeSet')));
}
);
foldedChangeSet: Array<IdentifierFieldWithFieldChangeSetTuple>;
/**
* Task to retrieve platform policies and set supported policies for the current platform
* @type {Task<Promise<any>, () => TaskInstance<Promise<any>>>}
* @memberof DatasetCompliance
*/
foldChangeSetTask = task(function*(this: DatasetCompliance): IterableIterator<any> {
//@ts-ignore dot notation for property access
yield waitForProperty(this, 'columnFieldsToCompliancePolicyTask.isIdle');
const filteredChangeSet = get(this, 'filteredChangeSet');
const foldedChangeSet: Array<IdentifierFieldWithFieldChangeSetTuple> = yield foldComplianceChangeSets(
filteredChangeSet
);
set(this, 'foldedChangeSet', sortFoldedChangeSetTuples(foldedChangeSet));
}).enqueue();
/**
* Invokes external action with flag indicating that at least 1 suggestion exists for a field in the changeSet
@ -716,13 +750,22 @@ export default class DatasetCompliance extends Component {
const formattedAndUnformattedEntities: FormattedAndUnformattedEntities = { formatted: [], unformatted: [] };
// All candidate fields that can be on policy, excluding tracking type fields
const changeSetEntities: Array<IComplianceEntity> = get(this, 'compliancePolicyChangeSet').map(
({ identifierField, identifierType = null, logicalType, nonOwner, securityClassification, readonly }) => ({
({
identifierField,
identifierType = null,
logicalType,
nonOwner,
securityClassification,
readonly,
valuePattern
}) => ({
identifierField,
identifierType,
logicalType,
nonOwner,
securityClassification,
readonly
readonly,
valuePattern
})
);
@ -885,8 +928,9 @@ export default class DatasetCompliance extends Component {
* @param {IComplianceChangeSet} tag properties for new field tag
* @return {IComplianceChangeSet}
*/
onFieldTagAdded(this: DatasetCompliance, tag: IComplianceChangeSet): IComplianceChangeSet {
return get(this, 'compliancePolicyChangeSet').addObject(tag);
onFieldTagAdded(this: DatasetCompliance, tag: IComplianceChangeSet): void {
get(this, 'compliancePolicyChangeSet').addObject(tag);
get(this, 'foldChangeSetTask').perform();
},
/**
@ -894,8 +938,9 @@ export default class DatasetCompliance extends Component {
* @param {IComplianceChangeSet} tag
* @return {IComplianceChangeSet}
*/
onFieldTagRemoved(this: DatasetCompliance, tag: IComplianceChangeSet): IComplianceChangeSet {
return get(this, 'compliancePolicyChangeSet').removeObject(tag);
onFieldTagRemoved(this: DatasetCompliance, tag: IComplianceChangeSet): void {
get(this, 'compliancePolicyChangeSet').removeObject(tag);
get(this, 'foldChangeSetTask').perform();
},
/**
@ -914,7 +959,8 @@ export default class DatasetCompliance extends Component {
identifierType,
logicalType: null,
nonOwner: null,
isDirty: true
isDirty: true,
valuePattern: void 0
});
}
@ -926,21 +972,34 @@ export default class DatasetCompliance extends Component {
* @param {IComplianceChangeSet} tag the tag to be updated
* @param {IComplianceChangeSet.logicalType} logicalType the updated logical type
*/
tagLogicalTypeChanged(
this: DatasetCompliance,
tag: IComplianceChangeSet,
logicalType: IComplianceChangeSet['logicalType']
) {
tagLogicalTypeChanged(tag: IComplianceChangeSet, logicalType: IComplianceChangeSet['logicalType']) {
setProperties(tag, { logicalType, isDirty: true });
},
/**
* Handles changes to the valuePattern attribute on a tag
* @param {IComplianceChangeSet} tag
* @param {string} pattern
* @return {string | void}
* @throws {SyntaxError}
*/
tagValuePatternChanged(tag: IComplianceChangeSet, pattern: string): string | void {
const isValidRegex = new RegExp(pattern); // Will throw if invalid
const isValidValuePattern = isValidCustomValuePattern(pattern);
if (isValidRegex.source !== emptyRegexSource && isValidRegex && isValidValuePattern) {
return set(tag, 'valuePattern', isValidRegex.source);
}
throw new Error('Pattern not valid');
},
/**
* Updates the security classification on a field tag
* @param {IComplianceChangeSet} tag the tag to be updated
* @param {IComplianceChangeSet.securityClassification} securityClassification the updated security classification value
*/
tagClassificationChanged(
this: DatasetCompliance,
tag: IComplianceChangeSet,
{ value: securityClassification = null }: { value: IComplianceChangeSet['securityClassification'] }
) {
@ -955,7 +1014,7 @@ export default class DatasetCompliance extends Component {
* @param {IComplianceChangeSet} tag the field tag to be updated
* @param {IComplianceChangeSet.nonOwner} nonOwner flag indicating the field property is a nonOwner
*/
tagOwnerChanged(this: DatasetCompliance, tag: IComplianceChangeSet, nonOwner: IComplianceChangeSet['nonOwner']) {
tagOwnerChanged(tag: IComplianceChangeSet, nonOwner: IComplianceChangeSet['nonOwner']) {
setProperties(tag, {
nonOwner,
isDirty: true
@ -1011,7 +1070,10 @@ export default class DatasetCompliance extends Component {
* @returns {ShowAllShowReview}
*/
onFieldReviewChange(this: DatasetCompliance, { value }: { value: ShowAllShowReview }): ShowAllShowReview {
return set(this, 'fieldReviewOption', value);
const option = set(this, 'fieldReviewOption', value);
get(this, 'foldChangeSetTask').perform();
return option;
},
/**

View File

@ -27,6 +27,7 @@ import {
filterEditableEntities,
SuggestionIntent
} from 'wherehows-web/constants';
import { iterateArrayAsync } from 'wherehows-web/utils/array';
/**
* Type alias for the response when container data items are batched
@ -170,7 +171,7 @@ export default class DatasetComplianceContainer extends Component {
readDatasetComplianceSuggestionByUrn(urn),
readDatasetSchemaByUrn(urn)
]);
const schemaFieldNamesMappedToDataTypes = columnDataTypesAndFieldNames(columns);
const schemaFieldNamesMappedToDataTypes = await iterateArrayAsync(columnDataTypesAndFieldNames)(columns);
this.onCompliancePolicyStateChange.call(this, { isNewComplianceInfo, fromUpstream: !!complianceInfo.fromUpstream });
@ -227,10 +228,12 @@ export default class DatasetComplianceContainer extends Component {
* Reads the schema properties for the dataset
* @type {Task<Promise<IDatasetSchema>, (a?: any) => TaskInstance<Promise<IDatasetSchema>>>}
*/
getDatasetSchemaTask = task(function*(this: DatasetComplianceContainer): IterableIterator<Promise<IDatasetSchema>> {
getDatasetSchemaTask = task(function*(
this: DatasetComplianceContainer
): IterableIterator<Promise<IDatasetSchema | Pick<IDatasetColumn, 'dataType' | 'fieldName'>[]>> {
try {
const { columns, schemaless }: IDatasetSchema = yield readDatasetSchemaByUrn(get(this, 'urn'));
const schemaFieldNamesMappedToDataTypes = columnDataTypesAndFieldNames(columns);
const schemaFieldNamesMappedToDataTypes = yield iterateArrayAsync(columnDataTypesAndFieldNames)(columns);
setProperties(this, { schemaFieldNamesMappedToDataTypes, schemaless });
} catch (e) {
// If this schema is missing, silence exception, otherwise propagate

View File

@ -1,7 +1,7 @@
import { PurgePolicy } from 'wherehows-web/constants/index';
import { IComplianceEntity, IComplianceInfo } from 'wherehows-web/typings/api/datasets/compliance';
import { IComplianceDataType } from 'wherehows-web/typings/api/list/compliance-datatypes';
import { arrayEvery, arrayFilter, arrayMap, arrayReduce, arraySome } from 'wherehows-web/utils/array';
import { arrayEvery, arrayFilter, arrayMap, arrayReduce, arraySome, reduceArrayAsync } from 'wherehows-web/utils/array';
import { fleece, hasEnumerableKeys } from 'wherehows-web/utils/object';
import { lastSeenSuggestionInterval } from 'wherehows-web/constants/metadata-acquisition';
import { decodeUrn } from 'wherehows-web/utils/validators/urn';
@ -357,10 +357,12 @@ const foldComplianceChangeSetToField = (
* @param {Array<IComplianceChangeSet>} changeSet
* @returns {Array<IdentifierFieldWithFieldChangeSetTuple>}
*/
const foldComplianceChangeSets = (
const foldComplianceChangeSets = async (
changeSet: Array<IComplianceChangeSet>
): Array<IdentifierFieldWithFieldChangeSetTuple> =>
Object.entries<Array<IComplianceChangeSet>>(arrayReduce(foldComplianceChangeSetToField, {})(changeSet));
): Promise<Array<IdentifierFieldWithFieldChangeSetTuple>> =>
Object.entries<Array<IComplianceChangeSet>>(
await reduceArrayAsync(arrayReduce(foldComplianceChangeSetToField, {}))(changeSet)
);
/**
* Builds a default shape for securitySpecification & privacyCompliancePolicy with default / unset values
@ -390,12 +392,14 @@ const createInitialComplianceInfo = (datasetId: string): IComplianceInfo => {
* }
* @returns {ISchemaFieldsToPolicy}
*/
const mapSchemaColumnPropsToCurrentPrivacyPolicy = ({
const asyncMapSchemaColumnPropsToCurrentPrivacyPolicy = ({
columnProps,
complianceEntities,
policyModificationTime
}: ISchemaColumnMappingProps): ISchemaFieldsToPolicy =>
arrayReduce(schemaFieldsWithPolicyTagsReducingFn(complianceEntities, policyModificationTime), {})(columnProps);
}: ISchemaColumnMappingProps): Promise<ISchemaFieldsToPolicy> =>
reduceArrayAsync(arrayReduce(schemaFieldsWithPolicyTagsReducingFn(complianceEntities, policyModificationTime), {}))(
columnProps
);
/**
* Creates a new tag / change set item for a compliance entity / field with default properties
@ -420,7 +424,8 @@ const complianceFieldChangeSetItemFactory = ({
nonOwner: null,
readonly: false,
privacyPolicyExists: false,
isDirty: true
isDirty: true,
valuePattern: void 0
},
suggestion ? { suggestion } : void 0,
suggestionAuthority ? { suggestionAuthority } : void 0
@ -490,7 +495,8 @@ const complianceFieldTagFactory = (identifierField: IComplianceEntity['identifie
logicalType: null,
securityClassification: null,
nonOwner: null,
readonly: false
readonly: false,
valuePattern: void 0
});
/**
@ -533,7 +539,7 @@ export {
idTypeFieldHasLogicalType,
idTypeFieldsHaveLogicalType,
changeSetReviewableAttributeTriggers,
mapSchemaColumnPropsToCurrentPrivacyPolicy,
asyncMapSchemaColumnPropsToCurrentPrivacyPolicy,
foldComplianceChangeSets,
complianceFieldChangeSetItemFactory,
sortFoldedChangeSetTuples

View File

@ -25,7 +25,8 @@ enum IdLogicalType {
Numeric = 'NUMERIC',
Urn = 'URN',
ReversedUrn = 'REVERSED_URN',
CompositeUrn = 'COMPOSITE_URN'
CompositeUrn = 'COMPOSITE_URN',
Custom = 'CUSTOM'
}
/**

View File

@ -177,6 +177,7 @@
&#{&} {
td {
border-bottom: 0;
height: item-spacing(8);
}
}
}
@ -209,6 +210,43 @@
padding: item-spacing(1);
}
&__text-pattern-wrap {
$error-font-size: 12px;
$wrap-width: 280px;
width: $wrap-width;
margin-left: item-spacing(2);
position: relative;
display: flex;
flex-direction: column;
&--input {
&:before,
&:after {
content: '/';
color: get-color(blue5);
font-size: large;
font-weight: bold;
}
}
&--error {
font-size: $error-font-size;
position: absolute;
bottom: -18px;
color: get-color(red7);
}
}
&__text-pattern {
$input-width: 260px;
padding: (item-spacing(3) / 2) item-spacing(2);
white-space: pre;
font-family: monospace;
width: $input-width;
outline: none;
}
&__id-field-wrap {
display: flex;
max-width: 100%;

View File

@ -26,9 +26,9 @@
bottom: 150%;
left: 50%;
margin-bottom: item-spacing(1);
margin-left: -(item-spacing(8));
margin-left: -(item-spacing(7));
padding: item-spacing(2);
width: item-spacing(8) * 2;
width: item-spacing(7) * 2;
border-radius: item-spacing(1);
background-color: get-color(black, 0.9);
color: white;

View File

@ -2,8 +2,11 @@
rowId=elementId
isIdType=isIdType
fieldFormats=fieldFormats
showCustomInput=showCustomInput
valuePatternError=valuePatternError
isTagFormatMissing=isTagFormatMissing
fieldIdDropDownOptions=fieldIdDropDownOptions
tagValuePatternDidChange=(action "tagValuePatternDidChange")
tagIdentifierTypeDidChange=(action "tagIdentifierTypeDidChange")
tagLogicalTypeDidChange=(action "tagLogicalTypeDidChange")
tagOwnerDidChange=(action "tagOwnerDidChange")

View File

@ -81,7 +81,8 @@
onTagIdentifierTypeChange=(action "tagIdentifierChanged")
onSuggestionIntent=(action "onFieldSuggestionIntentChange") as |row|
}}
<tr class="{{if row.isReadonly 'dataset-compliance-fields--readonly'}}" ondblclick={{action row.onFragmentDblClick}}>
<tr class="{{if row.isReadonly 'dataset-compliance-fields--readonly'}}" ondblclick={{action
row.onFragmentDblClick}}>
{{#row.cell}}
{{#if row.isReadonly}}
@ -94,7 +95,8 @@
(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>
<i class="fa fa-exclamation dataset-compliance-fields__has-suggestions__icon"
title="Compliance field has suggested values"></i>
</span>
{{else}}
@ -102,7 +104,8 @@
{{#if row.isReviewRequested}}
<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>
<i class="fa fa-question dataset-compliance-fields--review-required__icon"
title="Compliance policy information needs review"></i>
</span>
{{else}}
@ -237,6 +240,7 @@
fieldIdentifiers=row.taggedIdentifiers
onTagIdentifierTypeChange=(action "tagIdentifierChanged")
onTagLogicalTypeChange=(action "tagLogicalTypeChanged")
onTagValuePatternChange=(action "tagValuePatternChanged")
onTagOwnerChange=(action "tagOwnerChanged")
complianceFieldIdDropdownOptions=complianceFieldIdDropdownOptions
complianceDataTypes=complianceDataTypes as |tagRowComponent|
@ -277,6 +281,30 @@
}}
</div>
{{#if tagRowComponent.showCustomInput}}
<div class="dataset-compliance-fields__text-pattern-wrap">
{{#if (or (not isEditing) row.isReadonly)}}
{{tag.valuePattern}}
{{else}}
<div class="dataset-compliance-fields__text-pattern-wrap--input">
<input
placeholder="Enter regex"
value="{{readonly tag.valuePattern}}"
class="dataset-compliance-fields__text-pattern {{if tagRowComponent.valuePatternError
'dataset-compliance-fields--missing-selection'}}"
oninput={{action tagRowComponent.tagValuePatternDidChange value="target.value"}}
>
</div>
{{#if tagRowComponent.valuePatternError}}
<div class="dataset-compliance-fields__text-pattern-wrap--error">
{{tagRowComponent.valuePatternError}}
</div>
{{/if}}
{{/if}}
</div>
{{/if}}
{{#unless tagRowComponent.isTagFormatMissing}}
<span class="dataset-compliance-fields__tag-label">
Owner:

View File

@ -31,6 +31,8 @@ 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
valuePattern?: string;
}
/**

View File

@ -16,7 +16,7 @@ interface IColumnFieldProps {
}
/**
* Defines the interface for properties passed into the mapping function mapSchemaColumnPropsToCurrentPrivacyPolicy
* Defines the interface for properties passed into the mapping function asyncMapSchemaColumnPropsToCurrentPrivacyPolicy
* @interface ISchemaColumnMappingProps
*/
interface ISchemaColumnMappingProps {

View File

@ -24,7 +24,13 @@ interface IDatasetComplianceActions {
*/
type IComplianceEntityWithMetadata = Pick<
IComplianceEntity,
'identifierField' | 'identifierType' | 'logicalType' | 'securityClassification' | 'nonOwner' | 'readonly'
| 'identifierField'
| 'identifierType'
| 'logicalType'
| 'securityClassification'
| 'nonOwner'
| 'readonly'
| 'valuePattern'
> & {
// flag indicating that the field has a current policy upstream
privacyPolicyExists: boolean;

View File

@ -1,8 +1,22 @@
declare module 'ember-concurrency' {
export function timeout(delay: number): Promise<void>;
import ComputedProperty from '@ember/object/computed';
import RSVP from 'rsvp';
type ComputedProperties<T> = { [K in keyof T]: ComputedProperty<T[K]> | T[K] };
export function timeout(delay: number): Promise<void>;
export function waitForProperty<T, K extends keyof T>(
object: ComputedProperties<T>,
key: K,
predicateCallback: (arg: T[K]) => boolean | T[K]
): IterableIterator<T[K]>;
export function waitForProperty<T, K extends keyof T>(
object: T,
key: K,
predicateCallback: (arg: T[K]) => boolean | T[K]
): IterableIterator<T[K]>;
export enum TaskInstanceState {
Dropped = 'dropped',
Canceled = 'canceled',

View File

@ -60,4 +60,84 @@ const isListUnique = <T>(list: Array<T> = []): boolean => new Set(list).size ===
*/
const compact = <T>(list: Array<T> = []): Array<T> => list.filter(item => item);
export { arrayMap, arrayFilter, arrayReduce, isListUnique, compact, arrayEvery, arraySome };
/**
* Defines the interface for options that may be passed into the chunk function
* @interface {IChunkArrayOptions}
*/
interface IChunkArrayOptions {
chunkSize?: 50 | 100;
context?: null | object;
}
/**
* Asynchronously traverses a list in small chunks ensuring that a list can be iterated over without
* blocking the browser main thread.
* @template T type of values in list to be iterated over
* @template U the type of the value that is produced by an iteration of the list
* @param {(arr?: Array<T>) => U} iterateeSync an iteratee that consumes an list and returns a value of type U
* @param {(res: U) => U} accumulator a function that combines the result of successive iterations of the original list
* @param {IChunkArrayOptions} [{chunkSize = 50, context = null}={chunkSize: 50, context: null}]
* @param {50 | 100} chunkSize the maximum size to chunk at a time
* @param {object | null} [context] the optional execution context for the iteratee invocation
* @return {(list: Array<T>) => Promise<U>}
*/
const chunkArrayAsync = <T, U>(
iterateeSync: (arr?: Array<T>) => U,
accumulator: (res: U) => U,
{ chunkSize = 50, context = null }: IChunkArrayOptions = { chunkSize: 50, context: null }
) => (list: Array<T>) =>
new Promise<U>(function(resolve) {
const queue = list.slice(0); // creates a shallow copy of the original list
const delay = 25;
let result: U;
setTimeout(function chunk() {
const startTime = +new Date();
do {
result = accumulator(iterateeSync.call(context, queue.splice(0, chunkSize)));
} while (queue.length && +new Date() + startTime < 50);
// recurse through list if there are more items left
return queue.length ? setTimeout(chunk, delay) : resolve(result);
}, delay);
});
/**
* Asynchronously traverse a list and accumulate another list based on the iteratee
* @template T the type of values in the original list
* @template U the type of values in the transformed list
* @param {(arr?: Array<T>) => Array<U>} iteratee consumes a list and returns a new list of values
* @return {(list: Array<T>, context?: any) => Promise<Array<U>>}
*/
const iterateArrayAsync = <T, U = T>(iteratee: (arr?: Array<T>) => Array<U>) => (
list: Array<T>,
context = null
): Promise<Array<U>> => {
const accumulator = (base: Array<U>) => (arr: Array<U>) => [...base, ...arr];
return chunkArrayAsync(iteratee, accumulator([]), { chunkSize: 50, context })(list);
};
/**
* Asynchronously traverse a list and accumulate a value of type U, applies to cases of reduction or accumulation
* @template T the type of values in the original list
* @template U the type of value to be produced by reducing the list
* @param {(arr?: Array<T>) => U} reducer consumes a list and produces a single value
* @return {(list: Array<T>, context?: any) => Promise<U>}
*/
const reduceArrayAsync = <T, U>(reducer: (arr?: Array<T>) => U) => (list: Array<T>, context = null): Promise<U> => {
const accumulator = (base: U) => (int: U) => Object.assign(base, int);
return chunkArrayAsync(reducer, accumulator(reducer.call(context)))(list);
};
export {
arrayMap,
arrayFilter,
arrayReduce,
isListUnique,
compact,
arrayEvery,
arraySome,
iterateArrayAsync,
reduceArrayAsync
};

View File

@ -1,3 +1,9 @@
/**
* Constant value for an empty regex source string
* @type {string}
*/
const emptyRegexSource = '(?:)';
/**
* Sanitizes a string to be used in creating a runtime regular expression pattern by escaping special characters
* @param {string} pattern the string intended to be used to new a RegExp object
@ -15,4 +21,4 @@ const buildSaneRegExp = (pattern: string, flags?: string): RegExp => new RegExp(
export default buildSaneRegExp;
export { sanitizeRegExp, buildSaneRegExp };
export { sanitizeRegExp, buildSaneRegExp, emptyRegexSource };

View File

@ -41,6 +41,13 @@ const isWhUrn = (candidateUrn: string): boolean => datasetUrnRegexWH.test(String
*/
const isLiUrn = (candidateUrn: string): boolean => datasetUrnRegexLI.test(String(candidateUrn));
/**
* Checks that a string matches the expected valuePatternRegex
* @param {string} candidate the supplied pattern string
* @return {boolean}
*/
const isValidCustomValuePattern = (candidate: string): boolean => !!candidate; // TODO:
/**
* Asserts that a provided string matches the urn pattern above
* @param {string} candidateUrn the string to test on
@ -165,6 +172,7 @@ export default isUrn;
export {
datasetUrnRegexWH,
datasetUrnRegexLI,
isValidCustomValuePattern,
isWhUrn,
isLiUrn,
buildLiUrn,

View File

@ -55,7 +55,7 @@
"ember-cli-typescript": "^1.0.6",
"ember-cli-uglify": "^2.0.0",
"ember-composable-helpers": "^2.1.0",
"ember-concurrency": "^0.8.12",
"ember-concurrency": "^0.8.15",
"ember-decorators": "^1.3.4",
"ember-export-application-global": "^2.0.0",
"ember-fetch": "^3.4.4",
@ -99,7 +99,7 @@
"ember-lodash": "4.17.2",
"ember-modal-dialog": "^2.4.1",
"ember-moment": "^7.5.0",
"ember-power-select": "^1.9.5",
"ember-power-select": "^1.10.4",
"ember-radio-button": "^1.2.1",
"ember-redux": "^2.10.1",
"ember-redux-actions": "^0.3.0",

View File

@ -635,8 +635,8 @@ babel-core@^5.0.0:
try-resolve "^1.0.0"
babel-core@^6.14.0, babel-core@^6.24.1, babel-core@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.0.tgz#af32f78b31a6fcef119c87b0fd8d9753f03a0bb8"
version "6.26.3"
resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.3.tgz#b2e2f09e342d0f0c88e2f02e067794125e75c207"
dependencies:
babel-code-frame "^6.26.0"
babel-generator "^6.26.0"
@ -648,15 +648,15 @@ babel-core@^6.14.0, babel-core@^6.24.1, babel-core@^6.26.0:
babel-traverse "^6.26.0"
babel-types "^6.26.0"
babylon "^6.18.0"
convert-source-map "^1.5.0"
debug "^2.6.8"
convert-source-map "^1.5.1"
debug "^2.6.9"
json5 "^0.5.1"
lodash "^4.17.4"
minimatch "^3.0.4"
path-is-absolute "^1.0.1"
private "^0.1.7"
private "^0.1.8"
slash "^1.0.0"
source-map "^0.5.6"
source-map "^0.5.7"
babel-eslint@^8.0.1:
version "8.2.1"
@ -670,8 +670,8 @@ babel-eslint@^8.0.1:
eslint-visitor-keys "^1.0.0"
babel-generator@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.0.tgz#ac1ae20070b79f6e3ca1d3269613053774f20dc5"
version "6.26.1"
resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90"
dependencies:
babel-messages "^6.23.0"
babel-runtime "^6.26.0"
@ -679,7 +679,7 @@ babel-generator@^6.26.0:
detect-indent "^4.0.0"
jsesc "^1.3.0"
lodash "^4.17.4"
source-map "^0.5.6"
source-map "^0.5.7"
trim-right "^1.0.1"
babel-helper-builder-binary-assignment-operator-visitor@^6.24.1:
@ -1006,8 +1006,8 @@ babel-plugin-transform-es2015-modules-amd@^6.22.0, babel-plugin-transform-es2015
babel-template "^6.24.1"
babel-plugin-transform-es2015-modules-commonjs@^6.23.0, babel-plugin-transform-es2015-modules-commonjs@^6.24.1:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz#0d8394029b7dc6abe1a97ef181e00758dd2e5d8a"
version "6.26.2"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz#58a793863a9e7ca870bdc5a881117ffac27db6f3"
dependencies:
babel-plugin-transform-strict-mode "^6.24.1"
babel-runtime "^6.26.0"
@ -1295,8 +1295,8 @@ binary-extensions@^1.0.0:
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.11.0.tgz#46aa1751fb6a2f93ee5e689bb1087d4b14c6c205"
"binaryextensions@1 || 2":
version "2.0.0"
resolved "https://registry.yarnpkg.com/binaryextensions/-/binaryextensions-2.0.0.tgz#e597d1a7a6a3558a2d1c7241a16c99965e6aa40f"
version "2.1.1"
resolved "https://registry.yarnpkg.com/binaryextensions/-/binaryextensions-2.1.1.tgz#3209a51ca4a4ad541a3b8d3d6a6d5b83a2485935"
bindings@~1.2.1:
version "1.2.1"
@ -1406,13 +1406,20 @@ boxen@^1.0.0:
term-size "^1.2.0"
widest-line "^2.0.0"
brace-expansion@^1.0.0, brace-expansion@^1.1.7:
brace-expansion@^1.0.0:
version "1.1.8"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292"
dependencies:
balanced-match "^1.0.0"
concat-map "0.0.1"
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
dependencies:
balanced-match "^1.0.0"
concat-map "0.0.1"
braces@^1.8.2:
version "1.8.5"
resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7"
@ -1472,7 +1479,7 @@ broccoli-babel-transpiler@^5.6.2:
rsvp "^3.5.0"
workerpool "^2.2.1"
broccoli-babel-transpiler@^6.0.0, broccoli-babel-transpiler@^6.1.2:
broccoli-babel-transpiler@^6.0.0:
version "6.1.2"
resolved "https://registry.yarnpkg.com/broccoli-babel-transpiler/-/broccoli-babel-transpiler-6.1.2.tgz#26019c045b5ea3e44cfef62821302f9bd483cabd"
dependencies:
@ -1487,6 +1494,21 @@ broccoli-babel-transpiler@^6.0.0, broccoli-babel-transpiler@^6.1.2:
rsvp "^3.5.0"
workerpool "^2.2.1"
broccoli-babel-transpiler@^6.1.2:
version "6.1.4"
resolved "https://registry.yarnpkg.com/broccoli-babel-transpiler/-/broccoli-babel-transpiler-6.1.4.tgz#8be8074c42abf2e17ff79b2d2a21df5c51143c82"
dependencies:
babel-core "^6.14.0"
broccoli-funnel "^1.0.0"
broccoli-merge-trees "^1.0.0"
broccoli-persistent-filter "^1.4.0"
clone "^2.0.0"
hash-for-dep "^1.0.2"
heimdalljs-logger "^0.1.7"
json-stable-stringify "^1.0.0"
rsvp "^3.5.0"
workerpool "^2.3.0"
broccoli-brocfile-loader@^0.18.0:
version "0.18.0"
resolved "https://registry.yarnpkg.com/broccoli-brocfile-loader/-/broccoli-brocfile-loader-0.18.0.tgz#2e86021c805c34ffc8d29a2fb721cf273e819e4b"
@ -1996,8 +2018,8 @@ can-symlink@^1.0.0:
tmp "0.0.28"
caniuse-lite@^1.0.30000792:
version "1.0.30000792"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000792.tgz#d0cea981f8118f3961471afbb43c9a1e5bbf0332"
version "1.0.30000832"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000832.tgz#22a277f1d623774cc9aea2f7c1a65cb1603c63b8"
capture-exit@^1.1.0:
version "1.2.0"
@ -2417,7 +2439,7 @@ continuable-cache@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/continuable-cache/-/continuable-cache-0.3.1.tgz#bd727a7faed77e71ff3985ac93351a912733ad0f"
convert-source-map@^1.1.0, convert-source-map@^1.5.0:
convert-source-map@^1.1.0, convert-source-map@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5"
@ -2442,8 +2464,8 @@ core-js@^1.0.0:
resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
core-js@^2.4.0, core-js@^2.5.0:
version "2.5.3"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.3.tgz#8acc38345824f16d8365b7c9b4259168e8ed603e"
version "2.5.5"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.5.tgz#b14dde936c640c0579a6b50cabcc132dd6127e3b"
core-object@^1.1.0:
version "1.1.0"
@ -2770,8 +2792,8 @@ ecc-jsbn@~0.1.1:
jsbn "~0.1.0"
editions@^1.1.1:
version "1.3.3"
resolved "https://registry.yarnpkg.com/editions/-/editions-1.3.3.tgz#0907101bdda20fac3cbe334c27cbd0688dc99a5b"
version "1.3.4"
resolved "https://registry.yarnpkg.com/editions/-/editions-1.3.4.tgz#3662cb592347c3168eb8e498a0ff73271d67f50b"
editor@~1.0.0:
version "1.0.0"
@ -2782,8 +2804,8 @@ ee-first@1.1.1:
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
electron-to-chromium@^1.3.30:
version "1.3.31"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.31.tgz#00d832cba9fe2358652b0c48a8816c8e3a037e9f"
version "1.3.44"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.44.tgz#ef6b150a60d523082388cadad88085ecd2fd4684"
elegant-spinner@^1.0.1:
version "1.0.1"
@ -2841,7 +2863,7 @@ ember-cli-babel@^5.1.5, ember-cli-babel@^5.1.6, ember-cli-babel@^5.1.7, ember-cl
ember-cli-version-checker "^1.0.2"
resolve "^1.1.2"
ember-cli-babel@^6.0.0, ember-cli-babel@^6.0.0-beta.4, ember-cli-babel@^6.0.0-beta.7, ember-cli-babel@^6.1.0, ember-cli-babel@^6.10.0, ember-cli-babel@^6.11.0, ember-cli-babel@^6.3.0, ember-cli-babel@^6.6.0, ember-cli-babel@^6.7.2, ember-cli-babel@^6.8.0, ember-cli-babel@^6.8.1, ember-cli-babel@^6.8.2, ember-cli-babel@^6.9.0, ember-cli-babel@^6.9.2:
ember-cli-babel@^6.0.0, ember-cli-babel@^6.0.0-beta.7, ember-cli-babel@^6.1.0, ember-cli-babel@^6.10.0, ember-cli-babel@^6.3.0, ember-cli-babel@^6.6.0, ember-cli-babel@^6.7.2, ember-cli-babel@^6.8.0, ember-cli-babel@^6.8.1, ember-cli-babel@^6.9.0, ember-cli-babel@^6.9.2:
version "6.11.0"
resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-6.11.0.tgz#79cb184bac3c05bfe181ddc306bac100ab1f9493"
dependencies:
@ -2859,7 +2881,7 @@ ember-cli-babel@^6.0.0, ember-cli-babel@^6.0.0-beta.4, ember-cli-babel@^6.0.0-be
ember-cli-version-checker "^2.1.0"
semver "^5.4.1"
ember-cli-babel@^6.0.0-beta.9:
ember-cli-babel@^6.0.0-beta.4, ember-cli-babel@^6.0.0-beta.9, ember-cli-babel@^6.11.0, ember-cli-babel@^6.8.2:
version "6.12.0"
resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-6.12.0.tgz#3adcdbe1278da1fcd0b9038f1360cb4ac5d4414c"
dependencies:
@ -3213,13 +3235,20 @@ ember-cli-version-checker@^1.0.2, ember-cli-version-checker@^1.2.0:
dependencies:
semver "^5.3.0"
ember-cli-version-checker@^2.0.0, ember-cli-version-checker@^2.1.0:
ember-cli-version-checker@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/ember-cli-version-checker/-/ember-cli-version-checker-2.1.0.tgz#fc79a56032f3717cf844ada7cbdec1a06fedb604"
dependencies:
resolve "^1.3.3"
semver "^5.3.0"
ember-cli-version-checker@^2.1.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ember-cli-version-checker/-/ember-cli-version-checker-2.1.2.tgz#305ce102390c66e4e0f1432dea9dc5c7c19fed98"
dependencies:
resolve "^1.3.3"
semver "^5.3.0"
ember-cli@~2.18.0:
version "2.18.0"
resolved "https://registry.yarnpkg.com/ember-cli/-/ember-cli-2.18.0.tgz#75c7cf7be8d195ae2eb072489e6b7243c95f63d4"
@ -3324,13 +3353,12 @@ ember-composable-helpers@^2.1.0:
broccoli-funnel "^1.0.1"
ember-cli-babel "^6.6.0"
ember-concurrency@^0.8.12:
version "0.8.12"
resolved "https://registry.yarnpkg.com/ember-concurrency/-/ember-concurrency-0.8.12.tgz#fb91180e5efeb1024cfa2cfb99d2fe6721930c91"
ember-concurrency@^0.8.12, ember-concurrency@^0.8.15:
version "0.8.18"
resolved "https://registry.yarnpkg.com/ember-concurrency/-/ember-concurrency-0.8.18.tgz#20a9ac4ced6496ea4ebe52e88f4524a473871396"
dependencies:
babel-core "^6.24.1"
ember-cli-babel "^6.8.2"
ember-getowner-polyfill "^2.0.0"
ember-maybe-import-regenerator "^0.1.5"
ember-cookies@^0.1.0:
@ -3535,8 +3563,8 @@ ember-pikaday@^2.2.4:
pikaday "^1.5.1"
ember-power-calendar@^0.7.1:
version "0.7.1"
resolved "https://registry.yarnpkg.com/ember-power-calendar/-/ember-power-calendar-0.7.1.tgz#7dcf1e4b7b8f54c3ccae2f6179002d3494f027ef"
version "0.7.2"
resolved "https://registry.yarnpkg.com/ember-power-calendar/-/ember-power-calendar-0.7.2.tgz#08a25fc41a95dc4466a7c2a2cbca6fe91cec3051"
dependencies:
ember-assign-helper "0.1.2"
ember-cli-babel "^6.11.0"
@ -3546,7 +3574,7 @@ ember-power-calendar@^0.7.1:
ember-moment "^7.5.0"
ember-native-dom-helpers "^0.5.10"
ember-power-select@^1.9.5:
ember-power-select@^1.10.4:
version "1.10.4"
resolved "https://registry.yarnpkg.com/ember-power-select/-/ember-power-select-1.10.4.tgz#7f0bb8c55279375391f2d97591ed65f727061579"
dependencies:
@ -3623,7 +3651,11 @@ ember-rfc176-data@^0.2.7:
version "0.2.7"
resolved "https://registry.yarnpkg.com/ember-rfc176-data/-/ember-rfc176-data-0.2.7.tgz#bd355bc9b473e08096b518784170a23388bc973b"
ember-rfc176-data@^0.3.0, ember-rfc176-data@^0.3.1:
ember-rfc176-data@^0.3.0:
version "0.3.2"
resolved "https://registry.yarnpkg.com/ember-rfc176-data/-/ember-rfc176-data-0.3.2.tgz#bde5538939529b263c142b53a47402f8127f8dce"
ember-rfc176-data@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/ember-rfc176-data/-/ember-rfc176-data-0.3.1.tgz#6a5a4b8b82ec3af34f3010965fa96b936ca94519"
@ -5258,12 +5290,18 @@ interpret@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614"
invariant@^2.2.0, invariant@^2.2.1, invariant@^2.2.2:
invariant@^2.2.0, invariant@^2.2.1:
version "2.2.2"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360"
dependencies:
loose-envify "^1.0.0"
invariant@^2.2.2:
version "2.2.4"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
dependencies:
loose-envify "^1.0.0"
invert-kv@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
@ -6387,10 +6425,14 @@ lodash@^3.10.0, lodash@^3.10.1, lodash@^3.9.3:
version "3.10.1"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
lodash@^4.0.0, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.5.1, lodash@^4.6.1, lodash@^4.8.0, lodash@~4.17.4:
lodash@^4.0.0, lodash@^4.13.1, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.5.1, lodash@^4.6.1, lodash@^4.8.0, lodash@~4.17.4:
version "4.17.4"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
lodash@^4.14.0, lodash@^4.17.4:
version "4.17.10"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7"
log-symbols@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18"
@ -7532,7 +7574,7 @@ printf@^0.2.3:
version "0.2.5"
resolved "https://registry.yarnpkg.com/printf/-/printf-0.2.5.tgz#c438ca2ca33e3927671db4ab69c0e52f936a4f0f"
private@^0.1.6, private@^0.1.7, private@~0.1.5:
private@^0.1.6, private@^0.1.8, private@~0.1.5:
version "0.1.8"
resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
@ -8140,12 +8182,18 @@ resolve@1.1.x:
version "1.1.7"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
resolve@1.5.0, resolve@^1.1.2, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.3.0, resolve@^1.3.3, resolve@^1.4.0, resolve@^1.5.0:
resolve@1.5.0, resolve@^1.1.2, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.3.0, resolve@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.5.0.tgz#1f09acce796c9a762579f31b2c1cc4c3cddf9f36"
dependencies:
path-parse "^1.0.5"
resolve@^1.3.3, resolve@^1.4.0:
version "1.7.1"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3"
dependencies:
path-parse "^1.0.5"
restore-cursor@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541"
@ -8614,7 +8662,7 @@ source-map@0.5.6:
version "0.5.6"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"
source-map@^0.5.0, source-map@^0.5.6, source-map@~0.5.0, source-map@~0.5.1, source-map@~0.5.6:
source-map@^0.5.0, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.0, source-map@~0.5.1, source-map@~0.5.6:
version "0.5.7"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
@ -8905,8 +8953,8 @@ symbol-observable@^1.0.3, symbol-observable@^1.0.4:
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.1.0.tgz#5c68fd8d54115d9dfb72a84720549222e8db9b32"
symlink-or-copy@^1.0.0, symlink-or-copy@^1.0.1, symlink-or-copy@^1.1.8:
version "1.1.8"
resolved "https://registry.yarnpkg.com/symlink-or-copy/-/symlink-or-copy-1.1.8.tgz#cabe61e0010c1c023c173b25ee5108b37f4b4aa3"
version "1.2.0"
resolved "https://registry.yarnpkg.com/symlink-or-copy/-/symlink-or-copy-1.2.0.tgz#5d49108e2ab824a34069b68974486c290020b393"
sync-disk-cache@^1.3.2:
version "1.3.2"
@ -9025,8 +9073,8 @@ text-table@~0.2.0:
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
"textextensions@1 || 2":
version "2.1.0"
resolved "https://registry.yarnpkg.com/textextensions/-/textextensions-2.1.0.tgz#1be0dc2a0dc244d44be8a09af6a85afb93c4dbc3"
version "2.2.0"
resolved "https://registry.yarnpkg.com/textextensions/-/textextensions-2.2.0.tgz#38ac676151285b658654581987a0ce1a4490d286"
through2@^2.0.0:
version "2.0.3"
@ -9514,7 +9562,7 @@ wordwrap@~0.0.2:
version "0.0.3"
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107"
workerpool@^2.2.1:
workerpool@^2.2.1, workerpool@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-2.3.0.tgz#86c5cbe946b55e7dc9d12b1936c8801a6e2d744d"
dependencies: