-
Notifications
You must be signed in to change notification settings - Fork 3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #850 from theseyi/ownership-rewrite
- 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
Showing
23 changed files
with
1,049 additions
and
194 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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')); | ||
} | ||
}; | ||
} |
Oops, something went wrong.