Skip to content

Commit dadd219

Browse files
authored
Merge pull request #1 from rebortg/main
add phabricator autoclose script and github action
2 parents 9c40be5 + 0e274fe commit dadd219

File tree

5 files changed

+218
-0
lines changed

5 files changed

+218
-0
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
name: Autoclose Finished Phabricator Tasks
2+
on:
3+
workflow_dispatch:
4+
schedule:
5+
- cron: '0 6 * * *'
6+
7+
jobs:
8+
main:
9+
runs-on: ubuntu-latest
10+
steps:
11+
- name: Set Up Python
12+
uses: actions/setup-python@v5
13+
with:
14+
python-version: 3.11.x
15+
16+
- uses: actions/checkout@v4
17+
18+
- name: run script
19+
env:
20+
PHABRICATOR_WRITE_TOKEN: ${{ secrets.PHABRICATOR_WRITE_TOKEN }}
21+
if: env.PHABRICATOR_WRITE_TOKEN != null
22+
run: |
23+
pip3 install -r phabricator_tasks/requirements.txt
24+
python3 phabricator_tasks/tasks.py -t ${{ secrets.PHABRICATOR_WRITE_TOKEN }}

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
venv
2+
.DS_Store

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
11
# vyos-infrastructure
22
Various scripts and automations for VyOS infrastructure tasks
3+
4+
5+
## phabricator_tasks
6+
autoclose all finished tasks in https://vyos.dev

phabricator_tasks/requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
phabricator
2+
requests

