From 933982bc0deea47dc3217ebf162983c3df95a64a Mon Sep 17 00:00:00 2001 From: Seyi Adebajo Date: Wed, 2 May 2018 09:45:26 -0700 Subject: [PATCH] adds functions for async array traversal. adds CUSTOM pattern field for field formats. updates ember-concurrency to add support for waitForProperty, and updates typings. --- .../dataset-compliance-field-tag.ts | 50 +++++- .../dataset-compliance-rollup-row.ts | 8 +- .../app/components/dataset-compliance.ts | 168 ++++++++++++------ .../datasets/containers/dataset-compliance.ts | 9 +- .../app/constants/dataset-compliance.ts | 26 +-- .../app/constants/datasets/compliance.ts | 3 +- .../dataset-compliance/_compliance-table.scss | 38 ++++ .../styles/components/tooltips/_tooltip.scss | 4 +- .../dataset-compliance-field-tag.hbs | 3 + .../-dataset-compliance-entities.hbs | 34 +++- .../app/typings/api/datasets/compliance.d.ts | 2 + .../app/typings/app/dataset-columns.d.ts | 2 +- .../app/typings/app/dataset-compliance.d.ts | 8 +- .../app/typings/ember-concurrency.d.ts | 16 +- wherehows-web/app/utils/array.ts | 82 ++++++++- wherehows-web/app/utils/validators/regexp.ts | 8 +- wherehows-web/app/utils/validators/urn.ts | 8 + wherehows-web/package.json | 4 +- wherehows-web/yarn.lock | 138 +++++++++----- 19 files changed, 483 insertions(+), 128 deletions(-) diff --git a/wherehows-web/app/components/dataset-compliance-field-tag.ts b/wherehows-web/app/components/dataset-compliance-field-tag.ts index d8e018ae33..34e5778ed2 100644 --- a/wherehows-web/app/components/dataset-compliance-field-tag.ts +++ b/wherehows-web/app/components/dataset-compliance-field-tag.ts @@ -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} @@ -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} + */ + 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} @@ -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()); + } + } } diff --git a/wherehows-web/app/components/dataset-compliance-rollup-row.ts b/wherehows-web/app/components/dataset-compliance-rollup-row.ts index 40cf159928..bdf3186588 100644 --- a/wherehows-web/app/components/dataset-compliance-rollup-row.ts +++ b/wherehows-web/app/components/dataset-compliance-rollup-row.ts @@ -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} */ - hasNoneTag: ComputedProperty = computed('fieldChangeSet', function( + hasNoneTag: ComputedProperty = 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'), { diff --git a/wherehows-web/app/components/dataset-compliance.ts b/wherehows-web/app/components/dataset-compliance.ts index 255c02f2d8..c4f8194174 100644 --- a/wherehows-web/app/components/dataset-compliance.ts +++ b/wherehows-web/app/components/dataset-compliance.ts @@ -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 = >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 = []; + /** + * 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} + * Task to retrieve column fields async and set values on Component + * @type {Task, () => TaskInstance>>} + * @memberof DatasetCompliance */ - columnIdFieldsToCurrentPrivacyPolicy: ComputedProperty = computed( - `{schemaFieldNamesMappedToDataTypes,${policyComplianceEntitiesKey}.[]}`, - function(this: DatasetCompliance): ISchemaFieldsToPolicy { - const { - complianceEntities = [], - modifiedTime - }: Pick = 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 { + // 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 = get( + this, + 'complianceInfo' + )!; + const renameFieldNameAttr = ({ + fieldName, + dataType + }: Pick): { + identifierField: IDatasetColumn['fieldName']; + dataType: IDatasetColumn['dataType']; + } => ({ + identifierField: fieldName, + dataType + }); + const columnProps: Array = 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>} + * @type {Array} * @memberof DatasetCompliance */ - foldedChangeSet: ComputedProperty> = computed( - 'filteredChangeSet', - function(this: DatasetCompliance): Array { - return sortFoldedChangeSetTuples(foldComplianceChangeSets(get(this, 'filteredChangeSet'))); - } - ); + foldedChangeSet: Array; + + /** + * Task to retrieve platform policies and set supported policies for the current platform + * @type {Task, () => TaskInstance>>} + * @memberof DatasetCompliance + */ + foldChangeSetTask = task(function*(this: DatasetCompliance): IterableIterator { + //@ts-ignore dot notation for property access + yield waitForProperty(this, 'columnFieldsToCompliancePolicyTask.isIdle'); + const filteredChangeSet = get(this, 'filteredChangeSet'); + const foldedChangeSet: Array = 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 = 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; }, /** diff --git a/wherehows-web/app/components/datasets/containers/dataset-compliance.ts b/wherehows-web/app/components/datasets/containers/dataset-compliance.ts index 75ff697afe..5f3799a1a5 100644 --- a/wherehows-web/app/components/datasets/containers/dataset-compliance.ts +++ b/wherehows-web/app/components/datasets/containers/dataset-compliance.ts @@ -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, (a?: any) => TaskInstance>>} */ - getDatasetSchemaTask = task(function*(this: DatasetComplianceContainer): IterableIterator> { + getDatasetSchemaTask = task(function*( + this: DatasetComplianceContainer + ): IterableIterator[]>> { 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 diff --git a/wherehows-web/app/constants/dataset-compliance.ts b/wherehows-web/app/constants/dataset-compliance.ts index 2a6db84555..3dc93263e6 100644 --- a/wherehows-web/app/constants/dataset-compliance.ts +++ b/wherehows-web/app/constants/dataset-compliance.ts @@ -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} changeSet * @returns {Array} */ -const foldComplianceChangeSets = ( +const foldComplianceChangeSets = async ( changeSet: Array -): Array => - Object.entries>(arrayReduce(foldComplianceChangeSetToField, {})(changeSet)); +): Promise> => + Object.entries>( + 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 => + 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 diff --git a/wherehows-web/app/constants/datasets/compliance.ts b/wherehows-web/app/constants/datasets/compliance.ts index c7eb39d661..64fb81adc6 100644 --- a/wherehows-web/app/constants/datasets/compliance.ts +++ b/wherehows-web/app/constants/datasets/compliance.ts @@ -25,7 +25,8 @@ enum IdLogicalType { Numeric = 'NUMERIC', Urn = 'URN', ReversedUrn = 'REVERSED_URN', - CompositeUrn = 'COMPOSITE_URN' + CompositeUrn = 'COMPOSITE_URN', + Custom = 'CUSTOM' } /** diff --git a/wherehows-web/app/styles/components/dataset-compliance/_compliance-table.scss b/wherehows-web/app/styles/components/dataset-compliance/_compliance-table.scss index 7a09e33f63..882e56e866 100644 --- a/wherehows-web/app/styles/components/dataset-compliance/_compliance-table.scss +++ b/wherehows-web/app/styles/components/dataset-compliance/_compliance-table.scss @@ -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%; diff --git a/wherehows-web/app/styles/components/tooltips/_tooltip.scss b/wherehows-web/app/styles/components/tooltips/_tooltip.scss index 37c410398a..6d1709e289 100644 --- a/wherehows-web/app/styles/components/tooltips/_tooltip.scss +++ b/wherehows-web/app/styles/components/tooltips/_tooltip.scss @@ -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; diff --git a/wherehows-web/app/templates/components/dataset-compliance-field-tag.hbs b/wherehows-web/app/templates/components/dataset-compliance-field-tag.hbs index 081e84c14c..d4741c0e16 100644 --- a/wherehows-web/app/templates/components/dataset-compliance-field-tag.hbs +++ b/wherehows-web/app/templates/components/dataset-compliance-field-tag.hbs @@ -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") diff --git a/wherehows-web/app/templates/datasets/dataset-compliance/-dataset-compliance-entities.hbs b/wherehows-web/app/templates/datasets/dataset-compliance/-dataset-compliance-entities.hbs index ab58d3c272..637292cb6e 100644 --- a/wherehows-web/app/templates/datasets/dataset-compliance/-dataset-compliance-entities.hbs +++ b/wherehows-web/app/templates/datasets/dataset-compliance/-dataset-compliance-entities.hbs @@ -81,7 +81,8 @@ onTagIdentifierTypeChange=(action "tagIdentifierChanged") onSuggestionIntent=(action "onFieldSuggestionIntentChange") as |row| }} - + {{#row.cell}} {{#if row.isReadonly}} @@ -94,7 +95,8 @@ (and row.suggestion (and (not row.suggestionMatchesCurrentValue) (not row.suggestionResolution)))}} - + {{else}} @@ -102,7 +104,8 @@ {{#if row.isReviewRequested}} - + {{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 @@ }} + {{#if tagRowComponent.showCustomInput}} +
+ {{#if (or (not isEditing) row.isReadonly)}} + {{tag.valuePattern}} + {{else}} +
+ +
+ + {{#if tagRowComponent.valuePatternError}} +
+ {{tagRowComponent.valuePatternError}} +
+ {{/if}} + {{/if}} +
+ {{/if}} + {{#unless tagRowComponent.isTagFormatMissing}} Owner: diff --git a/wherehows-web/app/typings/api/datasets/compliance.d.ts b/wherehows-web/app/typings/api/datasets/compliance.d.ts index bcc84bf088..d4ef20eb53 100644 --- a/wherehows-web/app/typings/api/datasets/compliance.d.ts +++ b/wherehows-web/app/typings/api/datasets/compliance.d.ts @@ -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; } /** diff --git a/wherehows-web/app/typings/app/dataset-columns.d.ts b/wherehows-web/app/typings/app/dataset-columns.d.ts index e1f95f6584..d16ab25017 100644 --- a/wherehows-web/app/typings/app/dataset-columns.d.ts +++ b/wherehows-web/app/typings/app/dataset-columns.d.ts @@ -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 { diff --git a/wherehows-web/app/typings/app/dataset-compliance.d.ts b/wherehows-web/app/typings/app/dataset-compliance.d.ts index 8f14a58c3e..fd3b76bb13 100644 --- a/wherehows-web/app/typings/app/dataset-compliance.d.ts +++ b/wherehows-web/app/typings/app/dataset-compliance.d.ts @@ -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; diff --git a/wherehows-web/app/typings/ember-concurrency.d.ts b/wherehows-web/app/typings/ember-concurrency.d.ts index 8521bbb310..f6d8faf4d1 100644 --- a/wherehows-web/app/typings/ember-concurrency.d.ts +++ b/wherehows-web/app/typings/ember-concurrency.d.ts @@ -1,8 +1,22 @@ declare module 'ember-concurrency' { - export function timeout(delay: number): Promise; import ComputedProperty from '@ember/object/computed'; import RSVP from 'rsvp'; + type ComputedProperties = { [K in keyof T]: ComputedProperty | T[K] }; + + export function timeout(delay: number): Promise; + + export function waitForProperty( + object: ComputedProperties, + key: K, + predicateCallback: (arg: T[K]) => boolean | T[K] + ): IterableIterator; + export function waitForProperty( + object: T, + key: K, + predicateCallback: (arg: T[K]) => boolean | T[K] + ): IterableIterator; + export enum TaskInstanceState { Dropped = 'dropped', Canceled = 'canceled', diff --git a/wherehows-web/app/utils/array.ts b/wherehows-web/app/utils/array.ts index 8bd5d61f37..0a8822f4e0 100644 --- a/wherehows-web/app/utils/array.ts +++ b/wherehows-web/app/utils/array.ts @@ -60,4 +60,84 @@ const isListUnique = (list: Array = []): boolean => new Set(list).size === */ const compact = (list: Array = []): Array => 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) => 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) => Promise} + */ +const chunkArrayAsync = ( + iterateeSync: (arr?: Array) => U, + accumulator: (res: U) => U, + { chunkSize = 50, context = null }: IChunkArrayOptions = { chunkSize: 50, context: null } +) => (list: Array) => + new Promise(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) => Array} iteratee consumes a list and returns a new list of values + * @return {(list: Array, context?: any) => Promise>} + */ +const iterateArrayAsync = (iteratee: (arr?: Array) => Array) => ( + list: Array, + context = null +): Promise> => { + const accumulator = (base: Array) => (arr: Array) => [...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) => U} reducer consumes a list and produces a single value + * @return {(list: Array, context?: any) => Promise} + */ +const reduceArrayAsync = (reducer: (arr?: Array) => U) => (list: Array, context = null): Promise => { + 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 +}; diff --git a/wherehows-web/app/utils/validators/regexp.ts b/wherehows-web/app/utils/validators/regexp.ts index b780e3db09..04eebf4cdf 100644 --- a/wherehows-web/app/utils/validators/regexp.ts +++ b/wherehows-web/app/utils/validators/regexp.ts @@ -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 }; diff --git a/wherehows-web/app/utils/validators/urn.ts b/wherehows-web/app/utils/validators/urn.ts index 9f97ba1fe0..1e7f4672d8 100644 --- a/wherehows-web/app/utils/validators/urn.ts +++ b/wherehows-web/app/utils/validators/urn.ts @@ -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, diff --git a/wherehows-web/package.json b/wherehows-web/package.json index a1b5c481d4..380a391e95 100644 --- a/wherehows-web/package.json +++ b/wherehows-web/package.json @@ -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", diff --git a/wherehows-web/yarn.lock b/wherehows-web/yarn.lock index a6612c1c22..c0bf60717a 100644 --- a/wherehows-web/yarn.lock +++ b/wherehows-web/yarn.lock @@ -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: