Skip to content

Commit

Permalink
Merge pull request #850 from theseyi/ownership-rewrite
Browse files Browse the repository at this point in the history
 - adds owner constants: default values, confirm and update owner functions
 - opts in to Ember array prototypal extensions
 - implements ownership distinction for suggested owners and confirmed owners. updates type definitions for owners
 - defers to more robust isEqual function on lodash
 - corrects the typings for ownerType vs ownerIdType. changes updateOwnerType action name to changeOwnerType on dataset-author component. adds fixtures for owner and user types. uses correct handler selectionDidChange for ember selector component
 - adds services stub for current user and exercises dataset-authors component
 - refactors interface name format. remove unused computed property
 - replaces manual DOM event triggers with ember native dom helpers
  • Loading branch information
theseyi authored Nov 11, 2017
2 parents 668e631 + 59cd5b4 commit 0aa6f79
Show file tree
Hide file tree
Showing 23 changed files with 1,049 additions and 194 deletions.
144 changes: 144 additions & 0 deletions wherehows-web/app/components/dataset-author.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import Component from '@ember/component';
import ComputedProperty, { equal } from '@ember/object/computed';
import { getProperties, computed } from '@ember/object';
import { assert } from '@ember/debug';

import { IOwner } from 'wherehows-web/typings/api/datasets/owners';
import { OwnerSource, OwnerType } from 'wherehows-web/utils/api/datasets/owners';

/**
* This component renders a single owner record and also provides functionality for interacting with the component
* in the ui or performing operations on a single owner record
* @export
* @class DatasetAuthor
* @extends {Component}
*/
export default class DatasetAuthor extends Component {
tagName = 'tr';

classNames = ['dataset-author-record'];

classNameBindings = ['isConfirmedSuggestedOwner:dataset-author-record--disabled'];

/**
* The owner record being rendered
* @type {IOwner}
* @memberof DatasetAuthor
*/
owner: IOwner;

/**
* List of suggested owners that have been confirmed by a user
* @type {Array<IOwner>}
* @memberof DatasetAuthor
*/
commonOwners: Array<IOwner>;

/**
* External action to handle owner removal from the confirmed list
* @param {IOwner} owner the owner to be removed
* @memberof DatasetAuthor
*/
removeOwner: (owner: IOwner) => IOwner | void;

/**
* External action to handle owner addition to the confirmed list
* @param {IOwner} owner the suggested owner to be confirmed
* @return {Array<IOwner> | void} the list of owners or void if unsuccessful
* @memberof DatasetAuthor
*/
confirmSuggestedOwner: (owner: IOwner) => Array<IOwner> | void;

/**
* External action to handle owner property updates, currently on the confirmed list
* @param {IOwner} owner the owner to update
* @param {OwnerType} type the type of the owner
* @memberof DatasetAuthor
*/
updateOwnerType: (owner: IOwner, type: OwnerType) => void;

/**
* A list of available owner types retrieved from the api
* @type {Array<string>}
* @memberof DatasetAuthor
*/
ownerTypes: Array<string>;

/**
* Compares the source attribute on an owner, if it matches the OwnerSource.Ui type
* @type {ComputedProperty<boolean>}
* @memberof DatasetAuthor
*/
isOwnerMutable: ComputedProperty<boolean> = equal('owner.source', OwnerSource.Ui);

/**
* Determines if the owner record is a system suggested owner and if this record is confirmed by a user
* @type {ComputedProperty<boolean>}
* @memberof DatasetAuthor
*/
isConfirmedSuggestedOwner: ComputedProperty<boolean> = computed('commonOwners', function(this: DatasetAuthor) {
const { commonOwners, isOwnerMutable, owner: { userName } } = getProperties(this, [
'commonOwners',
'isOwnerMutable',
'owner'
]);

if (!isOwnerMutable) {
return commonOwners.findBy('userName', userName);
}

return false;
});

constructor() {
super(...arguments);
const typeOfRemoveOwner = typeof this.removeOwner;
const typeOfConfirmSuggestedOwner = typeof this.confirmSuggestedOwner;

// Checks that the expected external actions are provided
assert(
`Expected action removeOwner to be an function (Ember action), got ${typeOfRemoveOwner}`,
typeOfRemoveOwner === 'function'
);

assert(
`Expected action confirmOwner to be an function (Ember action), got ${typeOfConfirmSuggestedOwner}`,
typeOfConfirmSuggestedOwner === 'function'
);
}

actions = {
/**
* Invokes the external action removeOwner to remove an owner from the confirmed list
* @return {boolean | void | IOwner}
*/
removeOwner: () => {
const { owner, isOwnerMutable, removeOwner } = getProperties(this, ['owner', 'isOwnerMutable', 'removeOwner']);
return isOwnerMutable && removeOwner(owner);
},

/**
* Invokes the external action for confirming the suggested owner
* @return {Array<IOwner> | void}
*/
confirmOwner: () => {
const { owner, confirmSuggestedOwner } = getProperties(this, ['owner', 'confirmSuggestedOwner']);
return confirmSuggestedOwner(owner);
},

/**
* Updates the type attribute on the owner record
* @param {OwnerType} type value to update the type attribute with
* @return {void}
*/
changeOwnerType: (type: OwnerType) => {
const { owner, isOwnerMutable, updateOwnerType } = getProperties(this, [
'owner',
'isOwnerMutable',
'updateOwnerType'
]);

return isOwnerMutable && updateOwnerType(owner, type);
}
};
}
250 changes: 250 additions & 0 deletions wherehows-web/app/components/dataset-authors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
import Component from '@ember/component';
import { inject } from '@ember/service';
import ComputedProperty, { or, lt, filter } from '@ember/object/computed';
import { set, get, computed, getProperties } from '@ember/object';
import { assert } from '@ember/debug';
import { isEqual } from 'lodash';

