Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Activities Logs entity and model for User / Organization / Team, etc. #8201

Merged
merged 7 commits into from
Sep 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions packages/contracts/src/activity-log.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { ActorTypeEnum, IBasePerTenantAndOrganizationEntityModel, ID } from './base-entity.model';
import { IUser } from './user.model';

export interface IActivityLog extends IBasePerTenantAndOrganizationEntityModel {
entity: ActivityLogEntityEnum; // Entity / Table name concerned by activity log
entityId: ID; // The ID of the element we are interacting with (a task, an organization, an employee, ...)
action: ActionTypeEnum;
actorType?: ActorTypeEnum;
description?: string; // A short sentence describing the action performed. (E.g John Doe created this on 22.09.2024)
updatedFields?: string[]; // In case of update actions, which entity fields was modified simultaneously. Avoid multiple records. (E.g For task : ['name', 'members', 'projectId'])
previousValues?: IActivityLogUpdatedValues[]; // Values before update (E.g For task : {title: ' First Task', members: ['Member1Name', 'Member2Name'], projectId: 'project1UUId'})
updatedValues?: IActivityLogUpdatedValues[]; // Values after update (E.g For task : {title: ' First Task Updated', members: ['Member4Name', 'Member3Name'], projectId: 'project2UUId'})
previousEntities?: IActivityLogUpdatedValues[]; // Stores previous IDs or other values for related entities. Eg : {members: ['member_1_ID', 'member_2_ID']}
updatedEntities?: IActivityLogUpdatedValues[]; // Stores updated IDs, or other values for related entities. Eg : {members: ['member_1_ID', 'member_2_ID']},
creator?: IUser;
creatorId?: ID;
data?: Record<string, any>;
}

export enum ActionTypeEnum {
CREATED = 'Created',
UPDATED = 'Updated',
DELETED = 'Deleted'
}

export interface IActivityLogUpdatedValues {
[x: string]: any;
}

