refactor(core): Cache workflow ownership (#6738)
* refactor: Set up ownership service * refactor: Specify cache keys and values * refactor: Replace util with service calls * test: Mock service in tests * refactor: Use dependency injection * test: Write tests * refactor: Apply feedback from Omar and Micha * test: Fix tests * test: Fix missing spot * refactor: Return user entity from cache * refactor: More dependency injection!
This commit is contained in:
@@ -9,6 +9,10 @@ import { LoggerProxy, jsonStringify } from 'n8n-workflow';
|
||||
|
||||
@Service()
|
||||
export class CacheService {
|
||||
/**
|
||||
* Keys and values:
|
||||
* - `'cache:workflow-owner:${workflowId}'`: `User`
|
||||
*/
|
||||
private cache: RedisCache | MemoryCache | undefined;
|
||||
|
||||
async init() {
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import { EventEmitter } from 'events';
|
||||
import { Service } from 'typedi';
|
||||
import Container, { Service } from 'typedi';
|
||||
import type { INode, IRun, IWorkflowBase } from 'n8n-workflow';
|
||||
import { LoggerProxy } from 'n8n-workflow';
|
||||
import { StatisticsNames } from '@db/entities/WorkflowStatistics';
|
||||
import { WorkflowStatisticsRepository } from '@db/repositories';
|
||||
import { getWorkflowOwner } from '@/UserManagement/UserManagementHelper';
|
||||
import { UserService } from '@/user/user.service';
|
||||
import { OwnershipService } from './ownership.service';
|
||||
|
||||
@Service()
|
||||
export class EventsService extends EventEmitter {
|
||||
constructor(private repository: WorkflowStatisticsRepository) {
|
||||
constructor(
|
||||
private repository: WorkflowStatisticsRepository,
|
||||
private ownershipService: OwnershipService,
|
||||
) {
|
||||
super({ captureRejections: true });
|
||||
if ('SKIP_STATISTICS_EVENTS' in process.env) return;
|
||||
|
||||
@@ -41,7 +44,7 @@ export class EventsService extends EventEmitter {
|
||||
const upsertResult = await this.repository.upsertWorkflowStatistics(name, workflowId);
|
||||
|
||||
if (name === 'production_success' && upsertResult === 'insert') {
|
||||
const owner = await getWorkflowOwner(workflowId);
|
||||
const owner = await Container.get(OwnershipService).getWorkflowOwnerCached(workflowId);
|
||||
const metrics = {
|
||||
user_id: owner.id,
|
||||
workflow_id: workflowId,
|
||||
@@ -72,7 +75,7 @@ export class EventsService extends EventEmitter {
|
||||
if (insertResult === 'failed') return;
|
||||
|
||||
// Compile the metrics since this was a new data loaded event
|
||||
const owner = await getWorkflowOwner(workflowId);
|
||||
const owner = await this.ownershipService.getWorkflowOwnerCached(workflowId);
|
||||
|
||||
let metrics = {
|
||||
user_id: owner.id,
|
||||
|
||||
36
packages/cli/src/services/ownership.service.ts
Normal file
36
packages/cli/src/services/ownership.service.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Service } from 'typedi';
|
||||
import { CacheService } from './cache.service';
|
||||
import { RoleRepository, SharedWorkflowRepository, UserRepository } from '@/databases/repositories';
|
||||
import type { User } from '@/databases/entities/User';
|
||||
|
||||
@Service()
|
||||
export class OwnershipService {
|
||||
constructor(
|
||||
private cacheService: CacheService,
|
||||
private userRepository: UserRepository,
|
||||
private roleRepository: RoleRepository,
|
||||
private sharedWorkflowRepository: SharedWorkflowRepository,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieve the user who owns the workflow. Note that workflow ownership is **immutable**.
|
||||
*/
|
||||
async getWorkflowOwnerCached(workflowId: string) {
|
||||
const cachedValue = await this.cacheService.get<User>(`cache:workflow-owner:${workflowId}`);
|
||||
|
||||
if (cachedValue) return this.userRepository.create(cachedValue);
|
||||
|
||||
const workflowOwnerRole = await this.roleRepository.findWorkflowOwnerRole();
|
||||
|
||||
if (!workflowOwnerRole) throw new Error('Failed to find workflow owner role');
|
||||
|
||||
const sharedWorkflow = await this.sharedWorkflowRepository.findOneOrFail({
|
||||
where: { workflowId, roleId: workflowOwnerRole.id },
|
||||
relations: ['user', 'user.globalRole'],
|
||||
});
|
||||
|
||||
void this.cacheService.set(`cache:workflow-owner:${workflowId}`, sharedWorkflow.user);
|
||||
|
||||
return sharedWorkflow.user;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user