2019-08-31 20:51:14 -07:00
|
|
|
import { IEntityRenderProps } from '@datahub/data-models/types/entity/rendering/entity-render-props';
|
|
|
|
import { Snapshot } from '@datahub/metadata-types/types/metadata/snapshot';
|
2020-08-26 15:44:50 -07:00
|
|
|
import { computed, set } from '@ember/object';
|
2019-08-31 20:51:14 -07:00
|
|
|
import { MetadataAspect } from '@datahub/metadata-types/types/metadata/aspect';
|
|
|
|
import { getMetadataAspect } from '@datahub/metadata-types/constants/metadata/aspect';
|
2020-08-26 15:44:50 -07:00
|
|
|
import { map, mapBy } from '@ember/object/computed';
|
2019-08-31 20:51:14 -07:00
|
|
|
import {
|
|
|
|
IEntityLinkAttrs,
|
2020-01-29 16:48:24 -08:00
|
|
|
EntityLinkNode,
|
2019-08-31 20:51:14 -07:00
|
|
|
IBrowsePath,
|
2020-01-29 16:48:24 -08:00
|
|
|
IEntityLinkAttrsWithCount,
|
2020-08-26 15:44:50 -07:00
|
|
|
AppRoute,
|
|
|
|
EntityPageRoute
|
2019-08-31 20:51:14 -07:00
|
|
|
} from '@datahub/data-models/types/entity/shared';
|
|
|
|
import { NotImplementedError } from '@datahub/data-models/constants/entity/shared/index';
|
2019-09-18 10:07:04 -07:00
|
|
|
import { readBrowse, readBrowsePath } from '@datahub/data-models/api/browse';
|
2019-08-31 20:51:14 -07:00
|
|
|
import { getFacetDefaultValueForEntity } from '@datahub/data-models/entity/utils/facets';
|
|
|
|
import { InstitutionalMemory } from '@datahub/data-models/models/aspects/institutional-memory';
|
2020-01-29 16:48:24 -08:00
|
|
|
import { IBaseEntity } from '@datahub/metadata-types/types/entity';
|
|
|
|
import { readEntity } from '@datahub/data-models/api/entity';
|
2020-08-26 15:44:50 -07:00
|
|
|
import { relationship } from '@datahub/data-models/relationships/decorator';
|
|
|
|
import { PersonEntity } from '@datahub/data-models/entity/person/person-entity';
|
|
|
|
import { SocialAction } from '@datahub/data-models/constants/entity/person/social-actions';
|
|
|
|
import { ILikeAction, IFollowerType } from '@datahub/metadata-types/types/aspects/social-actions';
|
|
|
|
import {
|
|
|
|
readLikesForEntity,
|
|
|
|
addLikeForEntity,
|
|
|
|
removeLikeForEntity,
|
|
|
|
readFollowsForEntity,
|
|
|
|
addFollowForEntity,
|
|
|
|
removeFollowForEntity
|
|
|
|
} from '@datahub/data-models/api/common/social-actions';
|
|
|
|
import { DataModelName } from '@datahub/data-models/constants/entity';
|
2020-08-28 16:19:46 -07:00
|
|
|
import { getDefaultIfNotFoundError } from '@datahub/utils/api/error';
|
2020-08-26 15:44:50 -07:00
|
|
|
import { isArray } from '@ember/array';
|
|
|
|
import { noop } from 'lodash';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Options for get category method
|
|
|
|
*/
|
|
|
|
interface IGetCategoryOptions {
|
|
|
|
// Page number to fetch from BE starting from 0
|
|
|
|
page?: number;
|
|
|
|
// number of items per page that the BE should return
|
|
|
|
count?: number;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Parameters for the BaseEntity static method, getLinkForEntity
|
|
|
|
* @interface IGetLinkForEntityParams
|
|
|
|
*/
|
|
|
|
interface IGetLinkForEntityParams {
|
|
|
|
// The display text for the generated entity link, not related to BaseEntity['displayName']
|
|
|
|
displayName: string;
|
|
|
|
// The URN for the specific entity instance
|
|
|
|
entityUrn: string;
|
|
|
|
// Optional text for the title attribute in the consuming anchor element
|
|
|
|
title?: string;
|
|
|
|
}
|
2019-08-31 20:51:14 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Interfaces and abstract classes define the "instance side" of a type / class,
|
|
|
|
* therefore ts will only check the instance side
|
|
|
|
* To check the static side / constructor, the interface should define the constructor, then static properties
|
|
|
|
*
|
|
|
|
* statics is a generic ClassDecorator that checks the static side T of the decorated class
|
|
|
|
* @template T {T extends new (...args: Array<any>) => void} constrains T to constructor interfaces
|
|
|
|
* @type {() => ClassDecorator}
|
|
|
|
*/
|
2020-08-26 15:44:50 -07:00
|
|
|
export const statics = <T extends new (...args: Array<unknown>) => void>(): ((c: T) => void) => noop;
|
2019-08-31 20:51:14 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Defines the interface for the static side or constructor of a class that extends BaseEntity<T>
|
|
|
|
* @export
|
|
|
|
* @interface IBaseEntityStatics
|
|
|
|
* @template T constrained by the IBaseEntity interface, the entity interface that BaseEntity subclass will encapsulate
|
|
|
|
*/
|
2020-09-29 16:04:25 -07:00
|
|
|
export interface IBaseEntityStatics<T, S = Snapshot> {
|
|
|
|
new (urn: string): BaseEntity<T, S>;
|
2019-08-31 20:51:14 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Properties that guide the rendering of ui elements and features in the host application
|
|
|
|
* @readonly
|
|
|
|
* @static
|
|
|
|
*/
|
|
|
|
renderProps: IEntityRenderProps;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Statically accessible name of the concrete DataModel type
|
|
|
|
* @type {string}
|
|
|
|
*/
|
|
|
|
displayName: string;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Queries the entity's endpoint to retrieve the list of nodes that are contained in the hierarchy
|
2020-01-29 16:48:24 -08:00
|
|
|
* @param {(Array<string>)} args list of string values corresponding to the different hierarchical categories for the entity
|
2019-08-31 20:51:14 -07:00
|
|
|
*/
|
2020-08-26 15:44:50 -07:00
|
|
|
readCategories(args: Array<string>, options?: IGetCategoryOptions): Promise<IBrowsePath>;
|
2019-08-31 20:51:14 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Queries the batch GET endpoint for snapshots for the supplied urns
|
|
|
|
*/
|
2020-09-29 16:04:25 -07:00
|
|
|
readSnapshots(_urns: Array<string>): Promise<Array<S>>;
|
2019-08-31 20:51:14 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Builds a search query keyword from a list of segments for the related DataModelEntity
|
|
|
|
* @memberof IBaseEntityStatics
|
|
|
|
*/
|
|
|
|
getQueryForHierarchySegments(_segments: Array<string>): string;
|
2020-01-29 16:48:24 -08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the entity link for the current entity
|
|
|
|
*/
|
|
|
|
getLinkForEntity(params: { entityUrn: string; displayName: string }): IEntityLinkAttrs | void;
|
2019-08-31 20:51:14 -07:00
|
|
|
}
|
|
|
|
|
2020-01-29 16:48:24 -08:00
|
|
|
/**
|
|
|
|
* Check if entity extends baseEntity by checking on the urn property
|
|
|
|
*/
|
|
|
|
export const isBaseEntity = <T extends {}>(entity?: T | IBaseEntity): entity is IBaseEntity =>
|
|
|
|
(entity && entity.hasOwnProperty('urn')) || false;
|
|
|
|
|
2019-08-31 20:51:14 -07:00
|
|
|
/**
|
|
|
|
* This defines the base attributes and methods for the instance side of an entity data model
|
|
|
|
* i.e. the public interface for an entity object
|
|
|
|
* Shared methods and default properties may be defined on this class
|
|
|
|
* Properties intended to be implemented by the concrete entity, and shared methods that
|
|
|
|
* need to be delegated to the concrete entity should be declared here
|
|
|
|
* @export
|
|
|
|
* @abstract
|
|
|
|
* @class BaseEntity
|
|
|
|
* @template T the entity interface that the entity model (subclass) encapsulates
|
|
|
|
*/
|
2020-09-29 16:04:25 -07:00
|
|
|
export abstract class BaseEntity<T extends {} | IBaseEntity, S extends {} | Snapshot = Snapshot> {
|
2019-08-31 20:51:14 -07:00
|
|
|
/**
|
|
|
|
* A reference to the derived concrete entity instance
|
|
|
|
* @type {T}
|
|
|
|
*/
|
|
|
|
entity?: T;
|
|
|
|
|
|
|
|
/**
|
2020-08-26 15:44:50 -07:00
|
|
|
* TODO META-10097: We should be consistent with entity and use a generic type
|
2019-08-31 20:51:14 -07:00
|
|
|
* References the Snapshot for the related Entity
|
|
|
|
* @type {Snapshot}
|
|
|
|
*/
|
2020-09-29 16:04:25 -07:00
|
|
|
snapshot?: S;
|
2019-08-31 20:51:14 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* References the wiki related documents and objects related to this entity
|
|
|
|
*/
|
2020-08-26 15:44:50 -07:00
|
|
|
_institutionalMemories?: Array<InstitutionalMemory>;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Getter and setter for institutional memories
|
|
|
|
*/
|
|
|
|
@computed('_institutionalMemories')
|
|
|
|
get institutionalMemories(): Array<InstitutionalMemory> | undefined {
|
|
|
|
return this._institutionalMemories;
|
|
|
|
}
|
|
|
|
|
|
|
|
set institutionalMemories(institutionalMemories: Array<InstitutionalMemory> | undefined) {
|
|
|
|
set(this, '_institutionalMemories', institutionalMemories);
|
|
|
|
}
|
2019-08-31 20:51:14 -07:00
|
|
|
|
2020-01-29 16:48:24 -08:00
|
|
|
/**
|
|
|
|
* Hook for custom fetching operations after entity is created
|
|
|
|
*/
|
|
|
|
onAfterCreate(): Promise<void> {
|
|
|
|
return Promise.resolve();
|
|
|
|
}
|
|
|
|
|
2019-08-31 20:51:14 -07:00
|
|
|
/**
|
|
|
|
* A dictionary of host Ember application routes which can be used as route arguments to the link-to helper
|
|
|
|
*/
|
2020-01-29 16:48:24 -08:00
|
|
|
get hostRoutes(): Record<string, AppRoute | void> {
|
2019-08-31 20:51:14 -07:00
|
|
|
return { dataSourceRoute: 'datasets.dataset' };
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Indicates whether the entity has been removed (soft deletion), reads from reified entity,
|
|
|
|
* otherwise defaults to false
|
|
|
|
* @readonly
|
|
|
|
* @type {boolean}
|
|
|
|
* @memberof BaseEntity
|
|
|
|
*/
|
|
|
|
@computed('entity')
|
|
|
|
get removed(): boolean {
|
2020-01-29 16:48:24 -08:00
|
|
|
return isBaseEntity<T>(this.entity) ? this.entity.removed : false;
|
2019-08-31 20:51:14 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Selects the list of owners from the ownership aspect attribute of the entity
|
|
|
|
* All entities have an ownership aspect have this property exists on the base and is inherited
|
|
|
|
*
|
2020-08-26 15:44:50 -07:00
|
|
|
* This provides the full Owner objects in a list, if what is needed is just the list of
|
2019-08-31 20:51:14 -07:00
|
|
|
* owner urns, the macro value ownerUrns provides that immediately
|
|
|
|
* @readonly
|
|
|
|
* @type {Array<IOwner>}
|
|
|
|
* @memberof BaseEntity
|
|
|
|
*/
|
|
|
|
@computed('snapshot')
|
2020-08-26 15:44:50 -07:00
|
|
|
get owners(): Array<Com.Linkedin.Common.Owner> {
|
2020-09-29 16:04:25 -07:00
|
|
|
const ownership = getMetadataAspect(this.snapshot as Snapshot)(
|
2019-08-31 20:51:14 -07:00
|
|
|
'com.linkedin.common.Ownership'
|
|
|
|
) as MetadataAspect['com.linkedin.common.Ownership'];
|
|
|
|
|
|
|
|
return ownership ? ownership.owners : [];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Extracts the owner urns into a list from each IOwner instance
|
|
|
|
* @readonly
|
|
|
|
* @type {Array<string>}
|
|
|
|
* @memberof BaseEntity
|
|
|
|
*/
|
2020-08-26 15:44:50 -07:00
|
|
|
@map('owners.[]', ({ owner }: Com.Linkedin.Common.Owner): string => owner)
|
2019-08-31 20:51:14 -07:00
|
|
|
ownerUrns!: Array<string>;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Class instance JSON serialization does not by default serialize non-enumerable property names
|
|
|
|
* This provides a custom toJSON method to ensure that displayName attribute is present when serialized,
|
|
|
|
* allowing correct de-serialization back to source instance / DataModelEntity type
|
|
|
|
*/
|
|
|
|
toJSON(): this {
|
|
|
|
return { ...this, displayName: this.displayName };
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-08-26 15:44:50 -07:00
|
|
|
* Base Ember route reference for entity pages
|
2019-08-31 20:51:14 -07:00
|
|
|
* @static
|
|
|
|
*/
|
2020-08-26 15:44:50 -07:00
|
|
|
static entityBaseRoute: EntityPageRoute = 'entity-type.urn';
|
2019-08-31 20:51:14 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Base entity display name
|
|
|
|
* @static
|
|
|
|
*/
|
|
|
|
static displayName: string;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Human friendly string alias for the entity type e.g. Dashboard, Users
|
|
|
|
* @type {string}
|
|
|
|
*/
|
|
|
|
get displayName(): string {
|
|
|
|
// Implemented in concrete class
|
|
|
|
throw new Error(NotImplementedError);
|
|
|
|
}
|
|
|
|
|
2019-09-18 10:07:04 -07:00
|
|
|
/**
|
|
|
|
* Workaround to get the current static instance
|
|
|
|
* This makes sense if you want to get a static property only
|
|
|
|
* implemented in a subclass, therefore, the same static
|
|
|
|
* class is needed
|
|
|
|
*/
|
2020-09-29 16:04:25 -07:00
|
|
|
get staticInstance(): IBaseEntityStatics<T, S> {
|
|
|
|
return (this.constructor as unknown) as IBaseEntityStatics<T, S>;
|
2019-09-18 10:07:04 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Will read the current path for an entity
|
|
|
|
*/
|
|
|
|
get readPath(): Promise<Array<string>> {
|
2020-01-29 16:48:24 -08:00
|
|
|
const { urn, staticInstance } = this;
|
2020-08-26 15:44:50 -07:00
|
|
|
const entityName = staticInstance.renderProps.apiEntityName;
|
2019-09-18 10:07:04 -07:00
|
|
|
return readBrowsePath({
|
|
|
|
type: entityName,
|
2020-01-29 16:48:24 -08:00
|
|
|
urn
|
2019-09-18 10:07:04 -07:00
|
|
|
}).then(
|
|
|
|
(paths): Array<string> => {
|
|
|
|
return paths && paths.length > 0 ? paths[0].split('/').filter(Boolean) : [];
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-08-31 20:51:14 -07:00
|
|
|
/**
|
|
|
|
* Asynchronously resolves with an instance of T
|
|
|
|
* @readonly
|
|
|
|
* @type {Promise<T>}
|
|
|
|
*/
|
2020-01-29 16:48:24 -08:00
|
|
|
get readEntity(): Promise<T> | Promise<undefined> {
|
|
|
|
const { entityPage } = this.staticInstance.renderProps;
|
2020-08-26 15:44:50 -07:00
|
|
|
if (entityPage && entityPage.apiRouteName) {
|
|
|
|
return readEntity<T>(this.urn, entityPage.apiRouteName);
|
2020-01-29 16:48:24 -08:00
|
|
|
}
|
|
|
|
// Implemented in concrete class, if it exists for the entity
|
2019-08-31 20:51:14 -07:00
|
|
|
throw new Error(NotImplementedError);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Asynchronously resolves with the Snapshot for the entity
|
|
|
|
* This should be implemented on the concrete class and is enforced to be available with the
|
|
|
|
* abstract modifier
|
2020-08-26 15:44:50 -07:00
|
|
|
*
|
|
|
|
* Backend is moving away from snapshot api. UI won't enforce implementing this fn.
|
2019-08-31 20:51:14 -07:00
|
|
|
* @readonly
|
|
|
|
* @type {Promise<Snapshot>}
|
|
|
|
*/
|
2020-01-29 16:48:24 -08:00
|
|
|
get readSnapshot(): Promise<Snapshot> | Promise<undefined> {
|
|
|
|
// Implemented in concrete class, if it exists for the entity
|
|
|
|
return Promise.resolve(undefined);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Every entity should have a way to return the name.
|
|
|
|
* This can be used for search results or entity header
|
|
|
|
*/
|
|
|
|
get name(): string {
|
2019-08-31 20:51:14 -07:00
|
|
|
// Implemented in concrete class
|
|
|
|
throw new Error(NotImplementedError);
|
|
|
|
}
|
|
|
|
|
2020-01-29 16:48:24 -08:00
|
|
|
/**
|
|
|
|
* Returns a link for the entity page for this entity
|
|
|
|
*/
|
|
|
|
get entityLink(): IEntityLinkAttrs | void {
|
|
|
|
return this.staticInstance.getLinkForEntity({
|
|
|
|
entityUrn: this.urn,
|
|
|
|
displayName: this.name
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-08-26 15:44:50 -07:00
|
|
|
/**
|
|
|
|
* Constructs a link to a specific entity tab using the supplied tab name
|
|
|
|
* @param {string} tabName the name of the tab to generate a link for
|
|
|
|
*/
|
|
|
|
entityTabLink(tabName: string): this['entityLink'] {
|
|
|
|
const { entityLink } = this;
|
|
|
|
|
|
|
|
if (entityLink) {
|
|
|
|
const {
|
|
|
|
link,
|
|
|
|
link: { model = [], route }
|
|
|
|
} = entityLink;
|
|
|
|
|
|
|
|
return { ...entityLink, link: { ...link, route: `${route}.tab` as AppRoute, model: [...model, tabName] } };
|
|
|
|
}
|
|
|
|
|
|
|
|
return entityLink;
|
|
|
|
}
|
|
|
|
|
2019-08-31 20:51:14 -07:00
|
|
|
/**
|
|
|
|
* Class properties common across instances
|
|
|
|
* Dictates how visual ui components should be rendered
|
|
|
|
* Implemented as a getter to ensure that reads are idempotent
|
|
|
|
*/
|
|
|
|
static get renderProps(): IEntityRenderProps {
|
|
|
|
throw new Error(NotImplementedError);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-01-29 16:48:24 -08:00
|
|
|
* Queries the entity's endpoint to retrieve the list of nodes that are contained in the hierarchy
|
|
|
|
* @param {(Array<string>)} args list of string values corresponding to the different hierarchical categories for the entity
|
2019-08-31 20:51:14 -07:00
|
|
|
*/
|
2020-08-26 15:44:50 -07:00
|
|
|
static async readCategories(
|
|
|
|
segments: Array<string>,
|
|
|
|
{ page = 0, count = 100 }: IGetCategoryOptions = {}
|
|
|
|
): Promise<IBrowsePath> {
|
|
|
|
const { browse } = this.renderProps;
|
|
|
|
if (browse) {
|
|
|
|
const cleanSegments: Array<string> = segments.filter(Boolean) as Array<string>;
|
|
|
|
const defaultFacets: Record<string, Array<string>> = browse.attributes
|
|
|
|
? getFacetDefaultValueForEntity(browse.attributes)
|
|
|
|
: {};
|
|
|
|
const { elements, metadata, total } = await readBrowse({
|
|
|
|
type: this.renderProps.apiEntityName,
|
|
|
|
path: cleanSegments.length > 0 ? `/${cleanSegments.join('/')}` : '',
|
|
|
|
count,
|
|
|
|
start: page * count,
|
|
|
|
...defaultFacets
|
|
|
|
});
|
|
|
|
// List of entities
|
|
|
|
// Create links for the entities for the current category
|
|
|
|
const entityLinks: Array<IEntityLinkAttrs> = elements
|
|
|
|
.map((element): IEntityLinkAttrs | void =>
|
|
|
|
this.getLinkForEntity({
|
|
|
|
displayName: element.name,
|
|
|
|
entityUrn: element.urn
|
|
|
|
})
|
|
|
|
)
|
|
|
|
.filter((link): boolean => Boolean(link)) as Array<IEntityLinkAttrs>; // filter removed undefined
|
|
|
|
|
|
|
|
// List of folders
|
|
|
|
// For this category will append and create a link for the next category (the one that you can potentially go)
|
|
|
|
const categoryLinks: Array<IEntityLinkAttrsWithCount> = metadata.groups.map(
|
|
|
|
(group): IEntityLinkAttrsWithCount => {
|
|
|
|
return this.getLinkForCategory({
|
|
|
|
segments: [...cleanSegments, group.name],
|
|
|
|
count: group.count,
|
|
|
|
displayName: group.name
|
|
|
|
});
|
|
|
|
}
|
|
|
|
);
|
|
|
|
return {
|
|
|
|
segments,
|
|
|
|
title: segments[segments.length - 1] || this.displayName,
|
|
|
|
totalNumEntities: metadata.totalNumEntities,
|
|
|
|
entitiesPaginationCount: total,
|
|
|
|
entities: entityLinks,
|
|
|
|
groups: categoryLinks
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// if no browse available return empty
|
2019-08-31 20:51:14 -07:00
|
|
|
return {
|
2020-08-26 15:44:50 -07:00
|
|
|
segments: [],
|
|
|
|
title: '',
|
|
|
|
totalNumEntities: 0,
|
|
|
|
entitiesPaginationCount: 0,
|
|
|
|
entities: [],
|
|
|
|
groups: []
|
2019-08-31 20:51:14 -07:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Will generate a link for an entity based on a displayName and a entityUrn
|
2020-01-29 16:48:24 -08:00
|
|
|
* displayName attribute is used in the anchor tag as a the text representation if provided, is unrelated to BaseEntity['displayName']
|
|
|
|
* optionally, a title attribute can be provided to generate the consuming anchor element title
|
|
|
|
* @static
|
|
|
|
* @param {IGetLinkForEntityParams} params parameters for generating the link object matching the IEntityLinkAttrs interface
|
2019-08-31 20:51:14 -07:00
|
|
|
*/
|
2020-08-26 15:44:50 -07:00
|
|
|
static getLinkForEntity(params: IGetLinkForEntityParams): IEntityLinkAttrs | void {
|
|
|
|
const entityPage = this.renderProps.entityPage;
|
|
|
|
const { displayName = this.displayName, entityUrn, title = displayName } = params;
|
2019-08-31 20:51:14 -07:00
|
|
|
|
2020-08-26 15:44:50 -07:00
|
|
|
if (entityPage && entityUrn) {
|
|
|
|
const model = entityPage.route === 'entity-type.urn' ? [this.displayName, entityUrn] : [entityUrn];
|
|
|
|
|
|
|
|
const link: EntityLinkNode = {
|
|
|
|
route: entityPage.route,
|
|
|
|
text: displayName,
|
|
|
|
title,
|
|
|
|
model
|
|
|
|
};
|
|
|
|
|
|
|
|
return {
|
|
|
|
entity: this.displayName,
|
|
|
|
link
|
|
|
|
};
|
|
|
|
}
|
2019-08-31 20:51:14 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate link for category given segments, displayName and count as optional
|
|
|
|
*/
|
|
|
|
static getLinkForCategory(params: {
|
|
|
|
segments: Array<string>;
|
|
|
|
displayName: string;
|
|
|
|
count: number;
|
|
|
|
}): IEntityLinkAttrsWithCount {
|
|
|
|
const { segments, count, displayName } = params;
|
2020-01-29 16:48:24 -08:00
|
|
|
const link: EntityLinkNode<{ path: string }> = {
|
2019-08-31 20:51:14 -07:00
|
|
|
title: displayName || '',
|
|
|
|
text: displayName || segments[0] || '',
|
|
|
|
route: 'browse.entity',
|
|
|
|
model: [this.displayName],
|
|
|
|
queryParams: { path: segments.filter(Boolean).join('/') }
|
|
|
|
};
|
|
|
|
return {
|
|
|
|
link,
|
|
|
|
entity: this.displayName,
|
|
|
|
count
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Reads the snapshots for the entity
|
|
|
|
* @static
|
|
|
|
*/
|
|
|
|
static readSnapshots(_urns: Array<string>): Promise<Array<Snapshot>> {
|
|
|
|
throw new Error(NotImplementedError);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Builds a search query keyword from a list of segments
|
|
|
|
* @static
|
|
|
|
* @param {Array<string>} [segments=[]] the list of hierarchy segments to generate the keyword for
|
|
|
|
*/
|
|
|
|
static getQueryForHierarchySegments(segments: Array<string> = []): string {
|
2020-08-26 15:44:50 -07:00
|
|
|
return `browsePaths:\\/${segments.join('\\/').replace(/\s/gi, '\\ ')}`;
|
2019-08-31 20:51:14 -07:00
|
|
|
}
|
|
|
|
|
2020-08-26 15:44:50 -07:00
|
|
|
// TODO META-12149 this should be part of an Aspect. This fns can't live under BaseEntity as
|
|
|
|
// then we would have a circular dependency:
|
|
|
|
// BaseEntity -> InstitutionalMemory -> PersonEntity -> BaseEntity
|
2019-08-31 20:51:14 -07:00
|
|
|
/**
|
|
|
|
* Retrieves a list of wiki documents related to the particular entity instance
|
|
|
|
* @readonly
|
|
|
|
*/
|
|
|
|
readInstitutionalMemory(): Promise<Array<InstitutionalMemory>> {
|
|
|
|
throw new Error(NotImplementedError);
|
|
|
|
}
|
|
|
|
|
2020-08-26 15:44:50 -07:00
|
|
|
// TODO META-12149 this should be part of an Aspect. This fns can't live under BaseEntity as
|
|
|
|
// then we would have a circular dependency:
|
|
|
|
// BaseEntity -> InstitutionalMemory -> PersonEntity -> BaseEntity
|
2019-08-31 20:51:14 -07:00
|
|
|
/**
|
|
|
|
* Writes a list of wiki documents related to a particular entity instance to the api layer
|
|
|
|
*/
|
|
|
|
writeInstitutionalMemory(): Promise<void> {
|
|
|
|
throw new Error(NotImplementedError);
|
|
|
|
}
|
|
|
|
|
2020-08-26 15:44:50 -07:00
|
|
|
/**
|
|
|
|
* For social features, we flag whether or not the entity is opted into social actions
|
|
|
|
* @default true
|
|
|
|
*/
|
|
|
|
allowedSocialActions: Record<SocialAction, boolean> = {
|
|
|
|
like: true,
|
|
|
|
follow: true,
|
|
|
|
save: true
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* For this particular entity, get the list of like actions related to the entity,
|
|
|
|
* effectively getting the number of people that have upvoted the entity
|
|
|
|
*/
|
|
|
|
async readLikes(): Promise<void> {
|
|
|
|
if (this.allowedSocialActions.like) {
|
2020-08-28 16:19:46 -07:00
|
|
|
const likeActions = await readLikesForEntity(this.displayName as DataModelName, this.urn).catch(
|
|
|
|
getDefaultIfNotFoundError([])
|
|
|
|
);
|
2020-08-26 15:44:50 -07:00
|
|
|
set(this, 'likedByActions', likeActions || []);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* For this particular entity, add the user's like action to the entity
|
|
|
|
*/
|
|
|
|
async addLike(): Promise<void> {
|
|
|
|
if (this.allowedSocialActions.like) {
|
|
|
|
const updatedLikeActions = await addLikeForEntity(this.displayName as DataModelName, this.urn);
|
|
|
|
|
|
|
|
if (isArray(updatedLikeActions)) {
|
|
|
|
set(this, 'likedByActions', updatedLikeActions);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* For this particular entity, remove the user's like action from the entity
|
|
|
|
*/
|
|
|
|
async removeLike(): Promise<void> {
|
|
|
|
if (this.allowedSocialActions.like) {
|
|
|
|
const updatedLikeActions = await removeLikeForEntity(this.displayName as DataModelName, this.urn);
|
|
|
|
|
|
|
|
if (isArray(updatedLikeActions)) {
|
|
|
|
set(this, 'likedByActions', updatedLikeActions);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* For this particular entity, get the list of follow actions related to the entity,
|
|
|
|
* effectively getting the number of people that have subscribed to notifications for metadata
|
|
|
|
* changes in the entity
|
|
|
|
*/
|
|
|
|
async readFollows(): Promise<void> {
|
|
|
|
if (this.allowedSocialActions.follow) {
|
2020-08-28 16:19:46 -07:00
|
|
|
const followActions = await readFollowsForEntity(this.displayName as DataModelName, this.urn).catch(
|
|
|
|
getDefaultIfNotFoundError([])
|
|
|
|
);
|
2020-08-26 15:44:50 -07:00
|
|
|
|
|
|
|
set(this, 'followedByActions', followActions || []);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* For this particular entity, add the user's follow action to the entity, subscribing them to
|
|
|
|
* updates for metadata changes
|
|
|
|
*/
|
|
|
|
async addFollow(): Promise<void> {
|
|
|
|
if (this.allowedSocialActions.follow) {
|
|
|
|
const updatedFollowActions = await addFollowForEntity(this.displayName as DataModelName, this.urn);
|
|
|
|
|
|
|
|
if (isArray(updatedFollowActions)) {
|
|
|
|
set(this, 'followedByActions', updatedFollowActions);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* For this particular entity, remove the user's follow action from the entity, unsubscribing
|
|
|
|
* them from changes
|
|
|
|
*/
|
|
|
|
async removeFollow(): Promise<void> {
|
|
|
|
if (this.allowedSocialActions.follow) {
|
|
|
|
const updatedFollowActions = await removeFollowForEntity(this.displayName as DataModelName, this.urn);
|
|
|
|
|
|
|
|
if (isArray(updatedFollowActions)) {
|
|
|
|
set(this, 'followedByActions', updatedFollowActions);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Actions representing a like from a specific user
|
|
|
|
*/
|
|
|
|
likedByActions: Array<ILikeAction> = [];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* For social features, we add the concept of "liking" an entity which implies that the data
|
|
|
|
* related to the entity is useful or of importance
|
|
|
|
*/
|
|
|
|
@mapBy('likedByActions', 'likedBy')
|
|
|
|
likedByUrns!: Array<string>;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* For social features, we translate the urn ids of the people who have liked an entity into
|
|
|
|
* the related PersonEntity instances
|
|
|
|
*/
|
|
|
|
@relationship('people', 'likedByUrns')
|
|
|
|
likedBy!: Array<PersonEntity>;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Action objects representing a follow from a specific user
|
|
|
|
*/
|
|
|
|
followedByActions: Array<IFollowerType> = [];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* For social features, we add the concept of "following" an entity, which means that the person
|
|
|
|
* (current user) has opted into notifications of updates regarding this entity's metadata
|
|
|
|
*/
|
|
|
|
@computed('followedByActions')
|
|
|
|
get followedByUrns(): Array<string> {
|
|
|
|
const { followedByActions } = this;
|
|
|
|
// corpUser || corpGroup is guaranteed to be a string as one of them MUST be defined, as
|
|
|
|
// dictated by our API interface
|
|
|
|
return followedByActions.map(({ corpGroup, corpUser }): string => (corpUser || corpGroup) as string);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* For social features, we translate the urn ids of the people who have followed an entity into
|
|
|
|
* the related PersonEntity instances
|
|
|
|
*/
|
|
|
|
@relationship('people', 'followedByUrns')
|
|
|
|
followedBy!: Array<PersonEntity>;
|
|
|
|
|
2019-08-31 20:51:14 -07:00
|
|
|
/**
|
|
|
|
* Creates an instance of BaseEntity concrete class
|
|
|
|
* @param {string} urn the urn for the entity being instantiated. urn is a parameter property
|
|
|
|
* @memberof BaseEntity
|
|
|
|
*/
|
2020-01-29 16:48:24 -08:00
|
|
|
constructor(readonly urn: string = '') {}
|
2019-08-31 20:51:14 -07:00
|
|
|
}
|