Files
agentic-soc-platform/PLUGINS/SIRP/nocolyapi.py
T
2026-04-23 16:29:34 +08:00

553 lines
21 KiB
Python

from __future__ import annotations
import time
from typing import Dict
from typing import List, Any
import requests
from requests.adapters import HTTPAdapter
from Lib.log import logger
from Lib.xcache import Xcache
from PLUGINS.SIRP.CONFIG import SIRP_URL, SIRP_APPKEY, SIRP_SIGN
from PLUGINS.SIRP.nocolymodel import FieldType, OptionType
# SESSION
HEADERS = {"HAP-Appkey": SIRP_APPKEY,
"HAP-Sign": SIRP_SIGN}
SIRP_REQUEST_TIMEOUT = 10 # seconds
HTTP_SESSION = requests.Session()
HTTP_SESSION.headers.update(HEADERS)
HTTP_SESSION.verify = False
adapter = HTTPAdapter(
pool_connections=10,
pool_maxsize=10
)
HTTP_SESSION.mount('http://', adapter)
HTTP_SESSION.mount('https://', adapter)
def _request_with_timing(method: str, url: str, **kwargs):
started_at = time.perf_counter()
try:
response = HTTP_SESSION.request(method=method, url=url, **kwargs)
elapsed_ms = (time.perf_counter() - started_at) * 1000
logger.debug(
f"[SIRP] request method={method} url={url} status={response.status_code} elapsed_ms={elapsed_ms:.2f}"
)
return response
except Exception as exc:
elapsed_ms = (time.perf_counter() - started_at) * 1000
logger.debug(
f"[SIRP] request failed method={method} url={url} elapsed_ms={elapsed_ms:.2f} error={exc}"
)
raise
SYSTEM_FIELDS = ['_initiatedBy', '_owner', '_updatedAt', '_createdAt', '_remainingTime', '_createdBy', '_updatedBy', '_processName', '_nodeAssignees',
'_initiatedAt', '_nodeStartedAt', '_approvalCompletedAt', '_dueAt', '_processStatus']
SYSTEM_FIELDS_ROW = ['row_initiatedBy', 'row_owner', 'row_updatedAt', 'row_createdAt', 'row_remainingTime', 'row_createdBy', 'row_updatedBy', 'row_processName',
'row_nodeAssignees',
'row_initiatedAt', 'row_nodeStartedAt', 'row_approvalCompletedAt', 'row_dueAt', 'row_processStatus']
class Worksheet(object):
def __init__(self):
pass
@staticmethod
def get_fields(worksheet_id: str) -> Dict[str, FieldType]:
cached_fields = Xcache.get_sirp_fields(worksheet_id)
if cached_fields is not None:
return cached_fields
url = f"{SIRP_URL}/api/v3/app/worksheets/{worksheet_id}"
response = _request_with_timing(
"GET",
url
)
response.raise_for_status()
response_data = response.json()
if response_data.get("success"):
fields_list: List[FieldType] = response_data.get("data").get("fields")
fields_dict = {}
for field in fields_list:
fields_dict[field["alias"]] = field
Xcache.set_sirp_fields(worksheet_id, fields_dict)
return fields_dict
else:
raise Exception(f"error_code: {response_data.get('error_code')} error_msg: {response_data.get('error_msg')}")
class WorksheetRow(object):
def __init__(self):
pass
@staticmethod
def _format_input_row(row, fields_config, include_system_fields=True) -> dict:
data_new = {}
for alias_key_in_row in row:
if alias_key_in_row == "rowId": # rowId 特殊处理
data_new["row_id"] = row[alias_key_in_row]
elif alias_key_in_row in SYSTEM_FIELDS:
if include_system_fields:
data_new[f"row{alias_key_in_row}"] = row[alias_key_in_row]
else:
continue
else:
field_config = fields_config.get(alias_key_in_row)
if field_config is None:
logger.warning(f"field {alias_key_in_row} not found in fields")
for alias_key_in_field_config in fields_config:
if fields_config[alias_key_in_field_config]['id'] == alias_key_in_row:
logger.warning(f"error field is '{fields_config[alias_key_in_field_config]['name']}'")
logger.warning(f"row data : {row}")
continue
data_new[alias_key_in_row] = WorksheetRow._format_input_value(field_config, row[alias_key_in_row])
return data_new
@staticmethod
def _format_input_value(field, value):
field_type = field.get("type")
sub_type = field.get("subType")
if field_type in ["MultipleSelect"]:
value_list = []
for option in value:
value_list.append(option.get("value"))
return value_list
elif field_type in ['SingleSelect', "Dropdown"]:
if len(value) > 0:
return value[0].get("value")
else:
return None
elif field_type in ['Relation']:
if sub_type == 1:
value_list = []
for option in value:
value_list.append(option.get("sid"))
return value_list
else:
return value
elif field_type in ['Checkbox']:
return bool(int(value))
else:
return value
@staticmethod
def _format_output_value(fields_config, fields):
fields_new = []
for field in fields:
if field.get("id") == "row_id":
field["id"] = "rowId"
elif field.get("id") in SYSTEM_FIELDS_ROW:
field["id"] = field["id"][3:]
field_config = fields_config.get(field.get("id"))
if field_config is None:
raise Exception(f"field {field.get("id")} not found in fields_config")
field_type = field_config.get("type")
sub_type = field_config.get("subType")
value = field.get("value")
if field_type in ['Checkbox']:
field["value"] = 1 if value else 0
fields_new.append(field)
return fields_new
@staticmethod
def _translate_filter_names_to_ids(filter_data, fields_config):
if filter_data.get("type") == "group":
for child in filter_data.get("children", []):
WorksheetRow._translate_filter_names_to_ids(child, fields_config)
elif filter_data.get("type") == "condition":
if filter_data["field"] == "row_id":
filter_data["field"] = "rowId"
if filter_data.get("operator") == "in" and isinstance(filter_data.get("value"), list):
field_key = filter_data.get("field")
target_field_config = fields_config.get(field_key)
if not target_field_config:
for f in fields_config.values():
if f.get("id") == field_key:
target_field_config = f
break
if target_field_config and target_field_config.get("options"):
value_to_key = {opt["value"]: opt["key"] for opt in target_field_config["options"]}
filter_data["value"] = [value_to_key.get(v, v) for v in filter_data["value"]]
@staticmethod
def get(worksheet_id: str, row_id: str, include_system_fields=True) -> dict:
url = f"{SIRP_URL}/api/v3/app/worksheets/{worksheet_id}/rows/{row_id}"
fields = Worksheet.get_fields(worksheet_id)
response = _request_with_timing(
"GET",
url,
timeout=SIRP_REQUEST_TIMEOUT,
params={"includeSystemFields": include_system_fields}
)
response.raise_for_status()
response_data = response.json()
if response_data.get("success"):
row = response_data.get("data")
data_new = WorksheetRow._format_input_row(row, fields, include_system_fields)
return data_new
else:
raise Exception(f"error_code: {response_data.get('error_code')} error_msg: {response_data.get('error_msg')}")
@staticmethod
def list(worksheet_id: str, filter: dict, fields=[], include_system_fields=True) -> List:
url = f"{SIRP_URL}/api/v3/app/worksheets/{worksheet_id}/rows/list"
all_rows = []
page_index = 1
page_size = 1000
fields_config = Worksheet.get_fields(worksheet_id)
WorksheetRow._translate_filter_names_to_ids(filter, fields_config)
fields = [t.replace("row_id", "rowId") for t in fields]
while True:
data = {
"filter": filter,
"fields": fields,
"sorts": [
{
"field": "utime",
"isAsc": False
}
],
"includeTotalCount": True,
"pageSize": page_size,
"pageIndex": page_index
}
response = _request_with_timing("POST",
url,
timeout=SIRP_REQUEST_TIMEOUT,
headers=HEADERS,
json=data)
response.raise_for_status()
response_data = response.json()
if not response_data.get("success"):
raise Exception(f"error_code: {response_data.get('error_code')} error_msg: {response_data.get('error_msg')}")
result_data = response_data.get("data")
rows = result_data.get("rows")
total_count = result_data.get("total", 0)
if not rows:
break
for row in rows:
formatted_row = WorksheetRow._format_input_row(row, fields_config, include_system_fields)
all_rows.append(formatted_row)
if len(all_rows) >= total_count:
break
page_index += 1
return all_rows
@staticmethod
def create(worksheet_id: str, fields: List, trigger_workflow: bool = True):
url = f"{SIRP_URL}/api/v3/app/worksheets/{worksheet_id}/rows"
fields_config = Worksheet.get_fields(worksheet_id)
fields = WorksheetRow._format_output_value(fields_config, fields)
data = {
"triggerWorkflow": trigger_workflow,
"fields": fields
}
try:
response = _request_with_timing("POST",
url,
timeout=SIRP_REQUEST_TIMEOUT,
json=data)
response.raise_for_status()
response_data = response.json()
if response_data.get("success"):
return response_data.get("data").get("id")
else:
raise Exception(f"error_code: {response_data.get('error_code')} error_msg: {response_data.get('error_msg')} data: {response_data.get('data')}")
except Exception as e:
raise e
@staticmethod
def update(worksheet_id: str, row_id: str, fields: List, trigger_workflow: bool = True):
fields_config = Worksheet.get_fields(worksheet_id)
fields = WorksheetRow._format_output_value(fields_config, fields)
url = f"{SIRP_URL}/api/v3/app/worksheets/{worksheet_id}/rows/{row_id}"
data = {
"triggerWorkflow": trigger_workflow,
"fields": fields
}
response = _request_with_timing("PATCH",
url,
timeout=SIRP_REQUEST_TIMEOUT,
json=data)
response.raise_for_status()
response_data = response.json()
if response_data.get("success"):
return response_data.get("data")
else:
raise Exception(f"error_code: {response_data.get('error_code')} error_msg: {response_data.get('error_msg')}")
@staticmethod
def batch_create(worksheet_id: str,
rows: List[List[Dict]],
trigger_workflow: bool = True) -> Dict:
url = f"{SIRP_URL}/api/v3/app/worksheets/{worksheet_id}/rows/batch"
fields_config = Worksheet.get_fields(worksheet_id)
formatted_rows = []
for row_fields in rows:
formatted_fields = WorksheetRow._format_output_value(fields_config, row_fields)
formatted_rows.append({"fields": formatted_fields})
data = {
"rows": formatted_rows,
"triggerWorkflow": trigger_workflow
}
try:
response = _request_with_timing("POST",
url,
timeout=SIRP_REQUEST_TIMEOUT,
json=data)
response.raise_for_status()
response_data = response.json()
if response_data.get("success"):
return response_data.get("data") # {"rowIds":[row_id1,row_id2]}
else:
raise Exception(f"error_code: {response_data.get('error_code')} error_msg: {response_data.get('error_msg')} data: {response_data.get('data')}")
except Exception as e:
raise e
@staticmethod
def batch_update(worksheet_id: str,
row_ids: List[str],
fields: List[Dict],
trigger_workflow: bool = True) -> Dict:
url = f"{SIRP_URL}/api/v3/app/worksheets/{worksheet_id}/rows/batch"
fields_config = Worksheet.get_fields(worksheet_id)
fields = WorksheetRow._format_output_value(fields_config, fields)
data = {
"rowIds": row_ids,
"fields": fields,
"triggerWorkflow": trigger_workflow
}
try:
response = _request_with_timing("PATCH",
url,
timeout=SIRP_REQUEST_TIMEOUT,
json=data)
response.raise_for_status()
response_data = response.json()
if response_data.get("success"):
return response_data.get("data") # {"failedRowIds":[row_id1,row_id2], "successfulRowIds":[row_id3,row_id4]}
else:
raise Exception(f"error_code: {response_data.get('error_code')} error_msg: {response_data.get('error_msg')} data: {response_data.get('data')}")
except Exception as e:
raise e
@staticmethod
def delete(worksheet_id: str, row_id: List, trigger_workflow: bool = True):
url = f"{SIRP_URL}/api/v3/app/worksheets/{worksheet_id}/rows/{row_id}"
data = {
"triggerWorkflow": trigger_workflow,
}
response = _request_with_timing("DELETE",
url,
timeout=SIRP_REQUEST_TIMEOUT,
json=data)
response.raise_for_status()
response_data = response.json()
if response_data.get("success"):
return response_data.get("data")
else:
raise Exception(f"error_code: {response_data.get('error_code')} error_msg: {response_data.get('error_msg')}")
@staticmethod
def batch_delete(worksheet_id: str, row_ids: List, trigger_workflow: bool = True):
url = f"{SIRP_URL}/api/v3/app/worksheets/{worksheet_id}/rows/batch"
data = {
"rowIds": row_ids,
"triggerWorkflow": trigger_workflow,
}
response = _request_with_timing("DELETE",
url,
timeout=SIRP_REQUEST_TIMEOUT,
json=data)
response.raise_for_status()
response_data = response.json()
if response_data.get("success"):
return response_data.get("data")
elif response_data.get("error_code") == 0:
return True
else:
raise Exception(f"error_code: {response_data.get('error_code')} error_msg: {response_data.get('error_msg')}")
@staticmethod
def get_discussions(worksheet_id: str, row_id: str) -> List[Dict]:
url = f"{SIRP_URL}/api/v3/app/worksheets/{worksheet_id}/rows/{row_id}/discussions"
response = _request_with_timing("GET",
url,
timeout=SIRP_REQUEST_TIMEOUT)
response.raise_for_status()
response_data = response.json()
if response_data.get("success"):
# [
# {
# "id": "讨论ID",
# "message": "讨论内容",
# "_createdAt": "创建时间",
# "_createdBy": {"id", "name", "avatar", "isPortal", "status"},
# "replyToAuthor": {"id", "name", "avatar", "isPortal", "status"},
# "replyId": "回复ID",
# "projectId": "项目ID",
# "replyToId": [],
# "mentions": [],
# "attachments": [
# {"originalFilename", "ext", "filesize", "downloadUrl"}
# ]
# }
# ]
discussions = response_data.get("data", {}).get("discussions", [])
return discussions
else:
raise Exception(f"error_code: {response_data.get('error_code')} error_msg: {response_data.get('error_msg')}")
@staticmethod
def relations(worksheet_id: str, row_id: str, field: str, relation_worksheet_id: str, include_system_fields: bool = True, page_size: int = 1000,
page_index: int = 1):
fields = Worksheet.get_fields(relation_worksheet_id)
url = f"{SIRP_URL}/api/v3/app/worksheets/{worksheet_id}/rows/{row_id}/relations/{field}"
params = {}
if page_size is not None:
params["pageSize"] = page_size
if page_index is not None:
params["pageIndex"] = page_index
if include_system_fields is not None:
params["isReturnSystemFields"] = include_system_fields
response = _request_with_timing("GET",
url,
timeout=SIRP_REQUEST_TIMEOUT,
params=params)
response.raise_for_status()
response_data = response.json()
if response_data.get("success"):
rows = response_data.get("data").get("rows")
rows_new = []
for row in rows:
data_new = WorksheetRow._format_input_row(row, fields, include_system_fields)
rows_new.append(data_new)
return rows_new
else:
raise Exception(f"error_code: {response_data.get('error_code')} error_msg: {response_data.get('error_msg')}")
class OptionSet(object):
def __init__(self):
pass
@staticmethod
def list():
cached_optionsets = Xcache.get_sirp_optionset()
if cached_optionsets is not None:
return cached_optionsets
url = f"{SIRP_URL}/api/v3/app/optionsets"
response = _request_with_timing("GET",
url,
timeout=SIRP_REQUEST_TIMEOUT)
response.raise_for_status()
response_data = response.json()
if response_data.get("success"):
optionsets: List[Dict[str, Any]] = response_data.get("data").get("optionsets")
Xcache.set_sirp_optionset(optionsets)
return optionsets
else:
raise Exception(f"error_code: {response_data.get('error_code')} error_msg: {response_data.get('error_msg')}")
@staticmethod
def get(name):
optionsets = OptionSet.list()
for optionset in optionsets:
if optionset["name"] == name:
options = optionset.get("options", [])
return options
raise Exception(f"optionset {name} not found")
@staticmethod
def get_option_by_name_and_value(name, value) -> OptionType:
optionsets = OptionSet.list()
for optionset in optionsets:
if optionset["name"] == name:
options = optionset.get("options", [])
for option in options:
if option["value"] == value:
return option
raise Exception(f"optionset {name} {value} not found")
@staticmethod
def get_option_key_by_name_and_value(name, value):
optionsets = OptionSet.list()
for optionset in optionsets:
if optionset["name"] == name:
options = optionset.get("options", [])
for option in options:
if option["value"] == value:
return option["key"]
raise Exception(f"optionset {name} {value} not found")
if __name__ == "__main__":
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ASP.settings")
import django
django.setup()
result = WorksheetRow.get_discussions("case", "55c0ac33-65c2-420d-8e90-3ac62ca46f85")
print(result)