Загрузка данных
#!/usr/bin/env python3
import json
import ssl
import sys
import urllib.error
import urllib.request
from pathlib import Path
# Fill before running.
TT_BASE_URL = "https://portal.works.prod.localnet/swtr"
TT_TOKEN = ""
TT_TICKET = ""
# False: read author only. True: PATCH assigned_to to author on a noise-safe ticket.
RUN_EXECUTOR_UPDATE = False
# Use "createdBy" unless UI proves "reporter" is the business author.
AUTHOR_SOURCE = "createdBy"
VERIFY_SSL = False
TIMEOUT_SECONDS = 30
OUT = Path(__file__).resolve().parent
def die(message):
print("[ERROR] " + message, file=sys.stderr)
raise SystemExit(1)
def parse(text):
if not text:
return {}
try:
return json.loads(text)
except json.JSONDecodeError:
return text
def dump(name, value):
path = OUT / name
path.write_text(json.dumps(value, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
print("[OK] wrote " + str(path))
def tt(method, path, payload=None):
data = None if payload is None else json.dumps(payload, ensure_ascii=False).encode("utf-8")
req = urllib.request.Request(
TT_BASE_URL.rstrip("/") + "/" + path.lstrip("/"),
data=data,
method=method,
headers={
"Authorization": "Bearer " + TT_TOKEN,
"accept": "application/json",
"Content-Type": "application/json",
"Cookie": "api_swtr_as21=true",
},
)
context = None if VERIFY_SSL else ssl._create_unverified_context()
try:
with urllib.request.urlopen(req, timeout=TIMEOUT_SECONDS, context=context) as resp:
return resp.status, parse(resp.read().decode("utf-8", errors="replace"))
except urllib.error.HTTPError as err:
return err.code, parse(err.read().decode("utf-8", errors="replace"))
def get_unit():
status, body = tt("GET", f"rest/api/unit/v2/{TT_TICKET}?validatorEnabled=false")
if status < 200 or status >= 300:
dump("s46.read-error.json", {"http_status": status, "body": body})
die(f"ticket read failed: HTTP {status}")
if not isinstance(body, dict):
die("ticket read response is not a JSON object")
return body
def attr(unit, code):
for item in unit.get("attributes") or []:
if isinstance(item, dict) and item.get("code") == code:
return item
selected = unit.get("selected_attributes") or {}
if isinstance(selected, dict):
return selected.get(code)
return None
def login(value):
if not isinstance(value, dict):
return ""
value = value.get("value") if isinstance(value.get("value"), dict) else value
return str(value.get("login") or "")
def short_attr(item):
if not isinstance(item, dict):
return None
return {
"code": item.get("code"),
"name": item.get("name"),
"type": item.get("type"),
"value": item.get("value"),
"valueAsString": item.get("valueAsString"),
}
def compact(unit):
assigned_to = attr(unit, "assigned_to")
reporter = attr(unit, "reporter")
attrs = [short_attr(attr(unit, c)) for c in ("assigned_to", "reporter", "workflow_status")]
return {
"code": unit.get("code"),
"summary": unit.get("summary"),
"space": unit.get("space"),
"suit": unit.get("suit"),
"createdBy": unit.get("createdBy"),
"updatedBy": unit.get("updatedBy"),
"author_candidate_createdBy_login": login(unit.get("createdBy")),
"attributes": [x for x in attrs if x],
"assigned_to_login": login(assigned_to),
"reporter_login": login(reporter),
}
def pick_author(c):
if AUTHOR_SOURCE == "createdBy":
return c["author_candidate_createdBy_login"]
if AUTHOR_SOURCE == "reporter":
return c["reporter_login"]
die('AUTHOR_SOURCE must be "createdBy" or "reporter"')
def main():
if not TT_TOKEN or not TT_TICKET:
die("fill TT_TOKEN and TT_TICKET at the top of the script")
before = compact(get_unit())
dump("s46.author.compact.json", before)
author = pick_author(before)
if not author:
die("author login is empty")
print("[INFO] author_login=" + author)
print("[INFO] assigned_to_before=" + before["assigned_to_login"])
if not RUN_EXECUTOR_UPDATE:
print("[OK] read-only run complete")
return
request_body = {"attributes": {"assigned_to": author}}
status, body = tt("PATCH", f"rest/api/unit/v1/update/{TT_TICKET}", request_body)
dump("s46.executor-update.response.json", {"http_status": status, "ok": 200 <= status < 300, "request_body": request_body, "body": body})
if status < 200 or status >= 300:
die(f"executor update failed: HTTP {status}")
after = compact(get_unit())
after["executor_update_matches_author"] = after["assigned_to_login"] == author
dump("s46.after.compact.json", after)
print("[INFO] assigned_to_after=" + after["assigned_to_login"])
print("[INFO] matches=" + str(after["executor_update_matches_author"]))
if not after["executor_update_matches_author"]:
die("after-read did not confirm assigned_to equals author")
if __name__ == "__main__":
main()