From ef64ffbd7ed683a5dff299fe53921775217eff55 Mon Sep 17 00:00:00 2001 From: Seyi Adebajo Date: Wed, 8 Nov 2017 22:09:01 -0800 Subject: [PATCH 01/10] adds owner constants: default values, confirm and update owner functions --- wherehows-web/app/constants/datasets/owner.ts | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 wherehows-web/app/constants/datasets/owner.ts diff --git a/wherehows-web/app/constants/datasets/owner.ts b/wherehows-web/app/constants/datasets/owner.ts new file mode 100644 index 0000000000..4544b0a434 --- /dev/null +++ b/wherehows-web/app/constants/datasets/owner.ts @@ -0,0 +1,131 @@ +import { IOwner } from 'wherehows-web/typings/api/datasets/owners'; +import { OwnerSource, OwnerType, OwnerUrnNamespace } from 'wherehows-web/utils/api/datasets/owners'; +import { isListUnique } from 'wherehows-web/utils/array'; + +/** + * Initial user name for candidate owners + * @type {string} + */ +const defaultOwnerUserName = 'New Owner'; + +/** + * The minimum required number of owners + * @type {number} + */ +const minRequiredConfirmedOwners = 2; + +/** + * Class to toggle readonly mode vs edit mode + * @type {string} + */ +const userNameEditableClass = 'dataset-author-cell--editing'; + +/** + * Checks that a userName already exists in the list of IOwner instances + * @param {Array} owners the list of owners + * @param {Pick} newOwner userName for the owner + * @returns {boolean} true if owner username in current list of owners + */ +const ownerAlreadyExists = (owners: Array, newOwner: Pick) => { + const newUserNameRegEx = new RegExp(`.*${newOwner.userName}.*`, 'i'); + + return owners.mapBy('userName').some((userName: string) => newUserNameRegEx.test(userName)); +}; + +// overloads +function updateOwner(owners: Array, owner: IOwner, props: IOwner): void | Array; +function updateOwner( + owners: Array, + owner: IOwner, + props: K, + value: IOwner[K] +): void | Array; +/** + * Updates an IOwner instance in a list of IOwners using a known key and expected value type, + * or a replacement attributes + * @template K + * @param {Array} owners the list containing the owners + * @param {IOwner} owner the owner to update + * @param {(K | IOwner)} props the properties to replace the IOwner instance with, or a singe IOwner attribute + * @param {IOwner[K]} [value] optional value to update the attribute with + * @returns {(void | Array)} the updated list of owners if the owner list contains no duplicates + */ +function updateOwner( + owners: Array, + owner: IOwner, + props: K | IOwner, + value?: IOwner[K] +): void | Array { + // creates a local working copy of the list of owners + const updatingOwners = [...owners]; + + // ensure that the owner is in the list by referential equality + if (updatingOwners.includes(owner)) { + const ownerPosition = updatingOwners.indexOf(owner); + let updatedOwner: IOwner; + + // if props is a string, i.e. attribute IOwner, override the previous value, + // otherwise replace with new attributes + if (typeof props === 'string') { + updatedOwner = { ...owner, [props]: value }; + } else { + updatedOwner = props; + } + + // retain update position + const updatedOwners: Array = [ + ...updatingOwners.slice(0, ownerPosition), + updatedOwner, + ...updatingOwners.slice(ownerPosition + 1) + ]; + + // each owner is uniquely identified by the composite key of userName and source + const userKeys = updatedOwners.map(({ userName, source }) => `${userName}:${source}`); + + // ensure we have not duplicates + if (isListUnique(userKeys)) { + return owners.setObjects(updatedOwners); + } + } +} + +/** + * Sets the `confirmedBy` attribute to the currently logged in user + * @param {Array} owners the list of owners + * @param {IOwner} owner the owner to be updated + * @param {string} confirmedBy the userName of the confirming user + * @returns {(Array | void)} + */ +const confirmOwner = (owners: Array, owner: IOwner, confirmedBy: string): Array | void => { + const isConfirmedBy = confirmedBy || null; + return updateOwner(owners, owner, 'confirmedBy', isConfirmedBy); + // return set(owner, 'confirmedBy', isConfirmedBy); +}; + +/** + * Defines the default properties for a newly created IOwner instance + *@type {IOwner} + */ +const defaultOwnerProps: IOwner = { + userName: defaultOwnerUserName, + email: null, + name: '', + isGroup: false, + namespace: OwnerUrnNamespace.groupUser, + type: 'Owner', + subType: null, + sortId: 0, + source: OwnerSource.Ui, + confirmedBy: null, + idType: OwnerType.User +}; + +export { + defaultOwnerProps, + defaultOwnerUserName, + minRequiredConfirmedOwners, + userNameEditableClass, + ownerAlreadyExists, + updateOwner, + confirmOwner +}; From 4f2f16a1919b2cf85075b5c142ca039e64e021f8 Mon Sep 17 00:00:00 2001 From: Seyi Adebajo Date: Wed, 8 Nov 2017 22:16:41 -0800 Subject: [PATCH 02/10] opts in to Ember array prototypal extensions --- wherehows-web/app/typings/global-plugin.d.ts | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 wherehows-web/app/typings/global-plugin.d.ts diff --git a/wherehows-web/app/typings/global-plugin.d.ts b/wherehows-web/app/typings/global-plugin.d.ts new file mode 100644 index 0000000000..35e0bad9d1 --- /dev/null +++ b/wherehows-web/app/typings/global-plugin.d.ts @@ -0,0 +1,6 @@ +import Ember from 'ember'; + +// opt-in to allow types for Ember Array Prototype extensions +declare global { + interface Array extends Ember.ArrayPrototypeExtensions {} +} From d91bd4f1d049dc9971b50e87b5232dd2bb365a20 Mon Sep 17 00:00:00 2001 From: Seyi Adebajo Date: Wed, 8 Nov 2017 23:21:07 -0800 Subject: [PATCH 03/10] adds the dataset-author and dataset-authors components --- .../app/components/dataset-author.ts | 144 ++++++++++ .../app/components/dataset-authors.ts | 250 ++++++++++++++++++ 2 files changed, 394 insertions(+) create mode 100644 wherehows-web/app/components/dataset-author.ts create mode 100644 wherehows-web/app/components/dataset-authors.ts diff --git a/wherehows-web/app/components/dataset-author.ts b/wherehows-web/app/components/dataset-author.ts new file mode 100644 index 0000000000..6d3c78d43e --- /dev/null +++ b/wherehows-web/app/components/dataset-author.ts @@ -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} + * @memberof DatasetAuthor + */ + commonOwners: Array; + + /** + * 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 | void} the list of owners or void if unsuccessful + * @memberof DatasetAuthor + */ + confirmSuggestedOwner: (owner: IOwner) => Array | 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 retreived from the api + * @type {Array} + * @memberof DatasetAuthor + */ + ownerTypes: Array; + + /** + * Compares the source attribute on an owner, if it matches the OwnerSource.Ui type + * @type {ComputedProperty} + * @memberof DatasetAuthor + */ + isOwnerMutable: ComputedProperty = equal('owner.source', OwnerSource.Ui); + + /** + * Detemines if the owner record is a system suggested owner and if this record is confirmed by a user + * @type {ComputedProperty} + * @memberof DatasetAuthor + */ + isConfirmedSuggestedOwner: ComputedProperty = 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 {false | void | IOwner} + */ + removeOwner: () => { + const { owner, isOwnerMutable, removeOwner } = getProperties(this, ['owner', 'isOwnerMutable', 'removeOwner']); + return isOwnerMutable && removeOwner(owner); + }, + + /** + * @return {Array | void} + */ + confirmOwner: () => { + const { owner, confirmSuggestedOwner } = getProperties(this, ['owner', 'confirmSuggestedOwner']); + return confirmSuggestedOwner(owner); + }, + + /** + * Updates the type attribute on the owner record + * @param {HTMLSelectElement} {target} + * @return { void } + */ + updateOwnerType: ({ target }: Event) => { + const { value } = target; + const { owner, isOwnerMutable, updateOwnerType } = getProperties(this, [ + 'owner', + 'isOwnerMutable', + 'updateOwnerType' + ]); + + return isOwnerMutable && updateOwnerType(owner, value); + } + }; +} diff --git a/wherehows-web/app/components/dataset-authors.ts b/wherehows-web/app/components/dataset-authors.ts new file mode 100644 index 0000000000..af945a1d1b --- /dev/null +++ b/wherehows-web/app/components/dataset-authors.ts @@ -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 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, OwnerType } from 'wherehows-web/utils/api/datasets/owners'; +import { objectDeepEqual } from 'wherehows-web/utils/object'; +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) => Promise<{ status: ApiStatus }>; + + /** + * The list of owners + * @type {Array} + * @memberof DatasetAuthors + */ + owners: Array; + + /** + * Current user service + * @type {ComputedProperty} + * @memberof DatasetAuthors + */ + currentUser: ComputedProperty = inject(); + + /** + * User look up service + * @type {ComputedProperty} + * @memberof DatasetAuthors + */ + userLookup: ComputedProperty = 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} + * @memberof DatasetAuthors + */ + ownerTypes: Array; + + /** + * 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} + * @memberof DatasetAuthors + */ + ownershipIsInvalid: ComputedProperty = or('userNameInvalid', 'requiredMinNotConfirmed'); + + /** + * Checks that the list of owners does not contain a default user name + * @type {ComputedProperty} + * @memberof DatasetAuthors + */ + userNameInvalid: ComputedProperty = 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} + * @memberof DatasetAuthors + */ + requiredMinNotConfirmed: ComputedProperty = lt('confirmedOwners.length', minRequiredConfirmedOwners); + + /** + * Lists the owners that have be confirmed view the client ui + * @type {ComputedProperty>} + * @memberof DatasetAuthors + */ + confirmedOwners: ComputedProperty> = filter('owners', function({ source }: IOwner) { + return source === OwnerSource.Ui; + }); + + /** + * Intersection of confirmed owners and suggested owners + * @type {ComputedProperty>} + * @memberof DatasetAuthors + */ + commonOwners: ComputedProperty> = computed( + 'confirmedOwners.@each.userName', + 'systemGeneratedOwners.@each.userName', + 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>} + * @memberof DatasetAuthors + */ + systemGeneratedOwners: ComputedProperty> = 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 } = (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 ? OwnerType.Group : OwnerType.User + }; + + updateOwner(get(this, 'owners'), currentOwner, updatedOwnerProps); + } + } + }, + + /** + * Adds the component owner record to the list of owners with default props + * @returns {Array | 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 | void} + */ + confirmSuggestedOwner: (owner: IOwner) => { + const owners = get(this, 'owners') || []; + const suggestedOwner = { ...owner, source: OwnerSource.Ui }; + const hasSuggested = owners.find(owner => objectDeepEqual(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')); + } + }; +} From 953c340781f1b6c82dfae497dd49faf8547dd037 Mon Sep 17 00:00:00 2001 From: Seyi Adebajo Date: Thu, 9 Nov 2017 09:47:57 -0800 Subject: [PATCH 04/10] implements ownership distinction for suggested owners and confirmed owners. updates type defintions for owners --- .../app/components/dataset-author.ts | 2 +- .../components/dataset-author/_all.scss | 3 +- .../dataset-author/_dataset-author.scss | 23 ++ .../dataset-author/_owner-table.scss | 15 ++ .../templates/components/dataset-author.hbs | 198 +++++------------- .../templates/components/dataset-authors.hbs | 106 ++++++++++ .../app/templates/datasets/dataset.hbs | 5 +- .../app/typings/api/datasets/owners.d.ts | 24 +-- .../app/utils/api/datasets/owners.ts | 23 +- wherehows-web/app/utils/object.ts | 12 +- 10 files changed, 245 insertions(+), 166 deletions(-) create mode 100644 wherehows-web/app/styles/components/dataset-author/_dataset-author.scss create mode 100644 wherehows-web/app/templates/components/dataset-authors.hbs diff --git a/wherehows-web/app/components/dataset-author.ts b/wherehows-web/app/components/dataset-author.ts index 6d3c78d43e..8f5168d824 100644 --- a/wherehows-web/app/components/dataset-author.ts +++ b/wherehows-web/app/components/dataset-author.ts @@ -128,7 +128,7 @@ export default class DatasetAuthor extends Component { /** * Updates the type attribute on the owner record * @param {HTMLSelectElement} {target} - * @return { void } + * @return {void} */ updateOwnerType: ({ target }: Event) => { const { value } = target; diff --git a/wherehows-web/app/styles/components/dataset-author/_all.scss b/wherehows-web/app/styles/components/dataset-author/_all.scss index 7ea1a8e716..9bbf2321d8 100644 --- a/wherehows-web/app/styles/components/dataset-author/_all.scss +++ b/wherehows-web/app/styles/components/dataset-author/_all.scss @@ -1 +1,2 @@ -@import "owner-table"; \ No newline at end of file +@import 'owner-table'; +@import 'dataset-author'; diff --git a/wherehows-web/app/styles/components/dataset-author/_dataset-author.scss b/wherehows-web/app/styles/components/dataset-author/_dataset-author.scss new file mode 100644 index 0000000000..31c83d37fe --- /dev/null +++ b/wherehows-web/app/styles/components/dataset-author/_dataset-author.scss @@ -0,0 +1,23 @@ +.dataset-author { + margin-top: item-spacing(7); + + &__header { + display: flex; + font-size: 20px; + font-weight: fw(normal, 4); + } +} + +.dataset-author-record { + &--disabled { + color: set-color(grey, mid); + font-weight: fw(italic, 2); + pointer-events: none; + } + + &#{&} &__action { + &--disabled { + background-color: set-color(grey, mid); + } + } +} diff --git a/wherehows-web/app/styles/components/dataset-author/_owner-table.scss b/wherehows-web/app/styles/components/dataset-author/_owner-table.scss index 9b30efec6f..4a29dc6b80 100644 --- a/wherehows-web/app/styles/components/dataset-author/_owner-table.scss +++ b/wherehows-web/app/styles/components/dataset-author/_owner-table.scss @@ -58,3 +58,18 @@ $user-name-width: 170px; width: 9%; } } + +.dataset-owner-table { + &#{&} { + margin-top: item-spacing(4); + box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.16), 0 0 0 1px rgba(0, 0, 0, 0.08); + transition: box-shadow 200ms cubic-bezier(0.4, 0, 0.2, 1); + border: 0; + } + + &#{&} td, + &#{&} th { + padding: item-spacing(5 4); + font-weight: fw(normal, 4); + } +} diff --git a/wherehows-web/app/templates/components/dataset-author.hbs b/wherehows-web/app/templates/components/dataset-author.hbs index e90170acbc..b33adf1d82 100644 --- a/wherehows-web/app/templates/components/dataset-author.hbs +++ b/wherehows-web/app/templates/components/dataset-author.hbs @@ -1,148 +1,64 @@ -
-
-
- + + {{else}} + + {{#if isConfirmedSuggestedOwner}} + + - -
-
- - - {{#if errorMessage}} - {{/if}} + - {{#if actionMessage}} - - {{/if}} - - - - - - - - - - - - - - - - {{#each owners as |owner|}} - - - - - - - - - - - - {{/each}} - -
LDAP UsernameFull NameID TypeSourceLast Modified - Owner Type - - - - - - - - - Confirm?Remove
- - - {{aupac-typeahead - source=userNamesResolver - action=(action "editUserName" owner) - autoFocus=true - async=true - limit=10 - minLength=2 - placeholder="Find user by LDAP" - title="username" - class="form-control dataset-author-cell__user-name"}} - {{owner.name}}{{owner.idType}}{{owner.source}} - {{!-- e.g Jul 18th 2016, 11:11 am --}} - {{moment-calendar owner.modifiedTime sameElse="MMM Do YYYY, h:mm a"}} - - {{ember-selector - values=ownerTypes - selected=owner.type - change=(action "updateOwnerType" owner)}} - - {{input - type="checkbox" - title=(if owner.confirmedBy owner.confirmedBy "Not confirmed") - checked=(readonly owner.confirmedBy) - change=(action "confirmOwner" owner)}} - - -
-
diff --git a/wherehows-web/app/templates/components/dataset-authors.hbs b/wherehows-web/app/templates/components/dataset-authors.hbs new file mode 100644 index 0000000000..d121d5f77a --- /dev/null +++ b/wherehows-web/app/templates/components/dataset-authors.hbs @@ -0,0 +1,106 @@ +
+
+

+ Owners Confirmed +

+ + +
+ + + + + + + + + + + + + + + {{#each confirmedOwners as |confirmedOwner|}} + + + {{dataset-author + owner=confirmedOwner + ownerTypes=ownerTypes + removeOwner=(action "removeOwner") + confirmSuggestedOwner=(action "confirmSuggestedOwner") + updateOwnerType=(action "updateOwnerType") + }} + + {{else}} + {{/each}} +
LDAP UsernameFull NameID TypeSourceLast Modified + Ownership Type + + + + + + + + + Remove Owner
+
+ + +
+
+

+ System Suggested Owners +

+ + +
+ + + + + + + + + + + + + + + + {{#each systemGeneratedOwners as |systemGeneratedOwner|}} + {{dataset-author + owner=systemGeneratedOwner + ownerTypes=ownerTypes + commonOwners=commonOwners + removeOwner=(action "removeOwner") + confirmSuggestedOwner=(action "confirmSuggestedOwner") + updateOwnerType=(action "updateOwnerType") + }} + {{/each}} + + +
LDAP UsernameFull NameID TypeSourceLast Modified + Owner Type + + + + + + + + + Add Suggested Owner
+
diff --git a/wherehows-web/app/templates/datasets/dataset.hbs b/wherehows-web/app/templates/datasets/dataset.hbs index 7a824dae98..e48d8f1c82 100644 --- a/wherehows-web/app/templates/datasets/dataset.hbs +++ b/wherehows-web/app/templates/datasets/dataset.hbs @@ -223,12 +223,9 @@ }}
- {{dataset-author + {{dataset-authors owners=owners ownerTypes=ownerTypes - showMsg=showMsg - alertType=alertType - ownerMessage=ownerMessage save=(action "saveOwnerChanges") }}
diff --git a/wherehows-web/app/typings/api/datasets/owners.d.ts b/wherehows-web/app/typings/api/datasets/owners.d.ts index 08b0eab021..579fb5b9f6 100644 --- a/wherehows-web/app/typings/api/datasets/owners.d.ts +++ b/wherehows-web/app/typings/api/datasets/owners.d.ts @@ -1,30 +1,20 @@ import { ApiStatus } from 'wherehows-web/utils/api/shared'; -import { OwnerType } from 'wherehows-web/utils/api/datasets/owners'; - -/** - * Accepted string values for the Owner type - */ -type OwnerTypeLiteral = OwnerType.User | OwnerType.Group; - -/** - * Accepted string values for the namespace of a user - */ -type OwnerUrnLiteral = 'urn:li:corpuser' | 'urn:li:corpGroup'; +import { OwnerSource, OwnerType, OwnerUrnNamespace } from 'wherehows-web/utils/api/datasets/owners'; /** * Describes the interface for an Owner entity */ export interface IOwner { confirmedBy: null | string; - email: string; - idType: OwnerTypeLiteral; - isActive: boolean; + email: null | string; + idType: OwnerType; + isActive?: boolean; isGroup: boolean; - modifiedTime: number | Date; + modifiedTime?: number | Date; name: string; - namespace: OwnerUrnLiteral; + namespace: OwnerUrnNamespace; sortId: null | number; - source: string; + source: OwnerSource; subType: null; type: string; userName: string; diff --git a/wherehows-web/app/utils/api/datasets/owners.ts b/wherehows-web/app/utils/api/datasets/owners.ts index 1211008750..59ffc430a5 100644 --- a/wherehows-web/app/utils/api/datasets/owners.ts +++ b/wherehows-web/app/utils/api/datasets/owners.ts @@ -17,6 +17,27 @@ export enum OwnerType { Group = 'GROUP' } +/** + * Accepted string values for the namespace of a user + */ +export enum OwnerUrnNamespace { + corpUser = 'urn:li:corpuser', + groupUser = 'urn:li:corpGroup' +} + +export enum OwnerSource { + Scm = 'SCM', + Nuage = 'NUAGE', + Sos = 'SOS', + Db = 'DB', + Audit = 'AUDIT', + Jira = 'JIRA', + RB = 'RB', + Ui = 'UI', + Fs = 'FS', + Other = 'OTHER' +} + const { $: { getJSON } } = Ember; /** @@ -39,7 +60,7 @@ export const getDatasetOwners = async (id: number): Promise> => { return status === ApiStatus.OK ? owners.map(owner => ({ ...owner, - modifiedTime: new Date(owner.modifiedTime) + modifiedTime: new Date(owner.modifiedTime) })) : Promise.reject(status); }; diff --git a/wherehows-web/app/utils/object.ts b/wherehows-web/app/utils/object.ts index c101ec892c..8a279561d5 100644 --- a/wherehows-web/app/utils/object.ts +++ b/wherehows-web/app/utils/object.ts @@ -5,6 +5,16 @@ const isObject = (candidate: any): candidate is object => candidate && Object.prototype.toString.call(candidate) === '[object Object]'; +/** + * Checks if an object type is deeply equal to another + * @param {object} objectA the first object to compare + * @param {object} objectB the second object to compare against + * @returns {boolean} + */ +const objectDeepEqual = (objectA: object, objectB: object): boolean => { + return isObject(objectB) && isObject(objectA) && JSON.stringify(objectA) === JSON.stringify(objectB); +}; + /** * Checks that an object has it own enumerable props * @param {Object} object the object to the be tested @@ -12,4 +22,4 @@ const isObject = (candidate: any): candidate is object => */ const hasEnumerableKeys = (object: object): boolean => isObject(object) && !!Object.keys(object).length; -export { isObject, hasEnumerableKeys }; +export { isObject, hasEnumerableKeys, objectDeepEqual }; From 75a5d3145f7c03e7106a8a5ec91f88b8a3c11278 Mon Sep 17 00:00:00 2001 From: Seyi Adebajo Date: Thu, 9 Nov 2017 13:09:20 -0800 Subject: [PATCH 05/10] defers to more robust isequal function on lodash --- wherehows-web/app/components/dataset-authors.ts | 4 ++-- wherehows-web/app/utils/object.ts | 12 +----------- wherehows-web/package.json | 1 + wherehows-web/yarn.lock | 4 ++++ 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/wherehows-web/app/components/dataset-authors.ts b/wherehows-web/app/components/dataset-authors.ts index af945a1d1b..6d6629f6d1 100644 --- a/wherehows-web/app/components/dataset-authors.ts +++ b/wherehows-web/app/components/dataset-authors.ts @@ -3,6 +3,7 @@ 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'; @@ -17,7 +18,6 @@ import { updateOwner } from 'wherehows-web/constants/datasets/owner'; import { OwnerSource, OwnerType } from 'wherehows-web/utils/api/datasets/owners'; -import { objectDeepEqual } from 'wherehows-web/utils/object'; import { ApiStatus } from 'wherehows-web/utils/api'; /** @@ -223,7 +223,7 @@ export default class DatasetAuthors extends Component { confirmSuggestedOwner: (owner: IOwner) => { const owners = get(this, 'owners') || []; const suggestedOwner = { ...owner, source: OwnerSource.Ui }; - const hasSuggested = owners.find(owner => objectDeepEqual(owner, suggestedOwner)); + const hasSuggested = owners.find(owner => isEqual(owner, suggestedOwner)); if (!hasSuggested) { return owners.setObjects([...owners, suggestedOwner]); diff --git a/wherehows-web/app/utils/object.ts b/wherehows-web/app/utils/object.ts index 8a279561d5..c101ec892c 100644 --- a/wherehows-web/app/utils/object.ts +++ b/wherehows-web/app/utils/object.ts @@ -5,16 +5,6 @@ const isObject = (candidate: any): candidate is object => candidate && Object.prototype.toString.call(candidate) === '[object Object]'; -/** - * Checks if an object type is deeply equal to another - * @param {object} objectA the first object to compare - * @param {object} objectB the second object to compare against - * @returns {boolean} - */ -const objectDeepEqual = (objectA: object, objectB: object): boolean => { - return isObject(objectB) && isObject(objectA) && JSON.stringify(objectA) === JSON.stringify(objectB); -}; - /** * Checks that an object has it own enumerable props * @param {Object} object the object to the be tested @@ -22,4 +12,4 @@ const objectDeepEqual = (objectA: object, objectB: object): boolean => { */ const hasEnumerableKeys = (object: object): boolean => isObject(object) && !!Object.keys(object).length; -export { isObject, hasEnumerableKeys, objectDeepEqual }; +export { isObject, hasEnumerableKeys }; diff --git a/wherehows-web/package.json b/wherehows-web/package.json index e4b73daea2..256e8f3814 100644 --- a/wherehows-web/package.json +++ b/wherehows-web/package.json @@ -20,6 +20,7 @@ "devDependencies": { "@types/ember": "^2.8.0", "@types/ember-testing-helpers": "^0.0.3", + "@types/lodash": "^4.14.83", "@types/rsvp": "^4.0.0", "babel-eslint": "^8.0.1", "babel-plugin-transform-class-properties": "^6.24.1", diff --git a/wherehows-web/yarn.lock b/wherehows-web/yarn.lock index 41710e9621..92d7424e92 100644 --- a/wherehows-web/yarn.lock +++ b/wherehows-web/yarn.lock @@ -113,6 +113,10 @@ version "3.2.15" resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.2.15.tgz#3f620a9f5a0b296866f4bc729825226d0a35fba6" +"@types/lodash@^4.14.83": + version "4.14.83" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.83.tgz#2f2154797ce8fd8d6ea91a8d304a4e44ee95920c" + "@types/rsvp@*", "@types/rsvp@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/rsvp/-/rsvp-4.0.0.tgz#6c59d84bb5ea8a4fd11ec3d7aa748710e0e5e373" From 1893668806ef74a55bd10258f2e05e7aa25640ea Mon Sep 17 00:00:00 2001 From: Seyi Adebajo Date: Fri, 10 Nov 2017 00:08:50 -0800 Subject: [PATCH 06/10] 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 --- .../app/components/dataset-author.ts | 14 +-- .../app/components/dataset-authors.ts | 4 +- wherehows-web/app/constants/datasets/owner.ts | 6 +- wherehows-web/app/services/user-lookup.ts | 7 +- .../templates/components/dataset-author.hbs | 6 +- .../app/typings/api/datasets/owners.d.ts | 6 +- .../app/utils/api/datasets/owners.ts | 16 ++- wherehows-web/mirage/fixtures/owners.ts | 33 ++++++ wherehows-web/mirage/fixtures/users.ts | 15 +++ .../components/dataset-author-test.js | 103 ++++++++++++++++++ 10 files changed, 189 insertions(+), 21 deletions(-) create mode 100644 wherehows-web/mirage/fixtures/owners.ts create mode 100644 wherehows-web/mirage/fixtures/users.ts create mode 100644 wherehows-web/tests/integration/components/dataset-author-test.js diff --git a/wherehows-web/app/components/dataset-author.ts b/wherehows-web/app/components/dataset-author.ts index 8f5168d824..eafb3e4d3a 100644 --- a/wherehows-web/app/components/dataset-author.ts +++ b/wherehows-web/app/components/dataset-author.ts @@ -58,7 +58,7 @@ export default class DatasetAuthor extends Component { updateOwnerType: (owner: IOwner, type: OwnerType) => void; /** - * A list of available owner types retreived from the api + * A list of available owner types retrieved from the api * @type {Array} * @memberof DatasetAuthor */ @@ -72,7 +72,7 @@ export default class DatasetAuthor extends Component { isOwnerMutable: ComputedProperty = equal('owner.source', OwnerSource.Ui); /** - * Detemines if the owner record is a system suggested owner and if this record is confirmed by a user + * Determines if the owner record is a system suggested owner and if this record is confirmed by a user * @type {ComputedProperty} * @memberof DatasetAuthor */ @@ -110,7 +110,7 @@ export default class DatasetAuthor extends Component { actions = { /** * Invokes the external action removeOwner to remove an owner from the confirmed list - * @return {false | void | IOwner} + * @return {boolean | void | IOwner} */ removeOwner: () => { const { owner, isOwnerMutable, removeOwner } = getProperties(this, ['owner', 'isOwnerMutable', 'removeOwner']); @@ -118,6 +118,7 @@ export default class DatasetAuthor extends Component { }, /** + * Invokes the external action for confirming the suggested owner * @return {Array | void} */ confirmOwner: () => { @@ -127,18 +128,17 @@ export default class DatasetAuthor extends Component { /** * Updates the type attribute on the owner record - * @param {HTMLSelectElement} {target} + * @param {OwnerType} type value to update the type attribute with * @return {void} */ - updateOwnerType: ({ target }: Event) => { - const { value } = target; + changeOwnerType: (type: OwnerType) => { const { owner, isOwnerMutable, updateOwnerType } = getProperties(this, [ 'owner', 'isOwnerMutable', 'updateOwnerType' ]); - return isOwnerMutable && updateOwnerType(owner, value); + return isOwnerMutable && updateOwnerType(owner, type); } }; } diff --git a/wherehows-web/app/components/dataset-authors.ts b/wherehows-web/app/components/dataset-authors.ts index 6d6629f6d1..f51c9b763a 100644 --- a/wherehows-web/app/components/dataset-authors.ts +++ b/wherehows-web/app/components/dataset-authors.ts @@ -17,7 +17,7 @@ import { confirmOwner, updateOwner } from 'wherehows-web/constants/datasets/owner'; -import { OwnerSource, OwnerType } from 'wherehows-web/utils/api/datasets/owners'; +import { OwnerSource, OwnerIdType, OwnerType } from 'wherehows-web/utils/api/datasets/owners'; import { ApiStatus } from 'wherehows-web/utils/api'; /** @@ -180,7 +180,7 @@ export default class DatasetAuthors extends Component { source: OwnerSource.Ui, userName: label, name: displayName, - idType: isGroup ? OwnerType.Group : OwnerType.User + idType: isGroup ? OwnerIdType.Group : OwnerIdType.User }; updateOwner(get(this, 'owners'), currentOwner, updatedOwnerProps); diff --git a/wherehows-web/app/constants/datasets/owner.ts b/wherehows-web/app/constants/datasets/owner.ts index 4544b0a434..3f93d4fbee 100644 --- a/wherehows-web/app/constants/datasets/owner.ts +++ b/wherehows-web/app/constants/datasets/owner.ts @@ -1,5 +1,5 @@ import { IOwner } from 'wherehows-web/typings/api/datasets/owners'; -import { OwnerSource, OwnerType, OwnerUrnNamespace } from 'wherehows-web/utils/api/datasets/owners'; +import { OwnerIdType, OwnerSource, OwnerType, OwnerUrnNamespace } from 'wherehows-web/utils/api/datasets/owners'; import { isListUnique } from 'wherehows-web/utils/array'; /** @@ -112,12 +112,12 @@ const defaultOwnerProps: IOwner = { name: '', isGroup: false, namespace: OwnerUrnNamespace.groupUser, - type: 'Owner', + type: OwnerType.Owner, subType: null, sortId: 0, source: OwnerSource.Ui, confirmedBy: null, - idType: OwnerType.User + idType: OwnerIdType.User }; export { diff --git a/wherehows-web/app/services/user-lookup.ts b/wherehows-web/app/services/user-lookup.ts index 5437c8814b..8a91cc0fbe 100644 --- a/wherehows-web/app/services/user-lookup.ts +++ b/wherehows-web/app/services/user-lookup.ts @@ -10,9 +10,14 @@ import { IPartyEntity, IPartyProps } from 'wherehows-web/typings/api/datasets/pa * @param {Function} asyncResults callback * @return {Promise} */ -const ldapResolver = async (userNameQuery: string, _syncResults: Function, asyncResults: Function): Promise => { +const ldapResolver = async ( + userNameQuery: string, + _syncResults: Function, + asyncResults: (results: Array) => void +): Promise => { const ldapRegex = new RegExp(`^${userNameQuery}.*`, 'i'); const { userEntitiesSource = [] }: IPartyProps = await getUserEntities(); + asyncResults(userEntitiesSource.filter((entity: string) => ldapRegex.test(entity))); }; diff --git a/wherehows-web/app/templates/components/dataset-author.hbs b/wherehows-web/app/templates/components/dataset-author.hbs index b33adf1d82..2ddd1eefa5 100644 --- a/wherehows-web/app/templates/components/dataset-author.hbs +++ b/wherehows-web/app/templates/components/dataset-author.hbs @@ -26,7 +26,7 @@ values=ownerTypes selected=owner.type disabled=(not isOwnerMutable) - change=(action "updateOwnerType") + selectionDidChange=(action "changeOwnerType") }} @@ -34,7 +34,7 @@ {{#if isOwnerMutable}} diff --git a/wherehows-web/app/typings/api/datasets/owners.d.ts b/wherehows-web/app/typings/api/datasets/owners.d.ts index 579fb5b9f6..6d9d180899 100644 --- a/wherehows-web/app/typings/api/datasets/owners.d.ts +++ b/wherehows-web/app/typings/api/datasets/owners.d.ts @@ -1,5 +1,5 @@ import { ApiStatus } from 'wherehows-web/utils/api/shared'; -import { OwnerSource, OwnerType, OwnerUrnNamespace } from 'wherehows-web/utils/api/datasets/owners'; +import { OwnerIdType, OwnerSource, OwnerType, OwnerUrnNamespace } from 'wherehows-web/utils/api/datasets/owners'; /** * Describes the interface for an Owner entity @@ -7,7 +7,7 @@ import { OwnerSource, OwnerType, OwnerUrnNamespace } from 'wherehows-web/utils/a export interface IOwner { confirmedBy: null | string; email: null | string; - idType: OwnerType; + idType: OwnerIdType; isActive?: boolean; isGroup: boolean; modifiedTime?: number | Date; @@ -16,7 +16,7 @@ export interface IOwner { sortId: null | number; source: OwnerSource; subType: null; - type: string; + type: OwnerType; userName: string; } diff --git a/wherehows-web/app/utils/api/datasets/owners.ts b/wherehows-web/app/utils/api/datasets/owners.ts index 59ffc430a5..9d2bf8129b 100644 --- a/wherehows-web/app/utils/api/datasets/owners.ts +++ b/wherehows-web/app/utils/api/datasets/owners.ts @@ -12,11 +12,23 @@ import { IOwner, IOwnerResponse } from 'wherehows-web/typings/api/datasets/owner /** * Defines a string enum for valid owner types */ -export enum OwnerType { +export enum OwnerIdType { User = 'USER', Group = 'GROUP' } +/** + * Defines the string enum for the OwnerType attribute + * @type {string} + */ +export enum OwnerType { + Owner = 'Owner', + Consumer = 'Consumer', + Delegate = 'Delegate', + Producer = 'Producer', + Stakeholder = 'Stakeholder' +} + /** * Accepted string values for the namespace of a user */ @@ -143,5 +155,5 @@ export const getPartyEntitiesMap = (partyEntities: Array): userEnt * @return {boolean} */ export const isRequiredMinOwnersNotConfirmed = (owners: Array = []): boolean => - owners.filter(({ confirmedBy, type, idType }) => confirmedBy && type === 'Owner' && idType === OwnerType.User) + owners.filter(({ confirmedBy, type, idType }) => confirmedBy && type === 'Owner' && idType === OwnerIdType.User) .length < minRequiredConfirmed; diff --git a/wherehows-web/mirage/fixtures/owners.ts b/wherehows-web/mirage/fixtures/owners.ts new file mode 100644 index 0000000000..819f9f97fb --- /dev/null +++ b/wherehows-web/mirage/fixtures/owners.ts @@ -0,0 +1,33 @@ +import { IOwner } from 'wherehows-web/typings/api/datasets/owners'; +import { OwnerSource, OwnerIdType, OwnerUrnNamespace } from 'wherehows-web/utils/api/datasets/owners'; + +export default >[ + { + confirmedBy: '', + email: 'confirmed-owner@linkedin.com', + idType: OwnerIdType.User, + isActive: true, + isGroup: true, + modifiedTime: Date.now(), + name: 'confirmed owner', + userName: 'fakeconfirmedowner', + namespace: OwnerUrnNamespace.corpUser, + source: OwnerSource.Ui, + subType: null, + type: 'Owner' + }, + { + confirmedBy: '', + email: 'suggested-owner@linkedin.com', + idType: OwnerIdType.User, + isActive: true, + isGroup: true, + modifiedTime: Date.now(), + name: 'suggested owner', + userName: 'fakesuggestedowner', + namespace: OwnerUrnNamespace.corpUser, + source: OwnerSource.Nuage, + subType: null, + type: 'Owner' + } +]; diff --git a/wherehows-web/mirage/fixtures/users.ts b/wherehows-web/mirage/fixtures/users.ts new file mode 100644 index 0000000000..205af5ca08 --- /dev/null +++ b/wherehows-web/mirage/fixtures/users.ts @@ -0,0 +1,15 @@ +import { IUser } from 'wherehows-web/typings/api/authentication/user'; + +export default >[ + { + departmentNum: 42, + email: 'hitchiker@linkedin.com', + id: 1337, + name: 'fake user', + userName: 'p0wn', + userSetting: { + defaultWatch: '', + detailDefaultView: '' + } + } +]; diff --git a/wherehows-web/tests/integration/components/dataset-author-test.js b/wherehows-web/tests/integration/components/dataset-author-test.js new file mode 100644 index 0000000000..048c643fa8 --- /dev/null +++ b/wherehows-web/tests/integration/components/dataset-author-test.js @@ -0,0 +1,103 @@ +import { moduleForComponent, test } from 'ember-qunit'; +import hbs from 'htmlbars-inline-precompile'; +import { run } from '@ember/runloop'; + +import noop from 'wherehows-web/utils/noop'; +import owners from 'wherehows-web/mirage/fixtures/owners'; +import { OwnerType } from 'wherehows-web/utils/api/datasets/owners'; + +const [confirmedOwner, suggestedOwner] = owners; +const commonOwners = []; +const ownerTypes = Object.values(OwnerType); + +moduleForComponent('dataset-author', 'Integration | Component | dataset author', { + integration: true +}); + +test('it renders', function(assert) { + this.set('removeOwner', noop); + this.set('confirmSuggestedOwner', noop); + this.set('owner', confirmedOwner); + this.set('commonOwners', commonOwners); + + this.render( + hbs`{{dataset-author confirmSuggestedOwner=confirmSuggestedOwner removeOwner=removeOwner owner=owner commonOwners=commonOwners}}` + ); + + assert.equal(document.querySelector('tr.dataset-author-record').tagName, 'TR'); +}); + +test('triggers the removeOwner action when invoked', function(assert) { + assert.expect(2); + let removeActionCallCount = 0; + + this.set('removeOwner', () => { + removeActionCallCount++; + assert.equal(removeActionCallCount, 1, 'action is called once'); + }); + this.set('confirmSuggestedOwner', noop); + this.set('owner', confirmedOwner); + this.set('commonOwners', commonOwners); + + this.render( + hbs`{{dataset-author confirmSuggestedOwner=confirmSuggestedOwner removeOwner=removeOwner owner=owner commonOwners=commonOwners}}` + ); + + assert.equal(removeActionCallCount, 0, 'action is not called on render'); + + run(() => { + document.querySelector('.remove-dataset-author').click(); + }); +}); + +test('triggers the confirmSuggestedOwner action when invoked', function(assert) { + assert.expect(2); + let confirmSuggestedOwnerActionCallCount = 0; + + this.set('removeOwner', noop); + this.set('confirmSuggestedOwner', () => { + confirmSuggestedOwnerActionCallCount++; + assert.equal(confirmSuggestedOwnerActionCallCount, 1, 'action is called once'); + }); + this.set('owner', suggestedOwner); + this.set('commonOwners', commonOwners); + + this.render( + hbs`{{dataset-author confirmSuggestedOwner=confirmSuggestedOwner removeOwner=removeOwner owner=owner commonOwners=commonOwners}}` + ); + + assert.equal(confirmSuggestedOwnerActionCallCount, 0, 'action is not called on render'); + + run(() => { + document.querySelector('.confirm-suggested-dataset-author').click(); + }); +}); + +test('triggers the updateOwnerType action when invoked', function(assert) { + assert.expect(2); + + this.set('removeOwner', noop); + this.set('confirmSuggestedOwner', noop); + this.set('updateOwnerType', (owner, type) => { + 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('owner', confirmedOwner); + this.set('commonOwners', commonOwners); + this.set('ownerTypes', ownerTypes); + + this.render( + hbs`{{dataset-author confirmSuggestedOwner=confirmSuggestedOwner removeOwner=removeOwner owner=owner commonOwners=commonOwners updateOwnerType=updateOwnerType ownerTypes=ownerTypes}}` + ); + + run(() => { + // move to helper + document.querySelector('select').dispatchEvent( + new MouseEvent('change', { + view: window, + bubbles: true, + cancelable: true + }) + ); + }); +}); From 664173110a8be4678a05052ac151b06fe91c2656 Mon Sep 17 00:00:00 2001 From: Seyi Adebajo Date: Fri, 10 Nov 2017 02:05:48 -0800 Subject: [PATCH 07/10] adds services stub for current user and exercises dataset-authors component --- .../templates/components/dataset-authors.hbs | 14 +++- .../components/dataset-authors-test.js | 84 +++++++++++++++++++ .../tests/stubs/services/current-user.js | 11 +++ 3 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 wherehows-web/tests/integration/components/dataset-authors-test.js create mode 100644 wherehows-web/tests/stubs/services/current-user.js diff --git a/wherehows-web/app/templates/components/dataset-authors.hbs b/wherehows-web/app/templates/components/dataset-authors.hbs index d121d5f77a..51812034d9 100644 --- a/wherehows-web/app/templates/components/dataset-authors.hbs +++ b/wherehows-web/app/templates/components/dataset-authors.hbs @@ -51,7 +51,6 @@ -

@@ -104,3 +103,16 @@

+ +
+
+ + + +
+
+{{!--disabled={{ownershipIsInvalid}}--}} diff --git a/wherehows-web/tests/integration/components/dataset-authors-test.js b/wherehows-web/tests/integration/components/dataset-authors-test.js new file mode 100644 index 0000000000..c8a236fada --- /dev/null +++ b/wherehows-web/tests/integration/components/dataset-authors-test.js @@ -0,0 +1,84 @@ +import { moduleForComponent, test } from 'ember-qunit'; +import hbs from 'htmlbars-inline-precompile'; +import { run } from '@ember/runloop'; + +import noop from 'wherehows-web/utils/noop'; +import { OwnerType, OwnerSource } from 'wherehows-web/utils/api/datasets/owners'; +import owners from 'wherehows-web/mirage/fixtures/owners'; + +import userStub from 'wherehows-web/tests/stubs/services/current-user'; + +const [confirmedOwner] = owners; +const ownerTypes = Object.values(OwnerType); + +moduleForComponent('dataset-authors', 'Integration | Component | dataset authors', { + integration: true, + + beforeEach() { + this.register('service:current-user', userStub); + + this.inject.service('current-user'); + } +}); + +test('it renders', function(assert) { + assert.expect(1); + this.set('owners', owners); + this.set('ownerTypes', ownerTypes); + this.set('saveOwnerChanges', noop); + this.render(hbs`{{dataset-authors owners=owners ownerTypes=ownerTypes save=(action saveOwnerChanges)}}`); + + assert.equal(this.$('.dataset-author').length, 2, 'expected two dataset author components to be rendered'); +}); + +test('it should remove an owner when removeOwner is invoked', function(assert) { + assert.expect(1); + this.set('owners', [confirmedOwner]); + this.set('ownerTypes', ownerTypes); + this.set('saveOwnerChanges', noop); + this.render(hbs`{{dataset-authors owners=owners ownerTypes=ownerTypes save=(action saveOwnerChanges)}}`); + + run(() => { + document.querySelector('.remove-dataset-author').click(); + }); + + assert.equal(this.get('owners').length, 0); +}); + +test('it should update a suggested owner to confirmed', function(assert) { + assert.expect(3); + + const initialLength = owners.length; + this.set('owners', owners); + this.set('ownerTypes', ownerTypes); + this.set('saveOwnerChanges', noop); + this.render(hbs`{{dataset-authors owners=owners ownerTypes=ownerTypes save=(action saveOwnerChanges)}}`); + + assert.equal( + this.get('owners.length'), + initialLength, + `the list of owners is ${initialLength} before adding confirmed owner` + ); + run(() => { + document.querySelector('.confirm-suggested-dataset-author').click(); + }); + + assert.equal(this.get('owners.length'), initialLength + 1, 'the list of owner contains one more new owner'); + assert.equal(this.get('owners.lastObject.source'), OwnerSource.Ui, 'contains a new owner with ui source'); +}); + +test('it should invoke the external save action on save', function(assert) { + assert.expect(2); + this.set('owners', [confirmedOwner]); + this.set('ownerTypes', ownerTypes); + this.set('saveOwnerChanges', owners => { + assert.ok(owners === this.get('owners'), 'the list of owners is passed into the save action'); + }); + this.render(hbs`{{dataset-authors owners=owners ownerTypes=ownerTypes save=(action saveOwnerChanges)}}`); + + run(() => { + document.querySelector('.dataset-authors-save').click(); + }); + + assert.equal(this.get('owners').length, 1); +}); diff --git a/wherehows-web/tests/stubs/services/current-user.js b/wherehows-web/tests/stubs/services/current-user.js new file mode 100644 index 0000000000..2ab4cf6787 --- /dev/null +++ b/wherehows-web/tests/stubs/services/current-user.js @@ -0,0 +1,11 @@ +import Service from '@ember/service'; +import users from 'wherehows-web/mirage/fixtures/users'; + +const [user] = users; + +export default class extends Service { + currentUser = user; + load = () => Promise.resolve(); + invalidateSession = () => {}; + trackCurrentUser = () => {}; +} From 39df2b67177d319f675155dc3d38d74a64a464c8 Mon Sep 17 00:00:00 2001 From: Seyi Adebajo Date: Fri, 10 Nov 2017 02:15:24 -0800 Subject: [PATCH 08/10] removes unused npm package conflict --- wherehows-web/package.json | 1 - wherehows-web/yarn.lock | 7 ------- 2 files changed, 8 deletions(-) diff --git a/wherehows-web/package.json b/wherehows-web/package.json index 256e8f3814..515ba4b50d 100644 --- a/wherehows-web/package.json +++ b/wherehows-web/package.json @@ -52,7 +52,6 @@ "ember-export-application-global": "^2.0.0", "ember-fetch": "^3.4.3", "ember-load-initializers": "^1.0.0", - "ember-lodash-shim": "^2.0.5", "ember-metrics": "^0.12.1", "ember-pikaday": "^2.2.1", "ember-redux-shim": "^1.1.1", diff --git a/wherehows-web/yarn.lock b/wherehows-web/yarn.lock index 92d7424e92..f35d5078c8 100644 --- a/wherehows-web/yarn.lock +++ b/wherehows-web/yarn.lock @@ -2980,13 +2980,6 @@ ember-load-initializers@^1.0.0: dependencies: ember-cli-babel "^6.0.0-beta.7" -ember-lodash-shim@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/ember-lodash-shim/-/ember-lodash-shim-2.0.5.tgz#c5015c5e91e09510238885b5de9c831538a0156c" - dependencies: - ember-cli-babel "^5.1.7" - ember-cli-htmlbars "^1.1.1" - ember-lodash@4.17.2: version "4.17.2" resolved "https://registry.yarnpkg.com/ember-lodash/-/ember-lodash-4.17.2.tgz#0ed40ab89c2f9846765fc2504c0034000f666933" From 288411e2f0df050fc53a4b9a597fdbf3c6bb6162 Mon Sep 17 00:00:00 2001 From: Seyi Adebajo Date: Fri, 10 Nov 2017 10:28:50 -0800 Subject: [PATCH 09/10] refactors interface name format. remove unused computed property --- .../app/components/dataset-compliance.js | 7 ------- .../templates/components/dataset-compliance.hbs | 2 +- .../app/typings/api/datasets/party-entities.d.ts | 8 ++++---- wherehows-web/app/utils/api/datasets/owners.ts | 15 ++++++++------- 4 files changed, 13 insertions(+), 19 deletions(-) diff --git a/wherehows-web/app/components/dataset-compliance.js b/wherehows-web/app/components/dataset-compliance.js index db4f1e83d1..fc2b3a8bc5 100644 --- a/wherehows-web/app/components/dataset-compliance.js +++ b/wherehows-web/app/components/dataset-compliance.js @@ -340,13 +340,6 @@ export default Component.extend({ label: value ? formatAsCapitalizedStringWithSpaces(value) : '...' })), - /** - * Caches the policy's modification time in milliseconds - */ - policyModificationTimeInEpoch: computed('complianceInfo', function() { - return getWithDefault(this, 'complianceInfo.modifiedTime', 0); - }), - /** * @type {Boolean} cached boolean flag indicating that fields do contain a `kafka type` * tracking header. diff --git a/wherehows-web/app/templates/components/dataset-compliance.hbs b/wherehows-web/app/templates/components/dataset-compliance.hbs index c86b42dd86..44e8b89178 100644 --- a/wherehows-web/app/templates/components/dataset-compliance.hbs +++ b/wherehows-web/app/templates/components/dataset-compliance.hbs @@ -61,7 +61,7 @@ Last saved: {{if isNewComplianceInfo 'Never' - (moment-from-now policyModificationTimeInEpoch)}} + (moment-from-now complianceInfo.modifiedTime)}} diff --git a/wherehows-web/app/typings/api/datasets/party-entities.d.ts b/wherehows-web/app/typings/api/datasets/party-entities.d.ts index 29db48e72a..bfbbfefa71 100644 --- a/wherehows-web/app/typings/api/datasets/party-entities.d.ts +++ b/wherehows-web/app/typings/api/datasets/party-entities.d.ts @@ -18,9 +18,9 @@ export interface IPartyEntityResponse { } /** - * Describes a userEntityMap interface + * Describes a IUserEntityMap interface */ -export interface userEntityMap { +export interface IUserEntityMap { [label: string]: string; } @@ -29,6 +29,6 @@ export interface userEntityMap { */ export interface IPartyProps { userEntities: Array; - userEntitiesMaps: userEntityMap; - userEntitiesSource: Array; + userEntitiesMaps: IUserEntityMap; + userEntitiesSource: Array; } diff --git a/wherehows-web/app/utils/api/datasets/owners.ts b/wherehows-web/app/utils/api/datasets/owners.ts index 9d2bf8129b..e9613c85f5 100644 --- a/wherehows-web/app/utils/api/datasets/owners.ts +++ b/wherehows-web/app/utils/api/datasets/owners.ts @@ -5,7 +5,7 @@ import { IPartyEntity, IPartyEntityResponse, IPartyProps, - userEntityMap + IUserEntityMap } from 'wherehows-web/typings/api/datasets/party-entities'; import { IOwner, IOwnerResponse } from 'wherehows-web/typings/api/datasets/owners'; @@ -72,7 +72,7 @@ export const getDatasetOwners = async (id: number): Promise> => { return status === ApiStatus.OK ? owners.map(owner => ({ ...owner, - modifiedTime: new Date(owner.modifiedTime) + modifiedTime: new Date(owner.modifiedTime!) })) : Promise.reject(status); }; @@ -98,7 +98,7 @@ export const getUserEntities: () => Promise = (() => { * Memoized reference to the resolved value of a previous invocation to curried function in getUserEntities * @type {{result: IPartyProps | null}} */ - const cache: { result: IPartyProps | null; userEntitiesSource: Array } = { + const cache: { result: IPartyProps | null; userEntitiesSource: Array } = { result: null, userEntitiesSource: [] }; @@ -141,9 +141,9 @@ export const getUserEntities: () => Promise = (() => { /** * Transforms a list of party entities into a map of entity label to displayName value * @param {Array} partyEntities - * @return {Object} + * @return {IUserEntityMap} */ -export const getPartyEntitiesMap = (partyEntities: Array): userEntityMap => +export const getPartyEntitiesMap = (partyEntities: Array): IUserEntityMap => partyEntities.reduce( (map: { [label: string]: string }, { label, displayName }: IPartyEntity) => ((map[label] = displayName), map), {} @@ -155,5 +155,6 @@ export const getPartyEntitiesMap = (partyEntities: Array): userEnt * @return {boolean} */ export const isRequiredMinOwnersNotConfirmed = (owners: Array = []): boolean => - owners.filter(({ confirmedBy, type, idType }) => confirmedBy && type === 'Owner' && idType === OwnerIdType.User) - .length < minRequiredConfirmed; + owners.filter( + ({ confirmedBy, type, idType }) => confirmedBy && type === OwnerType.Owner && idType === OwnerIdType.User + ).length < minRequiredConfirmed; From 59cd5b4261ff93f305c63053c9d0d8376fc781b7 Mon Sep 17 00:00:00 2001 From: Seyi Adebajo Date: Fri, 10 Nov 2017 10:33:10 -0800 Subject: [PATCH 10/10] replaces manual DOM event triggers with ember native dom helpers --- wherehows-web/package.json | 1 + .../components/dataset-author-test.js | 20 ++++--------------- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/wherehows-web/package.json b/wherehows-web/package.json index 515ba4b50d..b99ace533f 100644 --- a/wherehows-web/package.json +++ b/wherehows-web/package.json @@ -53,6 +53,7 @@ "ember-fetch": "^3.4.3", "ember-load-initializers": "^1.0.0", "ember-metrics": "^0.12.1", + "ember-native-dom-helpers": "^0.5.4", "ember-pikaday": "^2.2.1", "ember-redux-shim": "^1.1.1", "ember-redux-thunk-shim": "^1.1.2", diff --git a/wherehows-web/tests/integration/components/dataset-author-test.js b/wherehows-web/tests/integration/components/dataset-author-test.js index 048c643fa8..f7e4d3cb83 100644 --- a/wherehows-web/tests/integration/components/dataset-author-test.js +++ b/wherehows-web/tests/integration/components/dataset-author-test.js @@ -1,5 +1,6 @@ import { moduleForComponent, test } from 'ember-qunit'; import hbs from 'htmlbars-inline-precompile'; +import { triggerEvent } from 'ember-native-dom-helpers'; import { run } from '@ember/runloop'; import noop from 'wherehows-web/utils/noop'; @@ -45,9 +46,7 @@ test('triggers the removeOwner action when invoked', function(assert) { assert.equal(removeActionCallCount, 0, 'action is not called on render'); - run(() => { - document.querySelector('.remove-dataset-author').click(); - }); + triggerEvent('.remove-dataset-author', 'click'); }); test('triggers the confirmSuggestedOwner action when invoked', function(assert) { @@ -68,9 +67,7 @@ test('triggers the confirmSuggestedOwner action when invoked', function(assert) assert.equal(confirmSuggestedOwnerActionCallCount, 0, 'action is not called on render'); - run(() => { - document.querySelector('.confirm-suggested-dataset-author').click(); - }); + triggerEvent('.confirm-suggested-dataset-author', 'click'); }); test('triggers the updateOwnerType action when invoked', function(assert) { @@ -90,14 +87,5 @@ test('triggers the updateOwnerType action when invoked', function(assert) { hbs`{{dataset-author confirmSuggestedOwner=confirmSuggestedOwner removeOwner=removeOwner owner=owner commonOwners=commonOwners updateOwnerType=updateOwnerType ownerTypes=ownerTypes}}` ); - run(() => { - // move to helper - document.querySelector('select').dispatchEvent( - new MouseEvent('change', { - view: window, - bubbles: true, - cancelable: true - }) - ); - }); + triggerEvent('select', 'change'); });