- README: описание репозитория, список workflows - workflows/plane-singularity/README.md: полная документация по интеграции (схема, payload структура, маппинг 28 проектов, Singularity API) - workflows/plane-singularity/update_workflow.py: скрипт обновления через n8n API Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
235 lines
10 KiB
Python
235 lines
10 KiB
Python
import json, urllib.request
|
||
|
||
N8N_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyOTBmYzEwZS1jY2JmLTQwZWQtOWIzYy0wNmIwMGY2N2QwMjEiLCJpc3MiOiJuOG4iLCJhdWQiOiJwdWJsaWMtYXBpIiwianRpIjoiNGM5M2IwNjMtNWQ3My00MTU0LWIyMjYtYWM0NDFmZmNmYTRiIiwiaWF0IjoxNzc0MjE3MDM2fQ.7uga-4ukI2uJSmRRep2ijlhy6x0I92nFVQl2nxMX1VU"
|
||
|
||
workflow = {
|
||
"name": "Plane → Singularity: назначение задачи",
|
||
"nodes": [
|
||
{
|
||
"id": "1",
|
||
"name": "Webhook",
|
||
"type": "n8n-nodes-base.webhook",
|
||
"typeVersion": 2,
|
||
"position": [240, 300],
|
||
"parameters": {
|
||
"httpMethod": "POST",
|
||
"path": "plane-assignment",
|
||
"responseMode": "onReceived",
|
||
"responseData": "firstEntryJson"
|
||
},
|
||
"webhookId": "plane-assignment"
|
||
},
|
||
{
|
||
"id": "2",
|
||
"name": "Тип события",
|
||
"type": "n8n-nodes-base.switch",
|
||
"typeVersion": 3,
|
||
"position": [480, 300],
|
||
"parameters": {
|
||
"mode": "rules",
|
||
"rules": {
|
||
"values": [
|
||
{
|
||
"conditions": {
|
||
"options": {"caseSensitive": True, "leftValue": "", "typeValidation": "strict"},
|
||
"conditions": [
|
||
{
|
||
"id": "a1",
|
||
"leftValue": "={{ $json.body.activity.field }}",
|
||
"rightValue": "assignee_ids",
|
||
"operator": {"type": "string", "operation": "equals"}
|
||
},
|
||
{
|
||
"id": "a2",
|
||
"leftValue": "={{ JSON.stringify($json.body.activity.new_value) }}",
|
||
"rightValue": "f7a5e9db-eaf0-4314-9440-4b28094f5db1",
|
||
"operator": {"type": "string", "operation": "contains"}
|
||
}
|
||
],
|
||
"combinator": "and"
|
||
},
|
||
"renameOutput": True,
|
||
"outputKey": "Назначена на меня"
|
||
},
|
||
{
|
||
"conditions": {
|
||
"options": {"caseSensitive": True, "leftValue": "", "typeValidation": "strict"},
|
||
"conditions": [
|
||
{
|
||
"id": "b1",
|
||
"leftValue": "={{ $json.body.activity.field }}",
|
||
"rightValue": "state_id",
|
||
"operator": {"type": "string", "operation": "equals"}
|
||
},
|
||
{
|
||
"id": "b2",
|
||
"leftValue": "={{ $json.body.data.state.group }}",
|
||
"rightValue": "completed",
|
||
"operator": {"type": "string", "operation": "equals"}
|
||
}
|
||
],
|
||
"combinator": "and"
|
||
},
|
||
"renameOutput": True,
|
||
"outputKey": "Задача завершена"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
}
|
||
},
|
||
{
|
||
"id": "6",
|
||
"name": "Подготовить данные",
|
||
"type": "n8n-nodes-base.code",
|
||
"typeVersion": 2,
|
||
"position": [720, 160],
|
||
"parameters": {
|
||
"jsCode": """const data = $json.body.data;
|
||
const activity = $json.body.activity;
|
||
|
||
const priorityMap = {
|
||
urgent: 0,
|
||
high: 0,
|
||
medium: 1,
|
||
low: 2,
|
||
none: 2
|
||
};
|
||
|
||
const projectMap = {
|
||
'0a821ae1-7564-431d-a78c-c76885d85a9a': 'P-b40d30ed-8480-47a0-b0fb-04005eab2028', // DRC - ДРЦ(Нагорное)
|
||
'87598e10-0c7d-4612-8a38-073085358580': 'P-00ddf477-f600-44fb-a177-db2af3861dfe', // NAVISCOPE
|
||
'1099af67-70b7-4260-90fd-31de5e82e05a': 'P-30340426-ff0e-40b7-af17-070372936101', // NAB - Прочее
|
||
'2d35134d-6055-4a88-be9e-a0ee49f018e9': 'P-5a0bd9ad-3df2-4d9c-a7bf-3f59621a2309', // MANYH - ИП Маняхин
|
||
'1f4bcaee-1ff7-4227-b160-8aa6561c55e7': 'P-638ec16a-9544-4161-ab39-014ca44772c2', // TCKT - HHIVP Инфра
|
||
'74d5b2f9-558c-4371-ab6a-21a082b33933': 'P-63c01156-e0fe-491d-a4f0-82fe4e0c4af3', // CIFRA - ЦифраЦифра
|
||
'26cd4aab-cad6-46ae-924b-57d911128506': 'P-39d04293-a3a3-4110-b246-db67dd4729cc', // VEHA - Веха
|
||
'66748cf3-1e67-4c7d-9c17-44e90854e7e3': 'P-b1a9d408-635e-4cf2-9b40-b830749fb996', // SINVS - Олимпийский 42а
|
||
'309e735f-5fd2-4090-a8ba-df5948532e21': 'P-8624fef6-01ec-44bf-b578-816d90ef56ec', // SHAUS - СтифтерХаус
|
||
'404b4c7a-cce0-4126-aa0e-3fd61ff53a14': 'P-ea61bc07-5c60-4b91-99e2-66d4c545338a', // YAR - Ярослав В.
|
||
'89133e7f-45d7-44ed-85b9-87f12634b0d4': 'P-e4c50922-162a-445f-a8e1-5621d2ef4124', // SBOR - Старый Большевик
|
||
'dd3495bf-3d49-4e30-b614-35e2362fad4f': 'P-cec926a1-8425-42cc-99e1-2079b7b1298f', // SMED - Северное Медведково
|
||
'94e0ddd0-200f-4eb7-8f89-a86ec8e63df4': 'P-e73fe9a8-ada5-46d4-9e8a-2ce5b889d95e', // SAMOI - А. Самойлов
|
||
'a3906157-53e4-47d0-b3c8-89b807d36ef7': 'P-32c6abd8-2246-4240-b1f0-c85c2a221331', // HG - Сад Здоровья
|
||
'ec70a115-6d14-43c9-8572-bd423bcf7289': 'P-281f799e-304b-4a19-bd9d-db0411e4335a', // ROMA - Ромашка
|
||
'04427cbb-d108-4558-8aa1-ad7a486e1f9b': 'P-30340426-ff0e-40b7-af17-070372936101', // NIIH - Прочее
|
||
'191c0a5b-e998-4503-84c7-aaee67093393': 'P-7377d35f-eb9e-4aa4-828a-1bc01b000624', // LIANZ - Лианозово
|
||
'5db759d0-a5fb-453a-90ca-30b0e6355635': 'P-db73597a-47b2-44b3-8282-f9f0e29d52df', // MOIS - ИП Моисеев
|
||
'60c0bd07-4867-4c8a-b6e3-b7c78701b36d': 'P-04b65df5-b055-4a30-9bb6-4ffbb3146523', // VONDI - Вондига
|
||
'c4cdd5fb-c5c7-4b80-b209-a5d6311090ec': 'P-30340426-ff0e-40b7-af17-070372936101', // 4101 - Прочее
|
||
'c6b2531b-7556-4f66-a646-9d4823ee0a82': 'P-30340426-ff0e-40b7-af17-070372936101', // BCOM - Прочее
|
||
'91a97d40-b389-49be-9887-0e791a3bb0f9': 'P-62de7005-33fd-4012-b6ac-7ce277cd0b5d', // DELTA - Дельта
|
||
'eaef875f-0d90-4828-93af-976199913078': 'P-638ec16a-9544-4161-ab39-014ca44772c2', // WEB - HHIVP Инфра
|
||
'51846118-c503-47a3-a94a-88aabf1b950a': 'P-a3f177b1-5078-4e05-9213-32d6af16827f', // REGRU - Домены
|
||
'910aa865-a8fa-416a-9c5c-c18cb4d2c653': 'P-05ca29bb-d9f8-4668-891f-92dfbe76fee9', // HARZL - ХарцЛабс
|
||
'dfd2a49f-5f5c-48c7-8000-476c56d39f62': 'P-181d5832-3468-4b4a-b1bd-92faa00d580b', // 3DRU - 3Д.РУ
|
||
'46f45846-116e-4dba-8ecc-6ba87a5b60f7': 'P-d7a92af2-06bf-4226-a7f4-fc1a2469fe0f', // IVA - Ива
|
||
'4df07960-f664-4aba-a757-94a1106c9bae': 'P-638ec16a-9544-4161-ab39-014ca44772c2' // HHVIP - HHIVP Инфра
|
||
};
|
||
|
||
const singularityProjectId = projectMap[data.project] || 'P-91f69023-bdd0-43c7-9caa-7994ec2b8cc8';
|
||
|
||
const result = {
|
||
title: `[${data.project_identifier}-${data.sequence_id}] ${data.name}`,
|
||
note: data.description_stripped || '',
|
||
priority: priorityMap[data.priority] ?? 1,
|
||
projectId: singularityProjectId,
|
||
issueName: data.name,
|
||
issueIdentifier: `${data.project_identifier}-${data.sequence_id}`
|
||
};
|
||
|
||
if (data.target_date) {
|
||
result.deadline = data.target_date;
|
||
}
|
||
|
||
return [{ json: result }];"""
|
||
}
|
||
},
|
||
{
|
||
"id": "3",
|
||
"name": "Создать задачу в Singularity",
|
||
"type": "n8n-nodes-base.httpRequest",
|
||
"typeVersion": 4.2,
|
||
"position": [960, 160],
|
||
"parameters": {
|
||
"method": "POST",
|
||
"url": "https://api.singularity-app.com/v2/task",
|
||
"sendHeaders": True,
|
||
"headerParameters": {
|
||
"parameters": [{"name": "Authorization", "value": "Bearer 11a4eac1-3dd9-4448-99f6-0b3c58315a5d"}]
|
||
},
|
||
"sendBody": True,
|
||
"specifyBody": "json",
|
||
"jsonBody": "={{ JSON.stringify({\n title: $json.title,\n note: $json.note,\n priority: $json.priority,\n projectId: $json.projectId,\n ...($json.deadline ? { deadline: $json.deadline } : {})\n}) }}"
|
||
}
|
||
},
|
||
{
|
||
"id": "4",
|
||
"name": "Найти задачу в Singularity",
|
||
"type": "n8n-nodes-base.httpRequest",
|
||
"typeVersion": 4.2,
|
||
"position": [720, 440],
|
||
"parameters": {
|
||
"method": "GET",
|
||
"url": "=https://api.singularity-app.com/v2/task?search={{ encodeURIComponent($json.body.data.project_identifier + \"-\" + $json.body.data.sequence_id) }}",
|
||
"sendHeaders": True,
|
||
"headerParameters": {
|
||
"parameters": [{"name": "Authorization", "value": "Bearer 11a4eac1-3dd9-4448-99f6-0b3c58315a5d"}]
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"id": "5",
|
||
"name": "Закрыть задачу в Singularity",
|
||
"type": "n8n-nodes-base.httpRequest",
|
||
"typeVersion": 4.2,
|
||
"position": [960, 440],
|
||
"parameters": {
|
||
"method": "PATCH",
|
||
"url": "=https://api.singularity-app.com/v2/task/{{ $json.tasks[0].id }}",
|
||
"sendHeaders": True,
|
||
"headerParameters": {
|
||
"parameters": [{"name": "Authorization", "value": "Bearer 11a4eac1-3dd9-4448-99f6-0b3c58315a5d"}]
|
||
},
|
||
"sendBody": True,
|
||
"contentType": "json",
|
||
"body": "{\"complete\": 1}"
|
||
}
|
||
}
|
||
],
|
||
"connections": {
|
||
"Webhook": {"main": [[{"node": "Тип события", "type": "main", "index": 0}]]},
|
||
"Тип события": {
|
||
"main": [
|
||
[{"node": "Подготовить данные", "type": "main", "index": 0}],
|
||
[{"node": "Найти задачу в Singularity", "type": "main", "index": 0}]
|
||
]
|
||
},
|
||
"Подготовить данные": {"main": [[{"node": "Создать задачу в Singularity", "type": "main", "index": 0}]]},
|
||
"Найти задачу в Singularity": {"main": [[{"node": "Закрыть задачу в Singularity", "type": "main", "index": 0}]]}
|
||
},
|
||
"settings": {"executionOrder": "v1"}
|
||
}
|
||
|
||
body = json.dumps(workflow, ensure_ascii=False).encode('utf-8')
|
||
req = urllib.request.Request(
|
||
"https://n8n.striker.su/api/v1/workflows/zyO77cIUNdbc4xPH",
|
||
data=body,
|
||
method="PUT",
|
||
headers={
|
||
"X-N8N-API-KEY": N8N_KEY,
|
||
"Content-Type": "application/json; charset=utf-8"
|
||
}
|
||
)
|
||
with urllib.request.urlopen(req) as resp:
|
||
result = json.loads(resp.read())
|
||
print("OK, id:", result.get("id"))
|
||
# Check the switch node conditions
|
||
for node in result.get("nodes", []):
|
||
if node["name"] == "Тип события":
|
||
rules = node["parameters"]["rules"]["values"]
|
||
for r in rules:
|
||
print(f"Branch '{r['outputKey']}':")
|
||
for c in r["conditions"]["conditions"]:
|
||
print(f" {c['leftValue']} {c['operator']['operation']} {c['rightValue']}")
|