Skip to main content

Work Item Model

The WorkItem model represents tasks and work items synced from Plane.so, supporting hierarchical relationships and comprehensive task management.

Schema Definition​

model WorkItem {
id String @id @default(uuid())
planeId String @unique
name String?
description String?
state String?
priority String?
assignees String[]
labels String[]
dueDate DateTime? @map("due_date")
startDate DateTime? @map("start_date")
estimatePoint Int? @map("estimate_point")
parentId String? @map("parent_id")
projectId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
sequenceId Int?
parent WorkItem? @relation("SubWorkItems", fields: [parentId], references: [id])
subWorkItems WorkItem[] @relation("SubWorkItems")
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)

@@map("work_items")
}

Field Descriptions​

Primary Identification​

FieldTypeDescription
idStringUUID primary key
planeIdStringUnique Plane.so work item identifier

Basic Information​

FieldTypeDescription
nameString?Work item title/name
descriptionString?Detailed description
stateString?Current state (todo, in_progress, done, cancelled)
priorityString?Priority level (urgent, high, medium, low)

Assignment and Organization​

FieldTypeDescription
assigneesString[]Array of assignee identifiers
labelsString[]Array of label names

Scheduling​

FieldTypeDescription
dueDateDateTime?Due date for completion
startDateDateTime?Planned start date

Estimation​

FieldTypeDescription
estimatePointInt?Story points or time estimate

Hierarchy​

FieldTypeDescription
parentIdString?Reference to parent work item (for subtasks)
sequenceIdInt?Order within parent or project

Relations​

FieldTypeDescription
projectIdStringReference to Project.id

Metadata​

FieldTypeDescription
createdAtDateTimeWork item sync timestamp
updatedAtDateTimeLast update timestamp

Relationships​

Belongs To​

  • project: Project - The project this work item belongs to (Cascade delete)
  • parent: WorkItem? - Parent work item (for hierarchical tasks)

One-to-Many​

  • subWorkItems: WorkItem[] - Child work items (subtasks)

Usage Examples​

Create Synced Work Item​

const workItem = await prisma.workItem.create({
data: {
planeId: "plane-issue-123",
name: "Implement user authentication",
description: "Add login and registration functionality",
state: "in_progress",
priority: "high",
assignees: ["user-456", "user-789"],
labels: ["authentication", "frontend"],
dueDate: new Date("2024-02-01"),
estimatePoint: 8,
projectId: "project-123"
}
});

Create Subtask​

const subtask = await prisma.workItem.create({
data: {
planeId: "plane-issue-124",
name: "Create login form",
description: "Design and implement login UI",
state: "todo",
priority: "medium",
parentId: "work-item-123", // Reference to parent
projectId: "project-123"
}
});

Update Work Item Status​

await prisma.workItem.update({
where: { planeId: "plane-issue-123" },
data: {
state: "done",
updatedAt: new Date()
}
});

Get Work Items with Hierarchy​

const workItems = await prisma.workItem.findMany({
where: { projectId: "project-123" },
include: {
subWorkItems: {
orderBy: { sequenceId: "asc" }
},
parent: true
}
});

Get Overdue Items​

const overdueItems = await prisma.workItem.findMany({
where: {
dueDate: {
lt: new Date()
},
state: {
not: "done"
}
}
});

Hierarchical Queries​

Get Root Level Items​

const rootItems = await prisma.workItem.findMany({
where: {
projectId: "project-123",
parentId: null // No parent = root level
},
orderBy: { sequenceId: "asc" }
});

Get Complete Hierarchy​

async function getWorkItemHierarchy(projectId: string) {
const allItems = await prisma.workItem.findMany({
where: { projectId },
include: {
subWorkItems: {
orderBy: { sequenceId: "asc" }
}
},
orderBy: { sequenceId: "asc" }
});

// Build tree structure
const itemMap = new Map();
const rootItems = [];

allItems.forEach(item => {
itemMap.set(item.id, { ...item, children: [] });
});

allItems.forEach(item => {
if (item.parentId) {
const parent = itemMap.get(item.parentId);
if (parent) {
parent.children.push(itemMap.get(item.id));
}
} else {
rootItems.push(itemMap.get(item.id));
}
});

return rootItems;
}