import UserLookup from 'wherehows-web/services/user-lookup';
import CurrentUser from 'wherehows-web/services/current-user';
import { IOwner } from 'wherehows-web/typings/api/datasets/owners';
import {
defaultOwnerProps,
defaultOwnerUserName,
minRequiredConfirmedOwners,
ownerAlreadyExists,
userNameEditableClass,
confirmOwner,
updateOwner
} from 'wherehows-web/constants/datasets/owner';
import { OwnerSource, OwnerIdType, OwnerType } from 'wherehows-web/utils/api/datasets/owners';
import { ApiStatus } from 'wherehows-web/utils/api';

/**
* Defines properties for the component that renders a list of owners and provides functionality for
* interacting with the list items or the list as whole
* @export
* @class DatasetAuthors
* @extends {Component}
*/
export default class DatasetAuthors extends Component {
/**
* Invokes an external save action to persist the list of owners
* @return {Promise<{ status: ApiStatus }>}
* @memberof DatasetAuthors
*/
save: (owners: Array<IOwner>) => Promise<{ status: ApiStatus }>;

/**
* The list of owners
* @type {Array<IOwner>}
* @memberof DatasetAuthors
*/
owners: Array<IOwner>;

/**
* Current user service
* @type {ComputedProperty<CurrentUser>}
* @memberof DatasetAuthors
*/
currentUser: ComputedProperty<CurrentUser> = inject();

/**
* User look up service
* @type {ComputedProperty<UserLookup>}
* @memberof DatasetAuthors
*/
userLookup: ComputedProperty<UserLookup> = inject();

/**
* Reference to the userNamesResolver function to asynchronously match userNames
* @type {UserLookup['userNamesResolver']}
* @memberof DatasetAuthors
*/
userNamesResolver: UserLookup['userNamesResolver'];

/**
* A list of valid owner type strings returned from the remote api endpoint
* @type {Array<string>}
* @memberof DatasetAuthors
*/
ownerTypes: Array<string>;

/**
* Computed flag indicating that a set of negative flags is true
* e.g. if the userName is invalid or the required minimum users are not confirmed
* @type {ComputedProperty<boolean>}
* @memberof DatasetAuthors
*/
ownershipIsInvalid: ComputedProperty<boolean> = or('userNameInvalid', 'requiredMinNotConfirmed');

/**
* Checks that the list of owners does not contain a default user name
* @type {ComputedProperty<boolean>}
* @memberof DatasetAuthors
*/
userNameInvalid: ComputedProperty<boolean> = computed('owners.[]', function(this: DatasetAuthors) {
const owners = get(this, 'owners') || [];

return owners.filter(({ userName }) => userName === defaultOwnerUserName).length > 0;
});

/**
* Flag that resolves in the affirmative if the number of confirmed owner is less the minimum required
* @type {ComputedProperty<boolean>}
* @memberof DatasetAuthors
*/
requiredMinNotConfirmed: ComputedProperty<boolean> = lt('confirmedOwners.length', minRequiredConfirmedOwners);

/**
* Lists the owners that have be confirmed view the client ui
* @type {ComputedProperty<Array<IOwner>>}
* @memberof DatasetAuthors
*/
confirmedOwners: ComputedProperty<Array<IOwner>> = filter('owners', function({ source }: IOwner) {
return source === OwnerSource.Ui;
});

/**
* Intersection of confirmed owners and suggested owners
* @type {ComputedProperty<Array<IOwner>>}
* @memberof DatasetAuthors
*/
commonOwners: ComputedProperty<Array<IOwner>> = computed(
'[email protected]',
'[email protected]',
function(this: DatasetAuthors) {
const { confirmedOwners = [], systemGeneratedOwners = [] } = getProperties(this, [
'confirmedOwners',
'systemGeneratedOwners'
]);

return confirmedOwners.reduce((common, owner) => {
const { userName } = owner;
return systemGeneratedOwners.findBy('userName', userName) ? [...common, owner] : common;
}, []);
}
);

/**
* Lists owners that have been gleaned from dataset metadata
* @type {ComputedProperty<Array<IOwner>>}
* @memberof DatasetAuthors
*/
systemGeneratedOwners: ComputedProperty<Array<IOwner>> = filter('owners', function({ source }: IOwner) {
return source !== OwnerSource.Ui;
});

constructor() {
super(...arguments);
const typeOfSaveAction = typeof this.save;

// on instantiation, sets a reference to the userNamesResolver async function
set(this, 'userNamesResolver', get(get(this, 'userLookup'), 'userNamesResolver'));

assert(
`Expected action save to be an function (Ember action), got ${typeOfSaveAction}`,
typeOfSaveAction === 'function'
);
}

actions = {
/**
* Prepares component for updates to the userName attribute
* @param {IOwner} _ unused
* @param {HTMLElement} { currentTarget }
*/
willEditUserName(_: IOwner, { currentTarget }: Event) {
const { classList } = <HTMLElement>(currentTarget || {});
if (classList instanceof HTMLElement) {
classList.add(userNameEditableClass);
}
},

/**
* Updates the owner instance userName property
* @param {IOwner} currentOwner an instance of an IOwner type to be updates
* @param {string} [userName] optional userName to update to
*/
editUserName: async (currentOwner: IOwner, userName?: string) => {
if (userName) {
const { getPartyEntityWithUserName } = get(this, 'userLookup');
const partyEntity = await getPartyEntityWithUserName(userName);

if (partyEntity) {
const { label, displayName, category } = partyEntity;
const isGroup = category === 'group';
const updatedOwnerProps: IOwner = {
...currentOwner,
isGroup,
source: OwnerSource.Ui,
userName: label,
name: displayName,
idType: isGroup ? OwnerIdType.Group : OwnerIdType.User
};

updateOwner(get(this, 'owners'), currentOwner, updatedOwnerProps);
}
}
},

/**
* Adds the component owner record to the list of owners with default props
* @returns {Array<IOwner> | void}
*/
addOwner: () => {
const owners = get(this, 'owners') || [];
const newOwner: IOwner = { ...defaultOwnerProps };

if (!ownerAlreadyExists(owners, { userName: newOwner.userName })) {
const { userName } = get(get(this, 'currentUser'), 'currentUser');
let updatedOwners = [newOwner, ...owners];
confirmOwner(get(this, 'owners'), newOwner, userName);

return owners.setObjects(updatedOwners);
}
},

/**
* Updates the type attribute for a given owner in the owner list
* @param {IOwner} owner owner to be updates
* @param {OwnerType} type new value to be set on the type attribute
*/
updateOwnerType: (owner: IOwner, type: OwnerType) => {
const owners = get(this, 'owners') || [];
return updateOwner(owners, owner, 'type', type);
},

/**
* Adds the owner instance to the list of owners with the source set to ui
* @param {IOwner} owner the owner to add to the list of owner with the source set to OwnerSource.Ui
* @return {Array<IOwner> | void}
*/
confirmSuggestedOwner: (owner: IOwner) => {
const owners = get(this, 'owners') || [];
const suggestedOwner = { ...owner, source: OwnerSource.Ui };
const hasSuggested = owners.find(owner => isEqual(owner, suggestedOwner));

if (!hasSuggested) {
return owners.setObjects([...owners, suggestedOwner]);
}
},

/**
* removes an owner instance from the list of owners
* @param {IOwner} owner the owner to be removed
*/
removeOwner: (owner: IOwner) => {
const owners = get(this, 'owners') || [];
return owners.removeObject(owner);
},

/**
* Persists the owners list by invoking the external action
*/
saveOwners: () => {
const { save } = this;
save(get(this, 'owners'));
}
};
}
Loading

0 comments on commit 0aa6f79

Please sign in to comment.