Skip to content

Commit fc68d7b

Browse files
msyycRAY-316
andauthored
[Release helper] Update (Azure#22196)
* update for Go * check tag consistency and optize reply * add write.md * update go js readme * update * Update common.py * Update common.py * update assignee for JS * update * update * Update common.py * Update common.py * Update release_helper.yml for Azure Pipelines * Update common.py * update * Update common.py * Update common.py * Update main.py * update * update bot advice * update * update * update excel * update output function * add exception handle for bad credential * fix static varaible in class * update auto_assignee algorithm * update bot token to have a try * update bot token * force to single process Co-authored-by: Zed <601306339@qq.com> Co-authored-by: Yiming Lei <59104634+RAY-316@users.noreply.github.com>
1 parent d085064 commit fc68d7b

File tree

7 files changed

+123
-57
lines changed

7 files changed

+123
-57
lines changed

scripts/release_helper/common.py

Lines changed: 98 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,62 @@
11
from datetime import date, datetime
22
from typing import Set, List, Dict
33
import os
4-
from utils import IssuePackage, REQUEST_REPO, AUTO_ASSIGN_LABEL, AUTO_PARSE_LABEL, get_origin_link_and_tag
4+
from utils import IssuePackage, REQUEST_REPO, AUTO_ASSIGN_LABEL, AUTO_PARSE_LABEL, get_origin_link_and_tag,\
5+
MULTI_LINK_LABEL
56
import re
67
import logging
78
import time
9+
from random import randint
810
from github import Github
911
from github.Repository import Repository
10-
import subprocess as sp
1112

1213
_LOG = logging.getLogger(__name__)
1314

1415
# assignee dict which will be assigned to handle issues
1516
_LANGUAGE_OWNER = {'msyyc'}
1617

1718
# 'github assignee': 'token'
18-
_ASSIGNEE_TOKEN = {'msyyc': os.getenv('PYTHON_MSYYC_TOKEN')}
19+
_ASSIGNEE_TOKEN = {'msyyc': os.getenv('AZURESDK_BOT_TOKEN')}
1920

2021
_SWAGGER_URL = 'https://github.com/Azure/azure-rest-api-specs/blob/main/specification'
2122
_SWAGGER_PULL = 'https://github.com/Azure/azure-rest-api-specs/pull'
2223

2324

24-
_SWAGGER_URL = 'https://github.com/Azure/azure-rest-api-specs/blob/main/specification'
25-
_SWAGGER_PULL = 'https://github.com/Azure/azure-rest-api-specs/pull'
26-
2725
class IssueProcess:
26+
"""
2827
# won't be changed anymore after __init__
2928
request_repo_dict = {} # request repo instance generated by different token
3029
owner = '' # issue owner
3130
assignee_candidates = {} # assignee candidates who will be assigned to handle issue
3231
language_owner = {} # language owner who may handle issue
3332
3433
# will be changed by order
35-
issue = None # issue that needs to handle
34+
issue_package = None # issue that needs to handle
3635
assignee = ''
37-
bot = [] # bot advice to help SDK owner
36+
bot_advice = [] # bot advice to help SDK owner
3837
target_readme_tag = '' # swagger content that customers want
3938
readme_link = '' # https link which swagger definition is in
4039
default_readme_tag = '' # configured in `README.md`
40+
package_name = '' # target package name
41+
target_date = '' # target release date asked by customer
42+
date_from_target = 0
43+
"""
4144

42-
def __init__(self, issue: IssuePackage, request_repo_dict: Dict[str, Repository],
45+
def __init__(self, issue_package: IssuePackage, request_repo_dict: Dict[str, Repository],
4346
assignee_candidates: Set[str], language_owner: Set[str]):
44-
self.issue_package = issue
47+
self.issue_package = issue_package
4548
self.request_repo_dict = request_repo_dict
46-
self.assignee = issue.issue.assignee.login
47-
self.owner = issue.issue.user.login
49+
self.assignee = issue_package.issue.assignee.login
50+
self.owner = issue_package.issue.user.login
4851
self.assignee_candidates = assignee_candidates
4952
self.language_owner = language_owner
53+
self.bot_advice = []
54+
self.target_readme_tag = ''
55+
self.readme_link = ''
56+
self.default_readme_tag = ''
57+
self.package_name = ''
58+
self.target_date = ''
59+
self.date_from_target = 0
5060

5161
def get_issue_body(self) -> List[str]:
5262
return [i for i in self.issue_package.issue.body.split("\n") if i]
@@ -82,8 +92,7 @@ def get_readme_from_pr_link(self, link: str) -> str:
8292
pr = f"{_SWAGGER_PULL}/{pr_number}"
8393
self.comment(
8494
f'Hi, @{self.assignee}, by parsing {pr}, there are multi service link: {multi_link}. Please decide which one is the right.')
85-
86-
self.bot.append('multi readme link!')
95+
self.add_label(MULTI_LINK_LABEL)
8796
raise Exception(f'multi link in "{pr}"')
8897

8998
return readme_link[0]
@@ -190,7 +199,7 @@ def auto_assign(self) -> None:
190199
return
191200
# assign averagely
192201
assignees = list(self.assignee_candidates)
193-
random_idx = int(str(time.time())[-1]) % len(assignees) if len(assignees) > 1 else 0
202+
random_idx = randint(0, len(assignees) - 1) if len(assignees) > 1 else 0
194203
assignee = assignees[random_idx]
195204

196205
# update assignee
@@ -203,83 +212,127 @@ def auto_assign(self) -> None:
203212
self.update_issue_instance()
204213
self.add_label(AUTO_ASSIGN_LABEL)
205214

206-
def bot_advice(self):
207-
latest_comments = ''
215+
def new_issue_policy(self):
216+
new_issue_advice = 'new issue.'
217+
if self.issue_package.issue.comments == 0:
218+
self.bot_advice.append(new_issue_advice)
219+
else:
220+
# issue that no comment from language owner will also be treated as new issue
221+
comment_from_owner = set(comment.user.login for comment in self.issue_package.issue.get_comments()
222+
if comment.user.login in self.language_owner)
223+
if not comment_from_owner:
224+
self.bot_advice.append(new_issue_advice)
225+
226+
def new_comment_policy(self):
227+
if self.issue_package.issue.comments == 0:
228+
return
208229
comments = [(comment.updated_at.timestamp(), comment.user.login) for comment in
209230
self.issue_package.issue.get_comments()]
210231
comments.sort()
211-
if comments:
212-
latest_comments = comments[-1][1]
213-
if self.issue_package.issue.comments == 0:
214-
self.bot = 'new issue ! <br>'
215-
elif latest_comments not in self.language_owner:
216-
self.bot = 'new comment. <br>'
232+
latest_comments = comments[-1][1]
233+
if latest_comments not in self.language_owner:
234+
self.bot_advice.append('new comment.')
235+
236+
def multi_link_policy(self):
237+
if MULTI_LINK_LABEL in self.issue_package.labels_name:
238+
self.bot_advice.append('multi readme link!')
239+
240+
def remind_logic(self) -> bool:
241+
return abs(self.date_from_target) <= 2
242+
243+
def print_date_from_target_date(self) -> str:
244+
return str(self.date_from_target) if self.remind_logic() else ''
245+
246+
def date_remind_policy(self):
247+
if self.remind_logic():
248+
self.bot_advice.append('close to release date. ')
249+
250+
def auto_bot_advice(self):
251+
self.new_issue_policy()
252+
self.new_comment_policy()
253+
self.multi_link_policy()
254+
self.date_remind_policy()
255+
256+
def get_target_date(self):
257+
body = self.get_issue_body()
258+
try:
259+
self.target_date = [line.split(':')[-1].strip() for line in body if 'Target release date' in line][0]
260+
self.date_from_target = int((time.mktime(time.strptime(self.target_date, '%Y-%m-%d')) - time.time()) / 3600 / 24)
261+
except Exception:
262+
self.target_date = 'fail to get.'
263+
self.date_from_target = 1000 # make a ridiculous data to remind failure when error happens
217264

218265
def run(self) -> None:
219266
# common part(don't change the order)
220267
self.auto_assign() # necessary flow
221268
self.auto_parse() # necessary flow
222-
self.bot_advice()
269+
self.get_target_date()
270+
self.auto_bot_advice() # make sure this is the last step
223271

224272

225273
class Common:
226-
""" The class defines some function for all languages to reference """
274+
""" The class defines some function for all languages to reference
227275
issues_package = None # issues that need to handle
228276
request_repo_dict = {} # request repo instance generated by different token
229277
assignee_candidates = {} # assignee candidates who will be assigned to handle issue
230278
language_owner = {} # language owner who may handle issue
279+
result = []
280+
file_out_name = '' # file that storages issue status
281+
"""
231282

232-
def __init__(self, issues: List[IssuePackage], assignee_token: Dict[str, str], language_owner: Set[str]):
233-
self.issues_package = issues
283+
def __init__(self, issues_package: List[IssuePackage], assignee_token: Dict[str, str], language_owner: Set[str]):
284+
self.issues_package = issues_package
234285
self.assignee_candidates = set(assignee_token.keys())
235286
self.language_owner = language_owner
236287
# arguments add to language.md
237288
self.file_out_name = 'common.md'
238289
self.target_release_date = ''
239290
self.date_from_target = ''
240291
self.package_name = ''
292+
self.result = []
293+
self.request_repo_dict = {}
241294

242295
for assignee in assignee_token:
243296
self.request_repo_dict[assignee] = Github(assignee_token[assignee]).get_repo(REQUEST_REPO)
244297

245-
def output_md(self, items):
298+
def output(self):
246299
with open(self.file_out_name, 'w') as file_out:
247-
file_out.write(
248-
'| issue | author | package | assignee | bot advice | created date of issue | target release date | date from target |\n')
300+
file_out.write('| issue | author | package | assignee | bot advice | created date of issue | target release date | date from target |\n')
249301
file_out.write('| ------ | ------ | ------ | ------ | ------ | ------ | ------ | :-----: |\n')
250-
file_out.writelines([self.output_python(item) for item in items])
302+
for item in self.result:
303+
try:
304+
item_status = Common.output_md(item)
305+
file_out.write(item_status)
306+
except Exception as e:
307+
_LOG.error(f'Error happened during output result of handled issue {item.issue_package.issue.number}: {e}')
251308

252-
def output_python(self, item):
309+
@staticmethod
310+
def output_md(item: IssueProcess):
253311
create_date = str(date.fromtimestamp(item.issue_package.issue.created_at.timestamp()).strftime('%m-%d'))
312+
target_date = str(datetime.strptime(item.target_date, "%Y-%m-%d").strftime('%m-%d'))
254313

255314
return '| [#{}]({}) | {} | {} | {} | {} | {} | {} | {} |\n'.format(
256315
item.issue_package.issue.html_url.split('/')[-1],
257316
item.issue_package.issue.html_url,
258317
item.issue_package.issue.user.login,
259-
self.package_name,
260-
item.issue_package.issue.assignee.login,
261-
item.bot,
318+
item.package_name,
319+
item.assignee,
320+
' '.join(item.bot_advice),
262321
create_date,
263-
self.target_release_date,
264-
self.date_from_target
322+
target_date,
323+
item.print_date_from_target_date()
265324
)
266325

267-
@staticmethod
268-
def push_md_to_storage():
269-
cmd_list = ['git add .', 'git commit -m \"update excel\"', 'git push -f origin HEAD']
270-
[sp.check_call(cmd, shell=True) for cmd in cmd_list]
271-
272326
def run(self):
273327
items = []
274328
for item in self.issues_package:
275329
issue = IssueProcess(item, self.request_repo_dict, self.assignee_candidates, self.language_owner)
276330
try:
277331
issue.run()
278-
items.append(issue)
332+
self.result.append(issue)
279333
except Exception as e:
280334
_LOG.error(f'Error happened during handling issue {item.issue.number}: {e}')
281-
self.output_md(items)
282-
335+
self.output()
283336

284337

285338
def common_process(issues: List[IssuePackage]):

scripts/release_helper/go.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
_GO_OWNER = {'ArcturusZhang'}
77

88
# 'github assignee': 'token'
9-
_ASSIGNEE_TOKEN_GO = {'ArcturusZhang': os.getenv('GO_DAPENGZHANG_TOKEN')}
9+
_ASSIGNEE_TOKEN_GO = {'ArcturusZhang': os.getenv('AZURESDK_BOT_TOKEN')}
1010

1111

1212
class IssueProcessGo(IssueProcess):

scripts/release_helper/java.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88

99
# 'github assignee': 'token'
1010
_ASSIGNEE_TOKEN_JAVA = {
11-
'weidongxu-microsoft': os.getenv('JAVA_WEIDONGXU_TOKEN'),
12-
'haolingdong-msft': os.getenv('JAVA_WEIDONGXU_TOKEN'),
13-
'XiaofeiCao': os.getenv('JAVA_WEIDONGXU_TOKEN'),
11+
'weidongxu-microsoft': os.getenv('AZURESDK_BOT_TOKEN'),
12+
'haolingdong-msft': os.getenv('AZURESDK_BOT_TOKEN'),
13+
'XiaofeiCao': os.getenv('AZURESDK_BOT_TOKEN'),
1414
}
1515

1616

scripts/release_helper/js.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
_JS_OWNER = {'qiaozha', 'lirenhe'}
77

88
# 'github assignee': 'token'
9-
_ASSIGNEE_TOKEN_JS = {'qiaozha': os.getenv('JS_QIAOQIAO_TOKEN')}
9+
_ASSIGNEE_TOKEN_JS = {'qiaozha': os.getenv('AZURESDK_BOT_TOKEN')}
1010

1111

1212
class IssueProcessJs(IssueProcess):

scripts/release_helper/main.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from java import java_process
77
from js import js_process
88
from common import common_process, Common
9+
import subprocess as sp
910

1011

1112
import os
@@ -53,9 +54,15 @@ def main():
5354
language = os.getenv('LANGUAGE')
5455
languages = {_CONVERT[language]: _LANGUAGES[_CONVERT[language]]} if language in _CONVERT else _LANGUAGES
5556
for language in languages:
56-
language_issues = select_language_issues(issues, language)
57-
languages[language](language_issues)
58-
Common.push_md_to_storage()
57+
try:
58+
language_issues = select_language_issues(issues, language)
59+
languages[language](language_issues)
60+
except Exception as e:
61+
_LOG.error(f'Error happened during handling {language} issue: {e}')
62+
63+
# output
64+
cmd_list = ['git add -u', 'git commit -m \"update excel\"', 'git push -f origin HEAD']
65+
[sp.call(cmd, shell=True) for cmd in cmd_list]
5966

6067

6168
if __name__ == '__main__':

scripts/release_helper/release_helper.yml

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,18 @@ schedules:
1515
- main
1616
always: true
1717

18+
variables:
19+
- group: Release Secrets for GitHub
20+
- group: SDK Release Helper
21+
- group: Azure SDK Auto Release Pipeline Secrets
22+
23+
1824
jobs:
1925
- job: ReleaseHelper
2026
displayName: ReleaseHelper Python 3.8
2127
timeoutInMinutes: 30
2228
strategy:
23-
maxParallel: 3
29+
maxParallel: 1
2430
pool:
2531
vmImage: 'ubuntu-20.04'
2632
steps:
@@ -44,11 +50,10 @@ jobs:
4450
export PYTHON_BIGCAT_TOKEN=$(Jiefeng-GitToken)
4551
export PYTHON_ZED_TOKEN=$(Zed-GitToken)
4652
export PYTHON_MSYYC_TOKEN=$(Yuchao-GitToken)
47-
export JAVA_WEIDONGXU_TOKEN=$(Weidong-GitToken)
48-
export GO_DAPENGZHANG_TOKEN=$(Dapeng-GitToken)
49-
export JS_QIAOQIAO_TOKEN=$(QiaoQiao-GitToken)
53+
export AZURESDK_BOT_TOKEN=$(azuresdk-github-pat)
5054
export LANGUAGE=$(RUN_LANGUAGE)
5155
56+
5257
# create virtual env
5358
python -m venv venv-sdk
5459
source venv-sdk/bin/activate

scripts/release_helper/utils.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
REST_REPO = 'Azure/azure-rest-api-specs'
88
AUTO_ASSIGN_LABEL = 'assigned'
99
AUTO_PARSE_LABEL = 'auto-link'
10+
MULTI_LINK_LABEL = 'MultiLink'
1011

1112
_LOG = logging.getLogger(__name__)
1213

0 commit comments

Comments
 (0)