mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 00:32:12 +02:00
feat: Add task number handling and display for AI Agent Tasks
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user