feat: Add task number handling and display for AI Agent Tasks

This commit is contained in:
Nawaz Dhandala
2025-12-29 11:59:41 +00:00
parent 660c800166
commit b08174be97
4 changed files with 161 additions and 0 deletions

View File

@@ -520,4 +520,30 @@ export default class AIAgentTask extends BaseModel {
transformer: ObjectID.getDatabaseTransformer(),
})
public createdByUserId?: ObjectID = undefined;
@ColumnAccessControl({
create: [],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.ReadProjectAIAgentTask,
],
update: [],
})
@Index()
@TableColumn({
isDefaultValueColumn: false,
required: true,
type: TableColumnType.Number,
title: "Task Number",
description:
"A unique, sequential number assigned to each AI Agent Task within a project.",
})
@Column({
type: ColumnType.Number,
nullable: false,
default: 1
})
public taskNumber?: number = undefined;
}

View File

@@ -7,6 +7,10 @@ import BadDataException from "../../Types/Exception/BadDataException";
import AIAgentService from "./AIAgentService";
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
import ObjectID from "../../Types/ObjectID";
import Semaphore, { SemaphoreMutex } from "../Infrastructure/Semaphore";
import SortOrder from "../../Types/BaseDatabase/SortOrder";
import logger from "../Utils/Logger";
import OneUptimeDate from "../../Types/Date";
export class Service extends DatabaseService<Model> {
public constructor() {
@@ -25,9 +29,84 @@ export class Service extends DatabaseService<Model> {
await this.validateAgentBelongsToProject(createBy);
}
// Generate task number
if (!createBy.data.projectId) {
throw new BadDataException(
"Project ID is required to create an AI Agent Task",
);
}
const projectId: ObjectID = createBy.data.projectId;
let mutex: SemaphoreMutex | null = null;
try {
mutex = await Semaphore.lock({
key: projectId.toString(),
namespace: "AIAgentTaskService.task-create",
lockTimeout: 15000,
acquireTimeout: 20000,
});
logger.debug(
"Mutex acquired - AIAgentTaskService.task-create " +
projectId.toString() +
" at " +
OneUptimeDate.getCurrentDateAsFormattedString(),
);
} catch (err) {
logger.debug(
"Mutex acquire failed - AIAgentTaskService.task-create " +
projectId.toString() +
" at " +
OneUptimeDate.getCurrentDateAsFormattedString(),
);
logger.error(err);
}
try {
const taskNumberForThisTask: number =
(await this.getExistingTaskNumberForProject({
projectId: projectId,
})) + 1;
createBy.data.taskNumber = taskNumberForThisTask;
} finally {
if (mutex) {
await Semaphore.release(mutex);
}
}
return { createBy, carryForward: null };
}
@CaptureSpan()
public async getExistingTaskNumberForProject(data: {
projectId: ObjectID;
}): Promise<number> {
// get last task number.
const lastTask: Model | null = await this.findOneBy({
query: {
projectId: data.projectId,
},
select: {
taskNumber: true,
},
sort: {
createdAt: SortOrder.Descending,
},
props: {
isRoot: true,
},
});
if (!lastTask) {
return 0;
}
return lastTask.taskNumber ? Number(lastTask.taskNumber) : 0;
}
@CaptureSpan()
private async getDefaultAgentId(
createBy: CreateBy<Model>,

View File

@@ -61,6 +61,13 @@ const AIAgentTaskTable: FunctionComponent<AIAgentTaskTableProps> = (
props: AIAgentTaskTableProps,
): ReactElement => {
const filters: Array<Filter<AIAgentTask>> = [
{
field: {
taskNumber: true,
},
title: "Task Number",
type: FieldType.Number,
},
{
field: {
name: true,
@@ -137,6 +144,20 @@ const AIAgentTaskTable: FunctionComponent<AIAgentTaskTableProps> = (
filters={filters}
showRefreshButton={true}
columns={[
{
field: {
taskNumber: true,
},
title: "Task Number",
type: FieldType.Element,
getElement: (item: AIAgentTask): ReactElement => {
if (!item.taskNumber) {
return <>-</>;
}
return <>#{item.taskNumber}</>;
},
},
{
field: {
name: true,

View File

@@ -30,6 +30,41 @@ const AIAgentTaskViewPage: FunctionComponent<
modelType: AIAgentTask,
id: "model-detail-ai-agent-task",
fields: [
{
field: {
taskNumber: true,
},
title: "Task Number",
fieldType: FieldType.Element,
getElement: (item: AIAgentTask): ReactElement => {
if (!item.taskNumber) {
return <>-</>;
}
return (
<div className="inline-flex items-center gap-2 px-3 py-2 rounded-lg bg-gray-50 border border-gray-200">
<div className="flex items-center justify-center w-6 h-6 rounded-md bg-gray-100">
<svg
className="w-3.5 h-3.5 text-gray-500"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"
/>
</svg>
</div>
<span className="text-lg font-semibold text-gray-700">
#{item.taskNumber}
</span>
</div>
);
},
},
{
field: {
_id: true,