267 lines
8.9 KiB
Python
267 lines
8.9 KiB
Python
|
from typing import List, Optional
|
||
|
from pydantic import BaseModel, Field, validator
|
||
|
from datetime import datetime, timezone
|
||
|
from tools.datetime import epoch_get
|
||
|
from enum import Enum
|
||
|
import json
|
||
|
|
||
|
class TaskCategory(str, Enum):
|
||
|
BUG = "bug"
|
||
|
QUESTION = "question"
|
||
|
STORY = "story"
|
||
|
TASK = "task"
|
||
|
VARIA = "varia"
|
||
|
FEATURE = "feature"
|
||
|
|
||
|
class TaskStatus(str, Enum):
|
||
|
NEW = "new"
|
||
|
PROGRESS = "progress"
|
||
|
TEST = "test"
|
||
|
DONE = "done"
|
||
|
BLOCKED = "blocked"
|
||
|
|
||
|
class TaskRemark(BaseModel):
|
||
|
remark: str
|
||
|
timestamp: int = Field(default_factory=lambda: int(datetime.now(timezone.utc).timestamp()))
|
||
|
tracked_hours: Optional[float] = None
|
||
|
assignee: Optional[int] = None
|
||
|
|
||
|
def get_formatted_timestamp(self) -> str:
|
||
|
return datetime.fromtimestamp(self.timestamp, tz=timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC')
|
||
|
|
||
|
class Task(BaseModel):
|
||
|
id: int
|
||
|
name: str
|
||
|
description: str
|
||
|
category: TaskCategory
|
||
|
status: TaskStatus = Field(default=TaskStatus.NEW)
|
||
|
priority: int = Field(default=1, ge=1, le=5)
|
||
|
created_at: int = Field(default_factory=lambda: int(datetime.now(timezone.utc).timestamp()))
|
||
|
deadline: Optional[int] = None
|
||
|
assignees: List[int] = []
|
||
|
tags: List[str] = []
|
||
|
remarks: List[TaskRemark] = []
|
||
|
estimated_hours: Optional[float] = None
|
||
|
actual_hours: Optional[float] = None
|
||
|
parent_task_id: Optional[int] = None
|
||
|
subtasks: List[int] = []
|
||
|
|
||
|
@validator('deadline')
|
||
|
def check_deadline(cls, v):
|
||
|
if v is not None and v < int(datetime.now(timezone.utc).timestamp()):
|
||
|
raise ValueError('Deadline cannot be in the past')
|
||
|
return v
|
||
|
|
||
|
def is_overdue(self) -> bool:
|
||
|
if self.deadline is None:
|
||
|
return False
|
||
|
return int(datetime.now(timezone.utc).timestamp()) > self.deadline
|
||
|
|
||
|
def get_formatted_deadline(self) -> Optional[str]:
|
||
|
if self.deadline is None:
|
||
|
return None
|
||
|
return datetime.fromtimestamp(self.deadline, tz=timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC')
|
||
|
|
||
|
def add_remark(self, remark: str, tracked_hours: Optional[float] = None, assignee: Optional[int] = None):
|
||
|
self.remarks.append(TaskRemark(remark=remark, tracked_hours=tracked_hours, assignee=assignee))
|
||
|
|
||
|
def get_total_tracked_hours(self) -> float:
|
||
|
return sum(remark.tracked_hours or 0 for remark in self.remarks)
|
||
|
|
||
|
def deadline_set(self, deadline: str):
|
||
|
"""
|
||
|
support +1h (hours), +2d (days), +1w (week), +1m (month)
|
||
|
support 20/10/2024 and 20/10 and 20/10/24 (all same day)
|
||
|
if hour not specified then midday (noon)
|
||
|
"""
|
||
|
self.deadline = epoch_get(deadline)
|
||
|
|
||
|
class TaskList(BaseModel):
|
||
|
tasks: List[Task] = []
|
||
|
|
||
|
def add_task(self, task: Task):
|
||
|
self.tasks.append(task)
|
||
|
|
||
|
def get_tasks(self):
|
||
|
return self.tasks
|
||
|
|
||
|
def get_task_by_id(self, task_id: int) -> Task:
|
||
|
task = next((task for task in self.tasks if task.id == task_id), None)
|
||
|
if task:
|
||
|
return task
|
||
|
raise Exception(f"Can't find task {task_id}")
|
||
|
|
||
|
|
||
|
def new(jsondata: str = "") -> TaskList:
|
||
|
if not jsondata:
|
||
|
return example()
|
||
|
try:
|
||
|
data = json.loads(jsondata)
|
||
|
return TaskList(**data)
|
||
|
except json.JSONDecodeError:
|
||
|
raise ValueError("Invalid JSON input")
|
||
|
except Exception as e:
|
||
|
raise ValueError(f"Error creating TaskList from JSON: {str(e)}")
|
||
|
|
||
|
return data
|
||
|
|
||
|
|
||
|
def example() -> TaskList:
|
||
|
task_list = TaskList()
|
||
|
|
||
|
# Create 10 tasks with different properties
|
||
|
tasks = [
|
||
|
Task(
|
||
|
id=1,
|
||
|
name="Implement user authentication",
|
||
|
description="Develop and integrate user authentication system",
|
||
|
category=TaskCategory.FEATURE,
|
||
|
status=TaskStatus.PROGRESS,
|
||
|
priority=4,
|
||
|
deadline=epoch_get("+5d"),
|
||
|
assignees=[101, 102],
|
||
|
tags=["security", "backend"],
|
||
|
estimated_hours=40,
|
||
|
),
|
||
|
Task(
|
||
|
id=2,
|
||
|
name="Design landing page",
|
||
|
description="Create a responsive design for the website landing page",
|
||
|
category=TaskCategory.TASK,
|
||
|
status=TaskStatus.NEW,
|
||
|
priority=3,
|
||
|
deadline=epoch_get("15/09/2023"),
|
||
|
assignees=[203],
|
||
|
tags=["frontend", "ui/ux"],
|
||
|
estimated_hours=20,
|
||
|
),
|
||
|
Task(
|
||
|
id=3,
|
||
|
name="Database optimization",
|
||
|
description="Optimize database queries for improved performance",
|
||
|
category=TaskCategory.TASK,
|
||
|
status=TaskStatus.NEW,
|
||
|
priority=2,
|
||
|
deadline=epoch_get("+2w"),
|
||
|
assignees=[104],
|
||
|
tags=["performance", "backend"],
|
||
|
estimated_hours=15,
|
||
|
),
|
||
|
Task(
|
||
|
id=4,
|
||
|
name="Write user documentation",
|
||
|
description="Create comprehensive user guide for the new features",
|
||
|
category=TaskCategory.TASK,
|
||
|
status=TaskStatus.NEW,
|
||
|
priority=1,
|
||
|
deadline=epoch_get("+1m"),
|
||
|
assignees=[305],
|
||
|
tags=["writing", "user-support"],
|
||
|
estimated_hours=25,
|
||
|
),
|
||
|
Task(
|
||
|
id=5,
|
||
|
name="Implement payment gateway",
|
||
|
description="Integrate third-party payment gateway for online transactions",
|
||
|
category=TaskCategory.FEATURE,
|
||
|
status=TaskStatus.PROGRESS,
|
||
|
priority=5,
|
||
|
deadline=epoch_get("+10d"),
|
||
|
assignees=[106, 107],
|
||
|
tags=["payment", "security", "backend"],
|
||
|
estimated_hours=50,
|
||
|
),
|
||
|
Task(
|
||
|
id=6,
|
||
|
name="Mobile app UI redesign",
|
||
|
description="Redesign the mobile app user interface for better user experience",
|
||
|
category=TaskCategory.TASK,
|
||
|
status=TaskStatus.NEW,
|
||
|
priority=3,
|
||
|
deadline=epoch_get("30/09/2023"),
|
||
|
assignees=[208],
|
||
|
tags=["mobile", "ui/ux"],
|
||
|
estimated_hours=35,
|
||
|
),
|
||
|
Task(
|
||
|
id=7,
|
||
|
name="Setup CI/CD pipeline",
|
||
|
description="Configure continuous integration and deployment pipeline",
|
||
|
category=TaskCategory.TASK,
|
||
|
status=TaskStatus.PROGRESS,
|
||
|
priority=4,
|
||
|
deadline=epoch_get("+1w"),
|
||
|
assignees=[109],
|
||
|
tags=["automation", "deployment"],
|
||
|
estimated_hours=30,
|
||
|
),
|
||
|
Task(
|
||
|
id=8,
|
||
|
name="Conduct user acceptance testing",
|
||
|
description="Organize and conduct UAT for the new features",
|
||
|
category=TaskCategory.TASK,
|
||
|
status=TaskStatus.NEW,
|
||
|
priority=2,
|
||
|
deadline=epoch_get("+3w"),
|
||
|
assignees=[210, 311],
|
||
|
tags=["testing", "user-feedback"],
|
||
|
estimated_hours=40,
|
||
|
),
|
||
|
Task(
|
||
|
id=9,
|
||
|
name="Implement data analytics dashboard",
|
||
|
description="Create a dashboard to visualize key performance metrics",
|
||
|
category=TaskCategory.FEATURE,
|
||
|
status=TaskStatus.NEW,
|
||
|
priority=3,
|
||
|
deadline=epoch_get("+20d"),
|
||
|
assignees=[112, 213],
|
||
|
tags=["analytics", "frontend", "data-visualization"],
|
||
|
estimated_hours=60,
|
||
|
),
|
||
|
Task(
|
||
|
id=10,
|
||
|
name="Optimize SEO",
|
||
|
description="Implement SEO best practices to improve search engine rankings",
|
||
|
category=TaskCategory.TASK,
|
||
|
status=TaskStatus.NEW,
|
||
|
priority=2,
|
||
|
deadline=epoch_get("31/10/2023"),
|
||
|
assignees=[314],
|
||
|
tags=["seo", "marketing"],
|
||
|
estimated_hours=25,
|
||
|
),
|
||
|
]
|
||
|
|
||
|
# Add tasks to the TaskList
|
||
|
for task in tasks:
|
||
|
task_list.add_task(task)
|
||
|
|
||
|
# Add some remarks to tasks
|
||
|
task_list.get_task_by_id(1).add_remark("Started working on user registration flow", tracked_hours=4.5, assignee=101)
|
||
|
task_list.get_task_by_id(5).add_remark("Completed integration with PayPal API", tracked_hours=8, assignee=106)
|
||
|
task_list.get_task_by_id(7).add_remark("Jenkins pipeline configured, working on Docker integration", tracked_hours=6, assignee=109)
|
||
|
|
||
|
# Print some information about the tasks
|
||
|
for task in task_list.get_tasks():
|
||
|
print(f"Task ID: {task.id}")
|
||
|
print(f"Name: {task.name}")
|
||
|
print(f"Category: {task.category.value}")
|
||
|
print(f"Status: {task.status.value}")
|
||
|
print(f"Priority: {task.priority}")
|
||
|
print(f"Deadline: {task.get_formatted_deadline()}")
|
||
|
print(f"Assignees: {task.assignees}")
|
||
|
print(f"Tags: {task.tags}")
|
||
|
print(f"Estimated hours: {task.estimated_hours}")
|
||
|
print(f"Total tracked hours: {task.get_total_tracked_hours()}")
|
||
|
print("Remarks:")
|
||
|
for remark in task.remarks:
|
||
|
print(f" - {remark.remark} (Hours: {remark.tracked_hours}, Assignee: {remark.assignee})")
|
||
|
print("---")
|
||
|
return task_list
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
|
||
|
task_list = new()
|
||
|
print(task_list)
|