Merge pull request #1427 from theseyi/obfuscation-feedback-ui

obfuscation feedback ui: accepting none should replace multi tagged field with single none tag
This commit is contained in:
Seyi Adebajo 2018-10-01 16:27:53 -07:00 committed by GitHub
commit fc6cbcff5f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 69 additions and 48 deletions

View File

@ -1,5 +1,5 @@
import Component from '@ember/component'; import Component from '@ember/component';
import { IAvatar, IAvatarDropDownAction } from 'wherehows-web/typings/app/avatars'; import { IAvatar } from 'wherehows-web/typings/app/avatars';
import { action, computed } from '@ember-decorators/object'; import { action, computed } from '@ember-decorators/object';
import { IDropDownOption } from 'wherehows-web/typings/app/dataset-compliance'; import { IDropDownOption } from 'wherehows-web/typings/app/dataset-compliance';
import { classNames } from '@ember-decorators/component'; import { classNames } from '@ember-decorators/component';
@ -18,6 +18,12 @@ export default class StackedAvatarsList extends Component {
*/ */
avatars: Array<IAvatar>; avatars: Array<IAvatar>;
/**
* External action to selection of an avatar's menu option
* @type {(avatar: IAvatar, option?: IDropDownOption<any>) => any}
*/
handleAvatarOptionSelection: (avatar: IAvatar, option?: IDropDownOption<any>) => any;
constructor() { constructor() {
super(...arguments); super(...arguments);
@ -64,13 +70,11 @@ export default class StackedAvatarsList extends Component {
/** /**
* Handler to invoke IAvatarDropDownAction instance when the drop down option is selected * Handler to invoke IAvatarDropDownAction instance when the drop down option is selected
* @param {IAvatar} avatar the avatar item selected from the list * @param {IAvatar} avatar the avatar item selected from the list
* @param {(IDropDownOption<IAvatarDropDownAction> | void)} selectedOption drop down option selected * @param {(IDropDownOption<any>)} [selectedOption] drop down option selected
* @memberof StackedAvatarsList * @memberof StackedAvatarsList
*/ */
@action @action
onAvatarOptionSelected(avatar: IAvatar, selectedOption: IDropDownOption<IAvatarDropDownAction> | void): void { onAvatarOptionSelected(avatar: IAvatar, selectedOption?: IDropDownOption<any>): void {
const { value } = selectedOption || { value: (a: IAvatar) => a }; this.handleAvatarOptionSelection(avatar, selectedOption);
value(avatar);
} }
} }

View File

@ -3,6 +3,7 @@ import { get, set } from '@ember/object';
import ComputedProperty, { gte } from '@ember/object/computed'; import ComputedProperty, { gte } from '@ember/object/computed';
import { TaskInstance, TaskProperty } from 'ember-concurrency'; import { TaskInstance, TaskProperty } from 'ember-concurrency';
import { action, computed } from '@ember-decorators/object'; import { action, computed } from '@ember-decorators/object';
import moment from 'moment';
import { import {
IAccessControlAccessTypeOption, IAccessControlAccessTypeOption,
IAccessControlEntry, IAccessControlEntry,
@ -12,8 +13,7 @@ import { getDefaultRequestAccessControlEntry } from 'wherehows-web/utils/dataset
import { IAvatar } from 'wherehows-web/typings/app/avatars'; import { IAvatar } from 'wherehows-web/typings/app/avatars';
import { arrayMap } from 'wherehows-web/utils/array'; import { arrayMap } from 'wherehows-web/utils/array';
import { IAppConfig } from 'wherehows-web/typings/api/configurator/configurator'; import { IAppConfig } from 'wherehows-web/typings/api/configurator/configurator';
import { getAvatarProps } from 'wherehows-web/constants/avatars/avatars'; import { makeAvatar } from 'wherehows-web/constants/avatars/avatars';
import moment from 'moment';
/** /**
* Date object with the minimum selectable date for acl request expiration, * Date object with the minimum selectable date for acl request expiration,
@ -141,7 +141,7 @@ export default class DatasetAclAccess extends Component {
const { acls, avatarProperties } = this; const { acls, avatarProperties } = this;
const aclWithAvatar = (acl: IAccessControlEntry): IAccessControlEntry & Record<'avatar', IAvatar> => ({ const aclWithAvatar = (acl: IAccessControlEntry): IAccessControlEntry & Record<'avatar', IAvatar> => ({
...acl, ...acl,
avatar: getAvatarProps(avatarProperties!)({ userName: acl.principal }) avatar: makeAvatar(avatarProperties!)({ userName: acl.principal })
}); });
return avatarProperties ? arrayMap(aclWithAvatar)(acls) : []; return avatarProperties ? arrayMap(aclWithAvatar)(acls) : [];

View File

@ -22,7 +22,7 @@ import { OwnerSource, OwnerType } from 'wherehows-web/utils/api/datasets/owners'
import Notifications, { NotificationEvent } from 'wherehows-web/services/notifications'; import Notifications, { NotificationEvent } from 'wherehows-web/services/notifications';
import { noop } from 'wherehows-web/utils/helpers/functions'; import { noop } from 'wherehows-web/utils/helpers/functions';
import { IAppConfig } from 'wherehows-web/typings/api/configurator/configurator'; import { IAppConfig } from 'wherehows-web/typings/api/configurator/configurator';
import { getAvatarProps } from 'wherehows-web/constants/avatars/avatars'; import { makeAvatar } from 'wherehows-web/constants/avatars/avatars';
import { OwnerWithAvatarRecord } from 'wherehows-web/typings/app/datasets/owners'; import { OwnerWithAvatarRecord } from 'wherehows-web/typings/app/datasets/owners';
type Comparator = -1 | 0 | 1; type Comparator = -1 | 0 | 1;
@ -177,7 +177,7 @@ export default class DatasetAuthors extends Component {
return { return {
owner, owner,
avatar: avatarProperties avatar: avatarProperties
? getAvatarProps(avatarProperties)({ userName: owner.userName }) ? makeAvatar(avatarProperties)({ userName: owner.userName })
: { imageUrl: '', imageUrlFallback: '/assets/assets/images/default_avatar.png' } : { imageUrl: '', imageUrlFallback: '/assets/assets/images/default_avatar.png' }
}; };
}; };

View File

@ -1,5 +1,5 @@
import Component from '@ember/component'; import Component from '@ember/component';
import ComputedProperty, { alias, equal, bool, mapBy } from '@ember/object/computed'; import ComputedProperty, { equal, bool, mapBy } from '@ember/object/computed';
import { get, getWithDefault, getProperties, computed } from '@ember/object'; import { get, getWithDefault, getProperties, computed } from '@ember/object';
import { action } from '@ember-decorators/object'; import { action } from '@ember-decorators/object';
import { import {
@ -21,8 +21,9 @@ import { getTagSuggestions } from 'wherehows-web/utils/datasets/compliance-sugge
import { IColumnFieldProps } from 'wherehows-web/typings/app/dataset-columns'; import { IColumnFieldProps } from 'wherehows-web/typings/app/dataset-columns';
import { fieldTagsHaveIdentifierType } from 'wherehows-web/constants/dataset-compliance'; import { fieldTagsHaveIdentifierType } from 'wherehows-web/constants/dataset-compliance';
import { IComplianceDataType } from 'wherehows-web/typings/api/list/compliance-datatypes'; import { IComplianceDataType } from 'wherehows-web/typings/api/list/compliance-datatypes';
import { arrayReduce } from 'wherehows-web/utils/array'; import { arrayEach, arrayReduce } from 'wherehows-web/utils/array';
import { IComplianceEntity } from 'wherehows-web/typings/api/datasets/compliance'; import { IComplianceEntity } from 'wherehows-web/typings/api/datasets/compliance';
import { alias } from '@ember-decorators/object/computed';
export default class DatasetComplianceRollupRow extends Component.extend({ export default class DatasetComplianceRollupRow extends Component.extend({
tagName: '' tagName: ''
@ -118,7 +119,8 @@ export default class DatasetComplianceRollupRow extends Component.extend({
* @type {ComputedProperty<string>} * @type {ComputedProperty<string>}
* @memberof DatasetComplianceRollupRow * @memberof DatasetComplianceRollupRow
*/ */
identifierField: ComputedProperty<string> = alias('field.firstObject'); @alias('field.firstObject')
identifierField: string;
/** /**
* References the second item in the IdentifierFieldWithFieldChangeSetTuple type, this is the list of tags * References the second item in the IdentifierFieldWithFieldChangeSetTuple type, this is the list of tags
@ -126,7 +128,8 @@ export default class DatasetComplianceRollupRow extends Component.extend({
* @type {ComputedProperty<Array<IComplianceChangeSet>>} * @type {ComputedProperty<Array<IComplianceChangeSet>>}
* @memberof DatasetComplianceRollupRow * @memberof DatasetComplianceRollupRow
*/ */
fieldChangeSet: ComputedProperty<Array<IComplianceChangeSet>> = alias('field.1'); @alias('field.1')
fieldChangeSet: Array<IComplianceChangeSet>;
/** /**
* References the first tag in the change set, this is the primary tag for the field and should not be deleted * References the first tag in the change set, this is the primary tag for the field and should not be deleted
@ -134,7 +137,8 @@ export default class DatasetComplianceRollupRow extends Component.extend({
* @type {ComputedProperty<IComplianceChangeSet>} * @type {ComputedProperty<IComplianceChangeSet>}
* @memberof DatasetComplianceRollupRow * @memberof DatasetComplianceRollupRow
*/ */
fieldProps: ComputedProperty<IComplianceChangeSet> = alias('fieldChangeSet.firstObject'); @alias('fieldChangeSet.firstObject')
fieldProps: IComplianceChangeSet;
/** /**
* Aliases the dataType property on the first item in the field change set, this should available * Aliases the dataType property on the first item in the field change set, this should available
@ -142,7 +146,8 @@ export default class DatasetComplianceRollupRow extends Component.extend({
* @type {ComputedProperty<string>} * @type {ComputedProperty<string>}
* @memberof DatasetComplianceRollupRow * @memberof DatasetComplianceRollupRow
*/ */
dataType: ComputedProperty<string> = alias('fieldProps.dataType'); @alias('fieldProps.dataType')
dataType: string;
/** /**
* Checks if the field has only one tag * Checks if the field has only one tag
@ -178,9 +183,8 @@ export default class DatasetComplianceRollupRow extends Component.extend({
* @type {(ComputedProperty<SuggestionIntent | void>)} * @type {(ComputedProperty<SuggestionIntent | void>)}
* @memberof DatasetComplianceRollupRow * @memberof DatasetComplianceRollupRow
*/ */
suggestionAuthority: ComputedProperty<IComplianceChangeSet['suggestionAuthority']> = alias( @alias('fieldProps.suggestionAuthority')
'fieldProps.suggestionAuthority' suggestionAuthority: IComplianceChangeSet['suggestionAuthority'];
);
/** /**
* Extracts the field suggestions into a cached computed property, if a suggestion exists * Extracts the field suggestions into a cached computed property, if a suggestion exists
@ -352,17 +356,19 @@ export default class DatasetComplianceRollupRow extends Component.extend({
// Field has only one tag, that tag has an identifierType // Field has only one tag, that tag has an identifierType
const updateDefault = hasSingleTag && fieldTagsHaveIdentifierType(get(this, 'fieldChangeSet')); const updateDefault = hasSingleTag && fieldTagsHaveIdentifierType(get(this, 'fieldChangeSet'));
// Identifier type and changeSet does not already have suggested type // Suggested identifierType exists but changeSet does not already have the suggested type
if (identifierType && !suggestedValuesInChangeSet.includes(identifierType)) { if (identifierType && !suggestedValuesInChangeSet.includes(identifierType)) {
if (updateDefault) { if (updateDefault) {
get(this, 'onTagIdentifierTypeChange')(get(this, 'fieldProps'), { get(this, 'onTagIdentifierTypeChange')(get(this, 'fieldProps'), {
value: <ComplianceFieldIdValue>identifierType value: <ComplianceFieldIdValue>identifierType
}); });
} else { } else {
// If suggested value is ComplianceFieldIdValue.None then do not add // If suggested value is ComplianceFieldIdValue.None, remove all other annotations first before tagging field as none
if (identifierType !== ComplianceFieldIdValue.None) { if (identifierType === ComplianceFieldIdValue.None) {
this.actions.onAddFieldTag.call(this, { identifierType, logicalType }); arrayEach(this.actions.onRemoveFieldTag.bind(this))(this.fieldChangeSet);
} }
this.actions.onAddFieldTag.call(this, { identifierType, logicalType });
} }
} }
} }

View File

@ -1,18 +1,20 @@
import Component from '@ember/component'; import Component from '@ember/component';
import { set } from '@ember/object'; import { set } from '@ember/object';
import { classNames } from '@ember-decorators/component'; import { classNames } from '@ember-decorators/component';
import { computed } from '@ember-decorators/object'; import { computed, action } from '@ember-decorators/object';
import { assert } from '@ember/debug'; import { assert } from '@ember/debug';
import { task } from 'ember-concurrency'; import { task } from 'ember-concurrency';
import { readDatasetOwnersByUrn } from 'wherehows-web/utils/api/datasets/owners'; import { readDatasetOwnersByUrn } from 'wherehows-web/utils/api/datasets/owners';
import { arrayMap, arrayPipe } from 'wherehows-web/utils/array'; import { arrayMap, arrayPipe } from 'wherehows-web/utils/array';
import { IAvatar } from 'wherehows-web/typings/app/avatars'; import { IAvatar } from 'wherehows-web/typings/app/avatars';
import { IOwner, IOwnerResponse } from 'wherehows-web/typings/api/datasets/owners'; import { IOwner, IOwnerResponse } from 'wherehows-web/typings/api/datasets/owners';
import { getAvatarProps } from 'wherehows-web/constants/avatars/avatars'; import { makeAvatar } from 'wherehows-web/constants/avatars/avatars';
import { confirmedOwners, avatarWithDropDownOption } from 'wherehows-web/constants/datasets/owner'; import { confirmedOwners, avatarWithDropDownOption } from 'wherehows-web/constants/datasets/owner';
import { containerDataSource } from 'wherehows-web/utils/components/containers/data-source'; import { containerDataSource } from 'wherehows-web/utils/components/containers/data-source';
import { decodeUrn, isLiUrn } from 'wherehows-web/utils/validators/urn'; import { decodeUrn, isLiUrn } from 'wherehows-web/utils/validators/urn';
import { IAppConfig } from 'wherehows-web/typings/api/configurator/configurator'; import { IAppConfig } from 'wherehows-web/typings/api/configurator/configurator';
import { buildMailToUrl } from 'wherehows-web/utils/helpers/email';
import { IDropDownOption } from 'wherehows-web/typings/app/dataset-compliance';
@classNames('dataset-owner-list') @classNames('dataset-owner-list')
@containerDataSource('getOwnersTask') @containerDataSource('getOwnersTask')
@ -59,12 +61,28 @@ export default class DatasetOwnerListContainer extends Component {
@computed('owners') @computed('owners')
get avatars(): Array<IAvatar> { get avatars(): Array<IAvatar> {
const { avatarEntityProps, owners } = this; const { avatarEntityProps, owners } = this;
const [getAvatarProperties, augmentAvatarsWithDropDownOption] = [ const [makeAvatars, augmentAvatarsWithDropDownOption] = [
arrayMap(getAvatarProps(avatarEntityProps)), arrayMap(makeAvatar(avatarEntityProps)),
arrayMap(avatarWithDropDownOption) arrayMap(avatarWithDropDownOption)
]; ];
return arrayPipe(getAvatarProperties, augmentAvatarsWithDropDownOption)(owners); return arrayPipe(makeAvatars, augmentAvatarsWithDropDownOption)(owners);
}
/**
* Handles user selection of an option for each owner avatar
* @param {IAvatar} avatar owner's avatar instance
* @param {IDropDownOption<any>} [_option] unused optional parameter indicating selected option
* @returns {(Window | null)}
* @memberof DatasetOwnerListContainer
*/
@action
onOwnerOptionSelected(avatar: IAvatar, _option?: IDropDownOption<any>): Window | null {
// if the owner avatar does not have an email then a null value is returned with no action performed
const emailOwner = ({ email }: IAvatar): Window | null =>
email ? window.open(buildMailToUrl({ to: email || '' }), '_blank') : null;
return emailOwner(avatar);
} }
/** /**

View File

@ -9,7 +9,7 @@ import { IAppConfig } from 'wherehows-web/typings/api/configurator/configurator'
* @param {IAppConfig.userEntityProps.aviUrlFallback} aviUrlFallback * @param {IAppConfig.userEntityProps.aviUrlFallback} aviUrlFallback
* @return {IAvatar} * @return {IAvatar}
*/ */
const getAvatarProps = ({ aviUrlPrimary, aviUrlFallback = '' }: IAppConfig['userEntityProps']) => ( const makeAvatar = ({ aviUrlPrimary, aviUrlFallback = '' }: IAppConfig['userEntityProps']) => (
object: Partial<IAvatar> object: Partial<IAvatar>
): IAvatar => { ): IAvatar => {
const props = pick(object, ['email', 'userName', 'name']); const props = pick(object, ['email', 'userName', 'name']);
@ -22,4 +22,4 @@ const getAvatarProps = ({ aviUrlPrimary, aviUrlFallback = '' }: IAppConfig['user
}; };
}; };
export { getAvatarProps }; export { makeAvatar };

View File

@ -3,7 +3,6 @@ import { IOwner } from 'wherehows-web/typings/api/datasets/owners';
import { OwnerIdType, OwnerSource, OwnerType, OwnerUrnNamespace } from 'wherehows-web/utils/api/datasets/owners'; import { OwnerIdType, OwnerSource, OwnerType, OwnerUrnNamespace } from 'wherehows-web/utils/api/datasets/owners';
import { arrayFilter, isListUnique } from 'wherehows-web/utils/array'; import { arrayFilter, isListUnique } from 'wherehows-web/utils/array';
import { IAvatar } from 'wherehows-web/typings/app/avatars'; import { IAvatar } from 'wherehows-web/typings/app/avatars';
import { buildMailToUrl } from 'wherehows-web/utils/helpers/email';
/** /**
* Initial user name for candidate owners * Initial user name for candidate owners
@ -164,9 +163,7 @@ const avatarWithDropDownOption = (avatar: IAvatar): IAvatar & Required<Pick<IAva
...avatar, ...avatar,
avatarOptions: [ avatarOptions: [
{ {
// if the owner avatar does not have an email then a null value is returned with no action performed value: email,
value: ({ email }: IAvatar): Window | null =>
email ? window.open(buildMailToUrl({ to: email || '' }), '_blank') : null,
label: email label: email
} }
] ]

View File

@ -5,7 +5,7 @@ import { get } from '@ember/object';
import ApplicationRouteMixin from 'ember-simple-auth/mixins/application-route-mixin'; import ApplicationRouteMixin from 'ember-simple-auth/mixins/application-route-mixin';
import { feedback, avatar } from 'wherehows-web/constants'; import { feedback, avatar } from 'wherehows-web/constants';
import Configurator from 'wherehows-web/services/configurator'; import Configurator from 'wherehows-web/services/configurator';
import { getAvatarProps } from 'wherehows-web/constants/avatars/avatars'; import { makeAvatar } from 'wherehows-web/constants/avatars/avatars';
const { mail, subject, title } = feedback; const { mail, subject, title } = feedback;
@ -48,7 +48,7 @@ export default Route.extend(ApplicationRouteMixin, {
getConfig('userEntityProps') getConfig('userEntityProps')
]; ];
const { userName, email, name } = get(this, 'sessionUser.currentUser') || {}; const { userName, email, name } = get(this, 'sessionUser.currentUser') || {};
const avatar = getAvatarProps(avatarEntityProps)({ userName, email, name }); const avatar = makeAvatar(avatarEntityProps)({ userName, email, name });
/** /**
* properties for the navigation link to allow a user to provide feedback * properties for the navigation link to allow a user to provide feedback

View File

@ -1 +1,5 @@
{{avatars/stacked-avatars-list avatars=avatars avatarType="owner"}} {{avatars/stacked-avatars-list
avatars=avatars
handleAvatarOptionSelection=onOwnerOptionSelected
avatarType="owner"
}}

View File

@ -1,13 +1,5 @@
import { IDropDownOption } from 'wherehows-web/typings/app/dataset-compliance'; import { IDropDownOption } from 'wherehows-web/typings/app/dataset-compliance';
/**
* Defines the interface for functions that are supplied as options to IAvatar.avatarOptions
* @interface IAvatarDropDownAction
*/
interface IAvatarDropDownAction {
(avatar: IAvatar): any;
}
/** /**
* Describes the interface for an avatar object * Describes the interface for an avatar object
* @interface IAvatar * @interface IAvatar
@ -22,7 +14,7 @@ interface IAvatar {
userName?: string; userName?: string;
name?: string; name?: string;
// Selection options for an avatar with dropdown // Selection options for an avatar with dropdown
avatarOptions?: Array<IDropDownOption<IAvatarDropDownAction>>; avatarOptions?: Array<IDropDownOption<any>>;
} }
export { IAvatar, IAvatarDropDownAction }; export { IAvatar };