Enhance ClickUp task creation, smart field setting, and Cora distribution comments
- Add set_custom_field_smart() to auto-resolve dropdown option UUIDs - Extend create_task with priority, assignees, time_estimate params - Expand clickup_create_task tool and CLI script with tags, due dates, custom fields - Add _comment_distributed_tasks to post clear ClickUp comments on Cora distribution (e.g. "Cora XLSX moved to cora-inbox" / "content-cora-inbox") - Remove unused _find_all_todo_tasks; simplify AutoCora sibling matching - Add tests for set_custom_field_smart dropdown and text fields Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>fix/customer-field-migration
parent
0d5450fb65
commit
fb4498b978
|
|
@ -93,6 +93,8 @@ uv add --group test <package>
|
|||
| `identity/SOUL.md` | Agent personality |
|
||||
| `identity/USER.md` | User profile |
|
||||
| `skills/` | Markdown skill files with YAML frontmatter |
|
||||
| `scripts/create_clickup_task.py` | CLI script to create ClickUp tasks |
|
||||
| `docs/clickup-task-creation.md` | Task creation conventions, per-type fields, and defaults |
|
||||
|
||||
## Conventions
|
||||
|
||||
|
|
|
|||
|
|
@ -456,6 +456,50 @@ class ClickUpClient:
|
|||
log.error("Failed to set field '%s' on task %s: %s", field_name, task_id, e)
|
||||
return False
|
||||
|
||||
def set_custom_field_smart(
|
||||
self, task_id: str, list_id: str, field_name: str, value: str
|
||||
) -> bool:
|
||||
"""Set a custom field by name, auto-resolving dropdown option UUIDs.
|
||||
|
||||
For dropdown fields, *value* is matched against option names
|
||||
(case-insensitive). For all other field types, *value* is passed through.
|
||||
"""
|
||||
try:
|
||||
fields = self.get_custom_fields(list_id)
|
||||
target = None
|
||||
for f in fields:
|
||||
if f.get("name") == field_name:
|
||||
target = f
|
||||
break
|
||||
|
||||
if not target:
|
||||
log.warning("Field '%s' not found in list %s", field_name, list_id)
|
||||
return False
|
||||
|
||||
field_id = target["id"]
|
||||
resolved = value
|
||||
|
||||
if target.get("type") == "drop_down":
|
||||
options = target.get("type_config", {}).get("options", [])
|
||||
for opt in options:
|
||||
if opt.get("name", "").lower() == value.lower():
|
||||
resolved = opt["id"]
|
||||
break
|
||||
else:
|
||||
log.warning(
|
||||
"Dropdown option '%s' not found for field '%s'",
|
||||
value,
|
||||
field_name,
|
||||
)
|
||||
return False
|
||||
|
||||
return self.set_custom_field_value(task_id, field_id, resolved)
|
||||
except Exception as e:
|
||||
log.error(
|
||||
"Failed to set field '%s' on task %s: %s", field_name, task_id, e
|
||||
)
|
||||
return False
|
||||
|
||||
def get_custom_field_by_name(self, task_id: str, field_name: str) -> Any:
|
||||
"""Read a custom field value from a task by field name.
|
||||
|
||||
|
|
@ -478,6 +522,9 @@ class ClickUpClient:
|
|||
due_date: int | None = None,
|
||||
tags: list[str] | None = None,
|
||||
custom_fields: list[dict] | None = None,
|
||||
priority: int | None = None,
|
||||
assignees: list[int] | None = None,
|
||||
time_estimate: int | None = None,
|
||||
) -> dict:
|
||||
"""Create a new task in a ClickUp list.
|
||||
|
||||
|
|
@ -489,6 +536,9 @@ class ClickUpClient:
|
|||
due_date: Due date as Unix timestamp in milliseconds.
|
||||
tags: List of tag names to apply.
|
||||
custom_fields: List of custom field dicts ({"id": ..., "value": ...}).
|
||||
priority: 1=Urgent, 2=High, 3=Normal, 4=Low.
|
||||
assignees: List of ClickUp user IDs.
|
||||
time_estimate: Time estimate in milliseconds.
|
||||
|
||||
Returns:
|
||||
API response dict containing task id, url, etc.
|
||||
|
|
@ -502,6 +552,12 @@ class ClickUpClient:
|
|||
payload["tags"] = tags
|
||||
if custom_fields:
|
||||
payload["custom_fields"] = custom_fields
|
||||
if priority is not None:
|
||||
payload["priority"] = priority
|
||||
if assignees:
|
||||
payload["assignees"] = assignees
|
||||
if time_estimate is not None:
|
||||
payload["time_estimate"] = time_estimate
|
||||
|
||||
def _call():
|
||||
resp = self._client.post(f"/list/{list_id}/task", json=payload)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
import shutil
|
||||
|
|
@ -1146,6 +1147,72 @@ class Scheduler:
|
|||
category="autocora",
|
||||
)
|
||||
|
||||
# Post ClickUp comment on matched tasks
|
||||
self._comment_distributed_tasks(client, xlsx_path.stem, copied_to, tasks, stem)
|
||||
|
||||
def _comment_distributed_tasks(
|
||||
self, client, xlsx_stem: str, copied_to: list[str], tasks, normalized_stem: str
|
||||
):
|
||||
"""Post a ClickUp comment on tasks when a Cora report is distributed."""
|
||||
from .tools.autocora import _slugify
|
||||
from .tools.linkbuilding import _fuzzy_keyword_match, _normalize_for_match
|
||||
|
||||
parts = []
|
||||
for dest in copied_to:
|
||||
if dest.startswith("link"):
|
||||
parts.append("cora-inbox")
|
||||
elif dest.startswith("content"):
|
||||
parts.append("content-cora-inbox")
|
||||
else:
|
||||
parts.append(dest)
|
||||
dest_label = " and ".join(parts)
|
||||
comment = f"Cora XLSX moved to {dest_label}."
|
||||
|
||||
# Try to find task_ids from job JSON files
|
||||
task_ids: list[str] = []
|
||||
jobs_dir = Path(self.config.autocora.jobs_dir)
|
||||
slug = _slugify(xlsx_stem)
|
||||
|
||||
if jobs_dir.is_dir():
|
||||
# Check both jobs/ root and processed/ subfolder
|
||||
search_dirs = [jobs_dir]
|
||||
processed = jobs_dir / "processed"
|
||||
if processed.is_dir():
|
||||
search_dirs.append(processed)
|
||||
|
||||
for search_dir in search_dirs:
|
||||
for job_file in search_dir.glob("job-*.json"):
|
||||
# Strip "job-{timestamp}-" prefix to get the slug
|
||||
parts = job_file.stem.split("-", 2)
|
||||
if len(parts) >= 3:
|
||||
job_slug = parts[2]
|
||||
if job_slug == slug:
|
||||
try:
|
||||
data = json.loads(job_file.read_text(encoding="utf-8"))
|
||||
task_ids = data.get("task_ids", [])
|
||||
except (json.JSONDecodeError, OSError) as e:
|
||||
log.warning("Could not read job file %s: %s", job_file, e)
|
||||
break
|
||||
if task_ids:
|
||||
break
|
||||
|
||||
# Fallback: match from the task list we already have
|
||||
if not task_ids:
|
||||
for task in tasks:
|
||||
keyword = task.custom_fields.get("Keyword", "")
|
||||
if not keyword:
|
||||
continue
|
||||
keyword_norm = _normalize_for_match(str(keyword))
|
||||
if _fuzzy_keyword_match(normalized_stem, keyword_norm):
|
||||
task_ids.append(task.id)
|
||||
|
||||
# Post comments
|
||||
for tid in task_ids:
|
||||
try:
|
||||
client.add_comment(tid, comment)
|
||||
except Exception as e:
|
||||
log.warning("Failed to comment on task %s: %s", tid, e)
|
||||
|
||||
# ── Morning Briefing ──
|
||||
|
||||
_CENTRAL = ZoneInfo("America/Chicago")
|
||||
|
|
|
|||
|
|
@ -217,18 +217,6 @@ def _is_lookahead(task, today_end_ms, lookahead_end_ms) -> bool:
|
|||
return False
|
||||
|
||||
|
||||
def _find_all_todo_tasks(client, config, categories: list[str]):
|
||||
"""Find ALL 'to do' tasks in cora_categories (no date filter).
|
||||
|
||||
Used to find sibling tasks sharing the same keyword.
|
||||
"""
|
||||
space_id = config.clickup.space_id
|
||||
if not space_id:
|
||||
return []
|
||||
|
||||
tasks = client.get_tasks_from_space(space_id, statuses=["to do"])
|
||||
return [t for t in tasks if t.task_type in categories]
|
||||
|
||||
|
||||
def _group_by_keyword(tasks, all_tasks):
|
||||
"""Group tasks by normalized keyword, pulling in sibling tasks from all_tasks.
|
||||
|
|
@ -324,11 +312,8 @@ def submit_autocora_jobs(target_date: str = "", ctx: dict | None = None) -> str:
|
|||
if not qualifying:
|
||||
return f"No qualifying tasks found ({label})."
|
||||
|
||||
# Find ALL to-do tasks in cora categories for sibling keyword matching
|
||||
all_todo = _find_all_todo_tasks(client, config, autocora.cora_categories)
|
||||
|
||||
# Group by keyword
|
||||
groups, alerts = _group_by_keyword(qualifying, all_todo)
|
||||
# Group by keyword — only siblings that also passed the sweep qualify
|
||||
groups, alerts = _group_by_keyword(qualifying, qualifying)
|
||||
|
||||
if not groups and alerts:
|
||||
return "No jobs submitted.\n\n" + "\n".join(f"- {a}" for a in alerts)
|
||||
|
|
|
|||
|
|
@ -163,7 +163,9 @@ def clickup_task_status(task_id: str, ctx: dict | None = None) -> str:
|
|||
@tool(
|
||||
"clickup_create_task",
|
||||
"Create a new ClickUp task for a client. Requires task name and client name. "
|
||||
"Optionally set work category, description, and status. "
|
||||
"Optionally set work category, description, status, due_date (Unix ms), "
|
||||
"tags (comma-separated), and arbitrary custom fields via custom_fields_json "
|
||||
'(JSON object like {"Keyword":"value","CLIFlags":"--tier1-count 5"}). '
|
||||
"The task is created in the 'Overall' list within the client's folder.",
|
||||
category="clickup",
|
||||
)
|
||||
|
|
@ -173,9 +175,17 @@ def clickup_create_task(
|
|||
work_category: str = "",
|
||||
description: str = "",
|
||||
status: str = "to do",
|
||||
due_date: str = "",
|
||||
tags: str = "",
|
||||
custom_fields_json: str = "",
|
||||
priority: int = 2,
|
||||
assignee: int = 10765627,
|
||||
time_estimate_ms: int = 0,
|
||||
ctx: dict | None = None,
|
||||
) -> str:
|
||||
"""Create a new ClickUp task in the client's Overall list."""
|
||||
import json as _json
|
||||
|
||||
client_obj = _get_clickup_client(ctx)
|
||||
if not client_obj:
|
||||
return "Error: ClickUp API token not configured."
|
||||
|
|
@ -188,29 +198,48 @@ def clickup_create_task(
|
|||
# Find the client's Overall list
|
||||
list_id = client_obj.find_list_in_folder(cfg.space_id, client)
|
||||
if not list_id:
|
||||
return f"Error: Could not find folder '{client}' with an 'Overall' list in space."
|
||||
return (
|
||||
f"Error: Could not find folder '{client}' "
|
||||
f"with an 'Overall' list in space."
|
||||
)
|
||||
|
||||
# Build create kwargs
|
||||
create_kwargs: dict = {
|
||||
"list_id": list_id,
|
||||
"name": name,
|
||||
"description": description,
|
||||
"status": status,
|
||||
"priority": priority,
|
||||
"assignees": [assignee],
|
||||
}
|
||||
if due_date:
|
||||
create_kwargs["due_date"] = int(due_date)
|
||||
if tags:
|
||||
create_kwargs["tags"] = [t.strip() for t in tags.split(",")]
|
||||
if time_estimate_ms:
|
||||
create_kwargs["time_estimate"] = time_estimate_ms
|
||||
|
||||
# Create the task
|
||||
result = client_obj.create_task(
|
||||
list_id=list_id,
|
||||
name=name,
|
||||
description=description,
|
||||
status=status,
|
||||
)
|
||||
result = client_obj.create_task(**create_kwargs)
|
||||
task_id = result.get("id", "")
|
||||
task_url = result.get("url", "")
|
||||
|
||||
# Set Client dropdown field
|
||||
client_obj.set_custom_field_by_name(task_id, "Client", client)
|
||||
client_obj.set_custom_field_smart(task_id, list_id, "Client", client)
|
||||
|
||||
# Set Work Category if provided
|
||||
if work_category:
|
||||
field_info = client_obj.discover_field_filter(list_id, "Work Category")
|
||||
if field_info and work_category in field_info["options"]:
|
||||
option_id = field_info["options"][work_category]
|
||||
client_obj.set_custom_field_value(task_id, field_info["field_id"], option_id)
|
||||
else:
|
||||
log.warning("Work Category '%s' not found in dropdown options", work_category)
|
||||
client_obj.set_custom_field_smart(
|
||||
task_id, list_id, "Work Category", work_category
|
||||
)
|
||||
|
||||
# Set any additional custom fields
|
||||
if custom_fields_json:
|
||||
extra_fields = _json.loads(custom_fields_json)
|
||||
for field_name, field_value in extra_fields.items():
|
||||
client_obj.set_custom_field_smart(
|
||||
task_id, list_id, field_name, str(field_value)
|
||||
)
|
||||
|
||||
return (
|
||||
f"Task created successfully!\n"
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@
|
|||
|
||||
Usage:
|
||||
uv run python scripts/create_clickup_task.py --name "Task" --client "Client"
|
||||
uv run python scripts/create_clickup_task.py --name "PR" --client "Acme" \\
|
||||
--category "Press Release"
|
||||
uv run python scripts/create_clickup_task.py --name "LB" --client "Acme" \\
|
||||
--category "Link Building" --due-date 2026-03-11 --tag feb26 \\
|
||||
--field "Keyword=some keyword" --field "CLIFlags=--tier1-count 5"
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
|
@ -12,6 +13,7 @@ import argparse
|
|||
import json
|
||||
import os
|
||||
import sys
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
|
||||
# Add project root to path so we can import cheddahbot
|
||||
|
|
@ -21,16 +23,80 @@ from dotenv import load_dotenv
|
|||
|
||||
from cheddahbot.clickup import ClickUpClient
|
||||
|
||||
DEFAULT_ASSIGNEE = 10765627 # Bryan Bigari
|
||||
|
||||
|
||||
def _date_to_unix_ms(date_str: str) -> int:
|
||||
"""Convert YYYY-MM-DD to Unix milliseconds (noon UTC).
|
||||
|
||||
Noon UTC ensures the date displays correctly in US timezones.
|
||||
"""
|
||||
dt = datetime.strptime(date_str, "%Y-%m-%d").replace(
|
||||
hour=12, tzinfo=UTC
|
||||
)
|
||||
return int(dt.timestamp() * 1000)
|
||||
|
||||
|
||||
def _parse_time_estimate(s: str) -> int:
|
||||
"""Parse a human time string like '2h', '30m', '1h30m' to ms."""
|
||||
import re
|
||||
|
||||
total_min = 0
|
||||
match = re.match(r"(?:(\d+)h)?(?:(\d+)m)?$", s.strip())
|
||||
if not match or not any(match.groups()):
|
||||
raise ValueError(f"Invalid time estimate: '{s}' (use e.g. '2h', '30m', '1h30m')")
|
||||
if match.group(1):
|
||||
total_min += int(match.group(1)) * 60
|
||||
if match.group(2):
|
||||
total_min += int(match.group(2))
|
||||
return total_min * 60 * 1000
|
||||
|
||||
|
||||
def main():
|
||||
load_dotenv()
|
||||
|
||||
parser = argparse.ArgumentParser(description="Create a ClickUp task")
|
||||
parser.add_argument("--name", required=True, help="Task name")
|
||||
parser.add_argument("--client", required=True, help="Client folder name in ClickUp")
|
||||
parser.add_argument("--category", default="", help="Work Category (e.g. 'Press Release')")
|
||||
parser.add_argument(
|
||||
"--client", required=True, help="Client folder name"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--category", default="", help="Work Category dropdown value"
|
||||
)
|
||||
parser.add_argument("--description", default="", help="Task description")
|
||||
parser.add_argument("--status", default="to do", help="Initial status (default: 'to do')")
|
||||
parser.add_argument(
|
||||
"--status", default="to do", help="Initial status (default: 'to do')"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--due-date", default="", help="Due date as YYYY-MM-DD"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--tag", action="append", default=[], help="Tag (mmmYY, repeatable)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--field",
|
||||
action="append",
|
||||
default=[],
|
||||
help="Custom field as Name=Value (repeatable)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--priority",
|
||||
type=int,
|
||||
default=2,
|
||||
help="Priority: 1=Urgent, 2=High, 3=Normal, 4=Low (default: 2)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--assignee",
|
||||
type=int,
|
||||
action="append",
|
||||
default=[],
|
||||
help="ClickUp user ID (default: Bryan 10765627)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--time-estimate",
|
||||
default="",
|
||||
help="Time estimate (e.g. '2h', '30m', '1h30m')",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
api_token = os.environ.get("CLICKUP_API_TOKEN", "")
|
||||
|
|
@ -43,6 +109,15 @@ def main():
|
|||
print("Error: CLICKUP_SPACE_ID not set", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Parse custom fields
|
||||
custom_fields: dict[str, str] = {}
|
||||
for f in args.field:
|
||||
if "=" not in f:
|
||||
print(f"Error: --field must be Name=Value, got: {f}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
name, value = f.split("=", 1)
|
||||
custom_fields[name] = value
|
||||
|
||||
client = ClickUpClient(api_token=api_token)
|
||||
try:
|
||||
# Find the client's Overall list
|
||||
|
|
@ -52,27 +127,47 @@ def main():
|
|||
print(msg, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Build create_task kwargs
|
||||
create_kwargs: dict = {
|
||||
"list_id": list_id,
|
||||
"name": args.name,
|
||||
"description": args.description,
|
||||
"status": args.status,
|
||||
}
|
||||
if args.due_date:
|
||||
create_kwargs["due_date"] = _date_to_unix_ms(args.due_date)
|
||||
if args.tag:
|
||||
create_kwargs["tags"] = args.tag
|
||||
create_kwargs["priority"] = args.priority
|
||||
create_kwargs["assignees"] = args.assignee or [DEFAULT_ASSIGNEE]
|
||||
if args.time_estimate:
|
||||
create_kwargs["time_estimate"] = _parse_time_estimate(
|
||||
args.time_estimate
|
||||
)
|
||||
|
||||
# Create the task
|
||||
result = client.create_task(
|
||||
list_id=list_id,
|
||||
name=args.name,
|
||||
description=args.description,
|
||||
status=args.status,
|
||||
)
|
||||
result = client.create_task(**create_kwargs)
|
||||
task_id = result.get("id", "")
|
||||
|
||||
# Set Client dropdown field
|
||||
client.set_custom_field_by_name(task_id, "Client", args.client)
|
||||
client.set_custom_field_smart(task_id, list_id, "Client", args.client)
|
||||
|
||||
# Set Work Category if provided
|
||||
if args.category:
|
||||
field_info = client.discover_field_filter(list_id, "Work Category")
|
||||
if field_info and args.category in field_info["options"]:
|
||||
option_id = field_info["options"][args.category]
|
||||
client.set_custom_field_value(task_id, field_info["field_id"], option_id)
|
||||
else:
|
||||
msg = f"Warning: Work Category '{args.category}' not found"
|
||||
print(msg, file=sys.stderr)
|
||||
client.set_custom_field_smart(
|
||||
task_id, list_id, "Work Category", args.category
|
||||
)
|
||||
|
||||
# Set any additional custom fields
|
||||
for field_name, field_value in custom_fields.items():
|
||||
ok = client.set_custom_field_smart(
|
||||
task_id, list_id, field_name, field_value
|
||||
)
|
||||
if not ok:
|
||||
print(
|
||||
f"Warning: Failed to set '{field_name}'",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
print(json.dumps({
|
||||
"id": task_id,
|
||||
|
|
|
|||
|
|
@ -204,9 +204,7 @@ class TestSubmitAutocoraJobs:
|
|||
monkeypatch.setattr(
|
||||
"cheddahbot.tools.autocora._find_qualifying_tasks", lambda *a, **kw: [task]
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"cheddahbot.tools.autocora._find_all_todo_tasks", lambda *a, **kw: [task]
|
||||
)
|
||||
|
||||
|
||||
result = submit_autocora_jobs(target_date="2025-01-01", ctx=ctx)
|
||||
assert "Submitted 1 job" in result
|
||||
|
|
@ -233,9 +231,7 @@ class TestSubmitAutocoraJobs:
|
|||
monkeypatch.setattr(
|
||||
"cheddahbot.tools.autocora._find_qualifying_tasks", lambda *a, **kw: [task]
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"cheddahbot.tools.autocora._find_all_todo_tasks", lambda *a, **kw: [task]
|
||||
)
|
||||
|
||||
|
||||
submit_autocora_jobs(target_date="2025-01-01", ctx=ctx)
|
||||
|
||||
|
|
@ -256,9 +252,7 @@ class TestSubmitAutocoraJobs:
|
|||
monkeypatch.setattr(
|
||||
"cheddahbot.tools.autocora._find_qualifying_tasks", lambda *a, **kw: [task]
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"cheddahbot.tools.autocora._find_all_todo_tasks", lambda *a, **kw: [task]
|
||||
)
|
||||
|
||||
|
||||
# First submit
|
||||
submit_autocora_jobs(target_date="2025-01-01", ctx=ctx)
|
||||
|
|
@ -278,9 +272,7 @@ class TestSubmitAutocoraJobs:
|
|||
monkeypatch.setattr(
|
||||
"cheddahbot.tools.autocora._find_qualifying_tasks", lambda *a, **kw: [task]
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"cheddahbot.tools.autocora._find_all_todo_tasks", lambda *a, **kw: [task]
|
||||
)
|
||||
|
||||
|
||||
result = submit_autocora_jobs(target_date="2025-01-01", ctx=ctx)
|
||||
assert "missing Keyword" in result
|
||||
|
|
@ -296,9 +288,7 @@ class TestSubmitAutocoraJobs:
|
|||
monkeypatch.setattr(
|
||||
"cheddahbot.tools.autocora._find_qualifying_tasks", lambda *a, **kw: [task]
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"cheddahbot.tools.autocora._find_all_todo_tasks", lambda *a, **kw: [task]
|
||||
)
|
||||
|
||||
|
||||
result = submit_autocora_jobs(target_date="2025-01-01", ctx=ctx)
|
||||
assert "Submitted 1 job" in result
|
||||
|
|
|
|||
|
|
@ -569,3 +569,73 @@ class TestClickUpClient:
|
|||
result = client.find_list_in_folder("space_1", "NonExistent Client")
|
||||
assert result is None
|
||||
client.close()
|
||||
|
||||
@respx.mock
|
||||
def test_set_custom_field_smart_dropdown(self):
|
||||
"""Resolves dropdown option name to UUID automatically."""
|
||||
respx.get(f"{BASE_URL}/list/list_1/field").mock(
|
||||
return_value=httpx.Response(
|
||||
200,
|
||||
json={
|
||||
"fields": [
|
||||
{
|
||||
"id": "cf_lb",
|
||||
"name": "LB Method",
|
||||
"type": "drop_down",
|
||||
"type_config": {
|
||||
"options": [
|
||||
{"id": "opt_cora", "name": "Cora Backlinks"},
|
||||
{"id": "opt_manual", "name": "Manual"},
|
||||
]
|
||||
},
|
||||
},
|
||||
]
|
||||
},
|
||||
)
|
||||
)
|
||||
respx.post(f"{BASE_URL}/task/t1/field/cf_lb").mock(
|
||||
return_value=httpx.Response(200, json={})
|
||||
)
|
||||
|
||||
client = ClickUpClient(api_token="pk_test")
|
||||
result = client.set_custom_field_smart(
|
||||
"t1", "list_1", "LB Method", "Cora Backlinks"
|
||||
)
|
||||
assert result is True
|
||||
import json
|
||||
|
||||
body = json.loads(respx.calls.last.request.content)
|
||||
assert body["value"] == "opt_cora"
|
||||
client.close()
|
||||
|
||||
@respx.mock
|
||||
def test_set_custom_field_smart_text(self):
|
||||
"""Passes text field values through without resolution."""
|
||||
respx.get(f"{BASE_URL}/list/list_1/field").mock(
|
||||
return_value=httpx.Response(
|
||||
200,
|
||||
json={
|
||||
"fields": [
|
||||
{
|
||||
"id": "cf_kw",
|
||||
"name": "Keyword",
|
||||
"type": "short_text",
|
||||
},
|
||||
]
|
||||
},
|
||||
)
|
||||
)
|
||||
respx.post(f"{BASE_URL}/task/t1/field/cf_kw").mock(
|
||||
return_value=httpx.Response(200, json={})
|
||||
)
|
||||
|
||||
client = ClickUpClient(api_token="pk_test")
|
||||
result = client.set_custom_field_smart(
|
||||
"t1", "list_1", "Keyword", "shaft manufacturing"
|
||||
)
|
||||
assert result is True
|
||||
import json
|
||||
|
||||
body = json.loads(respx.calls.last.request.content)
|
||||
assert body["value"] == "shaft manufacturing"
|
||||
client.close()
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ from cheddahbot.config import AutoCoraConfig, Config, ContentConfig, LinkBuildin
|
|||
class FakeTask:
|
||||
"""Minimal ClickUp task stub for distribution tests."""
|
||||
|
||||
id: str = "fake_id"
|
||||
name: str = ""
|
||||
task_type: str = ""
|
||||
custom_fields: dict = field(default_factory=dict)
|
||||
|
|
|
|||
Loading…
Reference in New Issue