mirror of
https://github.com/datahub-project/datahub.git
synced 2025-07-09 02:02:12 +00:00
360 lines
12 KiB
JavaScript
360 lines
12 KiB
JavaScript
import Ember from 'ember';
|
|
|
|
const {
|
|
set,
|
|
get,
|
|
getWithDefault,
|
|
computed,
|
|
Component,
|
|
inject: { service }
|
|
} = Ember;
|
|
|
|
/**
|
|
* Array of source names to restrict from user updates
|
|
* @type {[String]}
|
|
*/
|
|
const restrictedSources = ['SCM', 'NUAGE'];
|
|
/**
|
|
* Pattern to look for in source strings
|
|
* Will case-insensitive find strings containing `scm` or `nuage`
|
|
* @type {RegExp}
|
|
*/
|
|
const restrictedSourcesPattern = new RegExp(`.*${restrictedSources.join('|')}.*`, 'i');
|
|
const removeFromSourceMessage = `Owners sourced from ${restrictedSources.join(', ')} should be removed directly from that source`;
|
|
|
|
// Class to toggle readonly mode vs edit mode
|
|
const userNameEditableClass = 'dataset-author-cell--editing';
|
|
// Required minimum confirmed owners needed to update the list of owners
|
|
const minRequiredConfirmed = 2;
|
|
/**
|
|
* Initial user name for candidate owners
|
|
* @type {string}
|
|
* @private
|
|
*/
|
|
const _defaultOwnerUserName = 'New Owner';
|
|
|
|
const _defaultOwnerProps = {
|
|
userName: _defaultOwnerUserName,
|
|
email: null,
|
|
name: '',
|
|
isGroup: false,
|
|
namespace: 'urn:li:griduser',
|
|
type: 'Producer',
|
|
subType: null,
|
|
sortId: 0,
|
|
source: ''
|
|
};
|
|
|
|
export default Component.extend({
|
|
/**
|
|
* Inject the currentUser service to retrieve logged in userName
|
|
* @type {Ember.Service}
|
|
*/
|
|
sessionUser: service('current-user'),
|
|
|
|
/**
|
|
* Lookup for userEntity objects
|
|
* @type {Ember.Service}
|
|
*/
|
|
ldapUsers: service('user-lookup'),
|
|
|
|
$ownerTable: null,
|
|
|
|
restrictedSources,
|
|
|
|
removeFromSourceMessage,
|
|
|
|
init() {
|
|
this._super(...arguments);
|
|
|
|
// Sets a reference to the userNamesResolver function on instantiation.
|
|
// Typeahead component uses this function to resolve matches for user
|
|
// input
|
|
set(this, 'userNamesResolver', get(this, 'ldapUsers.userNamesResolver'));
|
|
},
|
|
|
|
/**
|
|
* Helper function get the userName for the currently logged in user
|
|
* @return {String|*} the userName is found
|
|
*/
|
|
loggedInUserName() {
|
|
// Current user service provides the userName on one of two api
|
|
// TODO: DSS-6718 Refactor. merge this into one
|
|
return get(this, 'sessionUser.userName') ||
|
|
get(this, 'sessionUser.currentUser.userName');
|
|
},
|
|
|
|
// Combination macro to check that the entered username is valid
|
|
// i.e. not _defaultOwnerUserName and the requiredMinConfirmed
|
|
ownershipIsInvalid: computed.or('userNameInvalid', 'requiredMinNotConfirmed'),
|
|
|
|
// Returns a list of owners with truthy value for their confirmedBy attribute,
|
|
// i.e. they confirmedBy contains a userName and
|
|
// type is `Owner` and idType is `USER`.
|
|
confirmedOwners: computed('owners.[]', function() {
|
|
return getWithDefault(this, 'owners', []).filter(
|
|
({ confirmedBy, type, idType }) =>
|
|
confirmedBy && type === 'Owner' && idType === 'USER'
|
|
);
|
|
}),
|
|
|
|
/**
|
|
* Checks that the number of confirmedOwners is < minRequiredConfirmed
|
|
* @type {Ember.ComputedProperty}
|
|
* @requires minRequiredConfirmed
|
|
*/
|
|
requiredMinNotConfirmed: computed.lt(
|
|
'confirmedOwners.length',
|
|
minRequiredConfirmed
|
|
),
|
|
|
|
/**
|
|
* Checks that the list of owners does not contain an owner
|
|
* with the default userName `_defaultOwnerUserName`
|
|
* @type {Ember.ComputedProperty}
|
|
* @requires _defaultOwnerUserName
|
|
*/
|
|
userNameInvalid: computed('owners.[]', function() {
|
|
return getWithDefault(this, 'owners', []).filter(
|
|
({ userName }) => userName === _defaultOwnerUserName
|
|
).length > 0;
|
|
}),
|
|
|
|
didInsertElement() {
|
|
this._super(...arguments);
|
|
// Cache reference to element on component
|
|
this.set('$ownerTable', $('[data-attribute=owner-table]'));
|
|
|
|
// Apply jQuery sortable plugin to element
|
|
this.get('$ownerTable').sortable({
|
|
start: (e, { item }) => set(this, 'startPosition', item.index()),
|
|
|
|
update: (e, { item }) => {
|
|
const from = get(this, 'startPosition');
|
|
const to = item.index(); // New position where row was dropped
|
|
const currentOwners = getWithDefault(this, 'owners', []);
|
|
|
|
// Updates the owners array to reflect the UI position changes
|
|
if (currentOwners.length) {
|
|
const reArrangedOwners = [...currentOwners];
|
|
const travelingOwner = reArrangedOwners.splice(from, 1).pop();
|
|
|
|
reArrangedOwners.splice(to, 0, travelingOwner);
|
|
currentOwners.setObjects(reArrangedOwners);
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
willDestroyElement() {
|
|
this._super(...arguments);
|
|
// Removes the sortable functionality from the cached DOM element reference
|
|
get(this, '$ownerTable').sortable('destroy');
|
|
},
|
|
|
|
/**
|
|
* Non mutative update to an owner on the list of current owners.
|
|
* @example _updateOwner(currentOwner, isConfirmed, false);
|
|
* @example _updateOwner(currentOwner, {isConfirmed: false, isGroup: true});
|
|
*
|
|
* @param {Object} currentProps the current props on the currentProps
|
|
* to be updated
|
|
* @param {String|Object} props the property to update on the currentProps in
|
|
* the list of owners. Props can be also be an Object containing the
|
|
* properties mapped to updated values.
|
|
* @param {*} [value] optional value to set on the currentProps in
|
|
* the source list, required is props is map of key -> value pairs
|
|
* @private
|
|
*/
|
|
_updateOwner(currentProps, ...[props, value]) {
|
|
const sourceOwners = get(this, 'owners');
|
|
// Create a copy so in-flight mutations are not propagates to the ui
|
|
const updatingOwners = [...sourceOwners];
|
|
|
|
// Ensure that the provided currentProps is in the list of sourceOwners
|
|
if (updatingOwners.includes(currentProps)) {
|
|
const ownerPosition = updatingOwners.indexOf(currentProps);
|
|
let updatedOwner = props;
|
|
|
|
if (typeof props === 'string') {
|
|
updatedOwner = Object.assign({}, currentProps, { [props]: value });
|
|
}
|
|
|
|
// Non-mutative array insertion
|
|
const updatedOwners = [
|
|
...updatingOwners.slice(0, ownerPosition),
|
|
updatedOwner,
|
|
...updatingOwners.slice(ownerPosition + 1)
|
|
];
|
|
|
|
// Full reset of the `owners` list with the new list
|
|
sourceOwners.setObjects(updatedOwners);
|
|
}
|
|
},
|
|
|
|
actions: {
|
|
/**
|
|
* Handles the user intention to update the owner userName, by
|
|
* adding a class signifying edit to the DOM td classList.
|
|
* Only `owners` in this list who are not sourced from `restrictedSources` (see var above) should
|
|
* the user be allowed to update / edit.
|
|
* The click event is handled on the TD which is the parent
|
|
* of the target label.
|
|
* @param {String} source source of the currently interactive owner from
|
|
* the list
|
|
* @param {DOMTokenList} classList retrieve the classList from the
|
|
* wrapping TD element, which handles the bubbled Mouse click on the
|
|
* label
|
|
*/
|
|
willEditUserName({ source = '' }, { currentTarget: { classList } }) {
|
|
// Add the className cached in `userNameEditableClass`. This renders
|
|
// the input element in the DOM, and removes the label from layout
|
|
const disallowEdit = String(source).match(restrictedSourcesPattern);
|
|
|
|
if (!disallowEdit && typeof classList.add === 'function') {
|
|
classList.add(userNameEditableClass);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Updates the owner.userName property, setting it to the value entered
|
|
* in the table input field
|
|
* @param {Object} currentOwner the currentOwner object rendered
|
|
* for this table row
|
|
* @param {String} userName the userName returned from the typeahead
|
|
* @return {Promise.<void>}
|
|
*/
|
|
async editUserName(currentOwner, userName) {
|
|
if (userName) {
|
|
// getUser returns a promise, treat as such
|
|
const getUser = get(this, 'ldapUsers.getPartyEntityWithUserName');
|
|
const { label, displayName, category } = await getUser(userName);
|
|
const isGroup = category === 'group';
|
|
|
|
const updatedProps = Object.assign({}, currentOwner, {
|
|
isGroup,
|
|
source: 'UI',
|
|
userName: label,
|
|
name: displayName,
|
|
idType: isGroup ? 'GROUP' : 'USER'
|
|
});
|
|
|
|
this._updateOwner(currentOwner, updatedProps);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Adds a candidate owner to the list of updated owners to be potentially
|
|
* propagated to the server.
|
|
* If the owner already exists in the list of currentOwners, this is a no-op.
|
|
* Also, sets the errorMessage to reflect this occurrence.
|
|
* @return {Array.<Object>|void}
|
|
*/
|
|
addOwner() {
|
|
// Make a copy of the list of current owners
|
|
const currentOwners = [...getWithDefault(this, 'owners', [])];
|
|
// Make a shallow copy for the new user. A shallow copy is fine,
|
|
// since the properties are scalars.
|
|
const newOwner = Object.assign({}, _defaultOwnerProps);
|
|
// Case insensitive regular exp to check that the username is not in
|
|
// the current list of owners
|
|
const regex = new RegExp(`.*${newOwner.userName}.*`, 'i');
|
|
let updatedOwners = [];
|
|
|
|
const ownerAlreadyExists = currentOwners
|
|
.mapBy('userName')
|
|
.some(userName => regex.test(userName));
|
|
|
|
if (!ownerAlreadyExists) {
|
|
updatedOwners = [newOwner, ...currentOwners];
|
|
return set(this, 'owners', updatedOwners);
|
|
}
|
|
|
|
set(
|
|
this,
|
|
'errorMessage',
|
|
`Uh oh! There is already a user with the username ${newOwner.userName} in the list of owners.`
|
|
);
|
|
},
|
|
|
|
/**
|
|
* Removes the provided owner from the current owners array. Pretty simple.
|
|
* @param {Object} owner
|
|
* @returns {any|Ember.Array|{}}
|
|
*/
|
|
removeOwner(owner) {
|
|
return getWithDefault(this, 'owners', []).removeObject(owner);
|
|
},
|
|
|
|
/**
|
|
* Updates the ownerType prop in the list of owners
|
|
* @param {Object} owner props to be updated
|
|
* @param {String} value current owner value
|
|
*/
|
|
updateOwnerType(owner, { target: { value } }) {
|
|
this._updateOwner(owner, 'ownerType', value);
|
|
},
|
|
|
|
/**
|
|
* Sets the checked confirmedBy property on an owner, when the user
|
|
* indicates this thought the UI, and if their username is
|
|
* available.
|
|
* @param {Object} owner the owner object to update
|
|
* @prop {String|null|void} owner.confirmedBy flag indicating userName
|
|
* that confirmed ownership, or null or void otherwise
|
|
* @param {Boolean = false} checked flag for the current ui checked state
|
|
*/
|
|
confirmOwner(owner, { target: { checked } = false }) {
|
|
// Attempt to get the userName from the currentUser service
|
|
const userName = get(this, 'loggedInUserName').call(this);
|
|
// If checked, assign userName otherwise, null
|
|
const isConfirmedBy = checked ? userName : null;
|
|
|
|
// If we have a non blank userName in confirmedBy
|
|
// assign to owner, otherwise assign a toggled blank.
|
|
// Blanking will prevent the UI updating it's state, since this will
|
|
// reset to value to a falsey value.
|
|
|
|
// A toggled blank is necessary as a state change device, since
|
|
// Ember does a deep comparison of object properties when deciding
|
|
// to perform a re-render. We could also use a device such as a hidden
|
|
// field with a randomized value.
|
|
// This helps to ensure our UI checkbox is consistent with our world
|
|
// state.
|
|
const currentConfirmation = get(owner, 'confirmedBy');
|
|
const nextBlank = currentConfirmation === null ? void 0 : null;
|
|
const confirmedBy = isConfirmedBy ? isConfirmedBy : nextBlank;
|
|
|
|
this._updateOwner(owner, 'confirmedBy', confirmedBy);
|
|
},
|
|
|
|
/**
|
|
* Invokes the passed in `save` closure action with the current list of owners.
|
|
* @returns {boolean|Promise.<TResult>|*}
|
|
*/
|
|
updateOwners() {
|
|
const closureAction = get(this, 'attrs.save');
|
|
|
|
return typeof closureAction === 'function' &&
|
|
closureAction(get(this, 'owners'))
|
|
.then(({
|
|
status
|
|
} = {}) => {
|
|
if (status === 'ok') {
|
|
set(
|
|
this,
|
|
'actionMessage',
|
|
'Successfully updated ownership for this dataset'
|
|
);
|
|
}
|
|
})
|
|
.catch(({ msg }) =>
|
|
set(
|
|
this,
|
|
'errorMessage',
|
|
`An error occurred while saving. Please let us know of this incident. ${msg}`
|
|
));
|
|
}
|
|
}
|
|
});
|