mirror of
https://github.com/datahub-project/datahub.git
synced 2025-07-25 02:19:58 +00:00
248 lines
8.4 KiB
TypeScript
248 lines
8.4 KiB
TypeScript
import WithEntityLists from '@datahub/lists/components/with-entity-lists';
|
|
// @ts-ignore: Ignore import of compiled template
|
|
import template from '../templates/components/entity-list-container';
|
|
import { layout, tagName } from '@ember-decorators/component';
|
|
import { DataModelEntity, DataModelName, DataModelEntityInstance } from '@datahub/data-models/constants/entity';
|
|
import { computed, action } from '@ember/object';
|
|
import { findEntityInList } from '@datahub/lists/utils';
|
|
import { alias, map, gt } from '@ember/object/computed';
|
|
import { capitalize } from 'lodash';
|
|
import { IEntityLinkAttrs } from '@datahub/data-models/types/entity/shared';
|
|
import { singularize } from 'ember-inflector';
|
|
import { Snapshot } from '@datahub/metadata-types/types/metadata/snapshot';
|
|
import { IStoredEntityAttrs } from '@datahub/lists/types/list';
|
|
import { set } from '@ember/object';
|
|
import { setProperties } from '@ember/object';
|
|
import Notifications from '@datahub/utils/services/notifications';
|
|
import { inject as service } from '@ember/service';
|
|
import { NotificationEvent } from '@datahub/utils/constants/notifications';
|
|
import { task } from 'ember-concurrency';
|
|
import { ETask } from '@datahub/utils/types/concurrency';
|
|
|
|
/**
|
|
* Defines the interface for the output of the EntityList mapping predicate function
|
|
* Outputs an entity with its generated link attributes
|
|
*/
|
|
interface IEntityWithLink {
|
|
// References the entity for which the link attributes should be generated
|
|
entity: DataModelEntityInstance;
|
|
|
|
// Generated link attributes for the aforementioned entity
|
|
linkAttr?: IEntityLinkAttrs;
|
|
}
|
|
|
|
/**
|
|
* Container component for a list of Entities
|
|
* Provides actions for the entire list and data transformation
|
|
* @export
|
|
* @class EntityListContainer
|
|
* @extends {WithEntityLists}
|
|
*/
|
|
@layout(template)
|
|
@tagName('')
|
|
export default class EntityListContainer extends WithEntityLists {
|
|
/**
|
|
* DataHub host application notifications service
|
|
*/
|
|
@service('notifications')
|
|
notificationsService?: Notifications;
|
|
|
|
/**
|
|
* The type of DataModel entity being rendered
|
|
*/
|
|
entityType?: DataModelName;
|
|
|
|
/**
|
|
* Retains a subset of the currently selected DataModelEntity types from the items in the EntityList
|
|
* This list is a non durable list because operations rely on object references for referential integrity
|
|
* If persistence is required for this list, then referencing items between other lists will requires
|
|
* more durable reference, for example using entity urns
|
|
*/
|
|
selectedEntities: Array<DataModelEntityInstance> = [];
|
|
|
|
/**
|
|
* A reference to the Entity class
|
|
* Required for the computed property dependent computed property entityListTrampoline
|
|
* @readonly
|
|
*/
|
|
@computed('entityType')
|
|
get entity(): DataModelEntity | null {
|
|
const { entityType } = this;
|
|
return entityType ? DataModelEntity[entityType] : null;
|
|
}
|
|
|
|
/**
|
|
* Singularized entity name
|
|
* @readonly
|
|
*/
|
|
@computed('entity')
|
|
get name(): string {
|
|
const { entity } = this;
|
|
return entity ? singularize(entity.displayName) : '';
|
|
}
|
|
|
|
/**
|
|
* Name of the list header
|
|
* @readonly
|
|
*/
|
|
@computed('name')
|
|
get listName(): string {
|
|
const { name } = this;
|
|
return name ? `${capitalize(name)} list` : '';
|
|
}
|
|
|
|
/**
|
|
* Number of items mapped from the source list
|
|
*/
|
|
@alias('entityListWithLinkAttrs.length')
|
|
listCount?: number;
|
|
|
|
/**
|
|
* Flag indicating that more than one entity from the list is currently selected
|
|
*/
|
|
@gt('selectedEntities.length', 1)
|
|
hasMultipleSelected!: boolean;
|
|
|
|
/**
|
|
* Flag indicating that all the items in the list have been selected
|
|
* Weak comparison on list length equality, suffices for the moment and
|
|
* cheaper than a full value equality or deep equality check which may come at cost for longer lists
|
|
* @readonly
|
|
*/
|
|
@computed('listCount', 'selectedEntities.length')
|
|
get selectedAll(): boolean {
|
|
const {
|
|
selectedEntities: { length },
|
|
listCount
|
|
} = this;
|
|
// Exclude zero length list
|
|
return Boolean(length) && listCount === length;
|
|
}
|
|
|
|
/**
|
|
* References the associated entity list
|
|
* @readonly
|
|
*/
|
|
@alias('entityListTrampoline.list')
|
|
list!: ReadonlyArray<IStoredEntityAttrs>;
|
|
|
|
/**
|
|
* Hydrated list of entities populated on initialization from the urns serialized in persistent storage
|
|
*/
|
|
instances: Array<DataModelEntityInstance> = [];
|
|
|
|
/**
|
|
* For each entity in the entity list, outputs a decoration with IEntityLinkAttrs instance
|
|
* This allows linking the list entity to the entity page
|
|
*/
|
|
@map('instances', function(this: EntityListContainer, instance: DataModelEntityInstance): IEntityWithLink {
|
|
// The entity class (statics) associated with this container
|
|
const { entity: entityType } = this;
|
|
const entityWithLink: IEntityWithLink = { entity: instance };
|
|
|
|
if (entityType && instance) {
|
|
const linkAttr = instance.entityLink;
|
|
|
|
return linkAttr ? { ...entityWithLink, linkAttr } : entityWithLink;
|
|
}
|
|
|
|
return entityWithLink;
|
|
})
|
|
entityListWithLinkAttrs!: Array<IEntityWithLink>;
|
|
|
|
/**
|
|
* Toggles a selected state when an entity in the list is selected or deselected in the ui
|
|
* Toggle action is determined based on presence of entity in selection subset: selectedEntities
|
|
* i.e. if absent added, otherwise removed
|
|
*/
|
|
@action
|
|
onSelectEntity(selectedEntity: DataModelEntityInstance): void {
|
|
const { selectedEntities } = this;
|
|
const isInSelection = findEntityInList(selectedEntities)(selectedEntity) as DataModelEntityInstance;
|
|
|
|
isInSelection ? selectedEntities.removeObject(isInSelection) : selectedEntities.addObject(selectedEntity);
|
|
}
|
|
|
|
/**
|
|
* Handles the change event for the list (group) checkbox element
|
|
* Toggling to checked will add all items in the list, otherwise clear the list
|
|
*/
|
|
@action
|
|
onSelectEntityList(): void {
|
|
const { selectedEntities, instances } = this;
|
|
this.selectedAll
|
|
? // Remove all items from selected entities list
|
|
selectedEntities.setObjects([])
|
|
: // Add all items to list
|
|
// If there are items in the list , then replace the selected entities list with all items
|
|
instances.length && selectedEntities.setObjects([...instances]);
|
|
}
|
|
|
|
/**
|
|
* Handler removes all the selected items from the entity list and the selected entities list
|
|
*/
|
|
@action
|
|
removeAllSelected(): void {
|
|
const { selectedEntities, listsManager, notificationsService, entity } = this;
|
|
const selectedCount = selectedEntities.length;
|
|
const notificationMessage = entity ? `${selectedCount} ${entity.displayName} removed from list successfully` : '';
|
|
|
|
// Remove entities from lists manager service
|
|
listsManager && listsManager.removeFromList(selectedEntities);
|
|
// Also remove from component selected list
|
|
selectedEntities.setObjects([]);
|
|
|
|
// On removal, refresh instances
|
|
|
|
this.hydrateEntitiesTask.perform();
|
|
|
|
notificationsService &&
|
|
notificationsService.notify({
|
|
type: NotificationEvent.success,
|
|
content: notificationMessage
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Reads the list of urn stored in persistent storage and then batch queries the remote for snapshots and entities for hydration
|
|
* then populates a local attribute with the received list of entities
|
|
*/
|
|
@(task(function*(
|
|
this: EntityListContainer
|
|
): IterableIterator<Promise<Array<Snapshot>> | Promise<Array<DataModelEntityInstance>>> {
|
|
const { entity } = this;
|
|
// Extract urns from the serialization list
|
|
const urns = this.list.map(({ urn }): string => urn);
|
|
|
|
if (entity && urns.length) {
|
|
// Hydrate entity instances with Snapshot and IBaseEntity attributes
|
|
const snapshots: Array<Snapshot> = yield entity.readSnapshots(urns);
|
|
// IBaseEntity property (entity) hydration happens in an async iteration because attributes batch GET endpoint for entities is N/A currently
|
|
const instances: Array<DataModelEntityInstance> = yield Promise.all(
|
|
snapshots.map(
|
|
async (snapshot): Promise<DataModelEntityInstance> => {
|
|
const listEntity = new entity(snapshot.urn);
|
|
setProperties(listEntity, { snapshot, entity: await listEntity.readEntity });
|
|
|
|
return listEntity;
|
|
}
|
|
)
|
|
);
|
|
|
|
return set(this, 'instances', instances);
|
|
}
|
|
|
|
return set(this, 'instances', []);
|
|
}).restartable())
|
|
hydrateEntitiesTask!: ETask<void>;
|
|
|
|
/**
|
|
* On initialization, hydrate the entities list with data serialized in persistent storage
|
|
*/
|
|
init(): void {
|
|
super.init();
|
|
|
|
this.hydrateEntitiesTask.perform();
|
|
}
|
|
}
|