支持验收项目空飞书链接更新

This commit is contained in:
2026-09-02 09:20:40 +08:00
parent b2fc2e01f4
commit b3e7937653
3 changed files with 59 additions and 6 deletions

View File

@@ -1807,21 +1807,47 @@ def update_acceptance_task(public_id: str, data: dict[str, Any]) -> dict[str, An
name = str(data.get("name") or "").strip() name = str(data.get("name") or "").strip()
keywords = list(dict.fromkeys(str(item).strip() for item in data.get("keywords", []) if str(item).strip())) keywords = list(dict.fromkeys(str(item).strip() for item in data.get("keywords", []) if str(item).strip()))
matches = list(dict.fromkeys(str(item).strip() for item in data.get("match_keywords", []) if str(item).strip())) matches = list(dict.fromkeys(str(item).strip() for item in data.get("match_keywords", []) if str(item).strip()))
if not name or not keywords or not matches: if not keywords or not matches:
raise TaskError("INVALID_ACCEPTANCE_PROJECT", "项目名称、搜索关键词和匹配关键词不能为空", 422) raise TaskError("INVALID_ACCEPTANCE_PROJECT", "搜索关键词和匹配关键词不能为空", 422)
links_supplied = "feishu_links" in data
links = list(dict.fromkeys(_validate_url(item) for item in data.get("feishu_links", []))) if links_supplied else []
schedule_supplied = "schedule_weekdays" in data
schedule_weekdays = _normalize_schedule_weekdays(data.get("schedule_weekdays")) if schedule_supplied else []
schedule_start = _normalize_schedule_time(data.get("schedule_start_time"), "开始时间") if schedule_supplied else ""
schedule_end = _normalize_schedule_time(data.get("schedule_end_time"), "结束时间") if schedule_supplied else ""
if schedule_supplied and schedule_start > schedule_end:
raise TaskError("INVALID_SCHEDULE_RANGE", "开始时间不能晚于结束时间", 422)
with connection() as conn: with connection() as conn:
task = conn.execute("SELECT * FROM acceptance_tasks WHERE public_task_id=?", (public_id,)).fetchone() task = conn.execute("SELECT * FROM acceptance_tasks WHERE public_task_id=?", (public_id,)).fetchone()
if not task: if not task:
raise TaskError("ACCEPTANCE_TASK_NOT_FOUND", "验收任务不存在", 404) raise TaskError("ACCEPTANCE_TASK_NOT_FOUND", "验收任务不存在", 404)
root_id = task["scheduled_from_task_id"] or task["id"] root_id = task["scheduled_from_task_id"] or task["id"]
root = conn.execute("SELECT * FROM acceptance_tasks WHERE id=?", (root_id,)).fetchone() root = conn.execute("SELECT * FROM acceptance_tasks WHERE id=?", (root_id,)).fetchone()
conn.execute("UPDATE acceptance_tasks SET name=?,updated_at=? WHERE id=?", (name, now_iso(), root_id)) resolved_name = name or f"项目验收|{keywords[0]}"
now = now_iso()
conn.execute("UPDATE acceptance_tasks SET name=?,updated_at=? WHERE id=?", (resolved_name, now, root_id))
conn.execute("DELETE FROM acceptance_keywords WHERE task_id=?", (root_id,)) conn.execute("DELETE FROM acceptance_keywords WHERE task_id=?", (root_id,))
conn.execute("DELETE FROM acceptance_match_keywords WHERE task_id=?", (root_id,)) conn.execute("DELETE FROM acceptance_match_keywords WHERE task_id=?", (root_id,))
conn.executemany("INSERT INTO acceptance_keywords(task_id,keyword_order,keyword,created_at) VALUES (?,?,?,?)", [(root_id, i, value, now_iso()) for i, value in enumerate(keywords)]) conn.executemany("INSERT INTO acceptance_keywords(task_id,keyword_order,keyword,created_at) VALUES (?,?,?,?)", [(root_id, i, value, now_iso()) for i, value in enumerate(keywords)])
conn.executemany("INSERT INTO acceptance_match_keywords(task_id,keyword_order,keyword,created_at) VALUES (?,?,?,?)", [(root_id, i, value, now_iso()) for i, value in enumerate(matches)]) conn.executemany("INSERT INTO acceptance_match_keywords(task_id,keyword_order,keyword,created_at) VALUES (?,?,?,?)", [(root_id, i, value, now_iso()) for i, value in enumerate(matches)])
if links_supplied:
conn.execute("DELETE FROM acceptance_feishu_sources WHERE task_id=?", (root_id,))
conn.executemany(
"""INSERT INTO acceptance_feishu_sources
(task_id,source_order,source_url,source_type,fetch_status,created_at,updated_at)
VALUES (?,?,?,?,'waiting',?,?)""",
[(root_id, index, url, _source_type(url), now, now) for index, url in enumerate(links)],
)
if schedule_supplied:
conn.execute(
"""UPDATE acceptance_tasks SET schedule_enabled=1,schedule_weekday=?,
schedule_weekdays=?,schedule_start_time=?,schedule_end_time=?,
next_run_at=?,schedule_error=NULL,updated_at=? WHERE id=?""",
(schedule_weekdays[0], json.dumps(schedule_weekdays), schedule_start,
schedule_end, _next_weekly_run(schedule_weekdays, schedule_start), now, root_id),
)
dashboard_key = root["dashboard_batch_key"] or root["public_task_id"] dashboard_key = root["dashboard_batch_key"] or root["public_task_id"]
sync_dashboard_acceptance_project(dashboard_key, name, keywords, matches) sync_dashboard_acceptance_project(dashboard_key, resolved_name, keywords, matches)
return get_acceptance_task(root["public_task_id"]) return get_acceptance_task(root["public_task_id"])

View File

@@ -67,10 +67,10 @@
"/api/v1/acceptance-tasks/{task_id}": { "/api/v1/acceptance-tasks/{task_id}": {
"patch": { "patch": {
"summary": "修改验收项目配置", "summary": "修改验收项目配置",
"description": "修改根项目名称、搜索关键词和匹配关键词,并同步更新相同 dashboard_batch_key 的数据看板主体和问题选项。", "description": "修改根项目名称、可选飞书链接、搜索关键词、匹配关键词和采集周期,并同步更新相同 dashboard_batch_key 的数据看板主体和问题选项。飞书链接允许传空数组。",
"tags": ["项目验收"], "tags": ["项目验收"],
"parameters": [{"$ref": "#/components/parameters/TaskId"}], "parameters": [{"$ref": "#/components/parameters/TaskId"}],
"requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["name", "keywords", "match_keywords"], "properties": {"name": {"type": "string"}, "keywords": {"type": "array", "items": {"type": "string"}}, "match_keywords": {"type": "array", "items": {"type": "string"}}}}}}}, "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["keywords", "match_keywords"], "properties": {"name": {"type": "string"}, "feishu_links": {"type": "array", "items": {"type": "string"}}, "keywords": {"type": "array", "items": {"type": "string"}}, "match_keywords": {"type": "array", "items": {"type": "string"}}, "schedule_weekdays": {"type": "array", "items": {"type": "integer", "minimum": 1, "maximum": 7}}, "schedule_start_time": {"type": "string", "example": "20:00"}, "schedule_end_time": {"type": "string", "example": "22:00"}}}}}},
"responses": {"200": {"description": "项目与数据看板配置同步成功"}} "responses": {"200": {"description": "项目与数据看板配置同步成功"}}
} }
}, },

