Fix all integrations
This commit is contained in:
@@ -65,6 +65,10 @@ _TASK_COMPLETE_CMD_RE = re.compile(
|
||||
r"^\s*\.task\s+(?:complete|done|close)\s+#?(?P<reference>[A-Za-z0-9_-]+)\s*$",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
_TASK_ADD_CMD_RE = re.compile(
|
||||
r"^\s*\.task\s+(?:add|create|new)\s+(?P<title>.+?)\s*$",
|
||||
re.IGNORECASE | re.DOTALL,
|
||||
)
|
||||
_DUE_ISO_RE = re.compile(
|
||||
r"\b(?:due|by)\s+(\d{4}-\d{2}-\d{2})\b",
|
||||
re.IGNORECASE,
|
||||
@@ -367,6 +371,102 @@ def _next_reference(user, project) -> str:
|
||||
return str(DerivedTask.objects.filter(user=user, project=project).count() + 1)
|
||||
|
||||
|
||||
def create_task_record(
|
||||
*,
|
||||
user,
|
||||
project,
|
||||
title: str,
|
||||
source_service: str,
|
||||
source_channel: str,
|
||||
origin_message: Message | None = None,
|
||||
actor_identifier: str = "",
|
||||
epic: TaskEpic | None = None,
|
||||
due_date: datetime.date | None = None,
|
||||
assignee_identifier: str = "",
|
||||
immutable_payload: dict | None = None,
|
||||
event_payload: dict | None = None,
|
||||
status_snapshot: str = "open",
|
||||
) -> tuple[DerivedTask, DerivedTaskEvent]:
|
||||
reference = _next_reference(user, project)
|
||||
task = DerivedTask.objects.create(
|
||||
user=user,
|
||||
project=project,
|
||||
epic=epic,
|
||||
title=str(title or "").strip()[:255] or "Untitled task",
|
||||
source_service=str(source_service or "web").strip() or "web",
|
||||
source_channel=str(source_channel or "").strip(),
|
||||
origin_message=origin_message,
|
||||
reference_code=reference,
|
||||
status_snapshot=str(status_snapshot or "open").strip() or "open",
|
||||
due_date=due_date,
|
||||
assignee_identifier=str(assignee_identifier or "").strip(),
|
||||
immutable_payload=dict(immutable_payload or {}),
|
||||
)
|
||||
event = DerivedTaskEvent.objects.create(
|
||||
task=task,
|
||||
event_type="created",
|
||||
actor_identifier=str(actor_identifier or "").strip(),
|
||||
source_message=origin_message,
|
||||
payload=dict(event_payload or {}),
|
||||
)
|
||||
return task, event
|
||||
|
||||
|
||||
async def create_task_record_and_sync(
|
||||
*,
|
||||
user,
|
||||
project,
|
||||
title: str,
|
||||
source_service: str,
|
||||
source_channel: str,
|
||||
origin_message: Message | None = None,
|
||||
actor_identifier: str = "",
|
||||
epic: TaskEpic | None = None,
|
||||
due_date: datetime.date | None = None,
|
||||
assignee_identifier: str = "",
|
||||
immutable_payload: dict | None = None,
|
||||
event_payload: dict | None = None,
|
||||
status_snapshot: str = "open",
|
||||
) -> tuple[DerivedTask, DerivedTaskEvent]:
|
||||
task, event = await sync_to_async(create_task_record)(
|
||||
user=user,
|
||||
project=project,
|
||||
title=title,
|
||||
source_service=source_service,
|
||||
source_channel=source_channel,
|
||||
origin_message=origin_message,
|
||||
actor_identifier=actor_identifier,
|
||||
epic=epic,
|
||||
due_date=due_date,
|
||||
assignee_identifier=assignee_identifier,
|
||||
immutable_payload=immutable_payload,
|
||||
event_payload=event_payload,
|
||||
status_snapshot=status_snapshot,
|
||||
)
|
||||
await _emit_sync_event(task, event, "create")
|
||||
return task, event
|
||||
|
||||
|
||||
async def mark_task_completed_and_sync(
|
||||
*,
|
||||
task: DerivedTask,
|
||||
actor_identifier: str = "",
|
||||
source_message: Message | None = None,
|
||||
payload: dict | None = None,
|
||||
) -> DerivedTaskEvent:
|
||||
task.status_snapshot = "completed"
|
||||
await sync_to_async(task.save)(update_fields=["status_snapshot"])
|
||||
event = await sync_to_async(DerivedTaskEvent.objects.create)(
|
||||
task=task,
|
||||
event_type="completion_marked",
|
||||
actor_identifier=str(actor_identifier or "").strip(),
|
||||
source_message=source_message,
|
||||
payload=dict(payload or {}),
|
||||
)
|
||||
await _emit_sync_event(task, event, "complete")
|
||||
return event
|
||||
|
||||
|
||||
async def _derive_title(message: Message) -> str:
|
||||
text = str(message.text or "").strip()
|
||||
if not text:
|
||||
@@ -600,6 +700,49 @@ async def _handle_scope_task_commands(
|
||||
await _send_scope_message(source, message, "\n".join(lines))
|
||||
return True
|
||||
|
||||
create_match = _TASK_ADD_CMD_RE.match(body)
|
||||
if create_match:
|
||||
task_text = str(create_match.group("title") or "").strip()
|
||||
if not task_text:
|
||||
await _send_scope_message(
|
||||
source, message, "[task] title is required for .task add."
|
||||
)
|
||||
return True
|
||||
epic = source.epic
|
||||
epic_name = _extract_epic_name_from_text(task_text)
|
||||
if epic_name:
|
||||
epic, _ = await sync_to_async(TaskEpic.objects.get_or_create)(
|
||||
project=source.project,
|
||||
name=epic_name,
|
||||
)
|
||||
cleaned_task_text = _strip_epic_token(task_text)
|
||||
task, _event = await create_task_record_and_sync(
|
||||
user=message.user,
|
||||
project=source.project,
|
||||
epic=epic,
|
||||
title=cleaned_task_text[:255],
|
||||
source_service=source.service or message.source_service or "web",
|
||||
source_channel=source.channel_identifier or message.source_chat_id or "",
|
||||
origin_message=message,
|
||||
actor_identifier=str(message.sender_uuid or ""),
|
||||
due_date=_parse_due_date(cleaned_task_text),
|
||||
assignee_identifier=_parse_assignee(cleaned_task_text),
|
||||
immutable_payload={
|
||||
"origin_text": text,
|
||||
"task_text": cleaned_task_text,
|
||||
"source": "chat_manual_command",
|
||||
},
|
||||
event_payload={
|
||||
"origin_text": text,
|
||||
"command": ".task add",
|
||||
"via": "chat_command",
|
||||
},
|
||||
)
|
||||
await _send_scope_message(
|
||||
source, message, f"[task] created #{task.reference_code}: {task.title}"
|
||||
)
|
||||
return True
|
||||
|
||||
undo_match = _UNDO_TASK_RE.match(body)
|
||||
if undo_match:
|
||||
reference = str(undo_match.group("reference") or "").strip()
|
||||
@@ -690,11 +833,8 @@ async def _handle_scope_task_commands(
|
||||
source, message, f"[task] #{reference} not found."
|
||||
)
|
||||
return True
|
||||
task.status_snapshot = "completed"
|
||||
await sync_to_async(task.save)(update_fields=["status_snapshot"])
|
||||
event = await sync_to_async(DerivedTaskEvent.objects.create)(
|
||||
await mark_task_completed_and_sync(
|
||||
task=task,
|
||||
event_type="completion_marked",
|
||||
actor_identifier=str(message.sender_uuid or ""),
|
||||
source_message=message,
|
||||
payload={
|
||||
@@ -703,7 +843,6 @@ async def _handle_scope_task_commands(
|
||||
"via": "chat_command",
|
||||
},
|
||||
)
|
||||
await _emit_sync_event(task, event, "complete")
|
||||
await _send_scope_message(
|
||||
source, message, f"[task] completed #{task.reference_code}: {task.title}"
|
||||
)
|
||||
@@ -763,6 +902,7 @@ def _is_task_command_candidate(text: str) -> bool:
|
||||
if (
|
||||
_LIST_TASKS_RE.match(body)
|
||||
or _LIST_TASKS_CMD_RE.match(body)
|
||||
or _TASK_ADD_CMD_RE.match(body)
|
||||
or _TASK_SHOW_RE.match(body)
|
||||
or _TASK_COMPLETE_CMD_RE.match(body)
|
||||
or _UNDO_TASK_RE.match(body)
|
||||
@@ -779,6 +919,7 @@ def _is_explicit_task_command(text: str) -> bool:
|
||||
return bool(
|
||||
_LIST_TASKS_RE.match(body)
|
||||
or _LIST_TASKS_CMD_RE.match(body)
|
||||
or _TASK_ADD_CMD_RE.match(body)
|
||||
or _TASK_SHOW_RE.match(body)
|
||||
or _TASK_COMPLETE_CMD_RE.match(body)
|
||||
or _UNDO_TASK_RE.match(body)
|
||||
@@ -878,16 +1019,12 @@ async def process_inbound_task_intelligence(message: Message) -> None:
|
||||
)
|
||||
return
|
||||
|
||||
task.status_snapshot = "completed"
|
||||
await sync_to_async(task.save)(update_fields=["status_snapshot"])
|
||||
event = await sync_to_async(DerivedTaskEvent.objects.create)(
|
||||
await mark_task_completed_and_sync(
|
||||
task=task,
|
||||
event_type="completion_marked",
|
||||
actor_identifier=str(message.sender_uuid or ""),
|
||||
source_message=message,
|
||||
payload={"marker": ref_code},
|
||||
)
|
||||
await _emit_sync_event(task, event, "complete")
|
||||
return
|
||||
|
||||
for source in sources:
|
||||
@@ -913,10 +1050,9 @@ async def process_inbound_task_intelligence(message: Message) -> None:
|
||||
source_chat_id=message.source_chat_id,
|
||||
)
|
||||
title = await _derive_title_with_flags(cloned_message, flags)
|
||||
reference = await sync_to_async(_next_reference)(message.user, source.project)
|
||||
parsed_due_date = _parse_due_date(task_text)
|
||||
parsed_assignee = _parse_assignee(task_text)
|
||||
task = await sync_to_async(DerivedTask.objects.create)(
|
||||
task, event = await create_task_record_and_sync(
|
||||
user=message.user,
|
||||
project=source.project,
|
||||
epic=epic,
|
||||
@@ -924,8 +1060,7 @@ async def process_inbound_task_intelligence(message: Message) -> None:
|
||||
source_service=source.service or message.source_service or "web",
|
||||
source_channel=source.channel_identifier or message.source_chat_id or "",
|
||||
origin_message=message,
|
||||
reference_code=reference,
|
||||
status_snapshot="open",
|
||||
actor_identifier=str(message.sender_uuid or ""),
|
||||
due_date=parsed_due_date,
|
||||
assignee_identifier=parsed_assignee,
|
||||
immutable_payload={
|
||||
@@ -933,15 +1068,8 @@ async def process_inbound_task_intelligence(message: Message) -> None:
|
||||
"task_text": task_text,
|
||||
"flags": flags,
|
||||
},
|
||||
event_payload={"origin_text": text},
|
||||
)
|
||||
event = await sync_to_async(DerivedTaskEvent.objects.create)(
|
||||
task=task,
|
||||
event_type="created",
|
||||
actor_identifier=str(message.sender_uuid or ""),
|
||||
source_message=message,
|
||||
payload={"origin_text": text},
|
||||
)
|
||||
await _emit_sync_event(task, event, "create")
|
||||
if bool(flags.get("announce_task_id", False)):
|
||||
try:
|
||||
await send_message_raw(
|
||||
|
||||
Reference in New Issue
Block a user