phabricator_tasks/tasks.py

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
from phabricator import Phabricator as PhabricatorOriginal
2+
from phabricator import parse_interfaces
3+
import argparse
4+
5+
6+
'''
7+
get project wide tasks which are not closed but all in the Finished column
8+
9+
1. get all Workboard columns
10+
- extract workboard phid for the Finished column
11+
- and the project phid and name
12+
13+
2. get all open taks from projects with Finish column
14+
3. get unique taskslists from previous step to get projekts of a task
15+
4. get all transactions for each task and check if the task is in the Finished column per project
16+
5. autoclose if task is in all Finished column
17+
18+
'''
19+
20+
'''
21+
extend of original Phabricator class to add new interface "project.column.search"
22+
this can be delete if PR https://github.com/disqus/python-phabricator/pull/71 is merged in the pip package
23+
24+
'''
25+
import copy
26+
import json
27+
import pkgutil
28+
29+
INTERFACES = json.loads(
30+
pkgutil.get_data('phabricator', 'interfaces.json')
31+
.decode('utf-8'))
32+
33+
INTERFACES['project.column.search'] = {
34+
"description": "Search for Workboard columns.",
35+
"params": {
36+
"ids": "optional list<int>",
37+
"phids": "optional list<phid>",
38+
"projects": "optional list<phid>"
39+
},
40+
"return": "list"
41+
}
42+
43+
class Phabricator(PhabricatorOriginal):
44+
def __init__(self, **kwargs):
45+
kwargs['interface'] = copy.deepcopy(parse_interfaces(INTERFACES))
46+
super(Phabricator, self).__init__(self, **kwargs)
47+
48+
''' end of extend the original Phabricator class'''
49+
50+
def phab_search(method, constraints=dict(), after=None):
51+
results = []
52+
while True:
53+
response = method(
54+
constraints=constraints,
55+
after=after
56+
)
57+
results.extend(response.response['data'])
58+
after = response.response['cursor']['after']
59+
if after is None:
60+
break
61+
return results
62+
63+
64+
def phab_query(method, after=None):
65+
results = []
66+
while True:
67+
response = method(
68+
offset=after
69+
)
70+
results.extend(response.response['data'])
71+
after = response.response['cursor']['after']
72+
if after is None:
73+
break
74+
return results
75+
76+
77+
def close_task(task_id, phab):
78+
try:
79+
response = phab.maniphest.update(
80+
id=task_id,
81+
status='resolved'
82+
)
83+
if response.response['isClosed']:
84+
print(f'T{task_id} closed')
85+
except Exception as e:
86+
print(f'T{task_id} Error: {e}')
87+
88+
89+
parser = argparse.ArgumentParser()
90+
parser.add_argument("-t", "--token", type=str, help="API token", required=True)
91+
args = parser.parse_args()
92+
93+
phab = Phabricator(host='https://vyos.dev/api/', token=args.token)
94+
phab.maniphest.update(id=6053, status='resolved')
95+
96+
workboards = phab_search(phab.project.column.search)
97+
project_hirarchy = {}
98+
99+
# get sub-project hirarchy from proxyPHID in workboards
100+
for workboard in workboards:
101+
if workboard['fields']['proxyPHID']:
102+
proxy_phid = workboard['fields']['proxyPHID']
103+
project_phid = workboard['fields']['project']['phid']
104+
105+
if project_phid not in project_hirarchy.keys():
106+
project_hirarchy[project_phid] = []
107+
project_hirarchy[project_phid].append(proxy_phid)
108+
109+
finished_boards = []
110+
111+
112+
for workboard in workboards:
113+
project_id = workboard['fields']['project']['phid']
114+
if project_id in project_hirarchy.keys():
115+
# skip projects with sub-projects
116+
continue
117+
if workboard['fields']['name'] == 'Finished':
118+
project_tasks = phab_search(phab.maniphest.search, constraints={
119+
'projects': [project_id],
120+
'statuses': ['open'],
121+
})
122+
finished_boards.append({
123+
'project_id': project_id,
124+
'project_name': workboard['fields']['project']['name'],
125+
'project_tasks': project_tasks,
126+
'should_board_id': workboard['phid'],
127+
})
128+
129+
# get unique tasks
130+
# tasks = {
131+
# 9999: {
132+
# 'PHID-PROJ-xxxxx': 'PHID-PCOL-xxxxx',
133+
# 'PHID-PROJ-yyyyy': 'PHID-PCOL-yyyyy'
134+
# }
135+
# }
136+
tasks = {}
137+
for project in finished_boards:
138+
project_id = project['project_id']
139+
board_id = project['should_board_id']
140+
for task in project['project_tasks']:
141+
task_id = task['id']
142+
if task_id not in tasks.keys():
143+
tasks[task_id] = {}
144+
if project_id not in tasks[task_id].keys():
145+
tasks[task_id][project_id] = board_id
146+
147+
tasks = dict(sorted(tasks.items()))
148+
149+
# get transactions for each task and compare if the task is in the Finished column
150+
for task_id, projects in tasks.items():
151+
fisnish_timestamp = 0
152+
project_ids = list(projects.keys())
153+
# don't use own pagination function, because endpoint without pagination
154+
transactions = phab.maniphest.gettasktransactions(ids=[task_id])
155+
transactions = transactions.response[str(task_id)]
156+
finished = False
157+
for transaction in transactions:
158+
if transaction['transactionType'] == 'core:columns':
159+
# test if projectid is in transaction
160+
if transaction['newValue'][0]['boardPHID'] in project_ids:
161+
# remove project_id from project_ids to use only last transaction in this
162+
# project
163+
project_ids.remove(transaction['newValue'][0]['boardPHID'])
164+
# test if boardid is the "Finished" board
165+
if fisnish_timestamp < int(transaction['dateCreated']):
166+
fisnish_timestamp = int(transaction['dateCreated'])
167+
if projects[transaction['newValue'][0]['boardPHID']] == transaction['newValue'][0]['columnPHID']:
168+
finished = True
169+
for project in finished_boards:
170+
if project['project_id'] == transaction['newValue'][0]['boardPHID']:
171+
project_name = project['project_name']
172+
# print(f'T{task_id} is Finished in {project_name}')
173+
if len(project_ids) == 0:
174+
print(f'T{task_id} is Finished in all projects')
175+
close_task(task_id, phab)
176+
break
177+
178+
#if len(project_ids) > 0 and finished:
179+
# collect project names for output
180+
# project_names = []
181+
# for project_id in project_ids:
182+
# for project in finished_boards:
183+
# if project['project_id'] == project_id:
184+
# project_names.append(project['project_name'])
185+
# print(f'T{task_id} is in a different column: {' and '.join(project_names)}')
186+

0 commit comments

Comments
 (0)