View File

@@ -13,6 +13,7 @@ from acceptance_service import (
run_due_acceptance_schedules, run_due_acceptance_schedules,
retry_failed_acceptance_devices, retry_failed_acceptance_devices,
stop_acceptance_schedule, stop_acceptance_schedule,
update_acceptance_task,
update_acceptance_schedule, update_acceptance_schedule,
) )
from config import Config from config import Config
@@ -80,6 +81,32 @@ class AcceptanceCreationTest(unittest.TestCase):
self.assertIsNotNone(task["next_run_at"]) self.assertIsNotNone(task["next_run_at"])
self.assertEqual(task["keyword_runs"], []) self.assertEqual(task["keyword_runs"], [])
@patch("acceptance_service.threading.Thread", _ThreadWithoutStart)
@patch("acceptance_service.sync_dashboard_acceptance_project")
@patch("acceptance_service.get_wen_resources")
def test_project_update_can_clear_feishu_links_and_save_schedule(self, resources, sync_dashboard):
resources.return_value = {"cookie_count": 2, "device_count": 3, "credential_ids": ["c1", "c2"]}
task = create_acceptance_task({
"acceptance_method": "combined", "name": "原项目",
"feishu_links": ["https://example.feishu.cn/docx/project"],
"keywords": ["山姆会员卡"], "match_keywords": ["民生"],
"schedule_weekdays": [1], "schedule_start_time": "20:00",
"schedule_end_time": "21:00", "defer_initial_collection": True,
})
updated = update_acceptance_task(task["task_id"], {
"name": "", "feishu_links": [], "keywords": ["山姆回馈金"],
"match_keywords": ["民生山姆卡"], "schedule_weekdays": [2, 5],
"schedule_start_time": "19:30", "schedule_end_time": "22:00",
})
self.assertEqual(updated["name"], "项目验收|山姆回馈金")
self.assertEqual(updated["feishu_links"], [])
self.assertEqual(updated["schedule_weekdays"], [2, 5])
self.assertEqual(updated["schedule_start_time"], "19:30")
self.assertEqual(updated["schedule_end_time"], "22:00")
sync_dashboard.assert_called_once()
@patch("acceptance_service.threading.Thread", _ThreadWithoutStart) @patch("acceptance_service.threading.Thread", _ThreadWithoutStart)
@patch("acceptance_service.get_wen_resources") @patch("acceptance_service.get_wen_resources")
def test_active_acceptance_task_can_be_cancelled(self, resources): def test_active_acceptance_task_can_be_cancelled(self, resources):