Sync Operations​

Incremental Sync​

async function syncWorkItems(projectId: string, since?: Date) {
const project = await prisma.project.findUnique({
where: { id: projectId },
include: { integration: true }
});

const query = since ? `?updated_after=${since.toISOString()}` : '';
const workItems = await fetchPlaneWorkItems(project.integration, project.planeId, query);

for (const item of workItems) {
await prisma.workItem.upsert({
where: { planeId: item.id },
update: {
name: item.name,
description: item.description,
state: item.state,
priority: item.priority,
assignees: item.assignees || [],
labels: item.labels || [],
dueDate: item.due_date ? new Date(item.due_date) : null,
startDate: item.start_date ? new Date(item.start_date) : null,
estimatePoint: item.estimate_point,
parentId: item.parent_id,
sequenceId: item.sequence_id,
updatedAt: new Date()
},
create: {
planeId: item.id,
name: item.name,
description: item.description,
state: item.state,
priority: item.priority,
assignees: item.assignees || [],
labels: item.labels || [],
dueDate: item.due_date ? new Date(item.due_date) : null,
startDate: item.start_date ? new Date(item.start_date) : null,
estimatePoint: item.estimate_point,
parentId: item.parent_id,
projectId: projectId,
sequenceId: item.sequence_id
}
});
}
}

Analytics and Reporting​

Work Item Statistics​

async function getWorkItemStats(projectId: string) {
const stats = await prisma.workItem.groupBy({
by: ['state', 'priority'],
where: { projectId },
_count: true
});

const overdueCount = await prisma.workItem.count({
where: {
projectId,
dueDate: { lt: new Date() },
state: { not: 'done' }
}
});

return {
byState: stats.reduce((acc, stat) => {
acc[stat.state] = stat._count;
return acc;
}, {}),
byPriority: stats.reduce((acc, stat) => {
acc[stat.priority] = (acc[stat.priority] || 0) + stat._count;
return acc;
}, {}),
overdue: overdueCount
};
}

Burndown Data​

async function getBurndownData(projectId: string, startDate: Date, endDate: Date) {
const workItems = await prisma.workItem.findMany({
where: {
projectId,
createdAt: {
gte: startDate,
lte: endDate
}
},
select: {
createdAt: true,
updatedAt: true,
state: true,
estimatePoint: true
}
});

// Process data for burndown chart
// Implementation depends on reporting requirements
}

Performance Considerations​

Indexing​

  • Unique index on planeId for sync operations
  • Index on projectId for project filtering
  • Index on parentId for hierarchy queries
  • Index on state for status filtering
  • Index on dueDate for deadline queries
  • Index on updatedAt for incremental sync

Query Optimization​

  • Use select to limit returned fields
  • Include relations only when needed
  • Use pagination for large result sets
  • Cache frequently accessed work items

Bulk Operations​

// Bulk status update
await prisma.workItem.updateMany({
where: {
projectId: "project-123",
state: "todo"
},
data: {
state: "in_progress"
}
});

Best Practices​

Data Consistency​

  • Always sync through Plane API
  • Validate data before saving
  • Handle circular references in hierarchy
  • Implement proper error handling

Hierarchy Management​

  • Limit hierarchy depth (max 3-4 levels)
  • Maintain sequence IDs for ordering
  • Handle orphaned items gracefully
  • Implement move operations carefully

Performance​

  • Use batch operations for bulk updates
  • Implement pagination for large lists
  • Cache hierarchy structures
  • Monitor query performance

Security​

  • Respect Plane permissions
  • Filter items based on user access
  • Log access for auditing
  • Implement rate limiting