feat(core): Add step-based progress tracking, task metadata and enhanced TaskManager scheduling/metadata APIs
This commit is contained in:
@@ -4,6 +4,7 @@ import {
|
||||
AbstractDistributedCoordinator,
|
||||
type IDistributedTaskRequestResult,
|
||||
} from './taskbuffer.classes.distributedcoordinator.js';
|
||||
import type { ITaskMetadata, ITaskExecutionReport, IScheduledTaskInfo } from './taskbuffer.interfaces.js';
|
||||
|
||||
export interface ICronJob {
|
||||
cronString: string;
|
||||
@@ -17,7 +18,7 @@ export interface ITaskManagerConstructorOptions {
|
||||
|
||||
export class TaskManager {
|
||||
public randomId = plugins.smartunique.shortId();
|
||||
public taskMap = new plugins.lik.ObjectMap<Task>();
|
||||
public taskMap = new plugins.lik.ObjectMap<Task<any, any>>();
|
||||
private cronJobManager = new plugins.smarttime.CronManager();
|
||||
public options: ITaskManagerConstructorOptions = {
|
||||
distributedCoordinator: null,
|
||||
@@ -27,18 +28,18 @@ export class TaskManager {
|
||||
this.options = Object.assign(this.options, options);
|
||||
}
|
||||
|
||||
public getTaskByName(taskName: string): Task {
|
||||
public getTaskByName(taskName: string): Task<any, any> {
|
||||
return this.taskMap.findSync((task) => task.name === taskName);
|
||||
}
|
||||
|
||||
public addTask(task: Task): void {
|
||||
public addTask(task: Task<any, any>): void {
|
||||
if (!task.name) {
|
||||
throw new Error('Task must have a name to be added to taskManager');
|
||||
}
|
||||
this.taskMap.add(task);
|
||||
}
|
||||
|
||||
public addAndScheduleTask(task: Task, cronString: string) {
|
||||
public addAndScheduleTask(task: Task<any, any>, cronString: string) {
|
||||
this.addTask(task);
|
||||
this.scheduleTaskByName(task.name, cronString);
|
||||
}
|
||||
@@ -51,7 +52,7 @@ export class TaskManager {
|
||||
return taskToTrigger.trigger();
|
||||
}
|
||||
|
||||
public async triggerTask(task: Task) {
|
||||
public async triggerTask(task: Task<any, any>) {
|
||||
return task.trigger();
|
||||
}
|
||||
|
||||
@@ -63,7 +64,7 @@ export class TaskManager {
|
||||
this.handleTaskScheduling(taskToSchedule, cronString);
|
||||
}
|
||||
|
||||
private handleTaskScheduling(task: Task, cronString: string) {
|
||||
private handleTaskScheduling(task: Task<any, any>, cronString: string) {
|
||||
const cronJob = this.cronJobManager.addCronjob(
|
||||
cronString,
|
||||
async (triggerTime: number) => {
|
||||
@@ -86,7 +87,7 @@ export class TaskManager {
|
||||
task.cronJob = cronJob;
|
||||
}
|
||||
|
||||
private logTaskState(task: Task) {
|
||||
private logTaskState(task: Task<any, any>) {
|
||||
console.log(`Taskbuffer schedule triggered task >>${task.name}<<`);
|
||||
const bufferState = task.buffered
|
||||
? `buffered with max ${task.bufferMax} buffered calls`
|
||||
@@ -95,7 +96,7 @@ export class TaskManager {
|
||||
}
|
||||
|
||||
private async performDistributedConsultation(
|
||||
task: Task,
|
||||
task: Task<any, any>,
|
||||
triggerTime: number,
|
||||
): Promise<IDistributedTaskRequestResult> {
|
||||
console.log('Found a distributed coordinator, performing consultation.');
|
||||
@@ -123,7 +124,7 @@ export class TaskManager {
|
||||
}
|
||||
}
|
||||
|
||||
public async descheduleTask(task: Task) {
|
||||
public async descheduleTask(task: Task<any, any>) {
|
||||
await this.descheduleTaskByName(task.name);
|
||||
}
|
||||
|
||||
@@ -145,4 +146,123 @@ export class TaskManager {
|
||||
await this.options.distributedCoordinator.stop();
|
||||
}
|
||||
}
|
||||
|
||||
// Get metadata for a specific task
|
||||
public getTaskMetadata(taskName: string): ITaskMetadata | null {
|
||||
const task = this.getTaskByName(taskName);
|
||||
if (!task) return null;
|
||||
return task.getMetadata();
|
||||
}
|
||||
|
||||
// Get metadata for all tasks
|
||||
public getAllTasksMetadata(): ITaskMetadata[] {
|
||||
return this.taskMap.getArray().map(task => task.getMetadata());
|
||||
}
|
||||
|
||||
// Get scheduled tasks with their schedules and next run times
|
||||
public getScheduledTasks(): IScheduledTaskInfo[] {
|
||||
const scheduledTasks: IScheduledTaskInfo[] = [];
|
||||
|
||||
for (const task of this.taskMap.getArray()) {
|
||||
if (task.cronJob) {
|
||||
scheduledTasks.push({
|
||||
name: task.name || 'unnamed',
|
||||
schedule: task.cronJob.cronExpression,
|
||||
nextRun: new Date(task.cronJob.getNextExecutionTime()),
|
||||
lastRun: task.lastRun,
|
||||
steps: task.getStepsMetadata?.(),
|
||||
metadata: task.getMetadata(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return scheduledTasks;
|
||||
}
|
||||
|
||||
// Get next scheduled runs across all tasks
|
||||
public getNextScheduledRuns(limit: number = 10): Array<{ taskName: string; nextRun: Date; schedule: string }> {
|
||||
const scheduledRuns = this.getScheduledTasks()
|
||||
.map(task => ({
|
||||
taskName: task.name,
|
||||
nextRun: task.nextRun,
|
||||
schedule: task.schedule,
|
||||
}))
|
||||
.sort((a, b) => a.nextRun.getTime() - b.nextRun.getTime())
|
||||
.slice(0, limit);
|
||||
|
||||
return scheduledRuns;
|
||||
}
|
||||
|
||||
// Add, execute, and remove a task while collecting metadata
|
||||
public async addExecuteRemoveTask<T, TSteps extends ReadonlyArray<{ name: string; description: string; percentage: number }>>(
|
||||
task: Task<T, TSteps>,
|
||||
options?: {
|
||||
schedule?: string;
|
||||
trackProgress?: boolean;
|
||||
}
|
||||
): Promise<ITaskExecutionReport> {
|
||||
// Add task to manager
|
||||
this.addTask(task);
|
||||
|
||||
// Optionally schedule it
|
||||
if (options?.schedule) {
|
||||
this.scheduleTaskByName(task.name!, options.schedule);
|
||||
}
|
||||
|
||||
const startTime = Date.now();
|
||||
const progressUpdates: Array<{ stepName: string; timestamp: number }> = [];
|
||||
|
||||
try {
|
||||
// Execute the task
|
||||
const result = await task.trigger();
|
||||
|
||||
// Collect execution report
|
||||
const report: ITaskExecutionReport = {
|
||||
taskName: task.name || 'unnamed',
|
||||
startTime,
|
||||
endTime: Date.now(),
|
||||
duration: Date.now() - startTime,
|
||||
steps: task.getStepsMetadata(),
|
||||
stepsCompleted: task.getStepsMetadata()
|
||||
.filter(step => step.status === 'completed')
|
||||
.map(step => step.name),
|
||||
progress: task.getProgress(),
|
||||
result,
|
||||
};
|
||||
|
||||
// Remove task from manager
|
||||
this.taskMap.remove(task);
|
||||
|
||||
// Deschedule if it was scheduled
|
||||
if (options?.schedule && task.name) {
|
||||
this.descheduleTaskByName(task.name);
|
||||
}
|
||||
|
||||
return report;
|
||||
} catch (error) {
|
||||
// Create error report
|
||||
const errorReport: ITaskExecutionReport = {
|
||||
taskName: task.name || 'unnamed',
|
||||
startTime,
|
||||
endTime: Date.now(),
|
||||
duration: Date.now() - startTime,
|
||||
steps: task.getStepsMetadata(),
|
||||
stepsCompleted: task.getStepsMetadata()
|
||||
.filter(step => step.status === 'completed')
|
||||
.map(step => step.name),
|
||||
progress: task.getProgress(),
|
||||
error: error as Error,
|
||||
};
|
||||
|
||||
// Remove task from manager even on error
|
||||
this.taskMap.remove(task);
|
||||
|
||||
// Deschedule if it was scheduled
|
||||
if (options?.schedule && task.name) {
|
||||
this.descheduleTaskByName(task.name);
|
||||
}
|
||||
|
||||
throw errorReport;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user