95 lines
3.3 KiB
Python
95 lines
3.3 KiB
Python
"""Query ClickUp 'to do' tasks tagged feb26 in OPT/LINKS/Content categories."""
|
|
|
|
import sys
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
|
|
sys.stdout.reconfigure(line_buffering=True)
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
|
|
from cheddahbot.config import load_config
|
|
from cheddahbot.clickup import ClickUpClient
|
|
|
|
CATEGORY_PREFIXES = ("opt", "link", "content", "ai content")
|
|
TAG_FILTER = "feb26"
|
|
|
|
|
|
def ms_to_date(ms_str: str) -> str:
|
|
if not ms_str:
|
|
return "—"
|
|
try:
|
|
ts = int(ms_str) / 1000
|
|
return datetime.fromtimestamp(ts, tz=timezone.utc).strftime("%m/%d")
|
|
except (ValueError, OSError):
|
|
return "—"
|
|
|
|
|
|
def main():
|
|
cfg = load_config()
|
|
if not cfg.clickup.api_token or not cfg.clickup.space_id:
|
|
print("ERROR: CLICKUP_API_TOKEN or CLICKUP_SPACE_ID not set.")
|
|
return
|
|
|
|
client = ClickUpClient(
|
|
api_token=cfg.clickup.api_token,
|
|
workspace_id=cfg.clickup.workspace_id,
|
|
task_type_field_name=cfg.clickup.task_type_field_name,
|
|
)
|
|
|
|
try:
|
|
# Fetch all 'to do' tasks across the space
|
|
tasks = client.get_tasks_from_space(cfg.clickup.space_id, statuses=["to do"])
|
|
|
|
# Filter by feb26 tag
|
|
tagged = [t for t in tasks if TAG_FILTER in [tag.lower() for tag in t.tags]]
|
|
|
|
if not tagged:
|
|
all_tags = set()
|
|
for t in tasks:
|
|
all_tags.update(t.tags)
|
|
print(f"No tasks with tag '{TAG_FILTER}'. Tags seen: {sorted(all_tags)}")
|
|
print(f"Total 'to do' tasks found: {len(tasks)}")
|
|
return
|
|
|
|
# Filter to OPT/LINKS/Content categories (by task name, Work Category, or list name)
|
|
def is_target_category(t):
|
|
name_lower = t.name.lower().strip()
|
|
wc = (t.custom_fields.get("Work Category") or "").lower()
|
|
ln = (t.list_name or "").lower()
|
|
for prefix in CATEGORY_PREFIXES:
|
|
if name_lower.startswith(prefix) or prefix in wc or prefix in ln:
|
|
return True
|
|
return False
|
|
|
|
filtered = [t for t in tagged if is_target_category(t)]
|
|
skipped = [t for t in tagged if not is_target_category(t)]
|
|
|
|
# Sort by due date (oldest first), tasks with no due date go last
|
|
filtered.sort(key=lambda t: int(t.due_date) if t.due_date else float("inf"))
|
|
|
|
top = filtered[:10]
|
|
|
|
# Build table
|
|
print(f"feb26-tagged 'to do' tasks — OPT / LINKS / Content (top 10, oldest first)")
|
|
print(f"\n{'#':>2} | {'ID':<11} | {'Keyword/Name':<50} | {'Due':<6} | {'Customer':<25} | Tags")
|
|
print("-" * 120)
|
|
for i, t in enumerate(top, 1):
|
|
customer = t.custom_fields.get("Customer", "") or "—"
|
|
due = ms_to_date(t.due_date)
|
|
tags = ", ".join(t.tags)
|
|
name = t.name[:50]
|
|
print(f"{i:>2} | {t.id:<11} | {name:<50} | {due:<6} | {customer:<25} | {tags}")
|
|
|
|
print(f"\nShowing {len(top)} of {len(filtered)} OPT/LINKS/Content tasks ({len(tagged)} total feb26-tagged).")
|
|
if skipped:
|
|
print(f"\nSkipped {len(skipped)} non-OPT/LINKS/Content tasks:")
|
|
for t in skipped:
|
|
print(f" - {t.name} ({t.id})")
|
|
|
|
finally:
|
|
client.close()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|