Merge pull request #1384 from theseyi/replace-user-avatars-with-avatar-image

replace user avatars with avatar image
This commit is contained in:
Seyi Adebajo 2018-09-14 10:31:36 -07:00 committed by GitHub
commit 9e84fcd7a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 328 additions and 244 deletions

View File

@ -2,12 +2,17 @@ import Component from '@ember/component';
import { get, set } from '@ember/object';
import ComputedProperty, { gte } from '@ember/object/computed';
import { TaskInstance, TaskProperty } from 'ember-concurrency';
import { action } from '@ember-decorators/object';
import { action, computed } from '@ember-decorators/object';
import {
IAccessControlAccessTypeOption,
IAccessControlEntry,
IRequestAccessControlEntry
} from 'wherehows-web/typings/api/datasets/aclaccess';
import { getDefaultRequestAccessControlEntry } from 'wherehows-web/utils/datasets/acl-access';
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';
/**
* Returns the number of days in milliseconds, default is 1 day
@ -30,6 +35,19 @@ const minSelectableExpirationDate = new Date(Date.now() + millisecondDays());
const maxSelectableExpirationDate = new Date(Date.now() + millisecondDays(7));
export default class DatasetAclAccess extends Component {
/**
* External component attribute with list of acls
* @type {Array<IAccessControlEntry>}
*/
acls: Array<IAccessControlEntry>;
/**
* External component attribute with properties to construct an acl's avatar image
* @type {(IAppConfig['userEntityProps'] | undefined)}
* @memberof DatasetAclAccess
*/
avatarProperties: IAppConfig['userEntityProps'] | undefined;
/**
* Named component argument with a string link reference to more information on acls
* @type {string}
@ -109,6 +127,23 @@ export default class DatasetAclAccess extends Component {
set(this, 'userAclRequest', getDefaultRequestAccessControlEntry());
}
/**
* Augments each acl in the list with properties for the user avatar
* @readonly
* @type {(Array<IAccessControlEntry & Record<'avatar', IAvatar>>)}
* @memberof DatasetAclAccess
*/
@computed('acls')
get aclsWithAvatarProps(): Array<IAccessControlEntry & Record<'avatar', IAvatar>> {
const { acls, avatarProperties } = this;
const aclWithAvatar = (acl: IAccessControlEntry): IAccessControlEntry & Record<'avatar', IAvatar> => ({
...acl,
avatar: getAvatarProps(avatarProperties!)({ userName: acl.principal })
});
return avatarProperties ? arrayMap(aclWithAvatar)(acls) : [];
}
/**
* Invokes external action when the accessType to be requested is modified
* @param {IAccessControlAccessTypeOption} arg

View File

@ -6,6 +6,7 @@ import { assert } from '@ember/debug';
import { IOwner } from 'wherehows-web/typings/api/datasets/owners';
import { OwnerSource, OwnerType } from 'wherehows-web/utils/api/datasets/owners';
import { action } from '@ember-decorators/object';
import { OwnerWithAvatarRecord } from 'wherehows-web/typings/app/datasets/owners';
/**
* This component renders a single owner record and also provides functionality for interacting with the component
@ -26,10 +27,10 @@ export default class DatasetAuthor extends Component {
/**
* The owner record being rendered
* @type {IOwner}
* @type {OwnerWithAvatarRecord}
* @memberof DatasetAuthor
*/
owner: IOwner;
owner: OwnerWithAvatarRecord;
/**
* List of suggested owners that have been confirmed by a user
@ -73,14 +74,14 @@ export default class DatasetAuthor extends Component {
* @type {ComputedProperty<boolean>}
* @memberof DatasetAuthor
*/
isOwnerMutable: ComputedProperty<boolean> = equal('owner.source', OwnerSource.Ui);
isOwnerMutable: ComputedProperty<boolean> = equal('owner.owner.source', OwnerSource.Ui);
/**
* Negates the owner attribute flag `isActive`, indicating owner record is considered inactive
* @type {ComputedProperty<boolean>}
* @memberOf DatasetAuthor
*/
isOwnerInActive: ComputedProperty<boolean> = not('owner.isActive');
isOwnerInActive: ComputedProperty<boolean> = not('owner.owner.isActive');
/**
* Determines if the owner record is a system suggested owner and if this record is confirmed by a user
@ -93,7 +94,9 @@ export default class DatasetAuthor extends Component {
const {
commonOwners,
isOwnerMutable,
owner: { userName }
owner: {
owner: { userName }
}
} = getProperties(this, ['commonOwners', 'isOwnerMutable', 'owner']);
return isOwnerMutable ? false : !!commonOwners.findBy('userName', userName);
@ -123,7 +126,7 @@ export default class DatasetAuthor extends Component {
@action
onRemoveOwner(): boolean | void | IOwner {
const { owner, isOwnerMutable, removeOwner } = getProperties(this, ['owner', 'isOwnerMutable', 'removeOwner']);
return isOwnerMutable && removeOwner(owner);
return isOwnerMutable && removeOwner(owner.owner);
}
/**
@ -133,7 +136,7 @@ export default class DatasetAuthor extends Component {
@action
confirmOwner(): Array<IOwner> | void {
const { owner, confirmSuggestedOwner } = getProperties(this, ['owner', 'confirmSuggestedOwner']);
return confirmSuggestedOwner(owner);
return confirmSuggestedOwner(owner.owner);
}
/**
@ -149,6 +152,6 @@ export default class DatasetAuthor extends Component {
'updateOwnerType'
]);
return isOwnerMutable && updateOwnerType(owner, type);
return isOwnerMutable && updateOwnerType(owner.owner, type);
}
}

View File

@ -2,7 +2,7 @@ import Component from '@ember/component';
import { set, get, getProperties } from '@ember/object';
import { assert } from '@ember/debug';
import { action, computed } from '@ember-decorators/object';
import { filter } from '@ember-decorators/object/computed';
import { filter, map } from '@ember-decorators/object/computed';
import { service } from '@ember-decorators/service';
import { task } from 'ember-concurrency';
@ -21,6 +21,9 @@ import {
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 { OwnerWithAvatarRecord } from 'wherehows-web/typings/app/datasets/owners';
type Comparator = -1 | 0 | 1;
@ -53,6 +56,13 @@ export default class DatasetAuthors extends Component {
*/
suggestedOwners: Array<IOwner>;
/**
* Avatar properties used to generate avatar images
* @type {(IAppConfig['userEntityProps'] | undefined)}
* @memberof DatasetAuthors
*/
avatarProperties: IAppConfig['userEntityProps'] | undefined;
/**
* Current user service
* @type {ComputedProperty<CurrentUser>}
@ -154,6 +164,35 @@ export default class DatasetAuthors extends Component {
@filter('owners', isConfirmedOwner)
confirmedOwners: Array<IOwner>;
/**
* Augments an IOwner instance with an IAvatar Record keyed by 'avatar'
* @this {DatasetAuthors}
* @param owner
* @memberof DatasetAuthors
* @returns {OwnerWithAvatarRecord}
*/
datasetAuthorsOwnersAugmentedWithAvatars = (owner: IOwner): OwnerWithAvatarRecord => {
const { avatarProperties } = this;
return {
owner,
avatar: avatarProperties
? getAvatarProps(avatarProperties)({ userName: owner.userName })
: { imageUrl: '', imageUrlFallback: '/assets/assets/images/default_avatar.png' }
};
};
/**
* Augments each confirmedOwner IOwner instance with an avatar Record
* @param {IOwner} owner the IOwner instance
* @returns {OwnerWithAvatarRecord}
* @memberof DatasetAuthors
*/
@map('confirmedOwners')
confirmedOwnersWithAvatars(owner: IOwner): OwnerWithAvatarRecord {
return this.datasetAuthorsOwnersAugmentedWithAvatars(owner);
}
/**
* Intersection of confirmed owners and suggested owners
* @type {ComputedProperty<Array<IOwner>>}
@ -185,6 +224,17 @@ export default class DatasetAuthors extends Component {
return (get(this, 'suggestedOwners') || []).slice(0);
}
/**
* Augments each systemGeneratedOwner IOwner instance with an avatar Record
* @param {IOwner} owner the IOwner instance
* @returns {OwnerWithAvatarRecord}
* @memberof DatasetAuthors
*/
@map('systemGeneratedOwners')
systemGeneratedOwnersWithAvatars(owner: IOwner): OwnerWithAvatarRecord {
return this.datasetAuthorsOwnersAugmentedWithAvatars(owner);
}
/**
* Invokes the external action as a dropping task
* @type {Task<Promise<Array<IOwner>>, void>}
@ -216,8 +266,10 @@ export default class DatasetAuthors extends Component {
*/
@action
addOwner(this: DatasetAuthors, newOwner: IOwner): Array<IOwner> | void {
const owners = get(this, 'owners') || [];
const { notify } = get(this, 'notifications');
const {
owners = [],
notifications: { notify }
} = this;
if (ownerAlreadyExists(owners, { userName: newOwner.userName, source: newOwner.source })) {
return void notify(NotificationEvent.info, { content: 'Owner has already been added to "confirmed" list' });

View File

@ -317,7 +317,7 @@ export default class DatasetCompliance extends Component {
): string {
type TagFilterHint = { [K in TagFilter]: string };
const { fieldReviewOption, foldedChangeSet = [] } = getProperties(this, ['fieldReviewOption', 'foldedChangeSet']);
const { fieldReviewOption, foldedChangeSet = [] } = this;
const hint = (<TagFilterHint>{
[TagFilter.showAll]: '',
@ -883,7 +883,7 @@ export default class DatasetCompliance extends Component {
* @type {Array<IdentifierFieldWithFieldChangeSetTuple>}
* @memberof DatasetCompliance
*/
foldedChangeSet: Array<IdentifierFieldWithFieldChangeSetTuple> | void;
foldedChangeSet: Array<IdentifierFieldWithFieldChangeSetTuple> = [];
/**
* Task to retrieve platform policies and set supported policies for the current platform

View File

@ -21,6 +21,9 @@ import Notifications, { NotificationEvent } from 'wherehows-web/services/notific
import { notificationDialogActionFactory } from 'wherehows-web/utils/notifications/notifications';
import { service } from '@ember-decorators/service';
import { equal } from '@ember-decorators/object/computed';
import { IAppConfig } from 'wherehows-web/typings/api/configurator/configurator';
import Configurator from 'wherehows-web/services/configurator';
import { containerDataSource } from 'wherehows-web/utils/components/containers/data-source';
/**
* Enumerates the string value for an acl access request
@ -31,6 +34,7 @@ enum AclRequestType {
Approval = 'approved'
}
@containerDataSource('getContainerDataTask')
export default class DatasetAclAccessContainer extends Component {
/**
* The currently logged in user service
@ -78,9 +82,17 @@ export default class DatasetAclAccessContainer extends Component {
/**
* Who to contact in case of error
* @type {string}
*/
jitAclContact: string;
/**
* Avatar properties used to generate the acl's avatar image
* @type {(IAppConfig['userEntityProps'] | undefined)}
* @memberof DatasetAclAccessContainer
*/
avatarProperties: IAppConfig['userEntityProps'] | undefined;
/**
* Request object for the current user requesting access control
* @type {IRequestAccessControlEntry}
@ -112,14 +124,6 @@ export default class DatasetAclAccessContainer extends Component {
*/
urn: string;
didInsertElement() {
get(this, 'getContainerDataTask').perform();
}
didUpdateAttrs() {
get(this, 'getContainerDataTask').perform();
}
constructor() {
super(...arguments);
@ -189,15 +193,17 @@ export default class DatasetAclAccessContainer extends Component {
* @memberof DatasetAclAccessContainer
*/
getContainerDataTask = task(function*(this: DatasetAclAccessContainer): IterableIterator<any> {
if (get(this, 'isJitAclAccessEnabled')) {
const { getCurrentUserTask, getDatasetAclsTask, checkUserAccessTask } = getProperties(this, [
if (this.isJitAclAccessEnabled) {
const { getCurrentUserTask, getDatasetAclsTask, checkUserAccessTask, getAvatarProperties } = getProperties(this, [
'getCurrentUserTask',
'getDatasetAclsTask',
'checkUserAccessTask'
'checkUserAccessTask',
'getAvatarProperties'
]);
const user: DatasetAclAccessContainer['user'] = yield getCurrentUserTask.perform();
if (user) {
yield getAvatarProperties.perform();
yield getDatasetAclsTask.perform();
yield checkUserAccessTask.perform();
}
@ -214,6 +220,16 @@ export default class DatasetAclAccessContainer extends Component {
return set(this, 'user', currentUser);
});
/**
* Fetches and sets the props used in constructing an acl's avatar
* @memberof DatasetAclAccessContainer
*/
getAvatarProperties = task(function*(
this: DatasetAclAccessContainer
): IterableIterator<IAppConfig['userEntityProps']> {
return set(this, 'avatarProperties', Configurator.getConfig('userEntityProps'));
});
/**
* Fetches the list of acls for this dataset
* @memberof DatasetAclAccessContainer

View File

@ -47,7 +47,7 @@ export default class DatasetOwnerListContainer extends Component {
* @type {IAppConfig.avatarEntityProps}
* @memberof DatasetOwnerListContainer
*/
avatarEntityProps!: IAppConfig['avatarEntityProps'];
avatarEntityProps!: IAppConfig['userEntityProps'];
/**
* Lists the avatar objects based off the dataset owners

View File

@ -13,7 +13,11 @@ import {
updateDatasetOwnersByUrn
} from 'wherehows-web/utils/api/datasets/owners';
import { service } from '@ember-decorators/service';
import { IAppConfig } from 'wherehows-web/typings/api/configurator/configurator';
import Configurator from 'wherehows-web/services/configurator';
import { containerDataSource } from 'wherehows-web/utils/components/containers/data-source';
@containerDataSource('getContainerDataTask')
export default class DatasetOwnershipContainer extends Component {
/**
* The urn identifier for the dataset
@ -58,26 +62,42 @@ export default class DatasetOwnershipContainer extends Component {
*/
upstreamUrn: string;
didInsertElement() {
get(this, 'getContainerDataTask').perform();
}
didUpdateAttrs() {
get(this, 'getContainerDataTask').perform();
}
/**
* Avatar properties used to generate avatar images
* @type {(IAppConfig['userEntityProps'] | undefined)}
* @memberof DatasetOwnershipContainer
*/
avatarProperties: IAppConfig['userEntityProps'] | undefined;
/**
* An async parent task to group all data tasks for this container component
* @type {Task<TaskInstance<Promise<any>>, (a?: any) => TaskInstance<TaskInstance<Promise<any>>>>}
*/
getContainerDataTask = task(function*(this: DatasetOwnershipContainer): IterableIterator<TaskInstance<Promise<any>>> {
getContainerDataTask = task(function*(
this: DatasetOwnershipContainer
): IterableIterator<TaskInstance<Promise<any> | IAppConfig['userEntityProps']>> {
const tasks = Object.values(
getProperties(this, ['getDatasetOwnersTask', 'getSuggestedOwnersTask', 'getDatasetOwnerTypesTask'])
getProperties(this, [
'getDatasetOwnersTask',
'getSuggestedOwnersTask',
'getDatasetOwnerTypesTask',
'getAvatarProperties'
])
);
yield* tasks.map(task => task.perform());
});
/**
* Fetches & sets avatar props to build owner avatar images
* @memberof DatasetOwnershipContainer
*/
getAvatarProperties = task(function*(
this: DatasetOwnershipContainer
): IterableIterator<IAppConfig['userEntityProps']> {
return set(this, 'avatarProperties', Configurator.getConfig('userEntityProps'));
});
/**
* Reads the owners for this dataset
* @type {Task<Promise<Array<IOwner>>, (a?: any) => TaskInstance<Promise<IOwnerResponse>>>}

View File

@ -1,12 +1,12 @@
import Component from '@ember/component';
import { IOwner } from 'wherehows-web/typings/api/datasets/owners';
import { get, set } from '@ember/object';
import { set } from '@ember/object';
import { computed } from '@ember-decorators/object';
import { empty } from '@ember-decorators/object/computed';
import { classNames } from '@ember-decorators/component';
import { OwnerWithAvatarRecord } from 'wherehows-web/typings/app/datasets/owners';
@classNames('dataset-authors-suggested')
export default class DatasetsOwnersSuggestedOwners extends Component {
classNames = ['dataset-authors-suggested'];
constructor() {
super(...arguments);
@ -17,17 +17,16 @@ export default class DatasetsOwnersSuggestedOwners extends Component {
* Whether or not the component is expanded. If not, users will only see the initial header information
* whereas if expanded then users will see the list of all suggested owners
* @type {boolean}
* @default false
*/
isExpanded = false;
/**
* Passed in value from parent component, `dataset-authors`, a.k.a. systemGeneratedOwners, this list
* represents a possible list of owners provided by scanning various systems.
* @type {Array<IOwner>}
* @type {Array<OwnerWithAvatarRecord>}
* @default []
*/
owners: Array<IOwner>;
owners: Array<OwnerWithAvatarRecord>;
/**
* Computed based on the owners array, detects whether this array is empty or not
@ -40,10 +39,10 @@ export default class DatasetsOwnersSuggestedOwners extends Component {
* For the facepile in the suggestions window header, we do not need tos how all the faces of all the
* possible owners as this could be a large amount. Take only up to the first four to pass into the
* template for rendering
* @type {ComputedProperty<Array<IOwner>>}
* @type {ComputedProperty<Array<OwnerWithAvatarRecord>>}
*/
@computed('owners')
get facepileOwners(this: DatasetsOwnersSuggestedOwners): Array<IOwner> {
return get(this, 'owners').slice(0, 4);
get facepileOwners(this: DatasetsOwnersSuggestedOwners): Array<OwnerWithAvatarRecord> {
return this.owners.slice(0, 4);
}
}

View File

@ -9,7 +9,7 @@ export default class GlobalHotkeys extends Component {
* Sets the class names binded to the html element generated by this component
* @type {Array<string>}
*/
classNames = ['global-hotkey-binder'];
classNames = ['global-hotkey'];
/**
* Allows us to bind the tabindex attribute to our element to make it focusable. This will

View File

@ -1,31 +0,0 @@
import Component from '@ember/component';
import ComputedProperty from '@ember/object/computed';
import { avatar } from 'wherehows-web/constants';
import { computed, get } from '@ember/object';
const { fallbackUrl, url }: { fallbackUrl: string; url: string } = avatar;
const headlessUserName: string = 'wherehows';
export default class UserAvatar extends Component {
tagName = 'span';
/**
* username for the user, e.g. ldap userName that can be used to construct the url
* @type {string}
*/
userName: string;
/**
* Ember.ComputedProperty that resolves with the image url for the avatar
* @type {ComputedProperty<string>}
* @memberof UserAvatar
*/
imageUrl: ComputedProperty<string> = computed('userName', function(this: UserAvatar) {
const userName = get(this, 'userName');
if (userName && userName !== headlessUserName) {
return url.replace('[username]', userName);
}
return fallbackUrl;
});
}

View File

@ -4,13 +4,4 @@
*/
const feedback = { mail: 'wherehows-dev@linkedin.com', subject: 'WhereHows Feedback', title: 'Provide Feedback' };
/**
* Defines the properties for the navigation bar avatar
* @type {object}
*/
const avatar = {
url: 'https://cinco.corp.linkedin.com/api/profile/[username]/picture/?access_token=2rzmbzEMGlHsszQktFY-B1TxUic',
fallbackUrl: '/assets/assets/images/default_avatar.png'
};
export { feedback, avatar };
export { feedback };

View File

@ -5,23 +5,19 @@ import { IAppConfig } from 'wherehows-web/typings/api/configurator/configurator'
/**
* Takes a Partial<IAvatar> object and builds an IAvatar
* @param {Partial<IAvatar>} object
* @param {IAppConfig.avatarEntityProps.urlPrimary} urlPrimary
* @param {IAppConfig.avatarEntityProps.urlPrimary} urlFallback
* @param {IAppConfig.userEntityProps.aviUrlPrimary} aviUrlPrimary primary url for avatar image
* @param {IAppConfig.userEntityProps.aviUrlFallback} aviUrlFallback
* @return {IAvatar}
*/
const getAvatarProps = ({ urlPrimary, urlFallback }: IAppConfig['avatarEntityProps']) => (
const getAvatarProps = ({ aviUrlPrimary, aviUrlFallback = '' }: IAppConfig['userEntityProps']) => (
object: Partial<IAvatar>
): IAvatar => {
const props = pick(object, ['email', 'userName', 'name']);
let imageUrl = urlFallback || '';
if (props.userName && urlPrimary) {
imageUrl = urlPrimary.replace('[username]', props.userName);
}
const hasRequiredUrlElements = props.userName && aviUrlPrimary;
return {
imageUrl,
imageUrlFallback: urlFallback,
imageUrl: hasRequiredUrlElements ? aviUrlPrimary.replace('[username]', props.userName!) : aviUrlFallback,
imageUrlFallback: aviUrlFallback,
...props
};
};

View File

@ -78,7 +78,7 @@ export default class DatasetController extends Controller {
* References a collection of properties for avatar properties
* @type {IAppConfig.avatarEntityProps}
*/
avatarEntityProps: IAppConfig['avatarEntityProps'];
avatarEntityProps: IAppConfig['userEntityProps'];
/**
* Flag indicating the dataset policy is derived from an upstream source

View File

@ -5,9 +5,9 @@ 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';
const { mail, subject, title } = feedback;
const { url: avatarUrl } = avatar;
export default Route.extend(ApplicationRouteMixin, {
// Injected Ember#Service for the current user
@ -42,14 +42,13 @@ export default Route.extend(ApplicationRouteMixin, {
*/
async model() {
const { getConfig } = Configurator;
const [isInternal, showStagingBanner, showLiveDataWarning] = await Promise.all([
getConfig('isInternal'),
const [showStagingBanner, showLiveDataWarning, avatarEntityProps] = [
getConfig('isStagingBanner', { useDefault: true, default: false }),
getConfig('isLiveDataWarning', { useDefault: true, default: false })
]);
const { userName } = get(this, 'sessionUser.currentUser') || {};
getConfig('isLiveDataWarning', { useDefault: true, default: false }),
getConfig('userEntityProps')
];
const { userName, email, name } = get(this, 'sessionUser.currentUser') || {};
const avatar = getAvatarProps(avatarEntityProps)({ userName, email, name });
/**
* properties for the navigation link to allow a user to provide feedback
@ -61,12 +60,7 @@ export default Route.extend(ApplicationRouteMixin, {
target: '_blank'
};
const brand = {
logo: isInternal ? '/assets/assets/images/wherehows-logo.png' : '',
avatarUrl: isInternal ? avatarUrl.replace('[username]', userName) : '/assets/assets/images/default_avatar.png'
};
return { feedbackMail, brand, showStagingBanner, showLiveDataWarning };
return { feedbackMail, showStagingBanner, showLiveDataWarning, avatar };
},
/**

View File

@ -99,7 +99,7 @@ export default class DatasetRoute extends Route {
shouldShowDatasetLineage: getConfig('shouldShowDatasetLineage'),
shouldShowDatasetHealth: getConfig('shouldShowDatasetHealth'),
wikiLinks: getConfig('wikiLinks'),
avatarEntityProps: getConfig('avatarEntityProps')
avatarEntityProps: getConfig('userEntityProps')
});
}

View File

@ -27,6 +27,7 @@
@import 'dataset-relationships/all';
@import 'visualization/all';
@import 'dataset-health/all';
@import 'hotkey/all';
@import 'nacho/nacho-button';
@import 'nacho/nacho-global-search';

View File

@ -118,3 +118,12 @@ $navbar-transition-speed: $banner-animation-speed;
border-radius: 0;
}
}
/**
* Adds styles for navigation bar avatar image
*/
.navbar-avatar-image {
&#{&} {
@include round-image(item-spacing(6));
}
}

View File

@ -1,14 +1,8 @@
$avatar-size: 18px;
/// Shared declaration for avatar rounding proportions
@mixin round-avatar {
@include round-image(item-spacing(7));
}
.user-avatar {
@include round-image($avatar-size);
}
.avatar-container {
display: flex;
justify-content: flex-end;

View File

@ -12,12 +12,6 @@
}
}
/// Overrides the nested user avatar class
.user-avatar {
width: item-spacing(6);
height: item-spacing(6);
}
&__header {
display: flex;
align-items: center;

View File

@ -6,11 +6,6 @@
overflow: hidden;
cursor: default;
.user-avatar {
height: 36px;
width: 36px;
}
&__header {
margin: 0;
font-weight: 400;

View File

@ -0,0 +1 @@
@import 'global-hotkey';

View File

@ -0,0 +1,3 @@
.global-hotkey {
outline: none;
}

View File

@ -1,6 +1,4 @@
<section class="comment-item__header">
{{user-avatar userName=comment.authorUserName}}
<div class="comment-item__meta">
<h5 class="comment-item__author">
{{comment.authorName}}

View File

@ -161,11 +161,11 @@
</p>
{{/unless}}
</header>
{{#if acls}}
{{#if aclsWithAvatarProps}}
{{#dataset-table
class="nacho-table nacho-table--stripped"
fields=acls
fields=aclsWithAvatarProps
sortColumnWithName=sortColumnWithName
filterBy=filterBy as |table|
}}
@ -191,7 +191,7 @@
{{#each (sort-by table.sortBy table.data) as |field|}}
{{#body.row as |row|}}
{{#row.cell}}
{{user-avatar userName=field.principal}}
{{avatars/avatar-image avatar=field.avatar}}
{{field.principal}}
{{/row.cell}}

View File

@ -1,75 +1,76 @@
<td>
{{#unless isOwnerInActive}}
{{user-avatar userName=owner.userName}}
{{/unless}}
{{owner.userName}}
{{#if isOwnerInActive}}
<span class="nacho-button nacho-button--small dataset-author-record__indicator--inactive">
Inactive
</span>
{{/if}}
</td>
<td>
{{owner.name}}
</td>
<td>
{{owner.idType}}
</td>
{{!-- hides source column for confirmed owners--}}
{{#unless isOwnerMutable}}
{{#let @owner.owner as |ownerRecord|}}
<td>
{{owner.source}}
{{#unless isOwnerInActive}}
{{avatars/avatar-image avatar=@owner.avatar}}
{{/unless}}
{{ownerRecord.userName}}
{{#if isOwnerInActive}}
<span class="nacho-button nacho-button--small dataset-author-record__indicator--inactive">
Inactive
</span>
{{/if}}
</td>
{{/unless}}
<td>
{{ownerRecord.name}}
</td>
<td>
{{ember-selector
class=(unless isOwnerMutable "nacho-select--hidden-state")
values=ownerTypes
selected=owner.type
disabled=(not isOwnerMutable)
selectionDidChange=(action "changeOwnerType")
}}
</td>
<td>
{{ownerRecord.idType}}
</td>
<td>
{{#if isOwnerMutable}}
{{!-- hides source column for confirmed owners--}}
{{#unless isOwnerMutable}}
<button
class="nacho-button nacho-button--small remove-dataset-author"
{{action "onRemoveOwner"}}>
<i class="fa fa-trash"
aria-label="Remove Owner"></i>
</button>
<td>
{{ownerRecord.source}}
</td>
{{else}}
{{/unless}}
{{#if isConfirmedSuggestedOwner}}
<td>
{{ember-selector
class=(unless isOwnerMutable "nacho-select--hidden-state")
values=ownerTypes
selected=ownerRecord.type
disabled=(not isOwnerMutable)
selectionDidChange=(action "changeOwnerType")
}}
</td>
<span
class="nacho-button nacho-button--small dataset-author-record__indicator--disabled">
Added
</span>
<td>
{{#if isOwnerMutable}}
<button
class="nacho-button nacho-button--small remove-dataset-author"
{{action "onRemoveOwner"}}>
<i class="fa fa-trash"
aria-label="Remove Owner"></i>
</button>
{{else}}
<button
class="nacho-button nacho-button--small confirm-suggested-dataset-author"
{{action "confirmOwner"}}>
<i class="fa fa-plus" title="Add an Owner"></i>
</button>
{{#if isConfirmedSuggestedOwner}}
<span
class="nacho-button nacho-button--small dataset-author-record__indicator--disabled">
Added
</span>
{{else}}
<button
class="nacho-button nacho-button--small confirm-suggested-dataset-author"
{{action "confirmOwner"}}>
<i class="fa fa-plus" title="Add an Owner"></i>
</button>
{{/if}}
{{/if}}
{{/if}}
</td>
</td>
{{/let}}

View File

@ -35,17 +35,17 @@
</tr>
</thead>
{{#each confirmedOwners as |confirmedOwner|}}
<tbody>
<tbody>
{{#each confirmedOwnersWithAvatars as |confirmedOwnerWithAvatar|}}
{{dataset-author
owner=confirmedOwner
owner=confirmedOwnerWithAvatar
ownerTypes=ownerTypes
removeOwner=(action "removeOwner")
confirmSuggestedOwner=(action "confirmSuggestedOwner")
updateOwnerType=(action "updateOwnerType")
}}
</tbody>
{{/each}}
{{/each}}
</tbody>
<tbody>
<tr>
@ -65,7 +65,7 @@
<section class="dataset-author">
{{datasets/owners/suggested-owners
owners=systemGeneratedOwners
owners=systemGeneratedOwnersWithAvatars
ownerTypes=ownerTypes
commonOwners=commonOwners
removeOwner=(action "removeOwner")

View File

@ -8,6 +8,7 @@
{{dataset-aclaccess
acls=acls
aclMoreInfoLink=@aclMoreInfoLink
avatarProperties=avatarProperties
jitAclContact=jitAclContact
hasValidAclRequest=hasValidAclRequest
userAclRequest=userAclRequest

View File

@ -33,6 +33,7 @@
{{dataset-authors
owners=owners
suggestedOwners=suggestedOwners
avatarProperties=avatarProperties
ownerTypes=ownerTypes
setOwnershipRuleChange=setOwnershipRuleChange
save=(action "saveOwnerChanges")

View File

@ -1,29 +1,30 @@
<section class="suggested-owner-card__owner-info">
<div class="suggested-owner-card__owner-info__profile">
{{user-avatar
class="suggested-owner-card__owner-info__profile__pic"
userName=owner.userName}}
<div class="suggested-owner-card__owner-info__profile__name">
<h5 class="suggested-owner-card__owner-info__profile__name__full">
{{owner.name}}
</h5>
<p class="suggested-owner-card__owner-info__profile__name__username">
{{owner.userName}}
</p>
{{#let @owner.owner as |ownerRecord|}}
<section class="suggested-owner-card__owner-info">
<div class="suggested-owner-card__owner-info__profile">
{{avatars/avatar-image avatar=@owner.avatar class="suggested-owner-card__owner-info__profile__pic"}}
<div class="suggested-owner-card__owner-info__profile__name">
<h5 class="suggested-owner-card__owner-info__profile__name__full">
{{ownerRecord.name}}
</h5>
<p class="suggested-owner-card__owner-info__profile__name__username">
{{ownerRecord.userName}}
</p>
</div>
</div>
</div>
<div class="suggested-owner-card__owner-info__add">
{{#if isConfirmedSuggestedOwner}}
{{fa-icon "check-circle-o" title="Added Owner" size="2"}}
<span class="suggested-owner-card__owner-info__add--disabled">Added</span>
{{else}}
<button class="nacho-button--secondary nacho-button--medium"
{{action "confirmOwner"}}>
Add as owner
</button>
{{/if}}
</div>
</section>
<section class="suggested-owner-card__source-info">
Source: {{owner.source}}
</section>
<div class="suggested-owner-card__owner-info__add">
{{#if isConfirmedSuggestedOwner}}
{{fa-icon "check-circle-o" title="Added Owner" size="2"}}
<span class="suggested-owner-card__owner-info__add--disabled">Added</span>
{{else}}
<button class="nacho-button--secondary nacho-button--medium"
{{action "confirmOwner"}}>
Add as owner
</button>
{{/if}}
</div>
</section>
<section class="suggested-owner-card__source-info">
Source: {{ownerRecord.source}}
</section>
{{/let}}

View File

@ -8,7 +8,7 @@
<section class="dataset-authors-suggested__info">
<div class="dataset-authors-suggested__info__facepile">
{{#each facepileOwners as |owner|}}
{{user-avatar class="dataset-authors-suggested__info__facepile__avatar" userName=owner.userName}}
{{avatars/avatar-image avatar=owner.avatar class="dataset-authors-suggested__info__facepile__avatar"}}
{{/each}}
</div>
<div class="dataset-authors-suggested__info__description">

View File

@ -1 +0,0 @@
<img src="{{imageUrl}}" alt="{{userName}}" class="user-avatar">

View File

@ -10,7 +10,7 @@
{{#link-to "index" class="navbar-brand"}}
<div>
<img src="{{model.brand.logo}}"
<img src="/assets/assets/images/wherehows-logo.png"
alt="WhereHows Logo">
<sup class="beta-badge">(Beta)</sup>
</div>
@ -26,7 +26,7 @@
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
<i class="caret"></i>
<img src="{{model.brand.avatarUrl}}" alt="User Avatar" class="user-avatar">
{{avatars/avatar-image avatar=model.avatar class="navbar-avatar-image"}}
</a>

View File

@ -18,9 +18,9 @@ interface IAppConfig {
// collection of links to external help resource pages
wikiLinks: Record<string, string>;
// properties for an avatar entity
avatarEntityProps: {
urlPrimary: string;
urlFallback: string;
userEntityProps: {
aviUrlPrimary: string;
aviUrlFallback: string;
};
tracking: {
isEnabled: boolean;

View File

@ -0,0 +1,11 @@
import { IOwner } from 'wherehows-web/typings/api/datasets/owners';
import { IAvatar } from 'wherehows-web/typings/app/avatars';
/**
* An IOwner instance augmented with an IAvatar Record keyed by 'avatar'
* @type OwnerWithAvatarRecord
* @alias
*/
type OwnerWithAvatarRecord = Record<'owner', IOwner> & Record<'avatar', IAvatar>;
export { OwnerWithAvatarRecord };

View File

@ -17,7 +17,7 @@ module('Integration | Component | dataset author', function(hooks) {
test('it renders', async function(assert) {
this.set('removeOwner', noop);
this.set('confirmSuggestedOwner', noop);
this.set('author', confirmedOwner);
this.set('author', { owner: confirmedOwner });
this.set('commonOwners', commonOwners);
await render(
@ -36,7 +36,7 @@ module('Integration | Component | dataset author', function(hooks) {
assert.equal(removeActionCallCount, 1, 'action is called once');
});
this.set('confirmSuggestedOwner', noop);
this.set('author', confirmedOwner);
this.set('author', { owner: confirmedOwner });
this.set('commonOwners', commonOwners);
await render(
@ -57,7 +57,7 @@ module('Integration | Component | dataset author', function(hooks) {
confirmSuggestedOwnerActionCallCount++;
assert.equal(confirmSuggestedOwnerActionCallCount, 1, 'action is called once');
});
this.set('author', suggestedOwner);
this.set('author', { owner: suggestedOwner });
this.set('commonOwners', commonOwners);
await render(
@ -78,7 +78,7 @@ module('Integration | Component | dataset author', function(hooks) {
assert.ok(confirmedOwner === owner, 'updateOwnerType action is invoked correct owner reference');
assert.equal(type, confirmedOwner.type, 'updateOwnerType action is invoked with selected type');
});
this.set('author', confirmedOwner);
this.set('author', { owner: confirmedOwner });
this.set('commonOwners', commonOwners);
this.set('ownerTypes', ownerTypes);

View File

@ -23,7 +23,7 @@ module('Integration | Component | datasets/owners/suggested owner card', functio
test('it renders for base and empty cases', async function(assert) {
this.setProperties({
commonOwners,
author: {},
author: { owner: {} },
ownerTypes: [],
removeOwner: noop,
confirmSuggestedOwner: noop
@ -49,7 +49,7 @@ module('Integration | Component | datasets/owners/suggested owner card', functio
this.setProperties({
ownerTypes,
commonOwners,
author: model,
author: { owner: model },
removeOwner: noop,
confirmSuggestedOwner: noop
});
@ -79,7 +79,7 @@ module('Integration | Component | datasets/owners/suggested owner card', functio
this.setProperties({
ownerTypes,
commonOwners,
author: model,
author: { owner: model },
removeOwner: noop,
confirmSuggestedOwner: owner => {
assert.equal(owner.name, model.name, 'Passes the correct information to the confirmOwner function');