mirror of
https://github.com/datahub-project/datahub.git
synced 2025-08-31 12:52:13 +00:00
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
This commit is contained in:
commit
0aa6f794bb
144
wherehows-web/app/components/dataset-author.ts
Normal file
144
wherehows-web/app/components/dataset-author.ts
Normal file
@ -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
wherehows-web/app/components/dataset-authors.ts
Normal file
250
wherehows-web/app/components/dataset-authors.ts
Normal file
@ -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(
|
||||
'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<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'));
|
||||
}
|
||||
};
|
||||
}
|
@ -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.
|
||||
|
131
wherehows-web/app/constants/datasets/owner.ts
Normal file
131
wherehows-web/app/constants/datasets/owner.ts
Normal file
@ -0,0 +1,131 @@
|
||||
import { IOwner } from 'wherehows-web/typings/api/datasets/owners';
|
||||
import { OwnerIdType, 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<IOwner>} owners the list of owners
|
||||
* @param {Pick<IOwner, 'userName'>} newOwner userName for the owner
|
||||
* @returns {boolean} true if owner username in current list of owners
|
||||
*/
|
||||
const ownerAlreadyExists = (owners: Array<IOwner>, newOwner: Pick<IOwner, 'userName'>) => {
|
||||
const newUserNameRegEx = new RegExp(`.*${newOwner.userName}.*`, 'i');
|
||||
|
||||
return owners.mapBy('userName').some((userName: string) => newUserNameRegEx.test(userName));
|
||||
};
|
||||
|
||||
// overloads
|
||||
function updateOwner(owners: Array<IOwner>, owner: IOwner, props: IOwner): void | Array<IOwner>;
|
||||
function updateOwner<K extends keyof IOwner>(
|
||||
owners: Array<IOwner>,
|
||||
owner: IOwner,
|
||||
props: K,
|
||||
value: IOwner[K]
|
||||
): void | Array<IOwner>;
|
||||
/**
|
||||
* 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<IOwner>} 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<IOwner>)} the updated list of owners if the owner list contains no duplicates
|
||||
*/
|
||||
function updateOwner<K extends keyof IOwner>(
|
||||
owners: Array<IOwner>,
|
||||
owner: IOwner,
|
||||
props: K | IOwner,
|
||||
value?: IOwner[K]
|
||||
): void | Array<IOwner> {
|
||||
// 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<IOwner> = [
|
||||
...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<IOwner>} owners the list of owners
|
||||
* @param {IOwner} owner the owner to be updated
|
||||
* @param {string} confirmedBy the userName of the confirming user
|
||||
* @returns {(Array<IOwner> | void)}
|
||||
*/
|
||||
const confirmOwner = (owners: Array<IOwner>, owner: IOwner, confirmedBy: string): Array<IOwner> | 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: OwnerType.Owner,
|
||||
subType: null,
|
||||
sortId: 0,
|
||||
source: OwnerSource.Ui,
|
||||
confirmedBy: null,
|
||||
idType: OwnerIdType.User
|
||||
};
|
||||
|
||||
export {
|
||||
defaultOwnerProps,
|
||||
defaultOwnerUserName,
|
||||
minRequiredConfirmedOwners,
|
||||
userNameEditableClass,
|
||||
ownerAlreadyExists,
|
||||
updateOwner,
|
||||
confirmOwner
|
||||
};
|
@ -10,9 +10,14 @@ import { IPartyEntity, IPartyProps } from 'wherehows-web/typings/api/datasets/pa
|
||||
* @param {Function} asyncResults callback
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
const ldapResolver = async (userNameQuery: string, _syncResults: Function, asyncResults: Function): Promise<void> => {
|
||||
const ldapResolver = async (
|
||||
userNameQuery: string,
|
||||
_syncResults: Function,
|
||||
asyncResults: (results: Array<string>) => void
|
||||
): Promise<void> => {
|
||||
const ldapRegex = new RegExp(`^${userNameQuery}.*`, 'i');
|
||||
const { userEntitiesSource = [] }: IPartyProps = await getUserEntities();
|
||||
|
||||
asyncResults(userEntitiesSource.filter((entity: string) => ldapRegex.test(entity)));
|
||||
};
|
||||
|
||||
|
@ -1 +1,2 @@
|
||||
@import "owner-table";
|
||||
@import 'owner-table';
|
||||
@import 'dataset-author';
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -1,148 +1,64 @@
|
||||
<div class="tab-body">
|
||||
<section class="action-bar">
|
||||
<div class="container action-bar__content">
|
||||
<button class="nacho-button nacho-button--large-inverse action-bar__item"
|
||||
title={{if ownershipIsInvalid
|
||||
"Need at least two confirmed owners to make changes
|
||||
and no Invalid users"
|
||||
"Save"}}
|
||||
disabled={{ownershipIsInvalid}}
|
||||
{{action "updateOwners"}}>
|
||||
Save
|
||||
<td>
|
||||
{{user-avatar userName=owner.userName}}
|
||||
{{owner.userName}}
|
||||
</td>
|
||||
|
||||
<td>
|
||||
{{owner.name}}
|
||||
</td>
|
||||
|
||||
<td>
|
||||
{{owner.idType}}
|
||||
</td>
|
||||
|
||||
<td>
|
||||
{{owner.source}}
|
||||
</td>
|
||||
|
||||
<td>
|
||||
{{moment-calendar owner.modifiedTime sameElse="MMM Do YYYY, h:mm a"}}
|
||||
</td>
|
||||
|
||||
|
||||
<td>
|
||||
{{ember-selector
|
||||
class=(unless isOwnerMutable "nacho-select--hidden-state")
|
||||
values=ownerTypes
|
||||
selected=owner.type
|
||||
disabled=(not isOwnerMutable)
|
||||
selectionDidChange=(action "changeOwnerType")
|
||||
}}
|
||||
</td>
|
||||
|
||||
<td>
|
||||
{{#if isOwnerMutable}}
|
||||
|
||||
<button
|
||||
class="nacho-button nacho-button--small remove-dataset-author"
|
||||
{{action "removeOwner"}}>
|
||||
<i class="fa fa-trash"
|
||||
aria-label="Remove Owner"></i>
|
||||
</button>
|
||||
|
||||
{{else}}
|
||||
|
||||
{{#if isConfirmedSuggestedOwner}}
|
||||
|
||||
<button
|
||||
class="nacho-button nacho-button--small dataset-author-record__action--disabled">
|
||||
Added
|
||||
</button>
|
||||
|
||||
<button class="nacho-button nacho-button--large action-bar__item"
|
||||
{{action "addOwner" owners}}>
|
||||
<i class="fa fa-plus" title="Add an Owner">
|
||||
</i>
|
||||
Add an Owner
|
||||
{{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>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="alert alert-info" role="alert">
|
||||
<p>
|
||||
<strong>
|
||||
Why can't I click save? It's greyed out
|
||||
</strong>
|
||||
</p>
|
||||
<p>
|
||||
Please note that to make any update the list of owners for this dataset,
|
||||
there needs to be:
|
||||
</p>
|
||||
{{/if}}
|
||||
|
||||
<p><strong>At least two (2)</strong> confirmed owners who are both</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
of ID Type <code>USER</code>
|
||||
</li>
|
||||
<li>
|
||||
of Owner Type <code>Owner</code>
|
||||
</li>
|
||||
</ul>
|
||||
<br>
|
||||
|
||||
<p>
|
||||
<strong>
|
||||
Why can't I remove an owner?
|
||||
</strong>
|
||||
</p>
|
||||
<p>
|
||||
Only owners that are not sourced from <code>SCM</code> or <code>NUAGE</code> can be removed
|
||||
from this list. To remove any such owner(s), please make the change at the
|
||||
source.</p>
|
||||
</div>
|
||||
|
||||
{{#if errorMessage}}
|
||||
<div class="alert alert-danger" role="alert">{{errorMessage}}</div>
|
||||
{{/if}}
|
||||
</td>
|
||||
|
||||
{{#if actionMessage}}
|
||||
<div class="alert alert-success" role="alert">{{actionMessage}}</div>
|
||||
{{/if}}
|
||||
|
||||
<table class="nacho-table nacho-table--bordered nacho-table--stripped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="dataset-author-column--wide">LDAP Username</th>
|
||||
<th>Full Name</th>
|
||||
<th class="dataset-author-column--narrow">ID Type</th>
|
||||
<th>Source</th>
|
||||
<th>Last Modified</th>
|
||||
<th>
|
||||
Owner Type
|
||||
|
||||
<!--TODO: DSS-6716-->
|
||||
<!-- DRY out with wrapper component that takes the link as an attribute-->
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://iwww.corp.linkedin.com/wiki/cf/display/DWH/Metadata+Acquisition#ProjectOverview-ownership">
|
||||
<sup>
|
||||
<span class="glyphicon glyphicon-question-sign"
|
||||
title="Link to more information"></span>
|
||||
</sup>
|
||||
</a>
|
||||
</th>
|
||||
<th class="dataset-author-column--narrow">Confirm?</th>
|
||||
<th>Remove</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody data-attribute="owner-table">
|
||||
{{#each owners as |owner|}}
|
||||
<tr>
|
||||
<td class="dataset-author-cell"
|
||||
onclick={{action "willEditUserName"
|
||||
owner}}>
|
||||
|
||||
<label class={{unless (contains owner.source restrictedSources) "dataset-author-cell__name-tag"}}>
|
||||
{{owner.userName}}
|
||||
</label>
|
||||
{{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"}}
|
||||
</td>
|
||||
<td>{{owner.name}}</td>
|
||||
<td>{{owner.idType}}</td>
|
||||
<td>{{owner.source}}</td>
|
||||
<td class="dataset-author-cell__owner-last-mod">
|
||||
{{!-- e.g Jul 18th 2016, 11:11 am --}}
|
||||
{{moment-calendar owner.modifiedTime sameElse="MMM Do YYYY, h:mm a"}}
|
||||
</td>
|
||||
<td>
|
||||
{{ember-selector
|
||||
values=ownerTypes
|
||||
selected=owner.type
|
||||
change=(action "updateOwnerType" owner)}}
|
||||
</td>
|
||||
<td>
|
||||
{{input
|
||||
type="checkbox"
|
||||
title=(if owner.confirmedBy owner.confirmedBy "Not confirmed")
|
||||
checked=(readonly owner.confirmedBy)
|
||||
change=(action "confirmOwner" owner)}}
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<button class="nacho-button nacho-button--small"
|
||||
title={{if (contains owner.source restrictedSources)
|
||||
removeFromSourceMessage
|
||||
"Remove"}}
|
||||
disabled={{contains owner.source restrictedSources}}
|
||||
{{action "removeOwner" owner}}>
|
||||
<i class="fa fa-trash"
|
||||
aria-label="Remove Owner"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
118
wherehows-web/app/templates/components/dataset-authors.hbs
Normal file
118
wherehows-web/app/templates/components/dataset-authors.hbs
Normal file
@ -0,0 +1,118 @@
|
||||
<section class="dataset-author">
|
||||
<header>
|
||||
<h2 class="dataset-author__header">
|
||||
Owners Confirmed
|
||||
</h2>
|
||||
|
||||
<p class="dataset-author__byline">
|
||||
These are dataset ownership records that have been manually entered / confirmed by a human
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<table class="nacho-table nacho-table--bordered dataset-owner-table dataset-owner-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="dataset-author-column--wide">LDAP Username</th>
|
||||
<th>Full Name</th>
|
||||
<th class="dataset-author-column--narrow">ID Type</th>
|
||||
<th>Source</th>
|
||||
<th>Last Modified</th>
|
||||
<th>
|
||||
Ownership Type
|
||||
|
||||
<!--TODO: DSS-6716-->
|
||||
<!-- DRY out with wrapper component that takes the link as an attribute-->
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://iwww.corp.linkedin.com/wiki/cf/display/DWH/Metadata+Acquisition#ProjectOverview-ownership">
|
||||
<sup>
|
||||
<span class="glyphicon glyphicon-question-sign"
|
||||
title="Link to more information"></span>
|
||||
</sup>
|
||||
</a>
|
||||
</th>
|
||||
<th>Remove Owner</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
{{#each confirmedOwners as |confirmedOwner|}}
|
||||
|
||||
<tbody>
|
||||
{{dataset-author
|
||||
owner=confirmedOwner
|
||||
ownerTypes=ownerTypes
|
||||
removeOwner=(action "removeOwner")
|
||||
confirmSuggestedOwner=(action "confirmSuggestedOwner")
|
||||
updateOwnerType=(action "updateOwnerType")
|
||||
}}
|
||||
</tbody>
|
||||
{{else}}
|
||||
{{/each}}
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section class="dataset-author">
|
||||
<header>
|
||||
<h2 class="dataset-author__header">
|
||||
System Suggested Owners
|
||||
</h2>
|
||||
|
||||
<p class="dataset-author__byline">
|
||||
These are dataset ownership records, suggested based information derived from the source metadata.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<table class="nacho-table nacho-table--bordered dataset-owner-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="dataset-author-column--wide">LDAP Username</th>
|
||||
<th>Full Name</th>
|
||||
<th class="dataset-author-column--narrow">ID Type</th>
|
||||
<th>Source</th>
|
||||
<th>Last Modified</th>
|
||||
<th>
|
||||
Owner Type
|
||||
|
||||
<!--TODO: DSS-6716-->
|
||||
<!-- DRY out with wrapper component that takes the link as an attribute-->
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://iwww.corp.linkedin.com/wiki/cf/display/DWH/Metadata+Acquisition#ProjectOverview-ownership">
|
||||
<sup>
|
||||
<span class="glyphicon glyphicon-question-sign"
|
||||
title="Link to more information"></span>
|
||||
</sup>
|
||||
</a>
|
||||
</th>
|
||||
<th>Add Suggested Owner</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{{#each systemGeneratedOwners as |systemGeneratedOwner|}}
|
||||
{{dataset-author
|
||||
owner=systemGeneratedOwner
|
||||
ownerTypes=ownerTypes
|
||||
commonOwners=commonOwners
|
||||
removeOwner=(action "removeOwner")
|
||||
confirmSuggestedOwner=(action "confirmSuggestedOwner")
|
||||
updateOwnerType=(action "updateOwnerType")
|
||||
}}
|
||||
{{/each}}
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section class="action-bar">
|
||||
<div class="container action-bar__content">
|
||||
|
||||
<button
|
||||
class="nacho-button nacho-button--large-inverse action-bar__item dataset-authors-save"
|
||||
{{action "saveOwners"}}>
|
||||
Save
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
{{!--disabled={{ownershipIsInvalid}}--}}
|
@ -61,7 +61,7 @@
|
||||
Last saved:
|
||||
<span class="policy-last-saved__saved">
|
||||
{{if isNewComplianceInfo 'Never'
|
||||
(moment-from-now policyModificationTimeInEpoch)}}
|
||||
(moment-from-now complianceInfo.modifiedTime)}}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
@ -223,12 +223,9 @@
|
||||
}}
|
||||
</div>
|
||||
<div id="ownertab" class="tab-pane active">
|
||||
{{dataset-author
|
||||
{{dataset-authors
|
||||
owners=owners
|
||||
ownerTypes=ownerTypes
|
||||
showMsg=showMsg
|
||||
alertType=alertType
|
||||
ownerMessage=ownerMessage
|
||||
save=(action "saveOwnerChanges")
|
||||
}}
|
||||
</div>
|
||||
|
@ -1,32 +1,22 @@
|
||||
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 { OwnerIdType, 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: OwnerIdType;
|
||||
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;
|
||||
type: OwnerType;
|
||||
userName: string;
|
||||
}
|
||||
|
||||
|
@ -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<IPartyEntity>;
|
||||
userEntitiesMaps: userEntityMap;
|
||||
userEntitiesSource: Array<keyof userEntityMap>;
|
||||
userEntitiesMaps: IUserEntityMap;
|
||||
userEntitiesSource: Array<keyof IUserEntityMap>;
|
||||
}
|
||||
|
6
wherehows-web/app/typings/global-plugin.d.ts
vendored
Normal file
6
wherehows-web/app/typings/global-plugin.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
import Ember from 'ember';
|
||||
|
||||
// opt-in to allow types for Ember Array Prototype extensions
|
||||
declare global {
|
||||
interface Array<T> extends Ember.ArrayPrototypeExtensions<T> {}
|
||||
}
|
@ -5,18 +5,51 @@ import {
|
||||
IPartyEntity,
|
||||
IPartyEntityResponse,
|
||||
IPartyProps,
|
||||
userEntityMap
|
||||
IUserEntityMap
|
||||
} from 'wherehows-web/typings/api/datasets/party-entities';
|
||||
import { IOwner, IOwnerResponse } from 'wherehows-web/typings/api/datasets/owners';
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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 +72,7 @@ export const getDatasetOwners = async (id: number): Promise<Array<IOwner>> => {
|
||||
return status === ApiStatus.OK
|
||||
? owners.map(owner => ({
|
||||
...owner,
|
||||
modifiedTime: new Date(owner.modifiedTime)
|
||||
modifiedTime: new Date(owner.modifiedTime!)
|
||||
}))
|
||||
: Promise.reject(status);
|
||||
};
|
||||
@ -65,7 +98,7 @@ export const getUserEntities: () => Promise<IPartyProps> = (() => {
|
||||
* 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<keyof userEntityMap> } = {
|
||||
const cache: { result: IPartyProps | null; userEntitiesSource: Array<keyof IUserEntityMap> } = {
|
||||
result: null,
|
||||
userEntitiesSource: []
|
||||
};
|
||||
@ -108,9 +141,9 @@ export const getUserEntities: () => Promise<IPartyProps> = (() => {
|
||||
/**
|
||||
* Transforms a list of party entities into a map of entity label to displayName value
|
||||
* @param {Array<IPartyEntity>} partyEntities
|
||||
* @return {Object<string>}
|
||||
* @return {IUserEntityMap}
|
||||
*/
|
||||
export const getPartyEntitiesMap = (partyEntities: Array<IPartyEntity>): userEntityMap =>
|
||||
export const getPartyEntitiesMap = (partyEntities: Array<IPartyEntity>): IUserEntityMap =>
|
||||
partyEntities.reduce(
|
||||
(map: { [label: string]: string }, { label, displayName }: IPartyEntity) => ((map[label] = displayName), map),
|
||||
{}
|
||||
@ -122,5 +155,6 @@ export const getPartyEntitiesMap = (partyEntities: Array<IPartyEntity>): userEnt
|
||||
* @return {boolean}
|
||||
*/
|
||||
export const isRequiredMinOwnersNotConfirmed = (owners: Array<IOwner> = []): boolean =>
|
||||
owners.filter(({ confirmedBy, type, idType }) => confirmedBy && type === 'Owner' && idType === OwnerType.User)
|
||||
.length < minRequiredConfirmed;
|
||||
owners.filter(
|
||||
({ confirmedBy, type, idType }) => confirmedBy && type === OwnerType.Owner && idType === OwnerIdType.User
|
||||
).length < minRequiredConfirmed;
|
||||
|
33
wherehows-web/mirage/fixtures/owners.ts
Normal file
33
wherehows-web/mirage/fixtures/owners.ts
Normal file
@ -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 <Array<IOwner>>[
|
||||
{
|
||||
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'
|
||||
}
|
||||
];
|
15
wherehows-web/mirage/fixtures/users.ts
Normal file
15
wherehows-web/mirage/fixtures/users.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { IUser } from 'wherehows-web/typings/api/authentication/user';
|
||||
|
||||
export default <Array<IUser>>[
|
||||
{
|
||||
departmentNum: 42,
|
||||
email: 'hitchiker@linkedin.com',
|
||||
id: 1337,
|
||||
name: 'fake user',
|
||||
userName: 'p0wn',
|
||||
userSetting: {
|
||||
defaultWatch: '',
|
||||
detailDefaultView: ''
|
||||
}
|
||||
}
|
||||
];
|
@ -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",
|
||||
@ -51,8 +52,8 @@
|
||||
"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-native-dom-helpers": "^0.5.4",
|
||||
"ember-pikaday": "^2.2.1",
|
||||
"ember-redux-shim": "^1.1.1",
|
||||
"ember-redux-thunk-shim": "^1.1.2",
|
||||
|
@ -0,0 +1,91 @@
|
||||
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';
|
||||
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');
|
||||
|
||||
triggerEvent('.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');
|
||||
|
||||
triggerEvent('.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}}`
|
||||
);
|
||||
|
||||
triggerEvent('select', 'change');
|
||||
});
|
@ -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);
|
||||
});
|
11
wherehows-web/tests/stubs/services/current-user.js
Normal file
11
wherehows-web/tests/stubs/services/current-user.js
Normal file
@ -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 = () => {};
|
||||
}
|
@ -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"
|
||||
@ -2976,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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user