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 { IAvatar, IAvatarDropDownAction } from 'wherehows-web/typings/app/avatars';
import { IAvatar } from 'wherehows-web/typings/app/avatars';
import { action, computed } from '@ember-decorators/object';
import { IDropDownOption } from 'wherehows-web/typings/app/dataset-compliance';
import { classNames } from '@ember-decorators/component';
@ -18,6 +18,12 @@ export default class StackedAvatarsList extends Component {
*/
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() {
super(...arguments);
@ -64,13 +70,11 @@ export default class StackedAvatarsList extends Component {
/**
* Handler to invoke IAvatarDropDownAction instance when the drop down option is selected
* @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
*/
@action
onAvatarOptionSelected(avatar: IAvatar, selectedOption: IDropDownOption<IAvatarDropDownAction> | void): void {
const { value } = selectedOption || { value: (a: IAvatar) => a };
value(avatar);
onAvatarOptionSelected(avatar: IAvatar, selectedOption?: IDropDownOption<any>): void {
this.handleAvatarOptionSelection(avatar, selectedOption);
}
}

View File

@ -3,6 +3,7 @@ import { get, set } from '@ember/object';
import ComputedProperty, { gte } from '@ember/object/computed';
import { TaskInstance, TaskProperty } from 'ember-concurrency';
import { action, computed } from '@ember-decorators/object';
import moment from 'moment';
import {
IAccessControlAccessTypeOption,
IAccessControlEntry,
@ -12,8 +13,7 @@ import { getDefaultRequestAccessControlEntry } from 'wherehows-web/utils/dataset
import { IAvatar } from 'wherehows-web/typings/app/avatars';
import { arrayMap } from 'wherehows-web/utils/array';
import { IAppConfig } from 'wherehows-web/typings/api/configurator/configurator';
import { getAvatarProps } from 'wherehows-web/constants/avatars/avatars';
import moment from 'moment';
import { makeAvatar } from 'wherehows-web/constants/avatars/avatars';
/**
* 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 aclWithAvatar = (acl: IAccessControlEntry): IAccessControlEntry & Record<'avatar', IAvatar> => ({
...acl,
avatar: getAvatarProps(avatarProperties!)({ userName: acl.principal })
avatar: makeAvatar(avatarProperties!)({ userName: acl.principal })
});
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 { noop } from 'wherehows-web/utils/helpers/functions';
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';
type Comparator = -1 | 0 | 1;
@ -177,7 +177,7 @@ export default class DatasetAuthors extends Component {
return {
owner,
avatar: avatarProperties
? getAvatarProps(avatarProperties)({ userName: owner.userName })
? makeAvatar(avatarProperties)({ userName: owner.userName })
: { imageUrl: '', imageUrlFallback: '/assets/assets/images/default_avatar.png' }
};
};

View File

@ -1,5 +1,5 @@
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 { action } from '@ember-decorators/object';
import {
@ -21,8 +21,9 @@ import { getTagSuggestions } from 'wherehows-web/utils/datasets/compliance-sugge
import { IColumnFieldProps } from 'wherehows-web/typings/app/dataset-columns';
import { fieldTagsHaveIdentifierType } from 'wherehows-web/constants/dataset-compliance';
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 { alias } from '@ember-decorators/object/computed';
export default class DatasetComplianceRollupRow extends Component.extend({
tagName: ''
@ -118,7 +119,8 @@ export default class DatasetComplianceRollupRow extends Component.extend({
* @type {ComputedProperty<string>}
* @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
@ -126,7 +128,8 @@ export default class DatasetComplianceRollupRow extends Component.extend({
* @type {ComputedProperty<Array<IComplianceChangeSet>>}
* @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
@ -134,7 +137,8 @@ export default class DatasetComplianceRollupRow extends Component.extend({
* @type {ComputedProperty<IComplianceChangeSet>}
* @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
@ -142,7 +146,8 @@ export default class DatasetComplianceRollupRow extends Component.extend({
* @type {ComputedProperty<string>}
* @memberof DatasetComplianceRollupRow
*/
dataType: ComputedProperty<string> = alias('fieldProps.dataType');
@alias('fieldProps.dataType')
dataType: string;
/**
* Checks if the field has only one tag
@ -178,9 +183,8 @@ export default class DatasetComplianceRollupRow extends Component.extend({
* @type {(ComputedProperty<SuggestionIntent | void>)}
* @memberof DatasetComplianceRollupRow
*/
suggestionAuthority: ComputedProperty<IComplianceChangeSet['suggestionAuthority']> = alias(
'fieldProps.suggestionAuthority'
);
@alias('fieldProps.suggestionAuthority')
suggestionAuthority: IComplianceChangeSet['suggestionAuthority'];
/**
* 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
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 (updateDefault) {
get(this, 'onTagIdentifierTypeChange')(get(this, 'fieldProps'), {
value: <ComplianceFieldIdValue>identifierType
});
} else {
// If suggested value is ComplianceFieldIdValue.None then do not add
if (identifierType !== ComplianceFieldIdValue.None) {
this.actions.onAddFieldTag.call(this, { identifierType, logicalType });
// If suggested value is ComplianceFieldIdValue.None, remove all other annotations first before tagging field as none
if (identifierType === ComplianceFieldIdValue.None) {
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 { set } from '@ember/object';
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 { task } from 'ember-concurrency';
import { readDatasetOwnersByUrn } from 'wherehows-web/utils/api/datasets/owners';
import { arrayMap, arrayPipe } from 'wherehows-web/utils/array';
import { IAvatar } from 'wherehows-web/typings/app/avatars';
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 { containerDataSource } from 'wherehows-web/utils/components/containers/data-source';
import { decodeUrn, isLiUrn } from 'wherehows-web/utils/validators/urn';
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')
@containerDataSource('getOwnersTask')
@ -59,12 +61,28 @@ export default class DatasetOwnerListContainer extends Component {
@computed('owners')
get avatars(): Array<IAvatar> {
const { avatarEntityProps, owners } = this;
const [getAvatarProperties, augmentAvatarsWithDropDownOption] = [
arrayMap(getAvatarProps(avatarEntityProps)),
const [makeAvatars, augmentAvatarsWithDropDownOption] = [
arrayMap(makeAvatar(avatarEntityProps)),
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
* @return {IAvatar}
*/
const getAvatarProps = ({ aviUrlPrimary, aviUrlFallback = '' }: IAppConfig['userEntityProps']) => (
const makeAvatar = ({ aviUrlPrimary, aviUrlFallback = '' }: IAppConfig['userEntityProps']) => (
object: Partial<IAvatar>
): IAvatar => {
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 { arrayFilter, isListUnique } from 'wherehows-web/utils/array';
import { IAvatar } from 'wherehows-web/typings/app/avatars';
import { buildMailToUrl } from 'wherehows-web/utils/helpers/email';
/**
* Initial user name for candidate owners
@ -164,9 +163,7 @@ const avatarWithDropDownOption = (avatar: IAvatar): IAvatar & Required<Pick<IAva
...avatar,
avatarOptions: [
{
// if the owner avatar does not have an email then a null value is returned with no action performed
value: ({ email }: IAvatar): Window | null =>
email ? window.open(buildMailToUrl({ to: email || '' }), '_blank') : null,
value: 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 { feedback, avatar } from 'wherehows-web/constants';
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;
@ -48,7 +48,7 @@ export default Route.extend(ApplicationRouteMixin, {
getConfig('userEntityProps')
];
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

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';
/**
* 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
* @interface IAvatar
@ -22,7 +14,7 @@ interface IAvatar {
userName?: string;
name?: string;
// Selection options for an avatar with dropdown
avatarOptions?: Array<IDropDownOption<IAvatarDropDownAction>>;
avatarOptions?: Array<IDropDownOption<any>>;
}
export { IAvatar, IAvatarDropDownAction };
export { IAvatar };