103 lines
3.1 KiB
Python
103 lines
3.1 KiB
Python
"""Query ClickUp 'to do' tasks tagged 'feb26' in OPT/LINKS/Content categories."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import sys
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
|
|
_root = Path(__file__).resolve().parent.parent
|
|
sys.path.insert(0, str(_root))
|
|
|
|
from dotenv import load_dotenv
|
|
load_dotenv(_root / ".env")
|
|
|
|
from cheddahbot.clickup import ClickUpClient
|
|
|
|
API_TOKEN = os.environ.get("CLICKUP_API_TOKEN", "")
|
|
SPACE_ID = os.environ.get("CLICKUP_SPACE_ID", "")
|
|
|
|
if not API_TOKEN:
|
|
sys.exit("ERROR: CLICKUP_API_TOKEN env var is required")
|
|
if not SPACE_ID:
|
|
sys.exit("ERROR: CLICKUP_SPACE_ID env var is required")
|
|
|
|
# Work Category values to include (case-insensitive partial match)
|
|
CATEGORY_FILTERS = ["opt", "link", "content"]
|
|
TAG_FILTER = "feb26"
|
|
|
|
|
|
def ms_to_date(ms_str: str) -> str:
|
|
"""Convert Unix-ms timestamp string to YYYY-MM-DD."""
|
|
if not ms_str:
|
|
return "—"
|
|
try:
|
|
ts = int(ms_str) / 1000
|
|
return datetime.fromtimestamp(ts, tz=timezone.utc).strftime("%Y-%m-%d")
|
|
except (ValueError, OSError):
|
|
return ms_str
|
|
|
|
|
|
def main() -> None:
|
|
client = ClickUpClient(api_token=API_TOKEN, task_type_field_name="Work Category")
|
|
|
|
print(f"Fetching 'to do' tasks from space {SPACE_ID} ...")
|
|
tasks = client.get_tasks_from_overall_lists(SPACE_ID, statuses=["to do"])
|
|
print(f"Total 'to do' tasks: {len(tasks)}")
|
|
|
|
# Filter by feb26 tag
|
|
tagged = [t for t in tasks if TAG_FILTER in [tag.lower() for tag in t.tags]]
|
|
print(f"Tasks with '{TAG_FILTER}' tag: {len(tagged)}")
|
|
|
|
# Show all Work Category values for debugging
|
|
categories = set()
|
|
for t in tagged:
|
|
wc = t.custom_fields.get("Work Category", "") or ""
|
|
categories.add(wc)
|
|
print(f"Work Categories found: {categories}")
|
|
|
|
# Filter by OPT/LINKS/Content categories
|
|
filtered = []
|
|
for t in tagged:
|
|
wc = str(t.custom_fields.get("Work Category", "") or "").lower()
|
|
if any(cat in wc for cat in CATEGORY_FILTERS):
|
|
filtered.append(t)
|
|
|
|
print(f"After category filter (OPT/LINKS/Content): {len(filtered)}")
|
|
|
|
# Sort by due date (oldest first), tasks with no due date go last
|
|
def sort_key(t):
|
|
if t.due_date:
|
|
try:
|
|
return (0, int(t.due_date))
|
|
except ValueError:
|
|
return (1, 0)
|
|
return (2, 0)
|
|
|
|
filtered.sort(key=sort_key)
|
|
|
|
# Top 10
|
|
top10 = filtered[:10]
|
|
|
|
# Print table
|
|
print(f"\n{'#':>3} | {'ID':>11} | {'Keyword/Name':<45} | {'Due':>10} | {'Customer':<20} | Tags")
|
|
print("-" * 120)
|
|
|
|
for i, t in enumerate(top10, 1):
|
|
customer = t.custom_fields.get("Customer", "") or "—"
|
|
due = ms_to_date(t.due_date)
|
|
wc = t.custom_fields.get("Work Category", "") or ""
|
|
tags_str = ", ".join(t.tags)
|
|
name_display = t.name[:45] if len(t.name) > 45 else t.name
|
|
print(f"{i:>3} | {t.id:>11} | {name_display:<45} | {due:>10} | {customer:<20} | {tags_str}")
|
|
|
|
if not top10:
|
|
print(" (no matching tasks found)")
|
|
|
|
print(f"\n--- {len(filtered)} total matching tasks, showing top {len(top10)} (oldest first) ---")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|