121 lines
4.0 KiB
Python
121 lines
4.0 KiB
Python
"""Query ClickUp 'to do' tasks tagged feb26 in OPT/LINKS/Content categories."""
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
from datetime import datetime, timezone
|
|
|
|
# Add project root to path
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
|
|
from cheddahbot.config import load_config
|
|
from cheddahbot.clickup import ClickUpClient
|
|
|
|
|
|
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 "—"
|
|
|
|
|
|
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,
|
|
)
|
|
|
|
# Step 1: Get folders, find OPT/LINKS/Content
|
|
target_folders = {"opt", "links", "content"}
|
|
try:
|
|
folders = client.get_folders(cfg.clickup.space_id)
|
|
except Exception as e:
|
|
print(f"ERROR fetching folders: {e}")
|
|
client.close()
|
|
return
|
|
|
|
print(f"All folders: {[f['name'] for f in folders]}")
|
|
|
|
matched_lists = [] # (list_id, list_name, folder_name)
|
|
for folder in folders:
|
|
if folder["name"].lower() in target_folders:
|
|
for lst in folder["lists"]:
|
|
matched_lists.append((lst["id"], lst["name"], folder["name"]))
|
|
|
|
if not matched_lists:
|
|
print(f"No folders matching {target_folders}. Falling back to full space scan.")
|
|
try:
|
|
tasks = client.get_tasks_from_space(cfg.clickup.space_id, statuses=["to do"])
|
|
finally:
|
|
client.close()
|
|
else:
|
|
print(f"Querying lists: {[(ln, fn) for _, ln, fn in matched_lists]}")
|
|
tasks = []
|
|
for list_id, list_name, folder_name in matched_lists:
|
|
try:
|
|
batch = client.get_tasks(list_id, statuses=["to do"])
|
|
# Stash folder name on each task for display
|
|
for t in batch:
|
|
t._folder = folder_name
|
|
tasks.extend(batch)
|
|
except Exception as e:
|
|
print(f" Error fetching {list_name}: {e}")
|
|
client.close()
|
|
|
|
print(f"Total 'to do' tasks from target folders: {len(tasks)}")
|
|
|
|
# Filter by "feb26" tag (case-insensitive)
|
|
tagged = [t for t in tasks if any(tag.lower() == "feb26" for tag in t.tags)]
|
|
|
|
if not tagged:
|
|
print(f"No 'to do' tasks with 'feb26' tag found.")
|
|
all_tags = set()
|
|
for t in tasks:
|
|
all_tags.update(t.tags)
|
|
print(f"Tags found across all to-do tasks: {sorted(all_tags)}")
|
|
return
|
|
|
|
filtered = tagged
|
|
|
|
# Sort by due date (oldest first), tasks without due date go last
|
|
def sort_key(t):
|
|
if t.due_date:
|
|
return (0, int(t.due_date))
|
|
return (1, 0)
|
|
|
|
filtered.sort(key=sort_key)
|
|
|
|
# Take top 10
|
|
top10 = filtered[:10]
|
|
|
|
# Build table
|
|
print(f"\n## ClickUp 'to do' — feb26 tag — OPT/LINKS/Content ({len(filtered)} total, showing top 10)\n")
|
|
print(f"{'#':<3} | {'ID':<12} | {'Keyword/Name':<40} | {'Due':<12} | {'Customer':<20} | Tags")
|
|
print(f"{'—'*3} | {'—'*12} | {'—'*40} | {'—'*12} | {'—'*20} | {'—'*15}")
|
|
|
|
for i, t in enumerate(top10, 1):
|
|
customer = t.custom_fields.get("Customer", "") or "—"
|
|
due = ms_to_date(t.due_date)
|
|
tags = ", ".join(t.tags) if t.tags else "—"
|
|
name = t.name[:38] + ".." if len(t.name) > 40 else t.name
|
|
print(f"{i:<3} | {t.id:<12} | {name:<40} | {due:<12} | {customer:<20} | {tags}")
|
|
|
|
print(f"\nCategory breakdown:")
|
|
from collections import Counter
|
|
cats = Counter(t.task_type for t in filtered)
|
|
for cat, count in cats.most_common():
|
|
print(f" {cat or '(none)'}: {count}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|