export enum ActivityLogEntityEnum {
Candidate = 'Candidate',
Contact = 'Contact',
Employee = 'Employee',
Expense = 'Expense',
DailyPlan = 'DailyPlan',
Invoice = 'Invoice',
Income = 'Income',
Organization = 'Organization',
OrganizationContact = 'OrganizationContact',
OrganizationDepartment = 'OrganizationDepartment',
OrganizationDocument = 'OrganizationDocument',
OrganizationProject = 'OrganizationProject',
OrganizationTeam = 'OrganizationTeam',
OrganizationProjectModule = 'OrganizationProjectModule',
OrganizationSprint = 'OrganizationSprint',
Task = 'Task',
User = 'User'
// Add other entities as we can to use them for activity history
}
1 change: 1 addition & 0 deletions packages/contracts/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './accounting-template.model';
/** App Setting Model */
export * from './activity-log.model';
export * from './app.model';
export * from './appointment-employees.model';
export * from './approval-policy.model';
Expand Down
121 changes: 121 additions & 0 deletions packages/core/src/activity-log/activity-log.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { EntityRepositoryType } from '@mikro-orm/core';
import { IsArray, IsEnum, IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator';
import { isMySQL, isPostgres } from '@gauzy/config';
import {
ActivityLogEntityEnum,
ActionTypeEnum,
ActorTypeEnum,
IActivityLog,
IActivityLogUpdatedValues,
ID,
IUser
} from '@gauzy/contracts';
import { TenantOrganizationBaseEntity, User } from '../core/entities/internal';
import { ColumnIndex, MultiORMColumn, MultiORMEntity, MultiORMManyToOne } from '../core/decorators/entity';
import { MikroOrmActivityLogRepository } from './repository/mikro-orm-activity-log.repository';
import { JoinColumn, RelationId } from 'typeorm';

@MultiORMEntity('activity_log', { mikroOrmRepository: () => MikroOrmActivityLogRepository })
export class ActivityLog extends TenantOrganizationBaseEntity implements IActivityLog {
[EntityRepositoryType]?: MikroOrmActivityLogRepository;

@ApiProperty({ type: () => String, enum: ActivityLogEntityEnum })
@IsNotEmpty()
@IsEnum(ActivityLogEntityEnum)
@ColumnIndex()
@MultiORMColumn()
entity: ActivityLogEntityEnum;

@ApiProperty({ type: () => String })
@IsNotEmpty()
@IsUUID()
@ColumnIndex()
@MultiORMColumn()
entityId: string;

@ApiProperty({ type: () => String, enum: ActionTypeEnum })
@IsNotEmpty()
@IsEnum(ActionTypeEnum)
@ColumnIndex()
@MultiORMColumn()
action: ActionTypeEnum;

@ApiPropertyOptional({ type: () => String, enum: ActorTypeEnum })
@IsOptional()
@IsEnum(ActorTypeEnum)
@ColumnIndex()
@MultiORMColumn({ nullable: true })
actorType?: ActorTypeEnum;

@ApiPropertyOptional({ type: () => String })
@IsOptional()
@IsString()
@MultiORMColumn({ type: 'text', nullable: true })
description?: string;

@ApiPropertyOptional({ type: () => Array })
@IsOptional()
@IsArray()
@MultiORMColumn({ type: isPostgres() ? 'jsonb' : isMySQL() ? 'json' : 'text', nullable: true })
updatedFields?: string[];

@ApiPropertyOptional({ type: () => Array })
@IsOptional()
@IsArray()
@MultiORMColumn({ type: isPostgres() ? 'jsonb' : isMySQL() ? 'json' : 'text', nullable: true })
previousValues?: IActivityLogUpdatedValues[];

@ApiPropertyOptional({ type: () => Array })
@IsOptional()
@IsArray()
@MultiORMColumn({ type: isPostgres() ? 'jsonb' : isMySQL() ? 'json' : 'text', nullable: true })
updatedValues?: IActivityLogUpdatedValues[];

@ApiPropertyOptional({ type: () => Array })
@IsOptional()
@IsArray()
@MultiORMColumn({ type: isPostgres() ? 'jsonb' : isMySQL() ? 'json' : 'text', nullable: true })
previousEntities?: IActivityLogUpdatedValues[];

@ApiPropertyOptional({ type: () => Array })
@IsOptional()
@IsArray()
@MultiORMColumn({ type: isPostgres() ? 'jsonb' : isMySQL() ? 'json' : 'text', nullable: true })
updatedEntities?: IActivityLogUpdatedValues[];

@ApiPropertyOptional({ type: () => Object })
@IsOptional()
@IsArray()
@MultiORMColumn({ type: isPostgres() ? 'jsonb' : isMySQL() ? 'json' : 'text', nullable: true })
data?: Record<string, any>;

/*
|--------------------------------------------------------------------------
| @ManyToOne
|--------------------------------------------------------------------------
*/

/**
* User performed action
*/
@ApiPropertyOptional({ type: () => Object })
@IsOptional()
@MultiORMManyToOne(() => User, {
/** Indicates if relation column value can be nullable or not. */
nullable: true,

/** Database cascade action on delete. */
onDelete: 'CASCADE'
})
@JoinColumn()
creator?: IUser;

@ApiPropertyOptional({ type: () => String })
@IsOptional()
@IsUUID()
@RelationId((it: ActivityLog) => it.creator)
@ColumnIndex()
@MultiORMColumn({ nullable: true, relationId: true })
creatorId?: ID;
}
1 change: 1 addition & 0 deletions packages/core/src/activity-log/repository/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './mikro-orm-activity-log.repository';
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { MikroOrmBaseEntityRepository } from '../../core/repository/mikro-orm-base-entity.repository';
import { ActivityLog } from '../activity-log.entity';

export class MikroOrmActivityLogRepository extends MikroOrmBaseEntityRepository<ActivityLog> {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { ActivityLog } from '../activity-log.entity';

@Injectable()
export class TypeOrmActivityLogRepository extends Repository<ActivityLog> {
constructor(@InjectRepository(ActivityLog) readonly repository: Repository<ActivityLog>) {
super(repository.target, repository.manager, repository.queryRunner);
}
}
2 changes: 2 additions & 0 deletions packages/core/src/core/entities/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
AccountingTemplate,
Activity,
ActivityLog,
AppointmentEmployee,
ApprovalPolicy,
AvailabilitySlot,
Expand Down Expand Up @@ -143,6 +144,7 @@ import {
export const coreEntities = [
AccountingTemplate,
Activity,
ActivityLog,
AppointmentEmployee,
ApprovalPolicy,
AvailabilitySlot,
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/core/entities/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export * from './translate-base';

//core entities
export * from '../../accounting-template/accounting-template.entity';
export * from '../../activity-log/activity-log.entity';
export * from '../../appointment-employees/appointment-employees.entity';
export * from '../../approval-policy/approval-policy.entity';
export * from '../../availability-slots/availability-slots.entity';
Expand Down
Loading
Loading