mirror of
https://github.com/datahub-project/datahub.git
synced 2025-08-27 18:45:50 +00:00
adds the dataset-author and dataset-authors components
This commit is contained in:
parent
4f2f16a191
commit
d91bd4f1d0
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 retreived 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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detemines 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 {false | void | IOwner}
|
||||||
|
*/
|
||||||
|
removeOwner: () => {
|
||||||
|
const { owner, isOwnerMutable, removeOwner } = getProperties(this, ['owner', 'isOwnerMutable', 'removeOwner']);
|
||||||
|
return isOwnerMutable && removeOwner(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 {HTMLSelectElement} {target}
|
||||||
|
* @return { void }
|
||||||
|
*/
|
||||||
|
updateOwnerType: ({ target }: Event) => {
|
||||||
|
const { value } = <HTMLSelectElement>target;
|
||||||
|
const { owner, isOwnerMutable, updateOwnerType } = getProperties(this, [
|
||||||
|
'owner',
|
||||||
|
'isOwnerMutable',
|
||||||
|
'updateOwnerType'
|
||||||
|
]);
|
||||||
|
|
||||||
|
return isOwnerMutable && updateOwnerType(owner, <OwnerType>value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
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 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<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 ? 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<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 => 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'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user